Nimbin[12]?SDL & Graphics / mapcreator / src/map/map.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/map/map.cpp 48.3 KB · 1178 lines raw
#include "map/map.hpp"

#include <cstddef>
#include <iostream>
#include <queue>
#include <set>

#include "building/building_data.hpp"
#include "resource/resource.hpp"
#include "building/building.hpp"
#include "game/game.hpp"
#include "farm/farm.hpp"
#include "bird/bird.hpp"

namespace Map {

    // save_MapTiles
    std::string save_path = "saves/save.map";
    std::string save_collission_path = "saves/save.collissions";

    bool setting_offset = false;

    SDL_Point offset_to_set = {0, 0};

    SDL_Point offset = {0, 0};

    SDL_Point size = {160, 100}; // map size in tiles...
    SDL_Point boundaries = {63, 39}; // JEee fucked up the size, 
                                     // to much work to resize (i have an outer cage which fucks the path finding)

    std::vector<TileMap> tile_map;

    std::vector<bool> tile_collission_map;

    int   frame_water = 0;
    float global_frame_time = 0.0f;
    float global_frame_time_max = 0.6f;

    int   frame_meadow = 0;
    float frame_meadow_time = 0.0f;
    float frame_meadow_time_max = 0.6f;

    void
    Init() {
            tile_map.resize(static_cast<size_t>(Map::size.x * Map::size.y));

            for (int y = 0; y < Map::size.y; ++y) {
            for (int x = 0; x < Map::size.x; ++x) {

                bool is_border =
                    (x == 0) || (y == 0) ||
                    (x == Map::size.x - 1) ||
                    (y == Map::size.y - 1);

                size_t index = static_cast<size_t>(y * Map::size.x + x);

                tile_map[index].background = { 0, SIZE_MAX, TILE_GROUND, {is_border ? 17 : 0, 0} };
                //tile_map[index].overlay    = { SIZE_MAX, SIZE_MAX, TILE_OVERLAY_CHARACTER, {0, 0} };
            }
        }

    }

    void
    init_Collider() {
        tile_collission_map.clear();
        // init map_colliders
        const size_t cells_per_tile_map = (Tile::tile_size / Tile::tile_collider_size)*(Tile::tile_size / Tile::tile_collider_size);
        for (size_t i = 0; i < tile_map.size(); i++) {
            for (size_t j = 0; j < cells_per_tile_map; j++)
                tile_collission_map.push_back(false);
        }
    }

    void
    Reset() {
        tile_map.clear();
        Init();
        init_Collider();
    }


    void
    set_CollisionForTile(const TileMap &tile_map_item,
            size_t map_tile_x,
            size_t map_tile_y) {

        const size_t cells_per_tile = Tile::tile_size / Tile::tile_collider_size;
        const size_t map_width_cells = static_cast<size_t>(Map::size.x) * cells_per_tile;
        const Tile_s &tile = tile_map_item.background;

        const SDL_Point position = tile.set_position;
        const TileSet &tileset = Tile::get_TilesetFromMapTile(tile);

        const size_t ts_width = static_cast<size_t>(tileset.size.x) * cells_per_tile;

        size_t map_cell_origin_x = map_tile_x * cells_per_tile;
        size_t map_cell_origin_y = map_tile_y * cells_per_tile;

        for (size_t y = 0; y < cells_per_tile; ++y) {
            for (size_t x = 0; x < cells_per_tile; ++x) {
                size_t ts_index =
                    (static_cast<size_t>(position.y) * cells_per_tile + y) * ts_width +
                    (static_cast<size_t>(position.x) * cells_per_tile + x);

                if (ts_index >= tileset.collission_map.size())
                    continue;

                size_t map_x = map_cell_origin_x + x;
                size_t map_y = map_cell_origin_y + y;
                size_t map_index = map_y * map_width_cells + map_x;

                if (map_index < tile_collission_map.size())
                    tile_collission_map[map_index] = tileset.collission_map[ts_index];
            }
        }
    }

    void
    set_CollissionFromTileset() {
        for (int tile_index = 0; tile_index < static_cast<int>(tile_map.size()); ++tile_index) {
            int map_tile_x = tile_index % Map::size.x;
            int map_tile_y = tile_index / Map::size.x;


            set_TileCollissionForObjects({map_tile_x, map_tile_y});
        }
    }


    void
    set_ObjectCollisionOnTilemap(size_t object_id, SDL_Point map_tile) {
        Object* object = Object::get_ObjectById(object_id);

        object->print_CollisionMap();
        const std::vector<bool>& collision_map = object->get_CollisionMap();

        const size_t scale = Tile::tile_size / Tile::tile_collider_size;

        const size_t obj_cols = static_cast<size_t>(object->dimension.x) * scale;
        const size_t obj_rows = static_cast<size_t>(object->dimension.y) * scale;

        std::cout << "DIM: " << object->dimension.x << " " << object->dimension.y << std::endl;
        std::cout << "COL: " << obj_cols << " " << obj_rows << std::endl;


        const size_t base_x = static_cast<size_t>(map_tile.x) * scale;
        const size_t base_y = static_cast<size_t>(map_tile.y) * scale;
        std::cout << "BAS: " << base_x << " " << base_y << std::endl;

        const size_t map_cols = static_cast<size_t>(Map::size.x) * scale;

        for (size_t oy = 0; oy < obj_rows; ++oy) {
            for (size_t ox = 0; ox < obj_cols; ++ox) {

                const size_t obj_idx = oy * obj_cols + ox;

                if (collision_map[obj_idx]) {
                    const size_t mx = base_x + ox;
                    const size_t my = base_y + oy;

                    const size_t map_idx = my * map_cols + mx;
                    std::cout << "COL: " << mx << " " << my << " " << map_idx << std::endl;
                    Map::tile_collission_map[map_idx] = true;
                }
            }
        }
    }


    void
    set_TileCollissionForObjects(SDL_Point map_tile) {
        const size_t tile_index = static_cast<size_t>(map_tile.y * Map::size.x + map_tile.x);

        const size_t scale = Tile::tile_size / Tile::tile_collider_size;

        const size_t start_y = static_cast<size_t>(map_tile.y)* scale;
        const size_t start_x = static_cast<size_t>(map_tile.x)* scale;

        const size_t map_cols = static_cast<size_t>(Map::size.x) * scale;

        const TileMap &tile_map_item = tile_map[tile_index];

        // reset collisions for this tile
        set_CollisionForTile(tile_map_item, static_cast<size_t>(map_tile.x), static_cast<size_t>(map_tile.y));

        // merge collisions from all objects on the tile
        for (const TileObject& to : tile_map[tile_index].objects) {

            Object* object = Object::get_ObjectById(to.object_id);

            const std::vector<bool> collision_map =
                object->get_CollissionsOfTile(to.position);

            for (size_t oy = 0; oy < scale; ++oy) {
                for (size_t ox = 0; ox < scale; ++ox) {

                    const size_t tile_cell = oy * scale + ox;

                    if (collision_map[tile_cell]) {
                        const size_t map_idx =
                            (start_y + oy) * map_cols + (start_x + ox);
                        Map::tile_collission_map[map_idx] = true;
                    }
                }
            }
        }
    }


    void
    save_TileCollision(const std::string &path) {
        std::ofstream out(path, std::ios::binary);
        if (!out) {
            std::cerr << "Failed to open file for writing: " << path << '\n';
            return;
        }

        out.write(reinterpret_cast<const char*>(&Map::size.x), sizeof(Map::size.x));
        out.write(reinterpret_cast<const char*>(&Map::size.y), sizeof(Map::size.y));
        // Write the size first
        size_t size = tile_collission_map.size();
        out.write(reinterpret_cast<const char*>(&size), sizeof(size));

        // Write each bool as a byte
        for (bool value : tile_collission_map) {
            uint8_t byte = value ? 1 : 0;
            out.write(reinterpret_cast<const char*>(&byte), sizeof(byte));
        }

        std::cout << "Saved " << size << " collision cells to " << path << '\n';
    }

    void
    load_TileCollision(const std::string &path) {
        std::ifstream in(path, std::ios::binary);
        if (!in) {
            std::cerr << "Failed to open file for reading: " << path << '\n';
            return;
        }


        int map_tilesize_x;
        int map_tilesize_y;
        in.read(reinterpret_cast<char*>(&map_tilesize_x), sizeof(map_tilesize_x));
        in.read(reinterpret_cast<char*>(&map_tilesize_y), sizeof(map_tilesize_y));

        std::cout << "WUU " << map_tilesize_x << " " << map_tilesize_y<< std::endl;

        // Read the size
        size_t size = 0;
        in.read(reinterpret_cast<char*>(&size), sizeof(size));

        if (!in) {
            std::cerr << "Failed to read size from file: " << path << '\n';
            return;
        }

        // Resize and read data
        std::vector<bool> temp(size);
        const size_t w = static_cast<size_t>(Map::size.x);
        const size_t h = static_cast<size_t>(Map::size.y);
        const size_t m = Tile::tile_size/Tile::tile_collider_size;

        Map::tile_collission_map.assign(w * h * m * m, false);
        for (size_t i = 0; i < size; ++i) {
            uint8_t byte = 0;
            in.read(reinterpret_cast<char*>(&byte), sizeof(byte));
            temp[i] = (byte != 0);
            size_t x = i% static_cast<size_t>(map_tilesize_x);
            size_t y = i/ static_cast<size_t>(map_tilesize_x);
            Map::tile_collission_map[y*static_cast<size_t>(Map::size.x)+x] = temp[i];
        }


        if (!in) {
            std::cerr << "File corrupted or incomplete: " << path << '\n';
            return;
        }

        //Map::tile_collission_map = std::move(temp);
        std::cout << "Loaded " << size << " collision cells from " << path << '\n';
    }

    void
    save_MapTiles(const std::vector<TileMap>& tile_map, const std::string& path) {
        std::ofstream file(path, std::ios::binary);
        if (!file.is_open()) return;

        size_t map_count = tile_map.size();
        file.write(reinterpret_cast<const char*>(&map_count), sizeof(map_count));

        size_t id = 0;
        for (const auto& map : tile_map) {
            // --- background ---

            size_t map_tile_x = id % static_cast<size_t>(Map::size.x);
            size_t map_tile_y = id / static_cast<size_t>(Map::size.x);
            file.write(reinterpret_cast<const char*>(&map_tile_x), sizeof(map_tile_x));
            file.write(reinterpret_cast<const char*>(&map_tile_y), sizeof(map_tile_y));
            file.write(reinterpret_cast<const char*>(&map.background.set_id), sizeof(map.background.set_id));
            file.write(reinterpret_cast<const char*>(&map.background.object_id), sizeof(map.background.object_id));
            file.write(reinterpret_cast<const char*>(&map.background.set_category), sizeof(map.background.set_category));
            file.write(reinterpret_cast<const char*>(&map.background.set_position), sizeof(map.background.set_position));
            // --- overlays ---
            const std::size_t overlay_count = map.overlays.size();
            file.write(reinterpret_cast<const char*>(&overlay_count), sizeof(overlay_count));

            for (const Tile_s& ov : map.overlays) {
                file.write(reinterpret_cast<const char*>(&ov.set_id), sizeof(ov.set_id));
                file.write(reinterpret_cast<const char*>(&ov.object_id), sizeof(ov.object_id));
                file.write(reinterpret_cast<const char*>(&ov.set_category), sizeof(ov.set_category));
                file.write(reinterpret_cast<const char*>(&ov.set_position), sizeof(ov.set_position));
            }

            // --- objects ---
            size_t obj_count = map.objects.size();
            file.write(reinterpret_cast<const char*>(&obj_count), sizeof(obj_count));

            for (const auto& obj : map.objects) {
                Object* o = Object::get_ObjectById(obj.object_id);

                // TilesetObject
                file.write(reinterpret_cast<const char*>(&obj.position.x), sizeof(obj.position.x));
                file.write(reinterpret_cast<const char*>(&obj.position.y), sizeof(obj.position.y));
                file.write(reinterpret_cast<const char*>(&o->dimension.x), sizeof(o->dimension.x));
                file.write(reinterpret_cast<const char*>(&o->dimension.y), sizeof(o->dimension.y));
                file.write(reinterpret_cast<const char*>(&obj.draw_from_this_tile), sizeof(obj.draw_from_this_tile));

                auto object_type = static_cast<int>(o->object_type);
                auto category    = static_cast<int>(o->category);
                file.write(reinterpret_cast<const char*>(&object_type), sizeof(object_type));
                file.write(reinterpret_cast<const char*>(&category), sizeof(category));
                // im getting tile_type on load

                int tileset_type = static_cast<int>(o->tileset_type);
                file.write(reinterpret_cast<const char*>(&tileset_type), sizeof(tileset_type));
                file.write(reinterpret_cast<const char*>(&o->tileset_id), sizeof(o->tileset_id));
                file.write(reinterpret_cast<const char*>(&o->tileset_object_id), sizeof(o->tileset_object_id));

                file.write(reinterpret_cast<const char*>(&o->z_index), sizeof(o->z_index));

                if (o->category == ObjectCategory::BUILDING) {
                    Building* b = static_cast<Building*>(o);

                    auto type   = static_cast<int>(b->building_type);
                    auto status = static_cast<int>(b->building_status);

                    file.write(reinterpret_cast<const char*>(&type),   sizeof(type));
                    file.write(reinterpret_cast<const char*>(&status), sizeof(status));
                    file.write(reinterpret_cast<const char*>(&b->building_time),
                            sizeof(b->building_time));

                    size_t cost_count = b->cost_fulfilled.size();
                    file.write(reinterpret_cast<const char*>(&cost_count), sizeof(cost_count));
                    for (auto& p : b->cost_fulfilled) {
                        auto r = static_cast<int>(p.first);
                        file.write(reinterpret_cast<const char*>(&r), sizeof(r));
                        file.write(reinterpret_cast<const char*>(&p.second),
                                sizeof(p.second));
                    }

                    size_t frac_count = b->fractional_buffer.size();
                    file.write(reinterpret_cast<const char*>(&frac_count), sizeof(frac_count));
                    for (auto& p : b->fractional_buffer) {
                        auto r = static_cast<int>(p.first);
                        file.write(reinterpret_cast<const char*>(&r), sizeof(r));
                        file.write(reinterpret_cast<const char*>(&p.second),
                                sizeof(p.second));
                    }
                }
                else if (o->category == ObjectCategory::FARM_CROP) {
                    Vegetable* v = static_cast<Vegetable*>(o);

                    auto type = static_cast<int>(v->type);
                    file.write(reinterpret_cast<const char*>(&type), sizeof(type));
                    file.write(reinterpret_cast<const char*>(&v->is_seed),
                            sizeof(v->is_seed));

                    file.write(reinterpret_cast<const char*>(&v->quantity),
                            sizeof(v->quantity));
                    file.write(reinterpret_cast<const char*>(&v->growth_stage),
                            sizeof(v->growth_stage));
                    file.write(reinterpret_cast<const char*>(&v->next_update_sec),
                            sizeof(v->next_update_sec));
                    file.write(reinterpret_cast<const char*>(&v->collecting_duration_max),
                            sizeof(v->collecting_duration_max));

                    file.write(reinterpret_cast<const char*>(&v->tileset_object_x),
                            sizeof(v->tileset_object_x));
                    file.write(reinterpret_cast<const char*>(&v->tileset_object_y),
                            sizeof(v->tileset_object_y));

                    auto slot = static_cast<int>(v->equipement_slot);
                    file.write(reinterpret_cast<const char*>(&slot), sizeof(slot));
                }
            }
            id++;
        }
    }


    std::vector<TileMap>
    load_MapTiles(const std::string& path) {
        std::vector<TileMap> tile_map;
        std::ifstream file(path, std::ios::binary);
        if (!file.is_open()) return tile_map;

        size_t map_count = 0;
        file.read(reinterpret_cast<char*>(&map_count), sizeof(map_count));
        tile_map.resize(static_cast<size_t>(Map::size.y*Map::size.x));

        for (size_t id = 0; id < map_count; id++) {
            //int x = i % static_cast<size_t>(Map::size.x);
            //int y = i / static_cast<size_t>(Map::size.x);
            size_t x;
            size_t y;

            file.read(reinterpret_cast<char*>(&x), sizeof(x));
            file.read(reinterpret_cast<char*>(&y), sizeof(y));
            auto& map = tile_map[y*static_cast<size_t>(Map::size.x)+x];
            // --- background ---
            file.read(reinterpret_cast<char*>(&map.background.set_id), sizeof(map.background.set_id));
            file.read(reinterpret_cast<char*>(&map.background.object_id), sizeof(map.background.object_id));
            file.read(reinterpret_cast<char*>(&map.background.set_category), sizeof(map.background.set_category));
            file.read(reinterpret_cast<char*>(&map.background.set_position), sizeof(map.background.set_position));

            // --- overlays ---
            std::size_t overlay_count = 0;
            file.read(reinterpret_cast<char*>(&overlay_count), sizeof(overlay_count));

            map.overlays.clear();
            map.overlays.resize(overlay_count);

            for (std::size_t i = 0; i < overlay_count; ++i) {
                Tile_s& ov = map.overlays[i];

                file.read(reinterpret_cast<char*>(&ov.set_id), sizeof(ov.set_id));
                file.read(reinterpret_cast<char*>(&ov.object_id), sizeof(ov.object_id));
                file.read(reinterpret_cast<char*>(&ov.set_category), sizeof(ov.set_category));
                file.read(reinterpret_cast<char*>(&ov.set_position), sizeof(ov.set_position));
            }

            TileSet& tileset = Tile::get_TilesetFromMapTile(map.background);
            map.background.object_id = static_cast<size_t>(map.background.set_position.y*tileset.size.x + map.background.set_position.x);
            map.background.set_category = Tile::tilesets[TILE_BACKGROUND][map.background.set_id].category;
            // std::cout << "SCM: " << map.background.set_id << " " << map.background.object_id<< std::endl;

            // --- objects ---
            size_t obj_count = 0;
            file.read(reinterpret_cast<char*>(&obj_count), sizeof(obj_count));
            map.objects.resize(obj_count);

            for (auto& map_object : map.objects) {
                TilesetObject obj;

                // TilesetObject
                file.read(reinterpret_cast<char*>(&obj.position.x), sizeof(obj.position.x));
                file.read(reinterpret_cast<char*>(&obj.position.y), sizeof(obj.position.y));
                file.read(reinterpret_cast<char*>(&obj.position.w), sizeof(obj.position.w));
                file.read(reinterpret_cast<char*>(&obj.position.h), sizeof(obj.position.h));

                file.read(reinterpret_cast<char*>(&obj.draw_from_this_tile),
                        sizeof(obj.draw_from_this_tile));

                int object_type;
                file.read(reinterpret_cast<char*>(&object_type), sizeof(object_type));

                int cat_i;
                file.read(reinterpret_cast<char*>(&cat_i), sizeof(cat_i));
                ObjectCategory category = static_cast<ObjectCategory>(cat_i);

                int tt;
                file.read(reinterpret_cast<char*>(&tt), sizeof(tt));
                TileType tileset_type = static_cast<TileType>(tt);

                file.read(reinterpret_cast<char*>(&obj.tileset_info.set_id),
                        sizeof(obj.tileset_info.set_id));
                file.read(reinterpret_cast<char*>(&obj.tileset_info.object_id),
                        sizeof(obj.tileset_info.object_id));

                file.read(reinterpret_cast<char*>(&obj.z_index), sizeof(obj.z_index));



                // SET DATA TO REAL OBJECT AND MAP OBJECT
                SDL_Point wp = {static_cast<int>(x)-obj.position.x, static_cast<int>(y)-obj.position.y};
                Object* o = Object::get_ObjectByWorldPositionAndTilesetObjectID(wp, obj.tileset_info.object_id);
                Object* on = nullptr;

                SDL_Point dim = {obj.position.w, obj.position.h};


                switch (category) {

                    case ObjectCategory::STATIC: {
                        on = new Static(
                                static_cast<ObjectType>(object_type),
                                ObjectCategory::STATIC,
                                tileset_type,
                                obj.tileset_info.set_id,
                                obj.tileset_info.object_id,
                                wp,
                                dim,
                                obj.z_index);
                        break;
                    }

                    case ObjectCategory::RESOURCE: {
                        Resource_s r = Tile::get_ResourceFromTileSet(
                                obj.tileset_info.set_id,
                                obj.tileset_info.object_id);

                        on = new Resource(
                                static_cast<ObjectType>(object_type),
                                ObjectCategory::RESOURCE,
                                tileset_type,
                                obj.tileset_info.set_id,
                                obj.tileset_info.object_id,
                                wp,
                                dim,
                                obj.z_index,
                                r.resource,
                                r.quantity,
                                r.collecting_methode,
                                r.collecting_duration);
                        break;
                    }

                    case ObjectCategory::BUILDING: {
                        int bt_i;
                        file.read(reinterpret_cast<char*>(&bt_i), sizeof(bt_i));
                        BuildingType bt = static_cast<BuildingType>(bt_i);

                        int status_i;
                        file.read(reinterpret_cast<char*>(&status_i), sizeof(status_i));
                        BuildingStatus status = static_cast<BuildingStatus>(status_i);

                        float time;
                        file.read(reinterpret_cast<char*>(&time), sizeof(time));

                        size_t cost_count;
                        file.read(reinterpret_cast<char*>(&cost_count), sizeof(cost_count));

                        std::unordered_map<ResourceType, int> cost_f;
                        for (size_t i = 0; i < cost_count; ++i) {
                            int r, v;
                            file.read(reinterpret_cast<char*>(&r), sizeof(r));
                            file.read(reinterpret_cast<char*>(&v), sizeof(v));
                            cost_f[static_cast<ResourceType>(r)] = v;
                        }

                        size_t frac_count;
                        file.read(reinterpret_cast<char*>(&frac_count), sizeof(frac_count));

                        std::unordered_map<ResourceType, float> frac_b;
                        for (size_t i = 0; i < frac_count; ++i) {
                            int r;
                            float v;
                            file.read(reinterpret_cast<char*>(&r), sizeof(r));
                            file.read(reinterpret_cast<char*>(&v), sizeof(v));
                            frac_b[static_cast<ResourceType>(r)] = v;
                        }

                        on = new Building(
                                static_cast<ObjectType>(object_type),
                                ObjectCategory::BUILDING,
                                tileset_type,
                                obj.tileset_info.set_id,
                                obj.tileset_info.object_id,
                                wp,
                                dim,
                                obj.z_index,
                                bt);

                        Building* b = static_cast<Building*>(on);
                        b->building_status   = status;
                        b->building_time     = time;
                        b->cost_fulfilled    = std::move(cost_f);
                        b->fractional_buffer = std::move(frac_b);
                        break;
                    }

                    case ObjectCategory::FARM_CROP: {
                        int type_i;
                        bool is_seed;
                        file.read(reinterpret_cast<char*>(&type_i), sizeof(type_i));
                        file.read(reinterpret_cast<char*>(&is_seed), sizeof(is_seed));

                        Vegetable* v = new Vegetable(static_cast<VegetableData::Type>(type_i), wp, is_seed);

                        file.read(reinterpret_cast<char*>(&v->quantity), sizeof(v->quantity));
                        file.read(reinterpret_cast<char*>(&v->growth_stage),
                                sizeof(v->growth_stage));
                        file.read(reinterpret_cast<char*>(&v->next_update_sec),
                                sizeof(v->next_update_sec));
                        file.read(reinterpret_cast<char*>(&v->collecting_duration_max),
                                sizeof(v->collecting_duration_max));

                        file.read(reinterpret_cast<char*>(&v->tileset_object_x),
                                sizeof(v->tileset_object_x));
                        file.read(reinterpret_cast<char*>(&v->tileset_object_y),
                                sizeof(v->tileset_object_y));

                        int slot;
                        file.read(reinterpret_cast<char*>(&slot), sizeof(slot));
                        v->equipement_slot = static_cast<EquipmentSlot>(slot);

                        on = v;
                        break;
                    }

                    default:
                        break;
                }


                if (!o && on) {
                    Object::objects.push_back(on);
                    o = on;
                }

                /*
                std::cout << "OP: " << obj.position.x << " " << obj.position.y << " ID " << o->id << std::endl;
                std::cout << "WP " << wp.x << " " << wp.y << std::endl;
                std::cout << "DM: " << obj.position.w << " " << obj.position.h << " ID " << obj.tileset_info.object_id << std::endl;
                */
                map_object = {{obj.position.x, obj.position.y}, obj.draw_from_this_tile, o ? o->id : 0};
            }
        }

        return tile_map;
    }

    inline bool
    isInView(float x, float y, float w, float h, const SDL_Rect &win) {
        return !(x + w < 0 ||
                 y + h < 0 ||
                 x > win.w ||
                 y > win.h);
    }

    inline int
    waveFrame( float t, int x_i, int y_i, int frame_count, float speed = 1.8f) {
        float wave =
            sinf(t * speed + x_i * 0.4f + y_i * 0.6f) +
            sinf(t * speed * 0.66f - x_i * 0.7f + y_i * 0.3f) * 0.5f;

        wave *= (1.0f / 1.5f);

        float scaled = (wave + 1.0f) * (frame_count * 0.5f);
        int frame = (int)scaled;

        if (frame < 0) frame = 0;
        if (frame >= frame_count) frame = frame_count - 1;

        return frame;
    }

    void
    draw_MapTiles(Game* game) {
        // std::cout << "FL " << Map::offset.x << " " << Map::offset.y << " " << Default::game_scale << std::endl;
        SDL_Renderer* renderer = game->renderer;
        Character& player = *game->state_playing->player;
        SDL_Rect win_size = game->win_size;

        float view_x0 = -Map::offset.x;
        float view_y0 = -Map::offset.y;
        float view_x1 = view_x0 + win_size.w / Scale(1.f);
        float view_y1 = view_y0 + win_size.h / Scale(1.f);

        int size = Tile::get_Size();

        global_frame_time += Default::delta_time;
        /*
        if (global_frame_time >= global_frame_time_max) {
            frame_water = frame_water > 0 ? frame_water-1 : 3;
            global_frame_time = 0.0f;
            if (frame_water == 0)
                global_frame_time_max = 0.3f + ((float)rand() / (float)RAND_MAX) * 1.2f;
            //std::cout << "global_frame_time_max: " << global_frame_time_max << std::endl;
        }
        */
        frame_meadow_time += Default::delta_time;
        if (frame_meadow_time >= frame_meadow_time_max) {
            frame_meadow++;
            frame_meadow %= 3;
            frame_meadow_time = 0.0f;
            if (frame_meadow == 0)
                frame_meadow_time_max = 1.4f + ((float)rand() / (float)RAND_MAX) * 2.3f;
            //std::cout << "frame_meadow_time_max: " << frame_meadow_time_max << std::endl;
        }
        for (size_t i = 0; i < Map::tile_map.size(); ++i) {
            // START draw background tiles
            Tile_s& tile = Map::tile_map[i].background;

            int x_i = i % static_cast<size_t>(Map::size.x);
            int y_i = i / static_cast<size_t>(Map::size.x);

            float world_x = x_i * size;
            float world_y = y_i * size;

            float world_x2 = world_x + size;
            float world_y2 = world_y + size;

            // CULL TILE IF OUTSIDE CAMERA
            if (world_x2 < view_x0 || world_x > view_x1 ||
                    world_y2 < view_y0 || world_y > view_y1)
                continue;

            float xf = world_x + Map::offset.x;
            float yf = world_y + Map::offset.y;

            SDL_FRect dst {
                    Scale(xf),
                    Scale(yf),
                    Scale(size),
                    Scale(size)
            };

            SDL_Point set_position = tile.set_position;


            if (tile.set_category == TileCategory::TILE_WATER && set_position.y < 5) {
                float local_speed = 1.8f * (0.8f + 0.4f * sinf(x_i*0.3f + y_i*0.5f));
                int frame = waveFrame(global_frame_time, x_i, y_i, 4, local_speed);
                set_position.x += frame * 6;
            }
            else if (tile.set_category == TileCategory::TILE_GROUND && set_position.y == 10) {
                float local_speed = 1.5f * (0.85f + 0.3f * sinf(x_i*0.25f + y_i*0.35f));
                int frame = waveFrame(global_frame_time, x_i, y_i, 3, local_speed);
                set_position.x += frame;
            }
            SDL_FRect src {
                    static_cast<float>(set_position.x) * Tile::tile_size,
                    static_cast<float>(set_position.y) * Tile::tile_size,
                    (float)Tile::tile_size,
                    (float)Tile::tile_size
            };

            SDL_RenderTexture(renderer,
                    Tile::tilesets[TILE_BACKGROUND][tile.set_id].texture,
                    &src,
                    &dst);

            for (Tile_s& overlay : Map::tile_map[i].overlays) {
                if (overlay.set_category == TileCategory::TILE_OVERLAY_FARM) {
                    Farm::soil_Render(renderer, {xf, yf}, overlay.set_position);
                }
            }
            // END draw background tiles


        }

        draw_ObjectTiles(renderer, player, win_size);

        // if (Default::game_state == GAME_PLAY) return;

        if (Default::modify_state == MODIFY_COLLISSION) {
            size_t selection_size = Tile::get_SelectionSize(Default::modify_state);
            const float cell_size = static_cast<float>(selection_size);
            size_t map_width  = static_cast<size_t>(Map::size.x)*(Tile::tile_size/Tile::tile_collider_size);
            size_t map_height = static_cast<size_t>(Map::size.y)*(Tile::tile_size/Tile::tile_collider_size);

            SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
            SDL_SetRenderDrawColor(renderer, 255, 0, 0, 100);

            for (size_t y = 0; y < map_height; ++y) {
                for (size_t x = 0; x < map_width; ++x) {
                    size_t index = y * map_width + x;
                    if (!Map::tile_collission_map[index])
                        continue;

                    SDL_FRect cell_rect = {
                        Scale(x * cell_size + Map::offset.x),
                        Scale(y * cell_size + Map::offset.y),
                        Scale(cell_size),
                        Scale(cell_size)
                    };

                    // draw cell outline
                    SDL_RenderRect(renderer, &cell_rect);

                    // draw cross
                    SDL_RenderLine(renderer,
                            cell_rect.x, cell_rect.y,
                            cell_rect.x + cell_rect.w, cell_rect.y + cell_rect.h);
                    SDL_RenderLine(renderer,
                            cell_rect.x + cell_rect.w, cell_rect.y,
                            cell_rect.x, cell_rect.y + cell_rect.h);
                }
            }
        }
    }


    void draw_SelectionBorder(SDL_Renderer* renderer, Input& input) {
        // START render selecting border
        bool cursor_over_tile = Tile::hovering_set;
        if (cursor_over_tile) return;

        int selection_size = Tile::get_SelectionSize(Default::modify_state);
        SDL_FRect tile_rect {
            Scale(input.mouse_tile_position.x * selection_size + Map::offset.x),
            Scale(input.mouse_tile_position.y * selection_size + Map::offset.y),
            Scale(selection_size) * Tile::selection_tile_dimension.x,
            Scale(selection_size) * Tile::selection_tile_dimension.y
        };

        SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
        SDL_RenderRect(renderer, &tile_rect);
        // END  render selecting border
    }

    void
    draw_ObjectTiles(SDL_Renderer* renderer, Character& player, SDL_Rect win_size) {
        int size = Tile::get_Size();

        std::vector<bool> enemies_drawn(Enemy::enemies.size(), false);

        // draw objects and character
        bool character_drawn = false;
        bool player_behind   = false;

        float character_collider_x = player.collider_world.x; // - player.collider.w*0.5;
        float character_collider_y = player.collider_world.y; // - player.collider.h*0.5;

        for (Bird* bird : Bird::birds)
            bird->drawn = false;

        for (size_t i = 0; i < Map::tile_map.size(); ++i) {
            TileMap& tilemap = Map::tile_map[i];

            int x_i = i % static_cast<size_t>(Map::size.x);
            int y_i = i / static_cast<size_t>(Map::size.x);

            float xf = (x_i * size);
            float yf = (y_i * size);

            SDL_FPoint xy_point = {xf+size, yf+size};

            // Render Character if no object is hiding it
            player_behind = false;

            for (TileObject& o : tilemap.objects) {
                Object* object = Object::get_ObjectById(o.object_id);
                // o = relative position,
                // so can be 0 0, 0 1, 1 0, 1 1 if object size is 2 2
#ifdef DEBUG_BUILD
                // START DEBUG RECT
                SDL_FRect dbg = {
                    Scale(xf + Map::offset.x),
                    Scale(yf + Map::offset.y),
                    Scale(size),
                    Scale(size)};
                SDL_FRect dbg_rect = dbg;
                SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
                SDL_RenderRect(renderer, &dbg_rect);
                // START DEBUG RECT
#endif
                //if (o.position.x != 0 || o.position.y != o.position.h-1) continue;
                // real object 0,0 start in map pixel
                float m_xf = xf - o.position.x*size; 
                float m_yf = yf - o.position.y*size; 

                float width  = static_cast<int>(object->dimension.x) * size;
                float height = static_cast<int>(object->dimension.y) * size;

                SDL_FPoint m_point = {m_xf+width, m_yf+height};

                SDL_FRect obj_bb {
                    Scale(m_xf + Map::offset.x),
                    Scale(m_yf + Map::offset.y),
                    Scale(width),
                    Scale(height)
                };

                if (!isInView(obj_bb.x, obj_bb.y, obj_bb.w, obj_bb.h, win_size))
                    continue;
                //if (!o.draw_from_this_tile) continue;

                size_t j = 0;
                //std::cout << "PS "<< o.position.x<< " " <<o.position.y <<std::endl;
                for (Enemy* enemy : Enemy::enemies) {
                    SDL_FPoint enemy_bb {
                        static_cast<float>(enemy->collider_world.x),
                        static_cast<float>(enemy->collider_world.y),
                    };
                    if (!isInView(enemy_bb.x, enemy_bb.y, enemy->collider.w, enemy->collider.h, win_size)) {
                        j++;
                        continue;
                    }
                    if ( !enemies_drawn[j] &&
                         (enemy->stats.health <= 0 ||
                         (enemy_bb.x >= m_xf &&
                          enemy_bb.y >= m_yf &&
                          enemy_bb.x + enemy->collider.w <= m_point.x &&
                          enemy_bb.y <= m_point.y &&
                          object->z_index > 0 ))) {
                        if ( enemy->stats.health <= 0 ||
                             object->is_InFrontOf({m_xf, m_yf},
                                    {enemy_bb.x,
                                     enemy_bb.y,
                                     enemy->collider.w,
                                     enemy->collider.h})) {
                            enemy->Draw(renderer);
                            enemies_drawn[j] = true;
                        }
                    }
                    j++;
                }



                player_behind = false;
                int am_i_behind_an_object = object->is_InFrontOf({m_xf, m_yf},
                        {character_collider_x,
                        character_collider_y,
                        player.collider.w,
                        player.collider.h});
                if (am_i_behind_an_object) {
                    if ( am_i_behind_an_object == 1 &&
                            character_collider_y-player.height/3 > m_yf &&
                            ( player.collider_world.x > m_xf ||
                              player.collider_world.x < m_xf+object->dimension.x ) )
                        player_behind = true;
                    if (!character_drawn) {
                        //std::cout << "DRAW BEHIND\n";
                        player.Draw(renderer);
                        character_drawn = true;
                    }
                }
                for (Bird* bird : Bird::birds) {
                    if (bird->drawn)
                        continue;
                    if ( static_cast<int>(bird->pos_x/Tile::tile_size) != x_i ||
                            static_cast<int>((bird->pos_y-bird->altitude)/Tile::tile_size) != y_i)
                        continue;

                    bird->Render(renderer);
                    // std::cout << "I AM GETTING CALLED BIRD AMP " << bird->pos_y << " " << bird->altitude << std::endl;
                    bird->drawn = true;
                }
                //std::cout <<  player_behind << std::endl;
                //std::cout << "CHA: " << character_collider_x << " " << character_collider_y << " " << player.collider.w << " " << player.collider.h << std::endl;
                //std::cout << "OJJ: " << m_xf << " " << m_yf << " " << m_point.x << " " << m_point.y << std::endl;



                // START OBJECT DRAW
                if (o.draw_from_this_tile)
                    object->Draw(renderer, {m_xf, m_yf}, player_behind);
                // END OBJECT DRAW

            }
            size_t j = 0;
            if ( !character_drawn &&
                     player.collider_world.x >= xf &&
                     player.collider_world.x <= xy_point.x &&
                     player.collider_world.y >= yf &&
                     player.collider_world.y <= xy_point.y) {
                player.Draw(renderer);
                character_drawn = true;
            }
            for (Enemy* enemy : Enemy::enemies) {
                if ( !enemies_drawn[j] &&
                     enemy->stats.health > 0 &&
                     enemy->collider_world.x >= xf &&
                     enemy->collider_world.x <= xy_point.x &&
                     enemy->collider_world.y >= yf &&
                     enemy->collider_world.y <= xy_point.y) {
                    enemy->Draw(renderer);
                    enemies_drawn[j] = true;
                }
                j++;
            }
        }
        for (Bird* bird : Bird::birds)
            if (!bird->drawn) {
                    //std::cout << "BIRD AMP " << bird->pos_y << " " << bird->altitude << std::endl;
                bird->Render(renderer);
            }
    }


    void
    set_Offset(Input& input) {
        // Set map map offset set
        if (!input.click_left && Map::offset_to_set.x != 0 && Map::offset_to_set.y != 0) {
            Map::offset_to_set.x = 0;
            Map::offset_to_set.y = 0;
            Map::setting_offset = false;
        }
        // Set map offset
        else if (Map::offset_to_set.x != 0 && Map::offset_to_set.y != 0) {
            Map::offset.x = input.mouse_state.x - Map::offset_to_set.x;
            Map::offset.y = input.mouse_state.y - Map::offset_to_set.y;
        }
        // Update map offset set
        if (input.click_left && input.pressed_ctrl &&
                (Map::offset_to_set.x == 0 && Map::offset_to_set.y == 0)) {
            Map::offset_to_set.x = input.mouse_state.x - Map::offset.x;
            Map::offset_to_set.y = input.mouse_state.y - Map::offset.y;
            Map::setting_offset = true;
        }
    }

    void
    set_Collission(Input& input) {
        size_t cells_per_tile = Tile::tile_size/Tile::tile_collider_size;
        const size_t map_width = static_cast<size_t>(Map::size.x)*cells_per_tile;

        for (size_t y = 0; y < static_cast<size_t>(Tile::selection_tile_dimension.y); ++y) {
            for (size_t x = 0; x < static_cast<size_t>(Tile::selection_tile_dimension.x); ++x) {

                size_t cell_x = static_cast<size_t>(input.mouse_tile_position.x) + x;
                size_t cell_y = static_cast<size_t>(input.mouse_tile_position.y) + y;
                size_t id = cell_y * map_width + cell_x;

                if (id >= Map::tile_collission_map.size())
                    continue;

                Map::tile_collission_map[id] = !Map::tile_collission_map[id];
            }
        }
    }

    std::vector<TileObject>&
    get_ObjectsFromMapTile(SDL_Point tile_point) {
        int index = static_cast<int>(tile_point.y * Map::size.x + tile_point.x);
        return Map::tile_map[static_cast<size_t>(index)].objects;
    }


    void
    place_ConstructionSide() {
        TilesetObject object = BuildingData::active_build.object;
        // object.building_status = TileBuildingStatus::CONSTRUCT;

        TileType type = Tile::get_Type(object.tileset_info.set_category);

        std::vector<bool> collision_map =
            Tile::get_ObjectCollisionsFromTileset(object.tileset_info.set_id, object.tileset_info.object_id, type);

        // std::cout << "I " << object.tileset_info.set_id << " " << object.tileset_info.object_id << " " << (int)type <<  " " << (int)BuildingData::active_build.type <<std::endl;

        // set draw tile
        SDL_Point draw_tile = {0,0};
        int s = Tile::tile_size / Tile::tile_collider_size;
        int width_c = object.position.w * s;
        for (int y = 0; y < object.position.w; y++) {
            for (int x = 0; x < object.position.w; x++) {
                for (int yc = y * s; yc < y + 1 * s; yc++) {
                    for (int xc = x * s; xc < x + 1 * s; xc++) {
                        size_t i = static_cast<size_t>(yc * width_c + xc);
                        if (i >= collision_map.size()) break;
                        if (collision_map[i] && y > draw_tile.y) {
                            if (x <= draw_tile.x) draw_tile = {x, y};
                        }
                    }
                }
            }
        }


        // place in map array
        Building* building = new Building(object.object_type,
                                          ObjectCategory::BUILDING,
                                          type,
                                          object.tileset_info.set_id,
                                          object.tileset_info.object_id,
                                          {static_cast<int>(BuildingData::active_build.tile_position.x), static_cast<int>(BuildingData::active_build.tile_position.y)},
                                          {object.position.w, object.position.h},
                                          object.z_index,
                                          BuildingData::active_build.type);
        Object::objects.push_back(building);

        for (int y = 0; y < object.position.h; y++) {
            for (int x = 0; x < object.position.w; x++) {
                SDL_Point world_position = {BuildingData::active_build.tile_position.x+x, BuildingData::active_build.tile_position.y+y};
                object.position.x = x;
                object.position.y = y;
                object.draw_from_this_tile = false;
                size_t index = static_cast<size_t>(world_position.y*Map::size.x + world_position.x);

                if (x == draw_tile.x && y == draw_tile.y)
                    object.draw_from_this_tile = true;
                Map::tile_map[index].objects.push_back({{x, y}, object.draw_from_this_tile, building->id});
                // Sort map objects
                std::sort(Map::tile_map[index].objects.begin(),
                        Map::tile_map[index].objects.end(),
                        [](const TileObject& a, const TileObject& b) {
                        Object* oa = Object::get_ObjectById(a.object_id);
                        Object* ob = Object::get_ObjectById(b.object_id);
                        return oa->z_index < ob->z_index;
                        });
                set_TileCollissionForObjects({world_position.x, world_position.y});

            }
        }
        std::cout << "PLACING!\n";
    }

    bool
    is_FreePosition(SDL_FRect candidate_rect) {
        int left_tile   = static_cast<int>(candidate_rect.x / Tile::tile_collider_size);
        int top_tile    = static_cast<int>(candidate_rect.y / Tile::tile_collider_size);
        int right_tile  = static_cast<int>((candidate_rect.x + candidate_rect.w) / Tile::tile_collider_size);
        int bottom_tile = static_cast<int>((candidate_rect.y + candidate_rect.h) / Tile::tile_collider_size);

        for (int ty = top_tile; ty <= bottom_tile; ++ty) {
            for (int tx = left_tile; tx <= right_tile; ++tx) {
                if (tx < 0 || ty < 0 || tx >= (Map::size.x*Tile::tile_collider_size) || ty >= (Map::size.y*Tile::tile_collider_size)) {
                    return false;
                }

                size_t idx = static_cast<size_t>(ty * Map::size.x*Tile::tile_collider_size + tx);
                if (Map::tile_collission_map[idx]) {
                    return false;
                }
            }
        }

        // CHECK ENEMY COLLISION
        if (Enemy::is_CollidingWithEnemy(candidate_rect))
                return false;

        return true;
    }


    SDL_FPoint
    get_ClosestPointWithoutCollision(const SDL_FRect &rect) {
        int cx_tile = static_cast<int>(rect.x / Tile::tile_size);
        int cy_tile = static_cast<int>(rect.y / Tile::tile_size);

        if (is_FreePosition({rect.x - rect.w*0.5f, rect.y - rect.h*0.5f, rect.w, rect.h}))
            return { rect.x, rect.y };

        struct TilePos { int x, y; };
        std::queue<TilePos> q;
        std::set<std::pair<int,int>> visited;

        q.push({ cx_tile, cy_tile });
        visited.insert({ cx_tile, cy_tile });

        const int dirs[4][2] = { {1,0}, {-1,0}, {0,1}, {0,-1} };

        while (!q.empty()) {
            TilePos t = q.front();
            q.pop();

            float px = static_cast<size_t>(t.x) * Tile::tile_size;
            float py = static_cast<size_t>(t.y) * Tile::tile_size;

            SDL_FRect test_rect = { px - rect.w*0.5f, py - rect.h*0.5f, rect.w, rect.h };
            if (is_FreePosition(test_rect))
                return { px, py };

            for (int i = 0; i < 4; ++i)
            {
                int nx = t.x + dirs[i][0];
                int ny = t.y + dirs[i][1];

                if (nx < 0 || ny < 0 || nx >= static_cast<int>(Map::size.x) || ny >= static_cast<int>(Map::size.y) )
                    continue;

                if (visited.insert({ nx, ny }).second)
                    q.push({ nx, ny });
            }
        }

        std::cout << "No point found!\n";
        return { 0,0 };
    }

};