diff --git a/src/audio_generic.cpp b/src/audio_generic.cpp index 69148bc290..8724e6cc0f 100644 --- a/src/audio_generic.cpp +++ b/src/audio_generic.cpp @@ -67,13 +67,17 @@ void GenericAudio::BGM_Play(const std::string& file, int volume, int pitch, int void GenericAudio::BGM_Pause() { for (unsigned i = 0; i < nr_of_bgm_channels; i++) { - BGM_Channels[i].paused = true; + if (BGM_Channels[i].decoder) { + BGM_Channels[i].paused = true; + } } } void GenericAudio::BGM_Resume() { for (unsigned i = 0; i < nr_of_bgm_channels; i++) { - BGM_Channels[i].paused = false; + if (BGM_Channels[i].decoder) { + BGM_Channels[i].paused = false; + } } } @@ -195,6 +199,7 @@ bool GenericAudio::PlayOnChannel(BgmChannel& chan, const std::string& file, int return true; } else { Output::Warning("Couldn't play BGM %s. Format not supported", FileFinder::GetPathInsideGamePath(file).c_str()); + chan.decoder.reset(); fclose(filehandle); } diff --git a/src/background.cpp b/src/background.cpp index c3dd94ffc4..a588fe9123 100644 --- a/src/background.cpp +++ b/src/background.cpp @@ -25,6 +25,8 @@ #include "background.h" #include "bitmap.h" #include "main_data.h" +#include "reader_util.h" +#include "output.h" Background::Background(const std::string& name) : visible(true), @@ -47,33 +49,38 @@ Background::Background(int terrain_id) : Graphics::RegisterDrawable(this); - const RPG::Terrain& terrain = Data::terrains[terrain_id - 1]; + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, terrain_id); + + if (!terrain) { + Output::Warning("Background: Invalid terrain ID %d", terrain_id); + return; + } // Either background or frame - if (terrain.background_type == RPG::Terrain::BGAssociation_background && !terrain.background_name.empty()) { - FileRequestAsync* request = AsyncHandler::RequestFile("Backdrop", terrain.background_name); + if (terrain->background_type == RPG::Terrain::BGAssociation_background && !terrain->background_name.empty()) { + FileRequestAsync* request = AsyncHandler::RequestFile("Backdrop", terrain->background_name); request_id = request->Bind(&Background::OnBackgroundGraphicReady, this); request->Start(); return; } // Frame - if (!terrain.background_a_name.empty()) { - FileRequestAsync* request = AsyncHandler::RequestFile("Frame", terrain.background_a_name); + if (!terrain->background_a_name.empty()) { + FileRequestAsync* request = AsyncHandler::RequestFile("Frame", terrain->background_a_name); request_id = request->Bind(&Background::OnBackgroundGraphicReady, this); request->Start(); - bg_hscroll = terrain.background_a_scrollh ? terrain.background_a_scrollh_speed : 0; - bg_vscroll = terrain.background_a_scrollv ? terrain.background_a_scrollv_speed : 0; + bg_hscroll = terrain->background_a_scrollh ? terrain->background_a_scrollh_speed : 0; + bg_vscroll = terrain->background_a_scrollv ? terrain->background_a_scrollv_speed : 0; } - if (terrain.background_b && !terrain.background_b_name.empty()) { - FileRequestAsync* request = AsyncHandler::RequestFile("Frame", terrain.background_b_name); + if (terrain->background_b && !terrain->background_b_name.empty()) { + FileRequestAsync* request = AsyncHandler::RequestFile("Frame", terrain->background_b_name); request_id = request->Bind(&Background::OnForegroundFrameGraphicReady, this); request->Start(); - fg_hscroll = terrain.background_b_scrollh ? terrain.background_b_scrollh_speed : 0; - fg_vscroll = terrain.background_b_scrollv ? terrain.background_b_scrollv_speed : 0; + fg_hscroll = terrain->background_b_scrollh ? terrain->background_b_scrollh_speed : 0; + fg_vscroll = terrain->background_b_scrollv ? terrain->background_b_scrollv_speed : 0; } } diff --git a/src/game_actor.cpp b/src/game_actor.cpp index eb25551fb4..292c998f84 100644 --- a/src/game_actor.cpp +++ b/src/game_actor.cpp @@ -26,10 +26,14 @@ #include "main_data.h" #include "output.h" #include "player.h" +#include "reader_util.h" #include "rpg_skill.h" #include "util_macro.h" #include "utils.h" +constexpr int max_level_2k = 50; +constexpr int max_level_2k3 = 99; + static int max_hp_value() { return Player::IsRPG2k() ? 999 : 9999; } @@ -54,21 +58,26 @@ void Game_Actor::Setup() { } void Game_Actor::Init() { - const std::vector& skills = Data::actors[actor_id - 1].skills; - for (int i = 0; i < (int)skills.size(); i++) - if (skills[i].level <= GetLevel()) + const std::vector& skills = GetActor().skills; + for (int i = 0; i < (int)skills.size(); i++) { + if (skills[i].level <= GetLevel()) { LearnSkill(skills[i].skill_id); - SetHp(GetMaxHp()); - SetSp(GetMaxSp()); - SetExp(exp_list[GetLevel() - 1]); + } + } - RemoveInvalidEquipment(); + RemoveInvalidData(); + + if (GetLevel() > 0) { + SetHp(GetMaxHp()); + SetSp(GetMaxSp()); + SetExp(exp_list[GetLevel() - 1]); + } } void Game_Actor::Fixup() { GetData().Fixup(actor_id); - RemoveInvalidEquipment(); + RemoveInvalidData(); } int Game_Actor::GetId() const { @@ -76,23 +85,27 @@ int Game_Actor::GetId() const { } bool Game_Actor::UseItem(int item_id) { - const RPG::Item& item = Data::items[item_id - 1]; + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + if (!item) { + Output::Warning("UseItem: Can't use invalid item %d", item_id); + return false; + } - if (IsDead() && item.type != RPG::Item::Type_medicine) { + if (IsDead() && item->type != RPG::Item::Type_medicine) { return false; } - if (item.type == RPG::Item::Type_book) { - return LearnSkill(item.skill_id); + if (item->type == RPG::Item::Type_book) { + return LearnSkill(item->skill_id); } - if (item.type == RPG::Item::Type_material) { - SetBaseMaxHp(GetBaseMaxHp() + item.max_hp_points); - SetBaseMaxSp(GetBaseMaxSp() + item.max_sp_points); - SetBaseAtk(GetBaseAtk() + item.atk_points2); - SetBaseDef(GetBaseDef() + item.def_points2); - SetBaseAgi(GetBaseAgi() + item.agi_points2); - SetBaseSpi(GetBaseSpi() + item.spi_points2); + if (item->type == RPG::Item::Type_material) { + SetBaseMaxHp(GetBaseMaxHp() + item->max_hp_points); + SetBaseMaxSp(GetBaseMaxSp() + item->max_sp_points); + SetBaseAtk(GetBaseAtk() + item->atk_points2); + SetBaseDef(GetBaseDef() + item->def_points2); + SetBaseAgi(GetBaseAgi() + item->agi_points2); + SetBaseSpi(GetBaseSpi() + item->spi_points2); return true; } @@ -101,14 +114,18 @@ bool Game_Actor::UseItem(int item_id) { } bool Game_Actor::IsItemUsable(int item_id) const { - const RPG::Item& item = Data::items[item_id - 1]; + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + if (!item) { + Output::Warning("IsItemUsable: Invalid item ID %d", item_id); + return false; + } // If the actor ID is out of range this is an optimization in the ldb file // (all actors missing can equip the item) - if (item.actor_set.size() <= (unsigned)(actor_id - 1)) { + if (item->actor_set.size() <= (unsigned)(actor_id - 1)) { return true; } else { - return item.actor_set.at(actor_id - 1); + return item->actor_set.at(actor_id - 1); } } @@ -117,18 +134,18 @@ bool Game_Actor::IsSkillLearned(int skill_id) const { } bool Game_Actor::IsSkillUsable(int skill_id) const { - if (skill_id <= 0 || skill_id > (int)Data::skills.size()) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + if (!skill) { + Output::Warning("IsSkillUsable: Invalid skill ID %d", skill_id); return false; } - const RPG::Skill& skill = Data::skills[skill_id - 1]; - // Actor must have all attributes of the skill equipped as weapons const RPG::Item* item = GetEquipment(RPG::Item::Type_weapon); const RPG::Item* item2 = HasTwoWeapons() ? GetEquipment(RPG::Item::Type_weapon + 1) : nullptr; - for (size_t i = 0; i < skill.attribute_effects.size(); ++i) { - bool required = skill.attribute_effects[i] && Data::attributes[i].type == RPG::Attribute::Type_physical; + for (size_t i = 0; i < skill->attribute_effects.size(); ++i) { + bool required = skill->attribute_effects[i] && Data::attributes[i].type == RPG::Attribute::Type_physical; if (required) { if (item && i < item->attribute_set.size()) { if (!item->attribute_set[i]) { @@ -169,6 +186,12 @@ int Game_Actor::CalculateSkillCost(int skill_id) const { bool Game_Actor::LearnSkill(int skill_id) { if (skill_id > 0 && !IsSkillLearned(skill_id)) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + if (!skill) { + Output::Warning("Actor %d: Can't learn invalid skill %d", GetId(), skill_id); + return false; + } + GetData().skills.push_back((int16_t)skill_id); GetData().skills_size = GetData().skills.size(); std::sort(GetData().skills.begin(), GetData().skills.end()); @@ -200,13 +223,9 @@ void Game_Actor::SetFace(const std::string& file_name, int index) { const RPG::Item* Game_Actor::GetEquipment(int equip_type) const { if (equip_type <= 0 || equip_type > (int)GetData().equipped.size()) return nullptr; - int item_id = GetData().equipped[equip_type - 1]; - - if (item_id <= 0 || item_id >(int)Data::items.size()) { - return nullptr; - } - return &Data::items[item_id - 1]; + int item_id = GetData().equipped[equip_type - 1]; + return ReaderUtil::GetElement(Data::items, item_id); } int Game_Actor::SetEquipment(int equip_type, int new_item_id) { @@ -214,8 +233,13 @@ int Game_Actor::SetEquipment(int equip_type, int new_item_id) { return -1; int old_item_id = GetData().equipped[equip_type - 1]; - if (old_item_id > (int)Data::items.size()) - old_item_id = 0; + + const RPG::Item* new_item = ReaderUtil::GetElement(Data::items, new_item_id); + if (new_item_id != 0 && !new_item) { + Output::Warning("SetEquipment: Can't equip item with invalid ID %d", new_item_id); + GetData().equipped[equip_type - 1] = 0; + return old_item_id; + } GetData().equipped[equip_type - 1] = (short)new_item_id; return old_item_id; @@ -293,12 +317,17 @@ int Game_Actor::GetSp() const { } int Game_Actor::GetBaseMaxHp(bool mod) const { - // Looks like RPG_RT only applies Class changes (class_id > 0 - 20kdc) - // when the class was changed by the ChangeClass event, otherwise it uses - // the normal actor attributes. - int n = GetData().class_id > 0 - ? Data::classes[GetData().class_id - 1].parameters.maxhp[GetData().level - 1] - : Data::actors[actor_id - 1].parameters.maxhp[GetData().level - 1]; + int n = 0; + // Special handling for games that use a level of 0 -> Return 0 Hp + // Same applies for other stats + if (GetLevel() > 0) { + // Looks like RPG_RT only applies Class changes (class_id > 0 - 20kdc) + // when the class was changed by the ChangeClass event, otherwise it uses + // the normal actor attributes. + n = GetData().class_id > 0 + ? *ReaderUtil::GetElement(GetClass()->parameters.maxhp, GetLevel()) + : *ReaderUtil::GetElement(GetActor().parameters.maxhp, GetLevel()); + } if (mod) n += GetData().hp_mod; @@ -311,9 +340,12 @@ int Game_Actor::GetBaseMaxHp() const { } int Game_Actor::GetBaseMaxSp(bool mod) const { - int n = GetData().class_id > 0 - ? Data::classes[GetData().class_id - 1].parameters.maxsp[GetData().level - 1] - : Data::actors[actor_id - 1].parameters.maxsp[GetData().level - 1]; + int n = 0; + if (GetLevel() > 0) { + n = GetData().class_id > 0 + ? *ReaderUtil::GetElement(GetClass()->parameters.maxsp, GetLevel()) + : *ReaderUtil::GetElement(GetActor().parameters.maxsp, GetLevel()); + } if (mod) n += GetData().sp_mod; @@ -326,9 +358,12 @@ int Game_Actor::GetBaseMaxSp() const { } int Game_Actor::GetBaseAtk(bool mod, bool equip) const { - int n = GetData().class_id > 0 - ? Data::classes[GetData().class_id - 1].parameters.attack[GetData().level - 1] - : Data::actors[actor_id - 1].parameters.attack[GetData().level - 1]; + int n = 0; + if (GetLevel() > 0) { + n = GetData().class_id > 0 + ? *ReaderUtil::GetElement(GetClass()->parameters.attack, GetLevel()) + : *ReaderUtil::GetElement(GetActor().parameters.attack, GetLevel()); + } if (mod) { n += GetData().attack_mod; @@ -337,7 +372,8 @@ int Game_Actor::GetBaseAtk(bool mod, bool equip) const { if (equip) { for (std::vector::const_iterator it = GetData().equipped.begin(); it != GetData().equipped.end(); ++it) { if (*it > 0 && *it <= (int)Data::items.size()) { - n += Data::items[*it - 1].atk_points1; + // Invalid equipment was removed + n += ReaderUtil::GetElement(Data::items, *it)->atk_points1; } } } @@ -350,9 +386,12 @@ int Game_Actor::GetBaseAtk() const { } int Game_Actor::GetBaseDef(bool mod, bool equip) const { - int n = GetData().class_id > 0 - ? Data::classes[GetData().class_id - 1].parameters.defense[GetData().level - 1] - : Data::actors[actor_id - 1].parameters.defense[GetData().level - 1]; + int n = 0; + if (GetLevel() > 0) { + n = GetData().class_id > 0 + ? *ReaderUtil::GetElement(GetClass()->parameters.defense, GetLevel()) + : *ReaderUtil::GetElement(GetActor().parameters.defense, GetLevel()); + } if (mod) { n += GetData().defense_mod; @@ -361,7 +400,8 @@ int Game_Actor::GetBaseDef(bool mod, bool equip) const { if (equip) { for (std::vector::const_iterator it = GetData().equipped.begin(); it != GetData().equipped.end(); ++it) { if (*it > 0 && *it <= (int)Data::items.size()) { - n += Data::items[*it - 1].def_points1; + // Invalid equipment was removed + n += ReaderUtil::GetElement(Data::items, *it)->def_points1; } } } @@ -374,9 +414,12 @@ int Game_Actor::GetBaseDef() const { } int Game_Actor::GetBaseSpi(bool mod, bool equip) const { - int n = GetData().class_id > 0 - ? Data::classes[GetData().class_id - 1].parameters.spirit[GetData().level - 1] - : Data::actors[actor_id - 1].parameters.spirit[GetData().level - 1]; + int n = 0; + if (GetLevel() > 0) { + n = GetData().class_id > 0 + ? *ReaderUtil::GetElement(GetClass()->parameters.spirit, GetLevel()) + : *ReaderUtil::GetElement(GetActor().parameters.spirit, GetLevel()); + } if (mod) { n += GetData().spirit_mod; @@ -385,7 +428,8 @@ int Game_Actor::GetBaseSpi(bool mod, bool equip) const { if (equip) { for (std::vector::const_iterator it = GetData().equipped.begin(); it != GetData().equipped.end(); ++it) { if (*it > 0 && *it <= (int)Data::items.size()) { - n += Data::items[*it - 1].spi_points1; + // Invalid equipment was removed + n += ReaderUtil::GetElement(Data::items, *it)->spi_points1; } } } @@ -398,9 +442,12 @@ int Game_Actor::GetBaseSpi() const { } int Game_Actor::GetBaseAgi(bool mod, bool equip) const { - int n = GetData().class_id > 0 - ? Data::classes[GetData().class_id - 1].parameters.agility[GetData().level - 1] - : Data::actors[actor_id - 1].parameters.agility[GetData().level - 1]; + int n = 0; + if (GetLevel() > 0) { + n = GetData().class_id > 0 + ? *ReaderUtil::GetElement(GetClass()->parameters.agility, GetLevel()) + : *ReaderUtil::GetElement(GetActor().parameters.agility, GetLevel()); + } if (mod) { n += GetData().agility_mod; @@ -409,7 +456,8 @@ int Game_Actor::GetBaseAgi(bool mod, bool equip) const { if (equip) { for (std::vector::const_iterator it = GetData().equipped.begin(); it != GetData().equipped.end(); ++it) { if (*it > 0 && *it <= (int)Data::items.size()) { - n += Data::items[*it - 1].agi_points1; + // Invalid equipment was removed + n += ReaderUtil::GetElement(Data::items, *it)->agi_points1; } } } @@ -421,17 +469,17 @@ int Game_Actor::GetBaseAgi() const { return GetBaseAgi(true, true); } -int Game_Actor::CalculateExp(int level) const -{ +int Game_Actor::CalculateExp(int level) const { + const RPG::Class* klass = ReaderUtil::GetElement(Data::classes, GetData().class_id); + double base, inflation, correction; - if (GetData().class_id > 0) { - const RPG::Class& klass = Data::classes[GetData().class_id - 1]; - base = klass.exp_base; - inflation = klass.exp_inflation; - correction = klass.exp_correction; + if (klass) { + base = klass->exp_base; + inflation = klass->exp_inflation; + correction = klass->exp_correction; } else { - const RPG::Actor& actor = Data::actors[actor_id - 1]; + const RPG::Actor& actor = *ReaderUtil::GetElement(Data::actors, actor_id); base = actor.exp_base; inflation = actor.exp_inflation; correction = actor.exp_correction; @@ -459,17 +507,16 @@ int Game_Actor::CalculateExp(int level) const } void Game_Actor::MakeExpList() { - int final_level = Data::actors[actor_id - 1].final_level; - exp_list.resize(final_level, 0);; - for (int i = 1; i < final_level; ++i) { + exp_list.resize((size_t)GetMaxLevel()); + for (int i = 1; i < (int)exp_list.size(); ++i) { exp_list[i] = CalculateExp(i); } } std::string Game_Actor::GetExpString() const { - std::stringstream ss; + std::stringstream ss; ss << GetExp(); - return ss.str(); + return ss.str(); } std::string Game_Actor::GetNextExpString() const { @@ -495,8 +542,10 @@ int Game_Actor::GetNextExp() const { } int Game_Actor::GetNextExp(int level) const { - if (level >= GetMaxLevel() || level <= 0) { + if (level >= GetMaxLevel() || level <= -1) { return -1; + } else if (level == 0) { + return 0; } else { return exp_list[level]; } @@ -505,21 +554,33 @@ int Game_Actor::GetNextExp(int level) const { int Game_Actor::GetStateProbability(int state_id) const { int rate = 2; // C - default - if (state_id <= (int)Data::actors[actor_id - 1].state_ranks.size()) { - rate = Data::actors[actor_id - 1].state_ranks[state_id - 1]; + const uint8_t* r = ReaderUtil::GetElement(GetActor().state_ranks, state_id); + if (r) { + rate = *r; } + // GetStateRate verifies the state_id return GetStateRate(state_id, rate); } int Game_Actor::GetAttributeModifier(int attribute_id) const { int rate = 2; // C - default - if (attribute_id <= (int)Data::actors[actor_id - 1].attribute_ranks.size()) { - rate = Data::actors[actor_id - 1].attribute_ranks[attribute_id - 1]; + const uint8_t* r = ReaderUtil::GetElement(GetActor().attribute_ranks, attribute_id); + if (r) { + rate = *r; } - rate += attribute_shift[attribute_id - 1]; + // GetAttributeRate will verify this but actors already need a check earlier + // because of attribute_shift + const int* shift = ReaderUtil::GetElement(attribute_shift, attribute_id); + + if (!shift) { + Output::Warning("GetAttributeModifier: Invalid attribute ID %d", attribute_id); + return 0; + } + + rate += *shift; if (rate < 0) { rate = 0; } else if (rate > 4) { @@ -583,7 +644,7 @@ int Game_Actor::GetLevel() const { } int Game_Actor::GetMaxLevel() const { - return Data::actors[actor_id - 1].final_level; + return std::max(1, std::min(GetActor().final_level, Player::IsRPG2k() ? max_level_2k : max_level_2k3)); } int Game_Actor::GetExp() const { @@ -616,7 +677,7 @@ void Game_Actor::ChangeExp(int exp, bool level_up_message) { SetExp(new_exp); - if (new_level != GetData().level) { + if (new_level != GetLevel()) { ChangeLevel(new_level, level_up_message); } } @@ -657,7 +718,13 @@ std::string Game_Actor::GetLevelUpMessage(int new_level) const { std::string Game_Actor::GetLearningMessage(const RPG::Learning& learn) const { std::stringstream ss; - std::string& skill_name = Data::skills[learn.skill_id - 1].name; + + std::string skill_name = "??? BAD SKILL ???"; + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, learn.skill_id); + if (skill) { + skill_name = skill->name; + } + if (Player::IsRPG2kE()) { return Utils::ReplacePlaceholders( Data::terms.skill_learned, @@ -675,9 +742,9 @@ std::string Game_Actor::GetLearningMessage(const RPG::Learning& learn) const { void Game_Actor::ChangeLevel(int new_level, bool level_up_message) { const std::vector* skills; if (GetData().class_id > 0) { - skills = &Data::classes[GetData().class_id - 1].skills; + skills = &GetClass()->skills; } else { - skills = &Data::actors[actor_id - 1].skills; + skills = &GetActor().skills; } bool level_up = false; @@ -726,8 +793,14 @@ void Game_Actor::ChangeLevel(int new_level, bool level_up_message) { } bool Game_Actor::IsEquippable(int item_id) const { + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + if (!item) { + Output::Warning("IsEquippable: Invalid item ID %d", item_id); + return false; + } + if (HasTwoWeapons() && - Data::items[item_id - 1].type == RPG::Item::Type_shield) { + item->type == RPG::Item::Type_shield) { return false; } @@ -760,10 +833,14 @@ const std::vector& Game_Actor::GetSkills() const { return GetData().skills; } -const RPG::Skill& Game_Actor::GetRandomSkill() const { +const RPG::Skill* Game_Actor::GetRandomSkill() const { const std::vector& skills = GetSkills(); + if (skills.empty()) { + return nullptr; + } - return Data::skills[skills[Utils::GetRandomNumber(0, skills.size() - 1)] - 1]; + // Skills are guaranteed to be valid + return ReaderUtil::GetElement(Data::skills, skills[Utils::GetRandomNumber(0, skills.size() - 1)]); } bool Game_Actor::HasTwoWeapons() const { @@ -777,13 +854,19 @@ bool Game_Actor::GetAutoBattle() const { int Game_Actor::GetBattleX() const { float position = 0.0; - if (Data::actors[actor_id - 1].battle_x == 0 || + if (GetActor().battle_x == 0 || Data::battlecommands.placement == RPG::BattleCommands::Placement_automatic) { int party_pos = Main_Data::game_party->GetActorPositionInParty(actor_id); int party_size = Main_Data::game_party->GetBattlerCount(); float left = GetBattleRow() == 1 ? 25.0 : 50.0; - float right = left + Data::terrains[Game_Battle::GetTerrainId() - 1].grid_c / 1103; + float right = left; + + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, Game_Battle::GetTerrainId()); + if (terrain) { + // No warning, already reported on battle start + right = left + terrain->grid_c / 1103; + } switch (party_size) { case 1: @@ -842,7 +925,7 @@ int Game_Actor::GetBattleX() const { else { //Output::Debug("%d %d %d %d", Data::terrains[0].grid_a, Data::terrains[0].grid_b, Data::terrains[0].grid_c, Data::terrains[0].grid_location); - position = (Data::actors[actor_id - 1].battle_x*SCREEN_TARGET_WIDTH / 320); + position = GetActor().battle_x * SCREEN_TARGET_WIDTH / 320; } return position; @@ -851,13 +934,19 @@ int Game_Actor::GetBattleX() const { int Game_Actor::GetBattleY() const { float position = 0.0; - if (Data::actors[actor_id - 1].battle_y == 0 || + if (GetActor().battle_y == 0 || Data::battlecommands.placement == RPG::BattleCommands::Placement_automatic) { int party_pos = Main_Data::game_party->GetActorPositionInParty(actor_id); int party_size = Main_Data::game_party->GetBattlerCount(); - float top = Data::terrains[Game_Battle::GetTerrainId() - 1].grid_a; - float bottom = top + Data::terrains[Game_Battle::GetTerrainId() - 1].grid_b / 13; + float top = 0.0f; + float bottom = 0.0f; + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, Game_Battle::GetTerrainId()); + if (terrain) { + // No warning, already reported on battle start + top = terrain->grid_a; + bottom = top + terrain->grid_b / 13; + } switch (party_size) { case 1: @@ -890,10 +979,10 @@ int Game_Actor::GetBattleY() const { position = top; break; case 1: - position = top + ((bottom - top) * 1.0/3); + position = top + ((bottom - top) * 1.0 / 3); break; case 2: - position = top + ((bottom - top) * 2.0/3); + position = top + ((bottom - top) * 2.0 / 3); break; case 3: position = bottom; @@ -904,14 +993,14 @@ int Game_Actor::GetBattleY() const { position -= 24; } else { - position = (Data::actors[actor_id - 1].battle_y*SCREEN_TARGET_HEIGHT / 240); + position = GetActor().battle_y * SCREEN_TARGET_HEIGHT / 240; } return (int)position; } const std::string& Game_Actor::GetSkillName() const { - return Data::actors[actor_id - 1].skill_name; + return GetActor().skill_name; } void Game_Actor::SetName(const std::string &new_name) { @@ -941,6 +1030,12 @@ void Game_Actor::ChangeBattleCommands(bool add, int id) { // The battle commands array always has a size of 7 padded with -1. The last element before the padding is 0 which // stands for the Row command if (add) { + const RPG::BattleCommand* cmd = ReaderUtil::GetElement(Data::battlecommands.commands, id); + if (!cmd) { + Output::Warning("ChangeBattleCommands: Can't add invalid battle command %d", id); + return; + } + if (std::find(cmds.begin(), cmds.end(), id) == cmds.end()) { std::vector new_cmds; std::copy_if(cmds.begin(), cmds.end(), @@ -975,9 +1070,7 @@ const std::vector Game_Actor::GetBattleCommands() con obc = Data::actors[actor_id - 1].battle_commands; } - for (size_t i = 0; i < obc.size(); ++i) { - int command_index = obc[i]; - + for (int command_index : obc) { if (command_index == 0) { // Row command -> not impl continue; @@ -988,7 +1081,13 @@ const std::vector Game_Actor::GetBattleCommands() con continue; } - commands.push_back(&Data::battlecommands.commands[command_index - 1]); + const RPG::BattleCommand* cmd = ReaderUtil::GetElement(Data::battlecommands.commands, command_index); + if (!cmd) { + Output::Warning("GetBattleCommands: Invalid battle command ID %d", command_index); + continue; + } + + commands.push_back(cmd); } return commands; @@ -999,17 +1098,22 @@ const RPG::Class* Game_Actor::GetClass() const { if (id < 0) { // This means class ID hasn't been changed yet. - id = Data::actors[actor_id - 1].class_id; + id = GetActor().class_id; } - if (id <= 0) { - // No class set. - return nullptr; - } - return &Data::classes[id - 1]; + return ReaderUtil::GetElement(Data::classes, id); } void Game_Actor::SetClass(int _class_id) { + if (_class_id != 0) { + const RPG::Class* cls = ReaderUtil::GetElement(Data::classes, _class_id); + + if (!cls) { + Output::Warning("Actor %d: Can't change to invalid class %d", GetId(), _class_id); + return; + } + } + GetData().class_id = _class_id; GetData().changed_battle_commands = true; // Any change counts as a battle commands change. @@ -1026,16 +1130,14 @@ void Game_Actor::SetClass(int _class_id) { GetData().battle_commands = GetClass()->battle_commands; } else { - const RPG::Actor& actor = Data::actors[actor_id - 1]; - - GetData().super_guard = actor.super_guard; - GetData().lock_equipment = actor.lock_equipment; - GetData().two_weapon = actor.two_weapon; - GetData().auto_battle = actor.auto_battle; + GetData().super_guard = GetActor().super_guard; + GetData().lock_equipment = GetActor().lock_equipment; + GetData().two_weapon = GetActor().two_weapon; + GetData().auto_battle = GetActor().auto_battle; GetData().battler_animation = 0; - GetData().battle_commands = actor.battle_commands; + GetData().battle_commands = GetActor().battle_commands; } MakeExpList(); @@ -1137,7 +1239,13 @@ int Game_Actor::GetBattleAnimationId() const { if ((GetData().class_id > 0) && GetClass()) { anim = GetClass()->battler_animation; } else { - anim = Data::battleranimations[Data::actors[actor_id - 1].battler_animation - 1].ID; + const RPG::BattlerAnimation* anima = ReaderUtil::GetElement(Data::battleranimations, GetActor().battler_animation); + if (!anima) { + Output::Warning("Actor %d: Invalid battle animation ID %d", GetId(), GetActor().battler_animation); + return 0; + } + + anim = anima->ID; } } else { anim = GetData().battler_animation; @@ -1156,18 +1264,35 @@ int Game_Actor::GetHitChance() const { } float Game_Actor::GetCriticalHitChance() const { - return Data::actors[actor_id - 1].critical_hit ? (1.0f / Data::actors[actor_id - 1].critical_hit_chance) : 0.0f; + return GetActor().critical_hit ? (1.0f / GetActor().critical_hit_chance) : 0.0f; } Game_Battler::BattlerType Game_Actor::GetType() const { return Game_Battler::Type_Ally; } -RPG::SaveActor & Game_Actor::GetData() const { - return Main_Data::game_data.actors[actor_id - 1]; +const RPG::Actor& Game_Actor::GetActor() const { + // Always valid + return *ReaderUtil::GetElement(Data::actors, actor_id); +} + +RPG::SaveActor& Game_Actor::GetData() const { + // Always valid because the array is resized to match actor size + return *ReaderUtil::GetElement(Main_Data::game_data.actors, actor_id); } -void Game_Actor::RemoveInvalidEquipment() { +void Game_Actor::RemoveInvalidData() { + /* + The following actor data is cleaned up: + - Invalid equipment is removed + - An invalid class is removed + - Invalid states are removed + - Level is between 0 and 99, and does not exceed MaxLevel + + For "external data" (not from LCF Actor or LSD SaveActor) the data is + verified in the corresponding functions. + */ + // Filter out invalid equipment int eq_types[] = { RPG::Item::Type_weapon, HasTwoWeapons() ? RPG::Item::Type_weapon : RPG::Item::Type_shield, @@ -1184,5 +1309,38 @@ void Game_Actor::RemoveInvalidEquipment() { SetEquipment(i, 0); } } -} + // Remove invalid class + if (GetData().class_id > 0) { + const RPG::Class* cls = ReaderUtil::GetElement(Data::classes, GetData().class_id); + if (!cls) { + Output::Warning("Actor %d: Removing invalid class %d", GetId(), GetData().class_id); + SetClass(0); + } + } + + // Remove invalid skills + for (int16_t skill_id : GetSkills()) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + if (!skill) { + Output::Warning("Actor %d: Removing invalid skill %d", GetId(), skill_id); + UnlearnSkill(skill_id); + } + } + + // Remove invalid states + if (GetStates().size() > Data::states.size()) { + Output::Warning("Actor %d: State array contains invalid states (%d > %d)", GetId(), GetStates().size(), Data::states.size()); + GetStates().resize(Data::states.size()); + } + + // Remove invalid levels + // Special handling for the game COLORS: Lost Memories which uses level 0 + // through database editing. Hopefully no game uses negative levels. + if (GetLevel() == 0) { + Output::Debug("Actor %d: Special handling for level 0", GetId()); + } else if (GetLevel() < 0 || GetLevel() > GetMaxLevel()) { + Output::Warning("Actor %d: Invalid level %d", GetId(), GetLevel()); + SetLevel(1); + } +} diff --git a/src/game_actor.h b/src/game_actor.h index de96844fc4..5900ce7685 100644 --- a/src/game_actor.h +++ b/src/game_actor.h @@ -27,6 +27,7 @@ #include "game_battler.h" namespace RPG { + class Actor; class Skill; class BattleCommand; class Item; @@ -316,7 +317,7 @@ class Game_Actor : public Game_Battler { /** * Sets exp of actor. * The value is adjusted to the boundary 0 up 999999. - * Other actor attributes are not altered. Use ChangeExp to do a proper + * Other actor attributes are not altered. Use ChangeExp to do a proper * experience change. * * @param _exp exp to set. @@ -344,7 +345,7 @@ class Game_Actor : public Game_Battler { /** * Sets level of actor. * The value is adjusted to the boundary 1 up to max level. - * Other actor attributes are not altered. Use ChangeLevel to do a proper + * Other actor attributes are not altered. Use ChangeLevel to do a proper * level change. * * @param _level level to set. @@ -365,10 +366,10 @@ class Game_Actor : public Game_Battler { * @return true if fixed */ bool IsEquipmentFixed() const; - + /** * Checks if the actors defense skill is stronger the usual. - * + * * @return true if strong defense */ bool HasStrongDefense() const override; @@ -379,7 +380,7 @@ class Game_Actor : public Game_Battler { * @return true if a weapon is having preempt attribute */ bool HasPreemptiveAttack() const override; - + /** * Sets face graphic of actor. * @param file_name file containing new face. @@ -447,7 +448,7 @@ class Game_Actor : public Game_Battler { * @return random skill */ - const RPG::Skill& GetRandomSkill() const; + const RPG::Skill* GetRandomSkill() const; /** * Gets actor states list. @@ -706,7 +707,7 @@ class Game_Actor : public Game_Battler { /** * Gets the actor's class name as a string. - * + * * @return Rpg2k3 hero class name */ std::string GetClassName() const; @@ -742,6 +743,11 @@ class Game_Actor : public Game_Battler { BattlerType GetType() const override; private: + /** + * @return Reference to the Actor data of the LDB + */ + const RPG::Actor& GetActor() const; + // same reason as for Game_Picture, see comment /** * @return Reference to the SaveActor data @@ -749,10 +755,9 @@ class Game_Actor : public Game_Battler { RPG::SaveActor& GetData() const; /** - * Removes invalid (wrong type) equipment from the equipment - * slots. + * Removes invalid data from the actor. */ - void RemoveInvalidEquipment(); + void RemoveInvalidData(); int actor_id; std::vector exp_list; diff --git a/src/game_actors.cpp b/src/game_actors.cpp index 824777f36d..3bbd0b68f4 100644 --- a/src/game_actors.cpp +++ b/src/game_actors.cpp @@ -27,8 +27,8 @@ namespace { } void Game_Actors::Init() { - data.resize(Data::actors.size() + 1); - for (size_t i = 1; i < data.size(); i++) + data.resize(Data::actors.size()); + for (size_t i = 1; i <= data.size(); i++) GetActor(i)->Init(); } @@ -36,6 +36,13 @@ void Game_Actors::Fixup() { for (size_t i = 1; i < data.size(); ++i) { GetActor(i)->Fixup(); } + + // Ensure actor save data and LDB actors has correct size + if (Main_Data::game_data.actors.size() != data.size()) { + Output::Warning("Actor array size doesn't match Savegame actor array size (%d != %d)", + Main_Data::game_data.actors.size(), data.size()); + Main_Data::game_data.actors.resize(data.size()); + } } void Game_Actors::Dispose() { @@ -44,14 +51,14 @@ void Game_Actors::Dispose() { Game_Actor* Game_Actors::GetActor(int actor_id) { if (!ActorExists(actor_id)) { - Output::Warning("Actor ID %d is invalid.", actor_id); - return NULL; - } else if (!data[actor_id]) - data[actor_id].reset(new Game_Actor(actor_id)); + return nullptr; + } else if (!data[actor_id - 1]) { + data[actor_id - 1].reset(new Game_Actor(actor_id)); + } - return data[actor_id].get(); + return data[actor_id - 1].get(); } bool Game_Actors::ActorExists(int actor_id) { - return actor_id > 0 && (size_t)actor_id < data.size(); + return actor_id > 0 && (size_t)actor_id <= data.size(); } diff --git a/src/game_battle.cpp b/src/game_battle.cpp index 56e3c79ad2..c7d786b3fd 100644 --- a/src/game_battle.cpp +++ b/src/game_battle.cpp @@ -17,7 +17,6 @@ #include #include -#include #include "data.h" #include "game_actors.h" #include "game_enemyparty.h" @@ -30,7 +29,9 @@ #include "game_interpreter_battle.h" #include "battle_animation.h" #include "game_battle.h" +#include "reader_util.h" #include "spriteset_battle.h" +#include "output.h" namespace Game_Battle { const RPG::Troop* troop; @@ -71,7 +72,8 @@ void Game_Battle::Init() { target_enemy_index = 0; need_refresh = false; - troop = &Data::troops[Game_Temp::battle_troop_id - 1]; + // troop_id is guaranteed to be valid + troop = ReaderUtil::GetElement(Data::troops, Game_Temp::battle_troop_id); page_executed.resize(troop->pages.size()); page_can_run.resize(troop->pages.size()); @@ -148,15 +150,25 @@ Spriteset_Battle& Game_Battle::GetSpriteset() { void Game_Battle::ShowBattleAnimation(int animation_id, Game_Battler* target, bool flash) { Main_Data::game_data.screen.battleanim_id = animation_id; - const RPG::Animation& anim = Data::animations[animation_id - 1]; - animation.reset(new BattleAnimationBattlers(anim, *target, flash)); + const RPG::Animation* anim = ReaderUtil::GetElement(Data::animations, animation_id); + if (!anim) { + Output::Warning("ShowBattleAnimation Single: Invalid animation ID %d", animation_id); + return; + } + + animation.reset(new BattleAnimationBattlers(*anim, *target, flash)); } void Game_Battle::ShowBattleAnimation(int animation_id, const std::vector& targets, bool flash) { Main_Data::game_data.screen.battleanim_id = animation_id; - const RPG::Animation& anim = Data::animations[animation_id - 1]; - animation.reset(new BattleAnimationBattlers(anim, targets, flash)); + const RPG::Animation* anim = ReaderUtil::GetElement(Data::animations, animation_id); + if (!anim) { + Output::Warning("ShowBattleAnimation Many: Invalid animation ID %d", animation_id); + return; + } + + animation.reset(new BattleAnimationBattlers(*anim, targets, flash)); } bool Game_Battle::IsBattleAnimationWaiting() { diff --git a/src/game_battlealgorithm.cpp b/src/game_battlealgorithm.cpp index c64014eb39..dc92d74e9a 100644 --- a/src/game_battlealgorithm.cpp +++ b/src/game_battlealgorithm.cpp @@ -33,6 +33,7 @@ #include "main_data.h" #include "output.h" #include "player.h" +#include "reader_util.h" #include "rpg_animation.h" #include "rpg_state.h" #include "rpg_skill.h" @@ -179,7 +180,7 @@ std::string Game_BattleAlgorithm::AlgorithmBase::GetDeathMessage() const { } bool is_ally = GetTarget()->GetType() == Game_Battler::Type_Ally; - const RPG::State* state = GetTarget()->GetSignificantState(); + const RPG::State* state = GetTarget()->GetSignificantState(); const std::string& message = is_ally ? state->message_actor : state->message_enemy; @@ -623,7 +624,7 @@ bool Game_BattleAlgorithm::AlgorithmBase::IsTargetValid() const { // the source return true; } - + if (current_target == targets.end()) { // End of target list reached return false; @@ -733,21 +734,28 @@ bool Game_BattleAlgorithm::Normal::Execute() { if (source->GetType() == Game_Battler::Type_Ally) { Game_Actor* ally = static_cast(source); int hit_chance = source->GetHitChance(); - int weaponID = ally->GetWeaponId() - 1; - - if (weaponID == -1) { + const RPG::Item* weapon = ReaderUtil::GetElement(Data::items, ally->GetWeaponId()); + + if (!weapon) { // No Weapon // Todo: Two Sword style - animation = &Data::animations[Data::actors[ally->GetId() - 1].unarmed_animation - 1]; + const RPG::Actor& actor = *ReaderUtil::GetElement(Data::actors, ally->GetId()); + animation = ReaderUtil::GetElement(Data::animations, actor.unarmed_animation); + if (!animation) { + Output::Warning("Algorithm Normal: Invalid unarmed animation ID %d", actor.unarmed_animation); + } } else { - animation = &Data::animations[Data::items[weaponID].animation_id - 1]; - RPG::Item weapon = Data::items[weaponID]; - hit_chance = weapon.hit; - crit_chance += crit_chance * weapon.critical_hit / 100.0f; - multiplier = GetAttributeMultiplier(weapon.attribute_set); + animation = ReaderUtil::GetElement(Data::animations, weapon->animation_id); + if (!animation) { + Output::Warning("Algorithm Normal: Invalid weapon animation ID %d", weapon->animation_id); + } + + hit_chance = weapon->hit; + crit_chance += crit_chance * weapon->critical_hit / 100.0f; + multiplier = GetAttributeMultiplier(weapon->attribute_set); } - if (weaponID != -1 && !Data::items[weaponID].ignore_evasion) { + if (weapon && !weapon->ignore_evasion) { to_hit = (int)(100 - (100 - hit_chance) * (1 + (1.0 * GetTarget()->GetAgi() / ally->GetAgi() - 1) / 2)); } else { to_hit = (int)(100 - (100 - hit_chance)); @@ -790,15 +798,23 @@ bool Game_BattleAlgorithm::Normal::Execute() { } else { if (source->GetType() == Game_Battler::Type_Ally) { - int weaponID = static_cast(source)->GetWeaponId() - 1; - if (weaponID != -1) { - RPG::Item item = Data::items[static_cast(source)->GetWeaponId() - 1]; - for (unsigned int i = 0; i < item.state_set.size(); i++) { - if (item.state_set[i] && Utils::GetRandomNumber(0, 99) < (item.state_chance * GetTarget()->GetStateProbability(Data::states[i].ID) / 100)) { - if (item.state_effect) { - healing = true; + const RPG::Item* weapon = ReaderUtil::GetElement(Data::items, static_cast(source)->GetWeaponId()); + + if (weapon) { + for (unsigned int i = 0; i < weapon->state_set.size(); i++) { + if (weapon->state_set[i]) { + const RPG::State* state = ReaderUtil::GetElement(Data::states, weapon->state_set[i]); + if (!state) { + Output::Warning("Algorithm Normal: Weapon %d causes invalid state %d", weapon->ID, weapon->state_set[i]); + continue; + } + + if (Utils::GetRandomNumber(0, 99) < (weapon->state_chance * GetTarget()->GetStateProbability(state->ID) / 100)) { + if (weapon->state_effect) { + healing = true; + } + conditions.push_back(*state); } - conditions.push_back(Data::states[i]); } } } @@ -820,8 +836,9 @@ void Game_BattleAlgorithm::Normal::Apply() { source->SetCharged(false); if (source->GetType() == Game_Battler::Type_Ally) { Game_Actor* src = static_cast(source); - if (src->GetWeaponId() != 0) { - source->ChangeSp(-Data::items[src->GetWeaponId() - 1].sp_cost / src->GetSpCostModifier()); + const RPG::Item* weapon = ReaderUtil::GetElement(Data::items, src->GetWeaponId()); + if (weapon) { + source->ChangeSp(-weapon->sp_cost / src->GetSpCostModifier()); } } } @@ -886,14 +903,14 @@ bool Game_BattleAlgorithm::Skill::IsTargetValid() const { if (current_target == targets.end()) { return false; } - + if (skill.scope == RPG::Skill::Scope_ally || skill.scope == RPG::Skill::Scope_party) { if (GetTarget()->IsDead()) { // Cures death return !skill.state_effects.empty() && skill.state_effects[0]; } - + return true; } @@ -907,7 +924,13 @@ bool Game_BattleAlgorithm::Skill::Execute() { Reset(); - animation = skill.animation_id == 0 ? NULL : &Data::animations[skill.animation_id - 1]; + animation = nullptr; + if (skill.animation_id != 0) { + animation = ReaderUtil::GetElement(Data::animations, skill.animation_id); + if (!animation) { + Output::Warning("Algorithm Skill: Invalid skill animation ID %d", skill.animation_id); + } + } this->success = false; @@ -976,7 +999,7 @@ bool Game_BattleAlgorithm::Skill::Execute() { if (skill.affect_sp) { this->sp = std::min(effect, GetTarget()->GetSp()); } - + if (skill.affect_attack) this->attack = effect; if (skill.affect_defense) @@ -994,7 +1017,7 @@ bool Game_BattleAlgorithm::Skill::Execute() { continue; this->success = true; - + if (healing || Utils::GetRandomNumber(0, 99) <= GetTarget()->GetStateProbability(Data::states[i].ID)) { conditions.push_back(Data::states[i]); } @@ -1447,7 +1470,7 @@ bool Game_BattleAlgorithm::SelfDestruct::Execute() { if (effect < 0) effect = 0; - + this->hp = effect / ( GetTarget()->IsDefending() ? GetTarget()->HasStrongDefense() ? 3 : 2 : 1); @@ -1568,7 +1591,7 @@ std::string Game_BattleAlgorithm::Transform::GetStartMessage() const { return Utils::ReplacePlaceholders( Data::terms.enemy_transform, {'S', 'O'}, - {source->GetName(), Data::enemies[new_monster_id - 1].name} + {source->GetName(), ReaderUtil::GetElement(Data::enemies, new_monster_id)->name} // Sanity check in Game_Enemy ); } else if (Player::IsRPG2k()) { diff --git a/src/game_battler.cpp b/src/game_battler.cpp index 37515fc53f..3330757e5f 100644 --- a/src/game_battler.cpp +++ b/src/game_battler.cpp @@ -29,6 +29,8 @@ #include "util_macro.h" #include "main_data.h" #include "utils.h" +#include "output.h" +#include "reader_util.h" Game_Battler::Game_Battler() { ResetBattle(); @@ -53,7 +55,8 @@ std::vector Game_Battler::GetInflictedStates() const { int Game_Battler::GetSignificantRestriction() { const std::vector states = GetInflictedStates(); for (int i = 0; i < (int)states.size(); i++) { - const RPG::State* state = &Data::states[states[i] - 1]; + // States are guaranteed to be valid + const RPG::State* state = ReaderUtil::GetElement(Data::states, states[i]); if (state->restriction != RPG::State::Restriction_normal) { return state->restriction; } @@ -64,7 +67,8 @@ int Game_Battler::GetSignificantRestriction() { bool Game_Battler::CanAct() const { const std::vector states = GetInflictedStates(); for (int i = 0; i < (int)states.size(); i++) { - const RPG::State* state = &Data::states[states[i] - 1]; + // States are guaranteed to be valid + const RPG::State* state = ReaderUtil::GetElement(Data::states, states[i]); if (state->restriction == RPG::State::Restriction_do_nothing) { return false; } @@ -90,7 +94,8 @@ const RPG::State* Game_Battler::GetSignificantState() const { const std::vector states = GetInflictedStates(); for (int i = 0; i < (int) states.size(); i++) { - const RPG::State* state = &Data::states[states[i] - 1]; + // States are guaranteed to be valid + const RPG::State* state = ReaderUtil::GetElement(Data::states, states[i]); // Death has highest priority if (state->ID == 1) return state; @@ -105,19 +110,24 @@ const RPG::State* Game_Battler::GetSignificantState() const { } int Game_Battler::GetStateRate(int state_id, int rate) const { - const RPG::State& state = Data::states[state_id - 1]; + const RPG::State* state = ReaderUtil::GetElement(Data::states, state_id); + + if (!state) { + Output::Warning("GetStateRate: Invalid state ID %d", state_id); + return 0; + } switch (rate) { case 0: - return state.a_rate; + return state->a_rate; case 1: - return state.b_rate; + return state->b_rate; case 2: - return state.c_rate; + return state->c_rate; case 3: - return state.d_rate; + return state->d_rate; case 4: - return state.e_rate; + return state->e_rate; default:; } @@ -126,19 +136,24 @@ int Game_Battler::GetStateRate(int state_id, int rate) const { } int Game_Battler::GetAttributeRate(int attribute_id, int rate) const { - const RPG::Attribute& state = Data::attributes[attribute_id - 1]; + const RPG::Attribute* attribute = ReaderUtil::GetElement(Data::attributes, attribute_id); + + if (!attribute) { + Output::Warning("GetAttributeRate: Invalid attribute ID %d", attribute_id); + return 0; + } switch (rate) { case 0: - return state.a_rate; + return attribute->a_rate; case 1: - return state.b_rate; + return attribute->b_rate; case 2: - return state.c_rate; + return attribute->c_rate; case 3: - return state.d_rate; + return attribute->d_rate; case 4: - return state.e_rate; + return attribute->e_rate; default:; } @@ -147,12 +162,13 @@ int Game_Battler::GetAttributeRate(int attribute_id, int rate) const { } bool Game_Battler::IsSkillUsable(int skill_id) const { - if (skill_id <= 0 || skill_id > (int)Data::skills.size()) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + + if (!skill) { + Output::Warning("IsSkillUsable: Invalid skill ID %d", skill_id); return false; } - const RPG::Skill& skill = Data::skills[skill_id - 1]; - if (CalculateSkillCost(skill_id) > GetSp()) { return false; } @@ -164,7 +180,8 @@ bool Game_Battler::IsSkillUsable(int skill_id) const { const std::vector states = GetInflictedStates(); for (std::vector::const_iterator it = states.begin(); it != states.end(); ++it) { - const RPG::State& state = Data::states[(*it) - 1]; + // States are guaranteed to be valid + const RPG::State& state = *ReaderUtil::GetElement(Data::states, (*it)); if (state.restrict_skill) { smallest_physical_rate = std::min(state.restrict_skill_level, smallest_physical_rate); @@ -175,10 +192,10 @@ bool Game_Battler::IsSkillUsable(int skill_id) const { } } - if (skill.physical_rate >= smallest_physical_rate) { + if (skill->physical_rate >= smallest_physical_rate) { return false; } - if (skill.magical_rate >= smallest_magical_rate) { + if (skill->magical_rate >= smallest_magical_rate) { return false; } @@ -186,17 +203,21 @@ bool Game_Battler::IsSkillUsable(int skill_id) const { } bool Game_Battler::UseItem(int item_id) { - const RPG::Item& item = Data::items[item_id - 1]; + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + if (!item) { + Output::Warning("UseItem: Can't use item with invalid ID %d", item_id); + return false; + } - if (item.type == RPG::Item::Type_medicine) { + if (item->type == RPG::Item::Type_medicine) { bool was_used = false; - int hp_change = item.recover_hp_rate * GetMaxHp() / 100 + item.recover_hp; - int sp_change = item.recover_sp_rate * GetMaxSp() / 100 + item.recover_sp; + int hp_change = item->recover_hp_rate * GetMaxHp() / 100 + item->recover_hp; + int sp_change = item->recover_sp_rate * GetMaxSp() / 100 + item->recover_sp; if (IsDead()) { // Check if item can revive - if (item.state_set.empty() || !item.state_set[0]) { + if (item->state_set.empty() || !item->state_set[0]) { return false; } @@ -205,7 +226,7 @@ bool Game_Battler::UseItem(int item_id) { ChangeHp(1); was_used = true; } - } else if (item.ko_only) { + } else if (item->ko_only) { // Must be dead return false; } @@ -220,8 +241,8 @@ bool Game_Battler::UseItem(int item_id) { was_used = true; } - for (int i = 0; i < (int)item.state_set.size(); i++) { - if (item.state_set[i]) { + for (int i = 0; i < (int)item->state_set.size(); i++) { + if (item->state_set[i]) { was_used |= HasState(Data::states[i].ID); RemoveState(Data::states[i].ID); } @@ -230,53 +251,57 @@ bool Game_Battler::UseItem(int item_id) { return was_used; } - if (item.type == RPG::Item::Type_switch) { + if (item->type == RPG::Item::Type_switch) { return true; } - switch (item.type) { + switch (item->type) { case RPG::Item::Type_weapon: case RPG::Item::Type_shield: case RPG::Item::Type_armor: case RPG::Item::Type_helmet: case RPG::Item::Type_accessory: - return item.use_skill && UseSkill(item.skill_id); + return item->use_skill && UseSkill(item->skill_id); case RPG::Item::Type_special: - return UseSkill(item.skill_id); + return UseSkill(item->skill_id); } return false; } bool Game_Battler::UseSkill(int skill_id) { - const RPG::Skill& skill = Data::skills[skill_id - 1]; + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + if (!skill) { + Output::Warning("UseSkill: Can't use skill with invalid ID %d", skill_id); + return false; + } bool was_used = false; - if (skill.type == RPG::Skill::Type_normal || skill.type >= RPG::Skill::Type_subskill) { + if (skill->type == RPG::Skill::Type_normal || skill->type >= RPG::Skill::Type_subskill) { // Only takes care of healing skills outside of battle, // the other skill logic is in Game_BattleAlgorithm - if (!(skill.scope == RPG::Skill::Scope_ally || - skill.scope == RPG::Skill::Scope_party || - skill.scope == RPG::Skill::Scope_self)) { + if (!(skill->scope == RPG::Skill::Scope_ally || + skill->scope == RPG::Skill::Scope_party || + skill->scope == RPG::Skill::Scope_self)) { return false; } // Skills only increase hp and sp outside of battle - if (skill.power > 0 && skill.affect_hp && !HasFullHp()) { + if (skill->power > 0 && skill->affect_hp && !HasFullHp()) { was_used = true; - ChangeHp(skill.power); + ChangeHp(skill->power); } - if (skill.power > 0 && skill.affect_sp && !HasFullSp()) { + if (skill->power > 0 && skill->affect_sp && !HasFullSp()) { was_used = true; - ChangeSp(skill.power); + ChangeSp(skill->power); } - for (int i = 0; i < (int) skill.state_effects.size(); i++) { - if (skill.state_effects[i]) { - if (skill.state_effect) { + for (int i = 0; i < (int) skill->state_effects.size(); i++) { + if (skill->state_effects[i]) { + if (skill->state_effect) { was_used |= !HasState(Data::states[i].ID); AddState(Data::states[i].ID); } else { @@ -285,10 +310,10 @@ bool Game_Battler::UseSkill(int skill_id) { } } } - } else if (skill.type == RPG::Skill::Type_teleport || skill.type == RPG::Skill::Type_escape) { + } else if (skill->type == RPG::Skill::Type_teleport || skill->type == RPG::Skill::Type_escape) { was_used = true; - } else if (skill.type == RPG::Skill::Type_switch) { - Game_Switches[skill.switch_id] = true; + } else if (skill->type == RPG::Skill::Type_switch) { + Game_Switches[skill->switch_id] = true; was_used = true; } @@ -296,11 +321,16 @@ bool Game_Battler::UseSkill(int skill_id) { } int Game_Battler::CalculateSkillCost(int skill_id) const { - const RPG::Skill& skill = Data::skills[skill_id - 1]; + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + if (!skill) { + Output::Warning("CalculateSkillCost: Invalid skill ID %d", skill_id); + return 0; + } + return (Player::engine == Player::EngineRpg2k3 && - skill.sp_type == RPG::Skill::SpType_percent) - ? GetMaxSp() * skill.sp_percent / 100 - : skill.sp_cost; + skill->sp_type == RPG::Skill::SpType_percent) + ? GetMaxSp() * skill->sp_percent / 100 + : skill->sp_cost; } void Game_Battler::SetAtkModifier(int modifier) { @@ -320,7 +350,9 @@ void Game_Battler::SetAgiModifier(int modifier) { } void Game_Battler::AddState(int state_id) { - if (state_id <= 0) { + const RPG::State* state = ReaderUtil::GetElement(Data::states, state_id); + if (!state) { + Output::Warning("AddState: Can't add state with invalid ID %d", state_id); return; } @@ -333,7 +365,9 @@ void Game_Battler::AddState(int state_id) { } void Game_Battler::RemoveState(int state_id) { - if (state_id <= 0) { + const RPG::State* state = ReaderUtil::GetElement(Data::states, state_id); + if (!state) { + Output::Warning("RemoveState: Can't delete state with invalid ID %d", state_id); return; } @@ -345,15 +379,11 @@ void Game_Battler::RemoveState(int state_id) { states[state_id - 1] = 0; } -static bool non_permanent(int state_id) { - return Data::states[state_id - 1].type == RPG::State::Persistence_ends; -} - -int Game_Battler::ApplyConditions() -{ +int Game_Battler::ApplyConditions() { int damageTaken = 0; for (int16_t inflicted : GetInflictedStates()) { - RPG::State state = Data::states[inflicted - 1]; + // States are guaranteed to be valid + RPG::State& state = *ReaderUtil::GetElement(Data::states, inflicted); int hp = state.hp_change_val + (int)(std::ceil(GetMaxHp() * state.hp_change_max / 100.0)); int sp = state.sp_change_val + (int)(std::ceil(GetMaxHp() * state.sp_change_max / 100.0)); int source_hp = this->GetHp(); @@ -382,7 +412,7 @@ int Game_Battler::ApplyConditions() } } - else if(state.sp_change_type == state.ChangeType_gain) { + else if (state.sp_change_type == state.ChangeType_gain) { src_sp = std::min(source_sp, sp); if(src_sp < 0 ) { src_sp = 0; @@ -399,6 +429,10 @@ int Game_Battler::ApplyConditions() return damageTaken; } +static bool non_permanent(int state_id) { + return ReaderUtil::GetElement(Data::states, state_id)->type == RPG::State::Persistence_ends; +} + void Game_Battler::RemoveBattleStates() { std::vector& states = GetStates(); @@ -488,8 +522,10 @@ int Game_Battler::GetAtk() const { int n = min(max(base_atk, 1), 999); for (int16_t i : GetInflictedStates()) { - if(Data::states[i - 1].affect_attack) { - n = AffectParameter(Data::states[i - 1].affect_type, base_atk); + // States are guaranteed to be valid + const RPG::State& state = *ReaderUtil::GetElement(Data::states, i); + if (state.affect_attack) { + n = AffectParameter(state.affect_type, base_atk); break; } } @@ -506,8 +542,10 @@ int Game_Battler::GetDef() const { int n = min(max(base_def, 1), 999); for (int16_t i : GetInflictedStates()) { - if (Data::states[i - 1].affect_defense) { - n = AffectParameter(Data::states[i - 1].affect_type, base_def); + // States are guaranteed to be valid + const RPG::State& state = *ReaderUtil::GetElement(Data::states, i); + if (state.affect_defense) { + n = AffectParameter(state.affect_type, base_def); break; } } @@ -524,8 +562,10 @@ int Game_Battler::GetSpi() const { int n = min(max(base_spi, 1), 999); for (int16_t i : GetInflictedStates()) { - if(Data::states[i - 1].affect_spirit) { - n = AffectParameter(Data::states[i - 1].affect_type, base_spi); + // States are guaranteed to be valid + const RPG::State& state = *ReaderUtil::GetElement(Data::states, i); + if (state.affect_spirit) { + n = AffectParameter(state.affect_type, base_spi); break; } } @@ -542,8 +582,10 @@ int Game_Battler::GetAgi() const { int n = min(max(base_agi, 1), 999); for (int16_t i : GetInflictedStates()) { - if(Data::states[i - 1].affect_agility) { - n = AffectParameter(Data::states[i - 1].affect_type, base_agi); + // States are guaranteed to be valid + const RPG::State& state = *ReaderUtil::GetElement(Data::states, i); + if (state.affect_agility) { + n = AffectParameter(state.affect_type, base_agi); break; } } @@ -664,7 +706,8 @@ std::vector Game_Battler::BattlePhysicalStateHeal(int physical_rate) { bool Game_Battler::HasReflectState() const { for (int16_t i : GetInflictedStates()) { - if (Data::states[i - 1].reflect_magic) { + // States are guaranteed to be valid + if (ReaderUtil::GetElement(Data::states, i)->reflect_magic) { return true; } } diff --git a/src/game_commonevent.cpp b/src/game_commonevent.cpp index daa5d02ad9..56f9d4da86 100644 --- a/src/game_commonevent.cpp +++ b/src/game_commonevent.cpp @@ -21,6 +21,7 @@ #include "game_switches.h" #include "game_interpreter_map.h" #include "main_data.h" +#include "reader_util.h" Game_CommonEvent::Game_CommonEvent(int common_event_id) : common_event_id(common_event_id) { @@ -77,24 +78,26 @@ int Game_CommonEvent::GetIndex() const { return common_event_id; } +// Game_Map ensures validity of Common Events + std::string Game_CommonEvent::GetName() const { - return Data::commonevents[common_event_id - 1].name; + return ReaderUtil::GetElement(Data::commonevents, common_event_id)->name; } bool Game_CommonEvent::GetSwitchFlag() const { - return Data::commonevents[common_event_id - 1].switch_flag; + return ReaderUtil::GetElement(Data::commonevents, common_event_id)->switch_flag; } int Game_CommonEvent::GetSwitchId() const { - return Data::commonevents[common_event_id - 1].switch_id; + return ReaderUtil::GetElement(Data::commonevents, common_event_id)->switch_id; } int Game_CommonEvent::GetTrigger() const { - return Data::commonevents[common_event_id - 1].trigger; + return ReaderUtil::GetElement(Data::commonevents, common_event_id)->trigger; } std::vector& Game_CommonEvent::GetList() { - return Data::commonevents[common_event_id - 1].event_commands; + return ReaderUtil::GetElement(Data::commonevents, common_event_id)->event_commands; } RPG::SaveEventData Game_CommonEvent::GetSaveData() { diff --git a/src/game_enemy.cpp b/src/game_enemy.cpp index ed04b462d9..abd2808de8 100644 --- a/src/game_enemy.cpp +++ b/src/game_enemy.cpp @@ -23,6 +23,7 @@ #include "game_enemy.h" #include "game_party.h" #include "game_switches.h" +#include "reader_util.h" #include "output.h" #include "utils.h" @@ -173,16 +174,16 @@ bool Game_Enemy::IsHidden() const { void Game_Enemy::Transform(int new_enemy_id) { enemy_id = new_enemy_id; - if (enemy_id <= 0 || enemy_id > static_cast(Data::enemies.size())) { + enemy = ReaderUtil::GetElement(Data::enemies, enemy_id); + + if (!enemy) { // Some games (e.g. Battle 5 in Embric) have invalid monsters in the battle. // This case will fail in RPG Maker and the game will exit with an error message. // Create a warning instead and continue the battle. - Output::Warning("Enemy id %d invalid", new_enemy_id); + Output::Warning("Invalid enemy ID %d", new_enemy_id); enemy_id = 1; // This generates an invisible monster with 0 HP and a minor memory leak enemy = new RPG::Enemy(); - } else { - enemy = &Data::enemies[enemy_id - 1]; } } diff --git a/src/game_enemyparty.cpp b/src/game_enemyparty.cpp index 5ce7287653..a391da4685 100644 --- a/src/game_enemyparty.cpp +++ b/src/game_enemyparty.cpp @@ -20,7 +20,9 @@ #include "game_interpreter.h" #include "game_enemyparty.h" #include "main_data.h" +#include "reader_util.h" #include "utils.h" +#include "output.h" Game_EnemyParty::Game_EnemyParty() { } @@ -39,7 +41,12 @@ int Game_EnemyParty::GetBattlerCount() const { void Game_EnemyParty::Setup(int battle_troop_id) { enemies.clear(); - troop = &Data::troops[battle_troop_id - 1]; + const RPG::Troop* troop = ReaderUtil::GetElement(Data::troops, battle_troop_id); + if (!troop) { + // Shouldn't happen because Scene_Battle verifies this + Output::Warning("Invalid battle troop ID %d", battle_troop_id); + return; + } int non_hidden = 0; for (const RPG::TroopMember& mem : troop->members) { diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 77d62767e2..a797c359c4 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -65,7 +65,7 @@ Game_Interpreter::Game_Interpreter(int _depth, bool _main_flag) { clear_child = false; if (depth > 100) { - Output::Warning("Too many event calls (over 9000)"); + Output::Warning("Interpreter: Maximum callstack depth (100) exceeded"); } Clear(); @@ -108,7 +108,11 @@ void Game_Interpreter::Setup( map_id = Game_Map::GetMapId(); event_id = _event_id; - list = _list; + + if (depth <= 100) { + list = _list; + } + triggered_by_decision_key = started_by_decision_key; index = 0; @@ -768,69 +772,73 @@ bool Game_Interpreter::CommandControlVariables(RPG::EventCommand const& com) { / case 5: // Hero actor = Game_Actors::GetActor(com.parameters[5]); - if (actor != NULL) { - switch (com.parameters[6]) { - case 0: - // Level - value = actor->GetLevel(); - break; - case 1: - // Experience - value = actor->GetExp(); - break; - case 2: - // Current HP - value = actor->GetHp(); - break; - case 3: - // Current MP - value = actor->GetSp(); - break; - case 4: - // Max HP - value = actor->GetMaxHp(); - break; - case 5: - // Max MP - value = actor->GetMaxSp(); - break; - case 6: - // Attack - value = actor->GetAtk(); - break; - case 7: - // Defense - value = actor->GetDef(); - break; - case 8: - // Intelligence - value = actor->GetSpi(); - break; - case 9: - // Agility - value = actor->GetAgi(); - break; - case 10: - // Weapon ID - value = actor->GetWeaponId(); - break; - case 11: - // Shield ID - value = actor->GetShieldId(); - break; - case 12: - // Armor ID - value = actor->GetArmorId(); - break; - case 13: - // Helmet ID - value = actor->GetHelmetId(); - break; - case 14: - // Accesory ID - value = actor->GetAccessoryId(); - break; - } + + if (!actor) { + Output::Warning("ControlVariables: Invalid actor ID %d", com.parameters[5]); + return true; + } + + switch (com.parameters[6]) { + case 0: + // Level + value = actor->GetLevel(); + break; + case 1: + // Experience + value = actor->GetExp(); + break; + case 2: + // Current HP + value = actor->GetHp(); + break; + case 3: + // Current MP + value = actor->GetSp(); + break; + case 4: + // Max HP + value = actor->GetMaxHp(); + break; + case 5: + // Max MP + value = actor->GetMaxSp(); + break; + case 6: + // Attack + value = actor->GetAtk(); + break; + case 7: + // Defense + value = actor->GetDef(); + break; + case 8: + // Intelligence + value = actor->GetSpi(); + break; + case 9: + // Agility + value = actor->GetAgi(); + break; + case 10: + // Weapon ID + value = actor->GetWeaponId(); + break; + case 11: + // Shield ID + value = actor->GetShieldId(); + break; + case 12: + // Armor ID + value = actor->GetArmorId(); + break; + case 13: + // Helmet ID + value = actor->GetHelmetId(); + break; + case 14: + // Accesory ID + value = actor->GetAccessoryId(); + break; } break; case 6: @@ -1040,14 +1048,23 @@ std::vector Game_Interpreter::GetActors(int mode, int id) { case 1: // Hero actor = Game_Actors::GetActor(id); - if (actor) - actors.push_back(actor); + + if (!actor) { + Output::Warning("Invalid actor ID %d", id); + return actors; + } + + actors.push_back(actor); break; case 2: // Var hero actor = Game_Actors::GetActor(Game_Variables[id]); - if (actor) - actors.push_back(actor); + if (!actor) { + Output::Warning("Invalid actor ID %d", Game_Variables[id]); + return actors; + } + + actors.push_back(actor); break; } @@ -1159,18 +1176,20 @@ bool Game_Interpreter::CommandChangePartyMember(RPG::EventCommand const& com) { actor = Game_Actors::GetActor(id); - if (actor != NULL) { + if (!actor) { + Output::Warning("ChangePartyMember: Invalid actor ID %d", id); + return true; + } - if (com.parameters[0] == 0) { - // Add members - Main_Data::game_party->AddActor(id); + if (com.parameters[0] == 0) { + // Add members + Main_Data::game_party->AddActor(id); - } else { - // Remove members - Main_Data::game_party->RemoveActor(id); + } else { + // Remove members + Main_Data::game_party->RemoveActor(id); - CheckGameOver(); - } + CheckGameOver(); } Game_Map::SetNeedRefresh(Game_Map::Refresh_Map); @@ -1272,21 +1291,26 @@ bool Game_Interpreter::CommandChangeSkills(RPG::EventCommand const& com) { // Co bool Game_Interpreter::CommandChangeEquipment(RPG::EventCommand const& com) { // Code 10450 int item_id; - int type; int slot; + const RPG::Item* item; switch (com.parameters[2]) { case 0: item_id = ValueOrVariable(com.parameters[3], com.parameters[4]); - type = Data::items[item_id - 1].type; - switch (type) { + item = ReaderUtil::GetElement(Data::items, item_id); + if (!item) { + Output::Warning("ChangeEquipment: Invalid item ID %d", item_id); + return true; + } + + switch (item->type) { case RPG::Item::Type_weapon: case RPG::Item::Type_shield: case RPG::Item::Type_armor: case RPG::Item::Type_helmet: case RPG::Item::Type_accessory: - slot = type; + slot = item->type; break; default: return true; @@ -1481,18 +1505,36 @@ bool Game_Interpreter::CommandGameOver(RPG::EventCommand const& /* com */) { // bool Game_Interpreter::CommandChangeHeroName(RPG::EventCommand const& com) { // code 10610 Game_Actor* actor = Game_Actors::GetActor(com.parameters[0]); + + if (!actor) { + Output::Warning("ChangeHeroName: Invalid actor ID %d", com.parameters[0]); + return true; + } + actor->SetName(com.string); return true; } bool Game_Interpreter::CommandChangeHeroTitle(RPG::EventCommand const& com) { // code 10620 Game_Actor* actor = Game_Actors::GetActor(com.parameters[0]); + + if (!actor) { + Output::Warning("ChangeHeroTitle: Invalid actor ID %d", com.parameters[0]); + return true; + } + actor->SetTitle(com.string); return true; } bool Game_Interpreter::CommandChangeSpriteAssociation(RPG::EventCommand const& com) { // code 10630 Game_Actor* actor = Game_Actors::GetActor(com.parameters[0]); + + if (!actor) { + Output::Warning("ChangeSpriteAssociation: Invalid actor ID %d", com.parameters[0]); + return true; + } + const std::string &file = com.string; int idx = com.parameters[1]; bool transparent = com.parameters[2] != 0; @@ -1503,11 +1545,14 @@ bool Game_Interpreter::CommandChangeSpriteAssociation(RPG::EventCommand const& c bool Game_Interpreter::CommandChangeActorFace(RPG::EventCommand const& com) { // code 10640 Game_Actor* actor = Game_Actors::GetActor(com.parameters[0]); - if (actor != NULL) { - actor->SetFace(com.string, com.parameters[1]); + + if (!actor) { + Output::Warning("CommandChangeActorFace: Invalid actor ID %d", com.parameters[0]); return true; } - return false; + + actor->SetFace(com.string, com.parameters[1]); + return true; } bool Game_Interpreter::CommandChangeVehicleGraphic(RPG::EventCommand const& com) { // code 10650 @@ -2582,6 +2627,13 @@ bool Game_Interpreter::CommandConditionalBranch(RPG::EventCommand const& com) { // Hero actor_id = com.parameters[1]; actor = Game_Actors::GetActor(actor_id); + + if (!actor) { + Output::Warning("ConditionalBranch: Invalid actor ID %d", actor_id); + // Use Else branch + return SkipTo(Cmd::ElseBranch, Cmd::EndBranch); + } + switch (com.parameters[2]) { case 0: // Is actor in party @@ -2680,11 +2732,10 @@ bool Game_Interpreter::CommandConditionalBranch(RPG::EventCommand const& com) { // Is Fullscreen active? result = DisplayUi->IsFullscreen(); break; - } break; default: - Output::Warning("Branch %d unsupported", com.parameters[0]); + Output::Warning("ConditionalBranch: Branch %d unsupported", com.parameters[0]); } if (result) @@ -2769,13 +2820,20 @@ bool Game_Interpreter::CommandCallEvent(RPG::EventCommand const& com) { // code child_interpreter.reset(new Game_Interpreter_Map(depth + 1, main_flag)); switch (com.parameters[0]) { - case 0: // Common Event + case 0: { // Common Event evt_id = com.parameters[1]; - // Forwarding the event_id is save because all RPG Maker engines prior 2k3 1.12 + Game_CommonEvent* common_event = ReaderUtil::GetElement(Game_Map::GetCommonEvents(), evt_id); + if (!common_event) { + Output::Warning("CallEvent: Can't call invalid common event %d", evt_id); + return true; + } + + // Forwarding the event_id is safe because all RPG Maker engines prior 2k3 1.12 // threw an error when ThisEvent was used in CommonEvents. // The exception is EraseEvent which is handled special (see the code) - child_interpreter->Setup(&Game_Map::GetCommonEvents()[evt_id - 1], event_id); + child_interpreter->Setup(common_event, event_id); return true; + } case 1: // Map Event evt_id = com.parameters[1]; event_page = com.parameters[2]; @@ -2797,7 +2855,7 @@ bool Game_Interpreter::CommandCallEvent(RPG::EventCommand const& com) { // code child_interpreter->event_info.y = event->GetY(); child_interpreter->event_info.page = page; } else { - Output::Warning("Can't call non-existant page %d of event %d", event_page, evt_id); + Output::Warning("CallEvent: Can't call non-existant page %d of event %d", event_page, evt_id); } } @@ -2811,12 +2869,18 @@ bool Game_Interpreter::CommandReturnToTitleScreen(RPG::EventCommand const& /* co } bool Game_Interpreter::CommandChangeClass(RPG::EventCommand const& com) { // code 1008 - int class_id = com.parameters[2]; + int class_id = com.parameters[2]; // 0: No class, 1+: Specific class bool level1 = com.parameters[3] > 0; int skill_mode = com.parameters[4]; // no change, replace, add int stats_mode = com.parameters[5]; // no change, halve, level 1, current level bool show = com.parameters[6] > 0; + const RPG::Class* cls = ReaderUtil::GetElement(Data::classes, class_id); + if (!cls) { + Output::Warning("ChangeClass: Can't change class. Class %d is invalid", class_id); + return true; + } + for (const auto& actor : GetActors(com.parameters[0], com.parameters[1])) { int actor_id = actor->GetId(); @@ -2905,7 +2969,7 @@ bool Game_Interpreter::CommandChangeClass(RPG::EventCommand const& com) { // cod } if (skill_mode > 0) { // Learn additionally - for (const RPG::Learning& learn : Data::classes[class_id - 1].skills) { + for (const RPG::Learning& learn : cls->skills) { if (level >= learn.level) { actor->LearnSkill(learn.skill_id); if (show) { diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index 6d681c0968..9282f51bcf 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -24,6 +24,7 @@ #include "game_switches.h" #include "game_system.h" #include "game_variables.h" +#include "reader_util.h" #include "output.h" #include "player.h" #include "game_temp.h" @@ -83,10 +84,16 @@ bool Game_Interpreter_Battle::CommandCallCommonEvent(RPG::EventCommand const& co if (child_interpreter) return false; - int event_id = com.parameters[0]; + int evt_id = com.parameters[0]; + + Game_CommonEvent* common_event = ReaderUtil::GetElement(Game_Map::GetCommonEvents(), evt_id); + if (!common_event) { + Output::Warning("CallCommonEvent: Can't call invalid common event %d", evt_id); + return true; + } child_interpreter.reset(new Game_Interpreter_Battle(depth + 1)); - child_interpreter->Setup(&Game_Map::GetCommonEvents()[event_id - 1], 0); + child_interpreter->Setup(common_event, 0); return true; } @@ -140,7 +147,14 @@ bool Game_Interpreter_Battle::CommandEnableCombo(RPG::EventCommand const& com) { int command_id = com.parameters[1]; int multiple = com.parameters[2]; - Game_Actors::GetActor(actor_id)->SetBattleCombo(command_id, multiple); + Game_Actor* actor = Game_Actors::GetActor(actor_id); + + if (!actor) { + Output::Warning("EnableCombo: Invalid actor ID %d", actor_id); + return true; + } + + actor->SetBattleCombo(command_id, multiple); return true; } @@ -336,9 +350,14 @@ bool Game_Interpreter_Battle::CommandConditionalBranchBattle(RPG::EventCommand c case 2: { // Hero can act Game_Actor* actor = Game_Actors::GetActor(com.parameters[1]); - if (actor) { - result = actor->CanAct(); + + if (!actor) { + Output::Warning("ConditionalBranchBattle: Invalid actor ID %d", com.parameters[1]); + // Use Else branch + return SkipTo(Cmd::ElseBranch_B, Cmd::EndBranch_B); } + + result = actor->CanAct(); break; } case 3: @@ -351,10 +370,19 @@ bool Game_Interpreter_Battle::CommandConditionalBranchBattle(RPG::EventCommand c // Monster is the current target result = Game_Battle::GetEnemyTargetIndex() == com.parameters[1]; break; - case 5: + case 5: { // Hero uses the ... command - result = Game_Actors::GetActor(com.parameters[1])->GetLastBattleAction() == com.parameters[2]; + Game_Actor *actor = Game_Actors::GetActor(com.parameters[1]); + + if (!actor) { + Output::Warning("ConditionalBranchBattle: Invalid actor ID %d", com.parameters[1]); + // Use Else branch + return SkipTo(Cmd::ElseBranch_B, Cmd::EndBranch_B); + } + + result = actor->GetLastBattleAction() == com.parameters[2]; break; + } } if (result) diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index c4c5217119..56ad9ca88c 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -548,10 +548,18 @@ bool Game_Interpreter_Map::CommandEnterHeroName(RPG::EventCommand const& com) { Game_Temp::hero_name_id = com.parameters[0]; Game_Temp::hero_name_charset = com.parameters[1]; - if (com.parameters[2] != 0) - Game_Temp::hero_name = Game_Actors::GetActor(Game_Temp::hero_name_id)->GetName(); - else + if (com.parameters[2] != 0) { + Game_Actor *actor = Game_Actors::GetActor(Game_Temp::hero_name_id); + + if (!actor) { + Output::Warning("EnterHeroName: Invalid actor ID %d", Game_Temp::hero_name_id); + Game_Temp::hero_name.clear(); + } else { + Game_Temp::hero_name = actor->GetName(); + } + } else { Game_Temp::hero_name.clear(); + } Game_Temp::name_calling = true; return true; diff --git a/src/game_map.cpp b/src/game_map.cpp index bb6bdd23f3..90d42be684 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -75,6 +75,8 @@ namespace { int last_map_id; bool teleport_delay; + + RPG::Chipset* chipset; } void Game_Map::Init() { @@ -224,6 +226,7 @@ void Game_Map::SetupCommon(int _id) { } else { map = LMU_Reader::LoadXml(map_file); } + Output::Debug("Loading Map %s", ss.str().c_str()); if (map.get() == NULL) { @@ -577,14 +580,17 @@ bool Game_Map::IsPassable(int x, int y, int d, const Game_Character* self_event) bool Game_Map::IsPassableVehicle(int x, int y, Game_Vehicle::Type vehicle_type) { if (!Game_Map::IsValid(x, y)) return false; - if (vehicle_type == Game_Vehicle::Boat) { - if (!Data::data.terrains[GetTerrainTag(x, y) -1].boat_pass) + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, GetTerrainTag(x, y)); + if (!terrain) { + Output::Warning("IsPassableVehicle: Invalid terrain at (%d, %d)", x, y); + } else if (vehicle_type == Game_Vehicle::Boat) { + if (!terrain->boat_pass) return false; } else if (vehicle_type == Game_Vehicle::Ship) { - if (!Data::data.terrains[GetTerrainTag(x, y) -1].ship_pass) + if (!terrain->ship_pass) return false; } else if (vehicle_type == Game_Vehicle::Airship) { - return Data::data.terrains[GetTerrainTag(x, y) -1].airship_pass; + return terrain->airship_pass; } int tile_id; @@ -689,7 +695,12 @@ bool Game_Map::IsPassableTile(int bit, int tile_index) { int Game_Map::GetBushDepth(int x, int y) { if (!Game_Map::IsValid(x, y)) return 0; - return Data::data.terrains[GetTerrainTag(x,y) - 1].bush_depth; + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, GetTerrainTag(x,y)); + if (!terrain) { + Output::Warning("GetBushDepth: Invalid terrain at (%d, %d)", x, y); + return 0; + } + return terrain->bush_depth; } bool Game_Map::IsCounter(int x, int y) { @@ -706,7 +717,7 @@ int Game_Map::GetTerrainTag(int x, int y) { x = RoundX(x); y = RoundY(y); - if (!Game_Map::IsValid(x, y)) return 9; + if (!Game_Map::IsValid(x, y) || !chipset) return 9; unsigned const chipID = map->lower_layer[x + y * GetWidth()]; unsigned chip_index = @@ -715,15 +726,12 @@ int Game_Map::GetTerrainTag(int x, int y) { (chipID < 5000)? 6 + (chipID-4000)/50 : (chipID < 5144)? 18 + (chipID-5000) : 0; - unsigned const chipset_index = map_info.chipset_id - 1; // Apply tile substitution if (chip_index >= 18 && chip_index <= 144) chip_index = map_info.lower_tiles[chip_index - 18] + 18; - assert(chipset_index < Data::data.chipsets.size()); - - auto& terrain_data = Data::data.chipsets[chipset_index].terrain_data; + auto& terrain_data = chipset->terrain_data; if (terrain_data.empty()) { // RPG_RT optimisation: When the terrain is all 1, no terrain data is stored @@ -736,7 +744,13 @@ int Game_Map::GetTerrainTag(int x, int y) { } bool Game_Map::AirshipLandOk(int const x, int const y) { - return Data::data.terrains[GetTerrainTag(x, y) - 1].airship_land; + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, GetTerrainTag(x, y)); + if (!terrain) { + Output::Warning("AirshipLandOk: Invalid terrain at (%d, %d)", x, y); + return false; + } + + return terrain->airship_land; } void Game_Map::GetEventsXY(std::vector& events, int x, int y) { @@ -876,10 +890,14 @@ void Game_Map::UpdateEncounterSteps() { int x = Main_Data::game_player->GetX(); int y = Main_Data::game_player->GetY(); - int terrain_id = GetTerrainTag(x, y); - const RPG::Terrain& terrain = Data::terrains[terrain_id - 1]; - location.encounter_steps = location.encounter_steps - terrain.encounter_rate; + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, GetTerrainTag(x,y)); + if (!terrain) { + Output::Warning("UpdateEncounterSteps: Invalid terrain at (%d, %d)", x, y); + return; + } + + location.encounter_steps = location.encounter_steps - terrain->encounter_rate; if (location.encounter_steps <= 0) { ResetEncounterSteps(); @@ -902,7 +920,13 @@ std::vector Game_Map::GetEncountersAt(int x, int y) { int terrain_tag = GetTerrainTag(Main_Data::game_player->GetX(), Main_Data::game_player->GetY()); std::function is_acceptable = [=](int troop_id) { - std::vector& terrain_set = Data::troops[troop_id - 1].terrain_set; + const RPG::Troop* troop = ReaderUtil::GetElement(Data::troops, troop_id); + if (!troop) { + Output::Warning("GetEncountersAt: Invalid troop ID %d in encounter list", troop_id); + return false; + } + + const std::vector& terrain_set = troop->terrain_set; // RPG_RT optimisation: Omitted entries are the default value (true) return terrain_set.size() <= (unsigned)(terrain_tag - 1) || @@ -980,19 +1004,24 @@ void Game_Map::SetupBattle() { } void Game_Map::ShowBattleAnimation(int animation_id, int target_id, bool global) { + const RPG::Animation* anim = ReaderUtil::GetElement(Data::animations, animation_id); + if (!anim) { + Output::Warning("ShowBattleAnimation: Invalid battle animation ID %d", animation_id); + return; + } + Main_Data::game_data.screen.battleanim_id = animation_id; Main_Data::game_data.screen.battleanim_target = target_id; Main_Data::game_data.screen.battleanim_global = global; - const RPG::Animation& anim = Data::animations[animation_id - 1]; Game_Character* chara = Game_Character::GetCharacter(target_id, target_id); if (chara) { - chara->SetFlashTimeLeft(0); // Any flash always ends + chara->SetFlashTimeLeft(0); // Any flash always ends if (global) { - animation.reset(new BattleAnimationGlobal(anim)); + animation.reset(new BattleAnimationGlobal(*anim)); } else { - animation.reset(new BattleAnimationChara(anim, *chara)); + animation.reset(new BattleAnimationChara(*anim, *chara)); } } } @@ -1075,10 +1104,6 @@ int Game_Map::GetAnimationSpeed() { return (animation_fast ? 12 : 24); } -std::vector& Game_Map::GetTerrainTags() { - return Data::chipsets[map_info.chipset_id - 1].terrain_data; -} - std::vector& Game_Map::GetEvents() { return events; } @@ -1133,12 +1158,18 @@ int Game_Map::GetParentId(int map_id) { void Game_Map::SetChipset(int id) { map_info.chipset_id = id; - RPG::Chipset &chipset = Data::chipsets[map_info.chipset_id - 1]; - chipset_name = chipset.chipset_name; - passages_down = chipset.passable_data_lower; - passages_up = chipset.passable_data_upper; - animation_type = chipset.animation_type; - animation_fast = chipset.animation_speed != 0; + + chipset = ReaderUtil::GetElement(Data::chipsets, map_info.chipset_id); + if (!chipset) { + Output::Warning("SetChipset: Invalid chipset ID %d", map_info.chipset_id); + } else { + chipset_name = chipset->chipset_name; + passages_down = chipset->passable_data_lower; + passages_up = chipset->passable_data_upper; + animation_type = chipset->animation_type; + animation_fast = chipset->animation_speed != 0; + } + if (passages_down.size() < 162) passages_down.resize(162, (unsigned char) 0x0F); if (passages_up.size() < 144) diff --git a/src/game_map.h b/src/game_map.h index 597fc4fad7..7bc068f126 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -474,13 +474,6 @@ namespace Game_Map { */ int GetAnimationSpeed(); - /** - * Gets terrain tags list. - * - * @return terrain tags list. - */ - std::vector& GetTerrainTags(); - /** * Gets events list. * diff --git a/src/game_party.cpp b/src/game_party.cpp index 93133672a0..c4bf08efd0 100644 --- a/src/game_party.cpp +++ b/src/game_party.cpp @@ -28,6 +28,7 @@ #include "game_targets.h" #include "game_temp.h" #include "game_system.h" +#include "reader_util.h" #include "output.h" static RPG::SaveInventory& data = Main_Data::game_data.inventory; @@ -35,14 +36,7 @@ static RPG::SaveInventory& data = Main_Data::game_data.inventory; Game_Party::Game_Party() { data.Setup(); - // Remove non existing actors - std::vector temp_party; - std::swap(temp_party, data.party); - std::vector::iterator it; - for (it = temp_party.begin(); it != temp_party.end(); ++it) - if (Game_Actors::ActorExists(*it)) - data.party.push_back(*it); - data.party_size = data.party.size(); + RemoveInvalidData(); } Game_Actor& Game_Party::operator[] (const int index) { @@ -165,7 +159,14 @@ void Game_Party::RemoveItem(int item_id, int amount) { } void Game_Party::ConsumeItemUse(int item_id) { - switch (Data::items[item_id - 1].type) { + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + + if (!item) { + Output::Warning("ConsumeItemUse: Invalid item ID %d.", item_id); + return; + } + + switch (item->type) { case RPG::Item::Type_normal: case RPG::Item::Type_weapon: case RPG::Item::Type_shield: @@ -175,24 +176,18 @@ void Game_Party::ConsumeItemUse(int item_id) { return; } - if (item_id < 1 || item_id > (int) Data::items.size()) { - Output::Warning("Can't use up item. %04d is not a valid item ID.", - item_id); - return; - } - for (int i = 0; i < (int) data.item_ids.size(); i++) { if (data.item_ids[i] != item_id) continue; - if (Data::items[item_id - 1].uses == 0) { + if (item->uses == 0) { // Unlimited uses return; } data.item_usage[i]++; - if (data.item_usage[i] >= Data::items[item_id - 1].uses) { + if (data.item_usage[i] >= item->uses) { if (data.item_counts[i] == 1) { // We just used up the last one data.item_ids.erase(data.item_ids.begin() + i); @@ -209,43 +204,43 @@ void Game_Party::ConsumeItemUse(int item_id) { } bool Game_Party::IsItemUsable(int item_id, const Game_Actor* target) const { - if (item_id <= 0 || item_id > (int)Data::items.size()) { + if (target && !target->IsItemUsable(item_id)) { return false; } - if (target && !target->IsItemUsable(item_id)) { + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + if (!item) { + Output::Warning("IsItemUsable: Invalid item ID %d", item_id); return false; } - RPG::Item& item = Data::items[item_id - 1]; - if (item_id > 0 && item_id <= (int)Data::items.size() && data.party.size() > 0) { - switch (item.type) { + switch (item->type) { case RPG::Item::Type_weapon: case RPG::Item::Type_shield: case RPG::Item::Type_armor: case RPG::Item::Type_helmet: case RPG::Item::Type_accessory: - return item.use_skill && IsSkillUsable(item.skill_id, nullptr, true); + return item->use_skill && IsSkillUsable(item->skill_id, nullptr, true); case RPG::Item::Type_special: - return IsSkillUsable(item.skill_id, nullptr, true); + return IsSkillUsable(item->skill_id, nullptr, true); } if (Game_Temp::battle_running) { - switch (item.type) { + switch (item->type) { case RPG::Item::Type_medicine: - return !item.occasion_field1; + return !item->occasion_field1; case RPG::Item::Type_switch: - return item.occasion_battle; + return item->occasion_battle; } } else { - switch (item.type) { + switch (item->type) { case RPG::Item::Type_medicine: case RPG::Item::Type_material: case RPG::Item::Type_book: return true; case RPG::Item::Type_switch: - return item.occasion_field2; + return item->occasion_field2; } } } @@ -282,19 +277,23 @@ bool Game_Party::IsSkillUsable(int skill_id, const Game_Actor* target, bool from return false; } - const RPG::Skill& skill = Data::skills[skill_id - 1]; - if (target && !target->IsSkillUsable(skill_id)) { return false; } - if (skill.type == RPG::Skill::Type_escape) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + if (!skill) { + Output::Warning("IsSkillUsable: Can't use skill with invalid ID %d", skill_id); + return false; + } + + if (skill->type == RPG::Skill::Type_escape) { return !Game_Temp::battle_running && Game_System::GetAllowEscape() && Game_Targets::HasEscapeTarget(); - } else if (skill.type == RPG::Skill::Type_teleport) { + } else if (skill->type == RPG::Skill::Type_teleport) { return !Game_Temp::battle_running && Game_System::GetAllowTeleport() && Game_Targets::HasTeleportTarget(); - } else if (skill.type == RPG::Skill::Type_normal || - skill.type >= RPG::Skill::Type_subskill) { - int scope = skill.scope; + } else if (skill->type == RPG::Skill::Type_normal || + skill->type >= RPG::Skill::Type_subskill) { + int scope = skill->scope; if (Game_Temp::battle_running) { return true; @@ -304,23 +303,23 @@ bool Game_Party::IsSkillUsable(int skill_id, const Game_Actor* target, bool from // RPG_RT logic... if (scope == RPG::Skill::Scope_self) { - return from_item || skill.affect_hp || skill.affect_sp; + return from_item || skill->affect_hp || skill->affect_sp; } if (scope == RPG::Skill::Scope_ally || scope == RPG::Skill::Scope_party) { return from_item || - skill.affect_hp || - skill.affect_sp || - !skill.state_effects.empty(); + skill->affect_hp || + skill->affect_sp || + !skill->state_effects.empty(); } - } else if (skill.type == RPG::Skill::Type_switch) { + } else if (skill->type == RPG::Skill::Type_switch) { if (Game_Temp::battle_running) { - return skill.occasion_battle; + return skill->occasion_battle; } - return skill.occasion_field; + return skill->occasion_field; } return false; @@ -557,3 +556,29 @@ int Game_Party::GetFatigue() { return (int)std::ceil(100 - 100.0f * (((float)hp / total_hp * 2.0f + (float)sp / total_sp) / 3.0f)); } + +void Game_Party::RemoveInvalidData() { + // Remove non existing actors + std::vector temp_party; + std::swap(temp_party, data.party); + std::vector::iterator it; + for (it = temp_party.begin(); it != temp_party.end(); ++it) { + if (Game_Actors::ActorExists(*it)) { + data.party.push_back(*it); + } else { + Output::Warning("Removing invalid party member %d", *it); + } + } + data.party_size = data.party.size(); + + // Remove non existing items + for (it = data.item_ids.begin(); it != data.item_ids.end(); ) { + if (!ReaderUtil::GetElement(Data::items, *it)) { + Output::Warning("Removing invalid item %d from party", *it); + it = data.item_ids.erase(it); + } else { + ++it; + } + } + data.items_size = data.item_ids.size(); +} diff --git a/src/game_party.h b/src/game_party.h index ea1fe9bc2d..1157ea7008 100644 --- a/src/game_party.h +++ b/src/game_party.h @@ -300,6 +300,11 @@ class Game_Party : public Game_Party_Base { * @return number of frames remaining. */ int GetTimerFrames(int which, bool& visible, bool& battle); + + /** + * Removes invalid actors and items from the party + */ + void RemoveInvalidData(); }; #endif diff --git a/src/game_picture.cpp b/src/game_picture.cpp index ab3ab3a574..432189c3f0 100644 --- a/src/game_picture.cpp +++ b/src/game_picture.cpp @@ -22,6 +22,7 @@ #include "game_picture.h" #include "player.h" #include "main_data.h" +#include "reader_util.h" // Applied to ensure that all pictures are above "normal" objects on this layer constexpr int z_mask = (1 << 16); @@ -369,5 +370,6 @@ void Game_Picture::SyncCurrentToFinish() { } RPG::SavePicture& Game_Picture::GetData() const { - return Main_Data::game_data.pictures[id - 1]; + // Save: Picture array is guaranteed to be of correct size + return *ReaderUtil::GetElement(Main_Data::game_data.pictures, id); } diff --git a/src/game_player.cpp b/src/game_player.cpp index 4a65e41a1c..3883725b31 100644 --- a/src/game_player.cpp +++ b/src/game_player.cpp @@ -30,6 +30,8 @@ #include "player.h" #include "util_macro.h" #include "game_switches.h" +#include "output.h" +#include "reader_util.h" #include #include @@ -730,11 +732,15 @@ bool Game_Player::CanWalk(int x, int y) { void Game_Player::BeginMove() { int terrain_id = Game_Map::GetTerrainTag(GetX(), GetY()); - const RPG::Terrain& terrain = Data::terrains[terrain_id - 1]; - if (!terrain.on_damage_se || (terrain.on_damage_se && (terrain.damage > 0))) { - Game_System::SePlay(terrain.footstep); + const RPG::Terrain* terrain = ReaderUtil::GetElement(Data::terrains, terrain_id); + if (terrain) { + if (!terrain->on_damage_se || (terrain->on_damage_se && (terrain->damage > 0))) { + Game_System::SePlay(terrain->footstep); + } + Main_Data::game_party->ApplyDamage(terrain->damage); + } else { + Output::Warning("Player BeginMove: Invalid terrain ID %d at (%d, %d)", terrain_id, GetX(), GetY()); } - Main_Data::game_party->ApplyDamage(terrain.damage); } void Game_Player::CancelMoveRoute() { diff --git a/src/game_switches.cpp b/src/game_switches.cpp index 6234bd412b..75b26b8acf 100644 --- a/src/game_switches.cpp +++ b/src/game_switches.cpp @@ -19,6 +19,7 @@ #include "game_switches.h" #include "main_data.h" #include "output.h" +#include "reader_util.h" #define PLAYER_VAR_LIMIT 1000000 @@ -51,10 +52,13 @@ std::vector::reference Game_Switches_Class::operator[](int switch_id) { } std::string Game_Switches_Class::GetName(int _id) const { - if (!(_id > 0 && _id <= (int)Data::switches.size())) { + const RPG::Switch* sw = ReaderUtil::GetElement(Data::switches, _id); + + if (!sw) { + // No warning, is valid because the switch array resizes dynamic during runtime return ""; } else { - return Data::switches[_id - 1].name; + return sw->name; } } diff --git a/src/game_variables.cpp b/src/game_variables.cpp index 7b1a2f5fb2..3765e9d9ad 100644 --- a/src/game_variables.cpp +++ b/src/game_variables.cpp @@ -19,6 +19,7 @@ #include "game_variables.h" #include "main_data.h" #include "output.h" +#include "reader_util.h" #define PLAYER_VAR_LIMIT 1000000 @@ -52,10 +53,13 @@ int& Game_Variables_Class::operator[] (int variable_id) { } std::string Game_Variables_Class::GetName(int _id) const { - if (!(_id > 0 && _id <= (int)Data::variables.size())) { + const RPG::Variable* var = ReaderUtil::GetElement(Data::variables, _id); + + if (!var) { + // No warning, is valid because the variable array resizes dynamic during runtime return ""; } else { - return Data::variables[_id - 1].name; + return var->name; } } diff --git a/src/player.cpp b/src/player.cpp index d0abd6678e..a1e4586c9a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -358,7 +358,6 @@ void Player::Exit() { Graphics::Quit(); FileFinder::Quit(); Output::Quit(); - Main_Data::Cleanup(); DisplayUi.reset(); #ifdef PSP2 @@ -702,7 +701,13 @@ void Player::ResetGameObjects() { request->Start(); } + // The init order is important + Main_Data::Cleanup(); + Main_Data::game_data.Setup(); + // Prevent a crash when Game_Map wants to reset the screen content + // because Setup() modified pictures array + Main_Data::game_screen.reset(new Game_Screen()); Game_Actors::Init(); Game_Map::Init(); @@ -711,10 +716,10 @@ void Player::ResetGameObjects() { Game_System::Init(); Game_Temp::Init(); Game_Variables.Reset(); + Main_Data::game_enemyparty.reset(new Game_EnemyParty()); Main_Data::game_party.reset(new Game_Party()); Main_Data::game_player.reset(new Game_Player()); - Main_Data::game_screen.reset(new Game_Screen()); FrameReset(); } @@ -763,8 +768,6 @@ void Player::LoadDatabase() { } static void OnMapSaveFileReady(FileRequestResult*) { - Game_Actors::Fixup(); - Game_Map::SetupFromSave(); Main_Data::game_player->MoveTo( @@ -788,6 +791,9 @@ void Player::LoadSavegame(const std::string& save_name) { Main_Data::game_data = *save.get(); Main_Data::game_data.system.Fixup(); + Game_Actors::Fixup(); + Main_Data::game_party->RemoveInvalidData(); + int map_id = save->party_location.map_id; FileRequestAsync* map = Game_Map::RequestMap(map_id); diff --git a/src/scene_actortarget.cpp b/src/scene_actortarget.cpp index cd0ed0ef58..d3cfe6000c 100644 --- a/src/scene_actortarget.cpp +++ b/src/scene_actortarget.cpp @@ -24,6 +24,8 @@ #include "main_data.h" #include "scene_item.h" #include "scene_skill.h" +#include "output.h" +#include "reader_util.h" Scene_ActorTarget::Scene_ActorTarget(int item_id) : id(item_id), actor_index(0), use_item(true) { @@ -46,20 +48,32 @@ void Scene_ActorTarget::Start() { target_window->SetIndex(0); if (use_item) { - if (Data::items[id - 1].entire_party) { + const RPG::Item* item = ReaderUtil::GetElement(Data::items, id); + if (!item) { + Output::Warning("Scene ActorTarget: Invalid item ID %d", id); + Scene::Pop(); + } + + if (item->entire_party) { target_window->SetIndex(-100); } status_window->SetData(id, true); - help_window->SetText(Data::items[id - 1].name); + help_window->SetText(item->name); } else { - if (Data::skills[id - 1].scope == RPG::Skill::Scope_self) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, id); + if (!skill) { + Output::Warning("Scene ActorTarget: Invalid skill ID %d", id); + Scene::Pop(); + } + + if (skill->scope == RPG::Skill::Scope_self) { target_window->SetIndex(-actor_index); - } else if (Data::skills[id - 1].scope == RPG::Skill::Scope_party) { + } else if (skill->scope == RPG::Skill::Scope_party) { target_window->SetIndex(-100); } status_window->SetData(id, false); - help_window->SetText(Data::skills[id - 1].name); + help_window->SetText(skill->name); } } diff --git a/src/scene_battle.cpp b/src/scene_battle.cpp index b50ce9ee37..706a6d1026 100644 --- a/src/scene_battle.cpp +++ b/src/scene_battle.cpp @@ -33,6 +33,7 @@ #include "game_enemyparty.h" #include "game_battle.h" #include "battle_animation.h" +#include "reader_util.h" #include "scene_battle.h" #include "scene_battle_rpg2k.h" #include "scene_battle_rpg2k3.h" @@ -55,9 +56,10 @@ void Scene_Battle::Start() { Game_Temp::battle_troop_id = Player::battle_test_troop_id; } - if (Game_Temp::battle_troop_id <= 0 || - Game_Temp::battle_troop_id > (int)Data::troops.size()) { - const char* error_msg = "Invalid Monster Party Id %d"; + const RPG::Troop* troop = ReaderUtil::GetElement(Data::troops, Game_Temp::battle_troop_id); + + if (!troop) { + const char* error_msg = "Invalid Monster Party ID %d"; if (Player::battle_test_flag) { Output::Error(error_msg, Game_Temp::battle_troop_id); } @@ -69,7 +71,8 @@ void Scene_Battle::Start() { return; } - Output::Debug("Starting battle %d (%s)", Game_Temp::battle_troop_id, Data::troops[Game_Temp::battle_troop_id-1].name.c_str()); + // Game_Temp::battle_troop_id is valid during the whole battle + Output::Debug("Starting battle %d (%s)", Game_Temp::battle_troop_id, troop->name.c_str()); if (Player::battle_test_flag) { InitBattleTest(); @@ -202,7 +205,17 @@ void Scene_Battle::EnemySelected() { if (previous_state == State_SelectCommand) { active_actor->SetBattleAlgorithm(std::make_shared(active_actor, target)); } else if (previous_state == State_SelectSkill || (previous_state == State_SelectItem && skill_item)) { - active_actor->SetBattleAlgorithm(std::make_shared(active_actor, target, skill_item ? Data::skills[skill_item->skill_id - 1] : *skill_window->GetSkill(), skill_item)); + if (skill_item) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_item->skill_id); + if (!skill) { + Output::Warning("EnemySelected: Item %d references invalid skill %d", skill_item->ID, skill_item->skill_id); + return; + } + active_actor->SetBattleAlgorithm(std::make_shared(active_actor, target, *skill, skill_item)); + } else { + active_actor->SetBattleAlgorithm( + std::make_shared(active_actor, target, *skill_window->GetSkill())); + } } else if (previous_state == State_SelectItem) { active_actor->SetBattleAlgorithm(std::make_shared(active_actor, target, *item_window->GetItem())); } else { @@ -222,7 +235,16 @@ void Scene_Battle::AllySelected() { Game_Actor& target = (*Main_Data::game_party)[status_window->GetIndex()]; if (previous_state == State_SelectSkill || (previous_state == State_SelectItem && skill_item)) { - active_actor->SetBattleAlgorithm(std::make_shared(active_actor, &target, skill_item ? Data::skills[skill_item->skill_id - 1] : *skill_window->GetSkill(), skill_item)); + if (skill_item) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_item->skill_id); + if (!skill) { + Output::Warning("AllySelected: Item %d references invalid skill %d", skill_item->ID, skill_item->skill_id); + return; + } + } + + active_actor->SetBattleAlgorithm(std::make_shared(active_actor, &target, + skill_item ? *ReaderUtil::GetElement(Data::skills, skill_item->skill_id) : *skill_window->GetSkill(), skill_item)); } else if (previous_state == State_SelectItem) { active_actor->SetBattleAlgorithm(std::make_shared(active_actor, &target, *item_window->GetItem())); } else { @@ -275,10 +297,16 @@ void Scene_Battle::ItemSelected() { case RPG::Item::Type_armor: case RPG::Item::Type_helmet: case RPG::Item::Type_accessory: - case RPG::Item::Type_special: + case RPG::Item::Type_special: { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, item->skill_id); + if (!skill) { + Output::Warning("ItemSelected: Item %d references invalid skill %d", item->ID, item->skill_id); + return; + } skill_item = item; - AssignSkill(&Data::skills[item->skill_id - 1]); + AssignSkill(skill); break; + } case RPG::Item::Type_medicine: if (item->entire_party) { active_actor->SetBattleAlgorithm(std::make_shared(active_actor, Main_Data::game_party.get(), *item_window->GetItem())); @@ -314,16 +342,41 @@ void Scene_Battle::AssignSkill(const RPG::Skill* skill) { switch (skill->type) { case RPG::Skill::Type_teleport: case RPG::Skill::Type_escape: - case RPG::Skill::Type_switch: - active_actor->SetBattleAlgorithm(std::make_shared(active_actor, skill_item ? Data::skills[skill_item->skill_id - 1] : *skill_window->GetSkill(), skill_item)); + case RPG::Skill::Type_switch: { + if (skill_item) { + const RPG::Skill *skill = ReaderUtil::GetElement(Data::skills, skill_item->skill_id); + if (!skill) { + Output::Warning("AssignSkill: Item %d references invalid skill %d", skill_item->ID, skill_item->skill_id); + return; + } + } + + active_actor->SetBattleAlgorithm(std::make_shared(active_actor, skill_item ? *ReaderUtil::GetElement(Data::skills, skill_item->skill_id) : *skill_window->GetSkill(), skill_item)); ActionSelectedCallback(active_actor); return; + } case RPG::Skill::Type_normal: case RPG::Skill::Type_subskill: default: break; } + switch (skill->scope) { + case RPG::Skill::Scope_enemies: + case RPG::Skill::Scope_self: + case RPG::Skill::Scope_party: { + if (skill_item) { + const RPG::Skill *skill = ReaderUtil::GetElement(Data::skills, skill_item->skill_id); + if (!skill) { + Output::Warning("AssignSkill: Item %d references invalid skill %d", skill_item->ID, skill_item->skill_id); + return; + } + } + } + default: + break; + } + switch (skill->scope) { case RPG::Skill::Scope_enemy: SetState(State_SelectEnemyTarget); @@ -333,15 +386,18 @@ void Scene_Battle::AssignSkill(const RPG::Skill* skill) { status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_All); break; case RPG::Skill::Scope_enemies: - active_actor->SetBattleAlgorithm(std::make_shared(active_actor, Main_Data::game_enemyparty.get(), skill_item ? Data::skills[skill_item->skill_id - 1] : *skill_window->GetSkill(), skill_item)); + active_actor->SetBattleAlgorithm(std::make_shared( + active_actor, Main_Data::game_enemyparty.get(), skill_item ? *ReaderUtil::GetElement(Data::skills, skill_item->skill_id) : *skill_window->GetSkill(), skill_item)); ActionSelectedCallback(active_actor); break; case RPG::Skill::Scope_self: - active_actor->SetBattleAlgorithm(std::make_shared(active_actor, active_actor, skill_item ? Data::skills[skill_item->skill_id - 1] : *skill_window->GetSkill(), skill_item)); + active_actor->SetBattleAlgorithm(std::make_shared( + active_actor, active_actor, skill_item ? *ReaderUtil::GetElement(Data::skills, skill_item->skill_id) : *skill_window->GetSkill(), skill_item)); ActionSelectedCallback(active_actor); break; case RPG::Skill::Scope_party: - active_actor->SetBattleAlgorithm(std::make_shared(active_actor, Main_Data::game_party.get(), skill_item ? Data::skills[skill_item->skill_id - 1] : *skill_window->GetSkill(), skill_item)); + active_actor->SetBattleAlgorithm(std::make_shared( + active_actor, Main_Data::game_party.get(), skill_item ? *ReaderUtil::GetElement(Data::skills, skill_item->skill_id) : *skill_window->GetSkill(), skill_item)); ActionSelectedCallback(active_actor); break; } @@ -430,9 +486,14 @@ void Scene_Battle::CreateEnemyActionSkill(Game_Enemy* enemy, const RPG::EnemyAct return; } - const RPG::Skill& skill = Data::skills[action->skill_id - 1]; + RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, action->skill_id); + if (!skill) { + Output::Warning("CreateEnemyAction: Enemy can't use invalid skill %d", action->skill_id); + return; + } + - switch (skill.type) { + switch (skill->type) { case RPG::Skill::Type_teleport: case RPG::Skill::Type_escape: // FIXME: Can enemy use this? @@ -444,21 +505,21 @@ void Scene_Battle::CreateEnemyActionSkill(Game_Enemy* enemy, const RPG::EnemyAct break; } - switch (skill.scope) { + switch (skill->scope) { case RPG::Skill::Scope_enemy: - enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_party->GetRandomActiveBattler(), skill)); + enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_party->GetRandomActiveBattler(), *skill)); break; case RPG::Skill::Scope_ally: - enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_enemyparty->GetRandomActiveBattler(), skill)); + enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_enemyparty->GetRandomActiveBattler(), *skill)); break; case RPG::Skill::Scope_enemies: - enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_party.get(), skill)); + enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_party.get(), *skill)); break; case RPG::Skill::Scope_self: - enemy->SetBattleAlgorithm(std::make_shared(enemy, enemy, skill)); + enemy->SetBattleAlgorithm(std::make_shared(enemy, enemy, *skill)); break; case RPG::Skill::Scope_party: - enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_enemyparty.get(), skill)); + enemy->SetBattleAlgorithm(std::make_shared(enemy, Main_Data::game_enemyparty.get(), *skill)); break; } diff --git a/src/scene_battle_rpg2k.cpp b/src/scene_battle_rpg2k.cpp index 9ee64a269e..07fca2c924 100644 --- a/src/scene_battle_rpg2k.cpp +++ b/src/scene_battle_rpg2k.cpp @@ -30,6 +30,7 @@ #include "game_battle.h" #include "game_battlealgorithm.h" #include "battle_animation.h" +#include "reader_util.h" #include "scene_battle_rpg2k.h" #include "scene_battle.h" #include "scene_gameover.h" @@ -429,15 +430,19 @@ bool Scene_Battle_Rpg2k::ProcessBattleAction(Game_BattleAlgorithm::AlgorithmBase bool message_to_show = false; if (!states_to_heal.empty() || !states_remaining.empty()) { battle_message_window->Clear(); - for (auto state : states_to_heal) { - if (!Data::states[state - 1].message_recovery.empty()) { - battle_message_window->PushWithSubject(Data::states[state- 1].message_recovery, action->GetSource()->GetName()); + for (auto state_id : states_to_heal) { + // BattleAlgorithm verifies the states + const RPG::State* state = ReaderUtil::GetElement(Data::states, state_id); + if (!state->message_recovery.empty()) { + battle_message_window->PushWithSubject(state->message_recovery, action->GetSource()->GetName()); message_to_show = true; } } - for (auto state : states_remaining) { - if (!Data::states[state - 1].message_affected.empty()) { - battle_message_window->PushWithSubject(Data::states[state- 1].message_affected, action->GetSource()->GetName()); + for (auto state_id : states_remaining) { + // BattleAlgorithm verifies the states + const RPG::State* state = ReaderUtil::GetElement(Data::states, state_id); + if (!state->message_affected.empty()) { + battle_message_window->PushWithSubject(state->message_affected, action->GetSource()->GetName()); message_to_show = true; } } @@ -948,7 +953,13 @@ void Scene_Battle_Rpg2k::PushItemRecievedMessages(std::vector drops) { std::stringstream ss; for (std::vector::iterator it = drops.begin(); it != drops.end(); ++it) { - std::string item_name = Data::items[*it - 1].name; + const RPG::Item* item = ReaderUtil::GetElement(Data::items, *it); + // No Output::Warning needed here, reported later when the item is added + std::string item_name = "??? BAD ITEM ???"; + if (item) { + item_name = item->name; + } + if (Player::IsRPG2kE()) { Game_Message::texts.push_back( Utils::ReplacePlaceholders( diff --git a/src/scene_battle_rpg2k3.cpp b/src/scene_battle_rpg2k3.cpp index e301cb9a69..ab00441545 100644 --- a/src/scene_battle_rpg2k3.cpp +++ b/src/scene_battle_rpg2k3.cpp @@ -30,6 +30,7 @@ #include "game_message.h" #include "game_battle.h" #include "game_battlealgorithm.h" +#include "reader_util.h" #include "scene_gameover.h" #include "utils.h" #include "font.h" @@ -204,9 +205,11 @@ void Scene_Battle_Rpg2k3::UpdateCursors() { contents->Clear(); int text_width = 0; - for (auto state : states) { - std::string name = Data::states[state - 1].name; - int color = Data::states[state - 1].color; + for (auto state_id : states) { + // States are sanitized in Game_Battler + const RPG::State* state = ReaderUtil::GetElement(Data::states, state_id); + std::string name = state->name; + int color = state->color; FontRef font = Font::Default(); contents->TextDraw(text_width, 2, color, name, Text::AlignLeft); text_width += font->GetSize(name + " ").width; @@ -985,9 +988,16 @@ bool Scene_Battle_Rpg2k3::CheckWin() { ss << Data::terms.gold_recieved_a << " " << money << Data::terms.gold << Data::terms.gold_recieved_b; Game_Message::texts.push_back(ss.str()); } - for(std::vector::iterator it = drops.begin(); it != drops.end(); ++it) { + for (std::vector::iterator it = drops.begin(); it != drops.end(); ++it) { + const RPG::Item* item = ReaderUtil::GetElement(Data::items, *it); + // No Output::Warning needed here, reported later when the item is added + std::string item_name = "??? BAD ITEM ???"; + if (item) { + item_name = item->name; + } + ss.str(""); - ss << Data::items[*it - 1].name << space << Data::terms.item_recieved; + ss << item_name << space << Data::terms.item_recieved; Game_Message::texts.push_back(ss.str()); } diff --git a/src/scene_item.cpp b/src/scene_item.cpp index 3fb76a32b4..5d7e232b02 100644 --- a/src/scene_item.cpp +++ b/src/scene_item.cpp @@ -24,9 +24,11 @@ #include "game_system.h" #include "game_targets.h" #include "input.h" +#include "reader_util.h" #include "scene_actortarget.h" #include "scene_map.h" #include "scene_teleport.h" +#include "output.h" Scene_Item::Scene_Item(int item_index) : item_index(item_index) { @@ -59,26 +61,33 @@ void Scene_Item::Update() { if (item_id > 0 && item_window->CheckEnable(item_id)) { Game_System::SePlay(Game_System::GetSystemSE(Game_System::SFX_Decision)); + // The party only has valid items const RPG::Item& item = *item_window->GetItem(); if (item.type == RPG::Item::Type_switch) { Main_Data::game_party->ConsumeItemUse(item_id); - Game_Switches[Data::items[item_id - 1].switch_id] = true; + Game_Switches[item.switch_id] = true; Scene::PopUntil(Scene::Map); Game_Map::SetNeedRefresh(Game_Map::Refresh_All); } else if (item.type == RPG::Item::Type_special && item.skill_id > 0) { - if (Data::skills[item.skill_id - 1].type == RPG::Skill::Type_teleport) { + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, item.skill_id); + if (!skill) { + Output::Warning("Scene Item: Item references invalid skill ID %d", item.skill_id); + return; + } + + if (skill->type == RPG::Skill::Type_teleport) { Scene::Push(std::make_shared(item)); - } else if (Data::skills[item.skill_id - 1].type == RPG::Skill::Type_escape) { + } else if (skill->type == RPG::Skill::Type_escape) { Main_Data::game_party->ConsumeItemUse(item_id); Main_Data::game_player->ReserveTeleport(*Game_Targets::GetEscapeTarget()); Main_Data::game_player->StartTeleport(); Scene::PopUntil(Scene::Map); - } else if (Data::skills[item.skill_id - 1].type == RPG::Skill::Type_switch) { + } else if (skill->type == RPG::Skill::Type_switch) { Main_Data::game_party->ConsumeItemUse(item_id); - Game_Switches[Data::items[item_id - 1].switch_id] = true; + Game_Switches[skill->switch_id] = true; Scene::PopUntil(Scene::Map); Game_Map::SetNeedRefresh(Game_Map::Refresh_All); } else { diff --git a/src/scene_shop.cpp b/src/scene_shop.cpp index c53e6b57e1..a65c1a5d0d 100644 --- a/src/scene_shop.cpp +++ b/src/scene_shop.cpp @@ -20,6 +20,7 @@ #include "game_system.h" #include "game_party.h" #include "input.h" +#include "reader_util.h" #include "scene_shop.h" #include "output.h" @@ -58,6 +59,19 @@ void Scene_Shop::Start() { Game_Temp::shop_transaction = false; timer = 0; + if (Game_Temp::shop_buys) { + // Sanitize shop items + for (auto it = Game_Temp::shop_goods.begin(); it != Game_Temp::shop_goods.end();) { + const RPG::Item *item = ReaderUtil::GetElement(Data::items, *it); + if (!item) { + Output::Warning("Removed invalid item %d from shop", *it); + it = Game_Temp::shop_goods.erase(it); + } else { + ++it; + } + } + } + if (Game_Temp::shop_buys && Game_Temp::shop_sells) { SetMode(BuySellLeave); } else if (Game_Temp::shop_buys) { @@ -225,19 +239,20 @@ void Scene_Shop::UpdateBuySelection() { } else if (Input::IsTriggered(Input::DECISION)) { int item_id = buy_window->GetItemId(); - //checks the money and number of items possessed before buy + // checks the money and number of items possessed before buy if (buy_window->CheckEnable(item_id)) { Game_System::SePlay(Game_System::GetSystemSE(Game_System::SFX_Decision)); - RPG::Item& item = Data::items[item_id - 1]; + // Items are guaranteed to be valid + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); int max; - if (item.price == 0) { + if (item->price == 0) { max = 99; } else { - max = Main_Data::game_party->GetGold() / item.price; + max = Main_Data::game_party->GetGold() / item->price; } - number_window->SetData(item_id, max, item.price); + number_window->SetData(item_id, max, item->price); SetMode(BuyHowMany); } @@ -256,17 +271,15 @@ void Scene_Shop::UpdateSellSelection() { Scene::Pop(); } } else if (Input::IsTriggered(Input::DECISION)) { - int item_id = sell_window->GetItem() == NULL ? 0 : sell_window->GetItem()->ID; - status_window->SetItemId(item_id); - party_window->SetItemId(item_id); + const RPG::Item* item = sell_window->GetItem(); + status_window->SetItemId(item->ID); + party_window->SetItemId(item->ID); - if (item_id > 0 && Data::items[item_id - 1].price > 0) { - RPG::Item& item = Data::items[item_id - 1]; + if (item->price > 0) { Game_System::SePlay(Game_System::GetSystemSE(Game_System::SFX_Decision)); - number_window->SetData(item_id, Main_Data::game_party->GetItemCount(item_id), item.price / 2); + number_window->SetData(item->ID, Main_Data::game_party->GetItemCount(item->ID), item->price / 2); SetMode(SellHowMany); - } - else { + } else { Game_System::SePlay(Game_System::GetSystemSE(Game_System::SFX_Buzzer)); } } diff --git a/src/sprite_battler.cpp b/src/sprite_battler.cpp index 7f8642c656..61ab9beb10 100644 --- a/src/sprite_battler.cpp +++ b/src/sprite_battler.cpp @@ -22,6 +22,7 @@ #include "cache.h" #include "main_data.h" #include "player.h" +#include "reader_util.h" #include "output.h" Sprite_Battler::Sprite_Battler(Game_Battler* battler) : @@ -95,6 +96,7 @@ void Sprite_Battler::Update() { // Animations for allies if (Player::IsRPG2k3()) { if (animation) { + // Is a battle animation animation->Update(); if (animation->IsDone()) { @@ -112,23 +114,35 @@ void Sprite_Battler::Update() { return; } + // Is a battle charset animation static const int frames[] = {0,1,2,1,0}; int frame = frames[cycle / 10]; if (frame == sprite_frame) return; - const RPG::BattlerAnimation& anim = Data::battleranimations[battler->GetBattleAnimationId() - 1]; - const RPG::BattlerAnimationExtension& ext = anim.base_data[anim_state - 1]; + const RPG::BattlerAnimation* anim = ReaderUtil::GetElement(Data::battleranimations, battler->GetBattleAnimationId()); + if (!anim) { + Output::Warning("Invalid battler animation ID %d", battler->GetBattleAnimationId()); + return; + } + + const RPG::BattlerAnimationExtension* ext = ReaderUtil::GetElement(anim->base_data, anim_state); + if (!ext) { + Output::Warning("Animation %d: Invalid battler anim-extension state %d", anim->ID, anim_state); + return; + } - SetSrcRect(Rect(frame * 48, ext.battler_index * 48, 48, 48)); + SetSrcRect(Rect(frame * 48, ext->battler_index * 48, 48, 48)); if (cycle == 40) { switch (loop_state) { case LoopState_DefaultAnimationAfterFinish: cycle = 0; - // fallthrough + idling = true; + break; case LoopState_WaitAfterFinish: + --cycle; // incremented to last cycle next update idling = true; break; case LoopState_LoopAnimation: @@ -168,18 +182,28 @@ void Sprite_Battler::SetAnimationState(int state, LoopState loop) { if (Player::IsRPG2k3()) { if (battler->GetBattleAnimationId() > 0) { - const RPG::BattlerAnimation& anim = Data::battleranimations[battler->GetBattleAnimationId() - 1]; - const RPG::BattlerAnimationExtension& ext = anim.base_data[anim_state - 1]; + const RPG::BattlerAnimation* anim = ReaderUtil::GetElement(Data::battleranimations, battler->GetBattleAnimationId()); + if (!anim) { + Output::Warning("Invalid battler animation ID %d", battler->GetBattleAnimationId()); + return; + } + + const RPG::BattlerAnimationExtension* ext = ReaderUtil::GetElement(anim->base_data, anim_state); + if (!ext) { + Output::Warning("Animation %d: Invalid battler anim-extension state %d", anim->ID, anim_state); + return; + } - sprite_file = ext.battler_name; + sprite_file = ext->battler_name; - if (ext.animation_type == RPG::BattlerAnimationExtension::AnimType_animation) { + if (ext->animation_type == RPG::BattlerAnimationExtension::AnimType_animation) { SetBitmap(BitmapRef()); - if (ext.animation_id < 1 || ext.animation_id > (int)Data::animations.size()) { - Output::Warning("Invalid battle animation: %d", ext.animation_id); + RPG::Animation* battle_anim = ReaderUtil::GetElement(Data::animations, ext->animation_id); + if (!battle_anim) { + Output::Warning("Invalid battle animation ID %d", ext->animation_id); animation.reset(); } else { - animation.reset(new BattleAnimationBattlers(Data::animations[ext.animation_id - 1], *battler)); + animation.reset(new BattleAnimationBattlers(*battle_anim, *battler)); animation->SetZ(GetZ()); } } @@ -187,7 +211,7 @@ void Sprite_Battler::SetAnimationState(int state, LoopState loop) { animation.reset(); if (!sprite_file.empty()) { FileRequestAsync* request = AsyncHandler::RequestFile("BattleCharSet", sprite_file); - request_id = request->Bind(&Sprite_Battler::OnBattlercharsetReady, this, ext.battler_index); + request_id = request->Bind(&Sprite_Battler::OnBattlercharsetReady, this, ext->battler_index); request->Start(); } } diff --git a/src/spriteset_battle.cpp b/src/spriteset_battle.cpp index 9da1c7466f..e7a56abce8 100644 --- a/src/spriteset_battle.cpp +++ b/src/spriteset_battle.cpp @@ -31,10 +31,12 @@ Spriteset_Battle::Spriteset_Battle() { // Create background - if (!Game_Temp::battle_background.empty()) + if (!Game_Temp::battle_background.empty()) { background.reset(new Background(Game_Temp::battle_background)); - else + } else { + // Background verifies that the Terrain ID is valid background.reset(new Background(Game_Battle::GetTerrainId())); + } background_name = Game_Temp::battle_background; Game_Battle::ChangeBackground(background_name); diff --git a/src/window_actorinfo.cpp b/src/window_actorinfo.cpp index 0fcf4b0128..48ab9591e0 100644 --- a/src/window_actorinfo.cpp +++ b/src/window_actorinfo.cpp @@ -40,34 +40,35 @@ void Window_ActorInfo::Refresh() { } void Window_ActorInfo::DrawInfo() { - // Draw Row formation. std::string battle_row = Game_Actors::GetActor(actor_id)->GetBattleRow() == 1 ? "Back" : "Front"; contents->TextDraw(contents->GetWidth(), 5, Font::ColorDefault, battle_row, Text::AlignRight); + const Game_Actor& actor = *Game_Actors::GetActor(actor_id); + // Draw Face - DrawActorFace(Game_Actors::GetActor(actor_id), 0, 0); - + DrawActorFace(actor, 0, 0); + // Draw Name contents->TextDraw(3, 50, 1, "Name"); - DrawActorName(Game_Actors::GetActor(actor_id), 36, 65); - + DrawActorName(actor, 36, 65); + // Draw Profession contents->TextDraw(3, 80, 1, "Profession"); - DrawActorClass(Game_Actors::GetActor(actor_id), 36, 95); + DrawActorClass(actor, 36, 95); // Draw Rank contents->TextDraw(3, 110, 1, "Title"); - DrawActorTitle(Game_Actors::GetActor(actor_id), 36, 125); + DrawActorTitle(actor, 36, 125); // Draw Status contents->TextDraw(3, 140, 1, "Status"); - DrawActorState(Game_Actors::GetActor(actor_id), 36, 155); + DrawActorState(actor, 36, 155); //Draw Level contents->TextDraw(3, 170, 1, Data::terms.lvl_short); std::stringstream ss; - ss << Game_Actors::GetActor(actor_id)->GetLevel(); + ss << actor.GetLevel(); contents->TextDraw(79, 170, Font::ColorDefault, ss.str(), Text::AlignRight); } diff --git a/src/window_actortarget.cpp b/src/window_actortarget.cpp index 0ce85147e8..06fc45fa01 100644 --- a/src/window_actortarget.cpp +++ b/src/window_actortarget.cpp @@ -37,13 +37,15 @@ void Window_ActorTarget::Refresh() { int y = 0; for (int i = 0; i < item_max; ++i) { - DrawActorFace(Main_Data::game_party->GetActors()[i], 0, i * 48 + y); - DrawActorName(Main_Data::game_party->GetActors()[i], 48 + 8, i * 48 + 2 + y); - DrawActorLevel(Main_Data::game_party->GetActors()[i], 48 + 8, i * 48 + 2 + 16 + y); - DrawActorState(Main_Data::game_party->GetActors()[i], 48 + 8, i * 48 + 2 + 16 + 16 + y); + const Game_Actor& actor = *(Main_Data::game_party->GetActors()[i]); + + DrawActorFace(actor, 0, i * 48 + y); + DrawActorName(actor, 48 + 8, i * 48 + 2 + y); + DrawActorLevel(actor, 48 + 8, i * 48 + 2 + 16 + y); + DrawActorState(actor, 48 + 8, i * 48 + 2 + 16 + 16 + y); int x_offset = 48 + 8 + 42 + (Player::IsRPG2k() ? 16 : 0); - DrawActorHp(Main_Data::game_party->GetActors()[i], x_offset, i * 48 + 2 + 16 + y); - DrawActorSp(Main_Data::game_party->GetActors()[i], x_offset, i * 48 + 2 + 16 + 16 + y); + DrawActorHp(actor, x_offset, i * 48 + 2 + 16 + y); + DrawActorSp(actor, x_offset, i * 48 + 2 + 16 + 16 + y); y += 10; } diff --git a/src/window_base.cpp b/src/window_base.cpp index 4a06b4cfd4..b517786bfc 100644 --- a/src/window_base.cpp +++ b/src/window_base.cpp @@ -69,6 +69,8 @@ void Window_Base::OnFaceReady(FileRequestResult* result, int face_index, int cx, } } +// All these functions assume that the input is valid + void Window_Base::DrawFace(const std::string& face_name, int face_index, int cx, int cy, bool flip) { if (face_name.empty()) { return; } @@ -77,37 +79,35 @@ void Window_Base::DrawFace(const std::string& face_name, int face_index, int cx, request->Start(); } -void Window_Base::DrawActorFace(Game_Actor* actor, int cx, int cy) { - DrawFace(actor->GetFaceName(), actor->GetFaceIndex(), cx, cy); +void Window_Base::DrawActorFace(const Game_Actor& actor, int cx, int cy) { + DrawFace(actor.GetFaceName(), actor.GetFaceIndex(), cx, cy); } -void Window_Base::DrawActorName(Game_Battler* actor, int cx, int cy) { - contents->TextDraw(cx, cy, Font::ColorDefault, actor->GetName()); +void Window_Base::DrawActorName(const Game_Battler& actor, int cx, int cy) const { + contents->TextDraw(cx, cy, Font::ColorDefault, actor.GetName()); } -void Window_Base::DrawActorTitle(Game_Actor* actor, int cx, int cy) { - contents->TextDraw(cx, cy, Font::ColorDefault, actor->GetTitle()); +void Window_Base::DrawActorTitle(const Game_Actor& actor, int cx, int cy) const { + contents->TextDraw(cx, cy, Font::ColorDefault, actor.GetTitle()); } -void Window_Base::DrawActorClass(Game_Actor* actor, int cx, int cy) { - contents->TextDraw(cx, cy, Font::ColorDefault, actor->GetClassName()); +void Window_Base::DrawActorClass(const Game_Actor& actor, int cx, int cy) const { + contents->TextDraw(cx, cy, Font::ColorDefault, actor.GetClassName()); } -void Window_Base::DrawActorLevel(Game_Actor* actor, int cx, int cy) { +void Window_Base::DrawActorLevel(const Game_Actor& actor, int cx, int cy) const { // Draw LV-String contents->TextDraw(cx, cy, 1, Data::terms.lvl_short); // Draw Level of the Actor std::stringstream ss; - ss << actor->GetLevel(); + ss << actor.GetLevel(); contents->TextDraw(cx + 24, cy, Font::ColorDefault, ss.str(), Text::AlignRight); } -void Window_Base::DrawActorState(Game_Battler* actor, int cx, int cy) { - std::vector states = actor->GetStates(); - +void Window_Base::DrawActorState(const Game_Battler& actor, int cx, int cy) const { // Unit has Normal state if no state is set - const RPG::State* state = actor->GetSignificantState(); + const RPG::State* state = actor.GetSignificantState(); if (!state) { contents->TextDraw(cx, cy, Font::ColorDefault, Data::terms.normal_status); } else { @@ -115,7 +115,7 @@ void Window_Base::DrawActorState(Game_Battler* actor, int cx, int cy) { } } -void Window_Base::DrawActorExp(Game_Actor* actor, int cx, int cy) { +void Window_Base::DrawActorExp(const Game_Actor& actor, int cx, int cy) const { // Draw EXP-String if (Player::IsRPG2k()) { contents->TextDraw(cx, cy, 1, Data::terms.exp_short); @@ -124,17 +124,17 @@ void Window_Base::DrawActorExp(Game_Actor* actor, int cx, int cy) { // Current Exp of the Actor // ------/------ std::stringstream ss; - ss << std::setfill(' ') << std::setw(6) << actor->GetExpString(); + ss << std::setfill(' ') << std::setw(6) << actor.GetExpString(); // Delimiter ss << '/'; // Exp for Level up - ss << std::setfill(' ') << std::setw(6) << actor->GetNextExpString(); + ss << std::setfill(' ') << std::setw(6) << actor.GetNextExpString(); contents->TextDraw(cx + (Player::IsRPG2k() ? 12 : 0), cy, Font::ColorDefault, ss.str(), Text::AlignLeft); } -void Window_Base::DrawActorHp(Game_Battler* actor, int cx, int cy, bool draw_max) { +void Window_Base::DrawActorHp(const Game_Battler& actor, int cx, int cy, bool draw_max) const { // Draw HP-String contents->TextDraw(cx, cy, 1, Data::terms.hp_short); @@ -142,13 +142,13 @@ void Window_Base::DrawActorHp(Game_Battler* actor, int cx, int cy, bool draw_max cx += 12; // Color: 0 okay, 4 critical, 5 dead int color = Font::ColorDefault; - if (actor->GetHp() == 0) { + if (actor.GetHp() == 0) { color = Font::ColorKnockout; - } else if (actor->GetHp() <= actor->GetMaxHp() / 4) { + } else if (actor.GetHp() <= actor.GetMaxHp() / 4) { color = Font::ColorCritical; } std::stringstream ss; - ss << actor->GetHp(); + ss << actor.GetHp(); contents->TextDraw(cx + (Player::IsRPG2k() ? 3 : 4) * 6, cy, color, ss.str(), Text::AlignRight); if (!draw_max) @@ -161,11 +161,11 @@ void Window_Base::DrawActorHp(Game_Battler* actor, int cx, int cy, bool draw_max // Draw Max Hp cx += 6; ss.str(""); - ss << actor->GetMaxHp(); + ss << actor.GetMaxHp(); contents->TextDraw(cx + (Player::IsRPG2k() ? 3 : 4) * 6, cy, Font::ColorDefault, ss.str(), Text::AlignRight); } -void Window_Base::DrawActorSp(Game_Battler* actor, int cx, int cy, bool draw_max) { +void Window_Base::DrawActorSp(const Game_Battler& actor, int cx, int cy, bool draw_max) const { // Draw SP-String contents->TextDraw(cx, cy, 1, Data::terms.sp_short); @@ -173,11 +173,11 @@ void Window_Base::DrawActorSp(Game_Battler* actor, int cx, int cy, bool draw_max cx += 12; // Color: 0 okay, 4 critical/empty int color = Font::ColorDefault; - if (actor->GetMaxSp() != 0 && actor->GetSp() <= actor->GetMaxSp() / 4) { + if (actor.GetMaxSp() != 0 && actor.GetSp() <= actor.GetMaxSp() / 4) { color = Font::ColorCritical; } std::stringstream ss; - ss << actor->GetSp(); + ss << actor.GetSp(); contents->TextDraw(cx + 18, cy, color, ss.str(), Text::AlignRight); if (!draw_max) @@ -190,30 +190,30 @@ void Window_Base::DrawActorSp(Game_Battler* actor, int cx, int cy, bool draw_max // Draw Max Sp cx += 6; ss.str(""); - ss << actor->GetMaxSp(); + ss << actor.GetMaxSp(); contents->TextDraw(cx + 18, cy, Font::ColorDefault, ss.str(), Text::AlignRight); } -void Window_Base::DrawActorParameter(Game_Battler* actor, int cx, int cy, int type) { +void Window_Base::DrawActorParameter(const Game_Battler& actor, int cx, int cy, int type) const { std::string name; int value; switch (type) { case 0: name = Data::terms.attack; - value = actor->GetAtk(); + value = actor.GetAtk(); break; case 1: name = Data::terms.defense; - value = actor->GetDef(); + value = actor.GetDef(); break; case 2: name = Data::terms.spirit; - value = actor->GetSpi(); + value = actor.GetSpi(); break; case 3: name = Data::terms.agility; - value = actor->GetAgi(); + value = actor.GetAgi(); break; default: return; @@ -228,7 +228,7 @@ void Window_Base::DrawActorParameter(Game_Battler* actor, int cx, int cy, int ty contents->TextDraw(cx + 78, cy, Font::ColorDefault, ss.str(), Text::AlignRight); } -void Window_Base::DrawEquipmentType(Game_Actor* actor, int cx, int cy, int type) { +void Window_Base::DrawEquipmentType(const Game_Actor& actor, int cx, int cy, int type) const { std::string name; switch (type) { @@ -236,7 +236,7 @@ void Window_Base::DrawEquipmentType(Game_Actor* actor, int cx, int cy, int type) name = Data::terms.weapon; break; case 1: - if (actor->HasTwoWeapons()) { + if (actor.HasTwoWeapons()) { name = Data::terms.weapon; } else { name = Data::terms.shield; @@ -258,17 +258,19 @@ void Window_Base::DrawEquipmentType(Game_Actor* actor, int cx, int cy, int type) contents->TextDraw(cx, cy, 1, name); } -void Window_Base::DrawItemName(RPG::Item* item, int cx, int cy, bool enabled) { +void Window_Base::DrawItemName(const RPG::Item& item, int cx, int cy, bool enabled) const { int color = enabled ? Font::ColorDefault : Font::ColorDisabled; - contents->TextDraw(cx, cy, color, item->name); + + contents->TextDraw(cx, cy, color, item.name); } -void Window_Base::DrawSkillName(RPG::Skill* skill, int cx, int cy, bool enabled) { +void Window_Base::DrawSkillName(const RPG::Skill& skill, int cx, int cy, bool enabled) const { int color = enabled ? Font::ColorDefault : Font::ColorDisabled; - contents->TextDraw(cx, cy, color, skill->name); + + contents->TextDraw(cx, cy, color, skill.name); } -void Window_Base::DrawCurrencyValue(int money, int cx, int cy) { +void Window_Base::DrawCurrencyValue(int money, int cx, int cy) const { // This function draws right aligned because of the dynamic with of the // gold output (cx and cy define the right border) std::stringstream gold; @@ -280,7 +282,7 @@ void Window_Base::DrawCurrencyValue(int money, int cx, int cy) { contents->TextDraw(cx - gold_text_size.width, cy, Font::ColorDefault, gold.str(), Text::AlignRight); } -void Window_Base::DrawGauge(Game_Battler* actor, int cx, int cy) { +void Window_Base::DrawGauge(const Game_Battler& actor, int cx, int cy) const { FileRequestAsync* request = AsyncHandler::RequestFile("System2", Data::system.system2_name); if (!request->IsReady()) { // Gauge refreshed each frame, so we can wait via polling @@ -290,8 +292,8 @@ void Window_Base::DrawGauge(Game_Battler* actor, int cx, int cy) { BitmapRef system2 = Cache::System2(Data::system.system2_name); - bool full = actor->IsGaugeFull(); - int gauge_w = actor->GetGauge() / 4; + bool full = actor.IsGaugeFull(); + int gauge_w = actor.GetGauge() / 4; // Which gauge (0 - 2) int gauge_y = 32 + 2 * 16; diff --git a/src/window_base.h b/src/window_base.h index b8a7d0b8a0..f44b763234 100644 --- a/src/window_base.h +++ b/src/window_base.h @@ -51,21 +51,21 @@ class Window_Base : public Window { */ /** @{ */ void DrawFace(const std::string& face_name, int face_index, int cx, int cy, bool flip = false); - void DrawActorFace(Game_Actor* actor, int cx, int cy); - void DrawActorName(Game_Battler* actor, int cx, int cy); - void DrawActorTitle(Game_Actor* actor, int cx, int cy); - void DrawActorClass(Game_Actor* actor, int cx, int cy); - void DrawActorLevel(Game_Actor* actor, int cx, int cy); - void DrawActorState(Game_Battler* actor, int cx, int cy); - void DrawActorExp(Game_Actor* actor, int cx, int cy); - void DrawActorHp(Game_Battler* actor, int cx, int cy, bool draw_max = true); - void DrawActorSp(Game_Battler* actor, int cx, int cy, bool draw_max = true); - void DrawActorParameter(Game_Battler* actor, int cx, int cy, int type); - void DrawEquipmentType(Game_Actor* actor, int cx, int cy, int type); - void DrawItemName(RPG::Item* item, int cx, int cy, bool enabled = true); - void DrawSkillName(RPG::Skill* skill, int cx, int cy, bool enabled = true); - void DrawCurrencyValue(int money, int cx, int cy); - void DrawGauge(Game_Battler* actor, int cx, int cy); + void DrawActorFace(const Game_Actor& actor, int cx, int cy); + void DrawActorName(const Game_Battler& actor, int cx, int cy) const; + void DrawActorTitle(const Game_Actor& actor, int cx, int cy) const; + void DrawActorClass(const Game_Actor& actor, int cx, int cy) const; + void DrawActorLevel(const Game_Actor& actor, int cx, int cy) const; + void DrawActorState(const Game_Battler& actor, int cx, int cy) const; + void DrawActorExp(const Game_Actor& actor, int cx, int cy) const; + void DrawActorHp(const Game_Battler& actor, int cx, int cy, bool draw_max = true) const; + void DrawActorSp(const Game_Battler& actor, int cx, int cy, bool draw_max = true) const; + void DrawActorParameter(const Game_Battler& actor, int cx, int cy, int type) const; + void DrawEquipmentType(const Game_Actor& actor, int cx, int cy, int type) const; + void DrawItemName(const RPG::Item& item, int cx, int cy, bool enabled = true) const; + void DrawSkillName(const RPG::Skill& skill, int cx, int cy, bool enabled = true) const; + void DrawCurrencyValue(int money, int cx, int cy) const; + void DrawGauge(const Game_Battler& actor, int cx, int cy) const; /** @} */ /** diff --git a/src/window_battlestatus.cpp b/src/window_battlestatus.cpp index 2fbdfc350c..cd0dea354e 100644 --- a/src/window_battlestatus.cpp +++ b/src/window_battlestatus.cpp @@ -63,7 +63,8 @@ void Window_BattleStatus::Refresh() { item_max = std::min(item_max, 4); for (int i = 0; i < item_max; i++) { - Game_Battler* actor; + // The party only contains valid battlers + const Game_Battler* actor; if (enemy) { actor = &(*Main_Data::game_enemyparty)[i]; } @@ -79,16 +80,16 @@ void Window_BattleStatus::Refresh() { break; } else { - DrawActorFace(static_cast(actor), 80 * i, 24); + DrawActorFace(*static_cast(actor), 80 * i, 24); } } else { int y = 2 + i * 16; - DrawActorName(actor, 4, y); - DrawActorState(actor, 84, y); - DrawActorHp(actor, 126, y, true); - DrawActorSp(actor, 198, y, false); + DrawActorName(*actor, 4, y); + DrawActorState(*actor, 84, y); + DrawActorHp(*actor, 126, y, true); + DrawActorSp(*actor, 198, y, false); } } @@ -102,6 +103,7 @@ void Window_BattleStatus::RefreshGauge() { } for (int i = 0; i < item_max; ++i) { + // The party always contains valid battlers Game_Battler* actor; if (enemy) { actor = &(*Main_Data::game_enemyparty)[i]; @@ -119,7 +121,7 @@ void Window_BattleStatus::RefreshGauge() { } else { BitmapRef system2 = Cache::System2(Data::system.system2_name); - + // Clear number drawing area contents->ClearRect(Rect(40 + 80 * i, 24, 8 * 4, 16)); contents->ClearRect(Rect(40 + 80 * i, 24 + 12 + 4, 8 * 4, 16)); @@ -127,7 +129,7 @@ void Window_BattleStatus::RefreshGauge() { // Number clearing removed part of the face, but both, clear and redraw // are needed because some games don't have face graphics that are huge enough // to clear the number area (e.g. Ara Fell) - DrawActorFace(static_cast(actor), 80 * i, 24); + DrawActorFace(*static_cast(actor), 80 * i, 24); // Background contents->StretchBlit(Rect(32 + i * 80, 24, 57, 48), *system2, Rect(0, 32, 48, 48), Opacity::opaque); @@ -138,7 +140,7 @@ void Window_BattleStatus::RefreshGauge() { DrawGaugeSystem2(48 + i * 80, 24 + 16, actor->GetSp(), actor->GetMaxSp(), 1); // Gauge DrawGaugeSystem2(48 + i * 80, 24 + 16 * 2, actor->GetGauge() * actor->GetMaxGauge() / 100, actor->GetMaxGauge(), 2); - + // Numbers DrawNumberSystem2(40 + 80 * i, 24, actor->GetHp()); DrawNumberSystem2(40 + 80 * i, 24 + 12 + 4, actor->GetSp()); @@ -147,8 +149,8 @@ void Window_BattleStatus::RefreshGauge() { else { int y = 2 + i * 16; - DrawGauge(actor, 198 - 10, y - 2); - DrawActorSp(actor, 198, y, false); + DrawGauge(*actor, 198 - 10, y - 2); + DrawActorSp(*actor, 198, y, false); } } } diff --git a/src/window_equip.cpp b/src/window_equip.cpp index a90de458fa..7003feef2a 100644 --- a/src/window_equip.cpp +++ b/src/window_equip.cpp @@ -19,6 +19,8 @@ #include "window_equip.h" #include "game_actors.h" #include "bitmap.h" +#include "reader_util.h" +#include "output.h" Window_Equip::Window_Equip(int ix, int iy, int iwidth, int iheight, int actor_id) : Window_Selectable(ix, iy, iwidth, iheight), @@ -49,13 +51,15 @@ void Window_Equip::Refresh() { // Draw equipment text for (int i = 0; i < 5; ++i) { - DrawEquipmentType(actor, 0, (12 + 4) * i + 2, i); + DrawEquipmentType(*actor, 0, (12 + 4) * i + 2, i); if (data[i] > 0) { - DrawItemName(&Data::items[data[i] - 1], 60, (12 + 4) * i + 2); + // Equipment and items are guaranteed to be valid + DrawItemName(*ReaderUtil::GetElement(Data::items, data[i]), 60, (12 + 4) * i + 2); } } } void Window_Equip::UpdateHelp() { - help_window->SetText(GetItemId() == 0 ? "" : Data::items[GetItemId() - 1].description); + help_window->SetText(GetItemId() == 0 ? "" : + ReaderUtil::GetElement(Data::items, GetItemId())->description); } diff --git a/src/window_equipitem.cpp b/src/window_equipitem.cpp index a4d9da6c48..2a876c3c1f 100644 --- a/src/window_equipitem.cpp +++ b/src/window_equipitem.cpp @@ -19,6 +19,8 @@ #include "window_equipitem.h" #include "game_actors.h" #include "game_party.h" +#include "reader_util.h" +#include "output.h" Window_EquipItem::Window_EquipItem(int actor_id, int equip_type) : Window_Item(0, 128, SCREEN_TARGET_WIDTH, (SCREEN_TARGET_HEIGHT-128)), @@ -43,21 +45,24 @@ bool Window_EquipItem::CheckInclude(int item_id) { bool result = false; + // Equipment and items are guaranteed to be valid + RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + switch (equip_type) { case Window_EquipItem::weapon: - result = (Data::items[item_id - 1].type == RPG::Item::Type_weapon); + result = item->type == RPG::Item::Type_weapon; break; case Window_EquipItem::shield: - result = (Data::items[item_id - 1].type == RPG::Item::Type_shield); + result = item->type == RPG::Item::Type_shield; break; case Window_EquipItem::armor: - result = (Data::items[item_id - 1].type == RPG::Item::Type_armor); + result = item->type == RPG::Item::Type_armor; break; case Window_EquipItem::helmet: - result = (Data::items[item_id - 1].type == RPG::Item::Type_helmet); + result = item->type == RPG::Item::Type_helmet; break; case Window_EquipItem::other: - result = (Data::items[item_id - 1].type == RPG::Item::Type_accessory); + result = item->type == RPG::Item::Type_accessory; break; default: return false; diff --git a/src/window_equipstatus.cpp b/src/window_equipstatus.cpp index 94637843bb..69a253c1a5 100644 --- a/src/window_equipstatus.cpp +++ b/src/window_equipstatus.cpp @@ -43,7 +43,8 @@ void Window_EquipStatus::Refresh() { if (draw_actorname) { y_offset = 18; - DrawActorName(Game_Actors::GetActor(actor_id), 0, 2); + // Actor data is guaranteed to be valid + DrawActorName(*Game_Actors::GetActor(actor_id), 0, 2); } else { y_offset = 2; } diff --git a/src/window_face.cpp b/src/window_face.cpp index 72ba9dfee5..067d992a20 100644 --- a/src/window_face.cpp +++ b/src/window_face.cpp @@ -28,7 +28,8 @@ Window_Face::Window_Face(int ix, int iy, int iwidth, int iheight) : void Window_Face::Refresh() { contents->Clear(); - DrawActorFace(Game_Actors::GetActor(actor_id), 0, 0); + // Actor data is guaranteed to be valid + DrawActorFace(*Game_Actors::GetActor(actor_id), 0, 0); } void Window_Face::Set(int id) { diff --git a/src/window_item.cpp b/src/window_item.cpp index bde612fcad..941972e062 100644 --- a/src/window_item.cpp +++ b/src/window_item.cpp @@ -23,6 +23,8 @@ #include "bitmap.h" #include "font.h" #include "game_temp.h" +#include "reader_util.h" +#include "output.h" Window_Item::Window_Item(int ix, int iy, int iwidth, int iheight) : Window_Selectable(ix, iy, iwidth, iheight) { @@ -30,11 +32,11 @@ Window_Item::Window_Item(int ix, int iy, int iwidth, int iheight) : } const RPG::Item* Window_Item::GetItem() const { - if (index < 0 || index >= (int)Data::items.size() || data[index] == 0) { - return NULL; + if (index < 0) { + return nullptr; } - return &Data::items[data[index] - 1]; + return ReaderUtil::GetElement(Data::items, data[index]); } bool Window_Item::CheckInclude(int item_id) { @@ -81,10 +83,10 @@ void Window_Item::Refresh() { item_max = data.size(); CreateContents(); - + if (index > 0 && index >= item_max) { --index; - } + } contents->Clear(); @@ -102,15 +104,16 @@ void Window_Item::DrawItem(int index) { if (item_id > 0) { int number = Main_Data::game_party->GetItemCount(item_id); + // Items are guaranteed to be valid + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); if (actor) { - const RPG::Item &item = Data::items[item_id - 1]; - if (item.use_skill) { + if (item->use_skill) { number += actor->GetItemCount(item_id); } } bool enabled = CheckEnable(item_id); - DrawItemName(&Data::items[item_id - 1], rect.x, rect.y, enabled); + DrawItemName(*item, rect.x, rect.y, enabled); std::stringstream ss; ss << number; @@ -122,7 +125,7 @@ void Window_Item::DrawItem(int index) { void Window_Item::UpdateHelp() { help_window->SetText(GetItem() == NULL ? "" : - Data::items[GetItem()->ID - 1].description); + GetItem()->description); } void Window_Item::SetActor(Game_Actor * actor) { diff --git a/src/window_menustatus.cpp b/src/window_menustatus.cpp index 28abc31b54..13e96e644a 100644 --- a/src/window_menustatus.cpp +++ b/src/window_menustatus.cpp @@ -38,13 +38,13 @@ void Window_MenuStatus::Refresh() { item_max = Main_Data::game_party->GetActors().size(); int y = 0; - for (int i = 0; i < item_max; ++i) - { - Game_Actor* actor = Main_Data::game_party->GetActors()[i]; + for (int i = 0; i < item_max; ++i) { + // The party always contains valid battlers + const Game_Actor& actor = *(Main_Data::game_party->GetActors()[i]); int face_x = 0; if (Player::IsRPG2k3()) { - face_x = actor->GetBattleRow() == 1 ? 5 : 0; + face_x = actor.GetBattleRow() == 1 ? 5 : 0; } DrawActorFace(actor, face_x, i*48 + y); diff --git a/src/window_shopbuy.cpp b/src/window_shopbuy.cpp index 9367907e8f..26b2755177 100644 --- a/src/window_shopbuy.cpp +++ b/src/window_shopbuy.cpp @@ -25,6 +25,8 @@ #include "game_party.h" #include "bitmap.h" #include "font.h" +#include "output.h" +#include "reader_util.h" Window_ShopBuy::Window_ShopBuy(int ix, int iy, int iwidth, int iheight) : Window_Selectable(ix, iy, iwidth, iheight) { @@ -56,24 +58,44 @@ void Window_ShopBuy::Refresh() { void Window_ShopBuy::DrawItem(int index) { int item_id = data[index]; - bool enabled = Data::items[item_id - 1].price <= Main_Data::game_party->GetGold(); + + // (Shop) items are guaranteed to be valid + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + + int price = 0; + bool enabled = false; + + if (!item) { + Output::Warning("Window ShopBuy: Invalid item ID %d", item_id); + } else { + enabled = item->price <= Main_Data::game_party->GetGold(); + price = item->price; + } + Rect rect = GetItemRect(index); contents->ClearRect(rect); - DrawItemName(&Data::items[item_id - 1], rect.x, rect.y, enabled); + DrawItemName(*item, rect.x, rect.y, enabled); - std::stringstream ss; - ss << Data::items[item_id - 1].price; - contents->TextDraw(rect.width + 4, rect.y, enabled ? Font::ColorDefault : Font::ColorDisabled, ss.str(), Text::AlignRight); + std::string str = Utils::ToString(price); + contents->TextDraw(rect.width + 4, rect.y, enabled ? Font::ColorDefault : Font::ColorDisabled, str, Text::AlignRight); } void Window_ShopBuy::UpdateHelp() { - help_window->SetText(GetItemId() == 0 ? "" : - Data::items[GetItemId() - 1].description); + std::string help_text = "??? BAD ITEM ???"; + const RPG::Item* item = ReaderUtil::GetElement(Data::items, data[index]); + if (item) { + help_text = item->description; + } + + help_window->SetText(help_text); } bool Window_ShopBuy::CheckEnable(int item_id) { - return ( - item_id > 0 && - Data::items[item_id - 1].price <= Main_Data::game_party->GetGold() && + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + if (!item) { + return false; + } + + return (item->price <= Main_Data::game_party->GetGold() && Main_Data::game_party->GetItemCount(item_id) < 99); } diff --git a/src/window_shopnumber.cpp b/src/window_shopnumber.cpp index e9b36f2118..0fc6758f17 100644 --- a/src/window_shopnumber.cpp +++ b/src/window_shopnumber.cpp @@ -23,6 +23,7 @@ #include "window_shopnumber.h" #include "bitmap.h" #include "font.h" +#include "reader_util.h" Window_ShopNumber::Window_ShopNumber(int ix, int iy, int iwidth, int iheight) : Window_Base(ix, iy, iwidth, iheight), @@ -43,7 +44,8 @@ void Window_ShopNumber::Refresh() { contents->Clear(); int y = 34; - DrawItemName(&Data::items[item_id - 1], 0, y); + // (Shop) items are guaranteed to be valid + DrawItemName(*ReaderUtil::GetElement(Data::items, item_id), 0, y); std::stringstream ss; ss << number; diff --git a/src/window_shopparty.cpp b/src/window_shopparty.cpp index 3f7574b221..a37ae069c0 100644 --- a/src/window_shopparty.cpp +++ b/src/window_shopparty.cpp @@ -21,6 +21,8 @@ #include "game_party.h" #include "game_actor.h" #include "window_shopparty.h" +#include "output.h" +#include "reader_util.h" Window_ShopParty::Window_ShopParty(int ix, int iy, int iwidth, int iheight) : Window_Base(ix, iy, iwidth, iheight) { @@ -64,7 +66,7 @@ void Window_ShopParty::Refresh() { } if (equippable) { - //check if item is equipped by each member + // check if item is equipped by each member bool is_equipped = false; for (int j = 1; j <= 5; ++j) { const RPG::Item* item = actor->GetEquipment(j); @@ -75,47 +77,37 @@ void Window_ShopParty::Refresh() { if (is_equipped) contents->Blit(i * 32 + 20, 24, *system, Rect(128 + 8 * phase, 24, 8, 8), 255); else { + // (Shop) items are guaranteed to be valid + const RPG::Item* new_item = ReaderUtil::GetElement(Data::items, item_id); - RPG::Item* new_item = &Data::items[item_id - 1]; - int item_type = new_item->type; - RPG::Item* current_item = NULL; + int item_type = new_item->type; + const RPG::Item* current_item = nullptr; switch (item_type) { - - //get the current equipped item + // get the current equipped item case RPG::Item::Type_weapon: if (actor->GetWeaponId() > 0) - current_item = &Data::items[actor->GetWeaponId() - 1]; - else - current_item = &Data::items[0]; + current_item = ReaderUtil::GetElement(Data::items, actor->GetWeaponId()); break; case RPG::Item::Type_helmet: if (actor->GetHelmetId() > 0) - current_item = &Data::items[actor->GetHelmetId() - 1]; - else - current_item = &Data::items[0]; + current_item = ReaderUtil::GetElement(Data::items, actor->GetHelmetId()); break; case RPG::Item::Type_shield: if (actor->GetShieldId() > 0) - current_item = &Data::items[actor->GetShieldId() - 1]; - else - current_item = &Data::items[0]; + current_item = ReaderUtil::GetElement(Data::items, actor->GetShieldId()); break; case RPG::Item::Type_armor: if (actor->GetArmorId() > 0) - current_item = &Data::items[actor->GetArmorId() - 1]; - else - current_item = &Data::items[0]; + current_item = ReaderUtil::GetElement(Data::items, actor->GetArmorId()); break; case RPG::Item::Type_accessory: if (actor->GetAccessoryId() > 0) - current_item = &Data::items[actor->GetAccessoryId() -1]; - else - current_item = &Data::items[0]; + current_item = ReaderUtil::GetElement(Data::items, actor->GetAccessoryId()); break; } - if (current_item != NULL) { + if (current_item != nullptr) { int diff_atk = new_item->atk_points1 - current_item->atk_points1; int diff_def = new_item->def_points1 - current_item->def_points1; int diff_spi = new_item->spi_points1 - current_item->spi_points1; diff --git a/src/window_shopsell.cpp b/src/window_shopsell.cpp index 56521886cb..fe64946716 100644 --- a/src/window_shopsell.cpp +++ b/src/window_shopsell.cpp @@ -20,10 +20,16 @@ #include #include "window_shopsell.h" #include "game_party.h" +#include "output.h" +#include "reader_util.h" Window_ShopSell::Window_ShopSell(int ix, int iy, int iwidth, int iheight) : Window_Item(ix, iy, iwidth, iheight) {} bool Window_ShopSell::CheckEnable(int item_id) { - return Data::items[item_id - 1].price > 0; + // Items are guaranteed to be valid + + const RPG::Item* item = ReaderUtil::GetElement(Data::items, item_id); + + return item->price > 0; } diff --git a/src/window_skill.cpp b/src/window_skill.cpp index 6bac539956..d40c01bd8c 100644 --- a/src/window_skill.cpp +++ b/src/window_skill.cpp @@ -26,6 +26,8 @@ #include "bitmap.h" #include "font.h" #include "player.h" +#include "output.h" +#include "reader_util.h" Window_Skill::Window_Skill(int ix, int iy, int iwidth, int iheight) : Window_Selectable(ix, iy, iwidth, iheight), actor_id(-1), subset(0) { @@ -38,11 +40,11 @@ void Window_Skill::SetActor(int actor_id) { } const RPG::Skill* Window_Skill::GetSkill() const { - if (index < 0 || index >= (int)Data::skills.size() || data[index] == 0) { - return NULL; + if (index < 0) { + return nullptr; } - return &Data::skills[data[index] - 1]; + return ReaderUtil::GetElement(Data::skills, data[index]); } void Window_Skill::Refresh() { @@ -87,7 +89,8 @@ void Window_Skill::DrawItem(int index) { contents->TextDraw(rect.x + rect.width - 28, rect.y, color, "-"); contents->TextDraw(rect.x + rect.width - 6, rect.y, color, ss.str(), Text::AlignRight); - DrawSkillName(&Data::skills[skill_id - 1], rect.x, rect.y, enabled); + // Skills are guaranteed to be valid + DrawSkillName(*ReaderUtil::GetElement(Data::skills, skill_id), rect.x, rect.y, enabled); } } @@ -105,7 +108,17 @@ bool Window_Skill::CheckInclude(int skill_id) { return true; } else { - return subset == 0 || Data::skills[skill_id - 1].type == subset; + if (subset == 0) { + return true; + } + + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, skill_id); + if (skill) { + return skill->type == subset; + } + + Output::Warning("Window Skill: Invalid skill ID %d", skill_id); + return false; } } diff --git a/src/window_skillstatus.cpp b/src/window_skillstatus.cpp index f272b8ab8e..923e8fe5b5 100644 --- a/src/window_skillstatus.cpp +++ b/src/window_skillstatus.cpp @@ -36,7 +36,8 @@ void Window_SkillStatus::SetActor(int actor_id) { void Window_SkillStatus::Refresh() { contents->ClearRect(Rect(0, 0, contents->GetWidth(), 16)); - Game_Actor* actor = Game_Actors::GetActor(actor_id); + // Actors are guaranteed to be valid + const Game_Actor& actor = *Game_Actors::GetActor(actor_id); DrawActorName(actor, 0, 0); DrawActorLevel(actor, 80, 0); diff --git a/src/window_targetstatus.cpp b/src/window_targetstatus.cpp index 05bb650166..d1583eaad3 100644 --- a/src/window_targetstatus.cpp +++ b/src/window_targetstatus.cpp @@ -21,6 +21,7 @@ #include "game_party.h" #include "bitmap.h" #include "font.h" +#include "reader_util.h" Window_TargetStatus::Window_TargetStatus(int ix, int iy, int iwidth, int iheight) : Window_Base(ix, iy, iwidth, iheight), id(-1), use_item(false) { @@ -41,15 +42,17 @@ void Window_TargetStatus::Refresh() { contents->TextDraw(0, 0, 1, Data::terms.sp_cost); } - std::stringstream ss; + // Scene_ActorTarget validates items and skills + std::string str; if (use_item) { - ss << Main_Data::game_party->GetItemCount(id); + str = Utils::ToString(Main_Data::game_party->GetItemCount(id)); } else { - ss << Data::skills[id - 1].sp_cost; + const RPG::Skill* skill = ReaderUtil::GetElement(Data::skills, id); + str = skill->sp_cost; } FontRef font = Font::Default(); - contents->TextDraw(contents->GetWidth() - font->GetSize(ss.str()).width, 0, Font::ColorDefault, ss.str(), Text::AlignRight); + contents->TextDraw(contents->GetWidth() - font->GetSize(str).width, 0, Font::ColorDefault, str, Text::AlignRight); } void Window_TargetStatus::SetData(int id, bool is_item) {