Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maniacs Feature - Pixel scrolling support for CommandPanScreen #3245

Merged
merged 2 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/game_interpreter_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
99 changes: 94 additions & 5 deletions src/game_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double>(GetDefaultPanX());
maniac_pan_current_y = static_cast<double>(GetDefaultPanY());

ResetAnimation();

Expand Down Expand Up @@ -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<double>(data()->pan_current_x);
maniac_pan_current_y = static_cast<double>(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<double>(new_pan_x) - maniac_pan_current_x));
v_speed = std::abs((static_cast<double>(new_pan_y) - maniac_pan_current_y));
} else if (interpolated) {
// Interpolate distance by number of frames
h_speed = std::abs((static_cast<double>(new_pan_x) - maniac_pan_current_x)) / (speed + 1);
v_speed = std::abs((static_cast<double>(new_pan_y) - maniac_pan_current_y)) / (speed + 1);
} else {
// Multiply speed by 0.001
h_speed = std::max(static_cast<double>(speed * TILE_SIZE * 0.001), 1.0);
v_speed = std::max(static_cast<double>(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<int>(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);
}
Expand All @@ -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<double>(pan_remain_x)));
double dy2 = std::min(step_y, std::abs(static_cast<double>(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<double>(std::abs(maniac_pan_current_x)) == std::ceil(std::abs(maniac_pan_current_x)) ? static_cast<int>(std::floor(dx2)) : static_cast<int>(std::ceil(dx2));
dy = Utils::RoundTo<double>(std::abs(maniac_pan_current_y)) == std::ceil(std::abs(maniac_pan_current_y)) ? static_cast<int>(std::floor(dy2)) : static_cast<int>(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();
Expand Down
5 changes: 5 additions & 0 deletions src/game_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
Loading