Nimbin[12]?SDL & Graphics / mapcreator / src/building/building.cpp

mapcreator git · master

SDL3 2.5D game and engine using assets, with map editor

sdl3 c++ game engine map-editor cmake · first commit 2025-10-31 · last commit 2026-09-07 (1 month ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-spieleentwicklung/map_creator

C++ 99.3%
git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gz
src/building/building.cpp 12.7 KB · 402 lines raw
#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<BuildingCost> 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<Building*>(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<ResourceType, float> 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<ResourceType, float> 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<ResourceType, float> 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<ResourceType, float> 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<int>(category) >= static_cast<int>(BuildingType::CRAFTING_STONE_1) &&
         static_cast<int>(category) <= static_cast<int>(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<size_t>(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();
}