mapcreator git · master
SDL3 2.5D game and engine using assets, with map editor
C++ 99.3%git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gzsrc/inventory/inventory.cpp raw
#include "inventory.hpp"
#include <string>
#include <sstream>
#include <iomanip>
#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<int>(*t) << std::endl;
if (messages)
messages->Add(MessageKey::ADD_VEGETABLE, static_cast<void*>(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<int>(*t) << std::endl;
if (messages)
messages->Add(MessageKey::ADD_VEGETABLE_SEED, static_cast<void*>(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<int>(*t) << std::endl;
messages->Add(MessageKey::ADD_RESOURCE, static_cast<void*>(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<InventoryItem>(*id, item));
if (messages) {
messages->Add(MessageKey::ADD_INVENTORY, static_cast<void*>(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<bool> 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<int>(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<bool> 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<size_t>(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<size_t>(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<uint32_t>(selected_item->vegetable)];
draw_line(def.name);
return;
}
if (selected_item->seed != VegetableData::Type::NONE)
{
const auto& def =
VegetableData::crop_defs[static_cast<uint32_t>(selected_item->seed)];
std::string name = std::string(def.name) + " seed";
draw_line(name);
return;
}
if (selected_item->item)
{
auto* inv_item =
static_cast<InventoryItem*>(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<float,float>(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: " <<data.icon_tile_x << " " << data.icon_tile_y << std::endl;
draw_icon(pos.first, pos.second, {data->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: " <<data.icon_tile_x << " " << data.icon_tile_y << std::endl;
draw_icon(pos.first, pos.second, {data->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<bool> 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<const char*>(&it.object_type), sizeof(it.object_type));
file.write(reinterpret_cast<const char*>(&it.id), sizeof(it.id));
file.write(reinterpret_cast<const char*>(&it.equipement_slot), sizeof(it.equipement_slot));
file.write(reinterpret_cast<const char*>(&it.type), sizeof(it.type));
file.write(reinterpret_cast<const char*>(&it.health_current), sizeof(it.health_current));
file.write(reinterpret_cast<const char*>(&it.health_max), sizeof(it.health_max));
file.write(reinterpret_cast<const char*>(&it.speed), sizeof(it.speed));
file.write(reinterpret_cast<const char*>(&it.level), sizeof(it.level));
}
static void WriteVegetable(std::ofstream &file, const InventoryVegetable &veg) {
file.write(reinterpret_cast<const char*>(&veg.object_type), sizeof(veg.object_type));
file.write(reinterpret_cast<const char*>(&veg.id), sizeof(veg.id));
file.write(reinterpret_cast<const char*>(&veg.equipement_slot), sizeof(veg.equipement_slot));
file.write(reinterpret_cast<const char*>(&veg.type), sizeof(veg.type));
file.write(reinterpret_cast<const char*>(&veg.count), sizeof(veg.count));
}
static void WriteSeed(std::ofstream &file, const InventoryVegetable &veg) {
file.write(reinterpret_cast<const char*>(&veg.object_type), sizeof(veg.object_type));
file.write(reinterpret_cast<const char*>(&veg.id), sizeof(veg.id));
file.write(reinterpret_cast<const char*>(&veg.equipement_slot), sizeof(veg.equipement_slot));
file.write(reinterpret_cast<const char*>(&veg.type), sizeof(veg.type));
file.write(reinterpret_cast<const char*>(&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<const char*>(&count), sizeof(count));
for (const auto &kv : resources) {
file.write(reinterpret_cast<const char*>(&kv.first), sizeof(kv.first));
file.write(reinterpret_cast<const char*>(&kv.second), sizeof(kv.second));
}
}
// items
{
uint32_t count = items.size();
std::cout << "ITEM COUNT WRITE: " << count << std::endl;
file.write(reinterpret_cast<const char*>(&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<const char*>(&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<const char*>(&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<const char*>(&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<const char*>(&slot), sizeof(slot));
file.write(reinterpret_cast<const char*>(&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<const char*>(&hotbar[i].item_id), sizeof(hotbar[i].item_id));
file.write(reinterpret_cast<const char*>(&hotbar[i].type), sizeof(hotbar[i].type));
}
// selected hotbars
std::cout << "WRITE SEL HOTBAR " << selected_hotbar << std::endl;
file.write(reinterpret_cast<const char*>(&selected_hotbar), sizeof(selected_hotbar));
file.write(reinterpret_cast<const char*>(&selected_hotbar_last), sizeof(selected_hotbar_last));
return file.good();
}
static void ReadBase(std::ifstream &file, Inventory_s *obj) {
// file.read(reinterpret_cast<char*>(&obj->object_type), sizeof(obj->object_type)); // remove
file.read(reinterpret_cast<char*>(&obj->id), sizeof(obj->id));
file.read(reinterpret_cast<char*>(&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<char*>(&it->type), sizeof(it->type));
file.read(reinterpret_cast<char*>(&it->health_current), sizeof(it->health_current));
file.read(reinterpret_cast<char*>(&it->health_max), sizeof(it->health_max));
file.read(reinterpret_cast<char*>(&it->speed), sizeof(it->speed));
file.read(reinterpret_cast<char*>(&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<char*>(&veg->type), sizeof(veg->type));
file.read(reinterpret_cast<char*>(&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<char*>(&veg->type), sizeof(veg->type));
file.read(reinterpret_cast<char*>(&veg->count), sizeof(veg->count));
return veg;
}
static Inventory_s* ReadObject(std::ifstream &file)
{
InventoryObjectType ot;
file.read(reinterpret_cast<char*>(&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<char*>(&count), sizeof(count));
for (uint32_t i = 0; i < count; ++i)
{
ResourceType key;
int value;
file.read(reinterpret_cast<char*>(&key), sizeof(key));
file.read(reinterpret_cast<char*>(&value), sizeof(value));
resources[key] = value;
std::cout << "RES SIZ " << resources.size() << std::endl;
}
}
// items
{
uint32_t count = 0;
file.read(reinterpret_cast<char*>(&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<InventoryItem>(
static_cast<InventoryItem*>(obj)
));
std::cout << "ITM SIZ " << items.size() << std::endl;
}
count = 0;
file.read(reinterpret_cast<char*>(&count), sizeof(count));
for (uint32_t i = 0; i < count; ++i) {
Inventory_s *obj = ReadObject(file);
InventoryVegetable* veg = static_cast<InventoryVegetable*>(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<char*>(&count), sizeof(count));
for (uint32_t i = 0; i < count; ++i) {
Inventory_s *obj = ReadObject(file);
InventoryVegetable* veg = static_cast<InventoryVegetable*>(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<char*>(&count), sizeof(count));
for (uint32_t i = 0; i < count; ++i) {
EquipmentSlot slot;
size_t id;
file.read(reinterpret_cast<char*>(&slot), sizeof(slot));
file.read(reinterpret_cast<char*>(&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<char*>(&hotbar[i].item_id), sizeof(hotbar[i].item_id));
//std::cout << "READ " << hotbar[i].item_id << std::endl;
file.read(reinterpret_cast<char*>(&hotbar[i].type), sizeof(hotbar[i].type));
}
// selected hotbar
file.read(reinterpret_cast<char*>(&selected_hotbar), sizeof(selected_hotbar));
std::cout << "READ SEL HOTBAR " << selected_hotbar << std::endl;
file.read(reinterpret_cast<char*>(&selected_hotbar_last), sizeof(selected_hotbar_last));
return file.good();
}