diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index 37269f0b66..d26586ebbe 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -637,6 +637,38 @@ bool Game_Interpreter_Map::CommandPanScreen(lcf::rpg::EventCommand const& com) { break; } + 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]) { + 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. _state.wait_time = player.GetPanWait(); diff --git a/src/game_player.cpp b/src/game_player.cpp index 3f46674ed3..0650ed366f 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(); @@ -787,19 +789,83 @@ void Game_Player::StartPan(int direction, int distance, int 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) { + 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; + + // 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 { + 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)); + 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) { data()->pan_finish_x = GetDefaultPanX(); data()->pan_finish_y = GetDefaultPanY(); 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() { + 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); } @@ -812,10 +878,33 @@ void Game_Player::UpdatePan() { 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; + int dx; + int dy; + + 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 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))); + + dx2 = pan_remain_x >= 0 ? dx2 : -dx2; + dy2 = pan_remain_y >= 0 ? dy2 : -dy2; + + maniac_pan_current_x -= dx2; + maniac_pan_current_y -= dy2; + + // 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)); + + 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 */