From 3579ea4f6f623e1e34bcfb7910fb5e90092f04e9 Mon Sep 17 00:00:00 2001 From: Tool Man Date: Sat, 6 Jul 2024 18:55:28 -0400 Subject: [PATCH 1/2] 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 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 */ From beb2d9656ca09f57719eb57083355bed192ad0ac Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 22 Nov 2024 00:21:26 +0100 Subject: [PATCH 2/2] Pixel scrolling: Cleanup --- src/game_interpreter_map.cpp | 19 +++----- src/game_player.cpp | 85 +++++++++++++++++------------------- 2 files changed, 46 insertions(+), 58 deletions(-) diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index 79e7c60061..d26586ebbe 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -608,15 +608,6 @@ 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]) { @@ -645,9 +636,13 @@ 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); + + if (Player::IsPatchManiac() && com.parameters.size() > 5) { + // Pixel scrolling with h/v offsets + bool centered = false; // absolute from default pan (centered on hero) + bool relative = false; // relative to current camera + int h = ValueOrVariableBitfield(com, 1, 0, 2); + int v = ValueOrVariableBitfield(com, 1, 1, 3); waiting_pan_screen = (com.parameters[4] & 0x01) != 0; speed = ValueOrVariableBitfield(com, 1, 2, 5); switch (com.parameters[0]) { diff --git a/src/game_player.cpp b/src/game_player.cpp index c620da5db4..0650ed366f 100644 --- a/src/game_player.cpp +++ b/src/game_player.cpp @@ -772,8 +772,6 @@ 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) { @@ -790,48 +788,44 @@ 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; + + if (Player::IsPatchManiac()) { + // Maniac uses separate horizontal/vertical pan for everything + data()->maniac_horizontal_pan_speed = data()->pan_speed; + data()->maniac_vertical_pan_speed = data()->pan_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) { + if (!Player::IsPatchManiac()) { return; } h *= TILE_SIZE; v *= TILE_SIZE; + maniac_pan_current_x = static_cast(data()->pan_current_x); + maniac_pan_current_y = static_cast(data()->pan_current_y); + 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); - + // FIXME: Fails when relative and centered are used in combination 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 { - 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; - } + new_pan_x = GetSpriteX() - h; + new_pan_y = GetSpriteY() - v; } + double h_speed; + double v_speed; + if (speed == 0) { // Instant pan if speed is zero h_speed = std::abs((static_cast(new_pan_x) - maniac_pan_current_x)); @@ -853,15 +847,15 @@ void Game_Player::StartPixelPan(int h, int v, int speed, bool interpolated, bool } 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; + + if (Player::IsPatchManiac()) { + // Maniac uses separate horizontal/vertical pan for everything + data()->maniac_horizontal_pan_speed = data()->pan_speed; + data()->maniac_vertical_pan_speed = data()->pan_speed; + } } int Game_Player::GetPanWait() { @@ -880,31 +874,30 @@ 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; - // 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) { + + if (Player::IsPatchManiac()) { + const double step_x = data()->maniac_horizontal_pan_speed; + const double step_y = data()->maniac_vertical_pan_speed; + // 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))); + double dx2 = std::min(step_x, std::abs(static_cast(pan_remain_x))); + double dy2 = 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; + dx2 = pan_remain_x >= 0 ? dx2 : -dx2; + dy2 = pan_remain_y >= 0 ? dy2 : -dy2; - maniac_pan_current_x -= ddx; - maniac_pan_current_y -= ddy; + maniac_pan_current_x -= dx2; + maniac_pan_current_y -= dy2; - // 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)); + // Depending on the position, floor or ceil the value + dx = Utils::RoundTo(std::abs(maniac_pan_current_x)) == std::ceil(std::abs(maniac_pan_current_x)) ? static_cast(std::floor(dx2)) : static_cast(std::ceil(dx2)); + dy = Utils::RoundTo(std::abs(maniac_pan_current_y)) == std::ceil(std::abs(maniac_pan_current_y)) ? static_cast(std::floor(dy2)) : static_cast(std::ceil(dy2)); } else { dx = std::min(step, std::abs(pan_remain_x)); dy = std::min(step, std::abs(pan_remain_y));