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 #define _USE_MATH_DEFINES
20 #include <algorithm>
21 #include <cassert>
22 #include <cmath>
23 #include <limits>
24 #include "player.h"
25 #include "game_battler.h"
26 #include "game_actor.h"
27 #include "game_enemyparty.h"
28 #include "game_battle.h"
29 #include "game_party.h"
30 #include "game_party_base.h"
31 #include "game_player.h"
32 #include "game_switches.h"
33 #include "game_system.h"
34 #include "game_targets.h"
35 #include "game_screen.h"
36 #include "util_macro.h"
37 #include "main_data.h"
38 #include "utils.h"
39 #include "output.h"
40 #include <lcf/reader_util.h>
41 #include "game_battlealgorithm.h"
42 #include "state.h"
43 #include "shake.h"
44 #include "attribute.h"
45 #include "algo.h"
46 #include "rand.h"
47
Game_Battler()48 Game_Battler::Game_Battler() {
49 }
50
HasState(int state_id) const51 bool Game_Battler::HasState(int state_id) const {
52 return State::Has(state_id, GetStates());
53 }
54
GetInflictedStates() const55 std::vector<int16_t> Game_Battler::GetInflictedStates() const {
56 auto& states = GetStates();
57 std::vector<int16_t> inf_states;
58 for (size_t i = 0; i < states.size(); ++i) {
59 if (states[i] > 0) {
60 inf_states.push_back(i + 1);
61 }
62 }
63 return inf_states;
64 }
65
GetPermanentStates() const66 PermanentStates Game_Battler::GetPermanentStates() const {
67 return PermanentStates();
68 }
69
EvadesAllPhysicalAttacks() const70 bool Game_Battler::EvadesAllPhysicalAttacks() const {
71 for (auto state_id: GetInflictedStates()) {
72 auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, state_id);
73 if (state && state->avoid_attacks) {
74 return true;
75 }
76 }
77 return false;
78 }
79
GetSignificantRestriction() const80 lcf::rpg::State::Restriction Game_Battler::GetSignificantRestriction() const {
81 return State::GetSignificantRestriction(GetStates());
82 }
83
CanAct() const84 bool Game_Battler::CanAct() const {
85 const auto& states = GetStates();
86 for (size_t i = 0; i < states.size(); ++i) {
87 if (states[i] > 0) {
88 const auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, i + 1);
89 assert(state);
90 if (state->restriction == lcf::rpg::State::Restriction_do_nothing) {
91 return false;
92 }
93 }
94 }
95 return true;
96 }
97
CanActOrRecoverable() const98 bool Game_Battler::CanActOrRecoverable() const {
99 const auto& states = GetStates();
100 for (size_t i = 0; i < states.size(); ++i) {
101 if (states[i] > 0) {
102 const auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, i + 1);
103 assert(state);
104 if (state->restriction == lcf::rpg::State::Restriction_do_nothing && state->auto_release_prob == 0) {
105 return false;
106 }
107 }
108 }
109 return true;
110 }
111
GetSignificantState() const112 const lcf::rpg::State* Game_Battler::GetSignificantState() const {
113 return State::GetSignificantState(GetStates());
114 }
115
GetStateRate(int state_id,int rate) const116 int Game_Battler::GetStateRate(int state_id, int rate) const {
117 return State::GetStateRate(state_id, rate);
118 }
119
IsSkillUsable(int skill_id) const120 bool Game_Battler::IsSkillUsable(int skill_id) const {
121 const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
122
123 if (!skill) {
124 Output::Warning("IsSkillUsable: Invalid skill ID {}", skill_id);
125 return false;
126 }
127
128 if (CalculateSkillCost(skill_id) > GetSp()) {
129 return false;
130 }
131
132 for (auto state_id: GetInflictedStates()) {
133 const auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, state_id);
134 if (state) {
135 if (state->restrict_skill && skill->physical_rate >= state->restrict_skill_level && !skill->easyrpg_ignore_restrict_skill) {
136 return false;
137 }
138 if (state->restrict_magic && skill->magical_rate >= state->restrict_magic_level && !skill->easyrpg_ignore_restrict_magic) {
139 return false;
140 }
141 }
142 }
143
144 return Algo::IsSkillUsable(*skill, true);
145 }
146
UseItem(int item_id,const Game_Battler * source)147 bool Game_Battler::UseItem(int item_id, const Game_Battler* source) {
148 const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
149 if (!item) {
150 Output::Warning("UseItem: Can't use item with invalid ID {}", item_id);
151 return false;
152 }
153
154 if (item->type == lcf::rpg::Item::Type_medicine) {
155 bool was_used = false;
156 int revived = 0;
157 int hp_change = item->recover_hp_rate * GetMaxHp() / 100 + item->recover_hp;
158 int sp_change = item->recover_sp_rate * GetMaxSp() / 100 + item->recover_sp;
159
160 if (IsDead()) {
161 // Check if item can revive
162 if (item->state_set.empty() || !item->state_set[0]) {
163 return false;
164 }
165 } else if (item->ko_only) {
166 // Must be dead
167 return false;
168 }
169
170 for (int i = 0; i < (int)item->state_set.size(); i++) {
171 if (item->state_set[i]) {
172 was_used |= HasState(lcf::Data::states[i].ID);
173 if (i == 0 && HasState(i + 1))
174 revived = 1;
175 RemoveState(lcf::Data::states[i].ID, false);
176 }
177 }
178
179 if (hp_change > 0 && !HasFullHp()) {
180 ChangeHp(hp_change - revived, false);
181 was_used = true;
182 }
183
184 if (sp_change > 0 && !HasFullSp()) {
185 ChangeSp(sp_change);
186 was_used = true;
187 }
188
189 return was_used;
190 }
191
192 if (item->type == lcf::rpg::Item::Type_switch) {
193 return true;
194 }
195
196 bool do_skill = (item->type == lcf::rpg::Item::Type_special)
197 || (item->use_skill && (
198 item->type == lcf::rpg::Item::Type_weapon
199 || item->type == lcf::rpg::Item::Type_shield
200 || item->type == lcf::rpg::Item::Type_armor
201 || item->type == lcf::rpg::Item::Type_helmet
202 || item->type == lcf::rpg::Item::Type_accessory
203 )
204 );
205
206 if (do_skill) {
207 auto* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, item->skill_id);
208 if (skill == nullptr) {
209 Output::Warning("UseItem: Can't use item {} skill with invalid ID {}", item->ID, item->skill_id);
210 return false;
211 }
212 return UseSkill(item->skill_id, source);
213 }
214
215 return false;
216 }
217
UseSkill(int skill_id,const Game_Battler * source)218 bool Game_Battler::UseSkill(int skill_id, const Game_Battler* source) {
219 const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
220 if (!skill) {
221 Output::Warning("UseSkill: Can't use skill with invalid ID {}", skill_id);
222 return false;
223 }
224
225 bool cure_hp_percentage = false;
226 bool was_used = false;
227 int revived = 0;
228
229 if (Algo::IsNormalOrSubskill(*skill)) {
230 // Only takes care of healing skills outside of battle,
231 // the other skill logic is in Game_BattleAlgorithm
232
233 if (!(skill->scope == lcf::rpg::Skill::Scope_ally ||
234 skill->scope == lcf::rpg::Skill::Scope_party ||
235 skill->scope == lcf::rpg::Skill::Scope_self)) {
236 return false;
237 }
238
239 // Calculate effect:
240 auto effect = Algo::CalcSkillEffect(*source, *this, *skill, true);
241
242 // Negative attributes do damage but cannot kill
243 bool negative_effect = false;
244 if (effect < 0) {
245 negative_effect = true;
246 effect = -effect;
247 }
248
249 // Cure states
250 for (int i = 0; i < (int)skill->state_effects.size(); i++) {
251 if (skill->state_effects[i]) {
252 if (skill->reverse_state_effect) {
253 was_used |= !HasState(lcf::Data::states[i].ID);
254 AddState(lcf::Data::states[i].ID, true);
255 }
256 else {
257 if (i == 0 && IsDead()) {
258 revived = 1;
259 }
260
261 was_used |= HasState(lcf::Data::states[i].ID);
262 RemoveState(lcf::Data::states[i].ID, false);
263
264 // If Death is cured and HP is not selected, we set a bool so it later heals HP percentage
265 if (i == 0 && !skill->affect_hp && revived) {
266 cure_hp_percentage = true;
267 }
268 }
269 }
270 }
271
272 // Skills only increase hp and sp outside of battle
273 if (!negative_effect) {
274 if (effect > 0 && skill->affect_hp && !HasFullHp() && !IsDead()) {
275 was_used = true;
276 ChangeHp(effect - revived, false);
277 }
278 else if (effect > 0 && cure_hp_percentage) {
279 was_used = true;
280 ChangeHp(GetMaxHp() * effect / 100 - revived, false);
281 }
282
283 if (effect > 0 && skill->affect_sp && !HasFullSp() && !IsDead()) {
284 was_used = true;
285 ChangeSp(effect);
286 }
287 } else {
288 if (effect > 0 && skill->affect_hp && !IsDead()) {
289 was_used = true;
290 ChangeHp(-effect, false);
291 }
292
293 if (effect > 0 && skill->affect_sp && !IsDead()) {
294 was_used = true;
295 ChangeSp(-effect);
296 }
297 }
298
299 } else if (skill->type == lcf::rpg::Skill::Type_teleport || skill->type == lcf::rpg::Skill::Type_escape) {
300 Main_Data::game_system->SePlay(skill->sound_effect);
301 was_used = true;
302 } else if (skill->type == lcf::rpg::Skill::Type_switch) {
303 Main_Data::game_system->SePlay(skill->sound_effect);
304 Main_Data::game_switches->Set(skill->switch_id, true);
305 was_used = true;
306 }
307
308 return was_used;
309 }
310
CalculateSkillCost(int skill_id) const311 int Game_Battler::CalculateSkillCost(int skill_id) const {
312 const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
313 if (!skill) {
314 Output::Warning("CalculateSkillCost: Invalid skill ID {}", skill_id);
315 return 0;
316 }
317 return Algo::CalcSkillCost(*skill, GetMaxSp(), false);
318 }
319
AddState(int state_id,bool allow_battle_states)320 bool Game_Battler::AddState(int state_id, bool allow_battle_states) {
321 auto was_added = State::Add(state_id, GetStates(), GetPermanentStates(), allow_battle_states);
322
323 if (!was_added) {
324 return was_added;
325 }
326
327 if (state_id == lcf::rpg::State::kDeathID) {
328 SetAtbGauge(0);
329 SetHp(0);
330 SetAtkModifier(0);
331 SetDefModifier(0);
332 SetSpiModifier(0);
333 SetAgiModifier(0);
334 SetIsDefending(false);
335 SetCharged(false);
336 attribute_shift.clear();
337 }
338
339 if (!Game_Battle::IsBattleRunning()) {
340 return was_added;
341 }
342
343 if (GetSignificantRestriction() != lcf::rpg::State::Restriction_normal) {
344 SetIsDefending(false);
345 SetCharged(false);
346 if (GetBattleAlgorithm() != nullptr
347 && GetBattleAlgorithm()->GetType() != Game_BattleAlgorithm::Type::None) {
348 this->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(this));
349 }
350 }
351
352 if (GetBattleAlgorithm() != nullptr && GetBattleAlgorithm()->GetType() == Game_BattleAlgorithm::Type::Skill) {
353 auto* algo = static_cast<Game_BattleAlgorithm::Skill*>(GetBattleAlgorithm().get());
354 auto& skill = algo->GetSkill();
355 if (!IsSkillUsable(skill.ID)) {
356 SetCharged(false);
357 this->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(this));
358 }
359 }
360
361 return was_added;
362 }
363
364 template<typename F>
RemoveStates(Game_Battler & battler,F && f)365 bool RemoveStates(Game_Battler& battler, F&& f) {
366 auto prev_restriction = battler.GetSignificantRestriction();
367
368 auto check_dead = [&]() {
369 // Cannot use IsDead here, as it checks for HP == 0
370 return State::Has(lcf::rpg::State::kDeathID, battler.GetStates());
371 };
372
373 bool is_dead = check_dead();
374 bool was_removed = f();
375 if (was_removed) {
376 if (is_dead != check_dead()) {
377 // Was revived
378 battler.SetHp(1);
379 }
380
381 auto cur_restriction = battler.GetSignificantRestriction();
382 if (battler.GetBattleAlgorithm() != nullptr
383 && battler.GetBattleAlgorithm()->GetType() != Game_BattleAlgorithm::Type::None
384 && cur_restriction != prev_restriction) {
385 battler.SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(&battler));
386 }
387 }
388 return was_removed;
389 }
390
RemoveBattleStates()391 void Game_Battler::RemoveBattleStates() {
392 RemoveStates(*this, [&]() {
393 return State::RemoveAllBattle(GetStates(), GetPermanentStates());
394 });
395 }
396
RemoveAllStates()397 void Game_Battler::RemoveAllStates() {
398 RemoveStates(*this, [&]() {
399 return State::RemoveAll(GetStates(), GetPermanentStates());
400 });
401 }
402
RemoveState(int state_id,bool always_remove_battle_states)403 bool Game_Battler::RemoveState(int state_id, bool always_remove_battle_states) {
404 return RemoveStates(*this, [&]() {
405 PermanentStates ps;
406
407 auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, state_id);
408 if (!(always_remove_battle_states && state && state->type == lcf::rpg::State::Persistence_ends)) {
409 ps = GetPermanentStates();
410 }
411
412 return State::Remove(state_id, GetStates(), ps);
413 });
414 }
415
ApplyConditions()416 int Game_Battler::ApplyConditions() {
417 int damageTaken = 0;
418 for (int16_t inflicted : GetInflictedStates()) {
419 // States are guaranteed to be valid
420 lcf::rpg::State& state = *lcf::ReaderUtil::GetElement(lcf::Data::states, inflicted);
421 int hp = state.hp_change_val + (GetMaxHp() * state.hp_change_max / 100);
422 int sp = state.sp_change_val + (GetMaxSp() * state.sp_change_max / 100);
423 int src_hp = 0;
424 int src_sp = 0;
425 if (state.hp_change_type == state.ChangeType_lose) {
426 src_hp = -hp;
427 if(src_hp > 0) {
428 src_hp = 0;
429 }
430 }
431 else if(state.hp_change_type == state.ChangeType_gain) {
432 src_hp = hp;
433 if(src_hp < 0) {
434 src_hp = 0;
435 }
436 }
437 else {
438 src_hp = 0;
439 }
440 if (state.sp_change_type == state.ChangeType_lose) {
441 src_sp = -sp;
442 if(src_sp > 0) {
443 src_sp = 0;
444 }
445
446 }
447 else if (state.sp_change_type == state.ChangeType_gain) {
448 src_sp = sp;
449 if(src_sp < 0) {
450 src_sp = 0;
451 }
452 }
453 else {
454 src_sp = 0;
455 }
456 this->ChangeHp(src_hp, false);
457 this->ChangeSp(src_sp);
458 damageTaken += src_hp;
459 }
460
461 return damageTaken;
462 }
463
ChangeHp(int hp,bool lethal)464 int Game_Battler::ChangeHp(int hp, bool lethal) {
465 if (IsDead()) {
466 return 0;
467 }
468 const auto prev_hp = GetHp();
469 auto req_new_hp = prev_hp + hp;
470 if (!lethal) {
471 req_new_hp = std::max(1, req_new_hp);
472 }
473 auto new_hp = SetHp(req_new_hp);
474
475 // Death
476 if (new_hp <= 0) {
477 AddState(lcf::rpg::State::kDeathID, true);
478 }
479 return new_hp - prev_hp;
480 }
481
GetMaxHp() const482 int Game_Battler::GetMaxHp() const {
483 return GetBaseMaxHp();
484 }
485
HasFullHp() const486 bool Game_Battler::HasFullHp() const {
487 return GetMaxHp() == GetHp();
488 }
489
ChangeSp(int sp)490 int Game_Battler::ChangeSp(int sp) {
491 const auto prev_sp = GetSp();
492 const auto new_sp = SetSp(prev_sp + sp);
493 return new_sp - prev_sp;
494 }
495
GetMaxSp() const496 int Game_Battler::GetMaxSp() const {
497 return GetBaseMaxSp();
498 }
499
HasFullSp() const500 bool Game_Battler::HasFullSp() const {
501 return GetMaxSp() == GetSp();
502 }
503
AdjustParam(int base,int mod,int maxval,Span<const int16_t> states,bool lcf::rpg::State::* adj)504 static int AdjustParam(int base, int mod, int maxval, Span<const int16_t> states, bool lcf::rpg::State::*adj) {
505 auto value = Utils::Clamp(base + mod, 1, maxval);
506 bool half = false;
507 bool dbl = false;
508 for (auto i: states) {
509 const auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, i);
510 assert(state);
511 if (state->*adj) {
512 half |= (state->affect_type == lcf::rpg::State::AffectType_half);
513 dbl |= (state->affect_type == lcf::rpg::State::AffectType_double);
514 }
515 }
516 if (dbl != half) {
517 if (dbl) {
518 value *= 2;
519 } else {
520 value = std::max(1, value / 2);
521 }
522 }
523 // NOTE: RPG_RT does not clamp these values to the upper range!
524 // Exceptions:
525 // * 2k3 special function which computes atk for individual weapons / dual wield dmg does clamp at the end
526 // * 2k3 special function which computes agi for individual weapons / dual wield hit ratio does clamp, but also has a bug where it ignores states which modify agi!
527 return value;
528 }
529
CalcValueAfterAtkStates(int value) const530 int Game_Battler::CalcValueAfterAtkStates(int value) const {
531 return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_attack);
532 }
533
CalcValueAfterDefStates(int value) const534 int Game_Battler::CalcValueAfterDefStates(int value) const {
535 return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_defense);
536 }
537
CalcValueAfterSpiStates(int value) const538 int Game_Battler::CalcValueAfterSpiStates(int value) const {
539 return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_spirit);
540 }
541
CalcValueAfterAgiStates(int value) const542 int Game_Battler::CalcValueAfterAgiStates(int value) const {
543 return AdjustParam(value, 0, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_agility);
544 }
545
GetAtk(Weapon weapon) const546 int Game_Battler::GetAtk(Weapon weapon) const {
547 return AdjustParam(GetBaseAtk(weapon), atk_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_attack);
548 }
549
GetDef(Weapon weapon) const550 int Game_Battler::GetDef(Weapon weapon) const {
551 return AdjustParam(GetBaseDef(weapon), def_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_defense);
552 }
553
GetSpi(Weapon weapon) const554 int Game_Battler::GetSpi(Weapon weapon) const {
555 return AdjustParam(GetBaseSpi(weapon), spi_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_spirit);
556 }
557
GetAgi(Weapon weapon) const558 int Game_Battler::GetAgi(Weapon weapon) const {
559 return AdjustParam(GetBaseAgi(weapon), agi_modifier, MaxStatBattleValue(), GetInflictedStates(), &lcf::rpg::State::affect_agility);
560 }
561
GetDisplayX() const562 int Game_Battler::GetDisplayX() const {
563 int shake_pos = Main_Data::game_screen->GetShakeOffsetX() + shake.position;
564 return (GetBattlePosition().x + shake_pos) * SCREEN_TARGET_WIDTH / 320;
565 }
566
GetDisplayY() const567 int Game_Battler::GetDisplayY() const {
568 int shake_pos = Main_Data::game_screen->GetShakeOffsetY();
569 return (GetBattlePosition().y + GetFlyingOffset() + shake_pos) * SCREEN_TARGET_HEIGHT / 240;
570 }
571
GetParty() const572 Game_Party_Base& Game_Battler::GetParty() const {
573 if (GetType() == Type_Ally) {
574 return *Main_Data::game_party;
575 } else {
576 return *Main_Data::game_enemyparty;
577 }
578 }
579
UpdateBattle()580 void Game_Battler::UpdateBattle() {
581 Shake::Update(shake.position, shake.time_left, shake.strength, shake.speed, false);
582 Flash::Update(flash.current_level, flash.time_left);
583 ++frame_counter;
584 }
585
BattleStateHeal()586 std::vector<int16_t> Game_Battler::BattleStateHeal() {
587 std::vector<int16_t> healed_states;
588 std::vector<int16_t>& states = GetStates();
589
590 for (size_t i = 0; i < states.size(); ++i) {
591 if (HasState(i + 1)) {
592 if (states[i] > lcf::Data::states[i].hold_turn
593 && Rand::ChanceOf(lcf::Data::states[i].auto_release_prob, 100)
594 && RemoveState(i + 1, false)
595 ) {
596 healed_states.push_back(i + 1);
597 } else {
598 ++states[i];
599 }
600 }
601 }
602
603 return healed_states;
604 }
605
HasReflectState() const606 bool Game_Battler::HasReflectState() const {
607 for (int16_t i : GetInflictedStates()) {
608 // States are guaranteed to be valid
609 if (lcf::ReaderUtil::GetElement(lcf::Data::states, i)->reflect_magic) {
610 return true;
611 }
612 }
613
614 return false;
615 }
616
ResetBattle()617 void Game_Battler::ResetBattle() {
618 // Note: ATB gauge is not reset here. This is on purpose because RPG_RT will freeze
619 // the gauge and carry it between battles if !CanActOrRecoverable().
620 SetCharged(false);
621 SetIsDefending(false);
622 SetHidden(false);
623 SetDirectionFlipped(false);
624 battle_turn = 0;
625 last_battle_action = -1;
626 atk_modifier = 0;
627 def_modifier = 0;
628 spi_modifier = 0;
629 agi_modifier = 0;
630 frame_counter = Rand::GetRandomNumber(0, 63);
631 battle_combo_command_id = -1;
632 battle_combo_times = 1;
633 attribute_shift.clear();
634 SetBattleAlgorithm(nullptr);
635 SetBattleSprite(nullptr);
636 SetWeaponSprite(nullptr);
637 }
638
GetAttributeRate(int attribute_id) const639 int Game_Battler::GetAttributeRate(int attribute_id) const {
640 auto rate = GetBaseAttributeRate(attribute_id);
641 rate += GetAttributeRateShift(attribute_id);
642 return Utils::Clamp(rate, 0, 4);
643 }
644
ShiftAttributeRate(int attribute_id,int shift)645 int Game_Battler::ShiftAttributeRate(int attribute_id, int shift) {
646 auto delta = CanShiftAttributeRate(attribute_id, shift);
647 if (delta) {
648 if (attribute_id > static_cast<int>(attribute_shift.size())) {
649 attribute_shift.resize(attribute_id);
650 }
651 attribute_shift[attribute_id - 1] += delta;
652 }
653 return delta;
654 }
655
GetAttributeRateShift(int attribute_id) const656 int Game_Battler::GetAttributeRateShift(int attribute_id) const {
657 if (attribute_id < 1 || attribute_id > static_cast<int>(attribute_shift.size())) {
658 return 0;
659 }
660 return attribute_shift[attribute_id - 1];
661 }
662
CanShiftAttributeRate(int attribute_id,int shift) const663 int Game_Battler::CanShiftAttributeRate(int attribute_id, int shift) const {
664 if (attribute_id < 1 || attribute_id > static_cast<int>(lcf::Data::attributes.size())) {
665 return 0;
666 }
667 const auto prev_shift = GetAttributeRateShift(attribute_id);
668 const auto new_shift = Utils::Clamp(prev_shift + shift, -1, 1);
669 return new_shift - prev_shift;
670 }
671
GetHitChanceModifierFromStates() const672 int Game_Battler::GetHitChanceModifierFromStates() const {
673 int modifier = 100;
674 // Modify hit chance for each state the source has
675 for (const auto id : GetInflictedStates()) {
676 auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, id);
677 if (state) {
678 modifier = std::min<int>(modifier, state->reduce_hit_ratio);
679 }
680 }
681 return modifier;
682 }
683
ShakeOnce(int strength,int speed,int frames)684 void Game_Battler::ShakeOnce(int strength, int speed, int frames) {
685 shake.strength = strength;
686 shake.speed = speed;
687 shake.time_left = frames;
688 // FIXME: RPG_RT doesn't reset position for screen shake. So we guess? it doesn't do so here either.
689 }
690
Flash(int r,int g,int b,int power,int frames)691 void Game_Battler::Flash(int r, int g, int b, int power, int frames) {
692 flash.red = r;
693 flash.green = g;
694 flash.blue = b;
695 flash.current_level = power;
696 flash.time_left = frames;
697 }
698
GetInflictedStatesOrderedByPriority() const699 const std::vector<lcf::rpg::State*> Game_Battler::GetInflictedStatesOrderedByPriority() const {
700 std::vector<lcf::rpg::State*> state_list = State::GetObjects(GetStates());
701 return State::SortedByPriority(state_list);
702 }
703
CanChangeAtkModifier(int modifier) const704 int Game_Battler::CanChangeAtkModifier(int modifier) const {
705 const auto prev = atk_modifier;
706 const auto base = GetBaseAtk();
707 const auto new_mod = (Utils::Clamp(atk_modifier + modifier, -base / 2, base));
708 return new_mod - prev;
709 }
710
CanChangeDefModifier(int modifier) const711 int Game_Battler::CanChangeDefModifier(int modifier) const {
712 const auto prev = def_modifier;
713 const auto base = GetBaseDef();
714 const auto new_mod = (Utils::Clamp(def_modifier + modifier, -base / 2, base));
715 return new_mod - prev;
716 }
717
CanChangeSpiModifier(int modifier) const718 int Game_Battler::CanChangeSpiModifier(int modifier) const {
719 const auto prev = spi_modifier;
720 const auto base = GetBaseSpi();
721 const auto new_mod = (Utils::Clamp(spi_modifier + modifier, -base / 2, base));
722 return new_mod - prev;
723 }
724
CanChangeAgiModifier(int modifier) const725 int Game_Battler::CanChangeAgiModifier(int modifier) const {
726 const auto prev = agi_modifier;
727 const auto base = GetBaseAgi();
728 const auto new_mod = (Utils::Clamp(agi_modifier + modifier, -base / 2, base));
729 return new_mod - prev;
730 }
731
ChangeAtkModifier(int modifier)732 int Game_Battler::ChangeAtkModifier(int modifier) {
733 auto delta = CanChangeAtkModifier(modifier);
734 SetAtkModifier(atk_modifier + delta);
735 return delta;
736 }
737
ChangeDefModifier(int modifier)738 int Game_Battler::ChangeDefModifier(int modifier) {
739 auto delta = CanChangeDefModifier(modifier);
740 SetDefModifier(def_modifier + delta);
741 return delta;
742 }
743
ChangeSpiModifier(int modifier)744 int Game_Battler::ChangeSpiModifier(int modifier) {
745 auto delta = CanChangeSpiModifier(modifier);
746 SetSpiModifier(spi_modifier + delta);
747 return delta;
748 }
749
ChangeAgiModifier(int modifier)750 int Game_Battler::ChangeAgiModifier(int modifier) {
751 auto delta = CanChangeAgiModifier(modifier);
752 SetAgiModifier(agi_modifier + delta);
753 return delta;
754 }
755
756