1 /*
2 * This file is part of EasyRPG Player.
3 *
4 * EasyRPG Player is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * EasyRPG Player is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16 */
17 #include "enemyai.h"
18 #include "game_actor.h"
19 #include "game_enemy.h"
20 #include "game_enemyparty.h"
21 #include "game_party.h"
22 #include "game_switches.h"
23 #include "game_battlealgorithm.h"
24 #include "game_battle.h"
25 #include "algo.h"
26 #include "player.h"
27 #include "output.h"
28 #include "rand.h"
29 #include <lcf/reader_util.h>
30 #include <lcf/data.h>
31
32 namespace EnemyAi {
33
34 #ifdef EP_DEBUG_ENEMYAI
35 template <typename... Args>
DebugLog(const char * fmt,Args &&...args)36 static void DebugLog(const char* fmt, Args&&... args) {
37 Output::Debug(fmt, std::forward<Args>(args)...);
38 }
39 #else
40 template <typename... Args>
41 static void DebugLog(const char*, Args&&...) {}
42 #endif
43
44 constexpr decltype(RpgRtCompat::name) RpgRtCompat::name;
45 constexpr decltype(RpgRtImproved::name) RpgRtImproved::name;
46
MakeAttack(Game_Enemy & enemy,int hits)47 static std::shared_ptr<Game_BattleAlgorithm::AlgorithmBase> MakeAttack(Game_Enemy& enemy, int hits) {
48 return std::make_shared<Game_BattleAlgorithm::Normal>(&enemy, Main_Data::game_party->GetRandomActiveBattler(), hits);
49 }
50
MakeAttackAllies(Game_Enemy & enemy,int hits)51 static std::shared_ptr<Game_BattleAlgorithm::AlgorithmBase> MakeAttackAllies(Game_Enemy& enemy, int hits) {
52 return std::make_shared<Game_BattleAlgorithm::Normal>(&enemy, Main_Data::game_enemyparty->GetRandomActiveBattler(), hits);
53 }
54
55
CreateAlgorithm(StringView name)56 std::unique_ptr<AlgorithmBase> CreateAlgorithm(StringView name) {
57 if (Utils::StrICmp(name, RpgRtImproved::name) == 0) {
58 return std::make_unique<RpgRtImproved>();
59 }
60 if (Utils::StrICmp(name, RpgRtCompat::name) != 0) {
61 static bool warned = false;
62 if (!warned) {
63 Output::Debug("Invalid AutoBattle algo name `{}' falling back to {} ...", name, RpgRtCompat::name);
64 warned = true;
65 }
66 }
67 return std::make_unique<RpgRtCompat>();
68 }
69
SetEnemyAiAction(Game_Enemy & source)70 void AlgorithmBase::SetEnemyAiAction(Game_Enemy& source) {
71 vSetEnemyAiAction(source);
72 if (source.GetBattleAlgorithm() == nullptr) {
73 source.SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(&source));
74 }
75 }
76
vSetEnemyAiAction(Game_Enemy & source)77 void RpgRtCompat::vSetEnemyAiAction(Game_Enemy& source) {
78 SelectEnemyAiActionRpgRtCompat(source, true);
79 }
80
vSetEnemyAiAction(Game_Enemy & source)81 void RpgRtImproved::vSetEnemyAiAction(Game_Enemy& source) {
82 SelectEnemyAiActionRpgRtCompat(source, false);
83 }
84
MakeBasicAction(Game_Enemy & enemy,const lcf::rpg::EnemyAction & action)85 static std::shared_ptr<Game_BattleAlgorithm::AlgorithmBase> MakeBasicAction(Game_Enemy& enemy, const lcf::rpg::EnemyAction& action) {
86 switch (action.basic) {
87 case lcf::rpg::EnemyAction::Basic_attack:
88 return MakeAttack(enemy, 1);
89 case lcf::rpg::EnemyAction::Basic_dual_attack:
90 return MakeAttack(enemy, 2);
91 case lcf::rpg::EnemyAction::Basic_defense:
92 return std::make_shared<Game_BattleAlgorithm::Defend>(&enemy);
93 case lcf::rpg::EnemyAction::Basic_observe:
94 return std::make_shared<Game_BattleAlgorithm::Observe>(&enemy);
95 case lcf::rpg::EnemyAction::Basic_charge:
96 return std::make_shared<Game_BattleAlgorithm::Charge>(&enemy);
97 case lcf::rpg::EnemyAction::Basic_autodestruction:
98 return std::make_shared<Game_BattleAlgorithm::SelfDestruct>(&enemy, Main_Data::game_party.get());
99 case lcf::rpg::EnemyAction::Basic_escape:
100 return std::make_shared<Game_BattleAlgorithm::Escape>(&enemy);
101 case lcf::rpg::EnemyAction::Basic_nothing:
102 return std::make_shared<Game_BattleAlgorithm::DoNothing>(&enemy);
103 }
104 return nullptr;
105 }
106
GetRandomSkillTarget(Game_Party_Base & party,const lcf::rpg::Skill & skill,bool emulate_bugs)107 static Game_Battler* GetRandomSkillTarget(Game_Party_Base& party, const lcf::rpg::Skill& skill, bool emulate_bugs) {
108 std::vector<Game_Battler*> battlers;
109 party.GetBattlers(battlers);
110 for (auto iter = battlers.begin(); iter != battlers.end();) {
111 if (IsSkillEffectiveOn(skill, **iter, emulate_bugs)) {
112 ++iter;
113 } else {
114 iter = battlers.erase(iter);
115 }
116 }
117 auto choice = Rand::GetRandomNumber(0, battlers.size() - 1);
118 return battlers[choice];
119 }
120
MakeSkillAction(Game_Enemy & enemy,const lcf::rpg::EnemyAction & action,bool emulate_bugs)121 static std::shared_ptr<Game_BattleAlgorithm::AlgorithmBase> MakeSkillAction(Game_Enemy& enemy, const lcf::rpg::EnemyAction& action, bool emulate_bugs) {
122 const auto* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, action.skill_id);
123 if (!skill) {
124 Output::Warning("EnemyAi::MakeSkillAction: Enemy can't use invalid skill {}", action.skill_id);
125 return nullptr;
126 }
127
128 switch (skill->scope) {
129 case lcf::rpg::Skill::Scope_enemy:
130 return std::make_shared<Game_BattleAlgorithm::Skill>(&enemy, Main_Data::game_party->GetRandomActiveBattler(), *skill);
131 case lcf::rpg::Skill::Scope_ally:
132 return std::make_shared<Game_BattleAlgorithm::Skill>(&enemy, GetRandomSkillTarget(*Main_Data::game_enemyparty, *skill, emulate_bugs), *skill);
133 case lcf::rpg::Skill::Scope_enemies:
134 return std::make_shared<Game_BattleAlgorithm::Skill>(&enemy, Main_Data::game_party.get(), *skill);
135 case lcf::rpg::Skill::Scope_self:
136 return std::make_shared<Game_BattleAlgorithm::Skill>(&enemy, &enemy, *skill);
137 case lcf::rpg::Skill::Scope_party:
138 return std::make_shared<Game_BattleAlgorithm::Skill>(&enemy, Main_Data::game_enemyparty.get(), *skill);
139 }
140 return nullptr;
141 }
142
MakeAction(Game_Enemy & enemy,const lcf::rpg::EnemyAction & action,bool emulate_bugs)143 static std::shared_ptr<Game_BattleAlgorithm::AlgorithmBase> MakeAction(Game_Enemy& enemy, const lcf::rpg::EnemyAction& action, bool emulate_bugs) {
144 switch (action.kind) {
145 case lcf::rpg::EnemyAction::Kind_basic:
146 return MakeBasicAction(enemy, action);
147 case lcf::rpg::EnemyAction::Kind_skill:
148 return MakeSkillAction(enemy, action, emulate_bugs);
149 case lcf::rpg::EnemyAction::Kind_transformation:
150 return std::make_shared<Game_BattleAlgorithm::Transform>(&enemy, action.enemy_id);
151 }
152 return nullptr;
153 }
154
SetEnemyAction(Game_Enemy & enemy,const lcf::rpg::EnemyAction & action,bool emulate_bugs)155 void SetEnemyAction(Game_Enemy& enemy, const lcf::rpg::EnemyAction& action, bool emulate_bugs) {
156 auto algo = MakeAction(enemy, action, emulate_bugs);
157
158 if (algo) {
159 if (action.switch_on) {
160 algo->SetSwitchEnable(action.switch_on_id);
161 }
162 if (action.switch_off) {
163 algo->SetSwitchDisable(action.switch_off_id);
164 }
165 }
166
167 enemy.SetBattleAlgorithm(std::move(algo));
168 }
169
170
IsSkillEffectiveOn(const lcf::rpg::Skill & skill,const Game_Battler & target,bool emulate_bugs)171 bool IsSkillEffectiveOn(const lcf::rpg::Skill& skill,
172 const Game_Battler& target,
173 bool emulate_bugs) {
174
175 if (skill.type == lcf::rpg::Skill::Type_switch) {
176 return true;
177 }
178
179 if (!Algo::IsNormalOrSubskill(skill)) {
180 return false;
181 }
182
183 if (Algo::SkillTargetsEnemies(skill)) {
184 return target.Exists();
185 }
186
187 if (!target.Exists()) {
188 // RPG_RT Bug: Ignores reverse_state_effects_flag
189 // RPG_RT Bug: Allows revival to target hidden enemies
190 return (skill.state_effects.size() > 0 && skill.state_effects[0])
191 && (emulate_bugs || (!skill.reverse_state_effect && target.IsDead()));
192 }
193
194 if (skill.affect_hp
195 || skill.affect_sp
196 || skill.affect_attack
197 || skill.affect_defense
198 || skill.affect_spirit
199 || skill.affect_agility) {
200 return true;
201 }
202
203 for (int id = 1; id <= static_cast<int>(skill.state_effects.size()); ++id) {
204 // RPG_RT Bug: Ignores reverse_state_effects_flag
205 if (skill.state_effects[id - 1] && (target.HasState(id) || (skill.reverse_state_effect && !emulate_bugs))) {
206 return true;
207 }
208 }
209
210 if (skill.affect_attr_defence) {
211 for (auto& attr: skill.attribute_effects) {
212 if (attr) {
213 return true;
214 }
215 }
216 }
217
218 return false;
219 }
220
IsActionValid(const Game_Enemy & source,const lcf::rpg::EnemyAction & action)221 bool IsActionValid(const Game_Enemy& source, const lcf::rpg::EnemyAction& action) {
222 if (action.kind == action.Kind_skill) {
223 if (!source.IsSkillUsable(action.skill_id)) {
224 return false;
225 }
226 }
227
228 switch (action.condition_type) {
229 case lcf::rpg::EnemyAction::ConditionType_always:
230 return true;
231 case lcf::rpg::EnemyAction::ConditionType_switch:
232 return Main_Data::game_switches->Get(action.switch_id);
233 case lcf::rpg::EnemyAction::ConditionType_turn:
234 {
235 int turns = Game_Battle::GetTurn();
236 return Game_Battle::CheckTurns(turns, action.condition_param2, action.condition_param1);
237 }
238 case lcf::rpg::EnemyAction::ConditionType_actors:
239 {
240 std::vector<Game_Battler*> battlers;
241 Main_Data::game_enemyparty->GetActiveBattlers(battlers);
242 int count = (int)battlers.size();
243 return count >= action.condition_param1 && count <= action.condition_param2;
244 }
245 case lcf::rpg::EnemyAction::ConditionType_hp:
246 {
247 int hp_percent = source.GetHp() * 100 / source.GetMaxHp();
248 return hp_percent >= action.condition_param1 && hp_percent <= action.condition_param2;
249 }
250 case lcf::rpg::EnemyAction::ConditionType_sp:
251 {
252 int sp_percent = source.GetSp() * 100 / source.GetMaxSp();
253 return sp_percent >= action.condition_param1 && sp_percent <= action.condition_param2;
254 }
255 case lcf::rpg::EnemyAction::ConditionType_party_lvl:
256 {
257 int party_lvl = Main_Data::game_party->GetAverageLevel();
258 return party_lvl >= action.condition_param1 && party_lvl <= action.condition_param2;
259 }
260 case lcf::rpg::EnemyAction::ConditionType_party_fatigue:
261 {
262 int party_exh = Main_Data::game_party->GetFatigue();
263 return party_exh >= action.condition_param1 && party_exh <= action.condition_param2;
264 }
265 default:
266 return true;
267 }
268 }
269
IsSkillEffectiveOnAnyTarget(Game_Enemy & source,int skill_id,bool emulate_bugs)270 static bool IsSkillEffectiveOnAnyTarget(Game_Enemy& source, int skill_id, bool emulate_bugs) {
271 const auto* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, skill_id);
272 assert(skill);
273 if (!Algo::IsNormalOrSubskill(*skill)) {
274 return true;
275 }
276
277 switch (skill->scope) {
278 case lcf::rpg::Skill::Scope_enemy:
279 case lcf::rpg::Skill::Scope_enemies:
280 break;
281 case lcf::rpg::Skill::Scope_self:
282 return IsSkillEffectiveOn(*skill, source, emulate_bugs);
283 case lcf::rpg::Skill::Scope_ally:
284 case lcf::rpg::Skill::Scope_party:
285 for (auto* enemy: Main_Data::game_enemyparty->GetEnemies()) {
286 if (IsSkillEffectiveOn(*skill, *enemy, emulate_bugs)) {
287 return true;
288 }
289 }
290 return false;
291 }
292
293 return true;
294 }
295
SelectEnemyAiActionRpgRtCompat(Game_Enemy & source,bool emulate_bugs)296 void SelectEnemyAiActionRpgRtCompat(Game_Enemy& source, bool emulate_bugs) {
297 const auto& actions = source.GetDbEnemy().actions;
298 std::vector<int> prios(actions.size(), 0);
299 int max_prio = 0;
300 for (int i = 0; i < static_cast<int>(actions.size()); ++i) {
301 const auto& action = actions[i];
302 if (IsActionValid(source, action)) {
303 prios[i] = action.rating;
304 max_prio = std::max<int>(max_prio, action.rating);
305 DebugLog("ENEMYAI: Enemy {}({}) Allow Action id={} kind={} basic={} rating={}", source.GetName(), source.GetTroopMemberId(), action.ID, action.kind, action.basic, action.rating);
306 } else {
307 DebugLog("ENEMYAI: Enemy {}({}) Discard Action id={} kind={} basic={} rating={}", source.GetName(), source.GetTroopMemberId(), action.ID, action.kind, action.basic, action.rating);
308 }
309 }
310
311 DebugLog("ENEMYAI: Enemy {}({}) max_prio={}", source.GetName(), source.GetTroopMemberId(), max_prio);
312
313 if (max_prio) {
314 for (auto& pr: prios) {
315 pr = std::max<int>(0, pr - max_prio + 10);
316 }
317 }
318
319 for (int i = 0; i < static_cast<int>(actions.size()); ++i) {
320 const auto& action = actions[i];
321 if (action.kind == lcf::rpg::EnemyAction::Kind_skill) {
322 if (prios[i] > 0 && !IsSkillEffectiveOnAnyTarget(source, action.skill_id, emulate_bugs)) {
323 DebugLog("ENEMYAI: Enemy {}({}) Discard Action id={} kind={} basic={}, rating={}: No effective targets!", source.GetName(), source.GetTroopMemberId(), action.ID, action.kind, action.basic, action.rating);
324 prios[i] = 0;
325 }
326 }
327 }
328
329 int sum_prios = 0;
330 for (auto& pr: prios) {
331 sum_prios += pr;
332 }
333
334
335 if (sum_prios == 0) {
336 DebugLog("ENEMYAI: Enemy {}({}) No Available Actions!", source.GetName(), source.GetTroopMemberId());
337 return;
338 }
339
340 int which = Rand::GetRandomNumber(0, sum_prios - 1);
341 DebugLog("ENEMYAI: Enemy {}({}) sum_prios={} which={}", source.GetName(), source.GetTroopMemberId(), sum_prios, which);
342 const lcf::rpg::EnemyAction* selected_action = nullptr;
343 for (int i = 0; i < static_cast<int>(actions.size()); ++i) {
344 auto& action = actions[i];
345 selected_action = &action;
346 which -= prios[i];
347 if (which < 0) {
348 break;
349 }
350 }
351
352 if (selected_action) {
353 DebugLog("ENEMYAI: Enemy {}({}) Selected Action id={} kind={} basic={}, rating={}", source.GetName(), source.GetTroopMemberId(), selected_action->ID, selected_action->kind, selected_action->basic, selected_action->rating);
354 SetEnemyAction(source, *selected_action, emulate_bugs);
355 } else {
356 DebugLog("ENEMYAI: Enemy {}({}) No Selected Action!", source.GetName(), source.GetTroopMemberId());
357 }
358 }
359
SetStateRestrictedAction(Game_Enemy & source)360 bool SetStateRestrictedAction(Game_Enemy& source) {
361 if (!source.CanAct()) {
362 source.SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(&source));
363 return true;
364 }
365
366 if (source.GetSignificantRestriction() == lcf::rpg::State::Restriction_attack_ally) {
367 source.SetBattleAlgorithm(MakeAttackAllies(source, 1));
368 return true;
369 }
370
371 if (source.GetSignificantRestriction() == lcf::rpg::State::Restriction_attack_enemy) {
372 source.SetBattleAlgorithm(MakeAttack(source, 1));
373 return true;
374 }
375
376 if (source.IsCharged()) {
377 source.SetBattleAlgorithm(MakeAttack(source, 1));
378 return true;
379 }
380
381 return false;
382 }
383
384 } // namespace EnemyAi
385