From a72286f2ef5430408bcdf9f0dea8368201d88566 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Mon, 15 Apr 2024 14:58:04 +0200 Subject: [PATCH] Redo the X/Y shifting (Sprite clones) on looping maps. 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 --- src/battle_animation.cpp | 2 +- src/game_character.cpp | 18 +++++------------- src/game_character.h | 11 +++++------ src/game_event.cpp | 4 ++-- src/game_event.h | 2 +- src/game_player.cpp | 4 ++-- src/game_player.h | 2 +- src/game_vehicle.cpp | 4 ++-- src/game_vehicle.h | 2 +- src/sprite_airshipshadow.cpp | 12 +++++------- src/sprite_airshipshadow.h | 6 +++--- src/sprite_character.cpp | 18 ++++++++---------- src/sprite_character.h | 9 +++++---- src/spriteset_map.cpp | 31 ++++++++++++++++++++----------- 14 files changed, 61 insertions(+), 64 deletions(-) diff --git a/src/battle_animation.cpp b/src/battle_animation.cpp index 6c552bbd1a8..ee0fe7b3887 100644 --- a/src/battle_animation.cpp +++ b/src/battle_animation.cpp @@ -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::instance.get())->spriteset->GetRenderOx(); y_off += static_cast(Scene::instance.get())->spriteset->GetRenderOy(); diff --git a/src/game_character.cpp b/src/game_character.cpp index 8c9150db361..0619addb883 100644 --- a/src/game_character.cpp +++ b/src/game_character.cpp @@ -71,7 +71,7 @@ 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()) { @@ -79,14 +79,10 @@ int Game_Character::GetScreenX(bool apply_shift) const { } 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) { @@ -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()) { @@ -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(GetScreenY(apply_shift, false) + 0x8000); - Drawable::Z_t x = static_cast(GetScreenX(apply_shift) + 0x8000); + Drawable::Z_t y = static_cast(GetScreenY(false) + y_offset + 0x8000); + Drawable::Z_t x = static_cast(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 diff --git a/src/game_character.h b/src/game_character.h index 64dcf2e4d81..152b275bb34 100644 --- a/src/game_character.h +++ b/src/game_character.h @@ -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. diff --git a/src/game_event.cpp b/src/game_event.cpp index 83d0be0c4bd..858427119d5 100644 --- a/src/game_event.cpp +++ b/src/game_event.cpp @@ -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 { diff --git a/src/game_event.h b/src/game_event.h index 7b763adf590..b1dede55baf 100644 --- a/src/game_event.h +++ b/src/game_event.h @@ -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; diff --git a/src/game_player.cpp b/src/game_player.cpp index 71c8694ac16..c20a02a9ef6 100644 --- a/src/game_player.cpp +++ b/src/game_player.cpp @@ -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) { diff --git a/src/game_player.h b/src/game_player.h index 79b24d82006..9cd826b0742 100644 --- a/src/game_player.h +++ b/src/game_player.h @@ -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; diff --git a/src/game_vehicle.cpp b/src/game_vehicle.cpp index 176e086fe1d..50688c8ebbc 100644 --- a/src/game_vehicle.cpp +++ b/src/game_vehicle.cpp @@ -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 { diff --git a/src/game_vehicle.h b/src/game_vehicle.h index e4072a7ead4..eb78c8a71b4 100644 --- a/src/game_vehicle.h +++ b/src/game_vehicle.h @@ -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(); diff --git a/src/sprite_airshipshadow.cpp b/src/sprite_airshipshadow.cpp index 15a0da95bf2..ca57f231d19 100644 --- a/src/sprite_airshipshadow.cpp +++ b/src/sprite_airshipshadow.cpp @@ -25,16 +25,14 @@ #include "sprite_airshipshadow.h" #include -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 @@ -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); } diff --git a/src/sprite_airshipshadow.h b/src/sprite_airshipshadow.h index adf689c9a89..2d5b89ea351 100644 --- a/src/sprite_airshipshadow.h +++ b/src/sprite_airshipshadow.h @@ -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 diff --git a/src/sprite_character.cpp b/src/sprite_character.cpp index bc702d4f44d..86218e04a91 100644 --- a/src/sprite_character.cpp +++ b/src/sprite_character.cpp @@ -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(); } @@ -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); @@ -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; diff --git a/src/sprite_character.h b/src/sprite_character.h index 16920c48da9..5c820c0a69a 100644 --- a/src/sprite_character.h +++ b/src/sprite_character.h @@ -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. @@ -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*); diff --git a/src/spriteset_map.cpp b/src/spriteset_map.cpp index 4d19c248315..0d16a5277ec 100644 --- a/src/spriteset_map.cpp +++ b/src/spriteset_map.cpp @@ -218,14 +218,18 @@ void Spriteset_Map::CreateSprite(Game_Character* character, bool create_x_clone, add_sprite(std::make_unique(character)); if (create_x_clone) { - add_sprite(std::make_unique(character, CloneType::XClone)); + add_sprite(std::make_unique(character, -map_tiles_x, 0)); + add_sprite(std::make_unique(character, map_tiles_x, 0)); } if (create_y_clone) { - add_sprite(std::make_unique(character, CloneType::YClone)); + add_sprite(std::make_unique(character, 0, -map_tiles_y)); + add_sprite(std::make_unique(character, 0, map_tiles_y)); } if (create_x_clone && create_y_clone) { - add_sprite(std::make_unique(character, - (CloneType)(CloneType::XClone | CloneType::YClone))); + add_sprite(std::make_unique(character, map_tiles_x, map_tiles_y)); + add_sprite(std::make_unique(character, -map_tiles_x, map_tiles_y)); + add_sprite(std::make_unique(character, map_tiles_x, -map_tiles_y)); + add_sprite(std::make_unique(character, -map_tiles_x, -map_tiles_y)); } } @@ -240,14 +244,18 @@ void Spriteset_Map::CreateAirshipShadowSprite(bool create_x_clone, bool create_y add_sprite(std::make_unique()); if (create_x_clone) { - add_sprite(std::make_unique(CloneType::XClone)); + add_sprite(std::make_unique(-map_tiles_x, 0)); + add_sprite(std::make_unique(map_tiles_x, 0)); } if (create_y_clone) { - add_sprite(std::make_unique(CloneType::YClone)); + add_sprite(std::make_unique(0, -map_tiles_y)); + add_sprite(std::make_unique(0, map_tiles_y)); } if (create_x_clone && create_y_clone) { - add_sprite(std::make_unique( - (CloneType)(CloneType::XClone | CloneType::YClone))); + add_sprite(std::make_unique(map_tiles_x, map_tiles_y)); + add_sprite(std::make_unique(-map_tiles_x, map_tiles_y)); + add_sprite(std::make_unique(map_tiles_x, -map_tiles_y)); + add_sprite(std::make_unique(-map_tiles_x, -map_tiles_y)); } } @@ -277,8 +285,10 @@ 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(Game_Map::GetTilesX(), 20) * TILE_SIZE; + map_tiles_y = std::max(Game_Map::GetTilesY(), 15) * TILE_SIZE; panorama->SetRenderOx(0); panorama->SetRenderOy(0); @@ -286,7 +296,6 @@ void Spriteset_Map::CalculateMapRenderOffset() { 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(Game_Map::GetTilesX(), 20) * TILE_SIZE; map_tiles_y = std::max(Game_Map::GetTilesY(), 15) * TILE_SIZE;