mirror of
https://github.com/libretro/easyrpg-libretro.git
synced 2026-11-10 02:41:35 -10:00
797 lines
21 KiB
C++
797 lines
21 KiB
C++
/*
|
|
* This file is part of EasyRPG Player.
|
|
*
|
|
* EasyRPG Player is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* EasyRPG Player is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// Headers
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include "system.h"
|
|
#include "game_party.h"
|
|
#include "game_actors.h"
|
|
#include "game_map.h"
|
|
#include "game_player.h"
|
|
#include "game_battle.h"
|
|
#include "game_targets.h"
|
|
#include "game_system.h"
|
|
#include "scene_battle.h"
|
|
#include <lcf/reader_util.h>
|
|
#include "output.h"
|
|
#include "algo.h"
|
|
|
|
Game_Party::Game_Party() {
|
|
}
|
|
|
|
void Game_Party::SetupNewGame() {
|
|
Clear();
|
|
|
|
data.party = lcf::Data::system.party;
|
|
RemoveInvalidData();
|
|
}
|
|
|
|
void Game_Party::SetupFromSave(lcf::rpg::SaveInventory save) {
|
|
data = std::move(save);
|
|
RemoveInvalidData();
|
|
|
|
// Old versions of player didn't sort the inventory, this ensures inventory is sorted
|
|
// as our Game_Party code relies on that. Items in RPG_RT are always sorted in the inventory.
|
|
if (!std::is_sorted(data.item_ids.begin(), data.item_ids.end())) {
|
|
Output::Debug("Loaded Save Game with unsorted inventory! Sorting ...");
|
|
// Resort the inventory.
|
|
struct ItemData { int id; int count; int usage; };
|
|
|
|
auto& ids = data.item_ids;
|
|
auto& counts = data.item_counts;
|
|
auto& usages = data.item_usage;
|
|
|
|
auto num_items = std::min(ids.size(), std::min(counts.size(), usages.size()));
|
|
std::vector<ItemData> items;
|
|
for (size_t i = 0; i < num_items; ++i) {
|
|
items.push_back(ItemData{ids[i], counts[i], usages[i]});
|
|
}
|
|
|
|
std::sort(items.begin(), items.end(), [](const ItemData& l, const ItemData& r) { return l.id < r.id; });
|
|
|
|
ids.clear();
|
|
counts.clear();
|
|
usages.clear();
|
|
|
|
for (auto& itd: items) {
|
|
ids.push_back(itd.id);
|
|
counts.push_back(itd.count);
|
|
usages.push_back(itd.usage);
|
|
}
|
|
}
|
|
}
|
|
|
|
Game_Actor& Game_Party::operator[] (const int index) {
|
|
std::vector<Game_Actor*> actors = GetActors();
|
|
|
|
if (index < 0 || (size_t)index >= actors.size()) {
|
|
assert(false && "Subscript out of range");
|
|
}
|
|
|
|
return *actors[index];
|
|
}
|
|
|
|
int Game_Party::GetBattlerCount() const {
|
|
return (int)GetActors().size();
|
|
}
|
|
|
|
int Game_Party::GetVisibleBattlerCount() const {
|
|
int visible = 0;
|
|
for (const auto& actor: GetActors()) {
|
|
visible += !actor->IsHidden();
|
|
}
|
|
return visible;
|
|
}
|
|
|
|
void Game_Party::SetupBattleTest() {
|
|
Clear();
|
|
|
|
for (auto& btdata : lcf::Data::system.battletest_data) {
|
|
AddActor(btdata.actor_id);
|
|
Game_Actor* actor = Main_Data::game_actors->GetActor(btdata.actor_id);
|
|
|
|
// Filter garbage btdata inserted by the editor
|
|
// The upper 16 bit look like uninitialized data
|
|
std::array<int, 5> ids = {{
|
|
btdata.weapon_id & 0xFFFF,
|
|
btdata.shield_id & 0xFFFF,
|
|
btdata.armor_id & 0xFFFF,
|
|
btdata.helmet_id & 0xFFFF,
|
|
btdata.accessory_id & 0xFFFF }};
|
|
std::replace_if(ids.begin(), ids.end(), [] (const int& item_id) {
|
|
return lcf::ReaderUtil::GetElement(lcf::Data::items, item_id) == nullptr;
|
|
}, 0);
|
|
|
|
actor->SetEquipment(lcf::rpg::Item::Type_weapon, ids[0]);
|
|
actor->SetEquipment(lcf::rpg::Item::Type_shield, ids[1]);
|
|
actor->SetEquipment(lcf::rpg::Item::Type_armor, ids[2]);
|
|
actor->SetEquipment(lcf::rpg::Item::Type_helmet, ids[3]);
|
|
actor->SetEquipment(lcf::rpg::Item::Type_accessory, ids[4]);
|
|
actor->ChangeLevel(btdata.level, nullptr);
|
|
actor->SetHp(actor->GetMaxHp());
|
|
actor->SetSp(actor->GetMaxSp());
|
|
}
|
|
}
|
|
|
|
void Game_Party::GetItems(std::vector<int>& item_list) {
|
|
item_list.clear();
|
|
|
|
std::vector<int16_t>::iterator it;
|
|
for (it = data.item_ids.begin(); it != data.item_ids.end(); ++it)
|
|
item_list.push_back(*it);
|
|
}
|
|
|
|
int Game_Party::GetItemCount(int item_id) const {
|
|
auto ip = GetItemIndex(item_id);
|
|
return ip.second ? data.item_counts[ip.first] : 0;
|
|
}
|
|
|
|
int Game_Party::GetEquippedItemCount(int item_id) const {
|
|
int number = 0;
|
|
if (item_id >= 0) {
|
|
for (int i = 0; i < (int) data.party.size(); i++) {
|
|
Game_Actor* actor = Main_Data::game_actors->GetActor(data.party[i]);
|
|
number += actor->GetItemCount(item_id);
|
|
}
|
|
}
|
|
return number;
|
|
}
|
|
|
|
int Game_Party::GetItemTotalCount(int item_id) const {
|
|
return GetItemCount(item_id) + GetEquippedItemCount(item_id);
|
|
}
|
|
|
|
int Game_Party::GetMaxItemCount(int item_id) const {
|
|
const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
|
|
if (!item || item->easyrpg_max_count == -1) {
|
|
return (lcf::Data::system.easyrpg_max_item_count == -1 ? 99 : lcf::Data::system.easyrpg_max_item_count);
|
|
} else {
|
|
return item->easyrpg_max_count;
|
|
}
|
|
}
|
|
|
|
void Game_Party::GainGold(int n) {
|
|
data.gold = data.gold + n;
|
|
data.gold = std::min<int32_t>(std::max<int32_t>(data.gold, 0), 999999);
|
|
}
|
|
|
|
void Game_Party::LoseGold(int n) {
|
|
data.gold = data.gold - n;
|
|
data.gold = std::min<int32_t>(std::max<int32_t>(data.gold, 0), 999999);
|
|
}
|
|
|
|
void Game_Party::AddItem(int item_id, int amount) {
|
|
if (item_id < 1 || item_id > (int) lcf::Data::items.size()) {
|
|
Output::Debug("Can't add item to party. {} is not a valid item ID.", item_id);
|
|
return;
|
|
}
|
|
|
|
int item_limit = GetMaxItemCount(item_id);
|
|
|
|
auto ip = GetItemIndex(item_id);
|
|
auto idx = ip.first;
|
|
auto has = ip.second;
|
|
if (!has) {
|
|
if (amount > 0) {
|
|
amount = std::min(amount, item_limit);
|
|
data.item_ids.insert(data.item_ids.begin() + idx, (int16_t)item_id);
|
|
data.item_counts.insert(data.item_counts.begin() + idx, (uint8_t)amount);
|
|
data.item_usage.insert(data.item_usage.begin() + idx, 0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
int total_items = data.item_counts[idx] + amount;
|
|
|
|
if (total_items <= 0) {
|
|
data.item_ids.erase(data.item_ids.begin() + idx);
|
|
data.item_counts.erase(data.item_counts.begin() + idx);
|
|
data.item_usage.erase(data.item_usage.begin() + idx);
|
|
return;
|
|
}
|
|
|
|
data.item_counts[idx] = (uint8_t)std::min(total_items, item_limit);
|
|
// If the item was removed, the number of uses resets.
|
|
// (Adding an item never changes the number of uses, even when
|
|
// you already have x99 of them.)
|
|
if (amount < 0) {
|
|
data.item_usage[idx] = 0;
|
|
}
|
|
}
|
|
|
|
void Game_Party::RemoveItem(int item_id, int amount) {
|
|
AddItem(item_id, -amount);
|
|
}
|
|
|
|
void Game_Party::ConsumeItemUse(int item_id) {
|
|
const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
|
|
|
|
if (!item) {
|
|
Output::Warning("ConsumeItemUse: Invalid item ID {}.", item_id);
|
|
return;
|
|
}
|
|
|
|
switch (item->type) {
|
|
case lcf::rpg::Item::Type_normal:
|
|
case lcf::rpg::Item::Type_weapon:
|
|
case lcf::rpg::Item::Type_shield:
|
|
case lcf::rpg::Item::Type_armor:
|
|
case lcf::rpg::Item::Type_helmet:
|
|
case lcf::rpg::Item::Type_accessory:
|
|
return;
|
|
}
|
|
|
|
if (item->uses == 0) {
|
|
// Unlimited uses
|
|
return;
|
|
}
|
|
|
|
auto ip = GetItemIndex(item_id);
|
|
auto idx = ip.first;
|
|
auto has = ip.second;
|
|
|
|
if (!has) {
|
|
return;
|
|
}
|
|
|
|
data.item_usage[idx]++;
|
|
|
|
if (data.item_usage[idx] >= item->uses) {
|
|
if (data.item_counts[idx] == 1) {
|
|
// We just used up the last one
|
|
data.item_ids.erase(data.item_ids.begin() + idx);
|
|
data.item_counts.erase(data.item_counts.begin() + idx);
|
|
data.item_usage.erase(data.item_usage.begin() + idx);
|
|
} else {
|
|
data.item_counts[idx]--;
|
|
data.item_usage[idx] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Game_Party::IsItemUsable(int item_id, const Game_Actor* target) const {
|
|
if (target && !target->IsItemUsable(item_id)) {
|
|
return false;
|
|
}
|
|
|
|
const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
|
|
if (!item) {
|
|
Output::Warning("IsItemUsable: Invalid item ID {}", item_id);
|
|
return false;
|
|
}
|
|
|
|
const auto* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, item->skill_id);
|
|
const bool in_battle = Game_Battle::IsBattleRunning();
|
|
|
|
if (item->use_skill) {
|
|
// RPG_RT BUG: Does not check if skill is usable.
|
|
return skill &&
|
|
(in_battle
|
|
|| skill->scope == lcf::rpg::Skill::Scope_self
|
|
|| skill->scope == lcf::rpg::Skill::Scope_ally
|
|
|| skill->scope == lcf::rpg::Skill::Scope_party);
|
|
}
|
|
|
|
switch (item->type) {
|
|
case lcf::rpg::Item::Type_medicine:
|
|
return !in_battle || !item->occasion_field1;
|
|
case lcf::rpg::Item::Type_material:
|
|
case lcf::rpg::Item::Type_book:
|
|
return !in_battle;
|
|
case lcf::rpg::Item::Type_switch:
|
|
return in_battle ? item->occasion_battle : item->occasion_field2;
|
|
case lcf::rpg::Item::Type_special:
|
|
if (skill && Algo::IsSkillUsable(*skill, false)) {
|
|
// RPG_RT requires one actor in the party and alive who can use the item.
|
|
// But only if the item invokes a normal or subskill. This check is
|
|
// not performed for escape, teleport, or switch skills!
|
|
if (!Algo::IsNormalOrSubskill(*skill)) {
|
|
return true;
|
|
} else {
|
|
for (auto* actor: GetActors()) {
|
|
if (actor->CanAct() && actor->IsItemUsable(item_id)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Game_Party::UseItem(int item_id, Game_Actor* target) {
|
|
bool was_used = false;
|
|
|
|
auto* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
|
|
if (!item) {
|
|
Output::Warning("UseItem: Can't use item with invalid ID {}", item_id);
|
|
return false;
|
|
}
|
|
|
|
bool do_skill = (item->type == lcf::rpg::Item::Type_special)
|
|
|| (item->use_skill && (
|
|
item->type == lcf::rpg::Item::Type_weapon
|
|
|| item->type == lcf::rpg::Item::Type_shield
|
|
|| item->type == lcf::rpg::Item::Type_armor
|
|
|| item->type == lcf::rpg::Item::Type_helmet
|
|
|| item->type == lcf::rpg::Item::Type_accessory
|
|
)
|
|
);
|
|
|
|
const lcf::rpg::Skill* skill = nullptr;
|
|
if (do_skill) {
|
|
skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, item->skill_id);
|
|
if (skill == nullptr) {
|
|
Output::Warning("UseItem: Can't use item {} skill with invalid ID {}", item->ID, item->skill_id);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const Game_Actor* fixed_source = nullptr;
|
|
if (skill && skill->scope != lcf::rpg::Skill::Scope_self) {
|
|
fixed_source = GetHighestLeveledActorWhoCanUse(item);
|
|
if (fixed_source == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (target) {
|
|
const auto* source = fixed_source ? fixed_source : target;
|
|
if (IsItemUsable(item_id, source)) {
|
|
was_used = target->UseItem(item_id, source);
|
|
}
|
|
} else {
|
|
for (auto* actor: GetActors()) {
|
|
const auto* source = fixed_source ? fixed_source : actor;
|
|
if (IsItemUsable(item_id, source)) {
|
|
was_used |= actor->UseItem(item_id, source);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (was_used) {
|
|
ConsumeItemUse(item_id);
|
|
}
|
|
|
|
return was_used;
|
|
}
|
|
|
|
bool Game_Party::IsSkillUsable(int skill_id, const Game_Actor* target, bool from_item) const {
|
|
if (skill_id <= 0 || skill_id > (int)lcf::Data::skills.size()) {
|
|
return false;
|
|
}
|
|
|
|
if (target && !target->IsSkillUsable(skill_id)) {
|
|
return false;
|
|
}
|
|
|
|
const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
|
|
if (!skill) {
|
|
Output::Warning("IsSkillUsable: Can't use skill with invalid ID {}", skill_id);
|
|
return false;
|
|
}
|
|
|
|
if (skill->type == lcf::rpg::Skill::Type_escape) {
|
|
return !Game_Battle::IsBattleRunning() && Main_Data::game_system->GetAllowEscape() && Main_Data::game_targets->HasEscapeTarget() && !Main_Data::game_player->IsFlying();
|
|
} else if (skill->type == lcf::rpg::Skill::Type_teleport) {
|
|
return !Game_Battle::IsBattleRunning() && Main_Data::game_system->GetAllowTeleport() && Main_Data::game_targets->HasTeleportTargets() && !Main_Data::game_player->IsFlying();
|
|
} else if (Algo::IsNormalOrSubskill(*skill)) {
|
|
int scope = skill->scope;
|
|
|
|
if (Game_Battle::IsBattleRunning()) {
|
|
return true;
|
|
}
|
|
|
|
// Self targeting skills can not cure states only (except if called by an item).
|
|
// RPG_RT logic...
|
|
|
|
if (scope == lcf::rpg::Skill::Scope_self) {
|
|
return from_item || skill->affect_hp || skill->affect_sp;
|
|
}
|
|
|
|
if (scope == lcf::rpg::Skill::Scope_ally ||
|
|
scope == lcf::rpg::Skill::Scope_party) {
|
|
|
|
if (from_item || skill->affect_hp || skill->affect_sp) {
|
|
return true;
|
|
}
|
|
for (size_t i = 0; i < skill->state_effects.size(); ++i) {
|
|
auto& state = lcf::Data::states[i];
|
|
if (skill->state_effects[i] && state.type == lcf::rpg::State::Persistence_persists) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
} else if (skill->type == lcf::rpg::Skill::Type_switch) {
|
|
if (Game_Battle::IsBattleRunning()) {
|
|
return skill->occasion_battle;
|
|
}
|
|
|
|
return skill->occasion_field;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Game_Party::UseSkill(int skill_id, Game_Actor* source, Game_Actor* target) {
|
|
bool was_used = false;
|
|
|
|
if (target) {
|
|
was_used = target->UseSkill(skill_id, source);
|
|
}
|
|
else {
|
|
std::vector<Game_Actor*> actors = GetActors();
|
|
std::vector<Game_Actor*>::iterator it;
|
|
for (it = actors.begin(); it != actors.end(); ++it) {
|
|
was_used |= (*it)->UseSkill(skill_id, source);
|
|
}
|
|
}
|
|
|
|
if (was_used) {
|
|
source->SetSp(source->GetSp() - source->CalculateSkillCost(skill_id));
|
|
source->SetHp(source->GetHp() - source->CalculateSkillHpCost(skill_id));
|
|
}
|
|
|
|
return was_used;
|
|
}
|
|
|
|
void Game_Party::AddActor(int actor_id) {
|
|
auto* actor = Main_Data::game_actors->GetActor(actor_id);
|
|
if (!actor) {
|
|
return;
|
|
}
|
|
|
|
if (IsActorInParty(actor_id))
|
|
return;
|
|
if (data.party.size() >= 4)
|
|
return;
|
|
data.party.push_back((int16_t)actor_id);
|
|
Main_Data::game_player->ResetGraphic();
|
|
|
|
auto scene = Scene::Find(Scene::Battle);
|
|
if (scene) {
|
|
scene->OnPartyChanged(actor, true);
|
|
}
|
|
}
|
|
|
|
void Game_Party::RemoveActor(int actor_id) {
|
|
if (!IsActorInParty(actor_id))
|
|
return;
|
|
data.party.erase(std::find(data.party.begin(), data.party.end(), actor_id));
|
|
Main_Data::game_player->ResetGraphic();
|
|
|
|
auto* actor = Main_Data::game_actors->GetActor(actor_id);
|
|
if (!actor) {
|
|
return;
|
|
}
|
|
|
|
auto scene = Scene::Find(Scene::Battle);
|
|
if (scene) {
|
|
scene->OnPartyChanged(actor, false);
|
|
}
|
|
}
|
|
|
|
void Game_Party::Clear() {
|
|
data.party.clear();
|
|
}
|
|
|
|
bool Game_Party::IsActorInParty(int actor_id) {
|
|
return std::find(data.party.begin(), data.party.end(), actor_id) != data.party.end();
|
|
}
|
|
|
|
int Game_Party::GetActorPositionInParty(int actor_id) {
|
|
std::vector<short>::iterator it = std::find(data.party.begin(), data.party.end(), actor_id);
|
|
|
|
return it != data.party.end() ? std::distance(data.party.begin(), it) : -1;
|
|
}
|
|
|
|
std::vector<Game_Actor*> Game_Party::GetActors() const {
|
|
std::vector<Game_Actor*> actors;
|
|
std::vector<int16_t>::const_iterator it;
|
|
for (it = data.party.begin(); it != data.party.end(); ++it)
|
|
actors.push_back(Main_Data::game_actors->GetActor(*it));
|
|
return actors;
|
|
}
|
|
|
|
Game_Actor* Game_Party::GetActor(int idx) const {
|
|
if (idx >= 0 && idx < static_cast<int>(data.party.size())) {
|
|
return Main_Data::game_actors->GetActor(data.party[idx]);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Game_Party::ApplyDamage(int damage, bool lethal) {
|
|
if (damage <= 0) {
|
|
return;
|
|
}
|
|
|
|
for (auto* actor: GetActors()) {
|
|
actor->ChangeHp(-damage, lethal);
|
|
}
|
|
}
|
|
|
|
void Game_Party::SetTimer(int which, int seconds) {
|
|
switch (which) {
|
|
case Timer1:
|
|
data.timer1_frames = seconds * DEFAULT_FPS + (DEFAULT_FPS - 1);
|
|
Game_Map::SetNeedRefresh(true);
|
|
break;
|
|
case Timer2:
|
|
data.timer2_frames = seconds * DEFAULT_FPS + (DEFAULT_FPS -1);
|
|
Game_Map::SetNeedRefresh(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Game_Party::StartTimer(int which, bool visible, bool battle) {
|
|
switch (which) {
|
|
case Timer1:
|
|
data.timer1_active = true;
|
|
data.timer1_visible = visible;
|
|
data.timer1_battle = battle;
|
|
break;
|
|
case Timer2:
|
|
data.timer2_active = true;
|
|
data.timer2_visible = visible;
|
|
data.timer2_battle = battle;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Game_Party::StopTimer(int which) {
|
|
switch (which) {
|
|
case Timer1:
|
|
data.timer1_active = false;
|
|
data.timer1_visible = false;
|
|
break;
|
|
case Timer2:
|
|
data.timer2_active = false;
|
|
data.timer2_visible = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Game_Party::UpdateTimers() {
|
|
const bool battle = Game_Battle::IsBattleRunning();
|
|
bool seconds_changed = false;
|
|
|
|
if (data.timer1_active && (data.timer1_battle || !battle) && data.timer1_frames > 0) {
|
|
data.timer1_frames = data.timer1_frames - 1;
|
|
|
|
const int seconds = data.timer1_frames / DEFAULT_FPS;
|
|
const int mod_frames = data.timer1_frames % DEFAULT_FPS;
|
|
seconds_changed |= (mod_frames == (DEFAULT_FPS - 1));
|
|
|
|
if (seconds == 0) {
|
|
StopTimer(Timer1);
|
|
}
|
|
}
|
|
|
|
if (data.timer2_active && (data.timer2_battle || !battle) && data.timer2_frames > 0) {
|
|
data.timer2_frames = data.timer2_frames - 1;
|
|
|
|
const int seconds = data.timer2_frames / DEFAULT_FPS;
|
|
const int mod_frames = data.timer2_frames % DEFAULT_FPS;
|
|
seconds_changed |= (mod_frames == (DEFAULT_FPS - 1));
|
|
|
|
if (seconds == 0) {
|
|
StopTimer(Timer2);
|
|
}
|
|
}
|
|
|
|
if (seconds_changed) {
|
|
Game_Map::SetNeedRefresh(true);
|
|
}
|
|
}
|
|
|
|
int Game_Party::GetTimerSeconds(int which) {
|
|
return GetTimerFrames(which) / DEFAULT_FPS;
|
|
}
|
|
|
|
int Game_Party::GetTimerFrames(int which) {
|
|
switch (which) {
|
|
case Timer1:
|
|
return data.timer1_frames;
|
|
case Timer2:
|
|
return data.timer2_frames;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool Game_Party::GetTimerVisible(int which, bool in_battle) {
|
|
bool visible = false;
|
|
bool battle = false;
|
|
switch (which) {
|
|
case Timer1:
|
|
visible = data.timer1_visible;
|
|
battle = data.timer1_battle;
|
|
break;
|
|
case Timer2:
|
|
visible = data.timer2_visible;
|
|
battle = data.timer2_battle;
|
|
break;
|
|
}
|
|
return visible && (!in_battle || battle);
|
|
}
|
|
|
|
int Game_Party::GetAverageLevel() {
|
|
int party_lvl = 0;
|
|
|
|
std::vector<Game_Actor*> actors = GetActors();
|
|
std::vector<Game_Actor*>::iterator it;
|
|
|
|
if (actors.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
for (it = actors.begin(); it != actors.end(); ++it) {
|
|
party_lvl += (*it)->GetLevel();
|
|
}
|
|
|
|
return party_lvl / (int)actors.size();
|
|
}
|
|
|
|
int Game_Party::GetFatigue() {
|
|
const auto& actors = GetActors();
|
|
|
|
if (actors.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
int hp = 0;
|
|
int total_hp = 0;
|
|
int sp = 0;
|
|
int total_sp = 0;
|
|
for (auto* a : actors) {
|
|
hp += a->GetHp();
|
|
total_hp += a->GetMaxHp();
|
|
sp += a->GetSp();
|
|
total_sp += a->GetMaxSp();
|
|
}
|
|
|
|
// SP is always 33.3% of fatigue, which means a 0 SP actor is never above 66%
|
|
if (total_sp == 0) {
|
|
total_sp = 1;
|
|
}
|
|
|
|
auto p = Utils::RoundTo<int>(100.0f * ((static_cast<double>(hp) / total_hp * 2.0 + static_cast<double>(sp) / total_sp) / 3.0f));
|
|
return 100 - p;
|
|
}
|
|
|
|
void Game_Party::RemoveInvalidData() {
|
|
// Remove non existing actors
|
|
std::vector<int16_t> temp_party;
|
|
std::swap(temp_party, data.party);
|
|
std::vector<int16_t>::iterator it;
|
|
for (it = temp_party.begin(); it != temp_party.end(); ++it) {
|
|
if (Main_Data::game_actors->ActorExists(*it)) {
|
|
data.party.push_back(*it);
|
|
} else {
|
|
Output::Debug("Removing invalid party member {}", *it);
|
|
}
|
|
}
|
|
|
|
// Remove non existing items
|
|
for (it = data.item_ids.begin(); it != data.item_ids.end(); ) {
|
|
if (!lcf::ReaderUtil::GetElement(lcf::Data::items, *it)) {
|
|
Output::Debug("Removing invalid item {} from party", *it);
|
|
it = data.item_ids.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<int16_t> Game_Party::GetInflictedStates() const {
|
|
std::vector<int16_t> states;
|
|
|
|
for (auto actor : GetActors()) {
|
|
std::vector<int16_t> actor_states = actor->GetInflictedStates();
|
|
states.insert(states.end(), actor_states.begin(), actor_states.end());
|
|
}
|
|
|
|
if (!states.empty()) {
|
|
std::sort(states.begin(), states.end());
|
|
states.erase(std::unique(states.begin(), states.end()), states.end());
|
|
}
|
|
return states;
|
|
}
|
|
|
|
bool Game_Party::ApplyStateDamage() {
|
|
bool damage = false;
|
|
std::vector<int16_t> states = GetInflictedStates();
|
|
|
|
const auto steps = GetSteps();
|
|
|
|
for (auto state_id : states) {
|
|
lcf::rpg::State *state = lcf::ReaderUtil::GetElement(lcf::Data::states, state_id);
|
|
|
|
if (state->hp_change_map_steps > 0
|
|
&& state->hp_change_map_val > 0
|
|
&& ((steps % state->hp_change_map_steps) == 0)
|
|
) {
|
|
for (auto actor : GetActors()) {
|
|
if (actor->HasState(state_id)) {
|
|
if (state->hp_change_type == lcf::rpg::State::ChangeType_lose) {
|
|
actor->ChangeHp(-state->hp_change_map_val, false);
|
|
damage = true;
|
|
}
|
|
else if (state->hp_change_type == lcf::rpg::State::ChangeType_gain) {
|
|
actor->ChangeHp(state->hp_change_map_val, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (state->sp_change_map_steps > 0
|
|
&& state->sp_change_map_val > 0
|
|
&& ((steps % state->sp_change_map_steps) == 0)
|
|
){
|
|
for (auto actor : GetActors()) {
|
|
if (actor->HasState(state_id)) {
|
|
if (state->sp_change_type == lcf::rpg::State::ChangeType_lose) {
|
|
actor->ChangeSp(-state->sp_change_map_val);
|
|
damage = true;
|
|
}
|
|
else if (state->sp_change_type == lcf::rpg::State::ChangeType_gain) {
|
|
actor->ChangeSp(state->sp_change_map_val);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return damage;
|
|
}
|
|
|
|
bool Game_Party::IsAnyControllable() {
|
|
for (auto& actor: GetActors()) {
|
|
if (actor->IsControllable()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Game_Actor* Game_Party::GetHighestLeveledActorWhoCanUse(const lcf::rpg::Item* item) const {
|
|
Game_Actor* best = nullptr;
|
|
|
|
for (auto* actor : GetActors()) {
|
|
if (actor->CanAct()
|
|
&& actor->IsItemUsable(item->ID)
|
|
&& (best == nullptr || best->GetLevel() < actor->GetLevel())) {
|
|
best = actor;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
std::pair<int,bool> Game_Party::GetItemIndex(int item_id) const {
|
|
auto& ids = data.item_ids;
|
|
auto iter = std::lower_bound(ids.begin(), ids.end(), item_id);
|
|
return std::make_pair<int,bool>(iter - ids.begin(), (iter != ids.end() && *iter == item_id));
|
|
}
|