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