From 557d0ceef32495c65e3559057f860018c7179eb8 Mon Sep 17 00:00:00 2001 From: Tool Man Date: Sat, 6 Jul 2024 18:55:28 -0400 Subject: [PATCH] Adding pixel scrolling support Removing debug code, polishing code + Added support for .relative and .centered options for Absolute Pixel pan. Needed to bitmask parameter[4] in order to check for those. = Renamed a few commands = Added comments for some functions - Removed debug code Adjusting the speed formula Now it works very well, even has differnet speeds for both H and V. However since Maniac internally uses doubles for screen panning, we might have to do some more work... Revert "Adjusting the speed formula" This reverts commit 52fa8429ff463989c70297989a01aeab12238f1b. Adjusting the speed formula Again Now everything works as it should. Time to use doubles for everything Corrected interpolation, and proper scrolling speed Correct formula for absolute positioning Absolute Cam Panning now works Changed some naming scheme First things to do for Pixel Scrolling Adding support for commands However scrolling is a bit jaggy compared to ManiacPatch --- src/game_interpreter_map.cpp | 37 ++++++++++++ src/game_player.cpp | 106 +++++++++++++++++++++++++++++++++-- src/game_player.h | 5 ++ 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index e0d4f42577..f9698852e4 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -603,6 +603,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]) { @@ -631,6 +640,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 48091e9677..c1e3c62b2a 100644 --- a/src/game_player.cpp +++ b/src/game_player.cpp @@ -149,6 +149,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(); @@ -771,6 +773,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) { @@ -787,20 +791,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); } @@ -809,14 +881,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 */