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