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

#include <stdlib.h>
#include <set>

#include "map/map.hpp"
#include "map/path_finding.hpp"


std::vector<Enemy*> Enemy::enemies;
std::vector<SpawnParams> Enemy::enemies_to_spawn;

SDL_FPoint Enemy::colider_default = {7, 7};
EnemyAnimation Enemy::animation_all;
size_t Enemy::tile_size = 64;

// Persistent thread pool
std::vector<SDL_Thread*> Enemy::workers;
std::vector<WorkerData> Enemy::job_queue;
std::mutex Enemy::queue_mutex;
std::condition_variable Enemy::queue_cv;

bool Enemy::stop_threads = false;

int Enemy::jobs_remaining = 0;
std::condition_variable Enemy::jobs_done_cv;

int Enemy::id_next = 1;

Enemy::Enemy(WeaponType weapon_type, WeaponMaterial weapon_material, SDL_FPoint position, EnemyStats status)
    : weapon(weapon_type, weapon_material) {
        x = position.x;
        y = position.y;
        stats = status;

        id = id_next++;

        detection_radius = 150 + rand() % 51;

        speed = 40.0f;
        run = 1.4f;
        direction = WalkDirection::DOWN;
        moving = false;
        flip = false;
        collider.w = colider_default.x;
        collider.h = colider_default.y;
        collider.x = (tile_size/2 - collider.w/2);
        collider.y = tile_size - collider.h - 16;
        collider_world.x = x + collider.x + collider.w * 0.5f;
        collider_world.y = y + collider.y + collider.h * 0.5f;
        arm_reach_radius = 6;
        resources = {};
        point_in_reach = false;

        dist_min = weapon.dimension.x-8;
        dist_max = weapon.dimension.x;

        max_death_time = 10.0;

        stats = status;

        death_duration = 0;
        death_duration_max = 0.8;
        direction_blocked[WalkDirection::DOWN]  = false;
        direction_blocked[WalkDirection::UP]    = false;
        direction_blocked[WalkDirection::LEFT]  = false;
        direction_blocked[WalkDirection::RIGHT] = false;
        animation = animation_all;
    }

void
Enemy::init_All(SDL_Renderer* renderer) {
    animation_all.run[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Run/Run_up.png", tile_size, tile_size, "None", false, false);
    animation_all.run[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Run/Run.png", tile_size, tile_size, "None", false, false);
    animation_all.run[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Run/Run_up.png", tile_size, tile_size, "None", false, false);

    animation_all.idle[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "None", false, false);
    animation_all.idle[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "None", false, false);
    animation_all.idle[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "None", false, false);

    animation_all.attack[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Attack/Attack_Down.png", tile_size, tile_size, "enemy.attack", false, true);
    animation_all.attack[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Attack/Attack.png",      tile_size, tile_size, "enemy.attack", false, true);
    animation_all.attack[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Attack/Attack_Up.png",   tile_size, tile_size, "enemy.attack", false, true);

    animation_all.hit[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "character.hit", false, false);
    animation_all.hit[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "character.hit", false, false);
    animation_all.hit[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Idle/Idle-Sheet_padded.png", tile_size, tile_size, "character.hit", false, false);

    animation_all.death[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Death/Death-Sheet_processed.png", tile_size, tile_size, "None", false, false);
    animation_all.death[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Death/Death-Sheet_processed.png", tile_size, tile_size, "None", false, false);
    animation_all.death[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Mobs/Skeleton Crew/Skeleton - Base/Death/Death-Sheet_processed.png", tile_size, tile_size, "None", false, false);

    init_Workers(10);
}


void
Enemy::Init() {
    handle_Animation();

    set_Status(EnemyStatus::IDLE);

    // REINIT POSITION
    SDL_FPoint new_position = Map::get_ClosestPointWithoutCollision(
                                    {static_cast<float>(collider_world.x), static_cast<float>(collider_world.y),
                                    collider.w, collider.h});

    if (new_position.x == 0 && new_position.y == 0) {
        std::cout << "No position found!\n";
        return;
    }
    x = new_position.x - collider.x - collider.w * 0.5; 
    y = new_position.y - collider.y - collider.h * 0.5; 

}


void Enemy::set_Status(const EnemyStatus& s) {
    status = s;
    active_anim->timer = 0;
    active_anim->current_frame = 0;
    Animation::animation_frame_time = active_anim->frame_time;
}

void
Enemy::handle_Animation() {
    AnimationData* active_anim_ref = nullptr;
    WalkDirection dir = direction;
    if ( direction == WalkDirection::LEFT ||
         direction == WalkDirection::RIGHT)
        dir = WalkDirection::SIDE;


    switch (status) {
        case EnemyStatus::RUN:
            active_anim_ref = &animation.run[dir];
            break;

        case EnemyStatus::ATTACK:
            active_anim_ref = &animation.attack[dir];
            break;

        case EnemyStatus::DYING:
            active_anim_ref = &animation.death[dir];
            break;

        case EnemyStatus::DEAD:
            active_anim_ref = &animation.death[dir];
            break;

        case EnemyStatus::HIT:
            active_anim_ref = &animation.hit[dir];
            break;

        default:
            active_anim_ref = &animation.idle[dir];
            break;
    }

    if (status == EnemyStatus::DEAD) {
        if (active_anim_ref) {
            AnimationData& ad = *active_anim_ref;

            // lock animation to last frame
            if (!ad.frames.empty()) {
                ad.current_frame = ad.frames.size() - 1;
            }

            // also stop the timer so update() never progresses
            ad.timer = 0.0f;
        }

        active_anim = active_anim_ref;
        return; // absolutely do NOT call update() when dead
    }

    if (active_anim_ref) {
        Animation::update(*active_anim_ref, static_cast<float>(Default::delta_time));
        active_anim = active_anim_ref;
    }
}

void
Enemy::Draw(SDL_Renderer* renderer) {
#ifdef DEBUG_BUILD
    // DEBUG COLLIDER
    SDL_FRect debug_rect {
        Scale((x + collider.x) + Map::offset.x),
        Scale((y + collider.y) + Map::offset.y),
        Scale(collider.w),
        Scale(collider.h)
    };
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    SDL_RenderRect(renderer, &debug_rect);
    // DEBUG CHARACTER RECTANGLE
    SDL_SetRenderDrawColor(renderer, 255, 255,255, 255);
    SDL_FRect debug_rect_1 {
        Scale(x+Map::offset.x),
        Scale(y+Map::offset.y),
        Scale(tile_size),
        Scale(tile_size)
    };
    SDL_RenderRect(renderer, &debug_rect_1);

    // DEBUG Arm Range
    SDL_SetRenderDrawColor(renderer, 0, 255, 0, 100);
    for (float angle = 0; angle < 2 * M_PI; angle += 0.1f) {
        float px = collider_world.x + cosf(angle) * arm_reach_radius + Map::offset.x;
        float py = collider_world.y + sinf(angle) * arm_reach_radius + Map::offset.y;
        SDL_RenderPoint(renderer, (int)Scale(px), (int)Scale(py));
    }

    // DEBUG Tiles in reach
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    SDL_SetRenderDrawColor(renderer, 0, 255, 0, 80);

    for (const auto& t : tiles_in_reach) {
        SDL_FRect tile_rect = {
            Scale(t.x * Tile::tile_size) + Map::offset.x,
            Scale(t.y * Tile::tile_size) + Map::offset.y,
            Scale(Tile::tile_size),
            Scale(Tile::tile_size)
        };

        SDL_RenderFillRect(renderer, &tile_rect);
    }
#endif

    // Render Enemy
    const SDL_FRect& src = active_anim->frames[active_anim->current_frame];

    SDL_FRect dst {
            Scale(x + Map::offset.x),
            Scale(y + Map::offset.y),
            Scale(src.w),
            Scale(src.h)
    };

    /*
    if (stats.health > 0)
        if (flip) weapon.Draw(renderer, x + Map::offset.x, y + Map::offset.y);
        */
    SDL_RenderTextureRotated(renderer, active_anim->texture, &src, &dst,
            0.0, nullptr,
            flip ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
    /*

    if (stats.health > 0)
        if (!flip) weapon.Draw(renderer, x + Map::offset.x, y + Map::offset.y);
    */

    // HEALTH BAR
    if (stats.health <= 0) return;

    float bar_width  = Scale(40.0f);
    float bar_height = Scale(4.0f);

    // position above the enemy sprite
    float bar_x = Scale(x + Map::offset.x + tile_size/2) - bar_width/2;
    float bar_y = Scale(y + Map::offset.y) + bar_height*2 - Scale(4.0f);

    // background (empty bar)
    SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
    SDL_FRect bar_bg {
        bar_x-1,
        bar_y-1,
        bar_width+2,
        bar_height+2
    };
    SDL_RenderFillRect(renderer, &bar_bg);

    // current health percentage
    float ratio = 0.0f;
    if (stats.health_max > 0.0f) {
        ratio = stats.health / stats.health_max;
        if (ratio < 0.0f) ratio = 0.0f;
        if (ratio > 1.0f) ratio = 1.0f;
    }

    // foreground (current health)
    SDL_SetRenderDrawColor(renderer, 200, 0, 0, 255);
    SDL_FRect bar_fg {
        bar_x,
        bar_y,
        bar_width * ratio,
        bar_height
    };
    SDL_RenderFillRect(renderer, &bar_fg);
}

Enemy*
Enemy::is_InEnemyReach(const SDL_FRect& rect) {

    for (Enemy* enemy : Enemy::enemies) {
        if (enemy->stats.health <= 0) continue;

        SDL_FRect other = {
            enemy->x + enemy->collider.x,
            enemy->y + enemy->collider.y,
            enemy->collider.w,
            enemy->collider.h
        };

        bool overlap =
            rect.x < other.x + other.w &&
            rect.x + rect.w > other.x &&
            rect.y < other.y + other.h &&
            rect.y + rect.h > other.y;

        if (overlap)
            return enemy;
    }
    return nullptr;
}

/*
static inline double ms_since(Uint64 start) {
    static const double freq = static_cast<double>(SDL_GetPerformanceFrequency());
    return (SDL_GetPerformanceCounter() - start) * 1000.0 / freq;
}
*/

int EnemyWorkerThread(void*) {
    while (true) {
        std::unique_lock<std::mutex> lock(Enemy::queue_mutex);
        Enemy::queue_cv.wait(lock, [] {
            return Enemy::stop_threads || !Enemy::job_queue.empty();
        });

        if (Enemy::stop_threads && Enemy::job_queue.empty())
            return 0;

        WorkerData job = Enemy::job_queue.back();
        Enemy::job_queue.pop_back();
        lock.unlock();

        job.enemy->set_NextMoves(*job.grid);

        std::lock_guard<std::mutex> finish_lock(Enemy::queue_mutex);
        if (--Enemy::jobs_remaining == 0)
            Enemy::jobs_done_cv.notify_one();
    }
}

void Enemy::init_Workers(int num_threads) {
    stop_threads = false;
    jobs_remaining = 0;
    for (int i = 0; i < num_threads; ++i) {
        SDL_Thread* th = SDL_CreateThread(EnemyWorkerThread, "EnemyWorker", nullptr);
        workers.push_back(th);
    }
}

void Enemy::shutdown_Workers() {
    {
        std::lock_guard<std::mutex> lock(queue_mutex);
        stop_threads = true;
    }
    queue_cv.notify_all();

    for (SDL_Thread* th : workers) {
        SDL_WaitThread(th, nullptr);
    }
    workers.clear();
}

void Enemy::run_GetMovesThreads()
{
    std::vector<bool> grid = Map::tile_collission_map;

    std::set<int> checks = {0,1,2,3,4,5,6,7,8,12,16,32,64,128,256};

    // ---- First pass: mark grid exactly like old version ----
    for (Enemy* e : Enemy::enemies) {
        if (e->status != EnemyStatus::RUN ||
            !checks.count(e->next_moves.size()))
            continue;

        int tx = static_cast<int>(e->collider_world.x / Tile::tile_collider_size);
        int ty = static_cast<int>(e->collider_world.y / Tile::tile_collider_size);

        if (tx < 0 || ty < 0 ||
            tx >= static_cast<int>(Map::size.x * Tile::tile_collider_size) ||
            ty >= static_cast<int>(Map::size.y * Tile::tile_collider_size))
            continue;

        size_t id = static_cast<size_t>(
            ty * (Map::size.x * Tile::tile_collider_size) + tx);

        grid[id] = true;
    }

    auto grid_ptr = std::make_shared<std::vector<bool>>(std::move(grid));

    // ---- Wait for previous frame to finish ----
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        jobs_done_cv.wait(lock, [] { return jobs_remaining == 0; });
    }

    // ---- Push jobs ----
    {
        std::lock_guard<std::mutex> lock(queue_mutex);

        for (Enemy* e : Enemy::enemies) {
            if (e->status != EnemyStatus::RUN)
                continue;
            if (e->point_in_reach)
                continue;

            if (e->next_position.x == 0 || e->next_position.y == 0 )
                continue;

            job_queue.push_back({ e, grid_ptr });
            ++jobs_remaining;
        }
    }

    queue_cv.notify_all();

    // ---- Wait like old SDL_WaitThread loop ----
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        jobs_done_cv.wait(lock, [] { return jobs_remaining == 0; });
    }
}



void Enemy::Decide(Character& player) {
    //float  d_x  = player.collider_world.x - collider_world.x;
    //float  d_y  = player.collider_world.y - collider_world.y;
    //size_t dc_x = static_cast<size_t>(abs(d_x));
    //size_t dc_y = static_cast<size_t>(abs(d_y));
    EnemyMovingType r;

    if (is_character_in_range) {
        r = EnemyMovingType::CHARACTER;
    } else if (move_focus != EnemyMovingType::NONE) {
        r = move_focus;
    } else {
        // pick random type excluding CHARACTER
        do {
            r = static_cast<EnemyMovingType>(1 + rand() % (static_cast<int>(EnemyMovingType::LAST) - 1));
        } while (r == EnemyMovingType::CHARACTER);
    }
    set_MoveLogic(r, player);
}

inline bool same_corner(const SDL_FPoint& a, const SDL_FPoint& b, float epsilon = 1.0f) {
    return std::fabs(a.x - b.x) <= epsilon && std::fabs(a.y - b.y) <= epsilon;
}



inline bool Enemy::character_InRange(SDL_FPoint char_pos) const {
    float dx = char_pos.x - collider_world.x;
    float dy = char_pos.y - collider_world.y;
    float distance_squared = dx * dx + dy * dy;
    return distance_squared <= detection_radius * detection_radius;
}


bool
Enemy::set_CollisinnWithEnemy(const SDL_FRect& rect) {

    if (avoiding_object)
        return false;

    for (Enemy* enemy : Enemy::enemies) {
        if (enemy == this || enemy->stats.health <= 0) continue;

        SDL_FRect other = {
            enemy->x + enemy->collider.x,
            enemy->y + enemy->collider.y,
            enemy->collider.w,
            enemy->collider.h
        };

        bool overlap =
            rect.x < other.x + other.w &&
            rect.x + rect.w > other.x &&
            rect.y < other.y + other.h &&
            rect.y + rect.h > other.y;

        if (overlap) { 
            last_object_collision.min_x = rect.x;
            last_object_collision.max_x = rect.x+rect.w;
            last_object_collision.min_y = rect.y;
            last_object_collision.max_y = rect.y+rect.h;
            last_object_collision.valid = true;
            collidiing_with_enemy = enemy;
            return true;
        }
    }
    return false;
}

bool
Enemy::is_CollidingWithEnemy(const SDL_FRect& rect) {
    for (Enemy* enemy : Enemy::enemies) {
        if (enemy->stats.health <= 0)
            continue;

        SDL_FRect other = {
            enemy->x + enemy->collider.x,
            enemy->y + enemy->collider.y,
            enemy->collider.w,
            enemy->collider.h
        };

        bool overlap =
            rect.x < other.x + other.w &&
            rect.x + rect.w > other.x &&
            rect.y < other.y + other.h &&
            rect.y + rect.h > other.y;

        if (overlap) { 
            return true;
        }
    }
    return false;
}

/*
bool
Enemy::is_CollidingWithObjects(const SDL_FRect& rect) {
    const size_t scale = Tile::tile_size / Tile::tile_collider_size;

    // convert rectangle to collider coordinates
    int start_cx = static_cast<int>(std::floor(rect.x / float(Tile::tile_collider_size)));
    int end_cx   = static_cast<int>(std::ceil((rect.x + rect.w) / float(Tile::tile_collider_size)));
    int start_cy = static_cast<int>(std::floor(rect.y / float(Tile::tile_collider_size)));
    int end_cy   = static_cast<int>(std::ceil((rect.y + rect.h) / float(Tile::tile_collider_size)));

    // loop over tiles overlapped by rectangle in tile units
    size_t tile_start_x = static_cast<size_t>(rect.x / Tile::tile_size);
    size_t tile_end_x   = static_cast<size_t>((rect.x + rect.w) / Tile::tile_size);
    size_t tile_start_y = static_cast<size_t>(rect.y / Tile::tile_size);
    size_t tile_end_y   = static_cast<size_t>((rect.y + rect.h) / Tile::tile_size);

    bool collision_found = false;

    float min_x = std::numeric_limits<float>::max();
    float min_y = std::numeric_limits<float>::max();
    float max_x = std::numeric_limits<float>::lowest();
    float max_y = std::numeric_limits<float>::lowest();
    for (size_t ty = tile_start_y; ty <= tile_end_y && ty < Map::size.y; ++ty) {
        for (size_t tx = tile_start_x; tx <= tile_end_x && tx < Map::size.x; ++tx) {
            size_t map_index = ty * Map::size.x + tx;
            TileMap& tilemap = Map::tile_map[map_index];

            for (const TileObject& obj : tilemap.objects) {
                Object* object = Object::get_ObjectById(obj.object_id);
                std::vector<bool> collision_map = object->get_CollisionMap();
                int obj_abs_x = static_cast<int>(tx) - obj.position.x;
                int obj_abs_y = static_cast<int>(ty) - obj.position.y;

                int obj_cx = obj_abs_x * static_cast<int>(scale);
                int obj_cy = obj_abs_y * static_cast<int>(scale);
                size_t obj_cw = static_cast<size_t>(object->dimension.x) * scale;
                size_t obj_ch = static_cast<size_t>(object->dimension.y) * scale;

                for (int cy = obj_cy; cy < obj_cy+static_cast<int>(obj_ch); ++cy) {
                    for (int cx = obj_cx; cx < obj_cx+static_cast<int>(obj_cw); ++cx) {
                        int rel_x = cx - obj_cx;
                        int rel_y = cy - obj_cy;

                        if (rel_x < 0 || rel_y < 0 || static_cast<size_t>(rel_x) >= obj_cw || static_cast<size_t>(rel_y) >= obj_ch)
                            continue;

                        size_t obj_index = static_cast<size_t>(rel_y) * obj_cw + static_cast<size_t>(rel_x);

                        if (collision_map[obj_index]) {
                            float cell_x = static_cast<float>(cx) * Tile::tile_collider_size;
                            float cell_y = static_cast<float>(cy) * Tile::tile_collider_size;

                            min_x = std::min(min_x, cell_x);
                            min_y = std::min(min_y, cell_y);
                            max_x = std::max(max_x, cell_x + static_cast<float>(Tile::tile_collider_size));
                            max_y = std::max(max_y, cell_y + static_cast<float>(Tile::tile_collider_size));

                            if (cx >= start_cx && cx < end_cx && cy >= start_cy && cy < end_cy)
                                collision_found = true;
                        }
                        if (cx >= end_cx && cy >= end_cy && !collision_found)
                            return false;

                    }
                }

            }
        }
    }
    if (collision_found) {
        last_object_collision.min_x = std::min(min_x, max_x);
        last_object_collision.max_x = std::max(min_x, max_x);
        last_object_collision.min_y = std::min(min_y, max_y);
        last_object_collision.max_y = std::max(min_y, max_y);
        last_object_collision.valid = true;
    }
    return collision_found;
}
*/

void Enemy::compute_MapCollisionInterval(size_t cx, size_t cy, size_t max_x, size_t max_y) {
    last_map_collision.valid = false;

    const size_t start_index = cy * max_x + cx;
    if (!Map::tile_collission_map[start_index])
        return;

    size_t min_x = cx;
    while (min_x > 0 &&
           Map::tile_collission_map[cy * max_x + (min_x - 1)])
        min_x--;

    size_t max_xi = cx;
    while (max_xi < max_x - 1 &&
           Map::tile_collission_map[cy * max_x + (max_xi + 1)])
        max_xi++;

    size_t min_y = cy;
    while (min_y > 0 &&
           Map::tile_collission_map[(min_y - 1) * max_x + cx])
        min_y--;

    size_t max_yi = cy;
    while (max_yi < max_y - 1 &&
           Map::tile_collission_map[(max_yi + 1) * max_x + cx])
        max_yi++;

    last_map_collision.min_x = min_x * Tile::tile_collider_size;
    last_map_collision.max_x = (max_xi + 1) * Tile::tile_collider_size;

    last_map_collision.min_y = min_y * Tile::tile_collider_size;
    last_map_collision.max_y = (max_yi + 1) * Tile::tile_collider_size;

    last_map_collision.valid = true;
}

bool
Enemy::is_CollidingWithWorld(const SDL_FRect& rect) {
    const size_t max_cells_x = static_cast<size_t>(Map::size.x * (Tile::tile_size / Tile::tile_collider_size));
    const size_t max_cells_y = static_cast<size_t>(Map::size.y * (Tile::tile_size / Tile::tile_collider_size));

    const float corners[4][2] = {
        { rect.x,             rect.y },
        { rect.x + rect.w,    rect.y },
        { rect.x,             rect.y + rect.h },
        { rect.x + rect.w,    rect.y + rect.h }
    };

    for (int i = 0; i < 4; ++i) {
        const size_t cell_x = corners[i][0] / Tile::tile_collider_size;
        const size_t cell_y = corners[i][1] / Tile::tile_collider_size;

        if (cell_x >= max_cells_x || cell_y >= max_cells_y)
            return true;

        const size_t index = static_cast<size_t>(cell_y * max_cells_x + cell_x);

        if (Map::tile_collission_map[index]) {
            const size_t index = cell_y * max_cells_x
                + cell_x;

            if (Map::tile_collission_map[index]) {
                compute_MapCollisionInterval(cell_x, cell_y, max_cells_x, max_cells_y);
                //std::cout << "COL: " << last_map_collision.min_x << " " << last_map_collision.max_x << " " << last_map_collision.min_y << " " << last_map_collision.max_y << std::endl;
                return true;
            }
        }
    }
    return false;
}

bool
Enemy::is_Colliding(const SDL_FRect& rect) {
    /*
    const size_t max_cells_x = Map::size.x * (Tile::tile_size / Tile::tile_collider_size);
    const size_t max_cells_y = Map::size.y * (Tile::tile_size / Tile::tile_collider_size);

    const float corners[4][2] = {
        { rect.x,             rect.y },
        { rect.x + rect.w,    rect.y },
        { rect.x,             rect.y + rect.h },
        { rect.x + rect.w,    rect.y + rect.h }
    };

    for (int i = 0; i < 4; ++i) {
        const size_t cell_x = corners[i][0] / Tile::tile_collider_size;
        const size_t cell_y = corners[i][1] / Tile::tile_collider_size;

        if (cell_x < 0 || cell_y < 0 || cell_x >= max_cells_x || cell_y >= max_cells_y)
            return true;

        const size_t index = static_cast<size_t>(cell_y) * static_cast<size_t>(max_cells_x)
            + static_cast<size_t>(cell_x);

        if (Map::tile_collission_map[index]) {
            const size_t index = static_cast<size_t>(cell_y) * static_cast<size_t>(max_cells_x)
                + static_cast<size_t>(cell_x);

            if (Map::tile_collission_map[index]) {
                compute_MapCollisionInterval(cell_x, cell_y, max_cells_x, max_cells_y);
                //std::cout << "COL: " << last_map_collision.min_x << " " << last_map_collision.max_x << " " << last_map_collision.min_y << " " << last_map_collision.max_y << std::endl;
                return true;
            }
        }
    }
    */

    // if (is_CollidingWithObjects(rect)) return true;

    return set_CollisinnWithEnemy(rect);
}


void Enemy::move_Path() {
    if (next_moves.empty()) return;

    SDL_FPoint move_to_position = next_moves.back();

    float cx = x + collider.x + collider.w * 0.5f;
    float cy = y + collider.y + collider.h * 0.5f;


    float dx = move_to_position.x - cx;
    float dy = move_to_position.y - cy;
    float dist = std::sqrt(dx*dx + dy*dy);


    const float max_dist = 2;

    if (dist <= max_dist) {
        next_moves.pop_back();

        if (next_moves.empty()) {
            // rotate to target
            float fx = next_position.x - cx;
            float fy = next_position.y - cy;
            flip = (fx < 0);
            if (std::abs(fx) > std::abs(fy)) {
                direction = (fx < 0 ? WalkDirection::LEFT : WalkDirection::RIGHT);
                weapon.flip = flip;
            } else {
                direction = (fy < 0 ? WalkDirection::UP : WalkDirection::DOWN);
            }

            return;
        }
        return;
    }

    // normalize step
    float step_len = speed * Default::delta_time;
    if (dist < step_len) step_len = dist;
    dx = dx / dist * step_len;
    dy = dy / dist * step_len;

    //std::cout << "MOVE_TO_POS: " << move_to_position.x << " " << move_to_position.y << "DX AND DY: " << dx << " " << dy << std::endl;

    SDL_FRect test = collider;


    bool is_actually_colliding = is_Colliding({collider.x + x, collider.y + y, collider.w, collider.h});

    test.x = x + collider.x + dx;
    test.y = y + collider.y + dy;

    if (!is_Colliding(test) || is_actually_colliding) {
        x += dx;
        moving_logic.moving_distance.x += std::abs(dx);
        y += dy;
        moving_logic.moving_distance.y += std::abs(dy);
        moving_logic.path_blocked = 0.0;
    } else {
        float dx = test.x - collider_world.x;
        float dy = test.y - collider_world.y;
        float len = std::sqrt(dx * dx + dy * dy);
        dx /= len;
        dy /= len;
        bool left = (std::rand() % 2) == 0;
        float px = left ? -dy :  dy;
        float py = left ?  dx : -dx;

        float distance = 40.0f + static_cast<float>(std::rand()) /
            (static_cast<float>(RAND_MAX) / 40.0f);
        SDL_FPoint result = {
            collider_world.x + px * distance,
            collider_world.y + py * distance
        };
        std::cout << "GETING OUTTTTT\n";
        moving_logic.position = Map::get_ClosestPointWithoutCollision({result.x, result.y, collider.w, collider.h});
        next_position = moving_logic.position;
        moving_logic.moving_distance.x = 0.0f;
        moving_logic.moving_distance.y = 0.0f;
        point_in_reach = false;
        moving_logic.path_blocked = 0.0;
        set_Status(EnemyStatus::RUN);
        moving_logic.avoid_blockage = true;
        moving_logic.move_type = EnemyMovingType::WALK;
        handle_Animation();
        return;
    }

    
    // SET WALK DIRECTION
    flip = (dx < 0);
    if (fabs(dx) > 0.0f && fabs(dx) > fabs(dy)) {
        direction = (dx < 0 ? WalkDirection::LEFT : WalkDirection::RIGHT);
    } else if (dy > 0.0f) {
        direction = WalkDirection::DOWN;
    } else if (dy < .0f) {
        direction = WalkDirection::UP;
    }

    if (status != EnemyStatus::RUN)
        set_Status(EnemyStatus::RUN);



    // update world positions
    collider_world.x = x + collider.x + collider.w * 0.5f;
    collider_world.y = y + collider.y + collider.h * 0.5f;
    tile_world.x = collider_world.x / Tile::tile_size;
    tile_world.y = collider_world.y / Tile::tile_size;

    //set_TilesInReach();
}

void
Enemy::remove_Dead() {
    if (Enemy::enemies.size() == 0)
        return;

    for (auto it = Enemy::enemies.begin(); it != Enemy::enemies.end(); ) {
        Enemy* e = *it;

        if (e->status == EnemyStatus::DEAD && e->death_duration > e->max_death_time) {
            delete e;
            it = Enemy::enemies.erase(it);
        } else {
            ++it;
        }
    }
}

void
Enemy::init_SpawnEnemy(double delay_to_spawn_in_sec, WeaponType weapon_type = WeaponType::SWORD,
                       WeaponMaterial weapon_material = WeaponMaterial::WOOD,
                       SDL_FPoint position = {100,100},
                       EnemyStats status = {16,16,2,false,false,0,1.2}) {

    double spawn_date = delay_to_spawn_in_sec + Default::get_TimeSinceStartInSec();
    Enemy::enemies_to_spawn.push_back({spawn_date,
                                       weapon_type,
                                       weapon_material,
                                       position,
                                       status});
}

bool
Enemy::spawn_Enemy(SpawnParams& params) {
    if (params.spawn_date > Default::get_TimeSinceStartInSec())
        return false;

    Enemy* enemy = new Enemy(params.type, params.material, params.pos, params.stats);
    enemy->Init();
    Enemy::enemies.push_back(enemy);
    return true;
}

void
Enemy::set_NextMoves(const std::vector<bool>& grid) {
    // std::cout << "PL: " << player_collider_world.x << " " << player_collider_world.y << std::endl;
    // std::cout << "TG: " << target_pos.x << " " << target_pos.y << std::endl;
    // std::cout << "DS: " << dist_max-dist_min << std::endl;
    next_moves = find_Path({collider_world.x, collider_world.y, collider.w, collider.h}, next_position, dist_max-dist_min, 0, grid);
    // std::cout << "NM " << next_moves.size() << " " << collider_world.x << " " << collider_world.y << " " << next_position.x << " " << next_position.y << std::endl;
}


void
Enemy::set_AlignPosition(const SDL_FPoint& pos) {
    float dist = dist_min + (dist_max-dist_min)/2;
    //std::cout << "DS: " << dist.x << " " << dist.y <<std::endl;

    const float px = pos.x;
    const float py = pos.y;

    float nx = collider_world.x;
    float ny = collider_world.y;

    float cand[4][2];
    cand[0][0] = px + dist;    cand[0][1] = py;
    cand[1][0] = px - dist;    cand[1][1] = py;
    cand[2][0] = px;           cand[2][1] = py + dist;
    cand[3][0] = px;           cand[3][1] = py - dist;

    // Compute distances from enemy to each candidate
    float dist2[4];
    for (int i = 0; i < 4; i++) {
        const float dx2 = cand[i][0] - collider_world.x;
        const float dy2 = cand[i][1] - collider_world.y;
        dist2[i] = dx2*dx2 + dy2*dy2;
    }

    // Find nearest candidate first
    int order[4] = {0,1,2,3};
    for (int i = 0; i < 4; i++) {
        int best = i;
        for (int j = i+1; j < 4; j++) {
            if (dist2[order[j]] < dist2[order[best]])
                best = j;
        }
        int tmp = order[i];
        order[i] = order[best];
        order[best] = tmp;
    }

    for (int k = 0; k < 4; k++) {
        const int idx = order[k];
        const float tx = cand[idx][0];
        const float ty = cand[idx][1];

        if (Map::is_FreePosition({tx-2, ty+collider.h-2, collider.w+4, collider.h+4})) {
            nx = tx;
            ny = ty;
            break;
        }
    }

    next_position.x = nx;
    next_position.y = ny;
}

void
Enemy::set_MoveLogic(EnemyMovingType& type, Character& player) {
    moving_logic = EnemyMovingLogic();
    moving_logic.move_type = type;

    EnemyStatus sta = EnemyStatus::IDLE;
    point_in_reach = true;
    moving_logic.avoid_blockage = false;
    stuck_moves = 0;

    // std::cout << "FUCK " << (int)type << std::endl;
    switch (type) {
        case EnemyMovingType::NONE:
            break;
        case EnemyMovingType::OBJECT:
            moving_logic.move_type = EnemyMovingType::NONE;
            break;
        case EnemyMovingType::IDLE:
            moving_logic.duration = (180 + (rand() % 200))/100.0f;
            sta = EnemyStatus::IDLE;
            break;
        case EnemyMovingType::WALK: {
            SDL_FRect test_pos = collider;
            int chance_move_far = rand() % 10;

            if (chance_move_far < 3) {
                moving_logic.min_distance = 100;
                moving_logic.max_distance = 140;
            } else {
                moving_logic.min_distance = 50;
                moving_logic.max_distance = 101;
            }

            // Retry generating a test position until it's valid
            SDL_FPoint test_pos_c = {0,0};
            // START CLAMP NEW POSITION TO BOUNDARIES WHICH SUUUUUUCKS!!!
            int i = 0;

            const int map_max_x = Map::boundaries.x * Tile::tile_size;
            const int map_max_y = Map::boundaries.y * Tile::tile_size;

            do {
                if (i++ > 3) {
                    moving_logic.move_type = EnemyMovingType::NONE;
                    return;
                }

                int dist_x = moving_logic.min_distance +
                    rand() % (moving_logic.max_distance - moving_logic.min_distance + 1);

                int dist_y = moving_logic.min_distance +
                    rand() % (moving_logic.max_distance - moving_logic.min_distance + 1);

                if (rand() % 2) dist_x = -dist_x;
                if (rand() % 2) dist_y = -dist_y;

                int new_x = static_cast<int>(collider_world.x) + dist_x;
                int new_y = static_cast<int>(collider_world.y) + dist_y;

                if (new_x < 0 || new_y < 0 ||
                        new_x >= map_max_x || new_y >= map_max_y)
                    continue;

                test_pos.x = new_x;
                test_pos.y = new_y;

                test_pos_c = Map::get_ClosestPointWithoutCollision(test_pos);

                if (player.is_attackable && test_pos_c.x <= 530 && test_pos_c.y <= 334)
                    continue;

                break;

            } while (true);
            // END CLAMP NEW POSITION TO BOUNDARIES WHICH SUUUUUUCkS!!!

            moving_logic.moving_distance = {0, 0};
            // moving_logic.position = Map::get_ClosestPointWithoutCollision(test_pos);
            next_position = test_pos_c;
            //set_AlignPosition(moving_logic.position);
            // std::cout << " MEEE1 " << player.is_attackable << " " << collider_world.x << " " << collider_world.y << std::endl;
            // std::cout << " MEEE2 " << player.is_attackable << " " << next_position.x << " " << next_position.y << std::endl;
            sta = EnemyStatus::RUN;
            point_in_reach = false;
            break;
        }
        case EnemyMovingType::CHARACTER: {
            bool is_player_alive = player.health_Get() > 0;
            if (!is_player_alive) {
                moving_logic.move_type = EnemyMovingType::NONE;
                break;
            }
            moving_logic.object   = static_cast<void*>(&player);
            moving_logic.position = player.collider_world;
            moving_logic.duration =  0.8 + ((double)rand() / RAND_MAX) * (1.8 - 0.8);
            set_AlignPosition(moving_logic.position);
            sta = EnemyStatus::RUN;
            point_in_reach = false;
            move_focus = EnemyMovingType::CHARACTER;
            break;
        }
        case EnemyMovingType::LAST:
            moving_logic.move_type = EnemyMovingType::NONE;
            break;
    }

    if (status != sta) {
        set_Status(sta);
        handle_Animation();
    }
}

void
Enemy::Update(Character& player) {

    point_in_reach = false;

    if (player.is_attackable) {
        is_character_in_range = character_InRange(player.collider_world);
        if (!is_character_in_range && moving_logic.move_type == EnemyMovingType::CHARACTER) {
            next_moves.clear();
            move_focus = EnemyMovingType::NONE;
            moving_logic.move_type = EnemyMovingType::NONE;
            attack_Stop();
         }
}

    // IS DEAD
    if (stats.health <= 0) {
        if (status == EnemyStatus::DYING ||
                   status == EnemyStatus::DEAD) {
            death();
        }
        handle_Animation();
        return;
    }

    // std::cout << "MVTY " << (int)moving_logic.move_type << std::endl;
    // DECIDE // REDECIDE
    //std::cout << "WLK " << moving_logic.moving_distance.x << " >= " << moving_logic.max_distance << " || " << moving_logic.moving_distance.y << " >= " << moving_logic.max_distance << std::endl; 
    if ( moving_logic.move_type == EnemyMovingType::NONE || (
          moving_logic.move_type == EnemyMovingType::IDLE &&
          moving_logic.duration <= 0)) {
        Decide(player);
        handle_Animation();
        return;
    } else
    if (moving_logic.move_type == EnemyMovingType::IDLE) {
        moving_logic.duration -= Default::delta_time;
        handle_Animation();
        return;
    } else
    if (moving_logic.move_type == EnemyMovingType::WALK) {
        if ( moving_logic.moving_distance.x >= moving_logic.max_distance ||
             moving_logic.moving_distance.y >= moving_logic.max_distance ) {
            moving_logic.move_type = EnemyMovingType::NONE;
            next_moves.clear();
            handle_Animation();
            return;
        }
    }

    float  d_x  = moving_logic.position.x - collider_world.x;
    float  d_y  = moving_logic.position.y - collider_world.y;
    /*
    size_t dc_x = static_cast<size_t>(abs(d_x));
    size_t dc_y = static_cast<size_t>(abs(d_y));

    bool   dist_ok_x = (dc_x >= dist_min && dc_x <= weapon.dimension.x) && (dc_y <= weapon.dimension.y/2);
    bool   dist_ok_y = (dc_y >= dist_min && dc_y <= weapon.dimension.x) && (dc_x <= weapon.dimension.y/2);
    */


    float dist_sq = d_x * d_x + d_y * d_y;

    float min_sq = dist_min * dist_min;
    float max_sq = dist_max * dist_max;

    //std::cout << "DSTQ " << (int)moving_logic.move_type << " " << next_moves.size()<< " "  << dist_sq << " " << min_sq << " " << max_sq << std::endl;
    bool point_moved = false;
    if ( player.collider_world.x != moving_logic.position.x ||
         player.collider_world.y != moving_logic.position.y ) {
        point_moved = true;
    }
    if (dist_sq >= min_sq && dist_sq <= max_sq) {
        point_in_reach = true;
        moving_logic.avoid_blockage = false;
        next_moves.clear();
    }
    if (next_moves.empty()) {
        if (moving_logic.move_type == EnemyMovingType::WALK || (move_focus == EnemyMovingType::CHARACTER && point_moved && moving_logic.duration >= 0)) {
            float mld = moving_logic.duration;
            set_Status(EnemyStatus::IDLE);
            moving_logic.move_type = EnemyMovingType::IDLE;
            if (moving_logic.move_type == EnemyMovingType::WALK)
                moving_logic.duration = (moving_logic.moving_distance.x + moving_logic.moving_distance.y) / 100 + 0.5;
            else
                moving_logic.duration = mld;
            std::cout << "Walk DONE !!!!\n";
            return;
        }
    } else if (next_moves.size() < 2) {
        ++stuck_moves;
        if (stuck_moves > 20) {
            // std::cout << "IM STUCK!!\n";
            set_Status(EnemyStatus::IDLE);
            moving_logic.move_type = EnemyMovingType::IDLE;
            next_position = collider_world;
            next_moves.clear();
            return;
        }
    }

    if (move_focus == EnemyMovingType::CHARACTER) {
        moving_logic.duration -= Default::delta_time;
        // std::cout << moving_logic.duration << std::endl;
        //std::cout << "NEXT MOVES: " << next_moves.size() << std::endl;
        if (!moving_logic.avoid_blockage && ((point_moved && moving_logic.duration <= 0) || moving_logic.move_type != EnemyMovingType::CHARACTER)) {
            set_MoveLogic(moving_logic.move_type, player);
            return;
        }
    }

    if (moving_logic.move_type == EnemyMovingType::CHARACTER) {
        if (stats.is_attacking) {
            Attack(point_in_reach);
            handle_Animation();
            return;
        }
    }

    if ( !point_in_reach ) {
        move_Path();
        handle_Animation();
        return;
    }


    next_moves.clear();

    // SET WALK DIRECTION
    flip = (d_x < 0);
    if (fabs(d_x) > 0.0f && fabs(d_x) > fabs(d_y)) {
        direction = (d_x < 0 ? WalkDirection::LEFT : WalkDirection::RIGHT);
    } else if (d_y > 0.0f) {
        direction = WalkDirection::DOWN;
    } else if (d_y < .0f) {
        direction = WalkDirection::UP;
    }

    switch (moving_logic.move_type) {
        case EnemyMovingType::CHARACTER:
            attack_Start();
            break;
        case EnemyMovingType::WALK:
            moving_logic.move_type = EnemyMovingType::IDLE;
            set_MoveLogic(moving_logic.move_type, player);
            break;
        default: break;
    }

    handle_Animation();
}

void 
Enemy::attack_Start() {
    set_Status(EnemyStatus::ATTACK);
    handle_Animation();

    Animation::animation_frame_time = stats.attack_duration_max / active_anim->frames.size();

    stats.attack_duration = 0;
    stats.is_attacking = true;
    stats.has_attacked = false;
}

void 
Enemy::Attack( bool in_reach) {
    // i should fic the frame_time to attack_duration
    //  so the animation finishes with the attack end.
    stats.attack_duration += Default::delta_time;
    if (stats.attack_duration < stats.attack_duration_max/2)
        return;

    if (in_reach && !stats.has_attacked) {
#ifdef DEBUG_BUILD
        std::cout << "Enemy: Attack character!\n";
#endif
        stats.has_attacked = true;
        if (moving_logic.move_type == EnemyMovingType::CHARACTER)
            static_cast<Character*>(moving_logic.object)->getting_Hit(stats.demage);
        //player.getting_Hit(stats.demage);
    }
    attack_Stop();
}

void 
Enemy::attack_Stop() {
    if (stats.attack_duration < stats.attack_duration_max)
        return;
    set_Status(EnemyStatus::IDLE);
    stats.attack_duration = 0;
    stats.is_attacking = false;
    stats.has_attacked = false;

    if (moving_logic.move_type == EnemyMovingType::CHARACTER) {
        if (static_cast<Character*>(moving_logic.object)->health_Get() <= 0)
            moving_logic.move_type = EnemyMovingType::NONE;

    }
}

void
Enemy::getting_Hit(float demage) {
    // if (status != EnemyStatus::HIT) set_Status(EnemyStatus::HIT);
    std::cout << "ENEM " << stats.health << " " << demage << std::endl;
    stats.health -= demage;

#ifdef DEBUG_BUILD
    std::cout << "Enemy: Getting hit! " << stats.health << std::endl;
#endif

    if (stats.health <= 0) {
        attack_Stop();
        stats.health = 0;
        death();
    }
}

void
Enemy::death() {
    death_duration += Default::delta_time;
    if (death_duration > death_duration_max) {
        if (status != EnemyStatus::DEAD)
            set_Status(EnemyStatus::DEAD);
    } else {
        if (status != EnemyStatus::DYING)
            set_Status(EnemyStatus::DYING);
    }
}

void
Enemy::set_TilesInReach() {
    tiles_in_reach.clear();

    float reach = arm_reach_radius;
    float reach_sq = reach * reach;

    // bounding box of tiles to check
    int min_tx = static_cast<int>((collider_world.x - reach) / Tile::tile_size);
    int max_tx = static_cast<int>((collider_world.x + reach) / Tile::tile_size);
    int min_ty = static_cast<int>((collider_world.y - reach) / Tile::tile_size);
    int max_ty = static_cast<int>((collider_world.y + reach) / Tile::tile_size);

    // clamp to map bounds
    if (min_tx < 0) min_tx = 0;
    if (min_ty < 0) min_ty = 0;
    if (max_tx >= static_cast<int>(Map::size.x)) max_tx = Map::size.x - 1;
    if (max_ty >= static_cast<int>(Map::size.y)) max_ty = Map::size.y - 1;

    for (int ty = min_ty; ty <= max_ty; ++ty) {
        for (int tx = min_tx; tx <= max_tx; ++tx) {
            // tile rectangle
            float tile_left   = tx * static_cast<int>(Tile::tile_size);
            float tile_top    = ty * static_cast<int>(Tile::tile_size);
            float tile_right  = tile_left + Tile::tile_size;
            float tile_bottom = tile_top + Tile::tile_size;

            // closest point on tile to character center
            float closest_x = std::max(tile_left, std::min(collider_world.x, tile_right));
            float closest_y = std::max(tile_top,  std::min(collider_world.y, tile_bottom));

            float dx = closest_x - collider_world.x;
            float dy = closest_y - collider_world.y;

            if (dx*dx + dy*dy <= reach_sq)
                tiles_in_reach.push_back({tx, ty});
        }
    }

    std::sort(tiles_in_reach.begin(), tiles_in_reach.end(),
    [this](const SDL_Point& a, const SDL_Point& b) {
        float ax = a.x * Tile::tile_size + Tile::tile_size * 0.5f;
        float ay = a.y * Tile::tile_size + Tile::tile_size * 0.5f;
        float bx = b.x * Tile::tile_size + Tile::tile_size * 0.5f;
        float by = b.y * Tile::tile_size + Tile::tile_size * 0.5f;

        float da = (ax - collider_world.x) * (ax - collider_world.x)
                 + (ay - collider_world.y) * (ay - collider_world.y);
        float db = (bx - collider_world.x) * (bx - collider_world.x)
                 + (by - collider_world.y) * (by - collider_world.y);
        return da < db;
    });

}












static void WriteStats(std::ofstream &file, const EnemyStats &s) {
    file.write(reinterpret_cast<const char*>(&s.health), sizeof(s.health));
    file.write(reinterpret_cast<const char*>(&s.health_max), sizeof(s.health_max));
    file.write(reinterpret_cast<const char*>(&s.demage), sizeof(s.demage));
    file.write(reinterpret_cast<const char*>(&s.is_attacking), sizeof(s.is_attacking));
    file.write(reinterpret_cast<const char*>(&s.has_attacked), sizeof(s.has_attacked));
    file.write(reinterpret_cast<const char*>(&s.attack_duration), sizeof(s.attack_duration));
    file.write(reinterpret_cast<const char*>(&s.attack_duration_max), sizeof(s.attack_duration_max));
}

static void ReadStats(std::ifstream &file, EnemyStats &s) {
    file.read(reinterpret_cast<char*>(&s.health), sizeof(s.health));
    file.read(reinterpret_cast<char*>(&s.health_max), sizeof(s.health_max));
    file.read(reinterpret_cast<char*>(&s.demage), sizeof(s.demage));
    file.read(reinterpret_cast<char*>(&s.is_attacking), sizeof(s.is_attacking));
    file.read(reinterpret_cast<char*>(&s.has_attacked), sizeof(s.has_attacked));
    file.read(reinterpret_cast<char*>(&s.attack_duration), sizeof(s.attack_duration));
    file.read(reinterpret_cast<char*>(&s.attack_duration_max), sizeof(s.attack_duration_max));
}

bool
Enemy::save_All(const std::string &path) {
    std::ofstream file(path, std::ios::binary | std::ios::trunc);
    if (!file)
        return false;

    uint32_t count_total = static_cast<uint32_t>(enemies.size() + enemies_to_spawn.size());
    file.write(reinterpret_cast<const char*>(&count_total), sizeof(count_total));

    for (auto *e : enemies) {
        SpawnParams sp;
        sp.spawn_date = 0.0;
        sp.type = e->weapon.type;
        sp.material = e->weapon.material;
        sp.pos = { e->x, e->y };
        sp.stats = e->stats;

        file.write(reinterpret_cast<const char*>(&sp.spawn_date), sizeof(sp.spawn_date));
        file.write(reinterpret_cast<const char*>(&sp.type), sizeof(sp.type));
        file.write(reinterpret_cast<const char*>(&sp.material), sizeof(sp.material));
        file.write(reinterpret_cast<const char*>(&sp.pos.x), sizeof(sp.pos.x));
        file.write(reinterpret_cast<const char*>(&sp.pos.y), sizeof(sp.pos.y));
        WriteStats(file, sp.stats);
    }

    // save pending
    double current_time = Default::get_TimeSinceStartInSec();
    for (const auto &sp : enemies_to_spawn) {
        SpawnParams tmp = sp;
        tmp.spawn_date -= current_time;

        file.write(reinterpret_cast<const char*>(&tmp.spawn_date), sizeof(tmp.spawn_date));
        file.write(reinterpret_cast<const char*>(&tmp.type), sizeof(tmp.type));
        file.write(reinterpret_cast<const char*>(&tmp.material), sizeof(tmp.material));
        file.write(reinterpret_cast<const char*>(&tmp.pos.x), sizeof(tmp.pos.x));
        file.write(reinterpret_cast<const char*>(&tmp.pos.y), sizeof(tmp.pos.y));
        WriteStats(file, tmp.stats);
    }

    return file.good();
}

bool Enemy::load_All(const std::string &path) {
    std::ifstream file(path, std::ios::binary);
    if (!file)
        return false;

    for (auto *e : enemies) delete e;
    enemies.clear();
    enemies_to_spawn.clear();

    uint32_t count_total = 0;
    file.read(reinterpret_cast<char*>(&count_total), sizeof(count_total));

    for (uint32_t i = 0; i < count_total; ++i) {
        SpawnParams sp;
        file.read(reinterpret_cast<char*>(&sp.spawn_date), sizeof(sp.spawn_date));
        file.read(reinterpret_cast<char*>(&sp.type), sizeof(sp.type));
        file.read(reinterpret_cast<char*>(&sp.material), sizeof(sp.material));
        file.read(reinterpret_cast<char*>(&sp.pos.x), sizeof(sp.pos.x));
        file.read(reinterpret_cast<char*>(&sp.pos.y), sizeof(sp.pos.y));
        ReadStats(file, sp.stats);

        double delay = sp.spawn_date;
        init_SpawnEnemy(delay, sp.type, sp.material, sp.pos, sp.stats);
    }

    return file.good();
}