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