#include "building/building.hpp" #include "defaults/ui.hpp" #include "map/map.hpp" #include "building/research.hpp" BuildingStatus Building::construction_mode = BuildingStatus::CONSTRUCTING; BuildingType Building::selected_building_type = BuildingType::NONE; int Building::draw_menu_curser_grid = -1; int Building::draw_menu_curser_grid_active = -1; void Building::Draw(SDL_Renderer* renderer, SDL_FPoint position, bool is_transparent, int transparency) { BuildingData_s* data = BuildingData::get_BuildingDataByType(building_type); if (!data) return; const bool is_deconstruct = (Building::construction_mode == BuildingStatus::DECONSTRUCT); if (!is_deconstruct && (building_time <= 0.0f || data->building_duration <= 0.0f)) { Object::Draw(renderer, position, is_transparent); return; } float progress = 1.0f - (building_time / data->building_duration); progress = std::clamp(progress, 0.0f, 1.0f); int fill_r = 0, fill_g = 200, fill_b = 0; if (is_deconstruct) { progress = 1.0f - progress; fill_r = 200; fill_g = 0; fill_b = 0; } if (!is_deconstruct) transparency = 100.0f + (progress * 100.0f); else transparency = 200.0f - (progress * 100.0f); const int tile = Tile::tile_size; const float bar_w = (float)(dimension.x * tile) * 0.8f; const float bar_h = (float)tile * 0.15f; float bx = position.x + ((float)dimension.x * tile * 0.5f) - (bar_w * 0.5f); float by = position.y - bar_h - 2.0f; Object::Draw(renderer, position, true, transparency); SDL_FRect bg{ Scale(bx + Map::offset.x), Scale(by + Map::offset.y), Scale(bar_w), Scale(bar_h) }; SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_RenderFillRect(renderer, &bg); float fill_w = bar_w * progress; SDL_FRect fg{ Scale(bx + Map::offset.x), Scale(by + Map::offset.y), Scale(fill_w), Scale(bar_h) }; SDL_SetRenderDrawColor(renderer, fill_r, fill_g, fill_b, 220); SDL_RenderFillRect(renderer, &fg); // RESOURCE LIST const float row_h = tile * 0.6f; const float icon_sz = row_h; const float pad = 2.0f; const float gap_y = 2.0f; float list_x = bx; float list_y = by - row_h - 4.0f; int rows = 0; if (!is_deconstruct) { for (const BuildingCost &rc : data->cost) { int missing = rc.amount - cost_fulfilled[rc.type]; if (missing > 0) rows++; } } else { for (const BuildingCost &rc : data->cost) { int remaining = cost_fulfilled[rc.type]; if (remaining > 0) rows++; } } if (rows == 0) return; float total_h = rows * row_h + (rows - 1) * gap_y + 2; SDL_FRect bg_all{ Scale(list_x + Map::offset.x), Scale(list_y - (total_h - row_h) + Map::offset.y), Scale(bar_w), Scale(total_h) }; SDL_SetRenderDrawColor(renderer, 0,0,0,transparency); SDL_RenderFillRect(renderer, &bg_all); float row_y = list_y; auto render_row = [&](int value, ResourceType type) { if (value <= 0) return; auto it = Icon::resource_pos.find(type); if (it == Icon::resource_pos.end()) return; Icon::TilePos pos = it->second; const float src_ts = 16.f; SDL_FRect src{ pos.x * src_ts, pos.y * src_ts, src_ts, src_ts }; SDL_FRect dst{ Scale(list_x + pad + Map::offset.x), Scale(row_y + pad - 4.0f + Map::offset.y), Scale(icon_sz), Scale(icon_sz) }; SDL_RenderTexture(renderer, Icon::icons_texture, &src, &dst); std::string t = std::to_string(value); SDL_Surface* surf = TTF_RenderText_Blended(Ui::font_tiny, t.c_str(), t.length(), {255,255,255,255}); if (surf) { SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); if (tex) { SDL_FRect r{ Scale(list_x + pad + icon_sz + 4.0f + Map::offset.x), Scale(row_y + (row_h - surf->h) * 0.5f + 2.0f + Map::offset.y), (float)surf->w, (float)surf->h }; SDL_RenderTexture(renderer, tex, nullptr, &r); SDL_DestroyTexture(tex); } SDL_DestroySurface(surf); } row_y -= (row_h + gap_y); }; // CONSTRUCTION if (!is_deconstruct) { for (const BuildingCost &rc : data->cost) { int missing = rc.amount - cost_fulfilled[rc.type]; render_row(missing, rc.type); // rc.type is ResourceType } } // DECONSTRUCTION else { std::vector cost_vec(data->cost.begin(), data->cost.end()); for (auto it = cost_vec.rbegin(); it != cost_vec.rend(); ++it) { int remaining = cost_fulfilled[it->type]; render_row(remaining, it->type); } } } Building* Building::get_BuildingById(size_t id) { Object* obj = Object::get_ObjectById(id); if (!obj) return nullptr; return dynamic_cast(obj); } bool Building::Build(Inventory* inventory) { BuildingData_s* data = BuildingData::get_BuildingDataByType(building_type); if (!data || building_time <= 0.0f ) return false; const float dt = Default::delta_time; if (dt <= 0.0f) return false; // Initialize cost_fulfilled if empty if (cost_fulfilled.empty()) { for (const BuildingCost& rc : data->cost) cost_fulfilled[rc.type] = 0; // integer } // Compute remaining cost std::unordered_map remaining; float total_required = 0.0f; float total_remaining = 0.0f; for (const BuildingCost& rc : data->cost) { float rem = (float)rc.amount - (float)cost_fulfilled[rc.type]; if (rem < 0.0f) rem = 0.0f; remaining[rc.type] = rem; total_required += rc.amount; total_remaining += rem; } if (total_remaining <= 0.0f) { building_time = 0.0f; return true; } // Compute per-second consumption rate std::unordered_map consumption_rate; for (const BuildingCost& rc : data->cost) consumption_rate[rc.type] = (float)rc.amount / data->building_duration; // Desired consumption this tick std::unordered_map desired; float redistribution_pool = 0.0f; for (auto& kv : remaining) { float want = consumption_rate[kv.first] * dt; desired[kv.first] = std::min(want, kv.second); } // First pass: consume what is available for (auto& kv : desired) { ResourceType t = kv.first; float want = kv.second; float inv_have = (float)inventory->get_Resource(t); float take = std::min(inv_have, want); // Add fractionally fractional_buffer[t] += take; // Convert to integer only when possible int consume_int = (int)fractional_buffer[t]; if (consume_int > 0) { inventory->resources[t] -= consume_int; cost_fulfilled[t] += consume_int; fractional_buffer[t] -= consume_int; } // Anything not consumed goes to redistribution if (take < want) { redistribution_pool += want - take; } } // Redistribution: proportional over remaining if (redistribution_pool > 0.0f) { float total_remaining_for_redistribution = 0.0f; for (auto& kv : remaining) total_remaining_for_redistribution += kv.second - desired[kv.first]; if (total_remaining_for_redistribution > 0.0f) { for (auto& kv : remaining) { float left = kv.second - desired[kv.first]; if (left <= 0.0f) continue; float share = (left / total_remaining_for_redistribution) * redistribution_pool; float inv_have = (float)inventory->get_Resource(kv.first); float take = std::min(share, inv_have); fractional_buffer[kv.first] += take; int take_int = (int)fractional_buffer[kv.first]; if (take_int > 0) { inventory->resources[kv.first] -= take_int; cost_fulfilled[kv.first] += take_int; fractional_buffer[kv.first] -= take_int; } } } } // Recompute remaining for progress total_remaining = 0.0f; for (const BuildingCost& rc : data->cost) { float rem = (float)rc.amount - (float)cost_fulfilled[rc.type]; if (rem < 0.0f) rem = 0.0f; total_remaining += rem; } float progress = 1.0f - (total_remaining / total_required); progress = std::clamp(progress, 0.0f, 1.0f); building_time = data->building_duration * (1.0f - progress); if (building_time <= 0.0f) { building_time = 0.0f; building_status = BuildingStatus::CONSTRUCTED; return true; } return true; } bool Building::Deconstruct(Inventory* inventory) { BuildingData_s* data = BuildingData::get_BuildingDataByType(building_type); if (!data) return false; if (building_time >= data->building_duration) { std::cout << "DECONSTRUCTER!\n"; return true; } const float dt = Default::delta_time; if (dt <= 0.0f) return false; // Initialize cost_fulfilled if empty (unlikely) if (cost_fulfilled.empty()) { for (const BuildingCost& rc : data->cost) cost_fulfilled[rc.type] = 0; } // Compute per-second deconstruction rate std::unordered_map deconstruct_rate; for (const BuildingCost& rc : data->cost) deconstruct_rate[rc.type] = (float)rc.amount / data->building_duration; float total_remaining = 0.0f; float total_required = 0.0f; for (const BuildingCost& rc : data->cost) { total_required += rc.amount; total_remaining += rc.amount - cost_fulfilled[rc.type]; } // Amount to return this tick for (auto& kv : cost_fulfilled) { ResourceType t = kv.first; int used = kv.second; if (used <= 0) continue; float want = deconstruct_rate[t] * dt; // Add fractionally fractional_buffer[t] += want; // Convert to integer only when possible int give_back = std::min((int)fractional_buffer[t], used); if (give_back > 0) { inventory->resources[t] += give_back; // return to inventory cost_fulfilled[t] -= give_back; fractional_buffer[t] -= give_back; } } // Recompute building progress total_remaining = 0.0f; for (const BuildingCost& rc : data->cost) { total_remaining += rc.amount - cost_fulfilled[rc.type]; } float progress = 1.0f - (total_remaining / total_required); progress = std::clamp(progress, 0.0f, 1.0f); building_time = data->building_duration * (1.0f - progress); if (building_time > data->building_duration) { building_time = data->building_duration; std::cout << "DECONSTRUCTER!\n"; return true; } return false; } bool Building::has_Menu() { if ( static_cast(category) >= static_cast(BuildingType::CRAFTING_STONE_1) && static_cast(category) <= static_cast(BuildingType::RESEARCH_IRON_3)) return true; return false; } CharacterMenuType Building::open_Menu(Character* player) { if (building_status != BuildingStatus::CONSTRUCTED) return CharacterMenuType::NONE; if (BuildingData::is_ResearchTable(building_type)) { Research::research_buildings.clear(); Research::research_items.clear(); for (BuildingData_s data : BuildingData::building_data) if (data.research_by == building_type && data.required_level <= player->level) Research::research_buildings.push_back(data.type); for (ItemStats data : Item::stats) if (data.research_station == building_type && data.research_level <= static_cast(player->level)) Research::research_items.push_back(data.type); std::cout << "Open Research menu\n"; std::cout << "Build Items: " << Research::research_buildings.size() << std::endl; std::cout << "Item Items: " << Research::research_items.size() << std::endl; selected_building_type = building_type; return CharacterMenuType::RESEARCH; } if (BuildingData::is_CraftingTable(building_type)) { std::cout << "Open Crafting menu\n"; selected_building_type = building_type; return CharacterMenuType::WORKBENCH; } return CharacterMenuType::NONE; } void Building::close_Menu() { std::cout << "Close Build menu\n"; Research::research_items.clear(); Research::research_buildings.clear(); }