#include "character/character.hpp" #include #include #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()), inventory(std::make_unique()), discovered_resources(static_cast(ResourceType::LAST), false), discovered_vegetables(static_cast(VegetableData::Type::COUNT), false), discovered_buildings(static_cast(BuildingType::LAST), false), researched_buildings(static_cast(BuildingType::LAST), false), researched_items (static_cast(ItemType::LAST), false) { std::cout << "CREATE CHARACTER\n"; active_menu = CharacterMenuType::NONE; researched_buildings[static_cast(BuildingType::RESEARCH_WOOD_1)] = true; researched_items[static_cast(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(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(radius * std::cos(angle1)); int y1 = cy + static_cast(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(radius * std::cos(angle1)); int y1 = cy + static_cast(radius * std::sin(angle1)); int x2 = cx + static_cast(radius * std::cos(angle2)); int y2 = cy + static_cast(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(selected_item); if (it->type == ItemType::CULTIVATOR) { if (cultivation_logic.is_removing) Farm::soil_RenderRemove(renderer, {static_cast(tile_world.x*Tile::tile_size) + Map::offset.x, static_cast(tile_world.y*Tile::tile_size) + Map::offset.y}); else Farm::soil_Render(renderer, {static_cast(tile_world.x*Tile::tile_size) + Map::offset.x, static_cast(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(std::floor(rect.x / float(Tile::tile_collider_size))); int end_cx = static_cast(std::ceil((rect.x + rect.w) / float(Tile::tile_collider_size))); int start_cy = static_cast(std::floor(rect.y / float(Tile::tile_collider_size))); int end_cy = static_cast(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(rect.x / Tile::tile_size); size_t tile_end_x = static_cast((rect.x + rect.w) / Tile::tile_size); size_t tile_start_y = static_cast(rect.y / Tile::tile_size); size_t tile_end_y = static_cast((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 collision_map = object->get_CollisionMap(); int obj_abs_x = static_cast(tx) - obj.position.x; int obj_abs_y = static_cast(ty) - obj.position.y; int obj_cx = obj_abs_x * static_cast(scale); int obj_cy = obj_abs_y * static_cast(scale); size_t obj_cw = static_cast(object->dimension.x) * scale; size_t obj_ch = static_cast(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(rel_x) >= obj_cw || static_cast(rel_y) >= obj_ch) continue; size_t obj_index = static_cast(rel_y) * obj_cw + static_cast(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(Map::size.x * (Tile::tile_size / Tile::tile_collider_size)); const int max_cells_y = static_cast(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(corners[i][0] / Tile::tile_collider_size); const int cell_y = static_cast(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(cell_y) * static_cast(max_cells_x) + static_cast(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(selected_item); // } if ( (category == ObjectCategory::NONE && o->category != ObjectCategory::STATIC)) { // THATS MIXING SHT if ( (o->category == ObjectCategory::RESOURCE && it && (static_cast(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(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(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(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(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(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(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(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(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(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(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(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(static_cast(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(selected_item); add_ToStatsBonus(InventoryObjectType::VEGETABLE, static_cast(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(Map::get_IdFromTilePoint(tile_world))].objects.size() > 0) { std::cout << "SIZE:: " << Map::tile_map[static_cast(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(selected_item)->type; planting_logic.is_seed = !static_cast(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(selected_item)->damage << std::endl; return static_cast(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(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(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(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(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(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 rs = Item::get_ResearchedItems(researched_items); if ( Building::draw_menu_curser_grid_active >= 0 && Building::draw_menu_curser_grid_active < static_cast(rs.size())) { if ( Craft::cursor_OverCraftButton(input.mouse_state_raw)) { Craft::craft_Start(inventory.get(), rs[static_cast(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(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(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((cx - reach) / ts); int max_tx = static_cast((cx + reach) / ts); int min_ty = static_cast((cy - reach) / ts); int max_ty = static_cast((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(selected_item); if (!it) return false; if (it->object_type != InventoryObjectType::ITEM) return false; ItemType& type = static_cast(it)->type; return Item::is_Weapon(type); } void Character::set_ResourceDiscovery(ResourceType resource) { size_t id = static_cast(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(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(bs.type)]) continue; bool discovered = true; if (level < bs.required_level) continue; if (!researched_buildings[static_cast(bs.type)]) continue; for (BuildingCost cost : bs.cost) { if (!discovered_resources[static_cast(cost.type)]) { discovered = false; break; } } if (!discovered) continue; discovered_buildings[static_cast(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(&stats), sizeof(stats)); file.write(reinterpret_cast(&x), sizeof(x)); file.write(reinterpret_cast(&y), sizeof(y)); file.write(reinterpret_cast(&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(&stats), sizeof(stats)); file.read(reinterpret_cast(&x), sizeof(x)); file.read(reinterpret_cast(&y), sizeof(y)); file.read(reinterpret_cast(&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(); }