Nimbin[12]?SDL & Graphics / mapcreator / src/building/building_data.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_data.cpp 22.4 KB · 696 lines raw
#include "building/building_data.hpp"

#include <SDL3_image/SDL_image.h>

#include "defaults/ui.hpp"
#include "map/map.hpp"

const int MENU_PADDING = 16;
const int MENU_TOP_PADDING  = 10 + Ui::top_bar_height;
const int MENU_ITEM_SPACING = 6;
const int MENU_BG_COLOR_R = 0;
const int MENU_BG_COLOR_G = 0;
const int MENU_BG_COLOR_B = 0;
const int MENU_BG_COLOR_A = 182;
const int WORKBENCH_ROW_HEIGHTS[6] = {4, 3, 4, 4, 3, 4};




namespace BuildingData {
    SDL_Texture* workbench_texture;
    ActiveBuild  active_build;


    // Keep the order of BuildingType enum!
    std::vector<BuildingData_s> building_data = {
        { BuildingType::NONE,
          "None",
          0,
          0,
          0,
          BuildingType::NONE,
          {},
          TileType::TILE_TYPE_LAST,
          0,
          0

        },

        // CRAFTING TABLE
        { BuildingType::CRAFTING_STONE_1,
          "Crafting table Stone",
          6.0,
          3,
          200,
          BuildingType::RESEARCH_STONE_1,
          {{ResourceType::STONE, 6}, {ResourceType::WOOD, 4}, {ResourceType::CLAY, 2}},
          TileType::TILE_BUILDING,
          5,
          0
        },
        { BuildingType::CRAFTING_STONE_2,
          "Crafting table Stone",
          10.0,
          3,
          400,
          BuildingType::RESEARCH_STONE_2,
          {{ResourceType::STONE, 20}, {ResourceType::WOOD, 7}, {ResourceType::CLAY, 7}},
          TileType::TILE_BUILDING,
          5,
          1
        },
        { BuildingType::CRAFTING_STONE_3,
          "Crafting table Stone",
          14.0,
          3,
          600,
          BuildingType::RESEARCH_STONE_3,
          {{ResourceType::STONE, 30}, {ResourceType::WOOD, 12}, {ResourceType::CLAY, 12}, {ResourceType::IRON, 4}},
          TileType::TILE_BUILDING,
          5,
          2
        },
        { BuildingType::CRAFTING_WOOD_1,
          "Crafting table Wood",
          6.0,
          3,
          1,
          BuildingType::RESEARCH_WOOD_1,
          {{ResourceType::WOOD, 8}, {ResourceType::CLAY, 2}},
          TileType::TILE_BUILDING,
          5,
          4
        },
        { BuildingType::CRAFTING_WOOD_2,
          "Crafting table Wood",
          10.0,
          3,
          300,
          BuildingType::RESEARCH_WOOD_2,
          {{ResourceType::WOOD, 20}, {ResourceType::CLAY, 10}, {ResourceType::STONE, 4}},
          TileType::TILE_BUILDING,
          5,
          13
        },
        { BuildingType::CRAFTING_WOOD_3,
          "Crafting table Wood",
          14.0,
          3,
          500,
          BuildingType::RESEARCH_WOOD_3,
          {{ResourceType::WOOD, 20}, {ResourceType::CLAY, 10}, {ResourceType::STONE, 4}, {ResourceType::IRON, 4}},
          TileType::TILE_BUILDING,
          5,
          14
        },
        { BuildingType::CRAFTING_IRON_1,
          "Crafting table Iron",
          6.0,
          3,
          400,
          BuildingType::RESEARCH_IRON_1,
          {{ResourceType::IRON, 10}, {ResourceType::WOOD, 4}},
          TileType::TILE_BUILDING,
          5,
          5
        },
        { BuildingType::CRAFTING_IRON_2,
          "Crafting table Iron",
          10.0,
          3,
          600,
          BuildingType::RESEARCH_IRON_2,
          {{ResourceType::IRON, 20}, {ResourceType::WOOD, 10}, {ResourceType::STONE, 4}},
          TileType::TILE_BUILDING,
          5,
          12
        },
        { BuildingType::CRAFTING_IRON_3,
          "Crafting table Iron",
          14.0,
          3,
          800,
          BuildingType::RESEARCH_IRON_3,
          {{ResourceType::IRON, 20}, {ResourceType::WOOD, 10}, {ResourceType::STONE, 4}, {ResourceType::CLAY, 4}},
          TileType::TILE_BUILDING,
          5,
          16
        },



        // RESEARCH TABLE
        { BuildingType::RESEARCH_STONE_1,
          "Research table Stone",
          6.0,
          3,
          200,
          BuildingType::RESEARCH_WOOD_1,
          {{ResourceType::STONE, 8}},
          TileType::TILE_BUILDING,
          5,
          6
        },
        { BuildingType::RESEARCH_STONE_2,
          "Research table Stone",
          10.0,
          3,
          400,
          BuildingType::RESEARCH_STONE_1,
          {{ResourceType::STONE, 14}, {ResourceType::WOOD, 4}, {ResourceType::CLAY, 4}},
          TileType::TILE_BUILDING,
          5,
          11
        },
        { BuildingType::RESEARCH_STONE_3,
          "Research table Stone",
          14.0,
          3,
          600,
          BuildingType::RESEARCH_STONE_2,
          {{ResourceType::STONE, 20}, {ResourceType::WOOD, 6}, {ResourceType::CLAY, 4}, {ResourceType::IRON, 4}},
          TileType::TILE_BUILDING,
          5,
          3
        },
        { BuildingType::RESEARCH_WOOD_1,
          "Research table Wood",
          6.0,
          3,
          1,
          BuildingType::NONE,
          {{ResourceType::WOOD, 8}, {ResourceType::CLAY, 4}},
          TileType::TILE_BUILDING,
          5,
          7
        },
        { BuildingType::RESEARCH_WOOD_2,
          "Research table Wood",
          10.0,
          3,
          300,
          BuildingType::RESEARCH_WOOD_2,
          {{ResourceType::WOOD, 14}, {ResourceType::CLAY, 4}, {ResourceType::STONE, 4}},
          TileType::TILE_BUILDING,
          5,
          10
        },
        { BuildingType::RESEARCH_WOOD_3,
          "Research table Wood",
          14.0,
          3,
          500,
          BuildingType::RESEARCH_WOOD_3,
          {{ResourceType::WOOD, 20}, {ResourceType::CLAY, 6}, {ResourceType::STONE, 4}, {ResourceType::IRON, 4}},
          TileType::TILE_BUILDING,
          5,
          15
        },
        { BuildingType::RESEARCH_IRON_1,
          "Research table Iron",
          6.0,
          3,
          400,
          BuildingType::RESEARCH_STONE_1,
          {{ResourceType::IRON, 8}, {ResourceType::WOOD, 4}},
          TileType::TILE_BUILDING,
          5,
          8
        },
        { BuildingType::RESEARCH_IRON_2,
          "Research table Iron",
          10.0,
          3,
          600,
          BuildingType::RESEARCH_IRON_1,
          {{ResourceType::IRON, 14}, {ResourceType::WOOD, 3}, {ResourceType::STONE, 4}},
          TileType::TILE_BUILDING,
          5,
          9
        },
        { BuildingType::RESEARCH_IRON_3,
          "Research table Iron",
          14.0,
          3,
          800,
          BuildingType::RESEARCH_IRON_2,
          {{ResourceType::IRON, 20}, {ResourceType::WOOD, 6}, {ResourceType::STONE, 4}, {ResourceType::CLAY, 4}},
          TileType::TILE_BUILDING,
          5,
          17
        },
    };

    bool Init(SDL_Renderer* renderer) {
        workbench_texture = IMG_LoadTexture(renderer, "Pixel_Crawler/Environment/Structures/Stations/Workbench/Workbench.png");
        if (!workbench_texture) {
            std::cerr << "Failed to load workbench_texture!\n";
            return false;
        }
        return true;
    }

    int
    get_WorkbenchLevel(BuildingType type) {
        switch (type) {
            case BuildingType::CRAFTING_STONE_1:
            case BuildingType::RESEARCH_STONE_1:
            case BuildingType::CRAFTING_WOOD_1:
            case BuildingType::RESEARCH_WOOD_1:
            case BuildingType::CRAFTING_IRON_1:
            case BuildingType::RESEARCH_IRON_1:
                return 1;
            case BuildingType::CRAFTING_STONE_2:
            case BuildingType::RESEARCH_STONE_2:
            case BuildingType::CRAFTING_WOOD_2:
            case BuildingType::RESEARCH_WOOD_2:
            case BuildingType::CRAFTING_IRON_2:
            case BuildingType::RESEARCH_IRON_2:
                return 2;
            case BuildingType::CRAFTING_STONE_3:
            case BuildingType::RESEARCH_STONE_3:
            case BuildingType::CRAFTING_WOOD_3:
            case BuildingType::RESEARCH_WOOD_3:
            case BuildingType::CRAFTING_IRON_3:
            case BuildingType::RESEARCH_IRON_3:
                return 3;
            default: return -1;
        }
    }

    BuildingData_s* get_BuildingDataByType(BuildingType type) {
        for (auto& b : building_data)
            if (b.type == type) return &b;

        return nullptr;
    }

    std::vector<BuildingData_s*>
    get_CraftingTable() {
        std::vector<BuildingData_s*> result;

        for (auto& b : building_data)
            if (is_CraftingTable(b.type))
                result.push_back(&b);

        return result;
    }

    std::vector<BuildingData_s*>
    get_ResearchTable() {
        std::vector<BuildingData_s*> result;

        for (auto& b : building_data)
            if (is_ResearchTable(b.type))
                result.push_back(&b);

        return result;
    }


    void
    draw_WorkbenchMenu(SDL_Renderer* renderer, SDL_Rect win_size, std::vector<bool>& enabled_buildings) {
        const int tile = Tile::tile_size;
        const int tile_scale = 2;

        std::vector<BuildingData_s*> research = BuildingData::get_ResearchTable();
        std::vector<BuildingData_s*> crafting = BuildingData::get_CraftingTable();

        struct Cell {
            bool enabled;
            float w, h;
            float sx, sy;
        };

        Cell cells[6][3];

        size_t active_id = 100;

        // PRECOMPUTE EVERYTHING
        for (size_t row = 0; row < 6; ++row) {
            for (size_t col = 0; col < 3; ++col) {

                BuildingData_s* data =
                    (row < 3)
                    ? research[row * 3 + col]
                    : crafting[(row - 3) * 3 + col];

                Cell& c = cells[row][col];
                c.enabled = enabled_buildings[static_cast<size_t>(data->type)];

                if (!c.enabled)
                    continue;

                if (data->type == active_build.type)
                    active_id = row * 3 + col;

                TilesetObject obj = Tile::get_ObjectFromTileSet(
                    data->tileset_id,
                    data->tileset_object_id,
                    data->tileset_type
                );

                c.sx = static_cast<float>(obj.position.x * tile);
                c.sy = static_cast<float>(obj.position.y * tile);
                c.w  = static_cast<float>(obj.position.w * tile);
                c.h  = static_cast<float>(obj.position.h * tile);
            }
        }

        // COMPUTE MENU WIDTH
        size_t max_w = 0;

        for (size_t row = 0; row < 6; ++row) {
            size_t row_w = 0;

            for (size_t col = 0; col < 3; ++col) {
                const Cell& c = cells[row][col];
                if (!c.enabled)
                    continue;

                row_w += (size_t)(c.w * tile_scale);
                if (col < 2)
                    row_w += MENU_ITEM_SPACING;
            }

            if (row_w > max_w)
                max_w = row_w;
        }

        if (max_w == 0)
            max_w = 2 * tile + MENU_ITEM_SPACING;

        const float MENU_W = MENU_PADDING * 2 + (float)max_w;
        const float menu_x = (float)win_size.w - MENU_W;
        const float MENU_H = (float)win_size.h;

        // BACKGROUND
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
        SDL_SetRenderDrawColor(renderer,
            MENU_BG_COLOR_R, MENU_BG_COLOR_G,
            MENU_BG_COLOR_B, MENU_BG_COLOR_A
        );

        SDL_FRect bg = { menu_x, 0, MENU_W, MENU_H };
        SDL_RenderFillRect(renderer, &bg);

        // DRAW
        float cy = MENU_PADDING + MENU_TOP_PADDING;

        for (size_t row = 0; row < 6; ++row) {
            bool has_enabled = false;
            float cx = menu_x + MENU_PADDING;

            for (size_t col = 0; col < 3; ++col) {
                const Cell& c = cells[row][col];
                if (!c.enabled)
                    continue;

                has_enabled = true;

                SDL_FRect src = { c.sx, c.sy, c.w, c.h };
                SDL_FRect dst = { cx, cy, c.w * tile_scale, c.h * tile_scale };

                // higlight cell
                if (active_id == row * 3 + col) {
                    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
                    SDL_SetRenderDrawColor(renderer, 255, 255, 0, 100);
                    SDL_RenderRect(renderer, &dst);
                    SDL_SetRenderDrawColor(renderer, 255, 255, 0, 40);
                    SDL_RenderFillRect(renderer, &dst);
                }

                SDL_RenderTexture(renderer, BuildingData::workbench_texture, &src, &dst);

                cx += c.w * tile_scale + MENU_ITEM_SPACING;
            }

            if (has_enabled) {
                cy += WORKBENCH_ROW_HEIGHTS[row] * tile * tile_scale + MENU_ITEM_SPACING;

            }
        }
    }


    BuildingType
    menu_DetectWorkbenchClick(const Input& input, SDL_Rect win_size, std::vector<bool>& enabled_buildings) {
        const int tile = Tile::tile_size;
        const int tile_scale = 2;
        std::vector<BuildingData_s*> research = BuildingData::get_ResearchTable();
        std::vector<BuildingData_s*> crafting = BuildingData::get_CraftingTable();

        struct Cell {
            BuildingData_s* data;
            float x, y, w, h;
        };

        Cell cells[6][3];
        size_t max_w = 0;

        // PRECOMPUTE CELLS
        float cy = MENU_PADDING + MENU_TOP_PADDING;
        for (size_t row = 0; row < 6; ++row) {
            float cx = MENU_PADDING;
            bool has_enabled = false;

            for (size_t col = 0; col < 3; ++col) {
                BuildingData_s* data = (row < 3)
                                       ? research[row * 3 + col]
                                       : crafting[(row - 3) * 3 + col];
                bool is_enabled = enabled_buildings[static_cast<size_t>(data->type)];
                if (!is_enabled) {
                    cells[row][col] = {nullptr, 0, 0, 0, 0};
                    continue;
                }

                TilesetObject obj = Tile::get_ObjectFromTileSet(
                    data->tileset_id,
                    data->tileset_object_id,
                    data->tileset_type
                );

                float w = static_cast<float>(obj.position.w * tile * tile_scale);
                float h = static_cast<float>(obj.position.h * tile * tile_scale);

                cells[row][col] = {data, cx, cy, w, h};

                cx += w + MENU_ITEM_SPACING;
                has_enabled = true;
            }

            if (has_enabled)
                cy += WORKBENCH_ROW_HEIGHTS[row] * tile * tile_scale + MENU_ITEM_SPACING;

            if (cx - MENU_ITEM_SPACING > max_w)
                max_w = static_cast<size_t>(cx - MENU_ITEM_SPACING);
        }

        if (max_w == 0)
            max_w = 2 * tile *tile_scale + MENU_ITEM_SPACING;

        const float menu_w = MENU_PADDING * 2 + static_cast<float>(max_w);
        const float menu_x = static_cast<float>(win_size.w) - menu_w;
        const float menu_y = 0.0f;

        SDL_FPoint mouse = input.mouse_state_raw;

        // check if mouse is inside menu
        if (mouse.x < menu_x || mouse.x > menu_x + menu_w ||
            mouse.y < menu_y || mouse.y > menu_y + win_size.h)
            return BuildingType::NONE;

        // detect clicked cell
        for (size_t row = 0; row < 6; ++row) {
            for (size_t col = 0; col < 3; ++col) {
                Cell& c = cells[row][col];
                if (!c.data) continue;

                float x0 = menu_x + c.x + MENU_PADDING;
                float y0 = menu_y + c.y;

                if (mouse.x >= x0 && mouse.x <= x0 + c.w &&
                    mouse.y >= y0 && mouse.y <= y0 + c.h) {
                    return c.data->type;
                }
            }
        }

        return BuildingType::NONE;
    }

    bool
    menu_Hovering(const Input& input, SDL_Rect win_size, std::vector<bool>& enabled_buildings) {
        const int tile = Tile::tile_size;
        std::vector<BuildingData_s*> research = BuildingData::get_ResearchTable();
        std::vector<BuildingData_s*> crafting = BuildingData::get_CraftingTable();
        size_t max_w = 0;

        for (size_t row = 0; row < 6; ++row) {
            float cx = MENU_PADDING;

            for (size_t col = 0; col < 3; ++col) {
                BuildingData_s* data = (row < 3)
                                       ? research[row * 3 + col]
                                       : crafting[(row - 3) * 3 + col];

                bool is_enabled = enabled_buildings[static_cast<size_t>(data->type)];
                if (!is_enabled)
                    continue;

                TilesetObject obj = Tile::get_ObjectFromTileSet(
                    data->tileset_id,
                    data->tileset_object_id,
                    data->tileset_type
                );

                float w = static_cast<float>(obj.position.w * tile);
                cx += w + MENU_ITEM_SPACING;
            }


            if (cx - MENU_ITEM_SPACING > max_w)
                max_w = static_cast<size_t>(cx - MENU_ITEM_SPACING);
        }

        if (max_w == 0)
            max_w = 2 * tile + MENU_ITEM_SPACING;

        const float menu_w = MENU_PADDING * 2 + static_cast<float>(max_w);
        const float menu_x = static_cast<float>(win_size.w) - menu_w;
        const float menu_y = 0.0f;
        const float menu_h = (float)win_size.h;

        SDL_FPoint mouse = input.mouse_state_raw;

        return mouse.x >= menu_x && mouse.x <= menu_x + menu_w &&
            mouse.y >= menu_y && mouse.y <= menu_y + menu_h;
    }

    bool
    is_BuildingCollidingWithObject() {
        SDL_Point position = active_build.tile_position;
        TilesetObject building = active_build.object;

        std::vector<bool>
        collision_map = Tile::get_ObjectCollisionsFromTileset(building.tileset_info.set_id,
                                                              building.tileset_info.object_id,
                                                              Tile::get_Type(building.tileset_info.set_category));

        int start_cx = std::floor(position.x * Tile::tile_size / float(Tile::tile_collider_size));
        int end_cx   = std::ceil(((position.x + building.position.w * Tile::tile_size) / float(Tile::tile_collider_size)));
        int start_cy = std::floor(position.y * Tile::tile_size / float(Tile::tile_collider_size));
        int end_cy   = std::ceil(((position.y + building.position.h * Tile::tile_size ) / float(Tile::tile_collider_size)));

        int map_width_in_colliders = Map::size.x * (Tile::tile_size / Tile::tile_collider_size);

        // For each map tile collider cell
        size_t i = 0;
        for (int cy = start_cy; cy < end_cy; ++cy) {
            for (int cx = start_cx; cx < end_cx; ++cx, i++) {
                size_t index = static_cast<size_t>(cy * map_width_in_colliders + cx);
                if (index >= Map::tile_collission_map.size())
                    continue;

                if (collision_map[i] && Map::tile_collission_map[index])
                    return true;
            }
        }

        // for each object collider cell
        for (int x = 0; x < building.position.w; x++) {
            for (int y = 0; y < building.position.h; y++) {
                SDL_Point p = {position.x+x, position.y+y};
                if ( Tile::get_FloorType(p) != TileTypeFloors::MEADOW &&
                     Tile::get_FloorType(p) != TileTypeFloors::BRICKS )
                    return true;
                size_t map_id = static_cast<size_t>(p.y * Map::size.x + x);
                if (Map::tile_map[map_id].overlays.size() > 0)
                    return true;
                std::vector<TileObject> objects = Map::get_ObjectsFromMapTile(p);
                std::vector<bool> c_b = Tile::get_CollissionsOfTileFromObject(building, {x, y});
                for (TileObject& o : objects) {
                    Object* oo = Object::get_ObjectById(o.object_id); 
                    // oo->print_CollisionMap();
                    std::vector<bool> c_o = oo->get_CollissionsOfTile(o.position);
                    for (size_t i = 0; i < c_o.size(); i++)
                        if (c_o[i] && c_b[i]) return true;
                }
            }
        }
        return false;
    }

    bool
    snap_BuildPosition(float in_x, float in_y) {
        float tile = Tile::tile_size;
        float w = active_build.object.position.w * tile;
        float h = active_build.object.position.h * tile;

        float dst_x = in_x - w * 0.5f;
        float dst_y = in_y - h;

        float snapped_x = std::round(dst_x / tile) * tile;
        float snapped_y = std::floor(dst_y / tile) * tile;

        while (snapped_y + tile > in_y) {
            snapped_y -= tile;
        }

        active_build.tile_position = Map::get_TileFromXy({snapped_x, snapped_y});

        if (active_build.tile_position.x >= Map::size.x ||
                active_build.tile_position.y >= Map::size.y)
            return false;

        active_build.world_position = {snapped_x, snapped_y};
        return true;
    }

    void
    update_ActiveBuildData(float pos_x, float pos_y) {
        if (!snap_BuildPosition(pos_x, pos_y))
            return;

        active_build.is_colliding = is_BuildingCollidingWithObject();
    }

    void
    draw_SelectedWorkbenchAt(SDL_Renderer* renderer) {
        BuildingType t = active_build.type;
        if (t == BuildingType::NONE)
            return;

        float snapped_x = active_build.world_position.x;
        float snapped_y = active_build.world_position.y;

        SDL_FRect dst = {
            Scale(snapped_x + Map::offset.x),
            Scale(snapped_y + Map::offset.y),
            Scale(active_build.object.position.w*static_cast<int>(Tile::tile_size)),
            Scale(active_build.object.position.h*static_cast<int>(Tile::tile_size))
        };

        // -------- RED BACKGROUND WHEN COLLIDING ----------
        if (active_build.is_colliding) {
            SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
            SDL_SetRenderDrawColor(renderer, 255, 0, 0, 40);
            SDL_RenderFillRect(renderer, &dst);
        }
        // --------------------------------------------------

        // Draw the workbench sprite on top
        SDL_FRect src = {
            static_cast<float>(active_build.object.position.x*static_cast<int>(Tile::tile_size)),
            static_cast<float>(active_build.object.position.y*static_cast<int>(Tile::tile_size)),
            static_cast<float>(active_build.object.position.w*static_cast<int>(Tile::tile_size)),
            static_cast<float>(active_build.object.position.h*static_cast<int>(Tile::tile_size))
        };

        SDL_SetTextureAlphaMod(BuildingData::workbench_texture, active_build.is_colliding ? 80 : 160);
        SDL_RenderTexture(renderer, BuildingData::workbench_texture, &src, &dst);
        SDL_SetTextureAlphaMod(BuildingData::workbench_texture, 255);
    }

}