diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index 37269f0b66..79e7c60061 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -608,6 +608,15 @@ bool Game_Interpreter_Map::CommandPanScreen(lcf::rpg::EventCommand const& com) { int speed; bool waiting_pan_screen = false; + // Maniac has new functions for pixel scrolling, which also have X and Y offsets + bool is_maniac = Player::IsPatchManiac(); + int h; + int v; + double h_speed; + double v_speed; + bool centered = false; + bool relative = false; + auto& player = *Main_Data::game_player; switch (com.parameters[0]) { @@ -636,6 +645,34 @@ bool Game_Interpreter_Map::CommandPanScreen(lcf::rpg::EventCommand const& com) { distance /= SCREEN_TILE_SIZE; break; } + if (is_maniac && com.parameters.size() > 5) { + h = ValueOrVariableBitfield(com, 1, 0, 2); + v = ValueOrVariableBitfield(com, 1, 1, 3); + waiting_pan_screen = (com.parameters[4] & 0x01) != 0; + speed = ValueOrVariableBitfield(com, 1, 2, 5); + switch (com.parameters[0]) { + case 4: // Relative Pixel Pan (speed) + centered = false; + relative = true; + player.StartPixelPan(h, v, speed, false, centered, relative); + break; + case 5: // Relative Pixel Pan (interpolated) + centered = false; + relative = true; + player.StartPixelPan(h, v, speed, true, centered, relative); + break; + case 6: // Absolute Pixel Pan (speed) + centered = (com.parameters[4] & 0x02) != 0; + relative = (com.parameters[4] & 0x04) != 0; + player.StartPixelPan(h, v, speed, false, centered, relative); + break; + case 7: // Absolute Pixel Pan (interpolated) + centered = (com.parameters[4] & 0x02) != 0; + relative = (com.parameters[4] & 0x04) != 0; + player.StartPixelPan(h, v, speed, true, centered, relative); + break; + } + } if (waiting_pan_screen) { // RPG_RT uses the max wait for all pending pan commands, not just the current one. diff --git a/src/game_player.cpp b/src/game_player.cpp index 3f46674ed3..c620da5db4 100644 --- a/src/game_player.cpp +++ b/src/game_player.cpp @@ -148,6 +148,8 @@ void Game_Player::MoveTo(int map_id, int x, int y) { data()->pan_finish_y = GetDefaultPanY(); data()->pan_current_x = GetDefaultPanX(); data()->pan_current_y = GetDefaultPanY(); + maniac_pan_current_x = static_cast(GetDefaultPanX()); + maniac_pan_current_y = static_cast(GetDefaultPanY()); ResetAnimation(); @@ -770,6 +772,8 @@ void Game_Player::UnlockPan() { } void Game_Player::StartPan(int direction, int distance, int speed) { + bool is_maniac = Player::IsPatchManiac(); + distance *= SCREEN_TILE_SIZE; if (direction == PanUp) { @@ -786,20 +790,88 @@ void Game_Player::StartPan(int direction, int distance, int speed) { data()->pan_finish_x = new_pan; } + // Maniac uses separate horizontal/vertical pan doubles for everything + if (is_maniac) { + data()->maniac_horizontal_pan_speed = static_cast(2 << speed); + data()->maniac_vertical_pan_speed = static_cast(2 << speed); + } data()->pan_speed = 2 << speed; } +void Game_Player::StartPixelPan(int h, int v, int speed, bool interpolated, bool centered, bool relative) { + const bool is_maniac = Player::IsPatchManiac(); + + if (!is_maniac) { + return; + } + + h *= TILE_SIZE; + v *= TILE_SIZE; + + int new_pan_x; + int new_pan_y; + + double h_speed; + double v_speed; + + int pan_current_x = data()->pan_current_x; + int pan_current_y = data()->pan_current_y; + maniac_pan_current_x = static_cast(pan_current_x); + maniac_pan_current_y = static_cast(pan_current_y); + + if (relative) { + new_pan_x = data()->pan_finish_x - h; + new_pan_y = data()->pan_finish_y - v; + } else { + if (centered) { + new_pan_x = GetSpriteX() + GetDefaultPanX() - h; + new_pan_y = GetSpriteY() + GetDefaultPanY() - v; + } else { + new_pan_x = GetSpriteX() - h; + new_pan_y = GetSpriteY() - v; + } + } + + if (speed == 0) { + // Instant pan if speed is zero + h_speed = std::abs((static_cast(new_pan_x) - maniac_pan_current_x)); + v_speed = std::abs((static_cast(new_pan_y) - maniac_pan_current_y)); + } else if (interpolated) { + // Interpolate distance by number of frames + h_speed = std::abs((static_cast(new_pan_x) - maniac_pan_current_x)) / (speed + 1); + v_speed = std::abs((static_cast(new_pan_y) - maniac_pan_current_y)) / (speed + 1); + } else { + // Multiply speed by 0.001 + h_speed = std::max(static_cast(speed * TILE_SIZE * 0.001), 1.0); + v_speed = std::max(static_cast(speed * TILE_SIZE * 0.001), 1.0); + } + + data()->pan_finish_x = new_pan_x; + data()->pan_finish_y = new_pan_y; + data()->maniac_horizontal_pan_speed = h_speed; + data()->maniac_vertical_pan_speed = v_speed; +} + void Game_Player::ResetPan(int speed) { + bool is_maniac = Player::IsPatchManiac(); data()->pan_finish_x = GetDefaultPanX(); data()->pan_finish_y = GetDefaultPanY(); + // Maniac uses separate horizontal/vertical pan doubles for everything + if (is_maniac) { + data()->maniac_horizontal_pan_speed = static_cast(2 << speed); + data()->maniac_vertical_pan_speed = static_cast(2 << speed); + } data()->pan_speed = 2 << speed; } int Game_Player::GetPanWait() { + bool is_maniac = Player::IsPatchManiac(); const auto distance = std::max( std::abs(data()->pan_current_x - data()->pan_finish_x), std::abs(data()->pan_current_y - data()->pan_finish_y)); - const auto speed = data()->pan_speed; + const auto speed = !is_maniac ? data()->pan_speed : static_cast(std::max( + std::abs(data()->maniac_horizontal_pan_speed), + std::abs(data()->maniac_vertical_pan_speed))); assert(speed > 0); return distance / speed + (distance % speed != 0); } @@ -808,14 +880,38 @@ void Game_Player::UpdatePan() { if (!IsPanActive()) return; + bool is_maniac = Player::IsPatchManiac(); const int step = data()->pan_speed; const int pan_remain_x = data()->pan_current_x - data()->pan_finish_x; const int pan_remain_y = data()->pan_current_y - data()->pan_finish_y; - int dx = std::min(step, std::abs(pan_remain_x)); - dx = pan_remain_x >= 0 ? dx : -dx; - int dy = std::min(step, std::abs(pan_remain_y)); - dy = pan_remain_y >= 0 ? dy : -dy; + // Maniac + const double step_x = data()->maniac_horizontal_pan_speed; + const double step_y = data()->maniac_vertical_pan_speed; + + int dx; + int dy; + if (is_maniac) { + // Maniac uses doubles for smoother screen scrolling + double ddx = std::min(step_x, std::abs(static_cast(pan_remain_x))); + double ddy = std::min(step_y, std::abs(static_cast(pan_remain_y))); + + ddx = pan_remain_x >= 0 ? ddx : -ddx; + ddy = pan_remain_y >= 0 ? ddy : -ddy; + + maniac_pan_current_x -= ddx; + maniac_pan_current_y -= ddy; + + // Depending on the position decimal, floor or ceil the value. + dx = std::round(std::abs(maniac_pan_current_x)) == std::ceil(std::abs(maniac_pan_current_x)) ? static_cast(std::floor(ddx)) : static_cast(std::ceil(ddx)); + dy = std::round(std::abs(maniac_pan_current_y)) == std::ceil(std::abs(maniac_pan_current_y)) ? static_cast(std::floor(ddy)) : static_cast(std::ceil(ddy)); + } else { + dx = std::min(step, std::abs(pan_remain_x)); + dy = std::min(step, std::abs(pan_remain_y)); + + dx = pan_remain_x >= 0 ? dx : -dx; + dy = pan_remain_y >= 0 ? dy : -dy; + } int screen_x = Game_Map::GetPositionX(); int screen_y = Game_Map::GetPositionY(); diff --git a/src/game_player.h b/src/game_player.h index 9cd826b074..6710dda2ba 100644 --- a/src/game_player.h +++ b/src/game_player.h @@ -139,9 +139,14 @@ class Game_Player : public Game_PlayerBase { static int GetDefaultPanX(); static int GetDefaultPanY(); + // Maniac uses these coordinates for smooth panning + double maniac_pan_current_x; + double maniac_pan_current_y; + void LockPan(); void UnlockPan(); void StartPan(int direction, int distance, int speed); + void StartPixelPan(int h, int v, int speed, bool interpolated, bool centered, bool relative); void ResetPan(int speed); /** @return how many frames it'll take to finish the current pan */