1 /*
2  * This file is part of EasyRPG Player.
3  *
4  * EasyRPG Player is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * EasyRPG Player is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 // Headers
19 #include "game_actors.h"
20 #include "game_battle.h"
21 #include "game_enemyparty.h"
22 #include "game_interpreter_battle.h"
23 #include "game_party.h"
24 #include "game_switches.h"
25 #include "game_system.h"
26 #include "game_variables.h"
27 #include <lcf/reader_util.h>
28 #include "output.h"
29 #include "player.h"
30 #include "game_map.h"
31 #include "spriteset_battle.h"
32 #include <cassert>
33 
34 enum BranchBattleSubcommand {
35 	eOptionBranchBattleElse = 1
36 };
37 
Game_Interpreter_Battle(Span<const lcf::rpg::TroopPage> pages)38 Game_Interpreter_Battle::Game_Interpreter_Battle(Span<const lcf::rpg::TroopPage> pages)
39 	: Game_Interpreter(true), pages(pages), executed(pages.size(), false)
40 {
41 }
42 
AreConditionsMet(const lcf::rpg::TroopPageCondition & condition,Game_Battler * source)43 bool Game_Interpreter_Battle::AreConditionsMet(const lcf::rpg::TroopPageCondition& condition, Game_Battler* source) {
44 	if (!condition.flags.switch_a &&
45 		!condition.flags.switch_b &&
46 		!condition.flags.variable &&
47 		!condition.flags.turn &&
48 		!condition.flags.turn_enemy &&
49 		!condition.flags.turn_actor &&
50 		!condition.flags.fatigue &&
51 		!condition.flags.enemy_hp &&
52 		!condition.flags.actor_hp &&
53 		!condition.flags.command_actor
54 		) {
55 		// Pages without trigger are never run
56 		return false;
57 	}
58 
59 	if (condition.flags.switch_a && !Main_Data::game_switches->Get(condition.switch_a_id))
60 		return false;
61 
62 	if (condition.flags.switch_b && !Main_Data::game_switches->Get(condition.switch_b_id))
63 		return false;
64 
65 	if (condition.flags.variable && !(Main_Data::game_variables->Get(condition.variable_id) >= condition.variable_value))
66 		return false;
67 
68 	if (condition.flags.turn && !Game_Battle::CheckTurns(Game_Battle::GetTurn(), condition.turn_b, condition.turn_a))
69 		return false;
70 
71 	if (condition.flags.turn_enemy) {
72 		const auto& enemy = (*Main_Data::game_enemyparty)[condition.turn_enemy_id];
73 		if (source && source != &enemy)
74 			return false;
75 		if (!Game_Battle::CheckTurns(enemy.GetBattleTurn(), condition.turn_enemy_b, condition.turn_enemy_a))
76 			return false;
77 	}
78 
79 	if (condition.flags.turn_actor) {
80 		const auto* actor = Main_Data::game_actors->GetActor(condition.turn_actor_id);
81 		if (source && source != actor)
82 			return false;
83 		if (!Game_Battle::CheckTurns(actor->GetBattleTurn(), condition.turn_actor_b, condition.turn_actor_a))
84 			return false;
85 	}
86 
87 	if (condition.flags.fatigue) {
88 		int fatigue = Main_Data::game_party->GetFatigue();
89 		if (fatigue < condition.fatigue_min || fatigue > condition.fatigue_max)
90 			return false;
91 	}
92 
93 	if (condition.flags.enemy_hp) {
94 		Game_Battler& enemy = (*Main_Data::game_enemyparty)[condition.enemy_id];
95 		int hp = enemy.GetHp();
96 		int hpmin = enemy.GetMaxHp() * condition.enemy_hp_min / 100;
97 		int hpmax = enemy.GetMaxHp() * condition.enemy_hp_max / 100;
98 		if (hp < hpmin || hp > hpmax)
99 			return false;
100 	}
101 
102 	if (condition.flags.actor_hp) {
103 		Game_Actor* actor = Main_Data::game_actors->GetActor(condition.actor_id);
104 		int hp = actor->GetHp();
105 		int hpmin = actor->GetMaxHp() * condition.actor_hp_min / 100;
106 		int hpmax = actor->GetMaxHp() * condition.actor_hp_max / 100;
107 		if (hp < hpmin || hp > hpmax)
108 			return false;
109 	}
110 
111 	if (condition.flags.command_actor) {
112 		if (!source)
113 			return false;
114 		const auto* actor = Main_Data::game_actors->GetActor(condition.command_actor_id);
115 		if (source != actor)
116 			return false;
117 		if (condition.command_id != actor->GetLastBattleAction())
118 			return false;
119 	}
120 
121 	return true;
122 }
123 
ScheduleNextPage(Game_Battler * source)124 int Game_Interpreter_Battle::ScheduleNextPage(Game_Battler* source) {
125 	lcf::rpg::TroopPageCondition::Flags f;
126 	for (auto& ff: f.flags) ff = true;
127 
128 	return ScheduleNextPage(f, source);
129 }
130 
HasRequiredCondition(lcf::rpg::TroopPageCondition::Flags page,lcf::rpg::TroopPageCondition::Flags required)131 static bool HasRequiredCondition(lcf::rpg::TroopPageCondition::Flags page, lcf::rpg::TroopPageCondition::Flags required) {
132 	for (size_t i = 0; i < page.flags.size(); ++i) {
133 		if (required.flags[i] && page.flags[i]) {
134 			return true;
135 		}
136 	}
137 	return false;
138 }
139 
ScheduleNextPage(lcf::rpg::TroopPageCondition::Flags required_conditions,Game_Battler * source)140 int Game_Interpreter_Battle::ScheduleNextPage(lcf::rpg::TroopPageCondition::Flags required_conditions, Game_Battler* source) {
141 	if (IsRunning()) {
142 		return 0;
143 	}
144 
145 	for (size_t i = 0; i < pages.size(); ++i) {
146 		auto& page = pages[i];
147 		if (executed[i]
148 				|| !HasRequiredCondition(page.condition.flags, required_conditions)
149 				|| !AreConditionsMet(page.condition, source)) {
150 			continue;
151 		}
152 		Clear();
153 		Push(page.event_commands, 0);
154 		executed[i] = true;
155 		return i + 1;
156 	}
157 	return 0;
158 }
159 
160 // Execute Command.
ExecuteCommand()161 bool Game_Interpreter_Battle::ExecuteCommand() {
162 	auto& frame = GetFrame();
163 	const auto& com = frame.commands[frame.current_command];
164 
165 	switch (static_cast<Cmd>(com.code)) {
166 		case Cmd::CallCommonEvent:
167 			return CommandCallCommonEvent(com);
168 		case Cmd::ForceFlee:
169 			return CommandForceFlee(com);
170 		case Cmd::EnableCombo:
171 			return CommandEnableCombo(com);
172 		case Cmd::ChangeMonsterHP:
173 			return CommandChangeMonsterHP(com);
174 		case Cmd::ChangeMonsterMP:
175 			return CommandChangeMonsterMP(com);
176 		case Cmd::ChangeMonsterCondition:
177 			return CommandChangeMonsterCondition(com);
178 		case Cmd::ShowHiddenMonster:
179 			return CommandShowHiddenMonster(com);
180 		case Cmd::ChangeBattleBG:
181 			return CommandChangeBattleBG(com);
182 		case Cmd::ShowBattleAnimation_B:
183 			return CommandShowBattleAnimation(com);
184 		case Cmd::TerminateBattle:
185 			return CommandTerminateBattle(com);
186 		case Cmd::ConditionalBranch_B:
187 			return CommandConditionalBranchBattle(com);
188 		case Cmd::ElseBranch_B:
189 			return CommandElseBranchBattle(com);
190 		case Cmd::EndBranch_B:
191 			return CommandEndBranchBattle(com);
192 		case Cmd::Maniac_ControlBattle:
193 			return CommandManiacControlBattle(com);
194 		case Cmd::Maniac_ControlAtbGauge:
195 			return CommandManiacControlAtbGauge(com);
196 		case Cmd::Maniac_ChangeBattleCommandEx:
197 			return CommandManiacChangeBattleCommandEx(com);
198 		case Cmd::Maniac_GetBattleInfo:
199 			return CommandManiacGetBattleInfo(com);
200 		default:
201 			return Game_Interpreter::ExecuteCommand();
202 	}
203 }
204 
205 // Commands
206 
CommandCallCommonEvent(lcf::rpg::EventCommand const & com)207 bool Game_Interpreter_Battle::CommandCallCommonEvent(lcf::rpg::EventCommand const& com) {
208 	int evt_id = com.parameters[0];
209 
210 	Game_CommonEvent* common_event = lcf::ReaderUtil::GetElement(Game_Map::GetCommonEvents(), evt_id);
211 	if (!common_event) {
212 		Output::Warning("CallCommonEvent: Can't call invalid common event {}", evt_id);
213 		return true;
214 	}
215 
216 	Push(common_event);
217 
218 	return true;
219 }
220 
CommandForceFlee(lcf::rpg::EventCommand const & com)221 bool Game_Interpreter_Battle::CommandForceFlee(lcf::rpg::EventCommand const& com) {
222 	bool check = com.parameters[2] == 0;
223 
224 	switch (com.parameters[0]) {
225 	case 0:
226 		if (!check || Game_Battle::GetBattleCondition() != lcf::rpg::System::BattleCondition_pincers) {
227 			this->force_flee_enabled = true;
228 		}
229 	    break;
230 	case 1:
231 		if (!check || Game_Battle::GetBattleCondition() != lcf::rpg::System::BattleCondition_surround) {
232 			int num_escaped = 0;
233 			for (auto* enemy: Main_Data::game_enemyparty->GetEnemies()) {
234 				if (enemy->Exists()) {
235 					enemy->SetHidden(true);
236 					enemy->SetDeathTimer();
237 					++num_escaped;
238 				}
239 			}
240 			if (num_escaped) {
241 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Game_System::SFX_Escape));
242 			}
243 		}
244 	    break;
245 	case 2:
246 		if (!check || Game_Battle::GetBattleCondition() != lcf::rpg::System::BattleCondition_surround) {
247 			auto* enemy = Main_Data::game_enemyparty->GetEnemy(com.parameters[1]);
248 			if (enemy->Exists()) {
249 				enemy->SetHidden(true);
250 				enemy->SetDeathTimer();
251 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Game_System::SFX_Escape));
252 			}
253 		}
254 	    break;
255 	}
256 
257 	return true;
258 }
259 
CommandEnableCombo(lcf::rpg::EventCommand const & com)260 bool Game_Interpreter_Battle::CommandEnableCombo(lcf::rpg::EventCommand const& com) {
261 	int actor_id = com.parameters[0];
262 
263 	if (!Main_Data::game_party->IsActorInParty(actor_id)) {
264 		return true;
265 	}
266 
267 	int command_id = com.parameters[1];
268 	int multiple = com.parameters[2];
269 
270 	Game_Actor* actor = Main_Data::game_actors->GetActor(actor_id);
271 
272 	if (!actor) {
273 		Output::Warning("EnableCombo: Invalid actor ID {}", actor_id);
274 		return true;
275 	}
276 
277 	actor->SetBattleCombo(command_id, multiple);
278 
279 	return true;
280 }
281 
CommandChangeMonsterHP(lcf::rpg::EventCommand const & com)282 bool Game_Interpreter_Battle::CommandChangeMonsterHP(lcf::rpg::EventCommand const& com) {
283 	int id = com.parameters[0];
284 	Game_Enemy& enemy = (*Main_Data::game_enemyparty)[id];
285 	bool lose = com.parameters[1] > 0;
286 	bool lethal = com.parameters[4] > 0;
287 	int hp = enemy.GetHp();
288 
289 	if (enemy.IsDead())
290 		return true;
291 
292 	int change = 0;
293 	switch (com.parameters[2]) {
294 	case 0:
295 		change = com.parameters[3];
296 	    break;
297 	case 1:
298 		change = Main_Data::game_variables->Get(com.parameters[3]);
299 	    break;
300 	case 2:
301 		change = com.parameters[3] * hp / 100;
302 	    break;
303 	}
304 
305 	if (lose) {
306 		change = -change;
307 	}
308 
309 	enemy.ChangeHp(change, lethal);
310 
311 	auto& scene = Scene::instance;
312 	if (scene) {
313 		scene->OnEventHpChanged(&enemy, change);
314 	}
315 
316 	if (enemy.IsDead()) {
317 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyKill));
318 		enemy.SetDeathTimer();
319 	}
320 
321 	return true;
322 }
323 
CommandChangeMonsterMP(lcf::rpg::EventCommand const & com)324 bool Game_Interpreter_Battle::CommandChangeMonsterMP(lcf::rpg::EventCommand const& com) {
325 	int id = com.parameters[0];
326 	Game_Enemy& enemy = (*Main_Data::game_enemyparty)[id];
327 	bool lose = com.parameters[1] > 0;
328 	int sp = enemy.GetSp();
329 
330 	int change = 0;
331 	switch (com.parameters[2]) {
332 	case 0:
333 		change = com.parameters[3];
334 	    break;
335 	case 1:
336 		change = Main_Data::game_variables->Get(com.parameters[3]);
337 	    break;
338 	}
339 
340 	if (lose)
341 		change = -change;
342 
343 	sp += change;
344 
345 	enemy.SetSp(sp);
346 
347 	return true;
348 }
349 
CommandChangeMonsterCondition(lcf::rpg::EventCommand const & com)350 bool Game_Interpreter_Battle::CommandChangeMonsterCondition(lcf::rpg::EventCommand const& com) {
351 	Game_Enemy& enemy = (*Main_Data::game_enemyparty)[com.parameters[0]];
352 	bool remove = com.parameters[1] > 0;
353 	int state_id = com.parameters[2];
354 	if (remove) {
355 		// RPG_RT BUG: Monster dissapears immediately and doesn't animate death
356 		enemy.RemoveState(state_id, false);
357 	} else {
358 		enemy.AddState(state_id, true);
359 	}
360 	return true;
361 }
362 
CommandShowHiddenMonster(lcf::rpg::EventCommand const & com)363 bool Game_Interpreter_Battle::CommandShowHiddenMonster(lcf::rpg::EventCommand const& com) {
364 	Game_Enemy& enemy = (*Main_Data::game_enemyparty)[com.parameters[0]];
365 	enemy.SetHidden(false);
366 	return true;
367 }
368 
CommandChangeBattleBG(lcf::rpg::EventCommand const & com)369 bool Game_Interpreter_Battle::CommandChangeBattleBG(lcf::rpg::EventCommand const& com) {
370 	Game_Battle::ChangeBackground(ToString(com.string));
371 	return true;
372 }
373 
CommandShowBattleAnimation(lcf::rpg::EventCommand const & com)374 bool Game_Interpreter_Battle::CommandShowBattleAnimation(lcf::rpg::EventCommand const& com) {
375 	int animation_id = com.parameters[0];
376 	int target = com.parameters[1];
377 	bool waiting_battle_anim = com.parameters[2] != 0;
378 	bool allies = false;
379 
380 	if (Player::IsRPG2k3()) {
381 		allies = com.parameters[3] != 0;
382 	}
383 
384 	int frames = 0;
385 
386 	if (target < 0) {
387 		std::vector<Game_Battler*> v;
388 
389 		if (allies) {
390 			Main_Data::game_party->GetBattlers(v);
391 		} else {
392 			Main_Data::game_enemyparty->GetBattlers(v);
393 		}
394 		auto iter = std::remove_if(v.begin(), v.end(),
395 				[](auto* target) { return !(target->Exists() || (target->GetType() == Game_Battler::Type_Ally && target->IsDead())); });
396 		v.erase(iter, v.end());
397 
398 		frames = Game_Battle::ShowBattleAnimation(animation_id, v, false);
399 	}
400 	else {
401 		Game_Battler* battler_target = nullptr;
402 
403 		if (allies) {
404 			// Allies counted from 1
405 			target -= 1;
406 			if (target >= 0 && target < Main_Data::game_party->GetBattlerCount()) {
407 				battler_target = &(*Main_Data::game_party)[target];
408 			}
409 		}
410 		else {
411 			if (target < Main_Data::game_enemyparty->GetBattlerCount()) {
412 				battler_target = &(*Main_Data::game_enemyparty)[target];
413 			}
414 		}
415 
416 		if (battler_target) {
417 			frames = Game_Battle::ShowBattleAnimation(animation_id, { battler_target });
418 		}
419 	}
420 
421 	if (waiting_battle_anim) {
422 		_state.wait_time = frames;
423 	}
424 
425 	return true;
426 }
427 
CommandTerminateBattle(lcf::rpg::EventCommand const &)428 bool Game_Interpreter_Battle::CommandTerminateBattle(lcf::rpg::EventCommand const& /* com */) {
429 	_async_op = AsyncOp::MakeTerminateBattle(static_cast<int>(BattleResult::Abort));
430 	return false;
431 }
432 
433 // Conditional branch.
CommandConditionalBranchBattle(lcf::rpg::EventCommand const & com)434 bool Game_Interpreter_Battle::CommandConditionalBranchBattle(lcf::rpg::EventCommand const& com) {
435 	bool result = false;
436 	int value1, value2;
437 
438 	switch (com.parameters[0]) {
439 		case 0:
440 			// Switch
441 			result = Main_Data::game_switches->Get(com.parameters[1]) == (com.parameters[2] == 0);
442 			break;
443 		case 1:
444 			// Variable
445 			value1 = Main_Data::game_variables->Get(com.parameters[1]);
446 			if (com.parameters[2] == 0) {
447 				value2 = com.parameters[3];
448 			} else {
449 				value2 = Main_Data::game_variables->Get(com.parameters[3]);
450 			}
451 			switch (com.parameters[4]) {
452 				case 0:
453 					// Equal to
454 					result = (value1 == value2);
455 					break;
456 				case 1:
457 					// Greater than or equal
458 					result = (value1 >= value2);
459 					break;
460 				case 2:
461 					// Less than or equal
462 					result = (value1 <= value2);
463 					break;
464 				case 3:
465 					// Greater than
466 					result = (value1 > value2);
467 					break;
468 				case 4:
469 					// Less than
470 					result = (value1 < value2);
471 					break;
472 				case 5:
473 					// Different
474 					result = (value1 != value2);
475 					break;
476 			}
477 			break;
478 		case 2: {
479 			// Hero can act
480 			Game_Actor* actor = Main_Data::game_actors->GetActor(com.parameters[1]);
481 
482 			if (!actor) {
483 				Output::Warning("ConditionalBranchBattle: Invalid actor ID {}", com.parameters[1]);
484 				// Use Else Branch
485 				SetSubcommandIndex(com.indent, 1);
486 				SkipToNextConditional({Cmd::ElseBranch_B, Cmd::EndBranch_B}, com.indent);
487 				return true;
488 			}
489 
490 			result = actor->CanAct();
491 			break;
492 		}
493 		case 3:
494 			// Monster can act
495 			if (com.parameters[1] < Main_Data::game_enemyparty->GetBattlerCount()) {
496 				result = (*Main_Data::game_enemyparty)[com.parameters[1]].CanAct();
497 			}
498 			break;
499 		case 4:
500 			// Monster is the current target
501 			result = (targets_single_enemy && target_enemy_index == com.parameters[1]);
502 			break;
503 		case 5: {
504 			// Hero uses the ... command
505 			if (current_actor_id == com.parameters[1]) {
506 				auto *actor = Main_Data::game_actors->GetActor(current_actor_id);
507 				if (actor) {
508 					result = actor->GetLastBattleAction() == com.parameters[2];
509 				}
510 			}
511 			break;
512 		}
513 	}
514 
515 	int sub_idx = subcommand_sentinel;
516 	if (!result) {
517 		sub_idx = eOptionBranchBattleElse;
518 		SkipToNextConditional({Cmd::ElseBranch_B, Cmd::EndBranch_B}, com.indent);
519 	}
520 
521 	SetSubcommandIndex(com.indent, sub_idx);
522 	return true;
523 }
524 
CommandElseBranchBattle(lcf::rpg::EventCommand const & com)525 bool Game_Interpreter_Battle::CommandElseBranchBattle(lcf::rpg::EventCommand const& com) { //code 23310
526 	return CommandOptionGeneric(com, eOptionBranchBattleElse, {Cmd::EndBranch_B});
527 }
528 
CommandEndBranchBattle(lcf::rpg::EventCommand const &)529 bool Game_Interpreter_Battle::CommandEndBranchBattle(lcf::rpg::EventCommand const& /* com */) { //code 23311
530 	return true;
531 }
532 
CommandManiacControlBattle(lcf::rpg::EventCommand const &)533 bool Game_Interpreter_Battle::CommandManiacControlBattle(lcf::rpg::EventCommand const&) {
534 	if (!Player::IsPatchManiac()) {
535 		return true;
536 	}
537 
538 	Output::Warning("Maniac Patch: Command ControlBattle not supported");
539 	return true;
540 }
541 
CommandManiacControlAtbGauge(lcf::rpg::EventCommand const &)542 bool Game_Interpreter_Battle::CommandManiacControlAtbGauge(lcf::rpg::EventCommand const&) {
543 	if (!Player::IsPatchManiac()) {
544 		return true;
545 	}
546 
547 	Output::Warning("Maniac Patch: Command ControlAtbGauge not supported");
548 	return true;
549 }
550 
CommandManiacChangeBattleCommandEx(lcf::rpg::EventCommand const &)551 bool Game_Interpreter_Battle::CommandManiacChangeBattleCommandEx(lcf::rpg::EventCommand const&) {
552 	if (!Player::IsPatchManiac()) {
553 		return true;
554 	}
555 
556 	Output::Warning("Maniac Patch: Command ChangeBattleCommandEx not supported");
557 	return true;
558 }
559 
CommandManiacGetBattleInfo(lcf::rpg::EventCommand const &)560 bool Game_Interpreter_Battle::CommandManiacGetBattleInfo(lcf::rpg::EventCommand const&) {
561 	if (!Player::IsPatchManiac()) {
562 		return true;
563 	}
564 
565 	Output::Warning("Maniac Patch: Command GetBattleInfo not supported");
566 	return true;
567 }
568 
569