diff --git a/src/saland/Game.cpp b/src/saland/Game.cpp index 0bf1d99..c40e582 100644 --- a/src/saland/Game.cpp +++ b/src/saland/Game.cpp @@ -29,6 +29,7 @@ https://github.com/sago007/saland #include "Game.hpp" #include "GameShop.hpp" #include "GameItems.hpp" +#include "GameInventoryState.hpp" #include "model/World.hpp" #include "../sagotmx/tmx_struct.h" #include "../sago/SagoMisc.hpp" @@ -203,6 +204,7 @@ struct Game::GameImpl { std::shared_ptr spell_holder; std::shared_ptr console; std::shared_ptr spellSelect; + std::shared_ptr inventoryState; bool consoleActive = false; bool debugMenuActive = false; int brushSize = 1; // Size of brush for tile placement/removal (1-5) @@ -227,31 +229,59 @@ static SpawnPoint GetSpawnpoint(const sago::tiled::TileMap& tm) { void ApplyEquippedItems(Player& player) { player.visible_bottom = ""; player.visible_top = ""; + + // Track which items are actually being used for each slot + std::string active_upper_item = ""; + std::string active_lower_item = ""; + std::vector items_to_keep; + + // First pass: find the last valid item for each slot for (const std::string& itemName : player.equipped_items) { auto it = player.item_inventory.find(itemName); - if (it != player.item_inventory.end()) { - // Item is in inventory, apply its effects - if (itemExists(itemName)) { - const ItemDef& item = getItem(itemName); - // Check if player race matches armor restrictions - bool race_allowed = item.armor_restriction.empty(); - for (const std::string& allowed_race : item.armor_restriction) { - if (allowed_race == player.race) { - race_allowed = true; - break; - } + if (it != player.item_inventory.end() && itemExists(itemName)) { + const ItemDef& item = getItem(itemName); + // Check if player race matches armor restrictions + bool race_allowed = item.armor_restriction.empty(); + for (const std::string& allowed_race : item.armor_restriction) { + if (allowed_race == player.race) { + race_allowed = true; + break; } - if (race_allowed) { - if (!item.armor_upper_body.empty()) { - player.visible_top = item.armor_upper_body; - } - if (!item.armor_lower_body.empty()) { - player.visible_bottom = item.armor_lower_body; - } + } + if (race_allowed) { + // Track the last item for each slot + if (!item.armor_upper_body.empty()) { + active_upper_item = itemName; + } + if (!item.armor_lower_body.empty()) { + active_lower_item = itemName; } } } } + + // Second pass: rebuild equipped_items with only one item per slot + for (const std::string& itemName : player.equipped_items) { + if (itemName == active_upper_item || itemName == active_lower_item) { + // Only keep this item if we haven't already added it + if (std::find(items_to_keep.begin(), items_to_keep.end(), itemName) == items_to_keep.end()) { + items_to_keep.push_back(itemName); + } + } + } + + // Update the equipped items list + player.equipped_items = items_to_keep; + + // Apply visual effects + if (!active_upper_item.empty() && itemExists(active_upper_item)) { + const ItemDef& item = getItem(active_upper_item); + player.visible_top = item.armor_upper_body; + } + if (!active_lower_item.empty() && itemExists(active_lower_item)) { + const ItemDef& item = getItem(active_lower_item); + player.visible_bottom = item.armor_lower_body; + } } void PlayerSave() { @@ -320,6 +350,7 @@ Game::Game() { data->middleField.SetHolder(globalData.dataHolder); data->middleField.SetFontSize(20); data->spellSelect = std::make_shared(); + data->inventoryState = std::make_shared(); data->spell_holder = std::make_shared(); data->spellSelect->spell_holder = data->spell_holder; data->spellSelect->tm = &data->gameRegion.world.tm; @@ -475,6 +506,7 @@ void Game::Draw(SDL_Renderer* target) { data->middleField.Draw(target, screen_width/2, screen_height/4, sago::SagoTextField::Alignment::center, sago::SagoTextField::VerticalAlignment::bottom, &globalData.logicalResize); data->spellSelect->Draw(target); + data->inventoryState->Draw(target); // Draw black bars where globalData.logicalResize suggests that the physical area ends. @@ -573,6 +605,10 @@ void Game::ProcessInput(const SDL_Event& event, bool& processed) { if (processed) { return; } + data->inventoryState->ProcessInput(event, processed); + if (processed) { + return; + } if (event.type == SDL_KEYDOWN) { if (event.key.keysym.sym == SDLK_1 || event.key.keysym.sym == SDLK_KP_1) { data->spell_holder->slot_selected = 0; diff --git a/src/saland/GameInventoryState.cpp b/src/saland/GameInventoryState.cpp new file mode 100644 index 0000000..1b4818a --- /dev/null +++ b/src/saland/GameInventoryState.cpp @@ -0,0 +1,230 @@ +/* +=========================================================================== + * Saland Adventures +Copyright (C) 2014-2026 Poul Sander + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/ + +Source information and contacts persons can be found at +https://github.com/sago007/saland +=========================================================================== +*/ + +#include "GameInventoryState.hpp" +#include "GameItems.hpp" +#include "globals.hpp" +#include "../global.hpp" +#include "imgui.h" +#include + +struct GameInventoryState::GameInventoryStateImpl { + bool active = false; + int current_tab = 0; +}; + +GameInventoryState::GameInventoryState() { + data.reset(new GameInventoryState::GameInventoryStateImpl()); +} + +GameInventoryState::~GameInventoryState() { +} + +void GameInventoryState::Activate() { + data->active = true; +} + +void GameInventoryState::Deactivate() { + data->active = false; +} + +bool GameInventoryState::IsActive() { + return data->active; +} + +void GameInventoryState::Draw(SDL_Renderer*) { + if (!data->active) { + return; + } + + ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver); + + if (ImGui::Begin("Inventory", &data->active)) { + if (ImGui::BeginTabBar("InventoryTabs")) { + // Tab 1: All Items + if (ImGui::BeginTabItem("All Items")) { + data->current_tab = 0; + + ImGui::Text("Your Items:"); + ImGui::Separator(); + + if (ImGui::BeginChild("ItemsList", ImVec2(0, 0), true)) { + for (const auto& item : globalData.player.item_inventory) { + ImGui::Text("%s: %d", item.first.c_str(), item.second); + } + if (globalData.player.item_inventory.empty()) { + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "No items in inventory"); + } + } + ImGui::EndChild(); + + ImGui::EndTabItem(); + } + + // Tab 2: Equipment + if (ImGui::BeginTabItem("Equipment")) { + data->current_tab = 1; + ImGui::Text("Equipped Items:"); + ImGui::Separator(); + // Display currently equipped items + ImGui::Text("Currently Equipped:"); + for (const std::string& equipped : globalData.player.equipped_items) { + ImGui::BulletText("%s", equipped.c_str()); + } + if (globalData.player.equipped_items.empty()) { + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Nothing equipped"); + } + ImGui::Separator(); + ImGui::Spacing(); + // Upper body section + ImGui::Text("Upper Body:"); + std::string current_top = ""; + for (const std::string& equipped : globalData.player.equipped_items) { + if (itemExists(equipped)) { + const ItemDef& item = getItem(equipped); + if (!item.armor_upper_body.empty()) { + current_top = equipped; + break; + } + } + } + if (!current_top.empty()) { + ImGui::Text("Equipped: %s", current_top.c_str()); + if (ImGui::Button("Unequip##top")) { + auto it = std::find(globalData.player.equipped_items.begin(), + globalData.player.equipped_items.end(), current_top); + if (it != globalData.player.equipped_items.end()) { + globalData.player.equipped_items.erase(it); + ApplyEquippedItems(globalData.player); + PlayerSave(); + } + } + } else { + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Nothing equipped"); + } + ImGui::Text("Available:"); + for (const auto& item : globalData.player.item_inventory) { + if (itemExists(item.first)) { + const ItemDef& itemDef = getItem(item.first); + if (!itemDef.armor_upper_body.empty()) { + bool already_equipped = (item.first == current_top); + if (!already_equipped) { + if (ImGui::Button(("Equip " + item.first).c_str())) { + // Remove any currently equipped upper body item + if (!current_top.empty()) { + auto it = std::find(globalData.player.equipped_items.begin(), + globalData.player.equipped_items.end(), current_top); + if (it != globalData.player.equipped_items.end()) { + globalData.player.equipped_items.erase(it); + } + } + // Equip the new item + globalData.player.equipped_items.push_back(item.first); + ApplyEquippedItems(globalData.player); + PlayerSave(); + } + } + } + } + } + + ImGui::Separator(); + ImGui::Spacing(); + + // Lower body section + ImGui::Text("Lower Body:"); + std::string current_bottom = ""; + for (const std::string& equipped : globalData.player.equipped_items) { + if (itemExists(equipped)) { + const ItemDef& item = getItem(equipped); + if (!item.armor_lower_body.empty()) { + current_bottom = equipped; + break; + } + } + } + + if (!current_bottom.empty()) { + ImGui::Text("Equipped: %s", current_bottom.c_str()); + if (ImGui::Button("Unequip##bottom")) { + auto it = std::find(globalData.player.equipped_items.begin(), + globalData.player.equipped_items.end(), current_bottom); + if (it != globalData.player.equipped_items.end()) { + globalData.player.equipped_items.erase(it); + ApplyEquippedItems(globalData.player); + PlayerSave(); + } + } + } else { + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Nothing equipped"); + } + + ImGui::Text("Available:"); + for (const auto& item : globalData.player.item_inventory) { + if (itemExists(item.first)) { + const ItemDef& itemDef = getItem(item.first); + if (!itemDef.armor_lower_body.empty()) { + bool already_equipped = (item.first == current_bottom); + if (!already_equipped) { + if (ImGui::Button(("Equip " + item.first).c_str())) { + // Remove any currently equipped lower body item + if (!current_bottom.empty()) { + auto it = std::find(globalData.player.equipped_items.begin(), + globalData.player.equipped_items.end(), current_bottom); + if (it != globalData.player.equipped_items.end()) { + globalData.player.equipped_items.erase(it); + } + } + // Equip the new item + globalData.player.equipped_items.push_back(item.first); + ApplyEquippedItems(globalData.player); + PlayerSave(); + } + } + } + } + } + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } + } + ImGui::End(); +} + +void GameInventoryState::ProcessInput(const SDL_Event& event, bool& processed) { + if (event.type == SDL_KEYDOWN) { + if (event.key.keysym.sym == SDLK_i) { + data->active = !data->active; + processed = true; + } + if (event.key.keysym.sym == SDLK_ESCAPE && data->active) { + data->active = false; + processed = true; + } + } +} + +void GameInventoryState::Update() { +} diff --git a/src/saland/GameInventoryState.hpp b/src/saland/GameInventoryState.hpp new file mode 100644 index 0000000..4c96e30 --- /dev/null +++ b/src/saland/GameInventoryState.hpp @@ -0,0 +1,45 @@ +/* +=========================================================================== + * Saland Adventures +Copyright (C) 2014-2026 Poul Sander + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/ + +Source information and contacts persons can be found at +https://github.com/sago007/saland +=========================================================================== +*/ + +#ifndef GAME_INVENTORY_STATE_HPP +#define GAME_INVENTORY_STATE_HPP + +#include +#include "../sago/GameStateInterface.hpp" + +class GameInventoryState : public sago::GameStateInterface { +public: + GameInventoryState(); + virtual bool IsActive() override; + virtual void Draw(SDL_Renderer* target) override; + virtual void ProcessInput(const SDL_Event& event, bool& processed) override; + virtual void Update() override; + virtual ~GameInventoryState(); + void Activate(); + void Deactivate(); +private: + struct GameInventoryStateImpl; + std::unique_ptr data; +}; + +#endif /* GAME_INVENTORY_STATE_HPP */ diff --git a/src/saland/globals.hpp b/src/saland/globals.hpp index 557f731..c9ce1f3 100644 --- a/src/saland/globals.hpp +++ b/src/saland/globals.hpp @@ -79,5 +79,6 @@ extern GlobalData globalData; // Player save/load functions void PlayerSave(); void PlayerLoad(); +void ApplyEquippedItems(Player& player); #endif /* GLOBALS_HPP */