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 <algorithm>
20 #include <sstream>
21 
22 #include "bitmap.h"
23 #include "input.h"
24 #include "output.h"
25 #include "player.h"
26 #include "transition.h"
27 #include "game_battlealgorithm.h"
28 #include "game_interpreter_battle.h"
29 #include "game_message.h"
30 #include "game_system.h"
31 #include "game_party.h"
32 #include "game_enemy.h"
33 #include "game_enemyparty.h"
34 #include "game_battle.h"
35 #include "game_screen.h"
36 #include "game_pictures.h"
37 #include "battle_animation.h"
38 #include <lcf/reader_util.h>
39 #include "scene_battle.h"
40 #include "scene_battle_rpg2k.h"
41 #include "scene_battle_rpg2k3.h"
42 #include "scene_gameover.h"
43 #include "scene_debug.h"
44 #include "game_interpreter.h"
45 #include "rand.h"
46 #include "autobattle.h"
47 #include "enemyai.h"
48 
Scene_Battle(const BattleArgs & args)49 Scene_Battle::Scene_Battle(const BattleArgs& args)
50 	: troop_id(args.troop_id),
51 	allow_escape(args.allow_escape),
52 	first_strike(args.first_strike),
53 	on_battle_end(args.on_battle_end)
54 {
55 	SetUseSharedDrawables(true);
56 
57 	Scene::type = Scene::Battle;
58 
59 	// Face graphic is cleared when battle scene is created.
60 	// Even if the battle gets interrupted by another scene and never starts.
61 	Main_Data::game_system->ClearMessageFace();
62 	Main_Data::game_system->SetBeforeBattleMusic(Main_Data::game_system->GetCurrentBGM());
63 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_BeginBattle));
64 	Main_Data::game_system->BgmPlay(Main_Data::game_system->GetSystemBGM(Main_Data::game_system->BGM_Battle));
65 
66 	Game_Battle::SetTerrainId(args.terrain_id);
67 	Game_Battle::ChangeBackground(args.background);
68 	Game_Battle::SetBattleCondition(args.condition);
69 	Game_Battle::SetBattleFormation(args.formation);
70 }
71 
~Scene_Battle()72 Scene_Battle::~Scene_Battle() {
73 	Game_Battle::Quit();
74 }
75 
Start()76 void Scene_Battle::Start() {
77 	if (Scene::Find(Scene::Map) == nullptr) {
78 		// Battletest mode - need to initialize screen
79 		Main_Data::game_screen->InitGraphics();
80 		Main_Data::game_pictures->InitGraphics();
81 	}
82 
83 	// RPG_RT will cancel any active screen flash from the map, including
84 	// wiping out all flash LSD chunks.
85 	Main_Data::game_screen->FlashOnce(0, 0, 0, 0, 0);
86 
87 	const lcf::rpg::Troop* troop = lcf::ReaderUtil::GetElement(lcf::Data::troops, troop_id);
88 
89 	if (!troop) {
90 		Output::Warning("Invalid Monster Party ID {}", troop_id);
91 		EndBattle(BattleResult::Victory);
92 		return;
93 	}
94 
95 	autobattle_algo = AutoBattle::CreateAlgorithm(Player::player_config.autobattle_algo.Get());
96 	enemyai_algo = EnemyAi::CreateAlgorithm(Player::player_config.enemyai_algo.Get());
97 
98 	Output::Debug("Starting battle {} ({}): algos=({}/{})", troop_id, troop->name, autobattle_algo->GetName(), enemyai_algo->GetName());
99 
100 	Game_Battle::Init(troop_id);
101 
102 	CreateUi();
103 
104 	InitEscapeChance();
105 
106 	SetState(State_Start);
107 }
108 
InitEscapeChance()109 void Scene_Battle::InitEscapeChance() {
110 	int avg_enemy_agi = Main_Data::game_enemyparty->GetAverageAgility();
111 	int avg_actor_agi = Main_Data::game_party->GetAverageAgility();
112 
113 	int base_chance = Utils::RoundTo<int>(100.0 * static_cast<double>(avg_enemy_agi) / static_cast<double>(avg_actor_agi));
114 	this->escape_chance = Utils::Clamp(150 - base_chance, 64, 100);
115 }
116 
TryEscape()117 bool Scene_Battle::TryEscape() {
118 	if (first_strike || Game_Battle::GetInterpreterBattle().IsForceFleeEnabled() || Rand::PercentChance(escape_chance)) {
119 		return true;
120 	}
121 	escape_chance += 10;
122 	return false;
123 }
124 
Continue(SceneType)125 void Scene_Battle::Continue(SceneType /* prev_scene */) {
126 	Game_Message::SetWindow(message_window.get());
127 
128 	// Debug scene / other scene could have changed party status.
129 	status_window->Refresh();
130 }
131 
TransitionIn(SceneType prev_scene)132 void Scene_Battle::TransitionIn(SceneType prev_scene) {
133 	if (prev_scene == Scene::Debug) {
134 		Scene::TransitionIn(prev_scene);
135 		return;
136 	}
137 	Transition::instance().InitShow(Main_Data::game_system->GetTransition(Main_Data::game_system->Transition_BeginBattleShow), this);
138 }
139 
TransitionOut(SceneType next_scene)140 void Scene_Battle::TransitionOut(SceneType next_scene) {
141 	auto& transition = Transition::instance();
142 
143 	if (next_scene == Scene::Debug) {
144 		transition.InitErase(Transition::TransitionCutOut, this);
145 		return;
146 	}
147 
148 	if (next_scene == Scene::Null || next_scene == Scene::Title) {
149 		Scene::TransitionOut(next_scene);
150 		return;
151 	}
152 
153 	transition.InitErase(Main_Data::game_system->GetTransition(Main_Data::game_system->Transition_EndBattleErase), this);
154 }
155 
DrawBackground(Bitmap & dst)156 void Scene_Battle::DrawBackground(Bitmap& dst) {
157 	dst.Clear();
158 }
159 
CreateUi()160 void Scene_Battle::CreateUi() {
161 	std::vector<std::string> commands;
162 
163 	for (auto option: lcf::Data::system.easyrpg_battle_options) {
164 		battle_options.push_back((BattleOptionType)option);
165 	}
166 
167 	// Add all menu items
168 	for (auto option: battle_options) {
169 		switch(option) {
170 		case Battle:
171 			commands.push_back(ToString(lcf::Data::terms.battle_fight));
172 			break;
173 		case AutoBattle:
174 			commands.push_back(ToString(lcf::Data::terms.battle_auto));
175 			break;
176 		case Escape:
177 			commands.push_back(ToString(lcf::Data::terms.battle_escape));
178 			break;
179 		}
180 	}
181 
182 	options_window.reset(new Window_Command(commands, option_command_mov));
183 	options_window->SetHeight(80);
184 	options_window->SetY(SCREEN_TARGET_HEIGHT - 80);
185 
186 	help_window.reset(new Window_Help(0, 0, SCREEN_TARGET_WIDTH, 32));
187 	help_window->SetVisible(false);
188 
189 	item_window.reset(new Window_Item(0, (SCREEN_TARGET_HEIGHT-80), SCREEN_TARGET_WIDTH, 80));
190 	item_window->SetHelpWindow(help_window.get());
191 	item_window->Refresh();
192 	item_window->SetIndex(0);
193 
194 	skill_window.reset(new Window_BattleSkill(0, (SCREEN_TARGET_HEIGHT-80), SCREEN_TARGET_WIDTH, 80));
195 	skill_window->SetHelpWindow(help_window.get());
196 
197 	message_window.reset(new Window_Message(0, (SCREEN_TARGET_HEIGHT - 80), SCREEN_TARGET_WIDTH, 80));
198 	Game_Message::SetWindow(message_window.get());
199 }
200 
UpdateScreen()201 void Scene_Battle::UpdateScreen() {
202 	Main_Data::game_screen->Update();
203 	Main_Data::game_pictures->Update(true);
204 }
205 
UpdateBattlers()206 void Scene_Battle::UpdateBattlers() {
207 	std::vector<Game_Battler*> battlers;
208 	Main_Data::game_enemyparty->GetBattlers(battlers);
209 	Main_Data::game_party->GetBattlers(battlers);
210 	for (auto* b : battlers) {
211 		b->UpdateBattle();
212 	}
213 	Game_Battle::UpdateAnimation();
214 }
215 
UpdateUi()216 void Scene_Battle::UpdateUi() {
217 	options_window->Update();
218 	status_window->Update();
219 	command_window->Update();
220 	help_window->Update();
221 	item_window->Update();
222 	skill_window->Update();
223 	target_window->Update();
224 
225 	Game_Message::Update();
226 }
227 
UpdateEvents()228 bool Scene_Battle::UpdateEvents() {
229 	auto& interp = Game_Battle::GetInterpreterBattle();
230 	interp.Update();
231 	status_window->Refresh();
232 
233 	if (interp.IsForceFleeEnabled()) {
234 		if (state != State_Escape) {
235 			SetState(State_Escape);
236 		}
237 	}
238 
239 	auto call = TakeRequestedScene();
240 	if (call && call->type == Scene::Gameover) {
241 		Scene::Push(std::move(call));
242 	}
243 
244 	if (interp.IsAsyncPending()) {
245 		auto aop = interp.GetAsyncOp();
246 
247 		if (aop.GetType() == AsyncOp::eTerminateBattle) {
248 			EndBattle(static_cast<BattleResult>(aop.GetBattleResult()));
249 			return false;
250 		}
251 
252 		if (CheckSceneExit(aop)) {
253 			return false;
254 		}
255 	}
256 
257 	return true;
258 }
259 
UpdateTimers()260 bool Scene_Battle::UpdateTimers() {
261 	const int timer1 = Main_Data::game_party->GetTimerSeconds(Game_Party::Timer1);
262 	const int timer2 = Main_Data::game_party->GetTimerSeconds(Game_Party::Timer2);
263 
264 	// Screen Effects
265 	Main_Data::game_party->UpdateTimers();
266 
267 	// Query Timer before and after update.
268 	// If it reached zero during update was a running battle timer.
269 	if ((Main_Data::game_party->GetTimerSeconds(Game_Party::Timer1) == 0 && timer1 > 0) ||
270 		(Main_Data::game_party->GetTimerSeconds(Game_Party::Timer2) == 0 && timer2 > 0)) {
271 		EndBattle(BattleResult::Abort);
272 		return false;
273 	}
274 	return true;
275 }
276 
UpdateGraphics()277 void Scene_Battle::UpdateGraphics() {
278 	Game_Battle::UpdateGraphics();
279 }
280 
IsWindowMoving()281 bool Scene_Battle::IsWindowMoving() {
282 	return options_window->IsMovementActive() || status_window->IsMovementActive() || command_window->IsMovementActive();
283 }
284 
EnemySelected()285 Game_Enemy* Scene_Battle::EnemySelected() {
286 	std::vector<Game_Battler*> enemies;
287 	Main_Data::game_enemyparty->GetActiveBattlers(enemies);
288 
289 	Game_Enemy* target = static_cast<Game_Enemy*>(enemies[target_window->GetIndex()]);
290 
291 	if (previous_state == State_SelectCommand) {
292 		active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Normal>(active_actor, target));
293 	} else if (previous_state == State_SelectSkill) {
294 		active_actor->SetBattleAlgorithm(
295 				std::make_shared<Game_BattleAlgorithm::Skill>(active_actor, target, *skill_window->GetSkill()));
296 	} else if (previous_state == State_SelectItem) {
297 		auto* item = item_window->GetItem();
298 		assert(item);
299 		if (item->type == lcf::rpg::Item::Type_special
300 				|| (item->use_skill && (item->type == lcf::rpg::Item::Type_weapon
301 						|| item->type == lcf::rpg::Item::Type_shield
302 						|| item->type == lcf::rpg::Item::Type_armor
303 						|| item->type == lcf::rpg::Item::Type_helmet
304 						|| item->type == lcf::rpg::Item::Type_accessory)))
305 		{
306 			const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, item->skill_id);
307 			if (!skill) {
308 				Output::Warning("EnemySelected: Item {} references invalid skill {}", item->ID, item->skill_id);
309 				return nullptr;
310 			}
311 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(active_actor, target, *skill, item));
312 		} else {
313 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Item>(active_actor, target, *item));
314 		}
315 	} else {
316 		assert("Invalid previous state for enemy selection" && false);
317 	}
318 
319 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
320 	ActionSelectedCallback(active_actor);
321 	return target;
322 }
323 
AllySelected()324 Game_Actor* Scene_Battle::AllySelected() {
325 	Game_Actor& target = (*Main_Data::game_party)[status_window->GetIndex()];
326 
327 	if (previous_state == State_SelectSkill) {
328 		active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(active_actor, &target, *skill_window->GetSkill()));
329 	} else if (previous_state == State_SelectItem) {
330 		auto* item = item_window->GetItem();
331 		assert(item);
332 		if (item->type == lcf::rpg::Item::Type_special
333 				|| (item->use_skill && (item->type == lcf::rpg::Item::Type_weapon
334 						|| item->type == lcf::rpg::Item::Type_shield
335 						|| item->type == lcf::rpg::Item::Type_armor
336 						|| item->type == lcf::rpg::Item::Type_helmet
337 						|| item->type == lcf::rpg::Item::Type_accessory)))
338 		{
339 			const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, item->skill_id);
340 			if (!skill) {
341 				Output::Warning("AllySelected: Item {} references invalid skill {}", item->ID, item->skill_id);
342 				return nullptr;
343 			}
344 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(active_actor, &target, *skill, item));
345 		} else {
346 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Item>(active_actor, &target, *item));
347 		}
348 	} else {
349 		assert("Invalid previous state for ally selection" && false);
350 	}
351 
352 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
353 	ActionSelectedCallback(active_actor);
354 	return &target;
355 }
356 
AttackSelected()357 void Scene_Battle::AttackSelected() {
358 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
359 
360 	if (active_actor->HasAttackAll()) {
361 		active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Normal>(active_actor, Main_Data::game_enemyparty.get()));
362 		ActionSelectedCallback(active_actor);
363 	} else {
364 		SetState(State_SelectEnemyTarget);
365 	}
366 }
367 
DefendSelected()368 void Scene_Battle::DefendSelected() {
369 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
370 
371 	active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Defend>(active_actor));
372 
373 	ActionSelectedCallback(active_actor);
374 }
375 
ItemSelected()376 void Scene_Battle::ItemSelected() {
377 	const lcf::rpg::Item* item = item_window->GetItem();
378 
379 	if (!item || !item_window->CheckEnable(item->ID)) {
380 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
381 		return;
382 	}
383 
384 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
385 
386 	switch (item->type) {
387 		case lcf::rpg::Item::Type_normal:
388 		case lcf::rpg::Item::Type_book:
389 		case lcf::rpg::Item::Type_material:
390 			assert(false);
391 			return;
392 		case lcf::rpg::Item::Type_weapon:
393 		case lcf::rpg::Item::Type_shield:
394 		case lcf::rpg::Item::Type_armor:
395 		case lcf::rpg::Item::Type_helmet:
396 		case lcf::rpg::Item::Type_accessory:
397 		case lcf::rpg::Item::Type_special: {
398 			const lcf::rpg::Skill* skill = lcf::ReaderUtil::GetElement(lcf::Data::skills, item->skill_id);
399 			if (!skill) {
400 				Output::Warning("ItemSelected: Item {} references invalid skill {}", item->ID, item->skill_id);
401 				return;
402 			}
403 			AssignSkill(skill, item);
404 			break;
405 		}
406 		case lcf::rpg::Item::Type_medicine:
407 			if (item->entire_party) {
408 				active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Item>(active_actor, Main_Data::game_party.get(), *item_window->GetItem()));
409 				ActionSelectedCallback(active_actor);
410 			} else {
411 				SetState(State_SelectAllyTarget);
412 				status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_All);
413 			}
414 			break;
415 		case lcf::rpg::Item::Type_switch:
416 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Item>(active_actor, *item_window->GetItem()));
417 			ActionSelectedCallback(active_actor);
418 			break;
419 	}
420 }
421 
SkillSelected()422 void Scene_Battle::SkillSelected() {
423 	const lcf::rpg::Skill* skill = skill_window->GetSkill();
424 
425 	if (!skill || !skill_window->CheckEnable(skill->ID)) {
426 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
427 		return;
428 	}
429 
430 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
431 
432 	AssignSkill(skill, nullptr);
433 }
434 
AssignSkill(const lcf::rpg::Skill * skill,const lcf::rpg::Item * item)435 void Scene_Battle::AssignSkill(const lcf::rpg::Skill* skill, const lcf::rpg::Item* item) {
436 	switch (skill->type) {
437 		case lcf::rpg::Skill::Type_teleport:
438 		case lcf::rpg::Skill::Type_escape:
439 		case lcf::rpg::Skill::Type_switch: {
440 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(active_actor, *skill, item));
441 			ActionSelectedCallback(active_actor);
442 			return;
443 		}
444 		default:
445 			break;
446 	}
447 
448 	switch (skill->scope) {
449 		case lcf::rpg::Skill::Scope_enemy:
450 			SetState(State_SelectEnemyTarget);
451 			break;
452 		case lcf::rpg::Skill::Scope_ally:
453 			SetState(State_SelectAllyTarget);
454 			status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_All);
455 			break;
456 		case lcf::rpg::Skill::Scope_enemies:
457 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(
458 					active_actor, Main_Data::game_enemyparty.get(), *skill, item));
459 			ActionSelectedCallback(active_actor);
460 			break;
461 		case lcf::rpg::Skill::Scope_self:
462 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(
463 					active_actor, active_actor, *skill, item));
464 			ActionSelectedCallback(active_actor);
465 			break;
466 		case lcf::rpg::Skill::Scope_party:
467 			active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Skill>(
468 					active_actor, Main_Data::game_party.get(), *skill, item));
469 			ActionSelectedCallback(active_actor);
470 			break;
471 	}
472 }
473 
Create(const BattleArgs & args)474 std::shared_ptr<Scene_Battle> Scene_Battle::Create(const BattleArgs& args)
475 {
476 	if (Player::IsRPG2k()) {
477 		return std::make_shared<Scene_Battle_Rpg2k>(args);
478 	}
479 	else {
480 		return std::make_shared<Scene_Battle_Rpg2k3>(args);
481 	}
482 }
483 
PrepareBattleAction(Game_Battler * battler)484 void Scene_Battle::PrepareBattleAction(Game_Battler* battler) {
485 	if (battler->GetBattleAlgorithm() == nullptr) {
486 		return;
487 	}
488 
489 	if (!battler->CanAct()) {
490 		if (battler->GetBattleAlgorithm()->GetType() != Game_BattleAlgorithm::Type::None) {
491 			battler->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(battler));
492 		}
493 		return;
494 	}
495 
496 	if (battler->GetSignificantRestriction() == lcf::rpg::State::Restriction_attack_ally) {
497 		Game_Battler *target = battler->GetType() == Game_Battler::Type_Enemy ?
498 			Main_Data::game_enemyparty->GetRandomActiveBattler() :
499 			Main_Data::game_party->GetRandomActiveBattler();
500 
501 		battler->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Normal>(battler, target));
502 		return;
503 	}
504 
505 	if (battler->GetSignificantRestriction() == lcf::rpg::State::Restriction_attack_enemy) {
506 		Game_Battler *target = battler->GetType() == Game_Battler::Type_Ally ?
507 			Main_Data::game_enemyparty->GetRandomActiveBattler() :
508 			Main_Data::game_party->GetRandomActiveBattler();
509 
510 		battler->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Normal>(battler, target));
511 		return;
512 	}
513 
514 	// If we can no longer perform the action (no more items, ran out of SP, etc..)
515 	if (!battler->GetBattleAlgorithm()->ActionIsPossible()) {
516 		battler->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(battler));
517 	}
518 }
519 
RemoveCurrentAction()520 void Scene_Battle::RemoveCurrentAction() {
521 	battle_actions.front()->SetBattleAlgorithm(nullptr);
522 	battle_actions.pop_front();
523 }
524 
ActionSelectedCallback(Game_Battler * for_battler)525 void Scene_Battle::ActionSelectedCallback(Game_Battler* for_battler) {
526 	assert(for_battler->GetBattleAlgorithm() != nullptr);
527 
528 	if (for_battler->GetBattleAlgorithm() == nullptr) {
529 		Output::Warning("ActionSelectedCallback: Invalid action for battler {} ({})",
530 				for_battler->GetId(), for_battler->GetName());
531 		Output::Warning("Please report a bug!");
532 	}
533 
534 	battle_actions.push_back(for_battler);
535 }
536 
CallDebug()537 bool Scene_Battle::CallDebug() {
538 	if (Player::debug_flag) {
539 		Scene::Push(std::make_shared<Scene_Debug>());
540 		return true;
541 	}
542 	return false;
543 }
544 
SelectionFlash(Game_Battler * battler)545 void Scene_Battle::SelectionFlash(Game_Battler* battler) {
546 	if (battler) {
547 		battler->Flash(31, 31, 31, 24, 16);
548 	}
549 }
550 
EndBattle(BattleResult result)551 void Scene_Battle::EndBattle(BattleResult result) {
552 	assert(Scene::instance.get() == this && "EndBattle called multiple times!");
553 
554 	Main_Data::game_party->IncBattleCount();
555 	switch (result) {
556 		case BattleResult::Victory: Main_Data::game_party->IncWinCount(); break;
557 		case BattleResult::Escape: Main_Data::game_party->IncRunCount(); break;
558 		case BattleResult::Defeat: Main_Data::game_party->IncDefeatCount(); break;
559 		case BattleResult::Abort: break;
560 	}
561 
562 	Scene::Pop();
563 
564 	// For RPG_RT compatibility, wait 30 frames if a battle test ends
565 	if (Game_Battle::battle_test.enabled) {
566 		Scene::instance->SetDelayFrames(30);
567 	}
568 
569 	if (on_battle_end) {
570 		on_battle_end(result);
571 		on_battle_end = {};
572 	}
573 }
574 
575