Files
Mauro Junior 1e3c78a470 Rewrite Map: Lock tiles against going OOB.
That solves some errors related out of bounds access.
Sometimes it happens when drawing a tile, others when checking passability.

It may differ from how maniacs deals with this (I suspect maniacs have an extra blank tile, with locked passability, just for OOB tiles).
2025-01-15 15:19:57 -03:00

338 lines
10 KiB
C++

/*
* This file is part of EasyRPG Player.
*
* EasyRPG Player is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EasyRPG Player is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
*/
// Headers
#include "spriteset_map.h"
#include "cache.h"
#include "game_dynrpg.h"
#include "game_map.h"
#include "main_data.h"
#include "sprite_airshipshadow.h"
#include "sprite_character.h"
#include "game_character.h"
#include "game_player.h"
#include "game_vehicle.h"
#include "game_screen.h"
#include "bitmap.h"
#include "player.h"
#include "drawable_list.h"
#include "map_data.h"
Spriteset_Map::Spriteset_Map() {
panorama = std::make_unique<Plane>();
panorama->SetZ(Priority_Background);
timer1 = std::make_unique<Sprite_Timer>(0);
timer2 = std::make_unique<Sprite_Timer>(1);
screen = std::make_unique<Screen>();
if (Player::IsRPG2k3()) {
frame = std::make_unique<Frame>();
}
ParallaxUpdated();
Refresh();
Update();
}
void Spriteset_Map::Refresh() {
CalculateMapRenderOffset();
tilemap = std::make_unique<Tilemap>();
tilemap->SetWidth(Game_Map::GetTilesX());
tilemap->SetHeight(Game_Map::GetTilesY());
tilemap->SetRenderOx(map_render_ox);
tilemap->SetRenderOy(map_render_oy);
airship_shadows.clear();
character_sprites.clear();
ChipsetUpdated();
need_x_clone = Game_Map::LoopHorizontal();
need_y_clone = Game_Map::LoopVertical();
for (Game_Event& ev : Game_Map::GetEvents()) {
CreateSprite(&ev, need_x_clone, need_y_clone);
}
CreateAirshipShadowSprite(need_x_clone, need_y_clone);
CreateSprite(Main_Data::game_player.get(), need_x_clone, need_y_clone);
for (bool& v: vehicle_loaded) {
v = false;
}
}
// Update
void Spriteset_Map::Update() {
Tone new_tone = Main_Data::game_screen->GetTone();
tilemap->SetOx(Game_Map::GetDisplayX() / (SCREEN_TILE_SIZE / TILE_SIZE));
tilemap->SetOy(Game_Map::GetDisplayY() / (SCREEN_TILE_SIZE / TILE_SIZE));
tilemap->SetTone(new_tone);
for (const auto& character_sprite : character_sprites) {
character_sprite->Update();
character_sprite->SetTone(new_tone);
}
panorama->SetOx(Game_Map::Parallax::GetX());
panorama->SetOy(Game_Map::Parallax::GetY());
panorama->SetTone(new_tone);
Game_Vehicle* vehicle;
int map_id = Game_Map::GetMapId();
for (int i = 1; i <= 3; ++i) {
vehicle = Game_Map::GetVehicle((Game_Vehicle::Type) i);
if (!vehicle_loaded[i - 1] && vehicle->GetMapId() == map_id) {
vehicle_loaded[i - 1] = true;
CreateSprite(vehicle, need_x_clone, need_y_clone);
}
}
for (auto& shadow : airship_shadows) {
shadow->SetTone(new_tone);
shadow->Update();
}
Main_Data::game_dynrpg->Update();
}
void Spriteset_Map::ChipsetUpdated() {
if (!Game_Map::GetChipsetName().empty()) {
FileRequestAsync* request = AsyncHandler::RequestFile("ChipSet", Game_Map::GetChipsetName());
tilemap_request_id = request->Bind(&Spriteset_Map::OnTilemapSpriteReady, this);
request->SetImportantFile(true);
request->SetGraphicFile(true);
request->Start();
}
else {
OnTilemapSpriteReady(NULL);
}
for (auto& sprite: character_sprites) {
sprite->ChipsetUpdated();
}
}
void Spriteset_Map::ParallaxUpdated() {
std::string name = Game_Map::Parallax::GetName();
if (name != panorama_name) {
panorama_name = name;
if (!name.empty()) {
FileRequestAsync* request = AsyncHandler::RequestFile("Panorama", panorama_name);
request->SetGraphicFile(true);
request->SetImportantFile(true);
panorama_request_id = request->Bind(&Spriteset_Map::OnPanoramaSpriteReady, this);
request->Start();
}
}
if (name.empty()) {
panorama->SetBitmap(BitmapRef());
Game_Map::Parallax::Initialize(0, 0);
}
}
void Spriteset_Map::SystemGraphicUpdated() {
for (auto& shadow : airship_shadows) {
shadow->RecreateShadow();
}
}
void Spriteset_Map::SubstituteDown(int old_id, int new_id) {
int num_subst = Game_Map::SubstituteDown(old_id, new_id);
if (num_subst) {
tilemap->OnSubstituteDown();
}
}
void Spriteset_Map::SubstituteUp(int old_id, int new_id) {
int num_subst = Game_Map::SubstituteUp(old_id, new_id);
if (num_subst) {
tilemap->OnSubstituteUp();
}
}
void Spriteset_Map::ReplaceDownAt(int x, int y, int tile_index, bool disable_autotile) {
if (tile_index >= BLOCK_F_INDEX) tile_index = BLOCK_F_INDEX - 1;
auto tile_id = IndexToChipId(tile_index);
tilemap->SetMapTileDataDownAt(x, y, tile_id, disable_autotile);
}
void Spriteset_Map::ReplaceUpAt(int x, int y, int tile_index) {
tile_index += BLOCK_F_INDEX;
if (tile_index >= NUM_UPPER_TILES + BLOCK_F_INDEX) tile_index = BLOCK_F_INDEX;
auto tile_id = IndexToChipId(tile_index);
tilemap->SetMapTileDataUpAt(x, y, tile_id);
}
bool Spriteset_Map::RequireClear(DrawableList& drawable_list) {
if (drawable_list.empty()) {
return true;
}
// When using a custom resolution that is not divisible by 16, clear to avoid
// artifacts at the borders
if (Player::screen_width % TILE_SIZE != 0 || Player::screen_height % TILE_SIZE != 0) {
return true;
}
// Speed optimisation:
// When there is nothing below the tilemap it can be drawn opaque (faster)
tilemap->SetFastBlitDown(false);
if (!panorama_name.empty()) {
// Map has a panorama -> No opaque tilemap blit possible
// but the panorama is drawn opaque -> clearing the screen is not needed
return false;
}
// The list is about to be drawn, so we can just sort it now if needed.
if (drawable_list.IsDirty()) {
drawable_list.Sort();
}
// Only if there is nothing below the tileset, can we do fast blitting.
if ((*drawable_list.begin())->GetZ() >= Priority_TilesetBelow) {
tilemap->SetFastBlitDown(true);
}
return true;
}
void Spriteset_Map::CreateSprite(Game_Character* character, bool create_x_clone, bool create_y_clone) {
auto add_sprite = [&](auto&& chara) {
chara->SetRenderOx(map_render_ox);
chara->SetRenderOy(map_render_oy);
character_sprites.push_back(std::forward<decltype(chara)>(chara));
};
add_sprite(std::make_unique<Sprite_Character>(character));
if (create_x_clone) {
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, 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, 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));
}
}
void Spriteset_Map::CreateAirshipShadowSprite(bool create_x_clone, bool create_y_clone) {
auto add_sprite = [&](auto&& chara) {
chara->SetRenderOx(map_render_ox);
chara->SetRenderOy(map_render_oy);
airship_shadows.push_back(std::forward<decltype(chara)>(chara));
};
add_sprite(std::make_unique<Sprite_AirshipShadow>());
if (create_x_clone) {
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>(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>(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));
}
}
void Spriteset_Map::OnTilemapSpriteReady(FileRequestResult*) {
if (!Game_Map::GetChipsetName().empty()) {
tilemap->SetChipset(Cache::Chipset(Game_Map::GetChipsetName()));
}
else {
tilemap->SetChipset(Bitmap::Create(480, 256));
}
tilemap->SetMapDataDown(Game_Map::GetMapDataDown());
tilemap->SetMapDataUp(Game_Map::GetMapDataUp());
tilemap->SetPassableDown(Game_Map::GetPassagesDown());
tilemap->SetPassableUp(Game_Map::GetPassagesUp());
tilemap->SetAnimationType(Game_Map::GetAnimationType());
tilemap->SetAnimationSpeed(Game_Map::GetAnimationSpeed());
}
void Spriteset_Map::OnPanoramaSpriteReady(FileRequestResult* result) {
BitmapRef panorama_bmp = Cache::Panorama(result->file);
panorama->SetBitmap(panorama_bmp);
Game_Map::Parallax::Initialize(panorama_bmp->GetWidth(), panorama_bmp->GetHeight());
CalculatePanoramaRenderOffset();
}
void Spriteset_Map::CalculateMapRenderOffset() {
map_render_ox = 0;
map_render_oy = 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
map_tiles_x = std::max<int>(Game_Map::GetTilesX(), 20) * TILE_SIZE;
map_tiles_y = std::max<int>(Game_Map::GetTilesY(), 15) * TILE_SIZE;
if (map_tiles_x < Player::screen_width) {
map_render_ox = (Player::screen_width - map_tiles_x) / 2;
}
if (map_tiles_y < Player::screen_height) {
map_render_oy = (Player::screen_height - map_tiles_y) / 2;
}
CalculatePanoramaRenderOffset();
screen->SetViewport({map_render_ox, map_render_oy, map_tiles_x, map_tiles_y});
}
}
void Spriteset_Map::CalculatePanoramaRenderOffset() {
// Resolution hack for Panorama
if (Player::game_config.fake_resolution.Get()) {
if (Game_Map::Parallax::FakeXPosition()) {
panorama->SetRenderOx((Player::screen_width - SCREEN_TARGET_WIDTH) / 2);
}
if (Game_Map::Parallax::FakeYPosition()) {
panorama->SetRenderOy((Player::screen_height - SCREEN_TARGET_HEIGHT) / 2);
}
}
}