mapcreator git · master
SDL3 2.5D game and engine using assets, with map editor
C++ 99.3%git clone https://git.christianimmanuel.de/sdl-graphics/mapcreator.gitwget https://git.christianimmanuel.de/sdl-graphics/mapcreator/archive/mapcreator.tar.gzsrc/character/character.cpp 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();
}