1 /*
2  *
3  * This file is part of EasyRPG Player.
4  *
5  * EasyRPG Player is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * EasyRPG Player is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <cassert>
20 #include <cmath>
21 #include <cstdlib>
22 #include <algorithm>
23 #include <sstream>
24 #include "game_actor.h"
25 #include "game_battle.h"
26 #include "game_battlealgorithm.h"
27 #include "game_battler.h"
28 #include "game_enemy.h"
29 #include "game_enemyparty.h"
30 #include "game_party.h"
31 #include "game_party_base.h"
32 #include "game_switches.h"
33 #include "game_system.h"
34 #include "main_data.h"
35 #include "battle_message.h"
36 #include "output.h"
37 #include "player.h"
38 #include <lcf/reader_util.h>
39 #include <lcf/rpg/animation.h>
40 #include <lcf/rpg/state.h>
41 #include <lcf/rpg/skill.h>
42 #include <lcf/rpg/item.h>
43 #include <lcf/rpg/battleranimation.h>
44 #include "sprite_battler.h"
45 #include "utils.h"
46 #include "rand.h"
47 #include "state.h"
48 #include "enemyai.h"
49 #include "algo.h"
50 #include "attribute.h"
51 #include "spriteset_battle.h"
52 
MaxDamageValue()53 static inline int MaxDamageValue() {
54 	return lcf::Data::system.easyrpg_max_damage == -1 ? (Player::IsRPG2k() ? 999 : 9999) : lcf::Data::system.easyrpg_max_damage;
55 }
56 
AlgorithmBase(Type ty,Game_Battler * source,Game_Battler * target)57 Game_BattleAlgorithm::AlgorithmBase::AlgorithmBase(Type ty, Game_Battler* source, Game_Battler* target) :
58 	AlgorithmBase(ty, source, std::vector<Game_Battler*>{ target }) {}
59 
AlgorithmBase(Type ty,Game_Battler * source,std::vector<Game_Battler * > in_targets)60 Game_BattleAlgorithm::AlgorithmBase::AlgorithmBase(Type ty, Game_Battler* source, std::vector<Game_Battler*> in_targets) :
61 	type(ty), source(source), targets(std::move(in_targets))
62 {
63 	assert(source != nullptr);
64 	for (auto* t: targets) {
65 		assert(t != nullptr);
66 	}
67 
68 	Reset();
69 
70 	source->SetIsDefending(false);
71 
72 	num_original_targets = targets.size();
73 	current_target = targets.end();
74 }
75 
AlgorithmBase(Type ty,Game_Battler * source,Game_Party_Base * target)76 Game_BattleAlgorithm::AlgorithmBase::AlgorithmBase(Type ty, Game_Battler* source, Game_Party_Base* target) :
77 	type(ty), source(source)
78 {
79 	assert(source != nullptr);
80 	assert(target != nullptr);
81 
82 	Reset();
83 
84 	source->SetIsDefending(false);
85 
86 	current_target = targets.end();
87 	party_target = target;
88 }
89 
Reset()90 void Game_BattleAlgorithm::AlgorithmBase::Reset() {
91 	hp = 0;
92 	sp = 0;
93 	attack = 0;
94 	defense = 0;
95 	spirit = 0;
96 	agility = 0;
97 	switch_id = 0;
98 	flags = {};
99 	states.clear();
100 	attributes.clear();
101 }
102 
PlayAnimation(int anim_id,bool only_sound,int cutoff,bool invert)103 int Game_BattleAlgorithm::AlgorithmBase::PlayAnimation(int anim_id, bool only_sound, int cutoff, bool invert) {
104 	if (anim_id == 0) {
105 		return 0;
106 	}
107 
108 	auto anim_iter = targets.begin();
109 	auto anim_end = targets.end();
110 	// If targets were added, skip the originals and use the added one
111 	// Cases: reflect, retargeting, etc..
112 	if (num_original_targets < static_cast<int>(targets.size())) {
113 		anim_iter += num_original_targets;
114 	}
115 
116 	std::vector<Game_Battler*> anim_targets;
117 	for (; anim_iter != anim_end; ++anim_iter) {
118 		auto* target = *anim_iter;
119 		if (!target->IsHidden() && (IsTargetValid(*target) || (target->GetType() == Game_Battler::Type_Ally && target->IsDead()))) {
120 			anim_targets.push_back(target);
121 		}
122 	}
123 
124 	return Game_Battle::ShowBattleAnimation(anim_id, anim_targets, only_sound, cutoff, invert);
125 }
126 
GetFailureMessage() const127 std::string Game_BattleAlgorithm::AlgorithmBase::GetFailureMessage() const {
128 	return BattleMessage::GetPhysicalFailureMessage(*GetSource(), *GetTarget());
129 }
130 
GetTarget() const131 Game_Battler* Game_BattleAlgorithm::AlgorithmBase::GetTarget() const {
132 	if (current_target == targets.end()) {
133 		return NULL;
134 	}
135 
136 	return *current_target;
137 }
138 
Execute()139 bool Game_BattleAlgorithm::AlgorithmBase::Execute() {
140 	Reset();
141 	return vExecute();
142 }
143 
vExecute()144 bool Game_BattleAlgorithm::AlgorithmBase::vExecute() {
145 	return SetIsSuccess();
146 }
147 
ApplyCustomEffect()148 void Game_BattleAlgorithm::AlgorithmBase::ApplyCustomEffect() {
149 }
150 
ApplySwitchEffect()151 int Game_BattleAlgorithm::AlgorithmBase::ApplySwitchEffect() {
152 	const auto sw = GetAffectedSwitch();
153 	if (sw > 0) {
154 		Main_Data::game_switches->Set(sw, true);
155 	}
156 	return sw;
157 }
158 
ApplyHpEffect()159 int Game_BattleAlgorithm::AlgorithmBase::ApplyHpEffect() {
160 	auto* target = GetTarget();
161 	assert(target);
162 
163 	if (target->IsDead()) {
164 		return 0;
165 	}
166 	int hp = GetAffectedHp();
167 	if (hp != 0) {
168 		hp = target->ChangeHp(hp, true);
169 		if (IsAbsorbHp()) {
170 			// Only absorb the hp that were left
171 			source->ChangeHp(-hp, true);
172 		}
173 	}
174 	return hp;
175 }
176 
ApplySpEffect()177 int Game_BattleAlgorithm::AlgorithmBase::ApplySpEffect() {
178 	auto* target = GetTarget();
179 	assert(target);
180 	auto sp = GetAffectedSp();
181 	if (sp != 0) {
182 		sp = target->ChangeSp(sp);
183 		if (IsAbsorbSp()) {
184 			// Only absorb the sp that were left
185 			source->ChangeSp(-sp);
186 		}
187 	}
188 	return sp;
189 }
190 
ApplyAtkEffect()191 int Game_BattleAlgorithm::AlgorithmBase::ApplyAtkEffect() {
192 	auto* target = GetTarget();
193 	assert(target);
194 	auto atk = GetAffectedAtk();
195 	if (atk != 0) {
196 		atk = target->ChangeAtkModifier(atk);
197 		if (IsAbsorbAtk()) {
198 			source->ChangeAtkModifier(-atk);
199 		}
200 	}
201 	return atk;
202 }
203 
ApplyDefEffect()204 int Game_BattleAlgorithm::AlgorithmBase::ApplyDefEffect() {
205 	auto* target = GetTarget();
206 	assert(target);
207 	auto def = GetAffectedDef();
208 	if (def != 0) {
209 		def = target->ChangeDefModifier(def);
210 		if (IsAbsorbDef()) {
211 			source->ChangeDefModifier(-def);
212 		}
213 	}
214 	return def;
215 }
216 
ApplySpiEffect()217 int Game_BattleAlgorithm::AlgorithmBase::ApplySpiEffect() {
218 	auto* target = GetTarget();
219 	assert(target);
220 	auto spi = GetAffectedSpi();
221 	if (spi) {
222 		spi = target->ChangeSpiModifier(spi);
223 		if (IsAbsorbSpi()) {
224 			source->ChangeSpiModifier(-spi);
225 		}
226 	}
227 	return spi;
228 }
229 
ApplyAgiEffect()230 int Game_BattleAlgorithm::AlgorithmBase::ApplyAgiEffect() {
231 	auto* target = GetTarget();
232 	assert(target);
233 	auto agi = GetAffectedAgi();
234 	if (agi) {
235 		agi = target->ChangeAgiModifier(agi);
236 		if (IsAbsorbAgi()) {
237 			source->ChangeAgiModifier(-agi);
238 		}
239 	}
240 	return agi;
241 }
242 
ApplyStateEffect(StateEffect se)243 bool Game_BattleAlgorithm::AlgorithmBase::ApplyStateEffect(StateEffect se) {
244 	auto* target = GetTarget();
245 	if (!target) {
246 		return false;
247 	}
248 
249 	bool rc = false;
250 	bool was_dead = target->IsDead();
251 
252 	// Apply states
253 	switch (se.effect) {
254 		case StateEffect::Inflicted:
255 			rc = target->AddState(se.state_id, true);
256 			break;
257 		case StateEffect::Healed:
258 		case StateEffect::HealedByAttack:
259 			rc = target->RemoveState(se.state_id, false);
260 			break;
261 		default:
262 			break;
263 	}
264 
265 	// Apply revived hp healing
266 	if (was_dead && !target->IsDead()) {
267 		auto hp = GetAffectedHp();
268 		target->ChangeHp(hp - 1, false);
269 	}
270 	return rc;
271 }
272 
ApplyStateEffects()273 void Game_BattleAlgorithm::AlgorithmBase::ApplyStateEffects() {
274 	// Apply states
275 	for (auto& se: states) {
276 		ApplyStateEffect(se);
277 	}
278 }
279 
ApplyAttributeShiftEffect(AttributeEffect ae)280 int Game_BattleAlgorithm::AlgorithmBase::ApplyAttributeShiftEffect(AttributeEffect ae) {
281 	auto* target = GetTarget();
282 	if (target) {
283 		return target->ShiftAttributeRate(ae.attr_id, ae.shift);
284 	}
285 	return 0;
286 }
287 
ApplyAttributeShiftEffects()288 void Game_BattleAlgorithm::AlgorithmBase::ApplyAttributeShiftEffects() {
289 	for (auto& ae: attributes) {
290 		ApplyAttributeShiftEffect(ae);
291 	}
292 }
293 
ApplyAll()294 void Game_BattleAlgorithm::AlgorithmBase::ApplyAll() {
295 	ApplyCustomEffect();
296 	ApplySwitchEffect();
297 	ApplyHpEffect();
298 	ApplySpEffect();
299 	ApplyAtkEffect();
300 	ApplyDefEffect();
301 	ApplySpiEffect();
302 	ApplyAgiEffect();
303 	ApplyStateEffects();
304 	ApplyAttributeShiftEffects();
305 }
306 
ProcessPostActionSwitches()307 void Game_BattleAlgorithm::AlgorithmBase::ProcessPostActionSwitches() {
308 	for (int s : switch_on) {
309 		Main_Data::game_switches->Set(s, true);
310 	}
311 
312 	for (int s : switch_off) {
313 		Main_Data::game_switches->Set(s, false);
314 	}
315 }
316 
IsTargetValid(const Game_Battler & target) const317 bool Game_BattleAlgorithm::AlgorithmBase::IsTargetValid(const Game_Battler& target) const {
318 	return target.Exists();
319 }
320 
GetSourcePose() const321 int Game_BattleAlgorithm::AlgorithmBase::GetSourcePose() const {
322 	return lcf::rpg::BattlerAnimation::Pose_Idle;
323 }
324 
GetCBAMovement() const325 int Game_BattleAlgorithm::AlgorithmBase::GetCBAMovement() const {
326 	return lcf::rpg::BattlerAnimationItemSkill::Movement_none;
327 }
328 
GetCBAAfterimage() const329 int Game_BattleAlgorithm::AlgorithmBase::GetCBAAfterimage() const {
330 	return lcf::rpg::BattlerAnimationItemSkill::Afterimage_none;
331 }
332 
GetWeaponAnimationData() const333 const lcf::rpg::BattlerAnimationItemSkill* Game_BattleAlgorithm::AlgorithmBase::GetWeaponAnimationData() const {
334 	return nullptr;
335 }
336 
GetWeaponData() const337 const lcf::rpg::Item* Game_BattleAlgorithm::AlgorithmBase::GetWeaponData() const {
338 	return nullptr;
339 }
340 
Start()341 void Game_BattleAlgorithm::AlgorithmBase::Start() {
342 	reflect_target = nullptr;
343 
344 	if (party_target) {
345 		targets.clear();
346 		party_target->GetBattlers(targets);
347 		num_original_targets = targets.size();
348 	} else {
349 		// Remove any previously set reflect targets
350 		targets.resize(num_original_targets);
351 	}
352 	current_target = targets.begin();
353 
354 	// Call any custom start logic, then check if we need to retarget
355 	bool allow_retarget = vStart();
356 
357 	if (!IsCurrentTargetValid()) {
358 		if (!TargetNext() && allow_retarget && !party_target && !targets.empty()) {
359 			auto* last_target = targets.back();
360 			auto* next_target = last_target->GetParty().GetNextActiveBattler(last_target);
361 			if (next_target) {
362 				current_target = targets.insert(targets.end(), next_target);
363 			}
364 
365 			if (!IsCurrentTargetValid()) {
366 				TargetNext();
367 			}
368 		}
369 	}
370 
371 	// This case must be true before returning.
372 	assert(current_target == targets.end() || IsCurrentTargetValid());
373 
374 	source->SetCharged(false);
375 }
376 
vStart()377 bool Game_BattleAlgorithm::AlgorithmBase::vStart() {
378 	return true;
379 }
380 
AddTarget(Game_Battler * target,bool set_current)381 void Game_BattleAlgorithm::AlgorithmBase::AddTarget(Game_Battler* target, bool set_current) {
382 	assert(target != nullptr);
383 
384 	const auto idx = std::distance(targets.begin(), current_target);
385 	const auto size = targets.size();
386 	targets.push_back(target);
387 	current_target = targets.begin() + (set_current ? size : idx);
388 }
389 
AddTargets(Game_Party_Base * party,bool set_current)390 void Game_BattleAlgorithm::AlgorithmBase::AddTargets(Game_Party_Base* party, bool set_current) {
391 	assert(party != nullptr);
392 	const auto idx = std::distance(targets.begin(), current_target);
393 	const auto size = targets.size();
394 	party->GetBattlers(targets);
395 	current_target = targets.begin() + (set_current ? size : idx);
396 }
397 
ReflectTargets()398 bool Game_BattleAlgorithm::AlgorithmBase::ReflectTargets() {
399 	auto iter = std::find_if(current_target, targets.end(), [this](auto* target) { return IsReflected(*target); });
400 
401 	// No reflect
402 	if (iter == targets.end()) {
403 		return false;
404 	}
405 
406 	reflect_target = *iter;
407 
408 	if (party_target) {
409 		// Reflect back on source party
410 		AddTargets(&source->GetParty(), true);
411 	} else {
412 		// Reflect back on source
413 		AddTarget(source, true);
414 	}
415 
416 	if (!IsCurrentTargetValid()) {
417 		TargetNext();
418 	}
419 	return true;
420 }
421 
TargetNext()422 bool Game_BattleAlgorithm::AlgorithmBase::TargetNext() {
423 	return TargetNextInternal();
424 }
425 
RepeatNext(bool require_valid)426 bool Game_BattleAlgorithm::AlgorithmBase::RepeatNext(bool require_valid) {
427 	++cur_repeat;
428 	if (cur_repeat >= repeat || (require_valid && !IsCurrentTargetValid())) {
429 		cur_repeat = 0;
430 		return false;
431 	}
432 	return true;
433 }
434 
IsCurrentTargetValid() const435 bool Game_BattleAlgorithm::AlgorithmBase::IsCurrentTargetValid() const {
436 	if (current_target == targets.end()) {
437 		return false;
438 	}
439 	return IsTargetValid(**current_target);
440 }
441 
TargetNextInternal()442 bool Game_BattleAlgorithm::AlgorithmBase::TargetNextInternal() {
443 	do {
444 		if (current_target == targets.end() || ++current_target == targets.end()) {
445 			return false;
446 		}
447 	} while (!IsCurrentTargetValid());
448 
449 	return true;
450 }
451 
SetRepeat(int repeat)452 void Game_BattleAlgorithm::AlgorithmBase::SetRepeat(int repeat) {
453 	this->repeat = std::max(1, repeat);
454 }
455 
SetSwitchEnable(int switch_id)456 void Game_BattleAlgorithm::AlgorithmBase::SetSwitchEnable(int switch_id) {
457 	switch_on.push_back(switch_id);
458 }
459 
SetSwitchDisable(int switch_id)460 void Game_BattleAlgorithm::AlgorithmBase::SetSwitchDisable(int switch_id) {
461 	switch_off.push_back(switch_id);
462 }
463 
GetStartSe() const464 const lcf::rpg::Sound* Game_BattleAlgorithm::AlgorithmBase::GetStartSe() const {
465 	return NULL;
466 }
467 
GetStartMessage(int) const468 std::string Game_BattleAlgorithm::AlgorithmBase::GetStartMessage(int) const {
469 	return "";
470 }
471 
GetFailureSe() const472 const lcf::rpg::Sound* Game_BattleAlgorithm::AlgorithmBase::GetFailureSe() const {
473 	return &Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Evasion);
474 }
475 
IsReflected(const Game_Battler &) const476 bool Game_BattleAlgorithm::AlgorithmBase::IsReflected(const Game_Battler&) const {
477 	return false;
478 }
479 
ApplyComboHitsMultiplier(int hits)480 void Game_BattleAlgorithm::AlgorithmBase::ApplyComboHitsMultiplier(int hits) {
481 	repeat *= hits;
482 }
483 
AddAffectedState(StateEffect se)484 void Game_BattleAlgorithm::AlgorithmBase::AddAffectedState(StateEffect se) {
485 	auto* target = GetTarget();
486 	if (se.state_id == lcf::rpg::State::kDeathID
487 			&& (se.effect == StateEffect::Healed || se.effect == StateEffect::HealedByAttack)
488 			&& target && target->IsDead()) {
489 		SetFlag(eRevived, true);
490 	}
491 	states.push_back(std::move(se));
492 }
493 
AddAffectedAttribute(AttributeEffect ae)494 void Game_BattleAlgorithm::AlgorithmBase::AddAffectedAttribute(AttributeEffect ae) {
495 	attributes.push_back(std::move(ae));
496 }
497 
BattlePhysicalStateHeal(int physical_rate,std::vector<int16_t> & target_states,const PermanentStates & ps)498 void Game_BattleAlgorithm::AlgorithmBase::BattlePhysicalStateHeal(int physical_rate, std::vector<int16_t>& target_states, const PermanentStates& ps) {
499 	if (physical_rate <= 0) {
500 		return;
501 	}
502 
503 	for (int i = 0; i < (int)target_states.size(); ++i) {
504 		auto state_id = i + 1;
505 		if (!State::Has(state_id, target_states)) {
506 			continue;
507 		}
508 
509 		auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, state_id);
510 		if (state == nullptr) {
511 			continue;
512 		}
513 		if (state->release_by_damage > 0) {
514 			int release_chance = state->release_by_damage * physical_rate / 100;
515 
516 			if (!Rand::ChanceOf(release_chance, 100)) {
517 				continue;
518 			}
519 
520 			if (State::Remove(state_id, target_states, ps)) {
521 				AddAffectedState(StateEffect{state_id, Game_BattleAlgorithm::StateEffect::HealedByAttack});
522 			}
523 		}
524 	}
525 }
526 
527 
528 
None(Game_Battler * source)529 Game_BattleAlgorithm::None::None(Game_Battler* source) :
530 AlgorithmBase(Type::None, source, source) {
531 	// no-op
532 }
533 
Normal(Game_Battler * source,Game_Battler * target,int hits_multiplier,Style style)534 Game_BattleAlgorithm::Normal::Normal(Game_Battler* source, Game_Battler* target, int hits_multiplier, Style style) :
535 	AlgorithmBase(Type::Normal, source, target), hits_multiplier(hits_multiplier)
536 {
537 	Init(style);
538 }
539 
Normal(Game_Battler * source,Game_Party_Base * target,int hits_multiplier,Style style)540 Game_BattleAlgorithm::Normal::Normal(Game_Battler* source, Game_Party_Base* target, int hits_multiplier, Style style) :
541 	AlgorithmBase(Type::Normal, source, target), hits_multiplier(hits_multiplier)
542 {
543 	Init(style);
544 }
545 
GetDefaultStyle()546 Game_BattleAlgorithm::Normal::Style Game_BattleAlgorithm::Normal::GetDefaultStyle() {
547 	return Player::IsRPG2k3() ? Style_MultiHit : Style_Combined;
548 }
549 
GetWeapon() const550 Game_Battler::Weapon Game_BattleAlgorithm::Normal::GetWeapon() const {
551 	if (weapon_style < 0) {
552 		return Game_Battler::WeaponAll;
553 	}
554 	return GetCurrentRepeat() >= weapon_style ? Game_Battler::WeaponSecondary : Game_Battler::WeaponPrimary;
555 }
556 
Init(Style style)557 void Game_BattleAlgorithm::Normal::Init(Style style) {
558 	auto* source = GetSource();
559 	charged_attack = source->IsCharged();
560 	weapon_style = -1;
561 	if (source->GetType() == Game_Battler::Type_Ally && style == Style_MultiHit) {
562 		auto* ally = static_cast<Game_Actor*>(source);
563 		if (ally->GetWeapon() && ally->Get2ndWeapon()) {
564 			auto hits = hits_multiplier * ally->GetNumberOfAttacks(Game_Battler::WeaponPrimary);
565 			weapon_style = hits;
566 			hits += hits_multiplier * ally->GetNumberOfAttacks(Game_Battler::WeaponSecondary);
567 			SetRepeat(hits);
568 			return;
569 		}
570 	}
571 	SetRepeat(hits_multiplier * source->GetNumberOfAttacks(GetWeapon()));
572 }
573 
ApplyComboHitsMultiplier(int hits)574 void Game_BattleAlgorithm::Normal::ApplyComboHitsMultiplier(int hits) {
575 	AlgorithmBase::ApplyComboHitsMultiplier(hits);
576 	if (weapon_style > 0) {
577 		// For dual wield normal attack, the first weapon gets combo'd then the second weapon.
578 		weapon_style *= hits;
579 	}
580 }
581 
582 
vStart()583 bool Game_BattleAlgorithm::Normal::vStart() {
584 	// If this weapon attacks all, then attack all enemies regardless of original targetting.
585 	const auto weapon = GetWeapon();
586 	auto* source = GetSource();
587 	if (GetOriginalPartyTarget() == nullptr && source->HasAttackAll(weapon)) {
588 		auto* target = GetOriginalTargets().back();
589 		AddTargets(&target->GetParty(), true);
590 	}
591 
592 	source->ChangeSp(-source->CalculateWeaponSpCost(weapon));
593 
594 	return true;
595 }
596 
GetAnimationId(int idx) const597 int Game_BattleAlgorithm::Normal::GetAnimationId(int idx) const {
598 	const auto weapon = GetWeapon();
599 	auto* source = GetSource();
600 	if (source->GetType() == Game_Battler::Type_Ally) {
601 		Game_Actor* ally = static_cast<Game_Actor*>(source);
602 		auto weapons = ally->GetWeapons(weapon);
603 		auto* item = (idx >= 0 && idx < static_cast<int>(weapons.size()))
604 			? weapons[idx] : nullptr;
605 		if (item) {
606 			return item->animation_id;
607 		} else if (idx == 0) {
608 			return ally->GetUnarmedBattleAnimationId();
609 		}
610 		return 0;
611 	}
612 	if (source->GetType() == Game_Battler::Type_Enemy
613 			&& Player::IsRPG2k3()
614 			&& !lcf::Data::animations.empty()) {
615 		Game_Enemy* enemy = static_cast<Game_Enemy*>(source);
616 		return enemy->GetUnarmedBattleAnimationId();
617 	}
618 	return 0;
619 }
620 
vExecute()621 bool Game_BattleAlgorithm::Normal::vExecute() {
622 	const auto weapon = GetWeapon();
623 	auto& source = *GetSource();
624 	auto& target = *GetTarget();
625 
626 	auto to_hit = Algo::CalcNormalAttackToHit(source, target, weapon, Game_Battle::GetBattleCondition(), true);
627 	auto crit_chance = Algo::CalcCriticalHitChance(source, target, weapon);
628 
629 	// Damage calculation
630 	if (!Rand::PercentChance(to_hit)) {
631 		return SetIsFailure();
632 	}
633 
634 	if (Rand::PercentChance(crit_chance)) {
635 		SetIsCriticalHit(true);
636 	}
637 
638 	auto effect = Algo::CalcNormalAttackEffect(source, target, weapon, IsCriticalHit(), charged_attack, true, Game_Battle::GetBattleCondition(), treat_enemies_asif_in_front_row);
639 	effect = Algo::AdjustDamageForDefend(effect, target);
640 
641 	effect = Utils::Clamp(effect, -MaxDamageValue(), MaxDamageValue());
642 
643 	this->SetAffectedHp(-effect);
644 
645 	// If target is killed, states not applied
646 	if (target.GetHp() + GetAffectedHp() <= 0) {
647 		return SetIsSuccess();
648 	}
649 
650 	// Make a copy of the target's state set and see what we can apply.
651 	auto target_states = target.GetStates();
652 	auto target_perm_states = target.GetPermanentStates();
653 
654 	// Conditions healed by physical attack:
655 	BattlePhysicalStateHeal(100, target_states, target_perm_states);
656 
657 	// Conditions caused / healed by weapon.
658 	if (source.GetType() == Game_Battler::Type_Ally) {
659 		auto& ally = static_cast<Game_Actor&>(source);
660 		const bool is2k3 = Player::IsRPG2k3();
661 		auto weapons = ally.GetWeapons(weapon);
662 
663 		if (weapons[0]) {
664 			int num_states = 0;
665 			for (auto* w: weapons) {
666 				if (w) {
667 					num_states = std::max(num_states, static_cast<int>(w->state_set.size()));
668 				}
669 			}
670 
671 			for (int i = 0; i < num_states; ++i) {
672 				// EasyRPG extension: This logic allows heal/inflict to work properly with a combined weapon attack.
673 				// If the first weapon heals and the second inflicts, then this will do then in the right order.
674 				// If both heal or both inflict, we take the max probability as RPG_RT does.
675 				int heal_pct = 0;
676 				int inflict_pct = 0;
677 				for (auto* w: weapons) {
678 					if (w && i < static_cast<int>(w->state_set.size()) && w->state_set[i]) {
679 						if (is2k3 && w->reverse_state_effect) {
680 							heal_pct = std::max(heal_pct, static_cast<int>(w->state_chance));
681 						} else {
682 							inflict_pct = std::max(inflict_pct, static_cast<int>(w->state_chance));
683 						}
684 					}
685 				}
686 				auto state_id = (i + 1);
687 
688 				for (auto* w: weapons) {
689 					if (is2k3 && w && w->reverse_state_effect) {
690 						if (heal_pct > 0 && Rand::PercentChance(heal_pct)) {
691 							if (State::Remove(state_id, target_states, target_perm_states)) {
692 								AddAffectedState(StateEffect{state_id, StateEffect::Healed});
693 							}
694 						}
695 						heal_pct = 0;
696 					} else if (inflict_pct > 0) {
697 						inflict_pct = inflict_pct * target.GetStateProbability(state_id) / 100;
698 						if (Rand::PercentChance(inflict_pct)) {
699 							// Unlike skills, weapons do not try to reinflict states already present
700 							if (!State::Has(state_id, target_states) && State::Add(state_id, target_states, target_perm_states, true)) {
701 								AddAffectedState(StateEffect{state_id, StateEffect::Inflicted});
702 							}
703 						}
704 						inflict_pct = 0;
705 					}
706 				}
707 			}
708 		}
709 	}
710 
711 	return SetIsSuccess();
712 }
713 
GetStartMessage(int line) const714 std::string Game_BattleAlgorithm::Normal::GetStartMessage(int line) const {
715 	if (line == 0) {
716 		if (Player::IsRPG2k()) {
717 			return BattleMessage::GetNormalAttackStartMessage2k(*GetSource());
718 		}
719 		if (GetSource()->GetType() == Game_Battler::Type_Enemy && hits_multiplier == 2) {
720 			return BattleMessage::GetDoubleAttackStartMessage2k3(*GetSource());
721 		}
722 	}
723 	return "";
724 }
725 
GetSourcePose() const726 int Game_BattleAlgorithm::Normal::GetSourcePose() const {
727 	auto weapon = GetWeapon();
728 	return weapon == Game_Battler::WeaponSecondary
729 		? lcf::rpg::BattlerAnimation::Pose_AttackLeft
730 		: lcf::rpg::BattlerAnimation::Pose_AttackRight;
731 }
732 
GetCBAMovement() const733 int Game_BattleAlgorithm::Normal::GetCBAMovement() const {
734 	const auto weapon = GetWeapon();
735 	auto* source = GetSource();
736 	if (source->GetType() == Game_Battler::Type_Ally) {
737 		auto* ally = static_cast<Game_Actor*>(source);
738 		auto weapons = ally->GetWeapons(weapon);
739 		auto* item = weapons[0];
740 		if (item) {
741 			if (static_cast<int>(item->animation_data.size()) > source->GetId() - 1) {
742 				return item->animation_data[source->GetId() - 1].movement;
743 			}
744 		}
745 	}
746 
747 	return lcf::rpg::BattlerAnimationItemSkill::Movement_none;
748 }
749 
GetCBAAfterimage() const750 int Game_BattleAlgorithm::Normal::GetCBAAfterimage() const {
751 	const auto weapon = GetWeapon();
752 	auto* source = GetSource();
753 	if (source->GetType() == Game_Battler::Type_Ally) {
754 		auto* ally = static_cast<Game_Actor*>(source);
755 		auto weapons = ally->GetWeapons(weapon);
756 		auto* item = weapons[0];
757 		if (item) {
758 			if (static_cast<int>(item->animation_data.size()) > source->GetId() - 1) {
759 				return item->animation_data[source->GetId() - 1].after_image;
760 			}
761 		}
762 	}
763 
764 	return lcf::rpg::BattlerAnimationItemSkill::Afterimage_none;
765 }
766 
GetWeaponAnimationData() const767 const lcf::rpg::BattlerAnimationItemSkill* Game_BattleAlgorithm::Normal::GetWeaponAnimationData() const {
768 	const auto weapon = GetWeapon();
769 	auto* source = GetSource();
770 	if (source->GetType() == Game_Battler::Type_Ally) {
771 		auto* ally = static_cast<Game_Actor*>(source);
772 		auto weapons = ally->GetWeapons(weapon);
773 		auto* item = weapons[0];
774 		if (item) {
775 			if (static_cast<int>(item->animation_data.size()) > source->GetId() - 1) {
776 				return &item->animation_data[source->GetId() - 1];
777 			}
778 		}
779 	}
780 
781 	return nullptr;
782 }
783 
GetWeaponData() const784 const lcf::rpg::Item* Game_BattleAlgorithm::Normal::GetWeaponData() const {
785 	const auto weapon = GetWeapon();
786 	auto* source = GetSource();
787 	if (source->GetType() == Game_Battler::Type_Ally) {
788 		auto* ally = static_cast<Game_Actor*>(source);
789 		auto weapons = ally->GetWeapons(weapon);
790 		auto* item = weapons[0];
791 		if (item) {
792 			return item;
793 		}
794 	}
795 
796 	return nullptr;
797 }
798 
GetStartSe() const799 const lcf::rpg::Sound* Game_BattleAlgorithm::Normal::GetStartSe() const {
800 	if (Player::IsRPG2k() && GetSource()->GetType() == Game_Battler::Type_Enemy) {
801 		return &Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyAttacks);
802 	}
803 	return nullptr;
804 }
805 
Skill(Game_Battler * source,Game_Battler * target,const lcf::rpg::Skill & skill,const lcf::rpg::Item * item)806 Game_BattleAlgorithm::Skill::Skill(Game_Battler* source, Game_Battler* target, const lcf::rpg::Skill& skill, const lcf::rpg::Item* item) :
807 	AlgorithmBase(Type::Skill, source, target), skill(skill), item(item)
808 {
809 	Init();
810 }
811 
Skill(Game_Battler * source,Game_Party_Base * target,const lcf::rpg::Skill & skill,const lcf::rpg::Item * item)812 Game_BattleAlgorithm::Skill::Skill(Game_Battler* source, Game_Party_Base* target, const lcf::rpg::Skill& skill, const lcf::rpg::Item* item) :
813 	AlgorithmBase(Type::Skill, source, target), skill(skill), item(item)
814 {
815 	Init();
816 }
817 
Skill(Game_Battler * source,const lcf::rpg::Skill & skill,const lcf::rpg::Item * item)818 Game_BattleAlgorithm::Skill::Skill(Game_Battler* source, const lcf::rpg::Skill& skill, const lcf::rpg::Item* item) :
819 	Skill(source, source, skill, item)
820 {
821 }
822 
Init()823 void Game_BattleAlgorithm::Skill::Init() {
824 }
825 
vStart()826 bool Game_BattleAlgorithm::Skill::vStart() {
827 	auto* source = GetSource();
828 	if (item) {
829 		Main_Data::game_party->ConsumeItemUse(item->ID);
830 	} else {
831 		source->ChangeSp(-source->CalculateSkillCost(skill.ID));
832 	}
833 	return true;
834 }
835 
GetAnimationId(int idx) const836 int Game_BattleAlgorithm::Skill::GetAnimationId(int idx) const {
837 	return idx == 0 && Algo::IsNormalOrSubskill(skill) ? skill.animation_id : 0;
838 }
839 
IsTargetValid(const Game_Battler & target) const840 bool Game_BattleAlgorithm::Skill::IsTargetValid(const Game_Battler& target) const {
841 	if (target.IsHidden()) {
842 		return false;
843 	}
844 
845 	if (target.IsDead()) {
846 		// Cures death
847 		// NOTE: RPG_RT 2k3 also allows this targetting if reverse_state_effect.
848 		return Algo::SkillTargetsAllies(skill) && !skill.state_effects.empty() && skill.state_effects[0];
849 	}
850 
851 	return true;
852 }
853 
854 
vExecute()855 bool Game_BattleAlgorithm::Skill::vExecute() {
856 	if (item && item->skill_id != skill.ID) {
857 		assert(false && "Item skill mismatch");
858 	}
859 	auto* source = GetSource();
860 	assert(source);
861 	auto* target = GetTarget();
862 	assert(target);
863 
864 	if (skill.type == lcf::rpg::Skill::Type_switch) {
865 		SetAffectedSwitch(skill.switch_id);
866 		return SetIsSuccess();
867 	}
868 
869 	if (!Algo::IsNormalOrSubskill(skill)) {
870 		// Possible to trigger in RPG_RT by using a weapon which invokes a teleport or escape skills.
871 		// Silently does nothing.
872 		return SetIsSuccess();
873 	}
874 
875 	SetIsPositive(Algo::SkillTargetsAllies(skill));
876 
877 	auto to_hit = Algo::CalcSkillToHit(*source, *target, skill);
878 	auto effect = Algo::CalcSkillEffect(*source, *target, skill, true);
879 	effect = Utils::Clamp(effect, -MaxDamageValue(), MaxDamageValue());
880 
881 	if (!IsPositive()) {
882 		effect = -effect;
883 	}
884 
885 	const bool is_dead = target->IsDead();
886 	const bool cures_death = IsPositive()
887 		&& !skill.state_effects.empty()
888 		&& skill.state_effects[lcf::rpg::State::kDeathID - 1]
889 		&& is_dead;
890 
891 	// Dead targets only allowed if this skill revives later
892 	if (is_dead && (!IsPositive() || !cures_death)) {
893 		return SetIsFailure();
894 	}
895 
896 	// Absorb only works on offensive skills.
897 	const auto absorb = (skill.absorb_damage && !IsPositive());
898 
899 	// Make a copy of the target's state set and see what we can apply.
900 	auto target_states = target->GetStates();
901 	auto target_perm_states = target->GetPermanentStates();
902 
903 	if (skill.affect_hp && Rand::PercentChance(to_hit)) {
904 		const auto hp_effect = IsPositive()
905 			? effect
906 			: Algo::AdjustDamageForDefend(effect, *target);
907 
908 		const auto cur_hp = target->GetHp();
909 
910 		if (absorb) {
911 			// Cannot aborb more hp than the target has.
912 			auto hp = std::max<int>(hp_effect, -cur_hp);
913 			if (hp != 0) {
914 				SetAffectedHp(hp);
915 				SetIsAbsorbHp(true);
916 
917 				// Absorb requires damage to be successful
918 				SetIsSuccess();
919 			}
920 		} else {
921 			if (IsPositive()) {
922 				// RPG_RT attribute inverted healing effects are non-lethal
923 				auto hp = std::max(-(cur_hp - 1), hp_effect);
924 				if (hp != 0) {
925 					// HP recovery is sucessful if the effect is non-zero, even at full hp.
926 					SetAffectedHp(hp);
927 					SetIsSuccess();
928 				}
929 			} else {
930 				SetAffectedHp(hp_effect);
931 
932 				if (cur_hp + GetAffectedHp() > 0) {
933 					// Conditions healed by physical attack, but only if target not killed.
934 					BattlePhysicalStateHeal(skill.physical_rate * 10, target_states, target_perm_states);
935 				}
936 
937 				// Hp damage always successful, even if 0
938 				SetIsSuccess();
939 			}
940 		}
941 	}
942 
943 	// If target will be killed, no further affects are applied.
944 	if (!is_dead && GetTarget()->GetHp() + this->GetAffectedHp() <= 0) {
945 		return IsSuccess();
946 	}
947 
948 	if (skill.affect_sp && Rand::PercentChance(to_hit)) {
949 		const auto sp_cost = (source == target) ? source->CalculateSkillCost(skill.ID) : 0;
950 		const auto max_sp = target->GetMaxSp();
951 		const auto cur_sp = target->GetSp() - sp_cost;
952 
953 		int sp = 0;
954 		if (absorb) {
955 			// Cannot aborb more sp than the target has.
956 			sp = std::max<int>(effect, -cur_sp);
957 		} else {
958 			sp = Utils::Clamp(cur_sp + effect, 0, max_sp) - cur_sp;
959 		}
960 
961 		if (sp != 0) {
962 			SetAffectedSp(sp);
963 			SetIsAbsorbSp(absorb);
964 			SetIsSuccess();
965 		}
966 	}
967 
968 	if (!IsPositive() && !IsSuccess() && (skill.affect_hp || skill.affect_sp)) {
969 		return IsSuccess();
970 	}
971 
972 	if (skill.affect_attack && Rand::PercentChance(to_hit)) {
973 		const auto atk = target->CanChangeAtkModifier(effect);
974 		if (atk != 0) {
975 			SetAffectedAtk(atk);
976 			if (skill.easyrpg_enable_stat_absorbing) {
977 				SetIsAbsorbAtk(absorb);
978 			}
979 			SetIsSuccess();
980 		}
981 	}
982 	if (skill.affect_defense && Rand::PercentChance(to_hit)) {
983 		const auto def = target->CanChangeDefModifier(effect);
984 		if (def != 0) {
985 			SetAffectedDef(def);
986 			if (skill.easyrpg_enable_stat_absorbing) {
987 				SetIsAbsorbDef(absorb);
988 			}
989 			SetIsSuccess();
990 		}
991 	}
992 	if (skill.affect_spirit && Rand::PercentChance(to_hit)) {
993 		const auto spi = target->CanChangeSpiModifier(effect);
994 		if (spi != 0) {
995 			SetAffectedSpi(spi);
996 			if (skill.easyrpg_enable_stat_absorbing) {
997 				SetIsAbsorbSpi(absorb);
998 			}
999 			SetIsSuccess();
1000 		}
1001 	}
1002 	if (skill.affect_agility && Rand::PercentChance(to_hit)) {
1003 		const auto agi = target->CanChangeAgiModifier(effect);
1004 		if (agi != 0) {
1005 			SetAffectedAgi(agi);
1006 			if (skill.easyrpg_enable_stat_absorbing) {
1007 				SetIsAbsorbAgi(absorb);
1008 			}
1009 			SetIsSuccess();
1010 		}
1011 	}
1012 
1013 	bool heals_states = IsPositive() ^ (Player::IsRPG2k3() && skill.reverse_state_effect);
1014 	bool affected_death = false;
1015 	int to_hit_states = (skill.easyrpg_state_hit != -1 ? skill.easyrpg_state_hit : to_hit);
1016 	for (int i = 0; i < static_cast<int>(skill.state_effects.size()); i++) {
1017 		if (!skill.state_effects[i])
1018 			continue;
1019 		auto state_id = i + 1;
1020 
1021 		bool target_has_state = State::Has(state_id, target_states);
1022 
1023 		if (!heals_states && target_has_state) {
1024 			SetIsSuccess();
1025 			AddAffectedState(StateEffect{state_id, StateEffect::AlreadyInflicted});
1026 			continue;
1027 		}
1028 
1029 		if (!Rand::PercentChance(to_hit_states)) {
1030 			continue;
1031 		}
1032 
1033 		if (heals_states) {
1034 			if (target_has_state) {
1035 				// RPG_RT 2k3 skills which fail due to permanent states don't "miss"
1036 				SetIsSuccess();
1037 				if (State::Remove(state_id, target_states, target_perm_states)) {
1038 					AddAffectedState(StateEffect{state_id, StateEffect::Healed});
1039 					affected_death |= (state_id == lcf::rpg::State::kDeathID);
1040 				}
1041 			}
1042 		} else if (Rand::PercentChance(target->GetStateProbability(state_id))) {
1043 			if (State::Add(state_id, target_states, target_perm_states, true)) {
1044 				SetIsSuccess();
1045 				AddAffectedState(StateEffect{state_id, StateEffect::Inflicted});
1046 				affected_death |= (state_id == lcf::rpg::State::kDeathID);
1047 			}
1048 		}
1049 	}
1050 
1051 	if (IsRevived() && effect > 0) {
1052 		// If resurrected and no HP selected, the effect value is a percentage:
1053 		if (skill.affect_hp) {
1054 			SetAffectedHp(std::max(0, effect));
1055 		} else {
1056 			SetAffectedHp(target->GetMaxHp() * effect / 100);
1057 		}
1058 	}
1059 
1060 	// When a skill inflicts death state, other states can also be inflicted, but attributes will be skipped
1061 	if (!heals_states && affected_death) {
1062 		return SetIsSuccess();
1063 	}
1064 
1065 	// Attribute resistance / weakness + an attribute selected + can be modified
1066 	int to_hit_attribute_shift = (skill.easyrpg_attribute_hit != -1 ? skill.easyrpg_attribute_hit : to_hit);
1067 	if (skill.affect_attr_defence) {
1068 		auto shift = IsPositive() ? 1 : -1;
1069 		for (int i = 0; i < static_cast<int>(skill.attribute_effects.size()); i++) {
1070 			auto id = i + 1;
1071 			if (skill.attribute_effects[i]
1072 					&& GetTarget()->CanShiftAttributeRate(id, shift)
1073 					&& Rand::PercentChance(to_hit_attribute_shift)
1074 					)
1075 			{
1076 				AddAffectedAttribute({ id, shift});
1077 				SetIsSuccess();
1078 			}
1079 		}
1080 	}
1081 
1082 	return IsSuccess();
1083 }
1084 
GetStartMessage(int line) const1085 std::string Game_BattleAlgorithm::Skill::GetStartMessage(int line) const {
1086 	if (item && item->using_message == 0) {
1087 		if (line == 0) {
1088 			if (Player::IsRPG2k()) {
1089 				return BattleMessage::GetItemStartMessage2k(*GetSource(), *item);
1090 			} else {
1091 				return BattleMessage::GetItemStartMessage2k3(*GetSource(), *item);
1092 			}
1093 		}
1094 		return "";
1095 	}
1096 
1097 	const auto* target = GetOriginalSingleTarget();
1098 	if (line == 0) {
1099 		if (Player::IsRPG2k()) {
1100 			if (!skill.using_message1.empty()) {
1101 				return BattleMessage::GetSkillFirstStartMessage2k(*GetSource(), target, skill);
1102 			} else {
1103 				return BattleMessage::GetSkillSecondStartMessage2k(*GetSource(), target, skill);
1104 			}
1105 		} else {
1106 			return BattleMessage::GetSkillStartMessage2k3(*GetSource(), target, skill);
1107 		}
1108 	}
1109 	if (line == 1 && Player::IsRPG2k() && !skill.using_message2.empty()) {
1110 		return BattleMessage::GetSkillSecondStartMessage2k(*GetSource(), target, skill);
1111 	}
1112 	return "";
1113 }
1114 
GetSourcePose() const1115 int Game_BattleAlgorithm::Skill::GetSourcePose() const {
1116 	auto* source = GetSource();
1117 	if (source->GetType() == Game_Battler::Type_Ally) {
1118 		if (static_cast<int>(skill.battler_animation_data.size()) > source->GetId() - 1) {
1119 			return skill.battler_animation_data[source->GetId() - 1].pose;
1120 		}
1121 	}
1122 
1123 	return lcf::rpg::BattlerAnimation::Pose_Skill;
1124 }
1125 
GetCBAMovement() const1126 int Game_BattleAlgorithm::Skill::GetCBAMovement() const {
1127 	auto* source = GetSource();
1128 	if (source->GetType() == Game_Battler::Type_Ally) {
1129 		if (static_cast<int>(skill.battler_animation_data.size()) > source->GetId() - 1) {
1130 			return skill.battler_animation_data[source->GetId() - 1].movement;
1131 		}
1132 	}
1133 
1134 	return lcf::rpg::BattlerAnimationItemSkill::Movement_none;
1135 }
1136 
GetCBAAfterimage() const1137 int Game_BattleAlgorithm::Skill::GetCBAAfterimage() const {
1138 	auto* source = GetSource();
1139 	if (source->GetType() == Game_Battler::Type_Ally) {
1140 		if (static_cast<int>(skill.battler_animation_data.size()) > source->GetId() - 1) {
1141 			return skill.battler_animation_data[source->GetId() - 1].after_image;
1142 		}
1143 	}
1144 
1145 	return lcf::rpg::BattlerAnimationItemSkill::Afterimage_none;
1146 }
1147 
GetStartSe() const1148 const lcf::rpg::Sound* Game_BattleAlgorithm::Skill::GetStartSe() const {
1149 	if (skill.type == lcf::rpg::Skill::Type_switch) {
1150 		return &skill.sound_effect;
1151 	}
1152 	return nullptr;
1153 }
1154 
GetFailureSe() const1155 const lcf::rpg::Sound* Game_BattleAlgorithm::Skill::GetFailureSe() const {
1156 	return skill.failure_message != 3
1157 		? nullptr
1158 		: &Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Evasion);
1159 }
1160 
GetFailureMessage() const1161 std::string Game_BattleAlgorithm::Skill::GetFailureMessage() const {
1162 	return BattleMessage::GetSkillFailureMessage(*GetSource(), *GetTarget(), skill);
1163 }
1164 
IsReflected(const Game_Battler & target) const1165 bool Game_BattleAlgorithm::Skill::IsReflected(const Game_Battler& target) const {
1166 	if (item || skill.easyrpg_ignore_reflect) {
1167 		return false;
1168 	}
1169 	return IsTargetValid(target) && target.HasReflectState() && target.GetType() != GetSource()->GetType();
1170 }
1171 
ActionIsPossible() const1172 bool Game_BattleAlgorithm::Skill::ActionIsPossible() const {
1173 	auto* source = GetSource();
1174 	if (item) {
1175 		return Main_Data::game_party->GetItemTotalCount(item->ID) > 0;
1176 	}
1177 	if (!source->IsSkillUsable(skill.ID)) {
1178 		return false;
1179 	}
1180 	// RPG_RT performs this check only for enemies and if skill is single target
1181 	const auto* target = GetOriginalSingleTarget();
1182 	if (source->GetType() == Game_Battler::Type_Enemy && target && target->GetType() == source->GetType()) {
1183 		if (!EnemyAi::IsSkillEffectiveOn(skill, *target, true)) {
1184 			return false;
1185 		}
1186 	}
1187 	return true;
1188 }
1189 
Item(Game_Battler * source,Game_Battler * target,const lcf::rpg::Item & item)1190 Game_BattleAlgorithm::Item::Item(Game_Battler* source, Game_Battler* target, const lcf::rpg::Item& item) :
1191 	AlgorithmBase(Type::Item, source, target), item(item) {
1192 		// no-op
1193 }
1194 
Item(Game_Battler * source,Game_Party_Base * target,const lcf::rpg::Item & item)1195 Game_BattleAlgorithm::Item::Item(Game_Battler* source, Game_Party_Base* target, const lcf::rpg::Item& item) :
1196 	AlgorithmBase(Type::Item, source, target), item(item) {
1197 		// no-op
1198 }
1199 
vStart()1200 bool Game_BattleAlgorithm::Item::vStart() {
1201 	Main_Data::game_party->ConsumeItemUse(item.ID);
1202 	return true;
1203 }
1204 
Item(Game_Battler * source,const lcf::rpg::Item & item)1205 Game_BattleAlgorithm::Item::Item(Game_Battler* source, const lcf::rpg::Item& item) :
1206 	Item(source, source, item) {}
1207 
IsTargetValid(const Game_Battler &) const1208 bool Game_BattleAlgorithm::Item::IsTargetValid(const Game_Battler&) const {
1209 	return item.type == lcf::rpg::Item::Type_medicine || item.type == lcf::rpg::Item::Type_switch;
1210 }
1211 
vExecute()1212 bool Game_BattleAlgorithm::Item::vExecute() {
1213 	auto* target = GetTarget();
1214 
1215 	if (item.type == lcf::rpg::Item::Type_switch) {
1216 		SetAffectedSwitch(item.switch_id);
1217 		return SetIsSuccess();
1218 	}
1219 
1220 	if (item.type == lcf::rpg::Item::Type_medicine) {
1221 		SetIsPositive(true);
1222 
1223 		// RM2k3 BUG: In rm2k3 battle system, this IsItemUsable() check is only applied when equipment_setting == actor, not for class.
1224 		if (GetTarget()->GetType() == Game_Battler::Type_Ally && !static_cast<Game_Actor*>(GetTarget())->IsItemUsable(item.ID)) {
1225 			// No effect, but doesn't behave like a dodge or damage to set healing and success to true.
1226 			return SetIsSuccess();
1227 		}
1228 
1229 		if (item.ko_only && !GetTarget()->IsDead()) {
1230 			return SetIsSuccess();
1231 		}
1232 
1233 		// Make a copy of the target's state set and see what we can apply.
1234 		auto target_states = target->GetStates();
1235 		auto target_perm_states = target->GetPermanentStates();
1236 
1237 		for (int i = 0; i < (int)item.state_set.size(); i++) {
1238 			if (item.state_set[i]) {
1239 				if (State::Remove(i + 1, target_states, target_perm_states)) {
1240 					AddAffectedState(StateEffect(i+1, StateEffect::Healed));
1241 				}
1242 			}
1243 		}
1244 
1245 		// HP recovery
1246 		if ((item.recover_hp != 0 || item.recover_hp_rate != 0) && (!target->IsDead() || IsRevived())) {
1247 			SetAffectedHp(item.recover_hp_rate * GetTarget()->GetMaxHp() / 100 + item.recover_hp);
1248 		}
1249 
1250 		// SP recovery
1251 		if (item.recover_sp != 0 || item.recover_sp_rate != 0) {
1252 			SetAffectedSp(item.recover_sp_rate * GetTarget()->GetMaxSp() / 100 + item.recover_sp);
1253 		}
1254 
1255 		return SetIsSuccess();
1256 	}
1257 
1258 	assert("Unsupported battle item type");
1259 	return SetIsFailure();
1260 }
1261 
GetStartMessage(int line) const1262 std::string Game_BattleAlgorithm::Item::GetStartMessage(int line) const {
1263 	if (line == 0) {
1264 		if (Player::IsRPG2k()) {
1265 			return BattleMessage::GetItemStartMessage2k(*GetSource(), item);
1266 		} else {
1267 			return BattleMessage::GetItemStartMessage2k3(*GetSource(), item);
1268 		}
1269 	}
1270 	return "";
1271 }
1272 
GetSourcePose() const1273 int Game_BattleAlgorithm::Item::GetSourcePose() const {
1274 	return lcf::rpg::BattlerAnimation::Pose_Item;
1275 }
1276 
GetCBAMovement() const1277 int Game_BattleAlgorithm::Item::GetCBAMovement() const {
1278 	auto* source = GetSource();
1279 	if (source->GetType() == Game_Battler::Type_Ally) {
1280 		if (static_cast<int>(item.animation_data.size()) > source->GetId() - 1) {
1281 			return item.animation_data[source->GetId() - 1].movement;
1282 		}
1283 	}
1284 
1285 	return lcf::rpg::BattlerAnimationItemSkill::Movement_none;
1286 }
1287 
GetStartSe() const1288 const lcf::rpg::Sound* Game_BattleAlgorithm::Item::GetStartSe() const {
1289 	if (item.type == lcf::rpg::Item::Type_medicine || item.type == lcf::rpg::Item::Type_switch) {
1290 		return &Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_UseItem);
1291 	}
1292 	return nullptr;
1293 }
1294 
ActionIsPossible() const1295 bool Game_BattleAlgorithm::Item::ActionIsPossible() const {
1296 	return Main_Data::game_party->GetItemTotalCount(item.ID) > 0;
1297 }
1298 
Defend(Game_Battler * source)1299 Game_BattleAlgorithm::Defend::Defend(Game_Battler* source) :
1300 	AlgorithmBase(Type::Defend, source, source) {
1301 		source->SetIsDefending(true);
1302 }
1303 
GetStartMessage(int line) const1304 std::string Game_BattleAlgorithm::Defend::GetStartMessage(int line) const {
1305 	if (line == 0) {
1306 		if (Player::IsRPG2k()) {
1307 			return BattleMessage::GetDefendStartMessage2k(*GetSource());
1308 		} else if (GetSource()->GetType() == Game_Battler::Type_Enemy) {
1309 			return BattleMessage::GetDefendStartMessage2k3(*GetSource());
1310 		}
1311 	}
1312 	return "";
1313 }
1314 
GetSourcePose() const1315 int Game_BattleAlgorithm::Defend::GetSourcePose() const {
1316 	return lcf::rpg::BattlerAnimation::Pose_Defend;
1317 }
1318 
Observe(Game_Battler * source)1319 Game_BattleAlgorithm::Observe::Observe(Game_Battler* source) :
1320 AlgorithmBase(Type::Observe, source, source) {
1321 	// no-op
1322 }
1323 
GetStartMessage(int line) const1324 std::string Game_BattleAlgorithm::Observe::GetStartMessage(int line) const {
1325 	if (line == 0) {
1326 		if (Player::IsRPG2k()) {
1327 			return BattleMessage::GetObserveStartMessage2k(*GetSource());
1328 		} else if (GetSource()->GetType() == Game_Battler::Type_Enemy) {
1329 			return BattleMessage::GetObserveStartMessage2k3(*GetSource());
1330 		}
1331 	}
1332 	return "";
1333 }
1334 
Charge(Game_Battler * source)1335 Game_BattleAlgorithm::Charge::Charge(Game_Battler* source) :
1336 AlgorithmBase(Type::Charge, source, source) {
1337 	// no-op
1338 }
1339 
GetStartMessage(int line) const1340 std::string Game_BattleAlgorithm::Charge::GetStartMessage(int line) const {
1341 	if (line == 0) {
1342 		if (Player::IsRPG2k()) {
1343 			return BattleMessage::GetChargeUpStartMessage2k(*GetSource());
1344 		} else if (GetSource()->GetType() == Game_Battler::Type_Enemy) {
1345 			return BattleMessage::GetChargeUpStartMessage2k3(*GetSource());
1346 		}
1347 	}
1348 	return "";
1349 }
1350 
ApplyCustomEffect()1351 void Game_BattleAlgorithm::Charge::ApplyCustomEffect() {
1352 	GetTarget()->SetCharged(true);
1353 }
1354 
SelfDestruct(Game_Battler * source,Game_Party_Base * target)1355 Game_BattleAlgorithm::SelfDestruct::SelfDestruct(Game_Battler* source, Game_Party_Base* target) :
1356 AlgorithmBase(Type::SelfDestruct, source, target) {
1357 	// no-op
1358 }
1359 
GetStartMessage(int line) const1360 std::string Game_BattleAlgorithm::SelfDestruct::GetStartMessage(int line) const {
1361 	if (line == 0) {
1362 		if (Player::IsRPG2k()) {
1363 			return BattleMessage::GetSelfDestructStartMessage2k(*GetSource());
1364 		} else if (GetSource()->GetType() == Game_Battler::Type_Enemy) {
1365 			return BattleMessage::GetSelfDestructStartMessage2k3(*GetSource());
1366 		}
1367 	}
1368 	return "";
1369 }
1370 
GetStartSe() const1371 const lcf::rpg::Sound* Game_BattleAlgorithm::SelfDestruct::GetStartSe() const {
1372 	return &Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyKill);
1373 }
1374 
vExecute()1375 bool Game_BattleAlgorithm::SelfDestruct::vExecute() {
1376 	auto& source = *GetSource();
1377 	auto& target = *GetTarget();
1378 
1379 	auto effect = Algo::CalcSelfDestructEffect(source, target, true);
1380 	effect = Algo::AdjustDamageForDefend(effect, target);
1381 	effect = Utils::Clamp(effect, -MaxDamageValue(), MaxDamageValue());
1382 
1383 	SetAffectedHp(-effect);
1384 
1385 	// Recover physical states only if not killed.
1386 	if (target.GetHp() + GetAffectedHp() > 0) {
1387 		// Make a copy of the target's state set and see what we can apply.
1388 		auto target_states = target.GetStates();
1389 		auto target_perm_states = target.GetPermanentStates();
1390 
1391 		// Conditions healed by physical attack:
1392 		BattlePhysicalStateHeal(100, target_states, target_perm_states);
1393 	}
1394 
1395 	return SetIsSuccess();
1396 }
1397 
ApplyCustomEffect()1398 void Game_BattleAlgorithm::SelfDestruct::ApplyCustomEffect() {
1399 	auto* source = GetSource();
1400 	// Only monster can self destruct
1401 	if (animate && source->GetType() == Game_Battler::Type_Enemy) {
1402 		auto* enemy = static_cast<Game_Enemy*>(source);
1403 		enemy->SetHidden(true);
1404 		enemy->SetExplodeTimer();
1405 	}
1406 	animate = false;
1407 }
1408 
Escape(Game_Battler * source)1409 Game_BattleAlgorithm::Escape::Escape(Game_Battler* source) :
1410 	AlgorithmBase(Type::Escape, source, source) {
1411 	// no-op
1412 }
1413 
GetStartMessage(int line) const1414 std::string Game_BattleAlgorithm::Escape::GetStartMessage(int line) const {
1415 	if (line == 0) {
1416 		if (Player::IsRPG2k()) {
1417 			return BattleMessage::GetEscapeStartMessage2k(*GetSource());
1418 		} else if (GetSource()->GetType() == Game_Battler::Type_Enemy) {
1419 			return BattleMessage::GetEscapeStartMessage2k3(*GetSource());
1420 		}
1421 	}
1422 	return "";
1423 }
1424 
GetSourcePose() const1425 int Game_BattleAlgorithm::Escape::GetSourcePose() const {
1426 	return lcf::rpg::BattlerAnimation::Pose_WalkRight;
1427 }
1428 
GetStartSe() const1429 const lcf::rpg::Sound* Game_BattleAlgorithm::Escape::GetStartSe() const {
1430 	auto* source = GetSource();
1431 	if (source->GetType() == Game_Battler::Type_Ally) {
1432 		return AlgorithmBase::GetStartSe();
1433 	}
1434 	return &Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Escape);
1435 }
1436 
ApplyCustomEffect()1437 void Game_BattleAlgorithm::Escape::ApplyCustomEffect() {
1438 	auto* source = GetSource();
1439 	if (source->GetType() == Game_Battler::Type_Enemy) {
1440 		auto* enemy = static_cast<Game_Enemy*>(source);
1441 		enemy->SetHidden(true);
1442 		enemy->SetDeathTimer();
1443 	}
1444 }
1445 
Transform(Game_Battler * source,int new_monster_id)1446 Game_BattleAlgorithm::Transform::Transform(Game_Battler* source, int new_monster_id) :
1447 AlgorithmBase(Type::Transform, source, source), new_monster_id(new_monster_id) {
1448 	// no-op
1449 }
1450 
GetStartMessage(int line) const1451 std::string Game_BattleAlgorithm::Transform::GetStartMessage(int line) const {
1452 	if (line == 0 && Player::IsRPG2k()) {
1453 		auto* enemy = lcf::ReaderUtil::GetElement(lcf::Data::enemies, new_monster_id);
1454 		return BattleMessage::GetTransformStartMessage(*GetSource(), *enemy);
1455 	}
1456 	return "";
1457 }
1458 
ApplyCustomEffect()1459 void Game_BattleAlgorithm::Transform::ApplyCustomEffect() {
1460 	auto* source = GetSource();
1461 	if (source->GetType() == Game_Battler::Type_Enemy) {
1462 		auto* enemy = static_cast<Game_Enemy*>(source);
1463 		enemy->Transform(new_monster_id);
1464 		enemy->Flash(31,31,31,31,20);
1465 	}
1466 }
1467 
DoNothing(Game_Battler * source)1468 Game_BattleAlgorithm::DoNothing::DoNothing(Game_Battler* source) :
1469 AlgorithmBase(Type::DoNothing, source, source) {
1470 	// no-op
1471 }
1472 
1473