1 /*
2 * This file is part of EasyRPG Player.
3 *
4 * EasyRPG Player is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * EasyRPG Player is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // Headers
19 #include <algorithm>
20 #include <sstream>
21 #include <iterator>
22 #include "game_actor.h"
23 #include "game_battle.h"
24 #include "game_message.h"
25 #include "game_party.h"
26 #include "sprite_actor.h"
27 #include "main_data.h"
28 #include "output.h"
29 #include "player.h"
30 #include <lcf/reader_util.h>
31 #include <lcf/rpg/skill.h>
32 #include "util_macro.h"
33 #include "utils.h"
34 #include "pending_message.h"
35 #include "compiler.h"
36 #include "attribute.h"
37 #include "rand.h"
38 #include "algo.h"
39
40 constexpr int max_level_2k = 50;
41 constexpr int max_level_2k3 = 99;
42
MaxHpValue() const43 int Game_Actor::MaxHpValue() const {
44 auto& val = lcf::Data::system.easyrpg_max_actor_hp;
45 if (val == -1) {
46 return Player::IsRPG2k() ? 999 : 9999;
47 }
48 return val;
49 }
50
MaxSpValue() const51 int Game_Actor::MaxSpValue() const {
52 auto& val = lcf::Data::system.easyrpg_max_actor_sp;
53 if (val == -1) {
54 return 999;
55 }
56 return val;
57 }
58
MaxStatBattleValue() const59 int Game_Actor::MaxStatBattleValue() const {
60 auto& val = lcf::Data::system.easyrpg_max_stat_battle_value;
61 if (val == -1) {
62 return 9999;
63 }
64 return val;
65 }
66
MaxStatBaseValue() const67 int Game_Actor::MaxStatBaseValue() const {
68 auto& val = lcf::Data::system.easyrpg_max_stat_base_value;
69 if (val == -1) {
70 return 999;
71 }
72 return val;
73 }
74
MaxExpValue() const75 int Game_Actor::MaxExpValue() const {
76 auto& val = lcf::Data::system.easyrpg_max_exp;
77 if (val == -1) {
78 return Player::IsRPG2k() ? 999999 : 9999999;
79 }
80 return val;
81 }
82
Game_Actor(int actor_id)83 Game_Actor::Game_Actor(int actor_id) {
84 data.ID = actor_id;
85 if (actor_id == 0) {
86 return;
87 }
88 dbActor = lcf::ReaderUtil::GetElement(lcf::Data::actors, GetId());
89
90 data.two_weapon = dbActor->two_weapon;
91 data.lock_equipment = dbActor->lock_equipment;
92 data.auto_battle = dbActor->auto_battle;
93 data.super_guard = dbActor->super_guard;
94
95 data.hp_mod = 0;
96 data.sp_mod = 0;
97 data.attack_mod = 0;
98 data.defense_mod = 0;
99 data.spirit_mod = 0;
100 data.agility_mod = 0;
101
102 MakeExpList();
103 SetBattlePosition(GetOriginalPosition());
104
105 data.level = 0;
106 if (dbActor->initial_level > 0) {
107 // For games like COLORS: Lost Memories which use level 0, don't change level because it'll clamp to 1.
108 ChangeLevel(dbActor->initial_level, nullptr);
109 }
110 SetHp(GetMaxHp());
111 SetSp(GetMaxSp());
112
113 // Remove items that do not exist in the database anymore
114 std::array<int, 5> ids = {{
115 dbActor->initial_equipment.weapon_id,
116 dbActor->initial_equipment.shield_id,
117 dbActor->initial_equipment.armor_id,
118 dbActor->initial_equipment.helmet_id,
119 dbActor->initial_equipment.accessory_id }};
120 std::replace_if(ids.begin(), ids.end(), [] (const int& item_id) {
121 return lcf::ReaderUtil::GetElement(lcf::Data::items, item_id) == nullptr;
122 }, 0);
123
124 for (int i = 0; i <= 4; i++) {
125 SetEquipment(i + 1, ids[i]);
126 }
127
128 data.status.resize(lcf::Data::states.size(), 0);
129
130 Fixup();
131 }
132
SetSaveData(lcf::rpg::SaveActor save)133 void Game_Actor::SetSaveData(lcf::rpg::SaveActor save) {
134 data = std::move(save);
135
136 if (Player::IsRPG2k()) {
137 data.two_weapon = dbActor->two_weapon;
138 data.lock_equipment = dbActor->lock_equipment;
139 data.auto_battle = dbActor->auto_battle;
140 data.super_guard = dbActor->super_guard;
141 }
142
143 MakeExpList();
144 Fixup();
145 }
146
GetSaveData() const147 lcf::rpg::SaveActor Game_Actor::GetSaveData() const {
148 auto save = data;
149 if (Player::IsRPG2k()) {
150 // RPG_RT doesn't save these chunks in rm2k as they are meaningless
151 save.two_weapon = false;
152 save.lock_equipment = false;
153 save.auto_battle = false;
154 save.super_guard = false;
155 }
156 return save;
157 }
158
Fixup()159 void Game_Actor::Fixup() {
160 RemoveInvalidData();
161 ResetEquipmentStates(false);
162 }
163
UseItem(int item_id,const Game_Battler * source)164 bool Game_Actor::UseItem(int item_id, const Game_Battler* source) {
165 const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
166 if (!item) {
167 Output::Warning("UseItem: Can't use invalid item {}", item_id);
168 return false;
169 }
170
171 if (!IsDead()) {
172 if (item->type == lcf::rpg::Item::Type_book) {
173 return LearnSkill(item->skill_id, nullptr);
174 }
175
176 if (item->type == lcf::rpg::Item::Type_material) {
177 SetBaseMaxHp(GetBaseMaxHp() + item->max_hp_points);
178 SetBaseMaxSp(GetBaseMaxSp() + item->max_sp_points);
179 SetBaseAtk(GetBaseAtk() + item->atk_points2);
180 SetBaseDef(GetBaseDef() + item->def_points2);
181 SetBaseAgi(GetBaseAgi() + item->agi_points2);
182 SetBaseSpi(GetBaseSpi() + item->spi_points2);
183
184 return true;
185 }
186 }
187
188 return Game_Battler::UseItem(item_id, source);
189 }
190
IsItemUsable(int item_id) const191 bool Game_Actor::IsItemUsable(int item_id) const {
192 const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
193 if (!item) {
194 Output::Warning("IsItemUsable: Invalid item ID {}", item_id);
195 return false;
196 }
197
198 int query_idx = GetId() - 1;
199 auto* query_set = &item->actor_set;
200 if (Player::IsRPG2k3() && lcf::Data::system.equipment_setting == lcf::rpg::System::EquipmentSetting_class) {
201 auto* cls = GetClass();
202
203 // Class index. If there's no class, in the "class_set" it's equal to 0. The first class is 1, not 0
204 query_idx = cls ? cls->ID : 0;
205 query_set = &item->class_set;
206 }
207
208 // If the actor or class ID is out of range this is an optimization in the ldb file
209 // (all actors or classes missing can equip the item)
210 if (query_set->size() <= (unsigned)(query_idx)) {
211 return true;
212 }
213 return (*query_set)[query_idx];
214 }
215
IsSkillLearned(int skill_id) const216 bool Game_Actor::IsSkillLearned(int skill_id) const {
217 return std::find(data.skills.begin(), data.skills.end(), skill_id) != data.skills.end();
218 }
219
IsSkillUsable(int skill_id) const220 bool Game_Actor::IsSkillUsable(int skill_id) const {
221 const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
222 if (!skill) {
223 Output::Warning("IsSkillUsable: Invalid skill ID {}", skill_id);
224 return false;
225 }
226
227 if (!skill->affect_attr_defence) {
228 // Actor must have all attributes of the skill equipped as weapons
229 const auto* w1 = GetWeapon();
230 const auto* w2 = Get2ndWeapon();
231
232 for (size_t i = 0; i < skill->attribute_effects.size(); ++i) {
233 bool required = skill->attribute_effects[i] && lcf::Data::attributes[i].type == lcf::rpg::Attribute::Type_physical;
234 if (required) {
235 if (w1 && i < w1->attribute_set.size() && w1->attribute_set[i]) {
236 continue;
237 }
238 if (w2 && i < w2->attribute_set.size() && w2->attribute_set[i]) {
239 continue;
240 }
241 return false;
242 }
243 }
244 }
245
246 return Game_Battler::IsSkillUsable(skill_id);
247 }
248
CalculateSkillCost(int skill_id) const249 int Game_Actor::CalculateSkillCost(int skill_id) const {
250 const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
251 if (!skill) {
252 Output::Warning("CalculateSkillCost: Invalid skill ID {}", skill_id);
253 return 0;
254 }
255 return Algo::CalcSkillCost(*skill, GetMaxSp(), HasHalfSpCost());
256 }
257
LearnSkill(int skill_id,PendingMessage * pm)258 bool Game_Actor::LearnSkill(int skill_id, PendingMessage* pm) {
259 if (skill_id > 0 && !IsSkillLearned(skill_id)) {
260 const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
261 if (!skill) {
262 Output::Warning("Actor {}: Can't learn invalid skill {}", GetId(), skill_id);
263 return false;
264 }
265
266 data.skills.push_back((int16_t)skill_id);
267 std::sort(data.skills.begin(), data.skills.end());
268
269 if (pm) {
270 pm->PushLine(GetLearningMessage(*skill));
271 }
272
273 return true;
274 }
275 return false;
276 }
277
LearnLevelSkills(int min_level,int max_level,PendingMessage * pm)278 int Game_Actor::LearnLevelSkills(int min_level, int max_level, PendingMessage* pm) {
279 auto& skills = data.class_id > 0 ? GetClass()->skills : dbActor->skills;
280
281 int count = 0;
282
283 // Learn new skills
284 for (const lcf::rpg::Learning& learn : skills) {
285 // Skill learning, up to current level
286 if (learn.level >= min_level && learn.level <= max_level) {
287 count += LearnSkill(learn.skill_id, pm);
288 }
289 }
290 return count;
291 }
292
UnlearnSkill(int skill_id)293 bool Game_Actor::UnlearnSkill(int skill_id) {
294 std::vector<int16_t>::iterator it = std::find(data.skills.begin(), data.skills.end(), skill_id);
295 if (it != data.skills.end()) {
296 data.skills.erase(it);
297 return true;
298 }
299 return false;
300 }
301
UnlearnAllSkills()302 void Game_Actor::UnlearnAllSkills() {
303 data.skills.clear();
304 }
305
SetFace(const std::string & file_name,int index)306 void Game_Actor::SetFace(const std::string& file_name, int index) {
307 if (file_name == dbActor->face_name && index == dbActor->face_index) {
308 data.face_name = "";
309 data.face_id = 0;
310 } else {
311 data.face_name.assign(file_name);
312 data.face_id = index;
313 }
314 }
315
GetEquipment(int equip_type) const316 const lcf::rpg::Item* Game_Actor::GetEquipment(int equip_type) const {
317 if (equip_type <= 0 || equip_type > (int)data.equipped.size())
318 return nullptr;
319
320 int item_id = data.equipped[equip_type - 1];
321 return lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
322 }
323
SetEquipment(int equip_type,int new_item_id)324 int Game_Actor::SetEquipment(int equip_type, int new_item_id) {
325 if (equip_type <= 0 || equip_type > (int) data.equipped.size())
326 return -1;
327
328 int old_item_id = data.equipped[equip_type - 1];
329 const lcf::rpg::Item* old_item = lcf::ReaderUtil::GetElement(lcf::Data::items, old_item_id);
330
331 const lcf::rpg::Item* new_item = lcf::ReaderUtil::GetElement(lcf::Data::items, new_item_id);
332 if (new_item_id != 0 && !new_item) {
333 Output::Warning("SetEquipment: Can't equip item with invalid ID {}", new_item_id);
334 new_item_id = 0;
335 }
336
337 data.equipped[equip_type - 1] = (short)new_item_id;
338
339 AdjustEquipmentStates(old_item, false, false);
340 AdjustEquipmentStates(new_item, true, false);
341
342 return old_item_id;
343 }
344
ChangeEquipment(int equip_type,int item_id)345 void Game_Actor::ChangeEquipment(int equip_type, int item_id) {
346 int prev_item = SetEquipment(equip_type, item_id);
347
348 if (prev_item != 0) {
349 Main_Data::game_party->AddItem(prev_item, 1);
350 }
351 if (item_id != 0) {
352 Main_Data::game_party->RemoveItem(item_id, 1);
353 }
354
355 // In case you have a two_handed weapon equipped, the other weapon is removed.
356 const lcf::rpg::Item* item = GetWeapon();
357 const lcf::rpg::Item* item2 = Get2ndWeapon();
358 if (item && item2 && (item->two_handed || item2->two_handed)) {
359 ChangeEquipment(equip_type == lcf::rpg::Item::Type_weapon ? equip_type + 1 : equip_type - 1, 0);
360 }
361 }
362
IsEquipped(int equip_id) const363 bool Game_Actor::IsEquipped(int equip_id) const {
364 for (auto equip : GetWholeEquipment()) {
365 if (equip == equip_id) {
366 return true;
367 }
368 }
369 return false;
370 }
371
RemoveWholeEquipment()372 void Game_Actor::RemoveWholeEquipment() {
373 for (int i = 1; i <= 5; ++i) {
374 ChangeEquipment(i, 0);
375 }
376 }
377
GetItemCount(int item_id)378 int Game_Actor::GetItemCount(int item_id) {
379 int number = 0;
380
381 if (item_id > 0) {
382 for (int16_t i : GetWholeEquipment()) {
383 if (item_id == i) {
384 ++number;
385 }
386 }
387 }
388
389 return number;
390 }
391
FullHeal()392 void Game_Actor::FullHeal() {
393 RemoveAllStates();
394 SetHp(GetMaxHp());
395 SetSp(GetMaxSp());
396 // Emulates RPG_RT behavior of resetting even battle equipment states on full heal.
397 ResetEquipmentStates(true);
398 }
399
GetBaseMaxHp(bool mod) const400 int Game_Actor::GetBaseMaxHp(bool mod) const {
401 int n = 0;
402 // Special handling for games that use a level of 0 -> Return 0 Hp
403 // Same applies for other stats
404 if (GetLevel() > 0) {
405 // Looks like RPG_RT only applies Class changes (class_id > 0 - 20kdc)
406 // when the class was changed by the ChangeClass event, otherwise it uses
407 // the normal actor attributes.
408 n = data.class_id > 0
409 ? *lcf::ReaderUtil::GetElement(GetClass()->parameters.maxhp, GetLevel())
410 : *lcf::ReaderUtil::GetElement(dbActor->parameters.maxhp, GetLevel());
411 }
412
413 if (mod)
414 n += data.hp_mod;
415
416 return Utils::Clamp(n, 1, MaxHpValue());
417 }
418
GetBaseMaxHp() const419 int Game_Actor::GetBaseMaxHp() const {
420 return GetBaseMaxHp(true);
421 }
422
GetBaseMaxSp(bool mod) const423 int Game_Actor::GetBaseMaxSp(bool mod) const {
424 int n = 0;
425 if (GetLevel() > 0) {
426 n = data.class_id > 0
427 ? *lcf::ReaderUtil::GetElement(GetClass()->parameters.maxsp, GetLevel())
428 : *lcf::ReaderUtil::GetElement(dbActor->parameters.maxsp, GetLevel());
429 }
430
431 if (mod)
432 n += data.sp_mod;
433
434 return Utils::Clamp(n, 0, MaxSpValue());
435 }
436
GetBaseMaxSp() const437 int Game_Actor::GetBaseMaxSp() const {
438 return GetBaseMaxSp(true);
439 }
440
IsArmorType(const lcf::rpg::Item * item)441 static bool IsArmorType(const lcf::rpg::Item* item) {
442 return item->type == lcf::rpg::Item::Type_shield
443 || item->type == lcf::rpg::Item::Type_armor
444 || item->type == lcf::rpg::Item::Type_helmet
445 || item->type == lcf::rpg::Item::Type_accessory;
446 }
447
448 template <bool allow_weapon, bool allow_armor, typename F>
ForEachEquipment(Span<const short> equipped,F && f,Game_Battler::Weapon weapon=Game_Battler::WeaponAll)449 void ForEachEquipment(Span<const short> equipped, F&& f, Game_Battler::Weapon weapon = Game_Battler::WeaponAll) {
450 for (int slot = 0; slot < static_cast<int>(equipped.size()); ++slot) {
451 const auto item_id = equipped[slot];
452 if (item_id <= 0) {
453 continue;
454 }
455
456 auto* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
457 // Invalid equipment was removed
458 assert(item != nullptr);
459
460 if (item->type == lcf::rpg::Item::Type_weapon) {
461 if (!allow_weapon || (weapon != Game_Battler::WeaponAll && weapon != slot + 1)) {
462 continue;
463 }
464 } else if (IsArmorType(item)) {
465 if (!allow_armor) {
466 continue;
467 }
468 } else {
469 assert(false && "Invalid item type equipped!");
470 continue;
471 }
472
473 f(*item);
474 }
475 }
476
GetBaseAtk(Weapon weapon,bool mod,bool equip) const477 int Game_Actor::GetBaseAtk(Weapon weapon, bool mod, bool equip) const {
478 int n = 0;
479 if (GetLevel() > 0) {
480 n = data.class_id > 0
481 ? *lcf::ReaderUtil::GetElement(GetClass()->parameters.attack, GetLevel())
482 : *lcf::ReaderUtil::GetElement(dbActor->parameters.attack, GetLevel());
483 }
484
485 if (mod) {
486 n += data.attack_mod;
487 }
488
489 if (equip) {
490 ForEachEquipment<true,true>(GetWholeEquipment(), [&](auto& item) { n += item.atk_points1; }, weapon);
491 }
492
493 return Utils::Clamp(n, 1, MaxStatBaseValue());
494 }
495
GetBaseAtk(Weapon weapon) const496 int Game_Actor::GetBaseAtk(Weapon weapon) const {
497 return GetBaseAtk(weapon, true, true);
498 }
499
GetBaseDef(Weapon weapon,bool mod,bool equip) const500 int Game_Actor::GetBaseDef(Weapon weapon, bool mod, bool equip) const {
501 int n = 0;
502 if (GetLevel() > 0) {
503 n = data.class_id > 0
504 ? *lcf::ReaderUtil::GetElement(GetClass()->parameters.defense, GetLevel())
505 : *lcf::ReaderUtil::GetElement(dbActor->parameters.defense, GetLevel());
506 }
507
508 if (mod) {
509 n += data.defense_mod;
510 }
511
512 if (equip) {
513 ForEachEquipment<true,true>(GetWholeEquipment(), [&](auto& item) { n += item.def_points1; }, weapon);
514 }
515
516 return Utils::Clamp(n, 1, MaxStatBaseValue());
517 }
518
GetBaseDef(Weapon weapon) const519 int Game_Actor::GetBaseDef(Weapon weapon) const {
520 return GetBaseDef(weapon, true, true);
521 }
522
GetBaseSpi(Weapon weapon,bool mod,bool equip) const523 int Game_Actor::GetBaseSpi(Weapon weapon, bool mod, bool equip) const {
524 int n = 0;
525 if (GetLevel() > 0) {
526 n = data.class_id > 0
527 ? *lcf::ReaderUtil::GetElement(GetClass()->parameters.spirit, GetLevel())
528 : *lcf::ReaderUtil::GetElement(dbActor->parameters.spirit, GetLevel());
529 }
530
531 if (mod) {
532 n += data.spirit_mod;
533 }
534
535 if (equip) {
536 ForEachEquipment<true,true>(GetWholeEquipment(), [&](auto& item) { n += item.spi_points1; }, weapon);
537 }
538
539 return Utils::Clamp(n, 1, MaxStatBaseValue());
540 }
541
GetBaseSpi(Weapon weapon) const542 int Game_Actor::GetBaseSpi(Weapon weapon) const {
543 return GetBaseSpi(weapon, true, true);
544 }
545
GetBaseAgi(Weapon weapon,bool mod,bool equip) const546 int Game_Actor::GetBaseAgi(Weapon weapon, bool mod, bool equip) const {
547 int n = 0;
548 if (GetLevel() > 0) {
549 n = data.class_id > 0
550 ? *lcf::ReaderUtil::GetElement(GetClass()->parameters.agility, GetLevel())
551 : *lcf::ReaderUtil::GetElement(dbActor->parameters.agility, GetLevel());
552 }
553
554 if (mod) {
555 n += data.agility_mod;
556 }
557
558 if (equip) {
559 ForEachEquipment<true,true>(GetWholeEquipment(), [&](auto& item) { n += item.agi_points1; }, weapon);
560 }
561
562 return Utils::Clamp(n, 1, MaxStatBaseValue());
563 }
564
GetBaseAgi(Weapon weapon) const565 int Game_Actor::GetBaseAgi(Weapon weapon) const {
566 return GetBaseAgi(weapon, true, true);
567 }
568
CalculateExp(int level) const569 int Game_Actor::CalculateExp(int level) const {
570 const lcf::rpg::Class* klass = lcf::ReaderUtil::GetElement(lcf::Data::classes, data.class_id);
571
572 int exp_curve = Player::IsRPG2k() ? 1 : 2;
573 if (lcf::Data::system.easyrpg_alternative_exp > 0) {
574 exp_curve = lcf::Data::system.easyrpg_alternative_exp;
575 }
576
577 double base, inflation, correction;
578 if (klass) {
579 base = klass->exp_base;
580 inflation = klass->exp_inflation;
581 correction = klass->exp_correction;
582 }
583 else {
584 const lcf::rpg::Actor& actor = *lcf::ReaderUtil::GetElement(lcf::Data::actors, GetId());
585 base = actor.exp_base;
586 inflation = actor.exp_inflation;
587 correction = actor.exp_correction;
588 }
589
590 int result = 0;
591 if (exp_curve == 1) {
592 inflation = 1.5 + (inflation * 0.01);
593
594 for (int i = level; i >= 1; i--)
595 {
596 result = result + (int)(correction + base);
597 base = base * inflation;
598 inflation = ((level+1) * 0.002 + 0.8) * (inflation - 1) + 1;
599 }
600 } else /*Rpg2k3*/ {
601 for (int i = 1; i <= level; i++)
602 {
603 result += (int)base;
604 result += i * (int)inflation;
605 result += (int)correction;
606 }
607 }
608 return min(result, MaxExpValue());
609 }
610
MakeExpList()611 void Game_Actor::MakeExpList() {
612 exp_list.resize((size_t)GetMaxLevel());
613 for (int i = 1; i < (int)exp_list.size(); ++i) {
614 exp_list[i] = CalculateExp(i);
615 }
616 }
617
GetExpString(bool status_scene) const618 std::string Game_Actor::GetExpString(bool status_scene) const {
619 // RPG_RT displays dashes for max level. As a customization
620 // we always display the amount of EXP.
621 // if (GetNextExp() == -1) { return (MaxExpValue() >= 1000000 || status_scene) ? "-------" : "------"; }
622 return std::to_string(GetExp());
623 }
624
GetNextExpString(bool status_scene) const625 std::string Game_Actor::GetNextExpString(bool status_scene) const {
626 if (GetNextExp() == -1) {
627 return (MaxExpValue() >= 1000000 || status_scene) ? "-------" : "------";
628 }
629 return std::to_string(GetNextExp());
630 }
631
GetBaseExp() const632 int Game_Actor::GetBaseExp() const {
633 return GetBaseExp(GetLevel());
634 }
635
GetBaseExp(int level) const636 int Game_Actor::GetBaseExp(int level) const {
637 return GetNextExp(level - 1);
638 }
639
GetNextExp() const640 int Game_Actor::GetNextExp() const {
641 return GetNextExp(GetLevel());
642 }
643
GetNextExp(int level) const644 int Game_Actor::GetNextExp(int level) const {
645 if (level >= GetMaxLevel() || level <= -1) {
646 return -1;
647 } else if (level == 0) {
648 return 0;
649 } else {
650 return exp_list[level];
651 }
652 }
653
GetStateProbability(int state_id) const654 int Game_Actor::GetStateProbability(int state_id) const {
655 int rate = 2, mul = 100; // C - default
656
657 const uint8_t* r = lcf::ReaderUtil::GetElement(dbActor->state_ranks, state_id);
658 if (r) {
659 rate = *r;
660 }
661
662 // This takes the armor of the character with the most resistance for that particular state
663 for (const auto equipment : GetWholeEquipment()) {
664 lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, equipment);
665 if (item != nullptr
666 && !(Player::IsRPG2k3() && item->reverse_state_effect)
667 && (item->type == lcf::rpg::Item::Type_shield || item->type == lcf::rpg::Item::Type_armor
668 || item->type == lcf::rpg::Item::Type_helmet || item->type == lcf::rpg::Item::Type_accessory)
669 && state_id <= static_cast<int>(item->state_set.size()) && item->state_set[state_id - 1]) {
670 mul = std::min<int>(mul, 100 - item->state_chance);
671 }
672 }
673
674 // GetStateRate verifies the state_id
675 return GetStateRate(state_id, rate) * mul / 100;
676 }
677
GetBaseAttributeRate(int attribute_id) const678 int Game_Actor::GetBaseAttributeRate(int attribute_id) const {
679 int rate = 2; // C - default
680
681 const auto* r = lcf::ReaderUtil::GetElement(dbActor->attribute_ranks, attribute_id);
682 if (r) {
683 rate = *r;
684 }
685
686 bool boost = false;
687 ForEachEquipment<false,true>(GetWholeEquipment(), [&](auto& item) {
688 boost |= attribute_id >= 1 && attribute_id <= static_cast<int>(item.attribute_set.size()) && item.attribute_set[attribute_id - 1];
689 });
690 rate += boost;
691
692 return Utils::Clamp(rate, 0, 4);
693 }
694
GetWeaponId() const695 int Game_Actor::GetWeaponId() const {
696 int item_id = GetWholeEquipment()[0];
697 return item_id <= (int)lcf::Data::items.size() ? item_id : 0;
698 }
699
GetShieldId() const700 int Game_Actor::GetShieldId() const {
701 int item_id = GetWholeEquipment()[1];
702 return item_id <= (int)lcf::Data::items.size() ? item_id : 0;
703 }
704
GetArmorId() const705 int Game_Actor::GetArmorId() const {
706 int item_id = GetWholeEquipment()[2];
707 return item_id <= (int)lcf::Data::items.size() ? item_id : 0;
708 }
709
GetHelmetId() const710 int Game_Actor::GetHelmetId() const {
711 int item_id = GetWholeEquipment()[3];
712 return item_id <= (int)lcf::Data::items.size() ? item_id : 0;
713 }
714
GetAccessoryId() const715 int Game_Actor::GetAccessoryId() const {
716 int item_id = GetWholeEquipment()[4];
717 return item_id <= (int)lcf::Data::items.size() ? item_id : 0;
718 }
719
GetMaxLevel() const720 int Game_Actor::GetMaxLevel() const {
721 int max_level = Player::IsRPG2k() ? max_level_2k : max_level_2k3;
722 if (lcf::Data::system.easyrpg_max_level > -1) {
723 max_level = lcf::Data::system.easyrpg_max_level;
724 }
725 return Utils::Clamp<int32_t>(max_level, 1, dbActor->final_level);
726 }
727
SetExp(int _exp)728 void Game_Actor::SetExp(int _exp) {
729 data.exp = Utils::Clamp<int32_t>(_exp, 0, MaxExpValue());
730 }
731
ChangeExp(int exp,PendingMessage * pm)732 void Game_Actor::ChangeExp(int exp, PendingMessage* pm) {
733 int new_level = GetLevel();
734 int new_exp = Utils::Clamp<int>(exp, 0, MaxExpValue());
735
736 if (new_exp > GetExp()) {
737 for (int i = GetLevel() + 1; i <= GetMaxLevel(); ++i) {
738 if (GetNextExp(new_level) != -1 && GetNextExp(new_level) > new_exp) {
739 break;
740 }
741 new_level++;
742 }
743 } else if (new_exp < GetExp()) {
744 for (int i = GetLevel(); i > 1; --i) {
745 if (new_exp >= GetNextExp(i - 1)) {
746 break;
747 }
748 new_level--;
749 }
750 }
751
752 SetExp(new_exp);
753
754 if (new_level != GetLevel()) {
755 ChangeLevel(new_level, pm);
756 }
757 }
758
SetLevel(int _level)759 void Game_Actor::SetLevel(int _level) {
760 data.level = Utils::Clamp(_level, 1, GetMaxLevel());
761 // Ensure current HP/SP remain clamped if new Max HP/SP is less.
762 SetHp(GetHp());
763 SetSp(GetSp());
764
765 }
766
GetLevelUpMessage(int new_level) const767 std::string Game_Actor::GetLevelUpMessage(int new_level) const {
768 std::stringstream ss;
769 if (Player::IsRPG2k3E()) {
770 ss << GetName();
771 ss << " " << lcf::Data::terms.level_up << " ";
772 ss << " " << lcf::Data::terms.level << " " << new_level;
773 return ss.str();
774 } else if (Player::IsRPG2kE()) {
775 ss << new_level;
776 return Utils::ReplacePlaceholders(
777 lcf::Data::terms.level_up,
778 Utils::MakeArray('S', 'V', 'U'),
779 Utils::MakeSvArray(GetName(), ss.str(), lcf::Data::terms.level)
780 );
781 } else {
782 std::string particle, space = "";
783 if (Player::IsCP932()) {
784 particle = "は";
785 space += " ";
786 }
787 else {
788 particle = " ";
789 }
790 ss << GetName();
791 ss << particle << lcf::Data::terms.level << " ";
792 ss << new_level << space << lcf::Data::terms.level_up;
793 return ss.str();
794 }
795 }
796
GetLearningMessage(const lcf::rpg::Skill & skill) const797 std::string Game_Actor::GetLearningMessage(const lcf::rpg::Skill& skill) const {
798 if (Player::IsRPG2kE()) {
799 return Utils::ReplacePlaceholders(
800 lcf::Data::terms.skill_learned,
801 Utils::MakeArray('S', 'O'),
802 Utils::MakeSvArray(GetName(), skill.name)
803 );
804 }
805
806 return ToString(skill.name) + (Player::IsRPG2k3E() ? " " : "") + ToString(lcf::Data::terms.skill_learned);
807 }
808
ChangeLevel(int new_level,PendingMessage * pm)809 void Game_Actor::ChangeLevel(int new_level, PendingMessage* pm) {
810 int old_level = GetLevel();
811 SetLevel(new_level);
812 new_level = GetLevel(); // Level adjusted to max
813
814 if (new_level > old_level) {
815 if (pm) {
816 pm->PushLine(GetLevelUpMessage(new_level));
817 }
818
819 // Learn new skills
820 LearnLevelSkills(old_level + 1, new_level, pm);
821
822 if (pm) {
823 pm->PushPageEnd();
824 }
825
826 // Experience adjustment:
827 // At least level minimum
828 SetExp(max(GetBaseExp(), GetExp()));
829 } else if (new_level < old_level) {
830 // Experience adjustment:
831 // Level minimum if higher then Level maximum
832 if (GetExp() >= GetNextExp()) {
833 SetExp(GetBaseExp());
834 }
835 }
836 }
837
IsEquippable(int item_id) const838 bool Game_Actor::IsEquippable(int item_id) const {
839 const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
840 if (!item) {
841 Output::Warning("IsEquippable: Invalid item ID {}", item_id);
842 return false;
843 }
844
845 if (HasTwoWeapons() &&
846 item->type == lcf::rpg::Item::Type_shield) {
847 return false;
848 }
849
850 return IsItemUsable(item_id);
851 }
852
IsEquipmentFixed(bool check_states) const853 bool Game_Actor::IsEquipmentFixed(bool check_states) const {
854 if (data.lock_equipment) {
855 return true;
856 }
857
858 if (check_states) {
859 for (auto state_id: GetInflictedStates()) {
860 auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, state_id);
861 if (state && state->cursed) {
862 return true;
863 }
864 }
865 }
866 return false;
867 }
868
GetRandomSkill() const869 const lcf::rpg::Skill* Game_Actor::GetRandomSkill() const {
870 const std::vector<int16_t>& skills = GetSkills();
871 if (skills.empty()) {
872 return nullptr;
873 }
874
875 // Skills are guaranteed to be valid
876 return lcf::ReaderUtil::GetElement(lcf::Data::skills, skills[Rand::GetRandomNumber(0, skills.size() - 1)]);
877 }
878
GetOriginalPosition() const879 Point Game_Actor::GetOriginalPosition() const {
880 return { dbActor->battle_x, dbActor->battle_y };
881 }
882
GetSkillName() const883 StringView Game_Actor::GetSkillName() const {
884 return dbActor->rename_skill ? StringView(dbActor->skill_name) : StringView(lcf::Data::terms.command_skill);
885 }
886
SetSprite(const std::string & file,int index,bool transparent)887 void Game_Actor::SetSprite(const std::string &file, int index, bool transparent) {
888 if (file == dbActor->character_name
889 && index == dbActor->character_index
890 && transparent == dbActor->transparent) {
891 data.sprite_name = "";
892 data.sprite_id = 0;
893 data.transparency = 0;
894 } else {
895 data.sprite_name = file;
896 data.sprite_id = index;
897 data.transparency = transparent ? 3 : 0;
898 }
899 }
900
ChangeBattleCommands(bool add,int id)901 void Game_Actor::ChangeBattleCommands(bool add, int id) {
902 auto& cmds = data.battle_commands;
903
904 // If changing battle commands, that is when RPG_RT will replace the -1 list with a 'true' list.
905 // Fetch original command array.
906 if (!data.changed_battle_commands) {
907 cmds = lcf::Data::actors[GetId() - 1].battle_commands;
908 data.changed_battle_commands = true;
909 }
910
911 // The battle commands array always has a size of 7 padded with -1. The last element before the padding is 0 which
912 // stands for the Row command
913 if (add) {
914 const lcf::rpg::BattleCommand* cmd = lcf::ReaderUtil::GetElement(lcf::Data::battlecommands.commands, id);
915 if (!cmd) {
916 Output::Warning("ChangeBattleCommands: Can't add invalid battle command {}", id);
917 return;
918 }
919
920 if (std::find(cmds.begin(), cmds.end(), id) == cmds.end()) {
921 std::vector<int32_t> new_cmds;
922 std::copy_if(cmds.begin(), cmds.end(),
923 std::back_inserter(new_cmds), [](int32_t i) { return i != 0 && i != -1; });
924 // Needs space for at least 2 more commands (new command and row)
925 if (new_cmds.size() >= 6) {
926 return;
927 }
928 new_cmds.push_back(id);
929 std::sort(new_cmds.begin(), new_cmds.end());
930 new_cmds.push_back(0);
931 cmds = new_cmds;
932 }
933 } else if (id == 0) {
934 cmds.clear();
935 cmds.push_back(0);
936 } else {
937 std::vector<int32_t>::iterator it;
938 it = std::find(cmds.begin(), cmds.end(), id);
939 if (it != cmds.end())
940 cmds.erase(it);
941 }
942
943 cmds.resize(7, -1);
944 }
945
GetBattleCommand(int idx) const946 const lcf::rpg::BattleCommand* Game_Actor::GetBattleCommand(int idx) const {
947 Span<const int32_t> commands;
948 if (data.changed_battle_commands) {
949 commands = data.battle_commands;
950 } else if (dbActor) {
951 commands = dbActor->battle_commands;
952 }
953 int cmd_id = 0;
954 if (idx >= 0 && idx < static_cast<int>(commands.size())) {
955 cmd_id = commands[idx];
956 }
957 return lcf::ReaderUtil::GetElement(lcf::Data::battlecommands.commands, cmd_id);
958 }
959
GetBattleCommands() const960 const std::vector<const lcf::rpg::BattleCommand*> Game_Actor::GetBattleCommands() const {
961 std::vector<const lcf::rpg::BattleCommand*> commands;
962 std::vector<int32_t> obc = data.battle_commands;
963 if (!data.changed_battle_commands) {
964 // In this case, get it straight from the LDB.
965 obc = lcf::Data::actors[GetId() - 1].battle_commands;
966 }
967
968 for (int command_index : obc) {
969 if (command_index == 0) {
970 // Row command -> not impl
971 continue;
972 }
973
974 if (command_index == -1) {
975 // Empty slot
976 continue;
977 }
978
979 const lcf::rpg::BattleCommand* cmd = lcf::ReaderUtil::GetElement(lcf::Data::battlecommands.commands, command_index);
980 if (!cmd) {
981 Output::Warning("GetBattleCommands: Invalid battle command ID {}", command_index);
982 continue;
983 }
984
985 commands.push_back(cmd);
986 }
987
988 return commands;
989 }
990
GetClass() const991 const lcf::rpg::Class* Game_Actor::GetClass() const {
992 int id = data.class_id;
993
994 if (id < 0) {
995 // This means class ID hasn't been changed yet.
996 id = dbActor->class_id;
997 }
998
999 return lcf::ReaderUtil::GetElement(lcf::Data::classes, id);
1000 }
1001
ChangeClass(int new_class_id,int new_level,ClassChangeSkillMode new_skill,ClassChangeParamMode new_param,PendingMessage * pm)1002 void Game_Actor::ChangeClass(int new_class_id,
1003 int new_level,
1004 ClassChangeSkillMode new_skill,
1005 ClassChangeParamMode new_param,
1006 PendingMessage* pm
1007 )
1008 {
1009 const auto* cls = lcf::ReaderUtil::GetElement(lcf::Data::classes, new_class_id);
1010 if (new_class_id != 0 && cls == nullptr) {
1011 Output::Warning("Actor {}: Can't change to invalid class {}", GetId(), new_class_id);
1012 return;
1013 }
1014
1015 // RPG_RT always removes all equipment on level change.
1016 RemoveWholeEquipment();
1017
1018 const auto prev_level = GetLevel();
1019 const auto hp = GetHp();
1020 const auto sp = GetSp();
1021
1022 auto max_hp = GetBaseMaxHp();
1023 auto max_sp = GetBaseMaxSp();
1024 auto atk = GetBaseAtk();
1025 auto def = GetBaseDef();
1026 auto spi = GetBaseSpi();
1027 auto agi = GetBaseAgi();
1028
1029 SetLevel(1);
1030 data.hp_mod = 0;
1031 data.sp_mod = 0;
1032 data.attack_mod = 0;
1033 data.defense_mod = 0;
1034 data.spirit_mod = 0;
1035 data.agility_mod = 0;
1036
1037 data.class_id = new_class_id;
1038 data.changed_battle_commands = true; // Any change counts as a battle commands change.
1039
1040 // The class settings are not applied when the actor has a class on startup
1041 // but only when the "Change Class" event command is used.
1042
1043 if (cls) {
1044 data.super_guard = cls->super_guard;
1045 data.lock_equipment = cls->lock_equipment;
1046 data.two_weapon = cls->two_weapon;
1047 data.auto_battle = cls->auto_battle;
1048
1049 data.battler_animation = cls->battler_animation;
1050
1051 data.battle_commands = cls->battle_commands;
1052 } else {
1053 data.super_guard = dbActor->super_guard;
1054 data.lock_equipment = dbActor->lock_equipment;
1055 data.two_weapon = dbActor->two_weapon;
1056 data.auto_battle = dbActor->auto_battle;
1057
1058 data.battler_animation = 0;
1059
1060 data.battle_commands = dbActor->battle_commands;
1061 }
1062
1063 MakeExpList();
1064
1065 switch (new_param) {
1066 case eParamNoChange:
1067 break;
1068 case eParamHalf:
1069 max_hp /= 2;
1070 max_sp /= 2;
1071 atk /= 2;
1072 def /= 2;
1073 spi /= 2;
1074 agi /= 2;
1075 break;
1076 case eParamResetLevel1:
1077 max_hp = GetBaseMaxHp();
1078 max_sp = GetBaseMaxSp();
1079 atk = GetBaseAtk();
1080 def = GetBaseDef();
1081 spi = GetBaseSpi();
1082 agi = GetBaseAgi();
1083 break;
1084 case eParamReset:
1085 break;
1086 }
1087
1088 SetLevel(new_level);
1089 if (pm && new_level > 1 && (new_level > prev_level || new_skill != eSkillNoChange)) {
1090 pm->PushLine(GetLevelUpMessage(new_level));
1091 }
1092
1093 // RPG_RT always resets EXP when class is changed, even if level unchanged.
1094 SetExp(GetBaseExp());
1095
1096 if (new_param != eParamReset) {
1097 SetBaseMaxHp(max_hp);
1098 SetBaseMaxSp(max_sp);
1099 SetBaseAtk(atk);
1100 SetBaseDef(def);
1101 SetBaseSpi(spi);
1102 SetBaseAgi(agi);
1103 }
1104
1105 SetHp(hp);
1106 SetSp(sp);
1107
1108 switch (new_skill) {
1109 case eSkillNoChange:
1110 break;
1111 case eSkillReset:
1112 // RPG_RT has a bug where if (new_level == 1 && new_class_id == prev_class_id) no skills are removed.
1113 UnlearnAllSkills();
1114 // fall through
1115 case eSkillAdd:
1116 // RPG_RT has a bug where if (new_class_id == prev_class_id) level 1 skills are not learned.
1117 LearnLevelSkills(1, new_level, pm);
1118 break;
1119 }
1120 }
1121
GetClassName() const1122 StringView Game_Actor::GetClassName() const {
1123 if (!GetClass()) {
1124 return {};
1125 }
1126 return GetClass()->name;
1127 }
1128
ClampMaxHpMod(int hp,const Game_Actor * actor)1129 static int ClampMaxHpMod(int hp, const Game_Actor* actor) {
1130 auto limit = actor->MaxHpValue();
1131 return Utils::Clamp(hp, -limit, limit);
1132 }
1133
ClampMaxSpMod(int sp,const Game_Actor * actor)1134 static int ClampMaxSpMod(int sp, const Game_Actor* actor) {
1135 auto limit = actor->MaxSpValue();
1136 return Utils::Clamp(sp, -limit, limit);
1137 }
1138
ClampStatMod(int value,const Game_Actor * actor)1139 static int ClampStatMod(int value, const Game_Actor* actor) {
1140 auto limit = actor->MaxStatBaseValue();
1141 return Utils::Clamp(value, -limit, limit);
1142 }
1143
SetBaseMaxHp(int maxhp)1144 void Game_Actor::SetBaseMaxHp(int maxhp) {
1145 int new_hp_mod = data.hp_mod + (maxhp - GetBaseMaxHp());
1146 data.hp_mod = ClampMaxHpMod(new_hp_mod, this);
1147
1148 SetHp(data.current_hp);
1149 }
1150
SetBaseMaxSp(int maxsp)1151 void Game_Actor::SetBaseMaxSp(int maxsp) {
1152 int new_sp_mod = data.sp_mod + (maxsp - GetBaseMaxSp());
1153 data.sp_mod = ClampMaxSpMod(new_sp_mod, this);
1154
1155 SetSp(data.current_sp);
1156 }
1157
SetHp(int hp)1158 int Game_Actor::SetHp(int hp) {
1159 data.current_hp = Utils::Clamp(hp, 0, GetMaxHp());
1160 return data.current_hp;
1161 }
1162
SetSp(int sp)1163 int Game_Actor::SetSp(int sp) {
1164 data.current_sp = Utils::Clamp(sp, 0, GetMaxSp());
1165 return data.current_sp;
1166 }
1167
SetBaseAtk(int atk)1168 void Game_Actor::SetBaseAtk(int atk) {
1169 int new_attack_mod = data.attack_mod + (atk - GetBaseAtk());
1170 data.attack_mod = ClampStatMod(new_attack_mod, this);
1171 }
1172
SetBaseDef(int def)1173 void Game_Actor::SetBaseDef(int def) {
1174 int new_defense_mod = data.defense_mod + (def - GetBaseDef());
1175 data.defense_mod = ClampStatMod(new_defense_mod, this);
1176 }
1177
SetBaseSpi(int spi)1178 void Game_Actor::SetBaseSpi(int spi) {
1179 int new_spirit_mod = data.spirit_mod + (spi - GetBaseSpi());
1180 data.spirit_mod = ClampStatMod(new_spirit_mod, this);
1181 }
1182
SetBaseAgi(int agi)1183 void Game_Actor::SetBaseAgi(int agi) {
1184 int new_agility_mod = data.agility_mod + (agi - GetBaseAgi());
1185 data.agility_mod = ClampStatMod(new_agility_mod, this);
1186 }
1187
GetBattleRow() const1188 Game_Actor::RowType Game_Actor::GetBattleRow() const {
1189 return RowType(data.row);
1190 }
1191
SetBattleRow(RowType battle_row)1192 void Game_Actor::SetBattleRow(RowType battle_row) {
1193 data.row = int(battle_row);
1194 }
1195
GetBattleAnimationId() const1196 int Game_Actor::GetBattleAnimationId() const {
1197 if (Player::IsRPG2k()) {
1198 return 0;
1199 }
1200
1201 int anim = 0;
1202
1203 if (data.battler_animation <= 0) {
1204 // Earlier versions of EasyRPG didn't save this value correctly
1205
1206 // The battle animation of the class only matters when the class was
1207 // changed by event "Change Class"
1208 if ((data.class_id > 0) && GetClass()) {
1209 anim = GetClass()->battler_animation;
1210 } else {
1211 const lcf::rpg::BattlerAnimation* anima = lcf::ReaderUtil::GetElement(lcf::Data::battleranimations, dbActor->battler_animation);
1212 if (!anima) {
1213 Output::Warning("Actor {}: Invalid battle animation ID {}", GetId(), dbActor->battler_animation);
1214 return 0;
1215 }
1216
1217 anim = anima->ID;
1218 }
1219 } else {
1220 anim = data.battler_animation;
1221 }
1222
1223 if (anim == 0) {
1224 // Chunk was missing, set to proper default
1225 return 1;
1226 }
1227
1228 return anim;
1229 }
1230
GetHitChance(Weapon weapon) const1231 int Game_Actor::GetHitChance(Weapon weapon) const {
1232 int hit = INT_MIN;
1233 ForEachEquipment<true, false>(GetWholeEquipment(), [&](auto& item) { hit = std::max(hit, static_cast<int>(item.hit)); }, weapon);
1234
1235 return hit != INT_MIN ? hit : 90;
1236 }
1237
GetCriticalHitChance(Weapon weapon) const1238 float Game_Actor::GetCriticalHitChance(Weapon weapon) const {
1239 float crit_chance = dbActor->critical_hit ? 1.0f / dbActor->critical_hit_chance : 0.0f;
1240
1241 float bonus = 0;
1242 ForEachEquipment<true, false>(GetWholeEquipment(), [&](auto& item) { bonus = std::max(bonus, static_cast<float>(item.critical_hit)); }, weapon);
1243 return crit_chance + (bonus / 100.0f);
1244 }
1245
IsControllable() const1246 int Game_Actor::IsControllable() const {
1247 return GetSignificantRestriction() == lcf::rpg::State::Restriction_normal && !GetAutoBattle();
1248 }
1249
1250
RemoveInvalidData()1251 void Game_Actor::RemoveInvalidData() {
1252 /*
1253 The following actor data is cleaned up:
1254 - Invalid equipment is removed
1255 - An invalid class is removed
1256 - Invalid states are removed
1257 - Level is between 0 and 99, and does not exceed MaxLevel
1258
1259 For "external data" (not from LCF Actor or LSD SaveActor) the data is
1260 verified in the corresponding functions.
1261 */
1262
1263 // Filter out invalid equipment
1264 int eq_types[] = { lcf::rpg::Item::Type_weapon,
1265 HasTwoWeapons() ? lcf::rpg::Item::Type_weapon : lcf::rpg::Item::Type_shield,
1266 lcf::rpg::Item::Type_armor,
1267 lcf::rpg::Item::Type_helmet,
1268 lcf::rpg::Item::Type_accessory
1269 };
1270
1271 auto& equipment = GetWholeEquipment();
1272 for (size_t i = 0; i < equipment.size(); ++i) {
1273 int eq_id = equipment[i];
1274 lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, eq_id);
1275
1276 if (!item && eq_id != 0) {
1277 Output::Debug("Actor {}: Removing invalid item {} from equipment slot {}",
1278 GetId(), eq_id, eq_types[i]);
1279 SetEquipment(i + 1, 0);
1280 } else if (item && item->type != eq_types[i]) {
1281 Output::Debug("Actor {}: Removing item {} (of type {}) from equipment slot {} (needs type {})",
1282 GetId(), item->ID, item->type, i + 1, eq_types[i]);
1283 SetEquipment(i + 1, 0);
1284 } else if (item && !IsItemUsable(item->ID)) {
1285 Output::Debug("Actor {}: Removing item {} from equipment slot {} (Not equippable by this actor)",
1286 GetId(), item->ID, i + 1);
1287 SetEquipment(i + 1, 0);
1288 }
1289 }
1290
1291 // Remove invalid class
1292 if (data.class_id > 0) {
1293 const lcf::rpg::Class* cls = lcf::ReaderUtil::GetElement(lcf::Data::classes, data.class_id);
1294 if (!cls) {
1295 Output::Warning("Actor {}: Removing invalid class {}", GetId(), data.class_id);
1296 ChangeClass(0, GetLevel(), eSkillNoChange, eParamNoChange, nullptr);
1297 }
1298 }
1299
1300 // Remove invalid skills
1301 for (int16_t skill_id : GetSkills()) {
1302 const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
1303 if (!skill) {
1304 Output::Warning("Actor {}: Removing invalid skill {}", GetId(), skill_id);
1305 UnlearnSkill(skill_id);
1306 }
1307 }
1308
1309 // Remove invalid states
1310 if (GetStates().size() > lcf::Data::states.size()) {
1311 Output::Warning("Actor {}: State array contains invalid states ({} > {})", GetId(), GetStates().size(), lcf::Data::states.size());
1312 GetStates().resize(lcf::Data::states.size());
1313 }
1314
1315 // Remove invalid levels
1316 // Special handling for the game COLORS: Lost Memories which uses level 0
1317 // through database editing. Hopefully no game uses negative levels.
1318 if (GetLevel() == 0) {
1319 Output::Debug("Actor {}: Special handling for level 0", GetId());
1320 } else if (GetLevel() < 0) {
1321 Output::Warning("Actor {}: Invalid level {}, changed to 1", GetId(), GetLevel());
1322 SetLevel(1);
1323 } else if (GetLevel() > GetMaxLevel()) {
1324 Output::Warning("Actor {}: Invalid level {}, changed to {}", GetId(), GetLevel(), GetMaxLevel());
1325 SetLevel(GetMaxLevel());
1326 }
1327 }
1328
GetWeapon() const1329 const lcf::rpg::Item* Game_Actor::GetWeapon() const {
1330 auto* weapon = GetEquipment(lcf::rpg::Item::Type_weapon);
1331 if (weapon && weapon->type == lcf::rpg::Item::Type_weapon) {
1332 return weapon;
1333 }
1334 return nullptr;
1335 }
1336
Get2ndWeapon() const1337 const lcf::rpg::Item* Game_Actor::Get2ndWeapon() const {
1338 // Checking of HasTwoWeapons() not neccessary. If true, the
1339 // item equipped in this slot will never be a weapon from
1340 // legitimate means.
1341 auto* weapon = GetEquipment(lcf::rpg::Item::Type_shield);
1342 if (weapon && weapon->type == lcf::rpg::Item::Type_weapon) {
1343 return weapon;
1344 }
1345 return nullptr;
1346 }
1347
GetShield() const1348 const lcf::rpg::Item* Game_Actor::GetShield() const {
1349 auto* shield = GetEquipment(lcf::rpg::Item::Type_shield);
1350 if (shield && shield->type == lcf::rpg::Item::Type_shield) {
1351 return shield;
1352 }
1353 return nullptr;
1354 }
1355
GetArmor() const1356 const lcf::rpg::Item* Game_Actor::GetArmor() const {
1357 auto* armor = GetEquipment(lcf::rpg::Item::Type_armor);
1358 if (armor && armor->type == lcf::rpg::Item::Type_armor) {
1359 return armor;
1360 }
1361 return nullptr;
1362 }
1363
GetHelmet() const1364 const lcf::rpg::Item* Game_Actor::GetHelmet() const {
1365 auto* helmet = GetEquipment(lcf::rpg::Item::Type_helmet);
1366 if (helmet && helmet->type == lcf::rpg::Item::Type_helmet) {
1367 return helmet;
1368 }
1369 return nullptr;
1370 }
1371
GetAccessory() const1372 const lcf::rpg::Item* Game_Actor::GetAccessory() const {
1373 auto* accessory = GetEquipment(lcf::rpg::Item::Type_accessory);
1374 if (accessory && accessory->type == lcf::rpg::Item::Type_accessory) {
1375 return accessory;
1376 }
1377 return nullptr;
1378 }
1379
HasPreemptiveAttack(Weapon weapon) const1380 bool Game_Actor::HasPreemptiveAttack(Weapon weapon) const {
1381 bool rc = false;
1382 ForEachEquipment<true, false>(GetWholeEquipment(), [&](auto& item) { rc |= item.preemptive; }, weapon);
1383 return rc;
1384 }
1385
GetNumberOfAttacks(Weapon weapon) const1386 int Game_Actor::GetNumberOfAttacks(Weapon weapon) const {
1387 int hits = 1;
1388 ForEachEquipment<true, false>(GetWholeEquipment(), [&](auto& item) { hits = std::max(hits, Algo::GetNumberOfAttacks(GetId(), item)); }, weapon);
1389 return hits;
1390 }
1391
HasAttackAll(Weapon weapon) const1392 bool Game_Actor::HasAttackAll(Weapon weapon) const {
1393 bool rc = false;
1394 ForEachEquipment<true, false>(GetWholeEquipment(), [&](auto& item) { rc |= item.attack_all; }, weapon);
1395 return rc;
1396 }
1397
AttackIgnoresEvasion(Weapon weapon) const1398 bool Game_Actor::AttackIgnoresEvasion(Weapon weapon) const {
1399 bool rc = false;
1400 ForEachEquipment<true, false>(GetWholeEquipment(), [&](auto& item) { rc |= item.ignore_evasion; }, weapon);
1401 return rc;
1402 }
1403
PreventsCritical() const1404 bool Game_Actor::PreventsCritical() const {
1405 bool rc = false;
1406 ForEachEquipment<false, true>(GetWholeEquipment(), [&](auto& item) { rc |= item.prevent_critical; });
1407 return rc;
1408 }
1409
PreventsTerrainDamage() const1410 bool Game_Actor::PreventsTerrainDamage() const {
1411 bool rc = false;
1412 ForEachEquipment<false, true>(GetWholeEquipment(), [&](auto& item) { rc |= item.no_terrain_damage; });
1413 return rc;
1414 }
1415
HasPhysicalEvasionUp() const1416 bool Game_Actor::HasPhysicalEvasionUp() const {
1417 bool rc = false;
1418 ForEachEquipment<false, true>(GetWholeEquipment(), [&](auto& item) { rc |= item.raise_evasion; });
1419 return rc;
1420 }
1421
HasHalfSpCost() const1422 bool Game_Actor::HasHalfSpCost() const {
1423 bool rc = false;
1424 ForEachEquipment<false, true>(GetWholeEquipment(), [&](auto& item) { rc |= item.half_sp_cost; });
1425 return rc;
1426 }
1427
CalculateWeaponSpCost(Weapon weapon) const1428 int Game_Actor::CalculateWeaponSpCost(Weapon weapon) const {
1429 int cost = 0;
1430 ForEachEquipment<true, false>(GetWholeEquipment(), [&](auto& item) { cost += item.sp_cost; }, weapon);
1431 if (HasHalfSpCost()) {
1432 cost = (cost + 1) / 2;
1433 }
1434
1435 return cost;
1436 }
1437
AdjustEquipmentStates(const lcf::rpg::Item * item,bool add,bool allow_battle_states)1438 void Game_Actor::AdjustEquipmentStates(const lcf::rpg::Item* item, bool add, bool allow_battle_states) {
1439 // All states inflicted by new armor get inflicted.
1440 if (Player::IsRPG2k3()
1441 && item
1442 && IsArmorType(item)
1443 && item->reverse_state_effect)
1444 {
1445 auto& states = item->state_set;
1446 for (int i = 0; i < (int)states.size(); ++i) {
1447 if (states[i]) {
1448 if (add) {
1449 AddState(i + 1, allow_battle_states);
1450 } else {
1451 RemoveState(i + 1, false);
1452 }
1453 }
1454 }
1455 }
1456 }
1457
1458
ResetEquipmentStates(bool allow_battle_states)1459 void Game_Actor::ResetEquipmentStates(bool allow_battle_states) {
1460 AdjustEquipmentStates(GetShield(), true, allow_battle_states);
1461 AdjustEquipmentStates(GetArmor(), true, allow_battle_states);
1462 AdjustEquipmentStates(GetHelmet(), true, allow_battle_states);
1463 AdjustEquipmentStates(GetAccessory(), true, allow_battle_states);
1464 }
1465
GetPermanentStates() const1466 PermanentStates Game_Actor::GetPermanentStates() const {
1467 PermanentStates ps;
1468 if (!Player::IsRPG2k3()) {
1469 return ps;
1470 }
1471
1472 auto addEquip = [&](const lcf::rpg::Item* item) {
1473 if (!item || !IsArmorType(item) || !item->reverse_state_effect) {
1474 return;
1475 }
1476 auto& states = item->state_set;
1477 // Invalid states in armor already reported earlier in
1478 // calls to AdjustEquipmentStates.
1479 int num_states = std::min<int>(states.size(), lcf::Data::states.size());
1480 for (int i = 0; i < num_states; ++i) {
1481 if (states[i]) {
1482 ps.Add(i + 1);
1483 }
1484 }
1485 };
1486
1487 addEquip(GetShield());
1488 addEquip(GetArmor());
1489 addEquip(GetHelmet());
1490 addEquip(GetAccessory());
1491
1492 return ps;
1493 }
1494
IsInParty() const1495 bool Game_Actor::IsInParty() const {
1496 return Main_Data::game_party->IsActorInParty(GetId());
1497 }
1498
GetWeapons(Game_Battler::Weapon weapon) const1499 std::array<const lcf::rpg::Item*, 2> Game_Actor::GetWeapons(Game_Battler::Weapon weapon) const {
1500 std::array<const lcf::rpg::Item*, 2> w = {{}};
1501 int i = 0;
1502 if (weapon == Game_Battler::WeaponPrimary || weapon == Game_Battler::WeaponAll) {
1503 w[i] = GetWeapon();
1504 if (w[i]) { ++i; }
1505 }
1506 if (weapon == Game_Battler::WeaponSecondary || weapon == Game_Battler::WeaponAll) {
1507 w[i] = Get2ndWeapon();
1508 }
1509 return w;
1510 }
1511
1512
UpdateBattle()1513 void Game_Actor::UpdateBattle() {
1514 Game_Battler::UpdateBattle();
1515 auto* sprite = GetActorBattleSprite();
1516 if (sprite) {
1517 sprite->Update();
1518 }
1519 auto* weapon = Game_Battler::GetWeaponSprite();
1520 if (weapon) {
1521 weapon->Update();
1522 }
1523 }
1524