git repos / saland

commit a430b23e

Poul Sander · 2026-01-31 19:18
a430b23ec89ad34b1be5414e4f8054ddd94771ce patch · browse files
parent 13ed841708449fea732613023faeaeee00b79aae

Add basic item handling. Started wiht "i".

Changed files

M src/saland/Game.cpp before
A src/saland/GameInventoryState.cpp
A src/saland/GameInventoryState.hpp
M src/saland/globals.hpp before
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<SpellHolder> spell_holder;
std::shared_ptr<Console> console;
std::shared_ptr<GameSpellState> spellSelect;
+ std::shared_ptr<GameInventoryState> 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<std::string> 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<GameSpellState>();
+ data->inventoryState = std::make_shared<GameInventoryState>();
data->spell_holder = std::make_shared<SpellHolder>();
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 <algorithm>
+
+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 <memory>
+#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<GameInventoryStateImpl> 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 */