// src/game/state.cpp #include "state.h" #include "../render/player.h" #include "../timing/speed_controller.h" #include "../assets/character_run_frames.h" #include "../assets/character_walk_frames.h" #include "../assets/character_crawl_frames.h" #include namespace { // Frame selector with two regimes: // 1) Normal (speed <= 6): ping‑pong with holds at both ends // 2) High‑speed (speed >= 7): 0, random(1..last-1), last, random(1..last-1)... static inline int compute_frame_index(int frame_count, float player_speed, int tick_counter) { if (frame_count <= 1) return 0; if (player_speed <= 0.0f) return 0; const int last = frame_count - 1; // High-speed pattern: 0, rand(1..last-1), last, rand(1..last-1), ... if (player_speed >= 7.0f) { // Special-case 2-frame animations if (frame_count <= 2) { return (tick_counter & 1) ? 1 : 0; } // Even ticks → extremes (0 or last), alternating each time. // Odd ticks → pseudo-random mid in [1..last-1]. if ((tick_counter & 1) == 0) { // Alternate 0 and last on even ticks bool choose_zero = ((tick_counter >> 1) & 1) == 0; return choose_zero ? 0 : last; } else { // Simple deterministic PRNG from tick_counter and speed unsigned int seed = static_cast(tick_counter) * 1664525u + 1013904223u; seed ^= static_cast(frame_count) * 2654435761u; seed ^= static_cast(player_speed * 997.0f); int range = last - 1; // size of 1..last-1 if (range <= 0) return 0; // safety int mid = 1 + static_cast(seed % static_cast(range)); return mid; } } // Normal-speed regime: discrete ping‑pong with holds at both ends. // - speed 1 => 0,1,2,...,last, last, ..., 2,1, 0,0 ... // - speed 2 => advances 2 steps per tick (skipping every second timeline entry) // - holds at ends make 0 and last appear more frequently const int hold = 2; // how many extra steps to hold at each end // One full ping‑pong cycle length: // [hold at 0] + [ascend 1..last] + [hold at last] + [descend last-1..0] const int ascend_len = last; // produces 1..last (length = last) const int descend_len = last; // produces last-1..0 (length = last) const int cycle_len = hold + ascend_len + hold + descend_len; // Advance by an integer number of steps per tick based on speed int step = static_cast(floorf(player_speed + 0.5f)); if (step < 1) step = 1; // Use 64-bit for safety, then fold into cycle length const long long pos64 = (static_cast(tick_counter) * static_cast(step)) % static_cast(cycle_len); int pos = static_cast(pos64); // Map timeline position to frame index if (pos < hold) { return 0; // hold at 0 } pos -= hold; if (pos < ascend_len) { // ascend: 1..last return (pos + 1); } pos -= ascend_len; if (pos < hold) { return last; // hold at last } pos -= hold; // descend: last-1 .. 0 return last - 1 - pos; } } // namespace void WalkingState::update() { // stop crawling after duration if (current_state == PlayerState::Crawl && state_timer.elapsed_time() >= CRAWL_DURATION) { current_state = PlayerState::Walk; state_timer.stop(); } } void WalkingState::start_crawl() { current_state = PlayerState::Crawl; state_timer.reset(); state_timer.start(); } void WalkingState::set_state(PlayerState s) { if (current_state != s) { walk_index = run_index = crawl_index = 0; phase = 0.0f; } current_state = s; } void WalkingState::set_motion_state_for_speed(int player_speed, int ground_speed) { int relative = player_speed - ground_speed; PlayerState new_state = (relative > 1) ? PlayerState::Run : PlayerState::Walk; if (new_state != current_state) { run_index = 0; walk_index = 0; } current_state = new_state; } // TODO THIS NEEDS REDESIGN FOR WALK/RUN void WalkingState::toggle_walk_frame(float player_speed, int tick_counter) { switch (current_state) { case PlayerState::Run: run_index = compute_frame_index(CHARACTER_RUN_FRAME_COUNT, player_speed, tick_counter); break; case PlayerState::Walk: walk_index = compute_frame_index(CHARACTER_WALK_FRAME_COUNT, player_speed, tick_counter); break; case PlayerState::Crawl: crawl_index = compute_frame_index(CHARACTER_CRAWL_FRAME_COUNT, player_speed, tick_counter); break; } } FrameSelection WalkingState::get_frame_selection() const { FrameSelection f{}; switch (current_state) { case PlayerState::Walk: f.movement = MovementType::Walk; f.frame_index = walk_index; break; case PlayerState::Run: f.movement = MovementType::Run; f.frame_index = run_index; break; case PlayerState::Crawl: f.movement = MovementType::Crawl; f.frame_index = crawl_index; break; default: f.movement = MovementType::Walk; f.frame_index = walk_index; break; } return f; }