Skip to content

Commit

Permalink
Redo the X/Y shifting (Sprite clones) on looping maps.
Browse files Browse the repository at this point in the history
The change doubles the amount of sprites on looping maps but this fixes all the rendering issues, especially with big charsets ($).

Most of the sprites are culled because they are out of bounds on larger maps, so the performance implications are small.

Fix #3149
  • Loading branch information
Ghabry committed Apr 15, 2024
1 parent 6cbd0a2 commit c338202
Show file tree
Hide file tree
Showing 14 changed files with 61 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/battle_animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ void BattleAnimationMap::DrawSingle(Bitmap& dst) {
}
const int character_height = 24;
int x_off = target.GetScreenX();
int y_off = target.GetScreenY(false, false);
int y_off = target.GetScreenY(false);
if (Scene::instance->type == Scene::Map) {
x_off += static_cast<Scene_Map*>(Scene::instance.get())->spriteset->GetRenderOx();
y_off += static_cast<Scene_Map*>(Scene::instance.get())->spriteset->GetRenderOy();
Expand Down
18 changes: 5 additions & 13 deletions src/game_character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,18 @@ int Game_Character::GetJumpHeight() const {
return 0;
}

int Game_Character::GetScreenX(bool apply_shift) const {
int Game_Character::GetScreenX() const {
int x = GetSpriteX() / TILE_SIZE - Game_Map::GetDisplayX() / TILE_SIZE + TILE_SIZE;

if (Game_Map::LoopHorizontal()) {
x = Utils::PositiveModulo(x, Game_Map::GetTilesX() * TILE_SIZE);
}
x -= TILE_SIZE / 2;

if (apply_shift) {
x += Game_Map::GetTilesX() * TILE_SIZE;
}

return x;
}

int Game_Character::GetScreenY(bool apply_shift, bool apply_jump) const {
int Game_Character::GetScreenY(bool apply_jump) const {
int y = GetSpriteY() / TILE_SIZE - Game_Map::GetDisplayY() / TILE_SIZE + TILE_SIZE;

if (apply_jump) {
Expand All @@ -97,14 +93,10 @@ int Game_Character::GetScreenY(bool apply_shift, bool apply_jump) const {
y = Utils::PositiveModulo(y, Game_Map::GetTilesY() * TILE_SIZE);
}

if (apply_shift) {
y += Game_Map::GetTilesY() * TILE_SIZE;
}

return y;
}

Drawable::Z_t Game_Character::GetScreenZ(bool apply_shift) const {
Drawable::Z_t Game_Character::GetScreenZ(int x_offset, int y_offset) const {
Drawable::Z_t z = 0;

if (IsFlying()) {
Expand All @@ -118,8 +110,8 @@ Drawable::Z_t Game_Character::GetScreenZ(bool apply_shift) const {
}

// 0x8000 (32768) is added to shift negative numbers into the positive range
Drawable::Z_t y = static_cast<Drawable::Z_t>(GetScreenY(apply_shift, false) + 0x8000);
Drawable::Z_t x = static_cast<Drawable::Z_t>(GetScreenX(apply_shift) + 0x8000);
Drawable::Z_t y = static_cast<Drawable::Z_t>(GetScreenY(false) + y_offset + 0x8000);
Drawable::Z_t x = static_cast<Drawable::Z_t>(GetScreenX() + x_offset + 0x8000);

// The rendering order of characters is: Highest Y-coordinate, Highest X-coordinate, Highest ID
// To encode this behaviour all of them get 16 Bit in the Z value
Expand Down
11 changes: 5 additions & 6 deletions src/game_character.h
Original file line number Diff line number Diff line change
Expand Up @@ -693,27 +693,26 @@ class Game_Character {
/**
* Gets sprite x coordinate transformed to screen coordinate in pixels.
*
* @param apply_shift When true the coordinate is shifted by the map width (for looping maps)
* @return screen x coordinate in pixels.
*/
virtual int GetScreenX(bool apply_shift = false) const;
virtual int GetScreenX() const;

/**
* Gets sprite y coordinate transformed to screen coordinate in pixels.
*
* @param apply_shift When true the coordinate is shifted by the map height (for looping maps)
* @param apply_jump Apply jump height modifier if character is jumping
* @return screen y coordinate in pixels.
*/
virtual int GetScreenY(bool apply_shift = false, bool apply_jump = true) const;
virtual int GetScreenY(bool apply_jump = true) const;

/**
* Gets screen z coordinate
*
* @param apply_shift Forwarded to GetScreenY
* @param x_offset Offset to apply to the X coordinate
* @param y_offset Offset to apply to the Y coordinate
* @return screen z coordinate
*/
virtual Drawable::Z_t GetScreenZ(bool apply_shift = false) const;
virtual Drawable::Z_t GetScreenZ(int x_offset, int y_offset) const;

/**
* Gets tile graphic ID.
Expand Down
4 changes: 2 additions & 2 deletions src/game_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ lcf::rpg::SaveMapEvent Game_Event::GetSaveData() const {
return save;
}

Drawable::Z_t Game_Event::GetScreenZ(bool apply_shift) const {
Drawable::Z_t Game_Event::GetScreenZ(int x_offset, int y_offset) const {
// Lowest 16 bit are reserved for the ID
// See base function for full explanation
return Game_Character::GetScreenZ(apply_shift) + GetId();
return Game_Character::GetScreenZ(x_offset, y_offset) + GetId();
}

int Game_Event::GetOriginalMoveRouteIndex() const {
Expand Down
2 changes: 1 addition & 1 deletion src/game_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Game_Event : public Game_EventBase {
* Implementation of abstract methods
*/
/** @{ */
Drawable::Z_t GetScreenZ(bool apply_shift = false) const override;
Drawable::Z_t GetScreenZ(int x_offset, int y_offset) const override;
bool Move(int dir) override;
void UpdateNextMovementAction() override;
bool IsVisible() const override;
Expand Down
4 changes: 2 additions & 2 deletions src/game_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ lcf::rpg::SavePartyLocation Game_Player::GetSaveData() const {
return *data();
}

Drawable::Z_t Game_Player::GetScreenZ(bool apply_shift) const {
Drawable::Z_t Game_Player::GetScreenZ(int x_offset, int y_offset) const {
// Player is always "same layer as hero".
// When the Player is on the same Y-coordinate as an event the Player is always rendered first.
// This is different to events where, when Y is the same, the highest X-coordinate is rendered first.
// To ensure this, fake a very high X-coordinate of 65535 (all bits set)
// See base function for full explanation of the bitmask
return Game_Character::GetScreenZ(apply_shift) | (0xFFFFu << 16u);
return Game_Character::GetScreenZ(x_offset, y_offset) | (0xFFFFu << 16u);
}

void Game_Player::ReserveTeleport(int map_id, int x, int y, int direction, TeleportTarget::Type tt) {
Expand Down
2 changes: 1 addition & 1 deletion src/game_player.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class Game_Player : public Game_PlayerBase {
* Implementation of abstract methods
*/
/** @{ */
Drawable::Z_t GetScreenZ(bool apply_shift = false) const override;
Drawable::Z_t GetScreenZ(int x_offset, int y_offset) const override;
bool IsVisible() const override;
bool MakeWay(int from_x, int from_y, int to_x, int to_y) override;
void UpdateNextMovementAction() override;
Expand Down
4 changes: 2 additions & 2 deletions src/game_vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ int Game_Vehicle::GetAltitude() const {
return SCREEN_TILE_SIZE / (SCREEN_TILE_SIZE / TILE_SIZE);
}

int Game_Vehicle::GetScreenY(bool apply_shift, bool apply_jump) const {
return Game_Character::GetScreenY(apply_shift, apply_jump) - GetAltitude();
int Game_Vehicle::GetScreenY(bool apply_jump) const {
return Game_Character::GetScreenY(apply_jump) - GetAltitude();
}

bool Game_Vehicle::CanLand() const {
Expand Down
2 changes: 1 addition & 1 deletion src/game_vehicle.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class Game_Vehicle : public Game_VehicleBase {
bool IsAboard() const;
void SyncWithRider(const Game_Character* rider);
bool AnimateAscentDescent();
int GetScreenY(bool apply_shift = false, bool apply_jump = true) const override;
int GetScreenY(bool apply_jump = true) const override;
bool CanLand() const;
void StartAscent();
void StartDescent();
Expand Down
12 changes: 5 additions & 7 deletions src/sprite_airshipshadow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,14 @@
#include "sprite_airshipshadow.h"
#include <string>

Sprite_AirshipShadow::Sprite_AirshipShadow(CloneType type) {
Sprite_AirshipShadow::Sprite_AirshipShadow(int x_offset, int y_offset) :
x_offset(x_offset), y_offset(y_offset) {
SetBitmap(Bitmap::Create(16,16));

SetOx(TILE_SIZE/2);
SetOy(TILE_SIZE);

RecreateShadow();

x_shift = ((type & XClone) == XClone);
y_shift = ((type & YClone) == YClone);
}

// Draws the two shadow sprites to a single intermediate bitmap to be blit to the map
Expand Down Expand Up @@ -70,8 +68,8 @@ void Sprite_AirshipShadow::Update() {
const double opacity = (double)altitude / max_altitude;
SetOpacity(opacity * 255);

SetX(Main_Data::game_player->GetScreenX(x_shift));
SetY(Main_Data::game_player->GetScreenY(y_shift) + Main_Data::game_player->GetJumpHeight());
SetX(Main_Data::game_player->GetScreenX() + x_offset);
SetY(Main_Data::game_player->GetScreenY() + y_offset + Main_Data::game_player->GetJumpHeight());
// Synchronized with airship priority
SetZ(airship->GetScreenZ(y_shift) - 1);
SetZ(airship->GetScreenZ(x_offset, y_offset) - 1);
}
6 changes: 3 additions & 3 deletions src/sprite_airshipshadow.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ class Sprite_AirshipShadow : public Sprite {
YClone = 4
};

Sprite_AirshipShadow(CloneType type = CloneType::Original);
Sprite_AirshipShadow(int x_offset = 0, int y_offset = 0);
void Update();
void RecreateShadow();

private:
bool x_shift = false;
bool y_shift = false;
int x_offset = 0;
int y_offset = 0;
};

#endif
18 changes: 8 additions & 10 deletions src/sprite_character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@
#include "bitmap.h"
#include "output.h"

Sprite_Character::Sprite_Character(Game_Character* character, CloneType type) :
Sprite_Character::Sprite_Character(Game_Character* character, int x_offset, int y_offset) :
character(character),
tile_id(-1),
character_index(0),
chara_width(0),
chara_height(0) {

x_shift = ((type & XClone) == XClone);
y_shift = ((type & YClone) == YClone);
chara_height(0),
x_offset(x_offset),
y_offset(y_offset) {

Update();
}
Expand Down Expand Up @@ -76,10 +75,9 @@ void Sprite_Character::Update() {
SetOpacity(character->GetOpacity());
SetVisible(character->IsVisible());

SetX(character->GetScreenX(x_shift));
SetY(character->GetScreenY(y_shift));
// y_shift because Z is calculated via the screen Y position
SetZ(character->GetScreenZ(y_shift));
SetX(character->GetScreenX() + x_offset);
SetY(character->GetScreenY() + y_offset);
SetZ(character->GetScreenZ(x_offset, y_offset));

int bush_split = 4 - character->GetBushDepth();
SetBushDepth(bush_split > 3 ? 0 : GetHeight() / bush_split);
Expand Down Expand Up @@ -134,7 +132,7 @@ Rect Sprite_Character::GetCharacterRect(StringView name, int index, const Rect b
// VX Ace uses a single 1x1 spriteset of 3x4 sprites.
if (!name.empty() && name.front() == '$') {
if (!Player::HasEasyRpgExtensions()) {
Output::Debug("Ignoring large charset {}. EasyRPG Extension not enabled.");
Output::Debug("Ignoring large charset {}. EasyRPG Extension not enabled.", name);
} else {
rect.width = bitmap_rect.width * (TILE_SIZE / 16) / 4;
rect.height = bitmap_rect.height * (TILE_SIZE / 16) / 2;
Expand Down
9 changes: 5 additions & 4 deletions src/sprite_character.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ class Sprite_Character : public Sprite {
* Constructor.
*
* @param character game character to display
* @param type Type of the sprite for multiple renderings on looping maps
* @param x_offset X Render offset when being a clone
* @param y_offset Y Render offset when being a clone
*/
Sprite_Character(Game_Character* character, CloneType type = CloneType::Original);
Sprite_Character(Game_Character* character, int x_offset = 0, int y_offset = 0);

/**
* Updates sprite state.
Expand Down Expand Up @@ -94,8 +95,8 @@ class Sprite_Character : public Sprite {
/** Returns true for charset sprites; false for tiles. */
bool UsesCharset() const;

bool x_shift = false;
bool y_shift = false;
int x_offset = 0;
int y_offset = 0;
bool refresh_bitmap = false;

void OnTileSpriteReady(FileRequestResult*);
Expand Down
31 changes: 20 additions & 11 deletions src/spriteset_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,18 @@ void Spriteset_Map::CreateSprite(Game_Character* character, bool create_x_clone,

add_sprite(std::make_unique<Sprite_Character>(character));
if (create_x_clone) {
add_sprite(std::make_unique<Sprite_Character>(character, CloneType::XClone));
add_sprite(std::make_unique<Sprite_Character>(character, -map_tiles_x, 0));
add_sprite(std::make_unique<Sprite_Character>(character, map_tiles_x, 0));
}
if (create_y_clone) {
add_sprite(std::make_unique<Sprite_Character>(character, CloneType::YClone));
add_sprite(std::make_unique<Sprite_Character>(character, 0, -map_tiles_y));
add_sprite(std::make_unique<Sprite_Character>(character, 0, map_tiles_y));
}
if (create_x_clone && create_y_clone) {
add_sprite(std::make_unique<Sprite_Character>(character,
(CloneType)(CloneType::XClone | CloneType::YClone)));
add_sprite(std::make_unique<Sprite_Character>(character, map_tiles_x, map_tiles_y));
add_sprite(std::make_unique<Sprite_Character>(character, -map_tiles_x, map_tiles_y));
add_sprite(std::make_unique<Sprite_Character>(character, map_tiles_x, -map_tiles_y));
add_sprite(std::make_unique<Sprite_Character>(character, -map_tiles_x, -map_tiles_y));
}
}

Expand All @@ -240,14 +244,18 @@ void Spriteset_Map::CreateAirshipShadowSprite(bool create_x_clone, bool create_y

add_sprite(std::make_unique<Sprite_AirshipShadow>());
if (create_x_clone) {
add_sprite(std::make_unique<Sprite_AirshipShadow>(CloneType::XClone));
add_sprite(std::make_unique<Sprite_AirshipShadow>(-map_tiles_x, 0));
add_sprite(std::make_unique<Sprite_AirshipShadow>(map_tiles_x, 0));
}
if (create_y_clone) {
add_sprite(std::make_unique<Sprite_AirshipShadow>(CloneType::YClone));
add_sprite(std::make_unique<Sprite_AirshipShadow>(0, -map_tiles_y));
add_sprite(std::make_unique<Sprite_AirshipShadow>(0, map_tiles_y));
}
if (create_x_clone && create_y_clone) {
add_sprite(std::make_unique<Sprite_AirshipShadow>(
(CloneType)(CloneType::XClone | CloneType::YClone)));
add_sprite(std::make_unique<Sprite_AirshipShadow>(map_tiles_x, map_tiles_y));
add_sprite(std::make_unique<Sprite_AirshipShadow>(-map_tiles_x, map_tiles_y));
add_sprite(std::make_unique<Sprite_AirshipShadow>(map_tiles_x, -map_tiles_y));
add_sprite(std::make_unique<Sprite_AirshipShadow>(-map_tiles_x, -map_tiles_y));
}
}

Expand Down Expand Up @@ -277,16 +285,17 @@ void Spriteset_Map::OnPanoramaSpriteReady(FileRequestResult* result) {
void Spriteset_Map::CalculateMapRenderOffset() {
map_render_ox = 0;
map_render_oy = 0;
map_tiles_x = 0;
map_tiles_y = 0;

// Smallest possible map. Smaller maps are hacked
map_tiles_x = std::max<int>(Game_Map::GetTilesX(), 20) * TILE_SIZE;
map_tiles_y = std::max<int>(Game_Map::GetTilesY(), 15) * TILE_SIZE;

panorama->SetRenderOx(0);
panorama->SetRenderOy(0);
screen->SetViewport(Rect());

if (Player::game_config.fake_resolution.Get()) {
// Resolution hack for tiles and sprites
// Smallest possible map. Smaller maps are hacked
map_tiles_x = std::max<int>(Game_Map::GetTilesX(), 20) * TILE_SIZE;
map_tiles_y = std::max<int>(Game_Map::GetTilesY(), 15) * TILE_SIZE;

Expand Down

0 comments on commit c338202

Please sign in to comment.