#include "bird.hpp" #include #include #include #include #include #include #include "defaults/defaults.hpp" #include "map/map.hpp" /* static constexpr float TAKEOFF_TIME = 1.8f; static constexpr float LANDING_TIME = 1.8f; */ static constexpr float FLIGHT_HEIGHT = 80.0f; // Consistent flying altitude SDL_Texture* Bird::texture; std::vector Bird::birds{}; std::atomic Bird::next_id{0}; Bird::Bird(float x, float y) : drawn(false), pos_x(x), pos_y(y), id(next_id.fetch_add(1, std::memory_order_relaxed)), target_pos{x, y}, decision_timer(0.0) { birds.push_back(this); set_State(BirdState::WALKING, 2.4); } bool Bird::Init(SDL_Renderer* renderer) { birds.clear(); texture = IMG_LoadTexture(renderer, "Bird/Spritesheet/Bird Spritesheet.png"); SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); return texture != nullptr; } void Bird::init_Landing() { //std::cout << "BIRD INIT LANDING!\n"; is_landing = true; landing_progress = 0.0f; landing_start_x = pos_x; landing_start_y = pos_y; landing_direction = facing_left ? -1.0f : 1.0f; // Store current flight direction landing_prev_x_offset = 0.0f; landing_prev_y_offset = 0.0f; } void Bird::Update(double delta_s, const SDL_FPoint& player_pos) { // std::cout << "ALT: " << altitude << std::endl; // std::cout << (int)state << " ISTART " << is_starting_to_fly << " ILAND " << is_landing << " XY: " << pos_x << " " << pos_y << " DC " << decision_timer << " STT: " << state_timer << std::endl; state_timer += delta_s; decision_timer -= delta_s; // Make decisions when idle on ground //if (landing_cooldown <= 0.0 && state == BirdState::IDLE && on_ground && decision_timer >= 2.0) { if ( decision_timer <= 0 && state != BirdState::FLYING) { Decide(player_pos); } /* // Handle eating state if (state == BirdState::EATING) { eating_timer += delta_s; if (eating_timer >= 3.0) { set_State(BirdState::START_TO_FLY, 0.0); pick_FlyTarget(player_pos); return; } } */ // Determine movement speed float speed = 0.0f; if (state == BirdState::FLYING) speed = FLY_SPEED; else if (state == BirdState::WALKING) speed = WALK_SPEED; // Calculate distance to target float dx = target_pos.x - pos_x; float dy = target_pos.y - pos_y; float dist = sqrtf(dx * dx + dy * dy); if (state == BirdState::WALKING) { if (dist > 0.4) { float move = speed * delta_s; if (move > dist) move = dist; float next_px = pos_x + (dx / dist) * move; float next_py = pos_y + (dy / dist) * move; bool can_walk = false; if (Map::is_FreePosition({ next_px - tile_size / 2, pos_y - tile_size / 2, tile_size, tile_size })) { pos_x = next_px; can_walk = true; } if (Map::is_FreePosition({ pos_x - tile_size / 2, next_py - tile_size / 2, tile_size, tile_size })) { pos_y += (dy / dist) * move; can_walk = true; } if (!can_walk) set_State(BirdState::IDLE, 0.4); if (fabsf(dx) > 0.01f) facing_left = (dx < 0.0f); } else { //std::cout << "BIRD WALK REACHED GOAL" << std::endl; set_State(BirdState::IDLE, 0.4); } } else // Handle flying movement if (state == BirdState::FLYING && !is_landing && !is_starting_to_fly) { if (dist > 2.0f) { pos_x += (dx / dist) * speed * delta_s; pos_y += (dy / dist) * speed * delta_s; // Update facing direction if (fabsf(dx) > 0.01f) { facing_left = (dx < 0.0f); } } else { // Reached target - begin landing init_Landing(); } } // Handle walking movement else if (is_starting_to_fly) { float overall_dx = target_pos.x - pos_x; takeoff_direction = (overall_dx >= 0.0f) ? 1.0f : -1.0f; float TAKEOFF_SPEED = 35.0f; takeoff_progress += TAKEOFF_SPEED * delta_s; float t = takeoff_progress / FLIGHT_HEIGHT; // Bias circle to start opposite target float circle_direction = -takeoff_direction; float angle, x_offset; if (t < 0.5f) { angle = (t / 0.5f) * (M_PI / 2.0f); x_offset = sinf(angle) * 30.0f * circle_direction; } else { angle = ((t - 0.5f) / 0.5f) * (M_PI / 2.0f); x_offset = (1.0f + sinf(angle)) * 30.0f * circle_direction; } float full_angle = t * M_PI; float y_offset = -sinf(full_angle) * 20.0f; // Delta float x_offset_delta = x_offset - takeoff_prev_x_offset; takeoff_prev_x_offset = x_offset; float y_offset_delta = -((takeoff_progress + y_offset) - takeoff_prev_y_offset); takeoff_prev_y_offset = takeoff_progress + y_offset; facing_left = ( pos_x+x_offset_delta - pos_x < 0.0f); pos_x += x_offset_delta; pos_y += y_offset_delta; altitude += y_offset_delta*-1; // If finished takeoff, align for straight flight if (takeoff_progress >= FLIGHT_HEIGHT) { is_starting_to_fly = false; // Reset offsets takeoff_prev_x_offset = 0.0f; takeoff_prev_y_offset = 0.0f; facing_left = (target_pos.x - pos_x < 0.0f); } // std::cout << "DEL: " << x_offset_delta << " " << y_offset_delta << std::endl; } // Handle landing animation with circular arc (position-based) else if (is_landing) { landing_progress += speed * delta_s; if (landing_progress >= FLIGHT_HEIGHT) { // if (fabs(target_pos.y - landing_start_y) >= FLIGHT_HEIGHT+1) { std::cout << "STOP FLYING! START LANDING" << std::endl; altitude = 0.0f; set_State(BirdState::STOP_FLYING, 0.0); return; } float t = landing_progress / FLIGHT_HEIGHT; float angle; float x_offset; if (t < 0.5f) { angle = (t / 0.5f) * (M_PI / 2.0f); x_offset = sinf(angle) * 30.0f * landing_direction; } else { angle = ((t - 0.5f) / 0.5f) * (M_PI / 2.0f); x_offset = (1.0f - sinf(angle)) * 30.0f * landing_direction; } float full_angle = t * M_PI; float y_offset = -sinf(full_angle) * 20.0f; float x_offset_delta = x_offset - landing_prev_x_offset; landing_prev_x_offset = x_offset; float y_offset_delta = (landing_progress + y_offset) - landing_prev_y_offset; landing_prev_y_offset = landing_progress + y_offset; float dx = pos_x + x_offset_delta - pos_x; if (fabsf(dx) > 0.01f) { facing_left = (dx < 0.0f); } altitude -= y_offset_delta; pos_x += x_offset_delta; pos_y += y_offset_delta; } // Update animation frames animation_timer += delta_s; if (animation_timer >= frame_duration_seconds) { animation_timer = 0.0; current_frame += frame_step; if (reverse_animation) { if (current_frame < 0) current_frame = 0; } else { if (current_frame >= frame_count) current_frame = 0; } src_rect.x = current_frame * tile_size; } } void Bird::Render(SDL_Renderer* renderer) { SDL_FRect dst; dst.x = Scale(pos_x + Map::offset.x); dst.y = Scale(pos_y + Map::offset.y); dst.w = Scale(tile_size); dst.h = Scale(tile_size); SDL_FlipMode flip = facing_left ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; SDL_RenderTextureRotated(renderer, texture, &src_rect, &dst, 0.0, nullptr, flip); } void Bird::set_Position(SDL_FPoint point) { pos_x = point.x; pos_y = point.y; } void Bird::set_State(BirdState new_state, double dec_timer) { //std::cout << "BIRD SET STATE " << (int)new_state << std::endl; decision_timer = dec_timer; /* if (state == new_state) return; */ state = new_state; state_timer = 0.0f; transition_timer = 0.0f; animation_timer = 0.0; reverse_animation = false; frame_step = 1; switch (state) { case BirdState::IDLE: current_frame = 0; set_Animation(0, 2, 0.25); break; case BirdState::FLYING: current_frame = 0; set_Animation(1, 8, 0.06); break; case BirdState::WALKING: current_frame = 0; set_Animation(2, 3, 0.15); break; case BirdState::EATING: if (!Animation::audio->is_Playing("bird.sing")) Animation::audio->Play("bird.sing"); current_frame = 0; set_Animation(3, 3, 0.15); break; case BirdState::START_TO_FLY: { current_frame = 0; is_starting_to_fly = true; altitude = 0.0f; set_Animation(4, 8, 0.06); decision_timer = 8*0.06; takeoff_progress = 0.0f; takeoff_prev_x_offset = 0.0f; takeoff_prev_y_offset = 0.0f; break; } case BirdState::STOP_FLYING: { frame_step = -1; reverse_animation = true; set_Animation(4, 8, 0.06); decision_timer = 8*0.06; is_landing = false; takeoff_direction = facing_left ? -1.0f : 1.0f; break; } case BirdState::ATTACK: { current_frame = 0; set_Animation(1, 8, 0.06); break; } } src_rect.x = current_frame * tile_size; } BirdState Bird::get_State() const { return state; } void Bird::set_Animation(int row, int frames, double frame_time) { frame_count = frames; frame_duration_seconds = frame_time; src_rect.x = current_frame * tile_size; src_rect.y = row * tile_size; src_rect.w = tile_size; src_rect.h = tile_size; } void Bird::Decide(const SDL_FPoint& player_pos) { // Calculate distance to player float dx = pos_x - player_pos.x; float dy = pos_y - player_pos.y; float dist_sq = dx * dx + dy * dy; //std::cout << "START DECIED\n"; // If too far, fly closer if ( dist_sq > MAX_RADIUS * MAX_RADIUS && state != BirdState::FLYING && state != BirdState::STOP_FLYING && state != BirdState::START_TO_FLY) { //std::cout << "BIRD TARGET TOO FAR\n"; set_State(BirdState::START_TO_FLY, 0.0); pick_FlyTarget(player_pos); return; } if (state == BirdState::START_TO_FLY) { set_State(BirdState::FLYING, 0.0); return; } // Random behavior when close to player int choice = rand() % 100; float duration = rand() % 70 * 0.1f + 1.1f; // std::cout << "COC " << choice << std::endl; if (state == BirdState::STOP_FLYING) { set_State(BirdState::IDLE, duration); return; } if (choice < 25) { set_State(BirdState::EATING, duration); } else if (choice < 50) { set_State(BirdState::WALKING, duration+1); pick_GroundTarget(player_pos); } else if (choice < 75) { set_State(BirdState::IDLE, duration); } else { set_State(BirdState::START_TO_FLY, 0.0); std::cout << "PDECIDE PICK START\n"; pick_FlyTarget(player_pos); std::cout << "PDECIDE PICK END\n"; } //std::cout << "END\n"; } void Bird::pick_FlyTarget(const SDL_FPoint& player_pos) { const float MIN_DIST = MAX_RADIUS; // minimum distance from current position float angle, radius, dx, dy, dist_sq; constexpr int max_attempts = 100; SDL_FPoint new_pos = target_pos; for (int i = 0; i < max_attempts; ++i) { angle = (rand() % 628) / 100.0f; // 0 to ~2*PI radius = MIN_RADIUS + (rand() % static_cast(MAX_RADIUS - MIN_RADIUS)); new_pos.x = player_pos.x + cosf(angle) * radius; new_pos.y = player_pos.y - FLIGHT_HEIGHT + sinf(angle) * (radius * 0.3f); // Clamp within map bounds new_pos.x = std::clamp(new_pos.x, 0.0f, static_cast(Map::size.x * Tile::tile_size)); new_pos.y = std::clamp(new_pos.y, 0.0f, static_cast(Map::size.y * Tile::tile_size)); dx = new_pos.x - pos_x; dy = new_pos.y - pos_y; dist_sq = dx * dx + dy * dy; if (!Map::is_FreePosition({ new_pos.x - tile_size / 2, new_pos.y - tile_size / 2, tile_size, tile_size })) continue; //dist_sq = MIN_DIST*MIN_DIST+1; if (dist_sq >= MIN_DIST * MIN_DIST) { target_pos = new_pos; return; } }; set_State(BirdState::IDLE, 0.8); } void Bird::pick_GroundTarget(const SDL_FPoint& player_pos) { constexpr int max_attempts = 100; SDL_FPoint new_pos = target_pos; for (int i = 0; i < max_attempts; ++i) { float offset_x= static_cast((rand() % 64) - 32); // -32 to +32 float offset_y= static_cast((rand() % 64) - 32); // -32 to +32 new_pos.x = player_pos.x + offset_x; new_pos.y = player_pos.y + offset_y; if (Map::is_FreePosition({ new_pos.x - tile_size / 2, new_pos.y - tile_size / 2, tile_size, tile_size })) { target_pos = new_pos; return; } }; set_State(BirdState::IDLE, 0.8); }