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

#include <algorithm>
#include <vector>

#include "map/map.hpp"
#include "building/building.hpp"
#include "defaults/ui.hpp"
#include "resource/resource_data.hpp"
#include "building/research.hpp"
#include "building/craft.hpp"
#include "farm/farm.hpp"
#include "character/message.hpp"
#include "cursor/animated_cursor.hpp"



Character::Character()
    : messages(std::make_unique<Messages>()),
      inventory(std::make_unique<Inventory>()),
      discovered_resources(static_cast<size_t>(ResourceType::LAST), false),
      discovered_vegetables(static_cast<size_t>(VegetableData::Type::COUNT), false),
      discovered_buildings(static_cast<size_t>(BuildingType::LAST), false),
      researched_buildings(static_cast<size_t>(BuildingType::LAST), false),
      researched_items    (static_cast<size_t>(ItemType::LAST),     false) {
    std::cout << "CREATE CHARACTER\n";
    active_menu = CharacterMenuType::NONE;
    researched_buildings[static_cast<size_t>(BuildingType::RESEARCH_WOOD_1)] = true;
    researched_items[static_cast<size_t>(ItemType::BUILDING_TOOL)] = true;
    std::cout << "[CTOR] " << this << " " << researched_items.size() << "\n";
}

Character::Character(Character&&) {
    std::cout << "[Character] Move constructor called!" << std::endl;
    std::cout << "[DTOR] " << this << " " << researched_items.size() << "\n";
}


Character::~Character() {
    delete bird;
}


void
Character::Init(SDL_Renderer* renderer) {
    x = 10.0f;
    y = 10.0f;
    speed = 62.0f; // pixels per second
    sprint_multiply = 1.6;
    direction = WalkDirection::DOWN;
    moving = false;
    flip = false;
    tile_size = 64;
    height    = 48; 
    width     = 16;
    collider.w = 3;
    collider.h = 3;
    collider.x = (tile_size/2 - collider.w/2);
    collider.y = tile_size - collider.h - 15;
    collider_world.x = x + collider.x + collider.w * 0.5f;
    collider_world.y = y + collider.y + collider.h * 0.5f;
    spawn_point = {collider_world.x, collider_world.y};
    arm_reach_radius = 6;
    collecting_logic.is_collecting = false;

    stats = { 25, 0, 25, 0, 50, 100, 2};

    attacking_logic   = { 0.0f, nullptr, AttackingLogicStatus_e::NONE, false, nullptr };
    consuming_logic   = { 0.0f, 3.0f, false, false, nullptr };
    cultivation_logic = { 0.0f, 3.0f, false, false, {0,0} };
    planting_logic    = { 0.0f, 3.0f, false, {0,0}, VegetableData::Type::NONE, false};
    sprintstart_logic = {0, 0.8, false};

    death_duration = 0;
    death_duration_max = 0.8;
    death_shuffler = nullptr;

    is_placing_build = false;

    selected_item = inventory->hotbar_hands;

    bird = new Bird(x, y);
    //bird->Init(renderer);
    bird->set_State(BirdState::WALKING, 1.4);




    // load_MapTiles animations
    animation.idle[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Idle_Base/Idle_Down-Sheet.png", tile_size, tile_size, "None", false, false);
    animation.idle[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Idle_Base/Idle_Side-Sheet.png", tile_size, tile_size, "None", false, false);
    animation.idle[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Idle_Base/Idle_Up-Sheet.png",   tile_size, tile_size, "None", false, false);

    animation.walk[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Walk_Base/Walk_Down-Sheet.png", tile_size, tile_size, "character.walk", false, false);
    animation.walk[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Walk_Base/Walk_Side-Sheet.png", tile_size, tile_size, "character.walk", false, false);
    animation.walk[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Walk_Base/Walk_Up-Sheet.png",   tile_size, tile_size, "character.walk", false, false);

    animation.run[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Run_Base/Run_Down-Sheet.png", tile_size, tile_size, "None", false, false);
    animation.run[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Run_Base/Run_Side-Sheet.png", tile_size, tile_size, "None", false, false);
    animation.run[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Run_Base/Run_Up-Sheet.png",   tile_size, tile_size, "None", false, false);

    animation.collect[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Down-Sheet.png", tile_size, tile_size, "character.collect", false, false);
    animation.collect[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Side-Sheet.png", tile_size, tile_size, "character.collect", false, false);
    animation.collect[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Up-Sheet.png",   tile_size, tile_size, "character.collect", false, false);

    animation.cultivating[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Down-Sheet.png", tile_size, tile_size, "character.cultivate", false, false);
    animation.cultivating[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Side-Sheet.png", tile_size, tile_size, "character.cultivate", false, false);
    animation.cultivating[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Up-Sheet.png",   tile_size, tile_size, "character.cultivate", false, false);

    animation.planting[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Down-Sheet.png", tile_size, tile_size, "character.plant", false, false);
    animation.planting[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Side-Sheet.png", tile_size, tile_size, "character.plant", false, false);
    animation.planting[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Up-Sheet.png",   tile_size, tile_size, "character.plant", false, false);

    animation.craft[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Down-Sheet.png", tile_size, tile_size, "character.craft", false, false);
    animation.craft[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Side-Sheet.png", tile_size, tile_size, "character.craft", false, false);
    animation.craft[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Up-Sheet.png",   tile_size, tile_size, "character.craft", false, false);

    animation.build[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Down-Sheet.png", tile_size, tile_size, "character.build", false, false);
    animation.build[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Side-Sheet.png", tile_size, tile_size, "character.build", false, false);
    animation.build[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Up-Sheet.png",   tile_size, tile_size, "character.build", false, false);

    animation.hit[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Hit_Base/Hit_Down-Sheet.png", tile_size, tile_size, "character.hit", false, false);
    animation.hit[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Hit_Base/Hit_Side-Sheet.png", tile_size, tile_size, "character.hit", false, false);
    animation.hit[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Hit_Base/Hit_Up-Sheet.png",   tile_size, tile_size, "character.hit", false, false);

    animation.attack_sword[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Pierce_Base/Pierce_Wood_Down.png", tile_size, tile_size, "character.attack", false, false);
    animation.attack_sword[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Pierce_Base/Pierce_Wood_Side.png", tile_size, tile_size, "character.attack", false, false);
    animation.attack_sword[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Pierce_Base/Pierce_Wood_Top.png",   tile_size, tile_size, "character.attack", false, false);

    animation.hit_woodaxe_stone[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Slice_Base/Slice_Down.png", tile_size, tile_size, "None", false, false);
    animation.hit_woodaxe_stone[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Slice_Base/Slice_Side.png", tile_size, tile_size, "None", false, false);
    animation.hit_woodaxe_stone[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Slice_Base/Slice_Up.png",   tile_size, tile_size, "None", false, false);

    animation.death[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Death_Base/Death_Down-Sheet.png", tile_size, tile_size, "character.die", false, false);
    animation.death[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Death_Base/Death_Side-Sheet.png", tile_size, tile_size, "character.die", false, false);
    animation.death[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Death_Base/Death_Up-Sheet.png",   tile_size, tile_size, "character.die", false, false);

    animation.block[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Pierce_Blocking/Down.png", tile_size, tile_size, "None", false, false);
    animation.block[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Pierce_Blocking/Side.png", tile_size, tile_size, "None", false, false);
    animation.block[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Pierce_Blocking/Up.png",   tile_size, tile_size, "None", false, false);

    animation.drinking_river[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Down-Sheet.png", tile_size, tile_size, "character.drink", false, false);
    animation.drinking_river[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Side-Sheet.png", tile_size, tile_size, "character.drink", false, false);
    animation.drinking_river[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Up-Sheet.png",   tile_size, tile_size, "character.drink", false, false);

    animation.eating[WalkDirection::DOWN] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Down-Sheet.png", tile_size, tile_size, "character.eat", false, false);
    animation.eating[WalkDirection::SIDE] = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Side-Sheet.png", tile_size, tile_size, "character.eat", false, false);
    animation.eating[WalkDirection::UP]   = Animation::load(renderer, "Pixel_Crawler/Entities/Characters/Body_A/Animations/Collect_Base/Collect_Up-Sheet.png",   tile_size, tile_size, "character.eat", false, false);

    Animation::audio->RegisterSound("character.dead", "music/character.dead.wav");

    handle_Animation();

    set_Status(CharacterStatus::IDLE);


    // Init message
    messages->Add(MessageKey::MOIN);
}

void Character::set_Status(const CharacterStatus& s) {
    if (status != s && status != CharacterStatus::DYING && active_anim && active_anim->has_audio)
        Animation::audio->Stop(active_anim->audio_name.c_str());
    status = s;
    active_anim->timer = 0;
    if (s != CharacterStatus::DEAD)
        active_anim->current_frame = 0;
    Animation::animation_frame_time = active_anim->frame_time;
}


void
Character::draw_CircleProgress(SDL_Renderer* renderer, int cx, int cy, int radius, float progress,
                        Uint8 color_r, Uint8 color_g, Uint8 color_b, Uint8 color_a) {
    if (progress < 0.0f) progress = 0.0f;
    if (progress > 1.0f) progress = 1.0f;

    int segments = 200;
    float angle_step = 2.0f * M_PI / segments;
    int filled_segments = static_cast<int>(segments * progress);

    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    SDL_SetRenderDrawColor(renderer, color_r, color_g, color_b, color_a);

    float offset_x = Scale(Map::offset.x);
    float offset_y = Scale(Map::offset.y);

    for (int i = 0; i < filled_segments; ++i) {
        float angle1 = i * angle_step;

        int x1 = cx + static_cast<int>(radius * std::cos(angle1));
        int y1 = cy + static_cast<int>(radius * std::sin(angle1));

        SDL_RenderLine(renderer, cx+offset_x, cy+offset_y, x1+offset_x, y1+offset_y);
    }

    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    SDL_SetRenderDrawColor(renderer, 16,47,144, 210);
    const int circle_segments = 100;
    for (int i = 0; i < circle_segments; ++i) {
        float angle1 = i * 2.0f * M_PI / circle_segments;
        float angle2 = (i + 1) * 2.0f * M_PI / circle_segments;
        int x1 = cx + static_cast<int>(radius * std::cos(angle1));
        int y1 = cy + static_cast<int>(radius * std::sin(angle1));
        int x2 = cx + static_cast<int>(radius * std::cos(angle2));
        int y2 = cy + static_cast<int>(radius * std::sin(angle2));
        SDL_RenderLine(renderer, x1+offset_x, y1+offset_y, x2+offset_x, y2+offset_y);
    }
}

void
Character::draw_HorizontalProgress(SDL_Renderer* renderer, float progress,
                                   Uint8 fill_r, Uint8 fill_g, Uint8 fill_b, Uint8 fill_a,
                                   Uint8 bg_r, Uint8 bg_g, Uint8 bg_b, Uint8 bg_a) {
    float width = Scale(20);   // horizontal bar width
    float height = Scale(2);   // bar thickness
    float x = Scale(collider_world.x + Map::offset.x) - width/2;
    float y = Scale(collider_world.y - collider.y*0.75 + Map::offset.y); // slightly above character

    if (progress < 0.0f) progress = 0.0f;
    if (progress > 1.0f) progress = 1.0f;

    SDL_FRect bg_rect = { x, y, width, height };
    SDL_FRect fill_rect = { x, y, width * progress, height };

    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

    // Draw background
    SDL_SetRenderDrawColor(renderer, bg_r, bg_g, bg_b, bg_a);
    SDL_RenderFillRect(renderer, &bg_rect);

    // Draw filled portion
    SDL_SetRenderDrawColor(renderer, fill_r, fill_g, fill_b, fill_a);
    SDL_RenderFillRect(renderer, &fill_rect);
}

void
Character::Draw(SDL_Renderer* renderer) {
#ifdef DEBUG_BUILD
    // 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(64),
        Scale(64)
    };
    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);
    }

    // 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_RenderFillRect(renderer, &debug_rect);
#endif

    if ( is_placing_build ) {
        BuildingData::draw_SelectedWorkbenchAt(renderer);
    }
    // Render Placinf Soil

    if (selected_item->object_type == InventoryObjectType::ITEM) {
        InventoryItem *it = static_cast<InventoryItem*>(selected_item);
        if (it->type == ItemType::CULTIVATOR) {
            if (cultivation_logic.is_removing)
                Farm::soil_RenderRemove(renderer, {static_cast<float>(tile_world.x*Tile::tile_size) + Map::offset.x, static_cast<float>(tile_world.y*Tile::tile_size) + Map::offset.y});
            else
                Farm::soil_Render(renderer, {static_cast<float>(tile_world.x*Tile::tile_size) + Map::offset.x, static_cast<float>(tile_world.y*Tile::tile_size) + Map::offset.y}, {0,0}, 140);
        }
    }

    // Render Character
    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)
    };

    SDL_Texture* txt = active_anim->texture;

    if (status == CharacterStatus::DEAD && death_shuffler)
        txt = SDL_CreateTextureFromSurface(renderer, death_shuffler->getSurface());

    SDL_RenderTextureRotated(renderer, txt, &src, &dst,
            0.0, nullptr,
            flip ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE);
    if (collecting_logic.is_collecting) {
        float collecting_progress = collecting_logic.duration / collecting_logic.duration_max;
        Character::draw_HorizontalProgress(renderer, collecting_progress);
    }


    // START DEBUG Weapon rect
#ifdef DEBUG_BUILD
    SDL_FRect weaponrect;

    const float weapon_w = 30.0f;
    const float weapon_h = 16.0f;

    float cx = collider_world.x;
    float cy = collider_world.y;

    switch (direction) {
        case WalkDirection::LEFT:
            weaponrect = { Scale(cx - weapon_w + Map::offset.x), Scale(cy - weapon_w*0.5 + Map::offset.y), Scale(weapon_w), Scale(weapon_h) };
            break;

        case WalkDirection::RIGHT:
            weaponrect = { Scale(cx + Map::offset.x), Scale(cy - weapon_w*0.5 + Map::offset.y), Scale(weapon_w), Scale(weapon_h)};
            break;                                          
                                                            
        case WalkDirection::UP:
            weaponrect = { Scale(cx - weapon_h*0.5 + Map::offset.x), Scale(cy - weapon_w + Map::offset.y), Scale(weapon_h), Scale(weapon_w)};
            break;

        case WalkDirection::DOWN:
            weaponrect = { Scale(cx - weapon_h*0.5 + Map::offset.x), Scale(cy + Map::offset.y), Scale(weapon_h), Scale(weapon_w) };
            break;
        default:
            break;
    }
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 140);
    SDL_RenderFillRect(renderer, &weaponrect);
    // END DEBUG Weapon rect
#endif



    // Draw character messages
    const float row_h = 14.0f;
    float center_x = x + (src.w * 0.5f);
    float start_y  = y - row_h - 2.0f;
    messages->Render(renderer, center_x, start_y);
}

/*
bool
Character::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);

    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 = start_cy; cy < end_cy; ++cy) {
                    for (int cx = start_cx; cx < end_cx; ++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);
                        bool cell_value = obj_index < collision_map.size() ? collision_map[obj_index] : false;

                        if (cell_value)
                            return true;
                    }
                }

            }
        }
    }
    return false;
}

*/

/*
bool
Character::is_Colliding(const SDL_FRect& rect) {
    const int max_cells_x = static_cast<int>(Map::size.x * (Tile::tile_size / Tile::tile_collider_size));
    const int max_cells_y = static_cast<int>(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 int cell_x = static_cast<int>(corners[i][0] / Tile::tile_collider_size);
        const int cell_y = static_cast<int>(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])
            return true;
    }

    return false;
    //return is_CollidingWithObjects(rect);
}
*/



TileObject*
Character::find_ClosestObject(SDL_Point* tile_found, ObjectCategory category) {
    for (const SDL_Point& tile_point : tiles_in_reach) {
        auto& objects = Map::get_ObjectsFromMapTile(tile_point);
        for (auto& object : objects) {
            Object* o = Object::get_ObjectById(object.object_id);
            collecting_logic.tile_point = tile_point;
            // find collectable or building
            InventoryItem *it = nullptr;
            // Why should this happen?
            // if (selected_item->object_type != InventoryObjectType::NONE) {
            it = static_cast<InventoryItem*>(selected_item);
            // }
            if ( (category == ObjectCategory::NONE && o->category != ObjectCategory::STATIC)) {
                // THATS MIXING SHT
                if ( (o->category == ObjectCategory::RESOURCE && it &&
                     (static_cast<Resource*>(o)->is_Collectable(it->type)))
                      || o->category == ObjectCategory::BUILDING) {
                    *tile_found = tile_point;
                    return &object;
                } else
                if (o->category == ObjectCategory::FARM_CROP) {
                    Vegetable* veg = static_cast<Vegetable*>(o);
                    if (veg->is_Collectable())
                        return &object;
                } else {
                }
            } else
            if ( category == o->category  ) {
                if ( o->category == ObjectCategory::BUILDING ) {
                    *tile_found = tile_point;
                    return &object;
                }
                if ( o->category == ObjectCategory::RESOURCE && it &&
                     static_cast<Resource*>(o)->is_Collectable(it->type)) {
                    *tile_found = tile_point;
                    return &object;
                }
            }
        }
    }
    return nullptr;
}

void
Character::collect_Start(TileObject* map_object_p) {

    if (!map_object_p) {
        std::cout << "No item!\n";
        collecting_logic.is_collecting = false;
        return;
    }
        //Tile::print_TileObject(*map_object_p);
    Object* object = Object::get_ObjectById(map_object_p->object_id);

    set_Status(CharacterStatus::COLLECT);
    std::cout << "stat: " << (int)status << std::endl;

    collecting_logic.duration = 0;
    collecting_logic.object = map_object_p;
    std::cout << "Item found!\n";
    if (!map_object_p || (object->category != ObjectCategory::RESOURCE &&
                         object->category != ObjectCategory::FARM_CROP)) {
        std::cout << "Invalid object: " << (int)object->category << std::endl;
        collect_Stop();
        return;
    }

    std::cout << "CH " << collider_world.x << " " << collider_world.y << std::endl;
    std::cout << "OB " << (object->world_tile_position.x+object->dimension.x/2)*16 << " " << (object->world_tile_position.y+object->dimension.y)*16-8 << std::endl;
    collecting_logic.duration_max = 3;
    // COLLECT RESOURCE
    if (object->object_type != ObjectType::VEGETABLE) {
        collecting_logic.resource = Tile::get_ResourceFromTileSet(object->tileset_id, object->tileset_object_id);
        collecting_logic.duration_max = collecting_logic.resource.collecting_duration;
    } else {
    // COLLECT VEGETABLE
        collecting_logic.duration_max = static_cast<Vegetable*>(object)->collecting_duration_max;
    }
    collecting_logic.is_collecting = true;

    float obj_x = (object->world_tile_position.x + object->dimension.x * 0.5f) * 16.0f;
    float obj_y = (object->world_tile_position.y + object->dimension.y) * 16.0f - 8.0f;

    float dx = obj_x - collider_world.x;
    float dy = obj_y - collider_world.y;

    float angle = std::atan2(dy, dx);

    const float pi = 3.14159265358979323846f;
    const float quarter = pi * 0.25f;
    const float three_quarter = pi * 0.75f;

    if (angle >= -quarter && angle < quarter) {
        direction = WalkDirection::RIGHT;
        flip = false;
    }
    else if (angle >= quarter && angle < three_quarter) {
        direction = WalkDirection::DOWN;
        flip = false;
    }
    else if (angle >= -three_quarter && angle < -quarter) {
        direction = WalkDirection::UP;
        flip = false;
    }
    else {
        direction = WalkDirection::LEFT;
        flip = true;
    }

    float cl = collecting_logic.duration_max/2;
    if (collecting_logic.duration_max > 3)
        cl /= 2;
    Animation::animation_frame_time = cl / active_anim->frames.size();
    handle_Animation();
}

void
Character::collect_Object() {
    collecting_logic.duration += Default::delta_time;
    if (collecting_logic.duration >= collecting_logic.duration_max) {
        std::cout << "Collected item!\n";
        // Remove object from MapTileObject s

        Object* object = Object::get_ObjectById(collecting_logic.object->object_id);
        if (object->object_type == ObjectType::VEGETABLE) {
            Vegetable* v = static_cast<Vegetable*>(object);
            if (v->is_Rotten()) {
                inventory->add_Seed(v->type, inventory->get_Seed(v->type) == 0 ? 1 : (rand() % 3 == 1 ? 1 : 0), messages.get());
            } else if (v->is_seed)
                inventory->add_Seed(v->type, v->quantity, messages.get());
            else
                inventory->add_Vegetable(v->type, v->quantity, messages.get());

            set_VegetableDiscovery(v->type);

        } else {
            inventory->add_Resource(collecting_logic.resource.resource, static_cast<int>(collecting_logic.resource.quantity), messages.get());
            set_ResourceDiscovery(collecting_logic.resource.resource);
        }
        /*
        resources[collecting_logic.resource.resource] += collecting_logic.resource.quantity;
        */

        if (object->object_type == ObjectType::VEGETABLE) {
            static_cast<Vegetable*>(object)->pick_Up();
        } else {
            TileObject* to = collecting_logic.object;
            Object::Remove(Object::get_ObjectById(to->object_id));
        }
        collect_Stop();
    }
}

void
Character::collect_Stop() {
    collecting_logic.duration = 0.0;
    collecting_logic.object = nullptr;
    collecting_logic.is_collecting = false;
    collecting_logic.resource = {};
    set_Status(CharacterStatus::IDLE);
    std::cout << "Collecting stopped!\n";
}


float
Character::get_SprintStartFactor() const {
    if (!sprintstart_logic.is_starting)
        return 1.0f;

    double now = Default::get_TimeSinceStartInSec();
    double elapsed = now - sprintstart_logic.started_gamesec;

    if (elapsed <= 0.0)
        return 0.0f;

    double t = elapsed / sprintstart_logic.starting_duration;

    if (t >= 1.0)
        return 1.0f;

    return static_cast<float>(t);
}




bool
Character::build_Start() {
    SDL_Point tile_point;
    TileObject* map_object_p = find_ClosestObject(&tile_point, ObjectCategory::BUILDING);

    if (!map_object_p) {
        // std::cout << "No building found!\n";
        return false;
    }
    //Tile::print_TileObject(*map_object_p);
    Building* building = Building::get_BuildingById(map_object_p->object_id);

    if (status != CharacterStatus::BUILD) {
        set_Status(CharacterStatus::BUILD);
    }

    if (Building::construction_mode == BuildingStatus::CONSTRUCTING) {
        if (!building->Build(inventory.get())) return false;
    } else
    if (Building::construction_mode == BuildingStatus::DECONSTRUCT){
        building->building_status = BuildingStatus::DECONSTRUCT;
        if (building->Deconstruct(inventory.get())) {
            Object::Remove(building);
            return false;
        }
    }

    // SET ANIMATION DIRECTION
    float dx = (tile_point.x * Tile::tile_size + Tile::tile_size * 0.5f)
             - (collider_world.x + collider.w * 0.5f);

    float dy = (tile_point.y * Tile::tile_size + Tile::tile_size * 0.5f)
             - (collider_world.y + collider.h * 0.5f);

    // Horizontal dominates
    if (std::fabs(dx) > std::fabs(dy)) {
        if (dx < 0)
            direction = WalkDirection::LEFT;
        else
            direction = WalkDirection::RIGHT;
        flip = (dx < 0);
    }
    // Vertical dominates
    else {
        if (dy > 0)
            direction = WalkDirection::DOWN;
        else
            direction = WalkDirection::UP;
        flip = false;
    }
    return true;
}


inline void setActiveMenu(CharacterMenuType& active_menu, const CharacterMenuType& new_menu, AnimatedCursor* cursor) {
    switch (active_menu) {
        case CharacterMenuType::NONE:
            active_menu = new_menu;
            cursor->set_State(CursorState::ANIMATED);
            break;
        default:
            active_menu = CharacterMenuType::NONE;
            cursor->set_State(CursorState::HIDDEN);
            break;
    }
}

void
Character::interact_WithBuilding(Building* building, Input& input) {
    if (active_menu == CharacterMenuType::WORKBENCH) {
        building->close_Menu();
        setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);
    } else
    if (building->has_Menu()) {
        std::cout << "INTERACT OPEN" << std::endl;
        CharacterMenuType type = building->open_Menu(this);
        if (type != CharacterMenuType::NONE)
            setActiveMenu(active_menu, type, input.cursor);
    }
}


bool
Character::is_StatsBonusAlready(InventoryObjectType object_type, uint32_t type) {
    for (CharacterStatsBonus& b : stats_bonus)
        if (b.object_type == object_type && b.type == type)
            return true;

    return false;
}

void
Character::add_ToStatsBonus(InventoryObjectType object_type,
                            uint32_t type,
                            ConsumingStatsBonus sb) {
    for (auto& entry : stats_bonus) {
        if (entry.object_type == object_type &&
            entry.type == type)
        {
            entry.stats = sb;
            entry.duration = sb.duration;
            health_Increase(sb.health);
            stamina_Increase(sb.stamina);
            std::cout << "ADD STATS " << (int)type << std::endl;
            return;
        }
    }

    if (stats_bonus.size() >= 2) {
        std::cout << "I am overfed\n";
        return;
    }

    stats_bonus.push_back({object_type, type, sb.duration, sb});
}

void
Character::reduce_StatsBonus() {
    for (auto it = stats_bonus.begin(); it != stats_bonus.end(); )
    {
        float total_duration = it->stats.duration;
        if (total_duration <= 0.0f)
        {
            it = stats_bonus.erase(it);
            continue;
        }

        float previous_ratio = it->duration / total_duration;

        it->duration -= Default::delta_time;
        if (it->duration < 0.0f)
            it->duration = 0.0f;

        float new_ratio = it->duration / total_duration;

        float scale = (previous_ratio > 0.0f)
            ? (new_ratio / previous_ratio)
            : 0.0f;

        float oh = it->stats.health;
        float os = it->stats.stamina;
        it->stats.health  *= scale;
        it->stats.stamina *= scale;
        it->stats.thirst  *= scale;
        it->stats.demage  *= scale;

        stats.health_reduced  -= (oh - it->stats.health);
        if (stats.health_reduced <= 0.0f)
            stats.health_reduced = 0.0f;

        stats.stamina_reduced -= (os - it->stats.stamina);
        if (stats.stamina_reduced <= 0.0f)
            stats.stamina_reduced = 0.0f;

        if (it->duration <= 0.0f)
            it = stats_bonus.erase(it);
        else
            ++it;
    }
}

void
Character::attack_Start() {
    std::cout << "Attack Start\n";
    InventoryItem *it = nullptr;
    if (selected_item->object_type == InventoryObjectType::ITEM) {
        it = static_cast<InventoryItem*>(selected_item);
    }
    if (!it) return;
    set_Status(CharacterStatus::ATTACK);
    handle_Animation();
    attacking_logic.duration = 0.0;
    attacking_logic.item = it;
    attacking_logic.has_attacked = false;
    attacking_logic.is_attacking = AttackingLogicStatus_e::ATTACKING;

    //Animation::animation_frame_time = attacking_logic.item->speed / static_cast<float>(active_anim->frames.size());
}

void
Character::attack_Enemy() {
    attacking_logic.duration += Default::delta_time;

    // just hit at the end of the hit
    if (attacking_logic.duration >= attacking_logic.item->speed) {
        attack_Stop();
        return;
    }
    if ( attacking_logic.has_attacked ||
         attacking_logic.duration < attacking_logic.item->speed*0.5)
        return;

    std::cout << "Attack Enemy!!\n";

    // check if weapnon is in enemy range
    SDL_FRect weaponrect;

    const float weapon_w = 30.0f;
    const float weapon_h = 16.0f;

    float cx = collider_world.x;
    float cy = collider_world.y;

    switch (direction) {
        case WalkDirection::LEFT:
            weaponrect = { cx - weapon_w, cy - weapon_w*0.5f, weapon_w, weapon_h };
            break;

        case WalkDirection::RIGHT:
            weaponrect = { cx, cy - weapon_w*0.5f, weapon_w, weapon_h };
            break;

        case WalkDirection::UP:
            weaponrect = { cx - weapon_h*0.5f, cy - weapon_w, weapon_h, weapon_w };
            break;

        case WalkDirection::DOWN:
            weaponrect = { cx - weapon_h*0.5f, cy, weapon_h, weapon_w };
            break;
        default:
            break;
    }

    Enemy* enemy = Enemy::is_InEnemyReach(weaponrect);

    if (enemy && enemy->stats.health > 0)
        enemy->getting_Hit(damage_Get());

    attacking_logic.has_attacked = true;
}
void
Character::attack_Stop() {
    std::cout << "Attack stopped.\n";
    attacking_logic.duration = 0.0;
    attacking_logic.is_attacking = AttackingLogicStatus_e::NONE;
    attacking_logic.item = nullptr;
    attacking_logic.enemy = nullptr;
    attacking_logic.has_attacked = false;
    set_Status(CharacterStatus::IDLE);
}

void
Character::block_Start() {
    std::cout << "Block Start\n";
    InventoryItem *it = nullptr;
    if (selected_item->object_type == InventoryObjectType::ITEM) {
        it = static_cast<InventoryItem*>(selected_item);
    }
    if (!it) return;
    set_Status(CharacterStatus::BLOCK);
    handle_Animation();

    attacking_logic.duration = 0.0;
    attacking_logic.item = it;
    attacking_logic.is_attacking = AttackingLogicStatus_e::BLOCKING_RUNNING;

    Animation::animation_frame_time = attacking_logic.item->speed / static_cast<float>(active_anim->frames.size());
}

void
Character::block_Enemy() {
    if (attacking_logic.duration <= attacking_logic.item->speed/2)
        attacking_logic.duration += Default::delta_time;
    else  {
        attacking_logic.is_attacking = AttackingLogicStatus_e::BLOCKING_RUNNING;
        attacking_logic.duration   = attacking_logic.item->speed/2;
        active_anim->current_frame = active_anim->frames.size()/2-1;
    }

    return;
}

void
Character::block_Stop() {
    if (attacking_logic.duration < attacking_logic.item->speed) {
        attacking_logic.duration += Default::delta_time;
        return;
    }
    attacking_logic.duration = 0.0;
    attacking_logic.is_attacking = AttackingLogicStatus_e::NONE;
    attacking_logic.enemy = nullptr;
    set_Status(CharacterStatus::IDLE);
    return;
}

void
Character::building_Stop() {
    BuildingData::active_build.type = BuildingType::NONE;
    BuildingData::active_build.data = nullptr;
    is_placing_build = false;
}


void
Character::eat_Start() {
    std::cout << "Eating Start\n";
    if ( stats_bonus.size() >= 2 &&
         !is_StatsBonusAlready(selected_item->object_type, static_cast<uint32_t>(static_cast<InventoryVegetable*>(selected_item)->type)))
            return;

    messages->Add(MessageKey::EAT_APPRECIATION);
    set_Status(CharacterStatus::EATING);
    handle_Animation();
    consuming_logic.duration     = 0.0;
    consuming_logic.duration_max = 3.0f;
    consuming_logic.is_consuming = true;
    consuming_logic.is_drinking  = false;
}

void
Character::eat_Something() {
    // std::cout << "Eating ...\n";
    if (consuming_logic.duration < consuming_logic.duration_max) {
        consuming_logic.duration += Default::delta_time;
        float stamina_per_second = 0.01f / consuming_logic.duration_max;
        stamina_Reduce(stamina_per_second);
        if (stamina_Get() <= 0) {
            stamina_StopReg();
            messages->Add(MessageKey::PLAYER_EXHAUSTED);
            consume_Stop();
        }
        return;
    }

    std::cout << "EATING COMPLETE\n";
    //InventoryVegetable
    if (selected_item->object_type == InventoryObjectType::VEGETABLE) {
        InventoryVegetable* it = static_cast<InventoryVegetable*>(selected_item);
        add_ToStatsBonus(InventoryObjectType::VEGETABLE, static_cast<uint32_t>(it->type), it->stats_bonus);
        if (inventory->remove_Vegetable(it->type, 1) == 0) {
            bool setted_hotbar = inventory->set_HotbarItem(Inventory::selected_hotbar, Inventory::hotbar_hands);
            if (setted_hotbar)
                inventory->Equip(Inventory::hotbar_hands);
            selected_item = inventory->get_SelectedHotbarItem();
        }
    }

    consume_Stop();
}

void
Character::drink_Start() {
    std::cout << "Drinking Start\n";
    messages->Add(MessageKey::WATER_APPRECIATION);
    if (stats.thirst >= stats.thirst_max)
        return;
    set_Status(CharacterStatus::DRINK_RIVER);
    handle_Animation();
    consuming_logic.duration = 0.0;
    consuming_logic.is_consuming = true;
    consuming_logic.is_drinking  = true;
}

void
Character::drink_River() {
    // std::cout << "Drinking ...\n";
    if (consuming_logic.duration < consuming_logic.duration_max) {
        if (stamina_Get() <= 0 || stats.thirst >= stats.thirst_max) {
            consume_Stop();
            return;
        }
        consuming_logic.duration += Default::delta_time;
        float amount_per_second = 25.0f / consuming_logic.duration_max;
        stats.thirst += amount_per_second * Default::delta_time;
        float stamina_per_second = 0.01f / consuming_logic.duration_max;
        stamina_Reduce(stamina_per_second);
        if (stamina_Get() <= 0) {
            stamina_StopReg();
            messages->Add(MessageKey::PLAYER_EXHAUSTED);
        }
        return;
    }

    if (stats.thirst > stats.thirst_max)
        stats.thirst = stats.thirst_max;

    health_Increase(2);

    consume_Stop();
}
void
Character::consume_Stop() {
    std::cout << "Consuming Stopped\n";
    consuming_logic.duration = 0.0;
    consuming_logic.is_consuming = false;
    set_Status(CharacterStatus::IDLE);
}

void
Character::cultivate_Start() {
    std::cout << "Cultivate Start\n";
    TileTypeFloors floor_type = Tile::get_FloorType(Map::tile_map[Map::get_IdFromTilePoint(tile_world)].background);
    std::cout << "FT: " << floor_type << " " << Map::get_IdFromTilePoint(tile_world) << std::endl;
    if (!Tile::floor_props[floor_type].can_buid_on)
        return;
    if (stamina_Get() <= 0)
        return;

    if ( Farm::soil_IsMaptile(tile_world) &&
         !cultivation_logic.is_removing)
        return;
    cultivate_Stop();
    set_Status(CharacterStatus::CULTIVATING);
    handle_Animation();
    cultivation_logic.is_cultivating = true;

    cultivation_logic.tile_world = tile_world;
}
void
Character::cultivate_Soil() {
    if (stamina_Get() <= 0) {
        cultivate_Stop();
        return;
    }
    cultivation_logic.duration += Default::delta_time;

    float stamina_per_second = 0.06f / cultivation_logic.duration_max;
    stamina_Reduce(stamina_per_second);
    if (stamina_Get() <= 0) {
        stamina_StopReg();
        messages->Add(MessageKey::PLAYER_EXHAUSTED);
        cultivate_Stop();
    }

    if (cultivation_logic.duration >= cultivation_logic.duration_max) {
        if (!cultivation_logic.is_removing)
            Farm::soil_Place(cultivation_logic.tile_world);
        else
            Farm::soil_Remove(cultivation_logic.tile_world);
        cultivate_Stop();
    }
}
void
Character::cultivate_Stop() {
    cultivation_logic.duration       = 0.0;
    cultivation_logic.is_cultivating = false;
    cultivation_logic.tile_world     = {0,0};
    set_Status(CharacterStatus::IDLE);
}

bool
Character::plant_Start() {
    std::cout << "Plant Start\n";
    if (stamina_Get() <= 0)
        return false;
    if (!Farm::soil_IsMaptile(tile_world))
        return false;
    if (Map::tile_map[static_cast<size_t>(Map::get_IdFromTilePoint(tile_world))].objects.size() > 0) {
        std::cout << "SIZE:: " << Map::tile_map[static_cast<size_t>(Map::get_IdFromTilePoint(tile_world))].objects.size() << std::endl;
        return false;
    }

    set_Status(CharacterStatus::PLANTING);
    handle_Animation();

    planting_logic.is_planting = true;
    planting_logic.tile_world  = tile_world;
    planting_logic.type        = static_cast<InventoryVegetable*>(selected_item)->type;
    planting_logic.is_seed = !static_cast<InventoryVegetable*>(selected_item)->is_seed;
    return true;
}

void
Character::plant_Vegetable() {
    if (stamina_Get() <= 0) {
        plant_Stop();
        return;
    }
    planting_logic.duration += Default::delta_time;

    float stamina_per_second = 0.06f / planting_logic.duration_max;
    stamina_Reduce(stamina_per_second);
    if (stamina_Get() <= 0) {
        stamina_StopReg();
        messages->Add(MessageKey::PLAYER_EXHAUSTED);
        plant_Stop();
    }

    if (planting_logic.duration >= planting_logic.duration_max) {
        Vegetable* v = new Vegetable(planting_logic.type, planting_logic.tile_world, planting_logic.is_seed);
        TileObject to = {{0,0}, true, v->id};
        size_t id = Map::get_IdFromTilePoint(planting_logic.tile_world);
        Map::tile_map[id].objects.push_back(to);
        if (!planting_logic.is_seed) // YES thats reversed!
            inventory->remove_Seed(planting_logic.type, 1);
        else
            inventory->remove_Vegetable(planting_logic.type, 1);

        selected_item = inventory->get_SelectedHotbarItem();

        plant_Stop();
    }
}

void
Character::plant_Stop() {
    std::cout << "Plant Stop\n";

    planting_logic.duration    = 0.0;
    planting_logic.is_planting = false;
    planting_logic.tile_world  = {0,0};
    set_Status(CharacterStatus::IDLE);
}


float
Character::health_Get() {
    return health_Max() - stats.health_reduced;
}
float
Character::health_Max() {
    return stats.health_max + health_MaxBonus();
}
float
Character::health_MaxBonus() {
    float h = 0.0f;
    for (auto& e : stats_bonus) {
        h += e.stats.health;
    }
    return h;
}
void
Character::health_Reduce(float amount) {
    stats.health_reduced += amount;
}
void
Character::health_Increase(float amount) {
    stats.health_reduced -= amount;
    if (stats.health_reduced < 0.0f)
        stats.health_reduced = 0.0;
}

float
Character::stamina_Get() {
    return stamina_Max() - stats.stamina_reduced;
}
float
Character::stamina_Max() {
    return stats.stamina_max + stamina_MaxBonus();
}
float
Character::stamina_MaxBonus() {
    float s = 0.0f;
    for (auto& e : stats_bonus) {
        s += e.stats.stamina;
    }
    return s;
}
void
Character::stamina_Reduce(float amount) {
    stats.stamina_reduced += amount;
}
void
Character::stamina_Increase(float amount) {
    stats.stamina_reduced -= amount;
    if (stats.stamina_reduced < 0.0f)
        stats.stamina_reduced = 0.0;
}

void
Character::stamina_StartReg() {
    if (stamina_Get() >= stamina_Max())
        return;

    if (!stamina_regen_logic.is_regenerating) {
        stamina_regen_logic.delay_is += Default::delta_time;
        if (stamina_regen_logic.delay_is >= stamina_regen_logic.delay_max)
            stamina_regen_logic.is_regenerating = true;
    } else {
        float amount_per_second = stamina_regen_logic.regen_speed / 1;
        if (status == CharacterStatus::WALK) {
            amount_per_second *= 0.5;
        }
        stamina_Increase(amount_per_second * Default::delta_time);
        if (stamina_Get() >= stamina_Max())
            stamina_StopReg();

    }
}

void
Character::stamina_StopReg() {
    stamina_regen_logic.is_regenerating = false;
    stamina_regen_logic.delay_is  = 0;
}

float
Character::damage_Get() {
    if (!using_Weapon())
        return 0.0f;
    std::cout << "DAMA " <<  static_cast<InventoryItem*>(selected_item)->damage << std::endl;
    return static_cast<InventoryItem*>(selected_item)->damage;
}


void
Character::thirst_Drain() {
    if (consuming_logic.is_consuming)
        return;

    if (stats.thirst <= 0) {
        float amount_per_second = 1;
        health_Reduce(amount_per_second * Default::delta_time);
        return;
    } else {
        thirst_drain_logic.send_message_0 = false;
    }

    if (!thirst_drain_logic.send_message_20 && stats.thirst <= 2*stats.thirst_max/10) {
        thirst_drain_logic.send_message_20 = true;
        messages->Add(MessageKey::THIRST_20);
    } else if (stats.thirst > 2*stats.thirst_max/10) {
        thirst_drain_logic.send_message_20 = false;
    }

    float amount_per_second = thirst_drain_logic.drain_speed / 1;
    stats.thirst -= amount_per_second * Default::delta_time;
    if (stats.thirst <= 0) {
        stats.thirst = 0;
        if (!thirst_drain_logic.send_message_0) {
            thirst_drain_logic.send_message_0 = true;
            messages->Add(MessageKey::THIRST_0);
        }
    }

}


void
Character::getting_Hit(float demage) {
    float block_staming = 2;
    if (attacking_logic.is_attacking == AttackingLogicStatus_e::NONE)
        set_Status(CharacterStatus::HIT);
    collecting_logic.is_collecting = false;
    if ((attacking_logic.is_attacking == AttackingLogicStatus_e::BLOCKING_RUNNING && stamina_Get() >= block_staming))
        stamina_Reduce(block_staming);
    else
        health_Reduce(demage);

    std::cout << "Char: Getting hit! " << std::endl;
}

void
Character::death() {
    if (status == CharacterStatus::DEAD) {
        if (death_shuffler)
            Resurrect();
        return;
    }
    death_duration += Default::delta_time;
    if (death_duration > death_duration_max) {
        if (status != CharacterStatus::DEAD) {
            set_Status(CharacterStatus::DEAD);
        }
    } else {
        if (status != CharacterStatus::DYING)
            set_Status(CharacterStatus::DYING);
    }
}


void
Character::Resurrect() {
    if (status != CharacterStatus::DEAD || !death_shuffler) {
        return;
    }

    shuffle_timer += Default::delta_time;

    constexpr float SHUFFLE_START_TIME = 1.6f;
    constexpr float MOVE_START_TIME    = 2.1f;
    constexpr float UNSHUFFLE_TIME     = 1.4f;
    constexpr float FRAME_REDUCE_TIME  = 0.8f;
    constexpr float MAX_DST            = 32.0f;

    auto distance = [](const SDL_FPoint& a, const SDL_FPoint& b) {
        float dx = a.x - b.x;
        float dy = a.y - b.y;
        return SDL_sqrtf(dx * dx + dy * dy);
    };

    float TICK_INTERVAL      = 0.04f;
    if (resurrect_phase > 0 && distance(collider_world, spawn_point) < MAX_DST) {
        TICK_INTERVAL      = 0.02f;
    }

    static float tick_accumulator = 0.0f;
    tick_accumulator += Default::delta_time;
    bool tick = false;
    if (tick_accumulator >= TICK_INTERVAL) {
        tick_accumulator -= TICK_INTERVAL;
        tick = true;
    }

    /* ---------------- PHASE 0: INITIAL DELAY ---------------- */
    if (resurrect_phase == 0) {
        if (shuffle_timer >= SHUFFLE_START_TIME) {
            resurrect_phase = 1;
        }
        return;
    }

    /* ---------------- PHASE 1: SHUFFLE ---------------- */
    if (resurrect_phase == 1) {
        if (tick) {
            death_shuffler->shufflePixels();
        }

        if (shuffle_timer >= MOVE_START_TIME) {
            move_logic.move_to_position = spawn_point;
            move_logic.automove = true;
            death_position = collider_world;
            resurrect_phase = 2;
        }
        return;
    }

    /* ---------------- PHASE 2: SHUFFLE + MOVE ---------------- */
    if (resurrect_phase == 2) {
        if (tick) {
            if (MAX_DST && distance(collider_world, spawn_point) > MAX_DST)
                death_shuffler->shufflePixels();
            else
                death_shuffler->unshuffleStep();
        }

        if (distance(collider_world, spawn_point) <= 2.0f) {
            resurrect_phase = 3;
            resurrection_timer = 0.0f;
            move_logic.automove = false;
        }
        return;
    }

    /* ---------------- PHASE 3: UNSHUFFLE (2.8s) ---------------- */
    if (resurrect_phase == 3) {
        resurrection_timer += Default::delta_time;

        if (tick) {
            death_shuffler->unshuffleStep();
        }

        if (resurrection_timer >= UNSHUFFLE_TIME) {
            resurrection_timer = 0.0f;
            resurrection_start_frame = active_anim->current_frame;
            resurrect_phase = 4;
        }
        return;
    }

    /* ---------------- PHASE 4: FRAME REDUCTION (0.8s) ---------------- */
    if (resurrect_phase == 4) {
        resurrection_timer += Default::delta_time;

        float t = resurrection_timer / FRAME_REDUCE_TIME;
        if (t > 1.0f) t = 1.0f;

        active_anim->current_frame =
            resurrection_start_frame * (1.0f - t);

        if (resurrection_timer >= FRAME_REDUCE_TIME) {
            resurrect_phase = 5;
        }
        return;
    }

    /* ---------------- PHASE 5: REVIVE ---------------- */
    delete death_shuffler;
    death_shuffler = nullptr;

    resurrect_phase = 0;
    shuffle_timer = 0.0f;
    resurrection_timer = 0.0f;

    Revive();
}


void
Character::Revive() {
    stats.health_reduced = 0.0f;
    stats.thirst = stats.thirst_max;
    move_logic.automove = false;
    set_Status(CharacterStatus::IDLE);
    if (death_shuffler)
        delete death_shuffler;

    death_shuffler = nullptr;
    resurrection_unshuffle_phase = false;
    resurrection_unshuffle_timer = 0.0f;
    shuffle_timer = 0.0f;
    death_duration = 0.0f;
    set_Status(CharacterStatus::IDLE);
    std::cout << "REVIVED!!\n";
}


void
set_AttackAnimation(AnimationData* &ad, WalkDirection& dir, Character* ch) {
    switch (static_cast<InventoryItem*>(ch->selected_item)->type) {
        case ItemType::SWORD_WOOD:
            ad = &ch->animation.attack_sword[dir];
            break;

        case ItemType::WOODAXE_STONE:
            ad = &ch->animation.hit_woodaxe_stone[dir];
            break;

        default:
            ad = nullptr;
            return;
    }

    if (!ad || ad->frames.empty()) {
        fprintf(stderr,
                "ERROR: Empty ATTACK animation\n"
                "  item=%d dir=%d\n",
                (int)static_cast<InventoryItem*>(ch->selected_item)->type,
                (int)dir
               );
    }
}

void
Character::handle_Animation() {

    AnimationData* active_anim_ref = nullptr;

    WalkDirection dir = direction;
    if ( direction == WalkDirection::LEFT ||
         direction == WalkDirection::RIGHT)
        dir = WalkDirection::SIDE;

    switch (status) {
        case CharacterStatus::COLLECT:
            active_anim_ref = &animation.collect[dir];
            break;
        case CharacterStatus::CULTIVATING:
            active_anim_ref = &animation.cultivating[dir];
            break;
        case CharacterStatus::PLANTING:
            active_anim_ref = &animation.planting[dir];
            break;
        case CharacterStatus::BUILD:
            active_anim_ref = &animation.build[dir];
            break;
        case CharacterStatus::WALK:
            active_anim_ref = &animation.walk[dir];
            break;
        case CharacterStatus::RUN:
            active_anim_ref = &animation.run[dir];
            break;

        case CharacterStatus::ATTACK:
            set_AttackAnimation(active_anim_ref, dir, this);
            break;

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

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

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

        case CharacterStatus::CRAFT:
            active_anim_ref = &animation.craft[dir];
            break;

        case CharacterStatus::BLOCK:
            active_anim_ref = &animation.block[dir];
            break;

        case CharacterStatus::DRINK_RIVER:
            active_anim_ref = &animation.drinking_river[dir];
            break;

        case CharacterStatus::EATING:
            active_anim_ref = &animation.eating[dir];
            break;

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

    if (status == CharacterStatus::DEAD) {
        if (active_anim_ref) {
            /* THIS SHOULD NOT BE REQUIRED
            if ( active_anim_ref->frames.size() == 0)
                active_anim_ref = &animation.idle[dir];
                */
            AnimationData& ad = *active_anim_ref;

            // lock last frame
            //if (!ad.frames.empty() && !ad.reverse) {
            if (resurrect_start == 2) {
                ad.current_frame = ad.frames.size() - 1;
            }

            ad.timer = 0.0f;
        }

        active_anim = active_anim_ref;
        return;
    }


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

    if (active_anim->frames.size() == 0) {
        std::cout << "STATUS: " << (int)status << " " << std::endl;
        exit(1);
    }
}


void
Character::handle_Input(Input& input, SDL_Rect win_size) {
    const bool* keys = SDL_GetKeyboardState(nullptr);

    // --- Handle mouse click for auto movement ---
    /*
    if (input.clicked_on_map) {
        move_logic.move_to_position = {input.mouse_state.x - Map::offset.x, input.mouse_state.y - Map::offset.y};
        move_logic.automove = true;
    }
    */

    thirst_Drain();
    if (health_Get() <= 0.0f) {
        death();
        return;
    }

    InventoryItem *sel_item = nullptr;
    if (selected_item->object_type == InventoryObjectType::ITEM) {
        sel_item = static_cast<InventoryItem*>(selected_item);
    }

    // BUILD MENU
    if (active_menu == CharacterMenuType::BUILDING) {
        if (BuildingData::menu_Hovering(input, win_size, discovered_buildings)) {
            if (input.click_left_released) {
                BuildingType t = BuildingData::menu_DetectWorkbenchClick(input, win_size, discovered_buildings);
                if (t != BuildingType::NONE) {
                    BuildingData::active_build.type = t;
                    BuildingData_s* data = BuildingData::get_BuildingDataByType(t);
                    BuildingData::active_build.data = data;
                    is_placing_build = true;
                    BuildingData::update_ActiveBuildData(collider_world.x, collider_world.y-collider.h);
                    BuildingData::active_build.object = Tile::get_ObjectFromTileSet(
                                                          data->tileset_id,
                                                          data->tileset_object_id,
                                                          data->tileset_type
                                                    );
                }
                return;
            }
        }
    } else
    // INVENTORY
    if (active_menu == CharacterMenuType::INVENTORY) {
        if (input.click_left || input.click_left_released) {
            if (Inventory::cursor_OverBuildMenu(input.mouse_state_raw)) {
                if (input.click_left_released) {
                    Inventory::selected_build_item = Inventory::hit_BuildMenu(input.mouse_state_raw, win_size, researched_items);
                    std::cout << "SLE IN " << (int)Inventory::selected_build_item << std::endl;
                    inventory->selected_inventory.is_valid = false;
                    Item::craft_Stop();
                    set_Status(CharacterStatus::IDLE);
                }
                return;
            } if (Inventory::cursor_OverInfoPanel(input.mouse_state_raw)){
                if (input.click_left_released) {
                    if ( Inventory::selected_build_item != ItemType::NONE && 
                         Inventory::cursor_OverCraftButton(input.mouse_state_raw)) {
                        if (Item::crafting_item.is_crafting) {
                            Item::craft_Stop();
                            return;
                        }
                        if (Item::craft_ItemStart(inventory.get(), Inventory::selected_build_item))
                            set_Status(CharacterStatus::CRAFT);
                    }
                }
                return;
            } if (Inventory::cursor_OverInventoryGrid(input.mouse_state_raw)){
                if (input.click_left_released) {
                    inventory->selected_inventory = inventory->hit_InventoryGrid(input.mouse_state_raw);
                    Inventory::selected_item = &inventory->selected_inventory;
                    Inventory::selected_build_item = ItemType::NONE;
                }
                return;
            }
        }
        if (Item::crafting_item.is_crafting && status == CharacterStatus::CRAFT) {
            if (!Item::craft_Item(inventory.get(), Item::crafting_item, messages.get())) {
                set_Status(CharacterStatus::IDLE);
                Item::craft_Stop();
            } else {
                if (Item::crafting_item.type == ItemType::SWORD_WOOD) {
                    if (!is_attackable) {
                        std::cout << "IM ATTACKABLE NOW!\n";
                        is_attackable = true;
                    }
                }
            }
            return;
        }
        if (keys[SDL_SCANCODE_1] ||
            keys[SDL_SCANCODE_2] ||
            keys[SDL_SCANCODE_3] ||
            keys[SDL_SCANCODE_4] ||
            keys[SDL_SCANCODE_5] ||
            keys[SDL_SCANCODE_6]) {
            if (inventory->selected_inventory.is_valid) {
                building_Stop();
                bool setted_hotbar = false;
                if (inventory->selected_inventory.vegetable != VegetableData::Type::NONE) {
                    InventoryVegetable& iv = inventory->vegetables.at(inventory->selected_inventory.vegetable);
                    setted_hotbar = inventory->set_HotbarItem(Inventory::selected_hotbar, &iv);
                    //inventory->Equip(it);
                } else
                if (inventory->selected_inventory.seed != VegetableData::Type::NONE) {
                    InventoryVegetable& iv = inventory->seeds.at(inventory->selected_inventory.seed);
                    setted_hotbar = inventory->set_HotbarItem(Inventory::selected_hotbar, &iv);
                    //inventory->Equip(it);
                } else {
                    setted_hotbar = inventory->set_HotbarItem(Inventory::selected_hotbar, inventory->selected_inventory.item);
                    if (setted_hotbar) {
                        if (sel_item) inventory->Equip(sel_item);
                    }
                    
                }
                if (setted_hotbar) {
                    if (consuming_logic.is_consuming && !consuming_logic.is_drinking)
                        consume_Stop();

                    selected_item = inventory->get_SelectedHotbarItem();
                    sel_item = nullptr;
                    if (selected_item->object_type == InventoryObjectType::ITEM) {
                        sel_item = static_cast<InventoryItem*>(selected_item);
                    }
                }
            }
            return;
        }
    } else
    // WORKBENCH
    if (active_menu == CharacterMenuType::WORKBENCH) {
        if (input.pressed_c_released )
            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);

        Building::draw_menu_curser_grid = Research::cursor_ResearchGridTileIndex(input.mouse_state_raw, win_size);
        // CRAFT
        if (input.click_left_released) {
            Craft::craft_Stop();

            if (Craft::is_CursorOverMenuInfoPanel(input.mouse_state_raw)) {
                std::vector<ItemType> rs = Item::get_ResearchedItems(researched_items);
                if ( Building::draw_menu_curser_grid_active >= 0 &&
                     Building::draw_menu_curser_grid_active < static_cast<int>(rs.size())) {
                    if ( Craft::cursor_OverCraftButton(input.mouse_state_raw)) {
                        Craft::craft_Start(inventory.get(), rs[static_cast<size_t>(Building::draw_menu_curser_grid_active)]);
                    }
                }

            } else { // SELECTING ? 
                Building::draw_menu_curser_grid_active = Building::draw_menu_curser_grid;
            }
        } else if (Craft::item.is_crafting) {
            bool enable_attackable = Craft::item.type == ItemType::SWORD_WOOD;
            if (!Craft::craft_Continue(inventory.get(), messages.get())) {
                Craft::craft_Stop();
                std::cout << "Crafting stopped..\n";
            } else {
                if (enable_attackable) {
                    if (!is_attackable) {
                        std::cout << "IM ATTACKABLE NOW!\n";
                        is_attackable = true;
                    }
                }
            }
        }
        return;
    } else
    // RESEARCH
    if (active_menu == CharacterMenuType::RESEARCH) {
        if (input.pressed_c_released )
            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);

        Building::draw_menu_curser_grid = Research::cursor_ResearchGridTileIndex(input.mouse_state_raw, win_size);
        if (input.click_left_released) {
            Research::research_Stop();

            if (Research::is_CursorOverMenuInfoPanel(input.mouse_state_raw)) {
                if ( Research::cursor_OverResearchButton(input.mouse_state_raw) &&
                     Building::draw_menu_curser_grid_active >= 0) {
                    Research::is_researching = true;
                    Research::research_Start();
                }

            } else {
                Building::draw_menu_curser_grid_active = Building::draw_menu_curser_grid;
            }
        } else if (Research::research_item.is_researching) {
            if (Research::research_Continue(researched_buildings, researched_items)) 
                set_BuildingDiscovery();
        }
    } else {
        if (active_menu != CharacterMenuType::NONE)
            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);

        Inventory::selected_build_item = ItemType::NONE;
        Item::crafting_item.is_crafting = false;
    }

    if (keys[SDL_SCANCODE_1] ||
            keys[SDL_SCANCODE_2] ||
            keys[SDL_SCANCODE_3] ||
            keys[SDL_SCANCODE_4] ||
            keys[SDL_SCANCODE_5] ||
            keys[SDL_SCANCODE_6]) {
        selected_item = inventory->get_SelectedHotbarItem();
        building_Stop();
        if (active_menu == CharacterMenuType::BUILDING)
            setActiveMenu(active_menu, CharacterMenuType::NONE, input.cursor);
        std::cout << "SWITCH " << selected_item->id << std::endl;
        return;
    }

    if ( Building::construction_mode == BuildingStatus::CONSTRUCTING &&
            is_placing_build &&
            input.click_left_released &&
            !BuildingData::active_build.is_colliding) {
        Map::place_ConstructionSide();
        BuildingData::active_build.type = BuildingType::NONE;
        BuildingData::active_build.data = nullptr;;
        is_placing_build = false;
        return;
    }


    // INTERACT OBJECT OR ITEM
    if (input.pressed_c_released && status != CharacterStatus::COLLECT) {
        SDL_Point tile_point;
        TileObject* map_object_p = find_ClosestObject(&tile_point, ObjectCategory::NONE);
        if (!map_object_p) {
            // check for river
            for (auto [x,y] : tiles_in_reach) {
                size_t id = static_cast<size_t>(y * Map::size.x + x);
                Tile_s ts = Map::tile_map[id].background;
                if (ts.object_id == SIZE_MAX) {
                    continue;
                }
                if (Tile::get_FloorType(ts) ==
                        TileTypeFloors::WATER) {
                    std::cout << "River found!\n";
                    drink_Start();
                    // character rotate to tile
                    return;
                }
            }
            std::cout << "Nothing to interact with! " << tiles_in_reach.size() << std::endl;
            if (!Animation::audio->is_Playing("character.hae"))
                Animation::audio->Play("character.hae");
            messages->Add(MessageKey::NOTHING_TO_INTERACT);
            return;
        }

        Object* object = Object::get_ObjectById(map_object_p->object_id);
        switch (object->category) {
            case ObjectCategory::RESOURCE:
            case ObjectCategory::FARM_CROP:
                collect_Start(map_object_p);
                std::cout << "STAT: " << (int)status << std::endl;
                return;
            case ObjectCategory::BUILDING:
                interact_WithBuilding(static_cast<Building*>(object), input);
                return;
            default: break;
        }
    }

    // CLICK MAP
    if (!input.pressed_ctrl) {
        // LEFT CLICK
        if (input.click_left) {
            if (sel_item) {
                // START CULTIVATE
                if ( sel_item->type == ItemType::CULTIVATOR && 
                     status != CharacterStatus::CULTIVATING)
                    cultivate_Start();
                else
                // START BUILD
                if (sel_item->type == ItemType::BUILDING_TOOL) {
                    build_Start();
                    return;
                } else
                // START ATTACK
                if (using_Weapon() &&
                        ( attacking_logic.is_attacking == AttackingLogicStatus_e::NONE ||
                          attacking_logic.is_attacking == AttackingLogicStatus_e::BLOCKING_RUNNING)) {
                    collecting_logic.is_collecting = false;
                    attack_Start();
                    return;
                }
            } else {
                if (!planting_logic.is_planting &&
                     Farm::soil_IsMaptile(tile_world)) {
                    switch (selected_item->object_type) {
                        case InventoryObjectType::VEGETABLE:
                        case InventoryObjectType::SEED:
                            if (plant_Start())
                                return;
                        default: break;
                    }
                }
                if (!planting_logic.is_planting && !consuming_logic.is_consuming && selected_item->object_type == InventoryObjectType::VEGETABLE) {
                    eat_Start();
                }
            }
        } else
        // RIGHT CLICK
        if (input.click_right) {
            if (using_Weapon() && attacking_logic.is_attacking == AttackingLogicStatus_e::NONE) {
                attacking_logic.is_attacking = AttackingLogicStatus_e::BLOCKING_START;
            }
        } else
        if (input.click_right_released) {
            if (attacking_logic.is_attacking == AttackingLogicStatus_e::BLOCKING_RUNNING) {
                std::cout << "STOP\n";
                attacking_logic.is_attacking = AttackingLogicStatus_e::BLOCKING_STOP;
            }
        }
    }


    if (consuming_logic.is_consuming) {
        if (consuming_logic.is_drinking)
            drink_River();
        else
            eat_Something();
        return;
    }


    if (status == CharacterStatus::BUILD) {
        set_Status(CharacterStatus::IDLE);
    }
    if (status == CharacterStatus::COLLECT) {
        collect_Object();
    } else if (collecting_logic.is_collecting) {
        collect_Stop();
    }

    if (input.pressed_shift && stamina_Get() > 0 &&
        (status == CharacterStatus::WALK || status == CharacterStatus::RUN)) {

        // START SPRINT
        double stamina_drain = 0.05;

        if (sprintstart_logic.is_starting) {
            double elapsed = Default::get_TimeSinceStartInSec() - sprintstart_logic.started_gamesec;

            double t = elapsed / sprintstart_logic.starting_duration;
            if (t > 1.0)
                t = 1.0;

            stamina_drain *= (t * t);

            if (t >= 1.0) {
                sprintstart_logic.has_started = true;
                sprintstart_logic.is_starting = false;
                std::cout << "SPRINTING..\n";
            }
        }

        stamina_Reduce(stamina_drain);
        if (stamina_Get() <= 0) {
            stamina_StopReg();
            messages->Add(MessageKey::PLAYER_EXHAUSTED);
        }
        else {
            is_sprinting = true;
            if (!sprintstart_logic.is_starting && !sprintstart_logic.has_started) {
                sprintstart_logic.started_gamesec = Default::get_TimeSinceStartInSec();
                sprintstart_logic.is_starting = true;
                std::cout << "STARTING SPRINT\n";
            }

            if (sprintstart_logic.is_starting) {
                double elapsed = Default::get_TimeSinceStartInSec() - sprintstart_logic.started_gamesec;
                if (elapsed >= sprintstart_logic.starting_duration) {
                    sprintstart_logic.has_started = true;
                    sprintstart_logic.is_starting = false;
                    std::cout << "SPRINTING..\n";
                }
            }
        }
    } else if (is_sprinting) {
        // SPRINT END
        is_sprinting = false;
        sprintstart_logic.started_gamesec = 0;
        sprintstart_logic.is_starting = false;
        sprintstart_logic.has_started = false;
        std::cout << "STOP SPRINTING\n";
    }


    switch (attacking_logic.is_attacking) {
        case AttackingLogicStatus_e::ATTACKING:
            attack_Enemy();
            break;
        case AttackingLogicStatus_e::BLOCKING_START:
            block_Start();
            break;
        case AttackingLogicStatus_e::BLOCKING_RUNNING:
            block_Enemy();
            break;
        case AttackingLogicStatus_e::BLOCKING_STOP:
            block_Stop();
            break;
        default: break;

    }
    if (attacking_logic.is_attacking != AttackingLogicStatus_e::NONE)
        return;
    if ( status == CharacterStatus::PLANTING && planting_logic.is_planting) {
        plant_Vegetable();
    }
    else
    if ( status == CharacterStatus::CULTIVATING) {
        cultivate_Soil();
    } else {
        if (cultivation_logic.is_cultivating) 
            cultivate_Stop();
    }

    if (stamina_regen_logic.is_regenerating && (
                status != CharacterStatus::IDLE &&
                status != CharacterStatus::WALK))
        stamina_StopReg();
    else if (status == CharacterStatus::IDLE ||
             status == CharacterStatus::WALK)
        stamina_StartReg();
}

void Character::handle_Move() {
    if (attacking_logic.is_attacking == AttackingLogicStatus_e::ATTACKING)
        return;
    const bool* keys = SDL_GetKeyboardState(nullptr);

    float dx = 0.0f;
    float dy = 0.0f;

    // --- Manual movement input ---
    if (keys[SDL_SCANCODE_W]) dy -= 1.0f;
    if (keys[SDL_SCANCODE_S]) dy += 1.0f;
    if (keys[SDL_SCANCODE_A]) dx -= 1.0f;
    if (keys[SDL_SCANCODE_D]) dx += 1.0f;

    moving = (dx != 0.0f || dy != 0.0f);

    // --- Disable automove when manually moving ---
    /*
    if (manual_move)
        move_logic.automove = false;
        */

    if (move_logic.automove) {
        float target_x = move_logic.move_to_position.x;
        float target_y = move_logic.move_to_position.y;

        float center_x = x + collider.x + collider.w * 0.5f;
        float center_y = y + collider.y + collider.h * 0.5f;

        dx = target_x - center_x;
        dy = target_y - center_y;

        float distance = std::sqrt(dx * dx + dy * dy);
        if (distance < 2.0f) { // close enough
            move_logic.automove = false;
            status = CharacterStatus::IDLE;
            return;
        }

        dx /= distance;
        dy /= distance;
        y += dy;
        x += dx;
        // --- 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;
        return;
    }

    // --- Stop if neither auto nor manual movement active ---
    if (!moving) {
        switch (status) {
            case CharacterStatus::WALK:
            case CharacterStatus::RUN:
                set_Status(CharacterStatus::IDLE);
                break;
            default: break;
        }
        return;
    }


    if (collecting_logic.is_collecting)
        collect_Stop();
    if (consuming_logic.is_consuming)
        consume_Stop();
    if (planting_logic.is_planting)
        plant_Stop();

    attacking_logic.is_attacking = AttackingLogicStatus_e::NONE;

    if (is_sprinting) {
        if (status != CharacterStatus::RUN) {
            set_Status(CharacterStatus::RUN);
        }
    } else
    if (status != CharacterStatus::WALK) {
        set_Status(CharacterStatus::WALK);
    }

    // --- Normalize and move ---
    float length = std::sqrt(dx * dx + dy * dy);
    if (length != 0.0f) {
        dx /= length;
        dy /= length;
    }

    float real_speed = speed;
    if (sprintstart_logic.is_starting) {
        float start_factor = get_SprintStartFactor();
        float current_mult = 1.0f + (sprint_multiply - 1.0f) * start_factor;
        real_speed *= current_mult;
    } else if (is_sprinting) {
        real_speed *= sprint_multiply;
    }


    float next_x = x + dx * real_speed * Default::delta_time;
    float next_y = y + dy * real_speed * Default::delta_time;

    SDL_FRect next_rect = collider;

    // Move horizontally
    next_rect.x = next_x + collider.x;
    next_rect.y = y + collider.y;
    if (Map::is_FreePosition(next_rect))
        x = next_x;

    // Move vertically
    next_rect.x = x + collider.x;
    next_rect.y = next_y + collider.y;
    if (Map::is_FreePosition(next_rect))
        y = next_y;

    // --- Direction / sprite flipping ---
    if (dx != 0.0f && dy != 0.0f) {
        if (dx < 0)
            direction = WalkDirection::LEFT;
        else
            direction = WalkDirection::RIGHT;
        flip = (dx < 0);
    } else if (dy > 0.0f) {
        direction = WalkDirection::DOWN;
        flip = false;
    } else if (dy < 0.0f) {
        direction = WalkDirection::UP;
        flip = false;
    } else if (dx != 0.0f) {
        if (dx < 0)
            direction = WalkDirection::LEFT;
        else
            direction = WalkDirection::RIGHT;
        flip = (dx < 0);
    }

    // --- 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 Character::Update() {
    //bird->set_Position({x,y});
    bird->Update(Default::delta_time, collider_world);

    // std::cout <<" COLOLOL " << collider_world.x << " "  << collider_world.y << std::endl;
    reduce_StatsBonus();

    if (status == CharacterStatus::HIT && active_anim->current_frame == 3) {
        set_Status(CharacterStatus::IDLE);
    }

    if ( is_placing_build)
        BuildingData::update_ActiveBuildData(collider_world.x, collider_world.y - collider.h);

    handle_Move();
    handle_Animation();
}


bool Character::is_RectWithinReach( const SDL_FRect& target) {
    // Player center
    float px = x+collider.x+collider.w*0.5;
    float py = y+collider.x+collider.w*0.5;

    // Target center
    float tx = target.x + target.w * 0.5f;
    float ty = target.y + target.h * 0.5f;

    float dx = tx - px;
    float dy = ty - py;
    float distance_sq = dx * dx + dy * dy;

    float reach_sq = arm_reach_radius * arm_reach_radius;
    return distance_sq <= reach_sq;
}


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

    const float reach       = arm_reach_radius;
    const float reach_sq    = reach * reach;
    const float cx          = collider_world.x;
    const float cy          = collider_world.y;
    const int   ts          = Tile::tile_size;

    int min_tx = static_cast<int>((cx - reach) / ts);
    int max_tx = static_cast<int>((cx + reach) / ts);
    int min_ty = static_cast<int>((cy - reach) / ts);
    int max_ty = static_cast<int>((cy + reach) / ts);

    if (min_tx < 0) min_tx = 0;
    if (min_ty < 0) min_ty = 0;
    if (max_tx >= Map::size.x) max_tx = Map::size.x - 1;
    if (max_ty >= Map::size.y) max_ty = Map::size.y - 1;

    for (int ty = min_ty; ty <= max_ty; ++ty) {
        float tile_top    = ty * ts;
        float tile_bottom = tile_top + ts;

        for (int tx = min_tx; tx <= max_tx; ++tx) {
            float tile_left  = tx * ts;
            float tile_right = tile_left + ts;

            float closest_x = cx < tile_left  ? tile_left  : (cx > tile_right  ? tile_right  : cx);
            float closest_y = cy < tile_top   ? tile_top   : (cy > tile_bottom ? tile_bottom : cy);

            float dx = closest_x - cx;
            float dy = closest_y - cy;

            if (dx * dx + dy * dy <= reach_sq)
                if (tx > 0 && ty > 0 && tx < Map::size.x && ty < Map::size.y)
                    tiles_in_reach.push_back({tx, ty});
        }
    }

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

            float da = (ax - cx) * (ax - cx) + (ay - cy) * (ay - cy);
            float db = (bx - cx) * (bx - cx) + (by - cy) * (by - cy);

            return da < db;
        }
    );
}

bool
Character::using_Weapon() {
    Inventory_s* it = nullptr;
    it = static_cast<Inventory_s*>(selected_item);
    if (!it) return false;
    if (it->object_type != InventoryObjectType::ITEM) 
        return false;
    ItemType& type = static_cast<InventoryItem*>(it)->type;
    return Item::is_Weapon(type);
}













void
Character::set_ResourceDiscovery(ResourceType resource) {
    size_t id = static_cast<size_t>(resource);

    if (discovered_resources[id])
        return;
    
    discovered_resources[id] = true;
    messages->Add(MessageKey::DISCOVERED_RESOURCE);
    set_BuildingDiscovery();
}


void
Character::set_VegetableDiscovery(VegetableData::Type vegetable) {
    size_t id = static_cast<size_t>(vegetable);

    if (discovered_vegetables[id])
        return;
    
    discovered_vegetables[id] = true;
    messages->Add(MessageKey::DISCOVERED_VEGETABLE);
}


void
Character::set_BuildingDiscovery() {
    for (BuildingData_s bs : BuildingData::building_data) {
        if (discovered_buildings[static_cast<size_t>(bs.type)])
            continue;
        bool discovered = true;
        if (level < bs.required_level)
            continue;
        if (!researched_buildings[static_cast<size_t>(bs.type)])
            continue;
        for (BuildingCost cost : bs.cost) {
            if (!discovered_resources[static_cast<size_t>(cost.type)]) {
                discovered = false;
                break;
            }
        }
        if (!discovered) 
            continue;

        discovered_buildings[static_cast<size_t>(bs.type)] = true;
        messages->Add(MessageKey::DISCOVERED_BUILDING);

    }
}



bool
Character::is_BuildingUnlocked(BuildingType type) {
    for (BuildingType t : unclocked__buildings)
        if (t == type) return true;
    return false;
}

void
Character::unlock_Building(BuildingType type) {
    unclocked__buildings.push_back(type);
}




bool
Character::Save(const std::string &path) const {
    std::cout << "Save Character\n";
    std::ofstream file(path + "/character", std::ios::binary | std::ios::trunc);
    if (!file) {
        std::cout << "No file found at: " << path << std::endl;
        return false;
    }

    file.write(reinterpret_cast<const char*>(&stats), sizeof(stats));
    file.write(reinterpret_cast<const char*>(&x),     sizeof(x));
    file.write(reinterpret_cast<const char*>(&y),     sizeof(y));
    file.write(reinterpret_cast<const char*>(&level), sizeof(level));


    inventory->Save(path);
    return file.good();
}


bool
Character::Load(const std::string &path) {
    std::cout << "Load Character\n";
    std::ifstream file(path + "/character", std::ios::binary);
    if (!file) {
        std::cout << "No file found at: " << path << std::endl;
        return false;
    }

    file.read(reinterpret_cast<char*>(&stats), sizeof(stats));
    file.read(reinterpret_cast<char*>(&x), sizeof(x));
    file.read(reinterpret_cast<char*>(&y), sizeof(y));
    file.read(reinterpret_cast<char*>(&level), sizeof(level));

    inventory->Load(path);

    selected_item = inventory->get_SelectedHotbarItem();

    for (auto rs : inventory->resources)
        set_ResourceDiscovery(rs.first);
    for (auto vs : inventory->vegetables)
        set_VegetableDiscovery(vs.first);
    for (auto vs : inventory->seeds)
        set_VegetableDiscovery(vs.first);
    set_BuildingDiscovery();

    return file.good();
}