#include "inventory.hpp" #include #include #include #include "defaults/icons.hpp" #include "map/tile.hpp" #include "defaults/ui.hpp" #include "character/message.hpp" static SDL_Color MENU_HOVER_BORDER_C = Default::menu_hovering_color; static constexpr float INFO_PANEL_WIDTH = 180; static constexpr float MENU_BORDER = 4.f; static SDL_FRect menu_inner = {0,0, 0, 0}; float inventory_grid_ts = 48.f; float inventory_grid_tile_border = 2.f; float inventory_grid_tile_gap = 4.f; float inventory_grid_slot_size = inventory_grid_ts + inventory_grid_tile_border * 2 + inventory_grid_tile_gap; int inventory_grid_cols = 5; int inventory_grid_rows = 5; float build_menu_width = 64.f; int build_menu_rows = 5; bool Inventory::draw_menu = false; bool Inventory::clicked_button_craft = false; int Inventory::selected_hotbar = 0; int Inventory::selected_hotbar_last = 0; Equipement Inventory::hotbar[HOTBAR_SLOTS]; InventoryItem* Inventory::hotbar_hands = nullptr; ItemType Inventory::selected_build_item = ItemType::NONE; InventorySelection* Inventory::selected_item = nullptr; Inventory::Inventory() { std::cout << "CREATE INVENTORY!\n"; selected_hotbar = 0; selected_hotbar_last = 0; draw_menu = false; clicked_button_craft = false; selected_build_item = ItemType::NONE; hotbar_hands = new InventoryItem(0, ItemType::HAND); next_item_id = 1; Inventory::selected_item = nullptr; for (int i = 1; i < HOTBAR_SLOTS; i++) { Inventory::hotbar[i].item_id = 0; Inventory::hotbar[i].type = InventoryObjectType::ITEM; } } Inventory::Inventory(Inventory&&) { std::cout << "[Inventory] Move constructor called!" << std::endl; } // EQUIPEMENT void Inventory::Equip(InventoryItem* item) { equipment[item->equipement_slot].item_id = item->id; equipment[item->equipement_slot].type = InventoryObjectType::ITEM; } void Inventory::Equip(InventoryVegetable* item) { equipment[item->equipement_slot].item_id = item->id; equipment[item->equipement_slot].type = InventoryObjectType::VEGETABLE; } void Inventory::Unequip(EquipmentSlot slot) { equipment.erase(slot); } // VEGETABLE void Inventory::add_Vegetable(VegetableData::Type type, size_t amount, Messages* messages) { auto [it, inserted] = vegetables.try_emplace(type, InventoryVegetable(next_item_id, type, false)); if (inserted) next_item_id++; it->second.count += amount; auto* t = new VegetableData::Type(type); std::cout << "ADD VEGETABLE TYPE: " << static_cast(*t) << std::endl; if (messages) messages->Add(MessageKey::ADD_VEGETABLE, static_cast(t)); } int Inventory::remove_Vegetable(VegetableData::Type type, size_t amount) { auto it = vegetables.find(type); int ret = -1; if (it == vegetables.end() || it->second.count < amount) return ret; it->second.count -= amount; ret = it->second.count; if (it->second.count == 0) { vegetables.erase(it); bool setted_hotbar = set_HotbarItem(Inventory::selected_hotbar, Inventory::hotbar_hands); if (setted_hotbar) Equip(Inventory::hotbar_hands); } return ret; } int Inventory::get_Vegetable(VegetableData::Type type) const { auto it = vegetables.find(type); return (it != vegetables.end()) ? it->second.count : 0; } // SEED void Inventory::add_Seed(VegetableData::Type type, size_t amount, Messages* messages) { auto [it, inserted] = seeds.try_emplace(type, InventoryVegetable(next_item_id, type, true)); if (inserted) next_item_id++; it->second.count += amount; auto* t = new VegetableData::Type(type); std::cout << "ADD VEGETABLE TYPE: " << static_cast(*t) << std::endl; if (messages) messages->Add(MessageKey::ADD_VEGETABLE_SEED, static_cast(t)); } bool Inventory::remove_Seed(VegetableData::Type type, size_t amount) { std::cout << "TSCHÖÖ\n"; auto it = seeds.find(type); if (it == seeds.end() || it->second.count < amount) return false; it->second.count -= amount; if (it->second.count == 0) { seeds.erase(it); bool setted_hotbar = set_HotbarItem(Inventory::selected_hotbar, Inventory::hotbar_hands); if (setted_hotbar) Equip(Inventory::hotbar_hands); } return true; } int Inventory::get_Seed(VegetableData::Type type) const { auto it = seeds.find(type); return (it != seeds.end()) ? it->second.count : 0; } // RESOURCE void Inventory::add_Resource(ResourceType type, int amount, Messages* messages) { if (type == ResourceType::NONE || amount <= 0) return; resources[type] += amount; ResourceType* t = new ResourceType(type); std::cout << "ADD RESOURCE TYPE: "<< static_cast(*t) << std::endl; messages->Add(MessageKey::ADD_RESOURCE, static_cast(t)); } bool Inventory::remove_Resource(ResourceType type, int amount) { auto it = resources.find(type); if (it == resources.end() || it->second < amount) return false; it->second -= amount; if (it->second == 0) resources.erase(it); return true; } int Inventory::get_Resource(ResourceType type) const { auto it = resources.find(type); return (it != resources.end()) ? it->second : 0; } // ITEM InventoryItem* Inventory::add_Item(ItemType item, Messages* messages) { if (item == ItemType::NONE) return nullptr; size_t* id = new size_t(next_item_id++); std::cout << "INV ID: " << *id << " " << next_item_id << std::endl; items.emplace_back(std::make_unique(*id, item)); if (messages) { messages->Add(MessageKey::ADD_INVENTORY, static_cast(id)); } std::cout << "ADD ITEM MESS\n"; std::cout << items.size() << " type: " << Item::to_String(items.back()->type) << std::endl; return items.back().get(); } bool Inventory::remove_Item(ItemType item) { for (auto it = items.begin(); it != items.end(); ++it) { if ((*it)->type == item) { items.erase(it); return true; } } return false; } bool Inventory::has_Item(ItemType item) const { for (auto& it : items) if (it->type == item) return true; return false; } // DRAW void Inventory::draw_BuildMenu(SDL_Renderer* renderer, std::vector researched_items) { const float ts = inventory_grid_ts; const float gap = inventory_grid_tile_gap; const float border = inventory_grid_tile_border; const float column_x = menu_inner.x + menu_inner.w - build_menu_width; const float column_w = build_menu_width; const float column_h = menu_inner.h; SDL_FRect bg{ column_x, menu_inner.y, column_w, column_h }; SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderFillRect(renderer, &bg); const float icon_x = column_x + (column_w - ts) * 0.5f; auto draw_slot = [&](float y, ItemType item) { SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_FRect slot_bg{ icon_x, y, ts, ts }; SDL_RenderFillRect(renderer, &slot_bg); if (item != ItemType::NONE) { auto it = Icon::item_pos.find(item); if (it != Icon::item_pos.end()) { SDL_FRect src{ it->second.x * 16.f, it->second.y * 16.f, 16.f, 16.f }; SDL_FRect dst{ icon_x, y, ts, ts }; SDL_RenderTexture(renderer, Icon::icons_texture, &src, &dst); } } if (item != ItemType::NONE && item == selected_build_item) SDL_SetRenderDrawColor(renderer, 40, 140, 40, 255); else SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_FRect border_rect{ icon_x - border, y - border, ts + border * 2, ts + border * 2 }; SDL_RenderRect(renderer, &border_rect); }; // row 0 draw_slot(menu_inner.y + 0 * (ts + gap), ItemType::BUILDING_TOOL); if (researched_items[static_cast(ItemType::SWORD_WOOD)]) draw_slot(menu_inner.y + 1 * (ts + gap), ItemType::SWORD_WOOD); else draw_slot(menu_inner.y + 1 * (ts + gap), ItemType::NONE); // rows not set jet for (int i = 2; i < build_menu_rows; i++) draw_slot(menu_inner.y + i * (ts + gap), ItemType::NONE); } ItemType Inventory::hit_BuildMenu(const SDL_FPoint& mouse, SDL_Rect, std::vector researched_items) { const float ts = inventory_grid_ts; const float gap = inventory_grid_tile_gap; const float column_x = menu_inner.x + menu_inner.w - build_menu_width; const float column_w = build_menu_width; const float icon_x = column_x + (column_w - ts) * 0.5f; const float icon_x_end = icon_x + ts; if (mouse.x < icon_x || mouse.x > icon_x_end) return ItemType::NONE; float rel_y = mouse.y - menu_inner.y; if (rel_y < 0) return ItemType::NONE; int row = (int)(rel_y / (ts + gap)); if (row < 0 || row >= build_menu_rows) return ItemType::NONE; switch (row) { case 0: return ItemType::BUILDING_TOOL; case 1: if (researched_items[static_cast(ItemType::SWORD_WOOD)]) return ItemType::SWORD_WOOD; return ItemType::NONE; default: return ItemType::NONE; } } bool Inventory::cursor_OverCraftButton(const SDL_FPoint& mouse) { const float panel_w = INFO_PANEL_WIDTH; const float panel_h = menu_inner.h; const float panel_x = menu_inner.x + menu_inner.w + 10.f; const float panel_y = menu_inner.y; const float btn_h = 24.f; const float btn_w = panel_w - 12.f; const float btn_x = panel_x + 6.f; const float btn_y = panel_y + panel_h - btn_h - 6.f; // simple AABB check return mouse.x >= btn_x && mouse.x <= btn_x + btn_w && mouse.y >= btn_y && mouse.y <= btn_y + btn_h; } void Inventory::draw_InfoPanel(SDL_Renderer* renderer, TTF_Font* font, ItemType /*unused*/, bool is_hovering_craftbutton) { const float panel_w = INFO_PANEL_WIDTH; const float panel_h = menu_inner.h; const float panel_x = menu_inner.x + menu_inner.w + 10.f; const float panel_y = menu_inner.y; const float border = 4.f; SDL_SetRenderDrawColor(renderer, 60, 60, 70, 255); SDL_FRect panel_border{ panel_x - border, panel_y - border, panel_w + border * 2.f, panel_h + border * 2.f }; SDL_RenderFillRect(renderer, &panel_border); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_FRect panel_bg{ panel_x, panel_y, panel_w, panel_h }; SDL_RenderFillRect(renderer, &panel_bg); const float line_h = 20.f; const float spacing = 4.f; float y = panel_y + 6.f; auto draw_line = [&](const std::string& text) { SDL_Color white{255,255,255,255}; SDL_Surface* surf = TTF_RenderText_Solid(font, text.c_str(), text.length(), white); if (!surf) return; SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); if (tex) { SDL_FRect dst{ panel_x + 6.f, y, (float)surf->w, (float)surf->h }; SDL_RenderTexture(renderer, tex, nullptr, &dst); SDL_DestroyTexture(tex); } y += line_h + spacing; SDL_DestroySurface(surf); }; auto resource_name = [](ResourceType type) -> const std::string& { size_t idx = static_cast(type); if (idx < ResourceData::names.size()) return ResourceData::names[idx]; return ResourceData::names.back(); }; const ItemCrafting& ci = Item::crafting_item; // ================================================================ // BUILD ITEM SELECTED // ================================================================ if (selected_build_item != ItemType::NONE) { const ItemStats& s = Item::get(selected_build_item); const bool craftable = (s.crafting_duration > 0.f) && (s.cost.size() != 0); draw_line("ITEM:"); draw_line(s.title); y += spacing; if (craftable) { draw_line("COST:"); for (const auto& c : s.cost) { const std::string& name = resource_name(c.type); draw_line(name + ": " + std::to_string(c.amount)); } y += spacing; } draw_line("STATS:"); if (s.base_damage > 0) draw_line("Damage: " + std::to_string(s.base_damage)); if (s.base_armor > 0) draw_line("Armor: " + std::to_string(s.base_armor)); draw_line("Health: " + std::to_string(s.base_health)); if (s.base_speed > 0.f) { std::ostringstream ss; ss << std::fixed << std::setprecision(1) << s.base_speed; draw_line("Speed: " + ss.str()); } if (s.max_level > 0) draw_line("Max Lvl: " + std::to_string(s.max_level)); if (!craftable) return; const float btn_h = 24.f; const float btn_w = panel_w - 12.f; const float btn_x = panel_x + 6.f; const float btn_y = panel_y + panel_h - btn_h - 6.f; SDL_SetRenderDrawColor(renderer, 40, 140, 40, 255); SDL_FRect craft_btn{ btn_x - 1.f, btn_y - 1.f, btn_w + 2.f, btn_h + 2.f }; SDL_RenderFillRect(renderer, &craft_btn); if (is_hovering_craftbutton) SDL_SetRenderDrawColor(renderer, MENU_HOVER_BORDER_C.r, MENU_HOVER_BORDER_C.g, MENU_HOVER_BORDER_C.b, MENU_HOVER_BORDER_C.a); else SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderRect(renderer, &craft_btn); if (ci.is_crafting && ci.stats.type == s.type) { float p = ci.duration / s.crafting_duration; if (p < 0.f) p = 0.f; if (p > 1.f) p = 1.f; SDL_SetRenderDrawColor(renderer, 80, 200, 80, 255); SDL_FRect fill{ btn_x, btn_y, btn_w * p, btn_h }; SDL_RenderFillRect(renderer, &fill); } SDL_Surface* surf = TTF_RenderText_Solid(Ui::font_menu_bold, "Craft", 5, SDL_Color{255,255,255,255}); if (surf) { SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); if (tex) { SDL_FRect dst{ btn_x + btn_w * 0.5f - surf->w * 0.5f, btn_y + btn_h * 0.5f - surf->h * 0.5f, (float)surf->w, (float)surf->h }; SDL_RenderTexture(renderer, tex, nullptr, &dst); SDL_DestroyTexture(tex); } SDL_DestroySurface(surf); } return; } // ================================================================ // IF CRAFTING BUT NOTHING SELECTED -> SHOW CRAFTING ITEM // ================================================================ if (ci.is_crafting) { const ItemStats& s = ci.stats; draw_line("CRAFTING:"); draw_line(s.title); y += spacing; const float btn_h = 24.f; const float btn_w = panel_w - 12.f; const float btn_x = panel_x + 6.f; const float btn_y = panel_y + panel_h - btn_h - 6.f; float p = 0.f; if (s.crafting_duration > 0.f) p = ci.duration / s.crafting_duration; if (p < 0.f) p = 0.f; if (p > 1.f) p = 1.f; SDL_SetRenderDrawColor(renderer, 80, 200, 80, 255); SDL_FRect fill{ btn_x, btn_y, btn_w * p, btn_h }; SDL_RenderFillRect(renderer, &fill); return; } // ================================================================ // INVENTORY SELECTION // ================================================================ if (selected_item && selected_item->is_valid) { draw_line("ITEM:"); if (selected_item->resource != ResourceType::NONE) { draw_line(resource_name(selected_item->resource)); return; } if (selected_item->vegetable != VegetableData::Type::NONE) { const auto& def = VegetableData::crop_defs[static_cast(selected_item->vegetable)]; draw_line(def.name); return; } if (selected_item->seed != VegetableData::Type::NONE) { const auto& def = VegetableData::crop_defs[static_cast(selected_item->seed)]; std::string name = std::string(def.name) + " seed"; draw_line(name); return; } if (selected_item->item) { auto* inv_item = static_cast(selected_item->item); const ItemStats& s = Item::get(inv_item->type); draw_line(s.title); draw_line("STATS:"); if (s.base_damage > 0) draw_line("Damage: " + std::to_string(s.base_damage)); if (s.base_armor > 0) draw_line("Armor: " + std::to_string(s.base_armor)); draw_line("Health: " + std::to_string(s.base_health)); if (s.base_speed > 0.f) draw_line("Speed: " + std::to_string(s.base_speed)); if (s.max_level > 0) draw_line("Max Lvl: " + std::to_string(s.max_level)); return; } } } void Inventory::draw_InventoryGrid(SDL_Renderer* renderer) { auto draw_icon = [&](float x, float y, Icon::TilePos pos, int count, bool highlight) { const float src_ts = 16.f; // border if (highlight) SDL_SetRenderDrawColor(renderer, MENU_HOVER_BORDER_C.r, MENU_HOVER_BORDER_C.g, MENU_HOVER_BORDER_C.b, MENU_HOVER_BORDER_C.a); else SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_FRect b{ x - inventory_grid_tile_border, y - inventory_grid_tile_border, inventory_grid_ts + inventory_grid_tile_border * 2, inventory_grid_ts + inventory_grid_tile_border * 2 }; SDL_RenderFillRect(renderer, &b); // infill SDL_SetRenderDrawColor(renderer, 60, 60, 70, 255); SDL_FRect i{ x, y, inventory_grid_ts, inventory_grid_ts }; SDL_RenderFillRect(renderer, &i); // icon SDL_FRect src{ pos.x * src_ts, pos.y * src_ts, src_ts, src_ts }; SDL_FRect dst{ x, y, inventory_grid_ts, inventory_grid_ts }; SDL_RenderTexture(renderer, Icon::icons_texture, &src, &dst); // text if (count > 0) { SDL_Color white{255,255,255,255}; std::string t = std::to_string(count); SDL_Surface* surf = TTF_RenderText_Solid(Ui::font_tiny, t.c_str(), t.length(), white); if (!surf) return; SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); if (tex) { SDL_FRect r{ x + inventory_grid_ts - surf->w, y + inventory_grid_ts - surf->h, (float)surf->w, (float)surf->h }; SDL_RenderTexture(renderer, tex, nullptr, &r); SDL_DestroyTexture(tex); } SDL_DestroySurface(surf); } }; float inv_left = menu_inner.x; float inv_top = menu_inner.y; auto get_tile_position = [&](int idx) { int col = idx % inventory_grid_cols; int row = idx / inventory_grid_cols; float x = inv_left + col * inventory_grid_slot_size + inventory_grid_tile_border; float y = inv_top + row * inventory_grid_slot_size + inventory_grid_tile_border; return std::pair(x,y); }; int index = 0; // RESOURCE for (auto& pair : resources) { if (pair.second <= 0) continue; //std::cout << "ITM: " << (int)pair.first << " " << pair.second << std::endl; if (index >= inventory_grid_cols * inventory_grid_rows) break; auto it = Icon::resource_pos.find(pair.first); if (it == Icon::resource_pos.end()) continue; auto pos = get_tile_position(index); bool highlight = (selected_inventory.is_valid && selected_inventory.resource == pair.first); draw_icon(pos.first, pos.second, it->second, pair.second, highlight); index++; } // VEGETABLE for (auto& pair : vegetables) { if (pair.second.count <= 0) continue; //std::cout << "ITM: " << (int)pair.first << " " << pair.second << std::endl; if (index >= inventory_grid_cols * inventory_grid_rows) break; VegetableData::Type type = pair.first; const VegetableData::CropDefinition* data = VegetableData::get_DataByType(type); auto pos = get_tile_position(index); bool highlight = (selected_inventory.is_valid && selected_inventory.vegetable == pair.first); //std::cout << "DAT: " <icon_tile_x, data->icon_tile_y}, pair.second.count, highlight); index++; } // SEEDS for (auto& pair : seeds) { if (pair.second.count <= 0) continue; //std::cout << "ITM: " << (int)pair.first << " " << pair.second << std::endl; if (index >= inventory_grid_cols * inventory_grid_rows) break; VegetableData::Type type = pair.first; const VegetableData::CropDefinition* data = VegetableData::get_DataByType(type); auto pos = get_tile_position(index); bool highlight = (selected_inventory.is_valid && selected_inventory.seed == pair.first); //std::cout << "DAT: " <icon_tile_x-1, data->icon_tile_y}, pair.second.count, highlight); index++; } // ITEM -> TOOL/WEAPON for (auto& inv_item : items) { if (index >= inventory_grid_cols * inventory_grid_rows) break; auto it = Icon::item_pos.find(inv_item->type); if (it == Icon::item_pos.end()) continue; auto pos = get_tile_position(index); bool highlight = (selected_inventory.item && selected_inventory.is_valid && selected_inventory.item->id == inv_item->id); draw_icon(pos.first, pos.second, it->second, 1, highlight); index++; } } InventorySelection Inventory::hit_InventoryGrid(const SDL_FPoint& mouse) { InventorySelection sel; float inv_left = menu_inner.x; float inv_top = menu_inner.y; int index = 0; auto rect_for_slot = [&](int idx) { int col = idx % inventory_grid_cols; int row = idx / inventory_grid_cols; float x = inv_left + col * inventory_grid_slot_size + inventory_grid_tile_border; float y = inv_top + row * inventory_grid_slot_size + inventory_grid_tile_border; return SDL_FRect{ x, y, inventory_grid_ts, inventory_grid_ts }; }; for (auto& pair : resources) { if (pair.second <= 0) continue; if (index >= inventory_grid_cols * inventory_grid_rows) break; auto it = Icon::resource_pos.find(pair.first); if (it == Icon::resource_pos.end()) continue; SDL_FRect r = rect_for_slot(index); if (mouse.x >= r.x && mouse.x <= r.x + r.w && mouse.y >= r.y && mouse.y <= r.y + r.h) { sel.is_valid = true; sel.resource = pair.first; sel.vegetable = VegetableData::Type::NONE; sel.item = nullptr; sel.tile = it->second; return sel; } index++; } for (auto& pair : vegetables) { if (pair.second.count <= 0) continue; if (index >= inventory_grid_cols * inventory_grid_rows) break; VegetableData::Type type = pair.first; const VegetableData::CropDefinition* data = VegetableData::get_DataByType(type); SDL_FRect r = rect_for_slot(index); if (mouse.x >= r.x && mouse.x <= r.x + r.w && mouse.y >= r.y && mouse.y <= r.y + r.h) { sel.is_valid = true; sel.vegetable = type; sel.resource = ResourceType::NONE; sel.item = nullptr; sel.tile = {data->icon_tile_x, data->icon_tile_y}; return sel; } index++; } for (auto& pair : seeds) { if (pair.second.count <= 0) continue; if (index >= inventory_grid_cols * inventory_grid_rows) break; VegetableData::Type type = pair.first; const VegetableData::CropDefinition* data = VegetableData::get_DataByType(type); SDL_FRect r = rect_for_slot(index); if (mouse.x >= r.x && mouse.x <= r.x + r.w && mouse.y >= r.y && mouse.y <= r.y + r.h) { sel.is_valid = true; sel.seed = type; sel.vegetable = VegetableData::Type::NONE; sel.resource = ResourceType::NONE; sel.item = nullptr; sel.tile = {data->icon_tile_x, data->icon_tile_y}; return sel; } index++; } for (auto &inv_item : items) { if (index >= inventory_grid_cols * inventory_grid_rows) break; auto it = Icon::item_pos.find(inv_item->type); if (it == Icon::item_pos.end()) continue; SDL_FRect r = rect_for_slot(index); if (mouse.x >= r.x && mouse.x <= r.x + r.w && mouse.y >= r.y && mouse.y <= r.y + r.h) { sel.is_valid = true; sel.item = inv_item.get(); sel.resource = ResourceType::NONE; sel.vegetable = VegetableData::Type::NONE; sel.tile = it->second; return sel; } index++; } return sel; } bool Inventory::cursor_OverInventoryGrid(const SDL_FPoint& mouse) { const float grid_x = menu_inner.x; const float grid_y = menu_inner.y; const float grid_w = inventory_grid_cols * inventory_grid_slot_size; const float grid_h = inventory_grid_rows * inventory_grid_slot_size; return mouse.x >= grid_x && mouse.x < grid_x + grid_w && mouse.y >= grid_y && mouse.y < grid_y + grid_h; } bool Inventory::cursor_OverBuildMenu(const SDL_FPoint& mouse) { const float column_x = menu_inner.x + menu_inner.w - build_menu_width; const float column_w = build_menu_width; return mouse.x >= column_x && mouse.x <= column_x + column_w && mouse.y >= menu_inner.y && mouse.y <= menu_inner.y + menu_inner.h; } bool Inventory::cursor_OverInfoPanel(const SDL_FPoint& mouse) { const float panel_x = menu_inner.x + menu_inner.w + 10.f; const float panel_w = INFO_PANEL_WIDTH; return mouse.x >= panel_x && mouse.x <= panel_x + panel_w && mouse.y >= menu_inner.y && mouse.y <= menu_inner.y + menu_inner.h; } void Inventory::draw_Menu(SDL_Renderer* renderer, SDL_Rect win_size, TTF_Font* font, std::vector researched_items, bool is_hovering_craftbutton) { // real grid sizes float grid_w = inventory_grid_cols * inventory_grid_slot_size; float grid_h = inventory_grid_rows * inventory_grid_slot_size; // inner area: grid + build menu on the right float inner_w = grid_w + build_menu_width; float inner_h = grid_h + 40.f; float total_w = inner_w + MENU_BORDER * 2; float total_h = inner_h + MENU_BORDER * 2; float start_x = win_size.w * 0.5f - total_w * 0.5f; float start_y = win_size.h * 0.5f - total_h * 0.5f; SDL_FRect outer{ start_x, start_y, total_w, total_h }; menu_inner = { start_x + MENU_BORDER, start_y + MENU_BORDER, inner_w, inner_h }; SDL_SetRenderDrawColor(renderer, 60, 60, 70, 255); SDL_RenderFillRect(renderer, &outer); SDL_SetRenderDrawColor(renderer, 120, 120, 140, 255); SDL_RenderFillRect(renderer, &menu_inner); draw_InventoryGrid(renderer); draw_BuildMenu(renderer, researched_items); draw_InfoPanel(renderer, font, selected_build_item, is_hovering_craftbutton); } void Inventory::draw_Hotbar(SDL_Renderer* renderer, TTF_Font* font) { const float slot_size = 48.f; const float spacing = 4.f; const float start_x = 20.f; const float start_y = 20.f; SDL_Color number_color = {255, 255, 255, 255}; // white numbers for (int i = 1; i < HOTBAR_SLOTS; ++i) { float x = start_x + i * (slot_size + spacing); float y = start_y; // Slot background SDL_FRect bg{ x, y, slot_size, slot_size }; SDL_SetRenderDrawColor(renderer, 50, 50, 60, 255); SDL_RenderFillRect(renderer, &bg); // Border if (i == selected_hotbar) SDL_SetRenderDrawColor(renderer, 255, 255, 100, 255); else SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderRect(renderer, &bg); // Draw item icon if exists if (hotbar[i].item_id != 0) { if (hotbar[i].type == InventoryObjectType::ITEM) { ItemType item = get_ItemById(hotbar[i].item_id)->type; if (item != ItemType::NONE) { auto it = Icon::item_pos.find(item); if (it != Icon::item_pos.end()) { SDL_FRect src{ it->second.x * 16.f, it->second.y * 16.f, 16.f, 16.f }; SDL_FRect dst{ x, y, slot_size, slot_size }; SDL_RenderTexture(renderer, Icon::icons_texture, &src, &dst); } } } else if (hotbar[i].type == InventoryObjectType::VEGETABLE) { InventoryVegetable& item = get_VegetableById(hotbar[i].item_id); const VegetableData::CropDefinition* data = get_DataByType(item.type); SDL_FRect src{ data->icon_tile_x * 16.f, data->icon_tile_y * 16.f, 16.f, 16.f }; SDL_FRect dst{ x, y, slot_size, slot_size }; SDL_RenderTexture(renderer, Icon::icons_texture, &src, &dst); } else if (hotbar[i].type == InventoryObjectType::SEED) { InventoryVegetable& item = get_SeedById(hotbar[i].item_id); const VegetableData::CropDefinition* data = get_DataByType(item.type); SDL_FRect src{ (data->icon_tile_x-1) * 16.f, data->icon_tile_y * 16.f, 16.f, 16.f }; SDL_FRect dst{ x, y, slot_size, slot_size }; SDL_RenderTexture(renderer, Icon::icons_texture, &src, &dst); } } // Draw slot number in bottom-right corner std::string number = std::to_string(i); SDL_Surface* surf = TTF_RenderText_Solid(font, number.c_str(), number.length(), number_color); if (surf) { SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); if (tex) { SDL_FRect dst{ x + slot_size - surf->w - 2.f, y + slot_size - surf->h - 2.f, (float)surf->w, (float)surf->h }; SDL_RenderTexture(renderer, tex, nullptr, &dst); SDL_DestroyTexture(tex); } SDL_DestroySurface(surf); } } } bool Inventory::set_HotbarItem(int slot, Inventory_s* item) { if (slot < 0 || slot >= HOTBAR_SLOTS || !item) return false; hotbar[slot].item_id = item->id; selected_hotbar_last = selected_hotbar; hotbar[slot].type = item->object_type; return true; } Inventory_s* Inventory::get_SelectedHotbarItem() { Equipement eq = hotbar[selected_hotbar]; Inventory_s* item = nullptr; if (eq.type == InventoryObjectType::ITEM) item = get_ItemById(hotbar[selected_hotbar].item_id); else if (eq.type == InventoryObjectType::VEGETABLE) item = &get_VegetableById(hotbar[selected_hotbar].item_id); else if (eq.type == InventoryObjectType::SEED) item = &get_SeedById(hotbar[selected_hotbar].item_id); if (selected_hotbar == 0 || !item) { // std::cout << "Something is broken,... " << selected_hotbar << " " << hotbar[selected_hotbar].item_id << std::endl; return hotbar_hands; } return item; } InventoryItem* Inventory::get_ItemById(size_t id) { for (auto &item : items) if (item->id == id) return item.get(); return nullptr; } InventoryVegetable& Inventory::get_VegetableById(size_t id) { for (auto &item : vegetables) if (item.second.id == id) return item.second; throw std::out_of_range("Vegetable not found"); } InventoryVegetable& Inventory::get_SeedById(size_t id) { for (auto &item : seeds) if (item.second.id == id) return item.second; throw std::out_of_range("Vegetable not found"); } static void WriteItem(std::ofstream &file, const InventoryItem &it) { file.write(reinterpret_cast(&it.object_type), sizeof(it.object_type)); file.write(reinterpret_cast(&it.id), sizeof(it.id)); file.write(reinterpret_cast(&it.equipement_slot), sizeof(it.equipement_slot)); file.write(reinterpret_cast(&it.type), sizeof(it.type)); file.write(reinterpret_cast(&it.health_current), sizeof(it.health_current)); file.write(reinterpret_cast(&it.health_max), sizeof(it.health_max)); file.write(reinterpret_cast(&it.speed), sizeof(it.speed)); file.write(reinterpret_cast(&it.level), sizeof(it.level)); } static void WriteVegetable(std::ofstream &file, const InventoryVegetable &veg) { file.write(reinterpret_cast(&veg.object_type), sizeof(veg.object_type)); file.write(reinterpret_cast(&veg.id), sizeof(veg.id)); file.write(reinterpret_cast(&veg.equipement_slot), sizeof(veg.equipement_slot)); file.write(reinterpret_cast(&veg.type), sizeof(veg.type)); file.write(reinterpret_cast(&veg.count), sizeof(veg.count)); } static void WriteSeed(std::ofstream &file, const InventoryVegetable &veg) { file.write(reinterpret_cast(&veg.object_type), sizeof(veg.object_type)); file.write(reinterpret_cast(&veg.id), sizeof(veg.id)); file.write(reinterpret_cast(&veg.equipement_slot), sizeof(veg.equipement_slot)); file.write(reinterpret_cast(&veg.type), sizeof(veg.type)); file.write(reinterpret_cast(&veg.count), sizeof(veg.count)); } bool Inventory::Save(const std::string &path) const { std::ofstream file(path + "/inventory", std::ios::binary | std::ios::trunc); std::cout << "Save Inventory\n"; if (!file) { std::cout << "No file found at: " << path + "/inventory" << std::endl; return false; } // resources { uint32_t count = resources.size(); file.write(reinterpret_cast(&count), sizeof(count)); for (const auto &kv : resources) { file.write(reinterpret_cast(&kv.first), sizeof(kv.first)); file.write(reinterpret_cast(&kv.second), sizeof(kv.second)); } } // items { uint32_t count = items.size(); std::cout << "ITEM COUNT WRITE: " << count << std::endl; file.write(reinterpret_cast(&count), sizeof(count)); for (const auto &item : items) WriteItem(file, *item); // vegetable count = vegetables.size(); std::cout << "VEGETABLE COUNT WRITE: " << count << std::endl; file.write(reinterpret_cast(&count), sizeof(count)); for (const auto &pair : vegetables) WriteVegetable(file, pair.second); // seed count = seeds.size(); std::cout << "Seeds COUNT WRITE: " << count << std::endl; file.write(reinterpret_cast(&count), sizeof(count)); for (const auto &pair : seeds) WriteSeed(file, pair.second); } // equipment slot item_id { uint32_t count = equipment.size(); file.write(reinterpret_cast(&count), sizeof(count)); for (const auto &kv : equipment) { EquipmentSlot slot = kv.first; size_t id = kv.second.item_id; std::cout << "EQ " << id << std::endl; file.write(reinterpret_cast(&slot), sizeof(slot)); file.write(reinterpret_cast(&id), sizeof(id)); } } // hotbar for (int i = 0; i < HOTBAR_SLOTS; ++i) { std::cout << "WRITE " << hotbar[i].item_id << std::endl; file.write(reinterpret_cast(&hotbar[i].item_id), sizeof(hotbar[i].item_id)); file.write(reinterpret_cast(&hotbar[i].type), sizeof(hotbar[i].type)); } // selected hotbars std::cout << "WRITE SEL HOTBAR " << selected_hotbar << std::endl; file.write(reinterpret_cast(&selected_hotbar), sizeof(selected_hotbar)); file.write(reinterpret_cast(&selected_hotbar_last), sizeof(selected_hotbar_last)); return file.good(); } static void ReadBase(std::ifstream &file, Inventory_s *obj) { // file.read(reinterpret_cast(&obj->object_type), sizeof(obj->object_type)); // remove file.read(reinterpret_cast(&obj->id), sizeof(obj->id)); file.read(reinterpret_cast(&obj->equipement_slot), sizeof(obj->equipement_slot)); } static InventoryItem* ReadItem(std::ifstream &file) { auto *it = new InventoryItem(0, ItemType::HAND); ReadBase(file, it); file.read(reinterpret_cast(&it->type), sizeof(it->type)); file.read(reinterpret_cast(&it->health_current), sizeof(it->health_current)); file.read(reinterpret_cast(&it->health_max), sizeof(it->health_max)); file.read(reinterpret_cast(&it->speed), sizeof(it->speed)); file.read(reinterpret_cast(&it->level), sizeof(it->level)); return it; } static InventoryVegetable* ReadVegetable(std::ifstream &file) { auto *veg = new InventoryVegetable(0, VegetableData::Type::NONE, false); ReadBase(file, veg); file.read(reinterpret_cast(&veg->type), sizeof(veg->type)); file.read(reinterpret_cast(&veg->count), sizeof(veg->count)); return veg; } static InventoryVegetable* ReadSeed(std::ifstream &file) { auto *veg = new InventoryVegetable(0, VegetableData::Type::NONE, true); ReadBase(file, veg); file.read(reinterpret_cast(&veg->type), sizeof(veg->type)); file.read(reinterpret_cast(&veg->count), sizeof(veg->count)); return veg; } static Inventory_s* ReadObject(std::ifstream &file) { InventoryObjectType ot; file.read(reinterpret_cast(&ot), sizeof(ot)); switch (ot) { case InventoryObjectType::ITEM: { auto* it = ReadItem(file); it->object_type = ot; return it; } case InventoryObjectType::VEGETABLE: { auto* veg = ReadVegetable(file); veg->object_type = ot; return veg; } case InventoryObjectType::SEED: { auto* veg = ReadSeed(file); veg->object_type = ot; return veg; } default: return nullptr; } } bool Inventory::Load(const std::string &path) { std::cout << "Load Inventory\n"; std::ifstream file(path + "/inventory", std::ios::binary); if (!file) { std::cout << "No file found at: " << path + "/inventory" << std::endl; return false; } resources.clear(); items.clear(); vegetables.clear(); equipment.clear(); // resources { uint32_t count = 0; file.read(reinterpret_cast(&count), sizeof(count)); for (uint32_t i = 0; i < count; ++i) { ResourceType key; int value; file.read(reinterpret_cast(&key), sizeof(key)); file.read(reinterpret_cast(&value), sizeof(value)); resources[key] = value; std::cout << "RES SIZ " << resources.size() << std::endl; } } // items { uint32_t count = 0; file.read(reinterpret_cast(&count), sizeof(count)); next_item_id = 0; for (uint32_t i = 0; i < count; ++i) { Inventory_s *obj = ReadObject(file); if (obj->id > next_item_id) next_item_id = obj->id; items.push_back(std::unique_ptr( static_cast(obj) )); std::cout << "ITM SIZ " << items.size() << std::endl; } count = 0; file.read(reinterpret_cast(&count), sizeof(count)); for (uint32_t i = 0; i < count; ++i) { Inventory_s *obj = ReadObject(file); InventoryVegetable* veg = static_cast(obj); vegetables.try_emplace(veg->type, *veg); if (veg->id > next_item_id) next_item_id = veg->id; std::cout << "VEG SIZ " << vegetables.size() << std::endl; // we no longer need the allocated pointer after copying delete veg; } count = 0; file.read(reinterpret_cast(&count), sizeof(count)); for (uint32_t i = 0; i < count; ++i) { Inventory_s *obj = ReadObject(file); InventoryVegetable* veg = static_cast(obj); seeds.try_emplace(veg->type, *veg); if (veg->id > next_item_id) next_item_id = veg->id; std::cout << "SEED SIZ " << seeds.size() << std::endl; // we no longer need the allocated pointer after copying delete veg; } next_item_id++; } // rebuild equipment map using item IDs { uint32_t count = 0; file.read(reinterpret_cast(&count), sizeof(count)); for (uint32_t i = 0; i < count; ++i) { EquipmentSlot slot; size_t id; file.read(reinterpret_cast(&slot), sizeof(slot)); file.read(reinterpret_cast(&id), sizeof(id)); std::cout << "EQ " << id << std::endl; equipment[slot].item_id = id; } } // hotbar for (int i = 0; i < HOTBAR_SLOTS; ++i) { file.read(reinterpret_cast(&hotbar[i].item_id), sizeof(hotbar[i].item_id)); //std::cout << "READ " << hotbar[i].item_id << std::endl; file.read(reinterpret_cast(&hotbar[i].type), sizeof(hotbar[i].type)); } // selected hotbar file.read(reinterpret_cast(&selected_hotbar), sizeof(selected_hotbar)); std::cout << "READ SEL HOTBAR " << selected_hotbar << std::endl; file.read(reinterpret_cast(&selected_hotbar_last), sizeof(selected_hotbar_last)); return file.good(); }