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 #define _USE_MATH_DEFINES
19 #include <cmath>
20 #include "scene_battle_rpg2k3.h"
21 #include <lcf/rpg/battlecommand.h>
22 #include <lcf/rpg/battleranimation.h>
23 #include <lcf/reader_util.h>
24 #include "drawable.h"
25 #include "input.h"
26 #include "output.h"
27 #include "player.h"
28 #include "sprite.h"
29 #include "sprite_enemy.h"
30 #include "sprite_actor.h"
31 #include "sprite_weapon.h"
32 #include "cache.h"
33 #include "game_actors.h"
34 #include "game_system.h"
35 #include "game_party.h"
36 #include "game_enemy.h"
37 #include "game_enemyparty.h"
38 #include "game_message.h"
39 #include "game_battle.h"
40 #include "game_interpreter_battle.h"
41 #include "game_battlealgorithm.h"
42 #include "game_screen.h"
43 #include <lcf/reader_util.h>
44 #include "scene_gameover.h"
45 #include "utils.h"
46 #include "font.h"
47 #include "output.h"
48 #include "autobattle.h"
49 #include "enemyai.h"
50 #include <algorithm>
51 #include <memory>
52 
Scene_Battle_Rpg2k3(const BattleArgs & args)53 Scene_Battle_Rpg2k3::Scene_Battle_Rpg2k3(const BattleArgs& args) :
54 	Scene_Battle(args),
55 	first_strike(args.first_strike)
56 {
57 }
58 
Start()59 void Scene_Battle_Rpg2k3::Start() {
60 	Scene_Battle::Start();
61 	InitBattleCondition(Game_Battle::GetBattleCondition());
62 	CreateEnemySprites();
63 	CreateActorSprites();
64 
65 	// We need to wait for actor and enemy graphics to load before we can finish initializing the battle.
66 	AsyncNext([this]() { Start2(); });
67 }
68 
Start2()69 void Scene_Battle_Rpg2k3::Start2() {
70 	InitEnemies();
71 	InitActors();
72 	InitAtbGauges();
73 
74 	// Changed enemy place means we need to recompute Z order
75 	ResetAllBattlerZ();
76 }
77 
InitBattleCondition(lcf::rpg::System::BattleCondition condition)78 void Scene_Battle_Rpg2k3::InitBattleCondition(lcf::rpg::System::BattleCondition condition) {
79 	if (condition == lcf::rpg::System::BattleCondition_pincers
80 			&& (lcf::Data::battlecommands.placement == lcf::rpg::BattleCommands::Placement_manual
81 				|| Main_Data::game_enemyparty->GetVisibleBattlerCount() <= 1))
82 	{
83 		condition = lcf::rpg::System::BattleCondition_back;
84 	}
85 
86 	if (condition == lcf::rpg::System::BattleCondition_surround
87 			&& (lcf::Data::battlecommands.placement == lcf::rpg::BattleCommands::Placement_manual
88 				|| Main_Data::game_party->GetVisibleBattlerCount() <= 1))
89 	{
90 		condition = lcf::rpg::System::BattleCondition_initiative;
91 	}
92 
93 	Game_Battle::SetBattleCondition(condition);
94 
95 	if (condition == lcf::rpg::System::BattleCondition_back || condition == lcf::rpg::System::BattleCondition_pincers) {
96 		first_strike = false;
97 	}
98 }
99 
InitEnemies()100 void Scene_Battle_Rpg2k3::InitEnemies() {
101 	const auto& enemies = Main_Data::game_enemyparty->GetEnemies();
102 	const auto cond = Game_Battle::GetBattleCondition();
103 
104 	// PLACEMENT AND DIRECTION
105 	for (int real_idx = 0, visible_idx = 0; real_idx < static_cast<int>(enemies.size()); ++real_idx) {
106 		auto& enemy = *enemies[real_idx];
107 		const auto idx = enemy.IsHidden() ? real_idx : visible_idx;
108 
109 		enemy.SetBattlePosition(Game_Battle::Calculate2k3BattlePosition(enemy));
110 
111 		switch(cond) {
112 			case lcf::rpg::System::BattleCondition_none:
113 				enemy.SetDirectionFlipped(false);
114 				break;
115 			case lcf::rpg::System::BattleCondition_initiative:
116 			case lcf::rpg::System::BattleCondition_back:
117 			case lcf::rpg::System::BattleCondition_surround:
118 				enemy.SetDirectionFlipped(true);
119 				break;
120 			case lcf::rpg::System::BattleCondition_pincers:
121 				enemy.SetDirectionFlipped(!(idx & 1));
122 				break;
123 		}
124 
125 		visible_idx += !enemy.IsHidden();
126 	}
127 }
128 
InitActors()129 void Scene_Battle_Rpg2k3::InitActors() {
130 	const auto& actors = Main_Data::game_party->GetActors();
131 	const auto cond = Game_Battle::GetBattleCondition();
132 
133 	// ROW ADJUSTMENT
134 	// If all actors in the front row have battle loss conditions,
135 	// all back row actors forced to the front row.
136 	bool force_front_row = true;
137 	for (auto& actor: actors) {
138 		if (actor->GetBattleRow() == Game_Actor::RowType::RowType_front
139 				&& !actor->IsHidden()
140 				&& actor->CanActOrRecoverable()) {
141 			force_front_row = false;
142 		}
143 	}
144 	if (force_front_row) {
145 		for (auto& actor: actors) {
146 			actor->SetBattleRow(Game_Actor::RowType::RowType_front);
147 		}
148 	}
149 
150 	// PLACEMENT AND DIRECTION
151 	for (int idx = 0; idx < static_cast<int>(actors.size()); ++idx) {
152 		auto& actor = *actors[idx];
153 
154 		actor.SetBattlePosition(Game_Battle::Calculate2k3BattlePosition(actor));
155 
156 		if (cond == lcf::rpg::System::BattleCondition_surround) {
157 			actor.SetDirectionFlipped(idx & 1);
158 		} else {
159 			actor.SetDirectionFlipped(false);
160 		}
161 	}
162 }
163 
~Scene_Battle_Rpg2k3()164 Scene_Battle_Rpg2k3::~Scene_Battle_Rpg2k3() {
165 }
166 
InitAtbGauge(Game_Battler & battler,int preempt_atb,int ambush_atb)167 void Scene_Battle_Rpg2k3::InitAtbGauge(Game_Battler& battler, int preempt_atb, int ambush_atb) {
168 	if (battler.IsHidden() || !battler.CanActOrRecoverable()) {
169 		return;
170 	}
171 
172 	switch(Game_Battle::GetBattleCondition()) {
173 		case lcf::rpg::System::BattleCondition_initiative:
174 		case lcf::rpg::System::BattleCondition_surround:
175 			battler.SetAtbGauge(preempt_atb);
176 			break;
177 		case lcf::rpg::System::BattleCondition_back:
178 		case lcf::rpg::System::BattleCondition_pincers:
179 			battler.SetAtbGauge(ambush_atb);
180 			break;
181 		case lcf::rpg::System::BattleCondition_none:
182 			if (first_strike || battler.HasPreemptiveAttack()) {
183 				battler.SetAtbGauge(preempt_atb);
184 			} else {
185 				battler.SetAtbGauge(Game_Battler::GetMaxAtbGauge() / 2);
186 			}
187 			break;
188 	}
189 }
190 
InitAtbGauges()191 void Scene_Battle_Rpg2k3::InitAtbGauges() {
192 	for (auto& enemy: Main_Data::game_enemyparty->GetEnemies()) {
193 		InitAtbGauge(*enemy, 0, Game_Battler::GetMaxAtbGauge());
194 	}
195 	for (auto& actor: Main_Data::game_party->GetActors()) {
196 		InitAtbGauge(*actor, Game_Battler::GetMaxAtbGauge(), 0);
197 	}
198 }
199 
200 template <typename O, typename M, typename C>
CheckFlip(const O & others,const M & me,bool prefer_flipped,C && cmp)201 static bool CheckFlip(const O& others, const M& me, bool prefer_flipped, C&& cmp) {
202 	for (auto& other: others) {
203 			if (!other->IsHidden() && cmp(other->GetBattlePosition().x, me.GetBattlePosition().x)) {
204 				return prefer_flipped;
205 			}
206 		}
207 		return !prefer_flipped;
208 	}
209 
UpdateEnemiesDirection()210 void Scene_Battle_Rpg2k3::UpdateEnemiesDirection() {
211 	const auto& enemies = Main_Data::game_enemyparty->GetEnemies();
212 	const auto& actors = Main_Data::game_party->GetActors();
213 
214 	for (int real_idx = 0, visible_idx = 0; real_idx < static_cast<int>(enemies.size()); ++real_idx) {
215 		auto& enemy = *enemies[real_idx];
216 		const auto idx = enemy.IsHidden() ? real_idx : visible_idx;
217 
218 		switch(Game_Battle::GetBattleCondition()) {
219 			case lcf::rpg::System::BattleCondition_none:
220 			case lcf::rpg::System::BattleCondition_initiative:
221 				enemy.SetDirectionFlipped(CheckFlip(actors, enemy, false, std::greater_equal<>()));
222 				break;
223 			case lcf::rpg::System::BattleCondition_back:
224 				enemy.SetDirectionFlipped(CheckFlip(actors, enemy, true, std::less_equal<>()));
225 				break;
226 			case lcf::rpg::System::BattleCondition_surround:
227 			case lcf::rpg::System::BattleCondition_pincers:
228 				enemy.SetDirectionFlipped(!(idx & 1));
229 				break;
230 		}
231 
232 		visible_idx += !enemy.IsHidden();
233 	}
234 }
235 
UpdateActorsDirection()236 void Scene_Battle_Rpg2k3::UpdateActorsDirection() {
237 	const auto& actors = Main_Data::game_party->GetActors();
238 	const auto& enemies = Main_Data::game_enemyparty->GetEnemies();
239 
240 	for (int idx = 0; idx < static_cast<int>(actors.size()); ++idx) {
241 		auto& actor = *actors[idx];
242 
243 		switch(Game_Battle::GetBattleCondition()) {
244 			case lcf::rpg::System::BattleCondition_none:
245 			case lcf::rpg::System::BattleCondition_initiative:
246 				actor.SetDirectionFlipped(CheckFlip(enemies, actor, false, std::less_equal<>()));
247 				break;
248 			case lcf::rpg::System::BattleCondition_back:
249 				actor.SetDirectionFlipped(CheckFlip(enemies, actor, true, std::greater_equal<>()));
250 				break;
251 			case lcf::rpg::System::BattleCondition_surround:
252 			case lcf::rpg::System::BattleCondition_pincers:
253 				actor.SetDirectionFlipped(idx & 1);
254 				break;
255 		}
256 	}
257 }
258 
FaceTarget(Game_Actor & source,const Game_Battler & target)259 void Scene_Battle_Rpg2k3::FaceTarget(Game_Actor& source, const Game_Battler& target) {
260 	const auto sx = source.GetBattlePosition().x;
261 	const auto tx = target.GetBattlePosition().x;
262 	const bool flipped = source.IsDirectionFlipped();
263 	if ((flipped && tx < sx) || (!flipped && tx > sx)) {
264 		source.SetDirectionFlipped(1 - flipped);
265 	}
266 }
267 
OnSystem2Ready(FileRequestResult * result)268 void Scene_Battle_Rpg2k3::OnSystem2Ready(FileRequestResult* result) {
269 	Cache::SetSystem2Name(result->file);
270 
271 	SetupSystem2Graphics();
272 }
273 
SetupSystem2Graphics()274 void Scene_Battle_Rpg2k3::SetupSystem2Graphics() {
275 	BitmapRef system2 = Cache::System2();
276 	if (!system2) {
277 		return;
278 	}
279 
280 	ally_cursor->SetBitmap(system2);
281 	ally_cursor->SetZ(Priority_Window);
282 	ally_cursor->SetVisible(false);
283 
284 	enemy_cursor->SetBitmap(system2);
285 	enemy_cursor->SetZ(Priority_Window);
286 	enemy_cursor->SetVisible(false);
287 }
288 
CreateUi()289 void Scene_Battle_Rpg2k3::CreateUi() {
290 	Scene_Battle::CreateUi();
291 
292 	CreateBattleTargetWindow();
293 	CreateBattleStatusWindow();
294 	CreateBattleCommandWindow();
295 
296 	RecreateSpWindow(nullptr);
297 
298 	ally_cursor.reset(new Sprite());
299 	enemy_cursor.reset(new Sprite());
300 
301 	if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
302 		item_window->SetY(64);
303 		skill_window->SetY(64);
304 	}
305 
306 	if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional) {
307 		int transp = IsTransparent() ? 160 : 255;
308 		options_window->SetBackOpacity(transp);
309 		item_window->SetBackOpacity(transp);
310 		skill_window->SetBackOpacity(transp);
311 		help_window->SetBackOpacity(transp);
312 		status_window->SetBackOpacity(transp);
313 	}
314 
315 	if (!Cache::System2() && Main_Data::game_system->HasSystem2Graphic()) {
316 		FileRequestAsync* request = AsyncHandler::RequestFile("System2", Main_Data::game_system->GetSystem2Name());
317 		request->SetGraphicFile(true);
318 		request_id = request->Bind(&Scene_Battle_Rpg2k3::OnSystem2Ready, this);
319 		request->Start();
320 	} else {
321 		SetupSystem2Graphics();
322 	}
323 
324 	if (lcf::Data::battlecommands.window_size == lcf::rpg::BattleCommands::WindowSize_small) {
325 		int height = 68;
326 		int y = SCREEN_TARGET_HEIGHT - height;
327 
328 		auto small_window = [&](auto& window) {
329 			if (window) {
330 				window->SetHeight(height);
331 				window->SetY(y);
332 				window->SetBorderY(5);
333 				window->SetContents(Bitmap::Create(window->GetWidth() - window->GetBorderX() * 2, window->GetHeight() - window->GetBorderY() * 2));
334 				window->SetMenuItemHeight(14);
335 			}
336 		};
337 
338 		small_window(options_window);
339 		small_window(command_window);
340 		small_window(skill_window);
341 		small_window(item_window);
342 		small_window(target_window);
343 
344 		options_window->Refresh();
345 		status_window->SetY(y);
346 
347 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
348 			command_window->SetY(SCREEN_TARGET_HEIGHT / 2 - 80 / 2 + 12);
349 			item_window->SetY(76);
350 			skill_window->SetY(76);
351 		}
352 	}
353 
354 	ResetWindows(true);
355 }
356 
CreateEnemySprites()357 void Scene_Battle_Rpg2k3::CreateEnemySprites() {
358 	for (auto* enemy: Main_Data::game_enemyparty->GetEnemies()) {
359 		enemy->SetBattleSprite(std::make_unique<Sprite_Enemy>(enemy));
360 	}
361 }
362 
CreateActorSprites()363 void Scene_Battle_Rpg2k3::CreateActorSprites() {
364 	for (auto* actor: Main_Data::game_party->GetActors()) {
365 		actor->SetBattleSprite(std::make_unique<Sprite_Actor>(actor));
366 		actor->SetWeaponSprite(std::make_unique<Sprite_Weapon>(actor));
367 	}
368 }
369 
ResetAllBattlerZ()370 void Scene_Battle_Rpg2k3::ResetAllBattlerZ() {
371 	for (auto* enemy: Main_Data::game_enemyparty->GetEnemies()) {
372 		auto* sprite = enemy->GetBattleSprite();
373 		if (sprite) {
374 			sprite->ResetZ();
375 		}
376 	}
377 
378 	for (auto* actor: Main_Data::game_party->GetActors()) {
379 		auto* sprite = actor->GetActorBattleSprite();
380 		if (sprite) {
381 			sprite->ResetZ();
382 			sprite->UpdatePosition();
383 			sprite->DetectStateChange();
384 		}
385 	}
386 }
387 
UpdateAnimations()388 void Scene_Battle_Rpg2k3::UpdateAnimations() {
389 	for (auto it = floating_texts.begin(); it != floating_texts.end();) {
390 		int &time = it->remaining_time;
391 
392 		if (time % 2 == 0) {
393 			int modifier = time <= 10 ? 1 :
394 						   time < 20 ? 0 :
395 						   -1;
396 			it->sprite->SetY(it->sprite->GetY() + modifier);
397 		}
398 
399 		--time;
400 		if (time <= 0) {
401 			it = floating_texts.erase(it);
402 		} else {
403 			++it;
404 		}
405 	}
406 
407 	if (running_away) {
408 		for (auto& actor: Main_Data::game_party->GetActors()) {
409 			Point p = actor->GetBattlePosition();
410 			if (actor->IsDirectionFlipped()) {
411 				p.x -= 6;
412 			} else {
413 				p.x += 6;
414 			}
415 			actor->SetBattlePosition(p);
416 		}
417 	}
418 
419 	auto frame_counter = Main_Data::game_system->GetFrameCounter();
420 
421 	bool ally_set = false;
422 	if (status_window->GetActive()
423 			&& lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional)
424 	{
425 		auto* actor = Main_Data::game_party->GetActor(status_window->GetIndex());
426 		if (actor) {
427 			const auto* sprite = actor->GetBattleSprite();
428 			if (sprite) {
429 				static const int frames[] = { 0, 1, 2, 1 };
430 				int sprite_frame = frames[(frame_counter / 15) % 4];
431 				ally_cursor->SetSrcRect(Rect(sprite_frame * 16, 16, 16, 16));
432 
433 				ally_cursor->SetVisible(true);
434 				ally_cursor->SetX(actor->GetBattlePosition().x);
435 				ally_cursor->SetY(actor->GetBattlePosition().y - 40);
436 
437 				if (frame_counter % 30 == 0) {
438 					SelectionFlash(actor);
439 				}
440 				ally_set = true;
441 			}
442 		}
443 	}
444 	if (!ally_set) {
445 		ally_cursor->SetVisible(false);
446 	}
447 
448 	bool enemy_set = false;
449 	if (target_window->GetActive()) {
450 		std::vector<Game_Battler*> battlers;
451 		Main_Data::game_enemyparty->GetActiveBattlers(battlers);
452 		auto idx = target_window->GetIndex();
453 		if (idx >= 0) {
454 			auto* enemy = battlers[idx];
455 			if (enemy) {
456 				const auto* sprite = enemy->GetBattleSprite();
457 				if (sprite) {
458 					static const int frames[] = { 0, 1, 2, 1 };
459 					int sprite_frame = frames[(frame_counter / 15) % 4];
460 					enemy_cursor->SetSrcRect(Rect(sprite_frame * 16, 0, 16, 16));
461 
462 					enemy_cursor->SetVisible(true);
463 					enemy_cursor->SetX(enemy->GetBattlePosition().x + sprite->GetWidth() / 2);
464 					enemy_cursor->SetY(enemy->GetBattlePosition().y);
465 
466 					std::vector<lcf::rpg::State*> ordered_states = enemy->GetInflictedStatesOrderedByPriority();
467 					if (ordered_states.size() > 0) {
468 						help_window->Clear();
469 						int state_counter = 0;
470 						for (lcf::rpg::State* state : ordered_states) {
471 							std::string state_name = fmt::format("{:9s}", state->name);
472 							help_window->AddText(state_name, state->color, Text::AlignLeft, false);
473 							if (++state_counter >= 5) break;
474 						}
475 						help_window->SetVisible(true);
476 					} else {
477 						help_window->SetVisible(false);
478 					}
479 
480 					if (sprite_frame % 30 == 0) {
481 						SelectionFlash(enemy);
482 					}
483 					enemy_set = true;
484 				}
485 			}
486 		}
487 		if (!enemy_set) {
488 			help_window->Clear();
489 		}
490 	}
491 
492 	if (!enemy_set) {
493 		enemy_cursor->SetVisible(false);
494 	}
495 }
496 
DrawFloatText(int x,int y,int color,StringView text)497 void Scene_Battle_Rpg2k3::DrawFloatText(int x, int y, int color, StringView text) {
498 	Rect rect = Font::Default()->GetSize(text);
499 
500 	BitmapRef graphic = Bitmap::Create(rect.width, rect.height);
501 	graphic->Clear();
502 	graphic->TextDraw(-rect.x, -rect.y, color, text);
503 
504 	std::shared_ptr<Sprite> floating_text = std::make_shared<Sprite>();
505 	floating_text->SetBitmap(graphic);
506 	floating_text->SetOx(rect.width / 2);
507 	floating_text->SetOy(rect.height + 5);
508 	floating_text->SetX(x);
509 	// Move 5 pixel down because the number "jumps" with the intended y as the peak
510 	floating_text->SetY(y + 5);
511 	floating_text->SetZ(Priority_Window + y);
512 
513 	FloatText float_text;
514 	float_text.sprite = floating_text;
515 
516 	floating_texts.push_back(float_text);
517 }
518 
IsTransparent() const519 bool Scene_Battle_Rpg2k3::IsTransparent() const {
520 	return lcf::Data::battlecommands.transparency == lcf::rpg::BattleCommands::Transparency_transparent;
521 }
522 
GetEnemyTargetNames()523 static std::vector<std::string> GetEnemyTargetNames() {
524 	std::vector<std::string> commands;
525 
526 	std::vector<Game_Battler*> enemies;
527 	Main_Data::game_enemyparty->GetActiveBattlers(enemies);
528 
529 	for (auto& enemy: enemies) {
530 		commands.push_back(ToString(enemy->GetName()));
531 	}
532 
533 	return commands;
534 }
535 
CreateBattleTargetWindow()536 void Scene_Battle_Rpg2k3::CreateBattleTargetWindow() {
537 	auto commands = GetEnemyTargetNames();
538 
539 	int width = (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional) ? 104 : 136;
540 
541 	target_window.reset(new Window_Command(std::move(commands), width, 4));
542 	target_window->SetHeight(80);
543 	target_window->SetY(SCREEN_TARGET_HEIGHT-80);
544 	// Above other windows
545 	target_window->SetZ(Priority_Window + 10);
546 
547 	if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional) {
548 		int transp = IsTransparent() ? 160 : 255;
549 		target_window->SetBackOpacity(transp);
550 	}
551 }
552 
RefreshTargetWindow()553 void Scene_Battle_Rpg2k3::RefreshTargetWindow() {
554 	// FIXME: Handle live refresh in traditional when the window is always visible
555 	auto commands = GetEnemyTargetNames();
556 	target_window->ReplaceCommands(std::move(commands));
557 	if (!target_window->GetActive()) {
558 		target_window->SetIndex(-1);
559 	}
560 }
561 
CreateBattleStatusWindow()562 void Scene_Battle_Rpg2k3::CreateBattleStatusWindow() {
563 	int x = 0;
564 	int y = SCREEN_TARGET_HEIGHT - 80;
565 	int w = SCREEN_TARGET_WIDTH;
566 	int h = 80;
567 
568 	switch (lcf::Data::battlecommands.battle_type) {
569 		case lcf::rpg::BattleCommands::BattleType_traditional:
570 			x = target_window->GetWidth();
571 			w = SCREEN_TARGET_WIDTH - x;
572 			break;
573 		case lcf::rpg::BattleCommands::BattleType_alternative:
574 			x = options_window->GetWidth();
575 			w = SCREEN_TARGET_WIDTH - x;
576 			break;
577 		case lcf::rpg::BattleCommands::BattleType_gauge:
578 			x = options_window->GetWidth();
579 			// Default window too small for 4 actors
580 			w = SCREEN_TARGET_WIDTH;
581 			break;
582 	}
583 
584 	status_window.reset(new Window_BattleStatus(x, y, w, h));
585 	status_window->SetZ(Priority_Window + 1);
586 }
587 
GetBattleCommandNames(const Game_Actor * actor)588 std::vector<std::string> Scene_Battle_Rpg2k3::GetBattleCommandNames(const Game_Actor* actor) {
589 	std::vector<std::string> commands;
590 	if (actor) {
591 		for (auto* cmd: actor->GetBattleCommands()) {
592 			commands.push_back(ToString(cmd->name));
593 		}
594 	}
595 	if (lcf::Data::battlecommands.easyrpg_enable_battle_row_command) {
596 		commands.push_back(ToString(lcf::Data::terms.row));
597 	}
598 
599 	return commands;
600 }
601 
SetBattleCommandsDisable(Window_Command & window,const Game_Actor * actor)602 void Scene_Battle_Rpg2k3::SetBattleCommandsDisable(Window_Command& window, const Game_Actor* actor) {
603 	if (actor) {
604 		const auto& cmds = actor->GetBattleCommands();
605 		for (size_t i = 0; i < cmds.size(); ++i) {
606 			auto* cmd = cmds[i];
607 			if (cmd->type == lcf::rpg::BattleCommand::Type_escape && !IsEscapeAllowedFromActorCommand()) {
608 				window.DisableItem(i);
609 			}
610 		}
611 	}
612 }
613 
CreateBattleCommandWindow()614 void Scene_Battle_Rpg2k3::CreateBattleCommandWindow() {
615 	auto* actor = Main_Data::game_party->GetActor(0);
616 	auto commands = GetBattleCommandNames(actor);
617 
618 	command_window.reset(new Window_Command(std::move(commands), option_command_mov));
619 
620 	SetBattleCommandsDisable(*command_window, actor);
621 
622 	command_window->SetHeight(80);
623 	switch (lcf::Data::battlecommands.battle_type) {
624 		case lcf::rpg::BattleCommands::BattleType_traditional:
625 			command_window->SetX(target_window->GetWidth() - command_window->GetWidth());
626 			command_window->SetY(SCREEN_TARGET_HEIGHT - 80);
627 			break;
628 		case lcf::rpg::BattleCommands::BattleType_alternative:
629 			command_window->SetX(SCREEN_TARGET_WIDTH);
630 			command_window->SetY(SCREEN_TARGET_HEIGHT - 80);
631 			break;
632 		case lcf::rpg::BattleCommands::BattleType_gauge:
633 			command_window->SetX(0);
634 			command_window->SetY(SCREEN_TARGET_HEIGHT / 2 - 80 / 2);
635 			break;
636 	}
637 	// Above the target window
638 	command_window->SetZ(Priority_Window + 20);
639 
640 	if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional) {
641 		int transp = IsTransparent() ? 160 : 255;
642 		command_window->SetBackOpacity(transp);
643 	}
644 }
645 
RefreshCommandWindow(const Game_Actor * actor)646 void Scene_Battle_Rpg2k3::RefreshCommandWindow(const Game_Actor* actor) {
647 	auto commands = GetBattleCommandNames(actor);
648 	command_window->ReplaceCommands(std::move(commands));
649 	SetBattleCommandsDisable(*command_window, actor);
650 	command_window->SetIndex(-1);
651 }
652 
SetActiveActor(int idx)653 void Scene_Battle_Rpg2k3::SetActiveActor(int idx) {
654 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
655 	Output::Debug("Battle2k3 SetActiveActor({}) frame={}", idx, Main_Data::game_system->GetFrameCounter());
656 #endif
657 	status_window->SetIndex(idx);
658 	active_actor = Main_Data::game_party->GetActor(idx);
659 	auto* display_actor = active_actor ? active_actor : Main_Data::game_party->GetActor(0);
660 	RefreshCommandWindow(display_actor);
661 }
662 
ResetWindows(bool make_invisible)663 void Scene_Battle_Rpg2k3::ResetWindows(bool make_invisible) {
664 	item_window->SetHelpWindow(nullptr);
665 	skill_window->SetHelpWindow(nullptr);
666 
667 	options_window->SetActive(false);
668 	status_window->SetActive(false);
669 	command_window->SetActive(false);
670 	item_window->SetActive(false);
671 	skill_window->SetActive(false);
672 	target_window->SetActive(false);
673 	sp_window->SetActive(false);
674 
675 	if (!make_invisible) {
676 		return;
677 	}
678 
679 	options_window->SetVisible(false);
680 	status_window->SetVisible(false);
681 	command_window->SetVisible(false);
682 	target_window->SetVisible(false);
683 	item_window->SetVisible(false);
684 	skill_window->SetVisible(false);
685 	help_window->SetVisible(false);
686 	sp_window->SetVisible(false);
687 }
688 
MoveCommandWindows(int x,int frames)689 void Scene_Battle_Rpg2k3::MoveCommandWindows(int x, int frames) {
690 	if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional) {
691 		options_window->InitMovement(options_window->GetX(), options_window->GetY(),
692 				x, options_window->GetY(), frames);
693 
694 		x += options_window->GetWidth();
695 
696 		status_window->InitMovement(status_window->GetX(), status_window->GetY(),
697 				x, status_window->GetY(), frames);
698 
699 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_alternative) {
700 			x += status_window->GetWidth();
701 			command_window->InitMovement(command_window->GetX(), command_window->GetY(),
702 					x, command_window->GetY(), frames);
703 		}
704 	}
705 }
706 
SetState(Scene_Battle::State new_state)707 void Scene_Battle_Rpg2k3::SetState(Scene_Battle::State new_state) {
708 	previous_state = state;
709 	state = new_state;
710 
711 	if (new_state == State_SelectActor) {
712 		auto_battle = false;
713 	}
714 	if (new_state == State_AutoBattle) {
715 		auto_battle = true;
716 	}
717 
718 	SetSceneActionSubState(0);
719 
720 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
721 	Output::Debug("Battle2k3 SetState state={} prev={} auto_battle={}", state, previous_state, auto_battle);
722 #endif
723 }
724 
ReturnToMainBattleState()725 void Scene_Battle_Rpg2k3::ReturnToMainBattleState() {
726 	SetState(auto_battle ? State_AutoBattle : State_SelectActor);
727 }
728 
SetSceneActionSubState(int substate)729 void Scene_Battle_Rpg2k3::SetSceneActionSubState(int substate) {
730 	scene_action_substate = substate;
731 }
732 
BattlerReadyToAct(const Game_Battler * battler)733 static bool BattlerReadyToAct(const Game_Battler* battler) {
734 	return battler->IsAtbGaugeFull() && battler->Exists() && battler->CanAct();
735 }
736 
GetFirstReadyActor()737 static int GetFirstReadyActor() {
738 	const auto& actors = Main_Data::game_party->GetActors();
739 	for (size_t i = 0; i < actors.size(); ++i) {
740 		auto* actor = actors[i];
741 		if (BattlerReadyToAct(actor)) {
742 			return i;
743 		}
744 	}
745 	return -1;
746 }
747 
748 
IsAtbAccumulating() const749 bool Scene_Battle_Rpg2k3::IsAtbAccumulating() const {
750 	if (Game_Battle::IsBattleAnimationWaiting()) {
751 		return false;
752 	}
753 
754 	const bool active_atb = Main_Data::game_system->GetAtbMode() == lcf::rpg::SaveSystem::AtbMode_atb_active;
755 
756 	switch(state) {
757 		case State_SelectEnemyTarget:
758 		case State_SelectAllyTarget:
759 		case State_SelectItem:
760 		case State_SelectSkill:
761 		case State_SelectCommand:
762 			return active_atb;
763 		case State_AutoBattle:
764 		case State_SelectActor:
765 			return true;
766 		default:
767 			break;
768 	}
769 	return false;
770 }
771 
CreateEnemyActions()772 void Scene_Battle_Rpg2k3::CreateEnemyActions() {
773 	// FIXME: RPG_RT checks animations and event ready flag?
774 	for (auto* enemy: Main_Data::game_enemyparty->GetEnemies()) {
775 		if (enemy->IsAtbGaugeFull() && !enemy->GetBattleAlgorithm()) {
776 			if (!EnemyAi::SetStateRestrictedAction(*enemy)) {
777 				enemyai_algo->SetEnemyAiAction(*enemy);
778 			}
779 			assert(enemy->GetBattleAlgorithm() != nullptr);
780 			ActionSelectedCallback(enemy);
781 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
782 			Output::Debug("Battle2k3 ScheduleEnemyAction name={} type={} frame={}", enemy->GetName(), enemy->GetBattleAlgorithm()->GetType(), Main_Data::game_system->GetFrameCounter());
783 #endif
784 		}
785 	}
786 }
787 
CreateActorAutoActions()788 void Scene_Battle_Rpg2k3::CreateActorAutoActions() {
789 	if (state != State_SelectActor
790 			&& state != State_AutoBattle
791 			&& state != State_Battle
792 			) {
793 		return;
794 	}
795 
796 	// FIXME: RPG_RT checks only actor animations?
797 	for (auto* actor: Main_Data::game_party->GetActors()) {
798 		if (!BattlerReadyToAct(actor)
799 				|| actor->GetBattleAlgorithm()
800 				|| (actor->IsControllable() && state != State_AutoBattle)
801 				) {
802 			continue;
803 		}
804 
805 		Game_Battler* random_target = nullptr;
806 		switch (actor->GetSignificantRestriction()) {
807 			case lcf::rpg::State::Restriction_attack_ally:
808 				random_target = Main_Data::game_party->GetRandomActiveBattler();
809 				break;
810 			case lcf::rpg::State::Restriction_attack_enemy:
811 				random_target = Main_Data::game_enemyparty->GetRandomActiveBattler();
812 				break;
813 			default:
814 				break;
815 		}
816 		if (random_target) {
817 			actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Normal>(actor, random_target));
818 		} else {
819 			this->autobattle_algo->SetAutoBattleAction(*actor);
820 			assert(actor->GetBattleAlgorithm() != nullptr);
821 		}
822 
823 		actor->SetLastBattleAction(-1);
824 		ActionSelectedCallback(actor);
825 	}
826 }
827 
UpdateAtb()828 bool Scene_Battle_Rpg2k3::UpdateAtb() {
829 	if (Game_Battle::GetInterpreter().IsRunning() || Game_Message::IsMessageActive()) {
830 		return true;
831 	}
832 	if (IsAtbAccumulating()) {
833 		// FIXME: If one monster can act now, he gets his battle algo set, and we abort updating atb for other monsters
834 		Game_Battle::UpdateAtbGauges();
835 	}
836 
837 	CreateEnemyActions();
838 	CreateActorAutoActions();
839 
840 	return true;
841 }
842 
IsBattleActionPending() const843 bool Scene_Battle_Rpg2k3::IsBattleActionPending() const {
844 	return !battle_actions.empty();
845 }
846 
UpdateBattleState()847 bool Scene_Battle_Rpg2k3::UpdateBattleState() {
848 	if (resume_from_debug_scene) {
849 		resume_from_debug_scene = false;
850 		return true;
851 	}
852 
853 	UpdateScreen();
854 	// FIXME: RPG_RT updates actors first, and this goes into doing CBA actor battle actions initiated last frame
855 	UpdateBattlers();
856 
857 	UpdateUi();
858 
859 	const auto battle_ending = (state == State_Victory || state == State_Defeat);
860 
861 	if (!battle_ending) {
862 		// FIXME: Interpreter also blocked by an RPG_RT continueBattle flag. What is this flag?
863 		if (!Game_Battle::IsBattleAnimationWaiting()) {
864 			if (!UpdateEvents()) {
865 				return false;
866 			}
867 		}
868 	}
869 
870 	// FIXME: Update Panorama
871 
872 	if (!battle_ending) {
873 		if (!UpdateTimers()) {
874 			return false;
875 		}
876 
877 		if (Input::IsTriggered(Input::DEBUG_MENU)) {
878 			if (this->CallDebug()) {
879 				// Set this flag so that when we return and run update again, we resume exactly from after this point.
880 				resume_from_debug_scene = true;
881 				return false;
882 			}
883 		}
884 
885 		CheckBattleEndConditions();
886 		UpdateAtb();
887 	}
888 	return true;
889 }
890 
Update()891 void Scene_Battle_Rpg2k3::Update() {
892 	const auto process_scene = UpdateBattleState();
893 
894 	while (process_scene) {
895 		// Something ended the battle.
896 		if (Scene::instance.get() != this) {
897 			break;
898 		}
899 
900 		if (IsWindowMoving()) {
901 			break;
902 		}
903 
904 		if (Game_Message::IsMessageActive()) {
905 			break;
906 		}
907 
908 		if (state != State_Victory && state != State_Defeat && Game_Battle::GetInterpreter().IsRunning()) {
909 			break;
910 		}
911 
912 		if (!CheckWait()) {
913 			break;
914 		}
915 
916 		if (ProcessSceneAction() == SceneActionReturn::eWaitTillNextFrame) {
917 			break;
918 		}
919 	}
920 
921 	UpdateAnimations();
922 	UpdateGraphics();
923 }
924 
NextTurn(Game_Battler * battler)925 void Scene_Battle_Rpg2k3::NextTurn(Game_Battler* battler) {
926 	Main_Data::game_party->IncTurns();
927 	battler->NextBattleTurn();
928 	Game_Battle::GetInterpreterBattle().ResetPagesExecuted();
929 }
930 
CheckBattleEndConditions()931 bool Scene_Battle_Rpg2k3::CheckBattleEndConditions() {
932 	if (state == State_Defeat || Game_Battle::CheckLose()) {
933 		if (state != State_Defeat) {
934 			SetState(State_Defeat);
935 		}
936 		return true;
937 	}
938 
939 	if (state == State_Victory || Game_Battle::CheckWin()) {
940 		if (state != State_Victory) {
941 			SetState(State_Victory);
942 		}
943 		return true;
944 	}
945 
946 	return false;
947 }
948 
949 
CheckBattleEndAndScheduleEvents(EventTriggerType tt,Game_Battler * source)950 bool Scene_Battle_Rpg2k3::CheckBattleEndAndScheduleEvents(EventTriggerType tt, Game_Battler* source) {
951 	auto& interp = Game_Battle::GetInterpreterBattle();
952 
953 	if (interp.IsRunning()) {
954 		return false;
955 	}
956 
957 	if (tt == EventTriggerType::eAfterBattleAction
958 			&& (Game_Message::IsMessageActive() || interp.IsWaitingForWaitCommand())) {
959 		return true;
960 	}
961 
962 	if (CheckBattleEndConditions()) {
963 		return false;
964 	}
965 
966 	lcf::rpg::TroopPageCondition::Flags flags;
967 	switch (tt) {
968 		case EventTriggerType::eBeforeBattleAction:
969 			flags.turn = flags.turn_actor = flags.turn_enemy = flags.command_actor = true;
970 			break;
971 		case EventTriggerType::eAfterBattleAction:
972 			flags.switch_a = flags.switch_b = flags.fatigue = flags.enemy_hp = flags.actor_hp = true;
973 			break;
974 		case EventTriggerType::eAll:
975 			for (auto& ff: flags.flags) ff = true;
976 			break;
977 	}
978 
979 	int page = interp.ScheduleNextPage(flags, source);
980 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
981 	if (page) {
982 		Output::Debug("Battle2k3 ScheduleNextEventPage Scheduled Page {} frame={}", page, Main_Data::game_system->GetFrameCounter());
983 	} else {
984 		Output::Debug("Battle2k3 ScheduleNextEventPage No Events to Run frame={}", Main_Data::game_system->GetFrameCounter());
985 	}
986 #else
987 	(void)page;
988 #endif
989 	RefreshTargetWindow();
990 
991 	return !interp.IsRunning();
992 }
993 
ProcessSceneAction()994 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneAction() {
995 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
996 	static int last_state = -1;
997 	static int last_substate = -1;
998 	if (state != last_state || scene_action_substate != last_substate) {
999 		int actor_id = active_actor ? active_actor->GetId() : 0;
1000 		StringView actor_name = active_actor ? StringView(active_actor->GetName()) : "Null";
1001 		Output::Debug("Battle2k3 ProcessSceneAction({}, {}) actor={}({}) frames={} auto_battle={}", state, scene_action_substate, actor_name, actor_id, Main_Data::game_system->GetFrameCounter(), auto_battle);
1002 		last_state = state;
1003 		last_substate = scene_action_substate;
1004 	}
1005 #endif
1006 
1007 	// If actor was killed or event removed from the party, immediately cancel out of menu states
1008 	if (active_actor && !active_actor->Exists()) {
1009 		status_window->Refresh();
1010 		SetActiveActor(-1);
1011 		if (state != State_Battle) {
1012 			ReturnToMainBattleState();
1013 		}
1014 	}
1015 
1016 	switch (state) {
1017 		case State_Start:
1018 			return ProcessSceneActionStart();
1019 		case State_SelectOption:
1020 			return ProcessSceneActionFightAutoEscape();
1021 		case State_SelectActor:
1022 			return ProcessSceneActionActor();
1023 		case State_AutoBattle:
1024 			return ProcessSceneActionAutoBattle();
1025 		case State_SelectCommand:
1026 			return ProcessSceneActionCommand();
1027 		case State_SelectItem:
1028 			return ProcessSceneActionItem();
1029 		case State_SelectSkill:
1030 			return ProcessSceneActionSkill();
1031 		case State_SelectEnemyTarget:
1032 			return ProcessSceneActionEnemyTarget();
1033 		case State_SelectAllyTarget:
1034 			return ProcessSceneActionAllyTarget();
1035 		case State_Battle:
1036 			return ProcessSceneActionBattle();
1037 		case State_Victory:
1038 			return ProcessSceneActionVictory();
1039 		case State_Defeat:
1040 			return ProcessSceneActionDefeat();
1041 		case State_Escape:
1042 			return ProcessSceneActionEscape();
1043 	}
1044 	assert(false && "Invalid SceneActionState!");
1045 	return SceneActionReturn::eWaitTillNextFrame;
1046 }
1047 
ProcessSceneActionStart()1048 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionStart() {
1049 	enum SubState {
1050 		eStartMessage,
1051 		eSpecialMessage,
1052 		eUpdateBattlers,
1053 		eUpdateEvents,
1054 	};
1055 
1056 	if (scene_action_substate == eStartMessage) {
1057 		ResetWindows(true);
1058 
1059 		if (!lcf::Data::terms.battle_start.empty()) {
1060 			ShowNotification(ToString(lcf::Data::terms.battle_start));
1061 			SetWait(10, 80);
1062 		}
1063 		SetSceneActionSubState(eSpecialMessage);
1064 		return SceneActionReturn::eContinueThisFrame;
1065 	}
1066 
1067 	if (scene_action_substate == eSpecialMessage) {
1068 		EndNotification();
1069 		const auto cond = Game_Battle::GetBattleCondition();
1070 		if ((!lcf::Data::terms.special_combat.empty() || !lcf::Data::terms.easyrpg_battle2k3_special_combat_back.empty()) && (cond != lcf::rpg::System::BattleCondition_none || first_strike)) {
1071 			if (!lcf::Data::terms.special_combat.empty() && (cond == lcf::rpg::System::BattleCondition_initiative || cond == lcf::rpg::System::BattleCondition_surround || (cond == lcf::rpg::System::BattleCondition_none && first_strike))) {
1072 				ShowNotification(ToString(lcf::Data::terms.special_combat));
1073 			}
1074 			if (!lcf::Data::terms.easyrpg_battle2k3_special_combat_back.empty() && (cond == lcf::rpg::System::BattleCondition_back || cond == lcf::rpg::System::BattleCondition_pincers)) {
1075 				ShowNotification(ToString(lcf::Data::terms.easyrpg_battle2k3_special_combat_back));
1076 			}
1077 			SetWait(30, 70);
1078 		}
1079 		SetSceneActionSubState(eUpdateBattlers);
1080 		return SceneActionReturn::eContinueThisFrame;
1081 	}
1082 
1083 	if (scene_action_substate == eUpdateBattlers) {
1084 		EndNotification();
1085 		UpdateEnemiesDirection();
1086 		UpdateActorsDirection();
1087 		SetSceneActionSubState(eUpdateEvents);
1088 		return SceneActionReturn::eContinueThisFrame;
1089 	}
1090 
1091 	if (scene_action_substate == eUpdateEvents) {
1092 		if (!CheckBattleEndAndScheduleEvents(EventTriggerType::eAll, nullptr)) {
1093 			return SceneActionReturn::eContinueThisFrame;
1094 		}
1095 
1096 		SetState(State_SelectOption);
1097 		return SceneActionReturn::eContinueThisFrame;
1098 	}
1099 
1100 	return SceneActionReturn::eWaitTillNextFrame;
1101 }
1102 
ProcessSceneActionFightAutoEscape()1103 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionFightAutoEscape() {
1104 	enum SubState {
1105 		eBegin,
1106 		eWaitInput,
1107 		ePreActor,
1108 	};
1109 
1110 	if (scene_action_substate == eBegin) {
1111 		ResetWindows(true);
1112 		target_window->SetIndex(-1);
1113 
1114 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional || ((std::find(battle_options.begin(), battle_options.end(), AutoBattle) == battle_options.end()) && !IsEscapeAllowedFromOptionWindow())) {
1115 			if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional) MoveCommandWindows(-options_window->GetWidth(), 1);
1116 			SetState(State_SelectActor);
1117 			return SceneActionReturn::eContinueThisFrame;
1118 		}
1119 
1120 		options_window->SetActive(true);
1121 
1122 		if (IsEscapeAllowedFromOptionWindow()) {
1123 			auto it = std::find(battle_options.begin(), battle_options.end(), Escape);
1124 			if (it != battle_options.end()) {
1125 				options_window->EnableItem(std::distance(battle_options.begin(), it));
1126 			}
1127 		} else {
1128 			auto it = std::find(battle_options.begin(), battle_options.end(), Escape);
1129 			if (it != battle_options.end()) {
1130 				options_window->DisableItem(std::distance(battle_options.begin(), it));
1131 			}
1132 		}
1133 
1134 		options_window->SetVisible(true);
1135 		status_window->SetVisible(true);
1136 		if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_gauge) {
1137 			command_window->SetVisible(true);
1138 		}
1139 		SetActiveActor(-1);
1140 		RefreshCommandWindow(Main_Data::game_party->GetActor(0));
1141 		status_window->Refresh();
1142 		command_window->SetIndex(-1);
1143 
1144 		if (previous_state != State_Start) {
1145 			MoveCommandWindows(0, 8);
1146 		}
1147 
1148 		SetSceneActionSubState(eWaitInput);
1149 		return SceneActionReturn::eContinueThisFrame;
1150 	}
1151 
1152 	if (scene_action_substate == eWaitInput) {
1153 		if (Input::IsTriggered(Input::DECISION)) {
1154 			if (message_window->IsVisible()) {
1155 				return SceneActionReturn::eWaitTillNextFrame;
1156 			}
1157 			switch (battle_options[options_window->GetIndex()]) {
1158 				case Battle: // Battle
1159 					Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
1160 					MoveCommandWindows(-options_window->GetWidth(), 8);
1161 					SetState(State_SelectActor);
1162 					break;
1163 				case AutoBattle: // Auto Battle
1164 					MoveCommandWindows(-options_window->GetWidth(), 8);
1165 					SetState(State_AutoBattle);
1166 					Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
1167 					break;
1168 				case Escape: // Escape
1169 					if (IsEscapeAllowedFromOptionWindow()) {
1170 						Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
1171 						SetState(State_Escape);
1172 					} else {
1173 						Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
1174 					}
1175 					break;
1176 			}
1177 		}
1178 		return SceneActionReturn::eWaitTillNextFrame;
1179 	}
1180 
1181 	return SceneActionReturn::eWaitTillNextFrame;
1182 }
1183 
ProcessSceneActionActor()1184 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionActor() {
1185 	enum SubState {
1186 		eBegin,
1187 		eWaitInput,
1188 		eWaitActor,
1189 	};
1190 
1191 	if (scene_action_substate == eBegin) {
1192 		ResetWindows(true);
1193 		target_window->SetIndex(-1);
1194 
1195 		status_window->SetVisible(true);
1196 		command_window->SetIndex(-1);
1197 
1198 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional) {
1199 			status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_None);
1200 			target_window->SetVisible(true);
1201 
1202 			SetSceneActionSubState(eWaitActor);
1203 			return SceneActionReturn::eContinueThisFrame;
1204 		}
1205 
1206 		status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_Ready);
1207 
1208 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_alternative) {
1209 			command_window->SetVisible(true);
1210 		}
1211 
1212 		if (lcf::Data::battlecommands.easyrpg_sequential_order) {
1213 			SetSceneActionSubState(eWaitActor);
1214 			return SceneActionReturn::eContinueThisFrame;
1215 		}
1216 
1217 		SetSceneActionSubState(eWaitInput);
1218 	}
1219 
1220 	if (scene_action_substate == eWaitInput) {
1221 		auto* selected_actor = Main_Data::game_party->GetActor(status_window->GetIndex());
1222 		if (selected_actor == nullptr || !BattlerReadyToAct(selected_actor)) {
1223 			// If current selection is no longer valid, force a new selection
1224 			const auto idx = GetFirstReadyActor();
1225 			if (idx != status_window->GetIndex()) {
1226 				SetActiveActor(idx);
1227 			}
1228 		} else if (selected_actor != active_actor) {
1229 			// If selection changed due to player input
1230 			SetActiveActor(status_window->GetIndex());
1231 		}
1232 		status_window->SetActive(active_actor != nullptr);
1233 
1234 		if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_alternative) {
1235 			command_window->SetVisible(status_window->GetActive());
1236 		}
1237 	}
1238 
1239 	// If any battler is waiting to attack, immediately interrupt and do the attack.
1240 	if (IsBattleActionPending()) {
1241 		SetState(State_Battle);
1242 		return SceneActionReturn::eContinueThisFrame;
1243 	}
1244 
1245 	if (scene_action_substate == eWaitInput) {
1246 		if (Input::IsTriggered(Input::CANCEL)) {
1247 			SetActiveActor(-1);
1248 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1249 			SetState(State_SelectOption);
1250 			return SceneActionReturn::eWaitTillNextFrame;
1251 		}
1252 
1253 		if (status_window->GetActive() && status_window->GetIndex() >= 0) {
1254 			if (Input::IsTriggered(Input::DECISION)) {
1255 				command_window->SetIndex(0);
1256 				SetState(State_SelectCommand);
1257 				return SceneActionReturn::eWaitTillNextFrame;
1258 			}
1259 		}
1260 
1261 		return SceneActionReturn::eWaitTillNextFrame;
1262 	}
1263 
1264 	if (scene_action_substate == eWaitActor) {
1265 		const auto idx = GetFirstReadyActor();
1266 		SetActiveActor(idx);
1267 		if (idx >= 0) {
1268 			command_window->SetIndex(0);
1269 			SetState(State_SelectCommand);
1270 			return SceneActionReturn::eWaitTillNextFrame;
1271 		}
1272 
1273 		if (lcf::Data::battlecommands.battle_type != lcf::rpg::BattleCommands::BattleType_traditional) {
1274 			if (Input::IsTriggered(Input::CANCEL)) {
1275 				SetActiveActor(-1);
1276 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1277 				SetState(State_SelectOption);
1278 				return SceneActionReturn::eWaitTillNextFrame;
1279 			}
1280 		}
1281 
1282 		return SceneActionReturn::eWaitTillNextFrame;
1283 	}
1284 
1285 	return SceneActionReturn::eWaitTillNextFrame;
1286 }
1287 
ProcessSceneActionAutoBattle()1288 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionAutoBattle() {
1289 	enum SubState {
1290 		eBegin,
1291 		eWaitInput,
1292 	};
1293 
1294 	if (scene_action_substate == eBegin) {
1295 		ResetWindows(true);
1296 		target_window->SetIndex(-1);
1297 
1298 		status_window->SetVisible(true);
1299 		command_window->SetIndex(-1);
1300 		status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_None);
1301 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_alternative) {
1302 			command_window->SetVisible(true);
1303 		}
1304 		SetActiveActor(-1);
1305 
1306 		SetSceneActionSubState(eWaitInput);
1307 	}
1308 
1309 	// If any battler is waiting to attack, immediately interrupt and do the attack.
1310 	if (IsBattleActionPending()) {
1311 		SetState(State_Battle);
1312 		return SceneActionReturn::eContinueThisFrame;
1313 	}
1314 
1315 	if (scene_action_substate == eWaitInput) {
1316 		if (Input::IsTriggered(Input::CANCEL)) {
1317 			SetActiveActor(-1);
1318 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1319 			SetState(State_SelectOption);
1320 			return SceneActionReturn::eWaitTillNextFrame;
1321 		}
1322 		return SceneActionReturn::eWaitTillNextFrame;
1323 	}
1324 
1325 	return SceneActionReturn::eWaitTillNextFrame;
1326 }
1327 
ProcessSceneActionCommand()1328 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionCommand() {
1329 	assert(active_actor != nullptr);
1330 	enum SubState {
1331 		eBegin,
1332 		eWaitInput,
1333 	};
1334 
1335 	if (scene_action_substate == eBegin) {
1336 		ResetWindows(true);
1337 		target_window->SetIndex(-1);
1338 
1339 		status_window->SetVisible(true);
1340 		command_window->SetVisible(true);
1341 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional) {
1342 			target_window->SetVisible(true);
1343 		}
1344 		command_window->SetActive(true);
1345 
1346 		SetSceneActionSubState(eWaitInput);
1347 	}
1348 
1349 	// If any battler is waiting to attack, immediately interrupt and do the attack.
1350 	if (Main_Data::game_system->GetAtbMode() == lcf::rpg::SaveSystem::AtbMode_atb_active && IsBattleActionPending()) {
1351 		SetState(State_Battle);
1352 		return SceneActionReturn::eContinueThisFrame;
1353 	}
1354 
1355 	if (scene_action_substate == eWaitInput) {
1356 		if (Input::IsTriggered(Input::DECISION)) {
1357 			int index = command_window->GetIndex();
1358 			// Row command always uses the last index
1359 			if (!lcf::Data::battlecommands.easyrpg_enable_battle_row_command || index < command_window->GetRowMax() - 1) {
1360 				const auto* command = active_actor->GetBattleCommand(index);
1361 
1362 				if (command) {
1363 					active_actor->SetLastBattleAction(command->ID);
1364 					switch (command->type) {
1365 						case lcf::rpg::BattleCommand::Type_attack:
1366 							AttackSelected();
1367 							break;
1368 						case lcf::rpg::BattleCommand::Type_defense:
1369 							DefendSelected();
1370 							break;
1371 						case lcf::rpg::BattleCommand::Type_escape:
1372 							EscapeSelected();
1373 							break;
1374 						case lcf::rpg::BattleCommand::Type_item:
1375 							Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
1376 							SetState(State_SelectItem);
1377 							break;
1378 						case lcf::rpg::BattleCommand::Type_skill:
1379 							Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
1380 							skill_window->SetSubsetFilter(0);
1381 							RecreateSpWindow(active_actor);
1382 							SetState(State_SelectSkill);
1383 							break;
1384 						case lcf::rpg::BattleCommand::Type_special:
1385 							Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
1386 							SpecialSelected();
1387 							break;
1388 						case lcf::rpg::BattleCommand::Type_subskill:
1389 							Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
1390 							SubskillSelected(command->ID);
1391 							break;
1392 					}
1393 				}
1394 			} else {
1395 				active_actor->SetLastBattleAction(-1);
1396 				// FIXME: Verify how battle interpreter runs with row command
1397 				RowSelected();
1398 			}
1399 			return SceneActionReturn::eWaitTillNextFrame;
1400 		}
1401 		if (Input::IsTriggered(Input::CANCEL)) {
1402 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1403 			SetState(State_SelectOption);
1404 
1405 			return SceneActionReturn::eWaitTillNextFrame;
1406 		}
1407 		return SceneActionReturn::eWaitTillNextFrame;
1408 	}
1409 	return SceneActionReturn::eWaitTillNextFrame;
1410 }
1411 
ProcessSceneActionItem()1412 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionItem() {
1413 	assert(active_actor != nullptr);
1414 	enum SubState {
1415 		eBegin,
1416 		eWaitInput,
1417 	};
1418 
1419 	if (scene_action_substate == eBegin) {
1420 		ResetWindows(true);
1421 		item_window->SetVisible(true);
1422 		item_window->SetActive(true);
1423 		item_window->SetActor(active_actor);
1424 
1425 		item_window->SetHelpWindow(help_window.get());
1426 		help_window->SetVisible(true);
1427 
1428 		item_window->Refresh();
1429 
1430 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
1431 			status_window->SetVisible(true);
1432 		}
1433 
1434 		SetSceneActionSubState(eWaitInput);
1435 	}
1436 
1437 	if (scene_action_substate == eWaitInput) {
1438 		if (Input::IsTriggered(Input::DECISION)) {
1439 			ItemSelected();
1440 			return SceneActionReturn::eWaitTillNextFrame;
1441 		}
1442 		if (Input::IsTriggered(Input::CANCEL)) {
1443 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1444 			SetState(State_SelectCommand);
1445 			return SceneActionReturn::eWaitTillNextFrame;
1446 		}
1447 	}
1448 	return SceneActionReturn::eWaitTillNextFrame;
1449 }
1450 
ProcessSceneActionSkill()1451 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionSkill() {
1452 	assert(active_actor != nullptr);
1453 	enum SubState {
1454 		eBegin,
1455 		eWaitInput,
1456 	};
1457 
1458 	const auto actor_index = Main_Data::game_party->GetActorPositionInParty(active_actor->GetId());
1459 
1460 	if (scene_action_substate == eBegin) {
1461 		ResetWindows(true);
1462 
1463 		skill_window->SetActive(true);
1464 		skill_window->SetActor(active_actor->GetId());
1465 		if (previous_state == State_SelectCommand) {
1466 			skill_window->RestoreActorIndex(actor_index);
1467 		}
1468 
1469 		skill_window->SetVisible(true);
1470 		skill_window->SetHelpWindow(help_window.get());
1471 		help_window->SetVisible(true);
1472 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional) {
1473 			sp_window->SetVisible(true);
1474 		}
1475 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_gauge) {
1476 			status_window->SetVisible(true);
1477 		}
1478 
1479 		SetSceneActionSubState(eWaitInput);
1480 	}
1481 
1482 	skill_window->SaveActorIndex(actor_index);
1483 
1484 	if (scene_action_substate == eWaitInput) {
1485 		if (Input::IsTriggered(Input::DECISION)) {
1486 			SkillSelected();
1487 			skill_window->SaveActorIndex(actor_index);
1488 			return SceneActionReturn::eWaitTillNextFrame;
1489 		}
1490 		if (Input::IsTriggered(Input::CANCEL)) {
1491 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1492 			SetState(State_SelectCommand);
1493 			skill_window->SaveActorIndex(actor_index);
1494 			return SceneActionReturn::eWaitTillNextFrame;
1495 		}
1496 	}
1497 	return SceneActionReturn::eWaitTillNextFrame;
1498 }
1499 
ProcessSceneActionEnemyTarget()1500 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionEnemyTarget() {
1501 	assert(active_actor != nullptr);
1502 	enum SubState {
1503 		eBegin,
1504 		eWaitInput,
1505 	};
1506 
1507 	if (scene_action_substate == eBegin) {
1508 		RefreshTargetWindow();
1509 		target_window->SetIndex(0);
1510 
1511 		switch (lcf::Data::battlecommands.battle_type) {
1512 			case lcf::rpg::BattleCommands::BattleType_traditional:
1513 				ResetWindows(false);
1514 				command_window->SetVisible(false);
1515 				target_window->SetVisible(true);
1516 				break;
1517 			case lcf::rpg::BattleCommands::BattleType_alternative:
1518 				ResetWindows(true);
1519 				status_window->SetVisible(true);
1520 				command_window->SetVisible(true);
1521 				break;
1522 			case lcf::rpg::BattleCommands::BattleType_gauge:
1523 				ResetWindows(true);
1524 				status_window->SetVisible(true);
1525 				break;
1526 		}
1527 
1528 		target_window->SetActive(true);
1529 
1530 		SetSceneActionSubState(eWaitInput);
1531 	}
1532 
1533 	if (scene_action_substate == eWaitInput) {
1534 		if (Input::IsTriggered(Input::DECISION)) {
1535 			auto* actor = active_actor;
1536 			// active_actor gets reset after the next call, so save it.
1537 			auto* enemy = EnemySelected();
1538 			if (enemy) {
1539 				FaceTarget(*actor, *enemy);
1540 			}
1541 			target_window->SetIndex(-1);
1542 			return SceneActionReturn::eWaitTillNextFrame;
1543 		}
1544 		if (Input::IsTriggered(Input::CANCEL)) {
1545 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1546 			SetState(previous_state);
1547 			target_window->SetIndex(-1);
1548 			return SceneActionReturn::eWaitTillNextFrame;
1549 		}
1550 	}
1551 	return SceneActionReturn::eWaitTillNextFrame;
1552 }
1553 
ProcessSceneActionAllyTarget()1554 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionAllyTarget() {
1555 	assert(active_actor != nullptr);
1556 	enum SubState {
1557 		eBegin,
1558 		eWaitInput,
1559 	};
1560 
1561 	if (scene_action_substate == eBegin) {
1562 		switch (lcf::Data::battlecommands.battle_type) {
1563 			case lcf::rpg::BattleCommands::BattleType_traditional:
1564 				ResetWindows(false);
1565 				status_window->SetVisible(true);
1566 				break;
1567 			case lcf::rpg::BattleCommands::BattleType_alternative:
1568 				ResetWindows(true);
1569 				status_window->SetVisible(true);
1570 				command_window->SetVisible(true);
1571 				command_window->SetIndex(-1);
1572 				break;
1573 			case lcf::rpg::BattleCommands::BattleType_gauge:
1574 				ResetWindows(true);
1575 				status_window->SetVisible(true);
1576 				break;
1577 		}
1578 
1579 		status_window->SetActive(true);
1580 
1581 		SetSceneActionSubState(eWaitInput);
1582 	}
1583 
1584 	if (scene_action_substate == eWaitInput) {
1585 		if (Input::IsTriggered(Input::DECISION)) {
1586 			AllySelected();
1587 			return SceneActionReturn::eWaitTillNextFrame;
1588 		}
1589 		if (Input::IsTriggered(Input::CANCEL)) {
1590 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
1591 			status_window->SetIndex(Main_Data::game_party->GetActorPositionInParty(active_actor->GetId()));
1592 			SetState(previous_state);
1593 			return SceneActionReturn::eWaitTillNextFrame;
1594 		}
1595 	}
1596 	return SceneActionReturn::eWaitTillNextFrame;
1597 }
1598 
ProcessSceneActionBattle()1599 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionBattle() {
1600 	enum SubState {
1601 		eBegin,
1602 		ePreAction,
1603 		eBattleAction,
1604 		ePostEvents,
1605 		ePost,
1606 	};
1607 
1608 	if (scene_action_substate == eBegin) {
1609 		ResetWindows(false);
1610 
1611 		SetSceneActionSubState(ePreAction);
1612 	}
1613 
1614 	if (scene_action_substate == ePreAction) {
1615 		if (battle_actions.empty()) {
1616 			SetSceneActionSubState(ePost);
1617 			return SceneActionReturn::eContinueThisFrame;
1618 		}
1619 
1620 		auto* battler = battle_actions.front();
1621 		// If we will start a new battle action, first check for state changes
1622 		// such as death, paralyze, confuse, etc..
1623 		PrepareBattleAction(battler);
1624 
1625 		pending_battle_action = battler->GetBattleAlgorithm();
1626 		SetBattleActionState(BattleActionState_Begin);
1627 
1628 		NextTurn(battler);
1629 
1630 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
1631 		Output::Debug("Battle2k3 StartBattleAction battler={} frame={} auto_battle={}", battler->GetName(), Main_Data::game_system->GetFrameCounter(), auto_battle);
1632 #endif
1633 		SetSceneActionSubState(eBattleAction);
1634 	}
1635 
1636 	if (scene_action_substate == eBattleAction) {
1637 		auto rc = ProcessBattleAction(pending_battle_action.get());
1638 		// If interpreter or something else changed the battle state, cleanup before we abort.
1639 		if (state != State_Battle) {
1640 			pending_battle_action = {};
1641 			RemoveCurrentAction();
1642 		}
1643 		if (rc == BattleActionReturn::eContinue) {
1644 			return SceneActionReturn::eContinueThisFrame;
1645 		}
1646 		if (rc == BattleActionReturn::eWait) {
1647 			return SceneActionReturn::eWaitTillNextFrame;
1648 		}
1649 
1650 		auto* battler = pending_battle_action->GetSource();
1651 		assert(battler != active_actor);
1652 
1653 		pending_battle_action = {};
1654 		RemoveCurrentAction();
1655 
1656 		// If battle ended, quit now
1657 		if (CheckBattleEndConditions()) {
1658 			return SceneActionReturn::eContinueThisFrame;
1659 		}
1660 
1661 		// Try next battler
1662 		SetSceneActionSubState(ePreAction);
1663 		return SceneActionReturn::eContinueThisFrame;
1664 	}
1665 
1666 	if (scene_action_substate == ePost) {
1667 		// If the selected actor acted, or if they were killed / removed, then cancel out of their menus
1668 		if (active_actor == nullptr || !active_actor->Exists()) {
1669 			ReturnToMainBattleState();
1670 		} else {
1671 			SetState(previous_state);
1672 		}
1673 		return SceneActionReturn::eWaitTillNextFrame;
1674 	}
1675 
1676 	return SceneActionReturn::eWaitTillNextFrame;
1677 }
1678 
ProcessSceneActionVictory()1679 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionVictory() {
1680 	enum SubState {
1681 		eCBAInit,
1682 		eCBAMove,
1683 		eBegin,
1684 		ePreMessage,
1685 		eMessages,
1686 		eEnd,
1687 	};
1688 
1689 	if (scene_action_substate == eCBAInit) {
1690 		for (auto* actor: Main_Data::game_party->GetActors()) {
1691 			auto* sprite = actor->GetActorBattleSprite();
1692 			if (actor->Exists() && sprite) {
1693 				sprite->SetNormalAttacking(false);
1694 				auto* weapon = actor->GetWeaponSprite();
1695 				if (weapon) {
1696 					weapon->StopAttack();
1697 				}
1698 			}
1699 		}
1700 
1701 		if (cba_action != nullptr && cba_direction_back) {
1702 			CBAInit();
1703 			SetSceneActionSubState(eCBAMove);
1704 		} else {
1705 			SetSceneActionSubState(eBegin);
1706 		}
1707 		return SceneActionReturn::eWaitTillNextFrame;
1708 	}
1709 
1710 	if (scene_action_substate == eCBAMove) {
1711 		CBAMove();
1712 		if (cba_move_frame >= cba_num_move_frames) {
1713 			SetSceneActionSubState(eBegin);
1714 		}
1715 		return SceneActionReturn::eWaitTillNextFrame;
1716 	}
1717 
1718 	if (scene_action_substate == eBegin) {
1719 		ResetWindows(true);
1720 		status_window->SetVisible(true);
1721 
1722 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional) {
1723 			status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_None);
1724 			target_window->SetVisible(true);
1725 		}
1726 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_alternative) {
1727 			command_window->SetVisible(true);
1728 		}
1729 		battle_end_timer = 60;
1730 		SetSceneActionSubState(ePreMessage);
1731 	}
1732 
1733 	if (scene_action_substate == ePreMessage) {
1734 		if (battle_end_timer > 0) {
1735 			--battle_end_timer;
1736 			return SceneActionReturn::eContinueThisFrame;
1737 		}
1738 
1739 		for (auto* actor: Main_Data::game_party->GetActors()) {
1740 			auto* sprite = actor->GetActorBattleSprite();
1741 			if (actor->Exists() && sprite) {
1742 				actor->SetIsDefending(false);
1743 				sprite->SetAnimationState(Sprite_Actor::AnimationState_Victory);
1744 			}
1745 		}
1746 		Main_Data::game_system->BgmPlay(Main_Data::game_system->GetSystemBGM(Main_Data::game_system->BGM_Victory));
1747 		SetWait(30, 30);
1748 		SetSceneActionSubState(eMessages);
1749 		return SceneActionReturn::eContinueThisFrame;
1750 	}
1751 
1752 	if (scene_action_substate == eMessages) {
1753 		int exp = Main_Data::game_enemyparty->GetExp();
1754 		int money = Main_Data::game_enemyparty->GetMoney();
1755 		std::vector<int> drops;
1756 		Main_Data::game_enemyparty->GenerateDrops(drops);
1757 
1758 		auto pm = PendingMessage();
1759 		pm.SetEnableFace(false);
1760 
1761 		pm.PushLine(ToString(lcf::Data::terms.victory) + Player::escape_symbol + "|");
1762 		pm.PushPageEnd();
1763 
1764 		std::string space = Player::IsRPG2k3E() ? " " : "";
1765 
1766 		std::stringstream ss;
1767 		if (exp > 0) {
1768 			ss << exp << space << lcf::Data::terms.exp_received;
1769 			pm.PushLine(ss.str());
1770 			pm.PushPageEnd();
1771 		}
1772 		if (money > 0) {
1773 			ss.str("");
1774 			ss << lcf::Data::terms.gold_recieved_a << " " << money << lcf::Data::terms.gold << lcf::Data::terms.gold_recieved_b;
1775 			pm.PushLine(ss.str());
1776 			pm.PushPageEnd();
1777 		}
1778 		for (auto& item_id: drops) {
1779 			const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, item_id);
1780 			// No Output::Warning needed here, reported later when the item is added
1781 			StringView item_name = item ? StringView(item->name) : StringView("??? BAD ITEM ???");
1782 
1783 			ss.str("");
1784 			ss << item_name << space << lcf::Data::terms.item_recieved;
1785 			pm.PushLine(ss.str());
1786 			pm.PushPageEnd();
1787 		}
1788 
1789 		for (auto* actor: Main_Data::game_party->GetActors()) {
1790 			if (actor->Exists()) {
1791 				actor->ChangeExp(actor->GetExp() + exp, &pm);
1792 			}
1793 		}
1794 
1795 		Main_Data::game_party->GainGold(money);
1796 		for (auto& item: drops) {
1797 			Main_Data::game_party->AddItem(item, 1);
1798 		}
1799 
1800 		message_window->SetHeight(32);
1801 		message_window->SetMaxLinesPerPage(1);
1802 		Game_Message::SetPendingMessage(std::move(pm));
1803 
1804 		status_window->Refresh();
1805 
1806 		SetSceneActionSubState(eEnd);
1807 		return SceneActionReturn::eContinueThisFrame;
1808 	}
1809 
1810 	if (scene_action_substate == eEnd) {
1811 		EndBattle(BattleResult::Victory);
1812 		return SceneActionReturn::eContinueThisFrame;
1813 	}
1814 
1815 	return SceneActionReturn::eWaitTillNextFrame;
1816 }
1817 
ProcessSceneActionDefeat()1818 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionDefeat() {
1819 	enum SubState {
1820 		eBegin,
1821 		ePreMessage,
1822 		eMessages,
1823 		eEnd,
1824 	};
1825 
1826 	if (scene_action_substate == eBegin) {
1827 		ResetWindows(true);
1828 		status_window->SetVisible(true);
1829 
1830 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_traditional) {
1831 			status_window->SetChoiceMode(Window_BattleStatus::ChoiceMode_None);
1832 			target_window->SetVisible(true);
1833 		}
1834 		if (lcf::Data::battlecommands.battle_type == lcf::rpg::BattleCommands::BattleType_alternative) {
1835 			command_window->SetVisible(true);
1836 		}
1837 
1838 		battle_end_timer = 60;
1839 		SetSceneActionSubState(ePreMessage);
1840 	}
1841 
1842 	if (scene_action_substate == ePreMessage) {
1843 		if (battle_end_timer > 0) {
1844 			--battle_end_timer;
1845 			return SceneActionReturn::eContinueThisFrame;
1846 		}
1847 		Main_Data::game_system->BgmPlay(Main_Data::game_system->GetSystemBGM(Main_Data::game_system->BGM_GameOver));
1848 		SetWait(60, 60);
1849 		SetSceneActionSubState(eMessages);
1850 		return SceneActionReturn::eContinueThisFrame;
1851 	}
1852 
1853 	if (scene_action_substate == eMessages) {
1854 		Main_Data::game_system->SetMessagePositionFixed(true);
1855 		Main_Data::game_system->SetMessagePosition(0);
1856 		Main_Data::game_system->SetMessageTransparent(false);
1857 
1858 		auto pm = PendingMessage();
1859 		pm.SetEnableFace(false);
1860 		pm.PushLine(ToString(lcf::Data::terms.defeat));
1861 
1862 		message_window->SetHeight(32);
1863 		message_window->SetMaxLinesPerPage(1);
1864 		Game_Message::SetPendingMessage(std::move(pm));
1865 
1866 		SetSceneActionSubState(eEnd);
1867 		return SceneActionReturn::eContinueThisFrame;
1868 	}
1869 
1870 	if (scene_action_substate == eEnd) {
1871 		EndBattle(BattleResult::Defeat);
1872 		return SceneActionReturn::eContinueThisFrame;
1873 	}
1874 
1875 	return SceneActionReturn::eWaitTillNextFrame;
1876 }
1877 
ProcessSceneActionEscape()1878 Scene_Battle_Rpg2k3::SceneActionReturn Scene_Battle_Rpg2k3::ProcessSceneActionEscape() {
1879 	enum SubState {
1880 		eBegin,
1881 		eFailure,
1882 		eSuccess,
1883 	};
1884 
1885 	if (scene_action_substate == eBegin) {
1886 		if (previous_state == State_SelectOption || TryEscape()) {
1887 			// There is no success text for escape in 2k3, however 2k3 still waits the same as if there was.
1888 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Escape));
1889 			for (auto& actor: Main_Data::game_party->GetActors()) {
1890 				auto* sprite = actor->GetActorBattleSprite();
1891 				if (sprite) {
1892 					sprite->SetAnimationState(Sprite_Actor::AnimationState_WalkingRight);
1893 				}
1894 			}
1895 			running_away = true;
1896 			SetSceneActionSubState(eSuccess);
1897 		} else {
1898 			SetSceneActionSubState(eFailure);
1899 			ShowNotification(ToString(lcf::Data::terms.escape_failure));
1900 		}
1901 		SetWait(10, 30);
1902 		return SceneActionReturn::eContinueThisFrame;
1903 	}
1904 
1905 	if (scene_action_substate == eFailure) {
1906 		EndNotification();
1907 		ReturnToMainBattleState();
1908 		return SceneActionReturn::eContinueThisFrame;
1909 	}
1910 
1911 	if (scene_action_substate == eSuccess) {
1912 		EndNotification();
1913 		EndBattle(BattleResult::Escape);
1914 		return SceneActionReturn::eContinueThisFrame;
1915 	}
1916 
1917 	return SceneActionReturn::eWaitTillNextFrame;
1918 }
1919 
AdjustPoseForDirection(const Game_Battler * battler,int pose)1920 static int AdjustPoseForDirection(const Game_Battler* battler, int pose) {
1921 	if (battler->IsDirectionFlipped()) {
1922 		switch (pose) {
1923 			case lcf::rpg::BattlerAnimation::Pose_AttackRight:
1924 				return lcf::rpg::BattlerAnimation::Pose_AttackLeft;
1925 			case lcf::rpg::BattlerAnimation::Pose_AttackLeft:
1926 				return lcf::rpg::BattlerAnimation::Pose_AttackRight;
1927 			case lcf::rpg::BattlerAnimation::Pose_WalkRight:
1928 				return lcf::rpg::BattlerAnimation::Pose_WalkLeft;
1929 			case lcf::rpg::BattlerAnimation::Pose_WalkLeft:
1930 				return lcf::rpg::BattlerAnimation::Pose_WalkRight;
1931 		}
1932 	}
1933 	return pose;
1934 }
1935 
SetBattleActionState(BattleActionState state)1936 void Scene_Battle_Rpg2k3::SetBattleActionState(BattleActionState state) {
1937 	battle_action_state = state;
1938 }
1939 
ProcessBattleAction(Game_BattleAlgorithm::AlgorithmBase * action)1940 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleAction(Game_BattleAlgorithm::AlgorithmBase* action) {
1941 	// End any notification started by battle action
1942 	EndNotification();
1943 	auto* source = action->GetSource();
1944 
1945 	if (action == nullptr) {
1946 		return BattleActionReturn::eFinished;
1947 	}
1948 
1949 	// Immediately quit for dead actors no move. Prevents any animations or delays.
1950 	if (action->GetType() == Game_BattleAlgorithm::Type::None && action->GetSource()->IsDead()) {
1951 		return BattleActionReturn::eFinished;
1952 	}
1953 
1954 	if (Game_Battle::IsBattleAnimationWaiting() && !(action->GetType() == Game_BattleAlgorithm::Type::Normal && source->GetType() == Game_Battler::Type_Enemy)) {
1955 		return BattleActionReturn::eWait;
1956 	}
1957 
1958 	if (source->GetType() == Game_Battler::Type_Ally) {
1959 		auto* sprite = static_cast<Game_Actor*>(source)->GetActorBattleSprite();
1960 		if (sprite && !sprite->IsIdling()) {
1961 			switch (battle_action_state) {
1962 				case BattleActionState_CBAMove:
1963 				case BattleActionState_StartAnimation:
1964 				case BattleActionState_CBARangedWeaponInit:
1965 				case BattleActionState_CBARangedWeaponMove:
1966 				case BattleActionState_Animation:
1967 					break;
1968 				default:
1969 					return BattleActionReturn::eWait;
1970 			}
1971 		}
1972 	}
1973 
1974 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
1975 	static int last_state = -1;
1976 	if (battle_action_state != last_state) {
1977 		auto* source = action->GetSource();
1978 		Output::Debug("Battle2k3 ProcessBattleAction({}, {}) actor={}({}) frames={} auto_battle={}", action->GetSource()->GetName(), battle_action_state,
1979 				source->GetName(), source->GetId(),
1980 				Main_Data::game_system->GetFrameCounter(), auto_battle);
1981 		last_state = battle_action_state;
1982 	}
1983 #endif
1984 
1985 	switch (battle_action_state) {
1986 		case BattleActionState_Begin:
1987 			return ProcessBattleActionBegin(action);
1988 		case BattleActionState_PreEvents:
1989 			return ProcessBattleActionPreEvents(action);
1990 		case BattleActionState_Conditions:
1991 			return ProcessBattleActionConditions(action);
1992 		case BattleActionState_Notify:
1993 			return ProcessBattleActionNotify(action);
1994 		case BattleActionState_Combo:
1995 			return ProcessBattleActionCombo(action);
1996 		case BattleActionState_StartAlgo:
1997 			return ProcessBattleActionStartAlgo(action);
1998 		case BattleActionState_CBAInit:
1999 			return ProcessBattleActionCBAInit(action);
2000 		case BattleActionState_CBAMove:
2001 			return ProcessBattleActionCBAMove(action);
2002 		case BattleActionState_StartAnimation:
2003 			return ProcessBattleActionStartAnimation(action);
2004 		case BattleActionState_CBARangedWeaponInit:
2005 			return ProcessBattleActionCBARangedWeaponInit(action);
2006 		case BattleActionState_CBARangedWeaponMove:
2007 			return ProcessBattleActionCBARangedWeaponMove(action);
2008 		case BattleActionState_Animation:
2009 			return ProcessBattleActionAnimation(action);
2010 		case BattleActionState_AnimationReflect:
2011 			return ProcessBattleActionAnimationReflect(action);
2012 		case BattleActionState_FinishPose:
2013 			return ProcessBattleActionFinishPose(action);
2014 		case BattleActionState_Execute:
2015 			return ProcessBattleActionExecute(action);
2016 		case BattleActionState_SwitchEvents:
2017 			return ProcessBattleActionSwitchEvents(action);
2018 		case BattleActionState_Apply:
2019 			return ProcessBattleActionApply(action);
2020 		case BattleActionState_PostAction:
2021 			return ProcessBattleActionPostAction(action);
2022 		case BattleActionState_PostEvents:
2023 			return ProcessBattleActionPostEvents(action);
2024 		case BattleActionState_Finished:
2025 			return ProcessBattleActionFinished(action);
2026 	}
2027 
2028 	assert(false && "Invalid BattleActionState!");
2029 
2030 	return BattleActionReturn::eFinished;
2031 }
2032 
ProcessBattleActionBegin(Game_BattleAlgorithm::AlgorithmBase * action)2033 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionBegin(Game_BattleAlgorithm::AlgorithmBase* action) {
2034 	auto* source = action->GetSource();
2035 
2036 	// Emulate an RPG_RT bug where whenver actors attack, the damage and evasion calculations are performed
2037 	// as if the enemies are in the front row.
2038 	if (source->GetType() == Game_Battler::Type_Ally && action->GetType() == Game_BattleAlgorithm::Type::Normal) {
2039 		static_cast<Game_BattleAlgorithm::Normal*>(action)->SetTreatEnemiesAsIfInFrontRow(true);
2040 	}
2041 
2042 	// Setup enemy targets
2043 	// FIXME: This is not 100% bug compatible with RPG_RT but pretty close
2044 	// See: https://github.com/EasyRPG/Player/issues/2405#issuecomment-716298981
2045 	if (source->GetType() == Game_Battler::Type_Ally) {
2046 		auto& interp = Game_Battle::GetInterpreterBattle();
2047 		auto* actor = static_cast<Game_Actor*>(source);
2048 		interp.SetCurrentActingActorId(actor->GetId());
2049 
2050 		if (action->GetType() == Game_BattleAlgorithm::Type::Normal
2051 				|| action->GetType() == Game_BattleAlgorithm::Type::Skill
2052 				|| action->GetType() == Game_BattleAlgorithm::Type::Item)
2053 		{
2054 			auto* original_target = action->GetOriginalSingleTarget();
2055 
2056 			if (original_target && original_target->GetType() == Game_Battler::Type_Enemy) {
2057 				auto* enemy = static_cast<Game_Enemy*>(original_target);
2058 				interp.SetCurrentEnemyTargetIndex(Main_Data::game_enemyparty->GetEnemyPositionInParty(enemy));
2059 				interp.SetCurrentActionTargetsSingleEnemy(true);
2060 			} else {
2061 				interp.SetCurrentActionTargetsSingleEnemy(false);
2062 			}
2063 		}
2064 	}
2065 	// Enemy doesn't change the values, and inherits whatever the last actor did...
2066 	// Defend, row, etc.. is similar..
2067 
2068 	SetBattleActionState(BattleActionState_PreEvents);
2069 	return BattleActionReturn::eContinue;
2070 }
2071 
2072 
ProcessBattleActionPreEvents(Game_BattleAlgorithm::AlgorithmBase * action)2073 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionPreEvents(Game_BattleAlgorithm::AlgorithmBase* action) {
2074 	auto* source = action->GetSource();
2075 
2076 	// RPG_RT always runs the interpreter before starting the action.
2077 	if (!CheckBattleEndAndScheduleEvents(EventTriggerType::eBeforeBattleAction, source)) {
2078 		return BattleActionReturn::eContinue;
2079 	}
2080 
2081 	// If any battle animation is running for any reason, RPG_RT waits until the animation finishes.
2082 	// This also means that the interpreter can run again.
2083 	if (Game_Battle::IsBattleAnimationWaiting()) {
2084 		return BattleActionReturn::eWait;
2085 	}
2086 
2087 	// If the event made the current action ususable, such as MP loss or silence etc..
2088 	PrepareBattleAction(source);
2089 	pending_battle_action = source->GetBattleAlgorithm();
2090 	action = pending_battle_action.get();
2091 
2092 	// Now perform filtering. RPG_RT will run events but will early abort the battle algo if any of the following conditions hold.
2093 	// FIXME: RPG_RT doesn't actually check hidden (maybe it's impossible?) But we do it here for extensions.
2094 	// FIXME: RPG_RT doesn't check for dead enemies, only actors. Why?
2095 	if (source->IsHidden()
2096 			|| !source->IsInParty()
2097 			|| !source->CanActOrRecoverable()
2098 			) {
2099 		return BattleActionReturn::eFinished;
2100 	}
2101 
2102 	// RPG_RT cancels all enemy actions when first_strike flag is still active. This is different than
2103 	// initiative / surround, unless the flag is set for those too.
2104 	if (source->GetType() == Game_Battler::Type_Enemy
2105 			&& !source->Exists()
2106 			&& first_strike) {
2107 		return BattleActionReturn::eFinished;
2108 	}
2109 
2110 	if (source->GetType() == Game_Battler::Type_Enemy) {
2111 		if (action->GetType() != Game_BattleAlgorithm::Type::None
2112 				&& action->GetType() != Game_BattleAlgorithm::Type::DoNothing) {
2113 			if (Player::IsEnglish() || Player::IsPatchDynRpg()) {
2114 				Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Game_System::SFX_EnemyAttacks));
2115 			}
2116 			source->Flash(31, 31,31, 32, 48);
2117 		}
2118 	}
2119 
2120 	SetBattleActionState(BattleActionState_Conditions);
2121 	return BattleActionReturn::eContinue;
2122 }
2123 
ProcessBattleActionConditions(Game_BattleAlgorithm::AlgorithmBase * action)2124 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionConditions(Game_BattleAlgorithm::AlgorithmBase* action) {
2125 	(void)action;
2126 
2127 	std::vector<Game_Battler*> battlers;
2128 	Main_Data::game_party->GetActiveBattlers(battlers);
2129 	Main_Data::game_enemyparty->GetActiveBattlers(battlers);
2130 
2131 	for (auto* b : battlers) {
2132 		b->BattleStateHeal();
2133 		int damageTaken = b->ApplyConditions();
2134 		if (damageTaken != 0) {
2135 			DrawFloatText(
2136 					b->GetBattlePosition().x,
2137 					b->GetBattlePosition().y,
2138 					damageTaken < 0 ? Font::ColorDefault : Font::ColorHeal,
2139 					std::to_string(std::abs(damageTaken)));
2140 		}
2141 		if (b->GetType() == Game_Battler::Type_Ally) {
2142 			auto* sprite = static_cast<Game_Actor*>(b)->GetActorBattleSprite();
2143 			if (sprite) {
2144 				sprite->DetectStateChange();
2145 			}
2146 		}
2147 	}
2148 
2149 	status_window->Refresh();
2150 
2151 	SetBattleActionState(BattleActionState_Notify);
2152 	return BattleActionReturn::eContinue;
2153 }
2154 
ProcessBattleActionNotify(Game_BattleAlgorithm::AlgorithmBase * action)2155 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionNotify(Game_BattleAlgorithm::AlgorithmBase* action) {
2156 	std::string notification = action->GetStartMessage(0);
2157 	ShowNotification(notification);
2158 	if (!notification.empty()) {
2159 		if (action->GetType() == Game_BattleAlgorithm::Type::Skill) {
2160 			SetWait(15, 50);
2161 		} else {
2162 			SetWait(10, 40);
2163 		}
2164 	}
2165 
2166 	SetBattleActionState(BattleActionState_Combo);
2167 	return BattleActionReturn::eContinue;
2168 }
2169 
ProcessBattleActionCombo(Game_BattleAlgorithm::AlgorithmBase * action)2170 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionCombo(Game_BattleAlgorithm::AlgorithmBase* action) {
2171 	auto* source = action->GetSource();
2172 	if (source->GetType() == Game_Battler::Type_Ally) {
2173 		auto* actor = static_cast<Game_Actor*>(source);
2174 		auto combo_cmd = actor->GetBattleComboCommand();
2175 		auto combo_times = actor->GetBattleComboTimes();
2176 
2177 		if (combo_times > 1 && combo_cmd >= 0 && combo_cmd == actor->GetLastBattleAction()) {
2178 			auto* cmd = lcf::ReaderUtil::GetElement(lcf::Data::battlecommands.commands, combo_cmd);
2179 			if (cmd && (cmd->type == lcf::rpg::BattleCommand::Type_attack
2180 						|| cmd->type == lcf::rpg::BattleCommand::Type_skill
2181 						|| cmd->type == lcf::rpg::BattleCommand::Type_subskill))
2182 			{
2183 				// RPG_RT doesn't allow combo for item or other actions other than attack and skills.
2184 				action->ApplyComboHitsMultiplier(combo_times);
2185 			}
2186 		}
2187 	}
2188 	SetBattleActionState(BattleActionState_StartAlgo);
2189 	return BattleActionReturn::eContinue;
2190 }
2191 
ProcessBattleActionStartAlgo(Game_BattleAlgorithm::AlgorithmBase * action)2192 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionStartAlgo(Game_BattleAlgorithm::AlgorithmBase* action) {
2193 	const auto is_target_party = action->GetOriginalPartyTarget() != nullptr;
2194 	auto* source = action->GetSource();
2195 
2196 	action->Start();
2197 
2198 	// Drop out of the battle state machine to process actor escape.
2199 	if (action->GetType() == Game_BattleAlgorithm::Type::Escape && source->GetType() == Game_Battler::Type_Ally) {
2200 		SetState(State_Escape);
2201 		return BattleActionReturn::eContinue;
2202 	}
2203 
2204 	// FIXME: This needs to be attached to the monster target window.
2205 	// Counterexample is weapon with attack all, engine still makes you target a specific enemy,
2206 	// even though your weapon will hit all enemies.
2207 	if (action->GetSource()->GetType() == Game_Battler::Type_Ally
2208 			&& !is_target_party
2209 			&& action->GetTarget()
2210 			&& action->GetTarget()->GetType() == Game_Battler::Type_Enemy)
2211 	{
2212 		auto* actor = static_cast<Game_Actor*>(action->GetSource());
2213 		FaceTarget(*actor, *action->GetTarget());
2214 	}
2215 
2216 	if (action->GetCurrentRepeat() == 0 && action->GetCBAMovement() != lcf::rpg::BattlerAnimationItemSkill::Movement_none && source->GetType() == Game_Battler::Type_Ally) {
2217 		cba_action = action;
2218 		cba_direction_back = false;
2219 		SetBattleActionState(BattleActionState_CBAInit);
2220 	} else {
2221 		SetBattleActionState(BattleActionState_StartAnimation);
2222 	}
2223 	return BattleActionReturn::eWait;
2224 }
2225 
ProcessBattleActionCBAInit(Game_BattleAlgorithm::AlgorithmBase * action)2226 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionCBAInit(Game_BattleAlgorithm::AlgorithmBase* action) {
2227 	CBAInit();
2228 
2229 	SetBattleActionState(BattleActionState_CBAMove);
2230 	return BattleActionReturn::eWait;
2231 }
2232 
ProcessBattleActionCBAMove(Game_BattleAlgorithm::AlgorithmBase * action)2233 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionCBAMove(Game_BattleAlgorithm::AlgorithmBase* action) {
2234 	CBAMove();
2235 
2236 	if (cba_move_frame >= cba_num_move_frames) {
2237 		if (cba_direction_back) {
2238 			SetBattleActionState(BattleActionState_PostAction);
2239 		} else {
2240 			SetBattleActionState(BattleActionState_StartAnimation);
2241 		}
2242 	}
2243 	return BattleActionReturn::eWait;
2244 }
2245 
ProcessBattleActionStartAnimation(Game_BattleAlgorithm::AlgorithmBase * action)2246 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionStartAnimation(Game_BattleAlgorithm::AlgorithmBase* action) {
2247 	auto* source = action->GetSource();
2248 	bool ranged_weapon = false;
2249 
2250 	if (source->GetType() == Game_Battler::Type_Ally) {
2251 		auto* actor = static_cast<Game_Actor*>(source);
2252 		auto* sprite = actor->GetActorBattleSprite();
2253 		if (sprite) {
2254 			const auto pose = AdjustPoseForDirection(action->GetSource(), action->GetSourcePose());
2255 			if (pose != lcf::rpg::BattlerAnimation::Pose_Idle) {
2256 				auto action_state = static_cast<Sprite_Actor::AnimationState>(pose + 1);
2257 
2258 				if (action->GetType() == Game_BattleAlgorithm::Type::Normal) {
2259 					sprite->SetNormalAttacking(true);
2260 					auto* weapon = actor->GetWeaponSprite();
2261 					int weapon_animation_id = 0;
2262 					if (weapon) {
2263 						auto* weapon_animation_data = action->GetWeaponAnimationData();
2264 						if (weapon_animation_data) {
2265 							if (weapon_animation_data->type == lcf::rpg::BattlerAnimationItemSkill::AnimType_weapon) {
2266 								if (weapon_animation_data->weapon_animation_id >= 0) {
2267 									weapon->SetWeaponAnimation(weapon_animation_data->weapon_animation_id + 1);
2268 									weapon->StartAttack(action->GetSourcePose() == lcf::rpg::BattlerAnimation::Pose_AttackLeft);
2269 								}
2270 							} else {
2271 								if (weapon_animation_data->type == lcf::rpg::BattlerAnimationItemSkill::AnimType_battle) {
2272 									weapon_animation_id = weapon_animation_data->battle_animation_id;
2273 								}
2274 							}
2275 							if (weapon_animation_data->ranged) {
2276 								ranged_weapon = true;
2277 							}
2278 						}
2279 					}
2280 					sprite->SetAnimationState(
2281 							action_state,
2282 							Sprite_Actor::LoopState_WaitAfterFinish,
2283 							weapon_animation_id);
2284 				} else {
2285 					sprite->SetAnimationState(
2286 							action_state,
2287 							Sprite_Actor::LoopState_WaitAfterFinish);
2288 				}
2289 			}
2290 		}
2291 	}
2292 
2293 	if (ranged_weapon) {
2294 		SetBattleActionState(BattleActionState_CBARangedWeaponInit);
2295 	} else {
2296 		SetBattleActionState(BattleActionState_Animation);
2297 	}
2298 	return BattleActionReturn::eWait;
2299 }
2300 
ProcessBattleActionCBARangedWeaponInit(Game_BattleAlgorithm::AlgorithmBase * action)2301 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionCBARangedWeaponInit(Game_BattleAlgorithm::AlgorithmBase* action) {
2302 	auto* source = action->GetSource();
2303 	cba_ranged_weapon_move_frame = 0;
2304 	cba_ranged.clear();
2305 
2306 	if (source->GetType() == Game_Battler::Type_Ally) {
2307 		auto* actor = static_cast<Game_Actor*>(source);
2308 
2309 		if (action->GetType() == Game_BattleAlgorithm::Type::Normal) {
2310 			auto* weapon_animation_data = action->GetWeaponAnimationData();
2311 			if (weapon_animation_data) {
2312 				cba_num_ranged_weapon_move_frames = (weapon_animation_data->ranged_speed + 1) * 20;
2313 
2314 				// The ranged weapon animation targets the original single target
2315 				// if the weapon has the "Attack All" flag set and the ranged attack
2316 				// range is set to "Single Enemy"
2317 				if (action->GetWeaponData()->attack_all) {
2318 					if (action->GetWeaponData()->ranged_target == lcf::rpg::Item::Target_single) {
2319 						cba_ranged.emplace_back(*action->GetOriginalSingleTarget(), nullptr);
2320 					} else if (action->GetWeaponData()->ranged_target == lcf::rpg::Item::Target_center) {
2321 						std::vector<Game_Battler*> enemies;
2322 						Main_Data::game_enemyparty->GetActiveBattlers(enemies);
2323 						int x = 0;
2324 						int y = 0;
2325 						for (Game_Battler* enemy : enemies) {
2326 							x += enemy->GetBattlePosition().x;
2327 							y += enemy->GetBattlePosition().y;
2328 						}
2329 						if (enemies.size() > 0) {
2330 							x /= enemies.size();
2331 							y /= enemies.size();
2332 						}
2333 						cba_ranged_center = Point(x, y);
2334 
2335 						// This is needed to make the ranged weapon animation appear
2336 						// even if the real target of the animation is the center
2337 						cba_ranged.emplace_back(*action->GetTarget(), nullptr);
2338 					} else if (action->GetWeaponData()->ranged_target == lcf::rpg::Item::Target_simultaneous) {
2339 						std::vector<Game_Battler*> battlers;
2340 						Main_Data::game_enemyparty->GetActiveBattlers(battlers);
2341 						for (auto& b: battlers) {
2342 							cba_ranged.emplace_back(*b, nullptr);
2343 						}
2344 					}
2345 				} else {
2346 					assert(action->GetTarget());
2347 					cba_ranged.emplace_back(*action->GetTarget(), nullptr);
2348 				}
2349 
2350 				for (auto& it: cba_ranged) {
2351 					std::unique_ptr<Sprite_Weapon> cba_ranged_weapon = std::make_unique<Sprite_Weapon>(actor);
2352 					cba_ranged_weapon->SetWeaponAnimation(weapon_animation_data->ranged_animation_id + 1);
2353 					cba_ranged_weapon->SetRanged(true);
2354 					cba_ranged_weapon->StartAttack(action->GetSourcePose() == lcf::rpg::BattlerAnimation::Pose_AttackLeft);
2355 					cba_ranged_weapon->Update();
2356 					it.second = std::move(cba_ranged_weapon);
2357 				}
2358 			}
2359 		}
2360 	}
2361 
2362 	SetBattleActionState(BattleActionState_CBARangedWeaponMove);
2363 	return BattleActionReturn::eWait;
2364 }
2365 
ProcessBattleActionCBARangedWeaponMove(Game_BattleAlgorithm::AlgorithmBase * action)2366 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionCBARangedWeaponMove(Game_BattleAlgorithm::AlgorithmBase* action) {
2367 	auto* source = action->GetSource();
2368 
2369 	if (cba_ranged_weapon_move_frame < cba_num_ranged_weapon_move_frames) {
2370 		cba_ranged_weapon_move_frame++;
2371 		for (auto& it: cba_ranged) {
2372 			int offset_x = 0;
2373 			int offset_y = 0;
2374 			if (action->GetWeaponData()->ranged_target == lcf::rpg::Item::Target_center && action->GetWeaponData()->attack_all) {
2375 				offset_x = cba_ranged_center.x - source->GetBattlePosition().x;
2376 				offset_y = cba_ranged_center.y - source->GetBattlePosition().y;
2377 			} else {
2378 				auto& battler = it.first;
2379 				offset_x = battler.GetBattlePosition().x - source->GetBattlePosition().x;
2380 				offset_y = battler.GetBattlePosition().y - source->GetBattlePosition().y;
2381 			}
2382 			auto& weapon = it.second;
2383 			assert(weapon);
2384 			weapon->SetX(source->GetBattlePosition().x + (offset_x * cba_ranged_weapon_move_frame / cba_num_ranged_weapon_move_frames));
2385 			weapon->SetY(source->GetBattlePosition().y + (offset_y * cba_ranged_weapon_move_frame / cba_num_ranged_weapon_move_frames));
2386 			weapon->Update();
2387 		}
2388 	}
2389 
2390 	if (cba_ranged_weapon_move_frame >= cba_num_ranged_weapon_move_frames) {
2391 		for (auto& it: cba_ranged) {
2392 			auto& weapon = it.second;
2393 			weapon->StopAttack();
2394 			weapon = nullptr;
2395 		}
2396 		SetBattleActionState(BattleActionState_Animation);
2397 	}
2398 
2399 	return BattleActionReturn::eWait;
2400 }
2401 
ProcessBattleActionAnimation(Game_BattleAlgorithm::AlgorithmBase * action)2402 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionAnimation(Game_BattleAlgorithm::AlgorithmBase* action) {
2403 	const auto anim_id = action->GetAnimationId(0);
2404 	if (anim_id) {
2405 		action->PlayAnimation(anim_id, false, -1, CheckAnimFlip(action->GetSource()));
2406 	}
2407 	if (action->GetStartSe()) {
2408 		Main_Data::game_system->SePlay(*action->GetStartSe());
2409 	}
2410 
2411 	if (action->GetCBAMovement() != lcf::rpg::BattlerAnimationItemSkill::Movement_none) {
2412 		cba_direction_back = true;
2413 	}
2414 
2415 	if (action->ReflectTargets()) {
2416 		SetBattleActionState(BattleActionState_AnimationReflect);
2417 	} else {
2418 		SetBattleActionState(BattleActionState_Execute);
2419 	}
2420 	return BattleActionReturn::eContinue;
2421 }
2422 
ProcessBattleActionAnimationReflect(Game_BattleAlgorithm::AlgorithmBase * action)2423 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionAnimationReflect(Game_BattleAlgorithm::AlgorithmBase* action) {
2424 	const auto anim_id = action->GetAnimationId(0);
2425 	if (anim_id) {
2426 		assert(action->GetReflectTarget());
2427 		action->PlayAnimation(anim_id, false, -1, CheckAnimFlip(action->GetReflectTarget()));
2428 	}
2429 	SetBattleActionState(BattleActionState_FinishPose);
2430 	return BattleActionReturn::eContinue;
2431 }
2432 
ProcessBattleActionFinishPose(Game_BattleAlgorithm::AlgorithmBase * action)2433 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionFinishPose(Game_BattleAlgorithm::AlgorithmBase* action) {
2434 	auto* source = action->GetSource();
2435 	if (source->GetType() == Game_Battler::Type_Ally) {
2436 		auto* sprite = static_cast<Game_Actor*>(source)->GetActorBattleSprite();
2437 		if (sprite) {
2438 			sprite->SetAnimationLoop(Sprite_Actor::LoopState_DefaultAnimationAfterFinish);
2439 		}
2440 	}
2441 
2442 	SetBattleActionState(BattleActionState_Execute);
2443 	return BattleActionReturn::eContinue;
2444 }
2445 
ProcessBattleActionExecute(Game_BattleAlgorithm::AlgorithmBase * action)2446 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionExecute(Game_BattleAlgorithm::AlgorithmBase* action) {
2447 	if (!action->IsCurrentTargetValid()) {
2448 		if (action->GetCBAMovement() != lcf::rpg::BattlerAnimationItemSkill::Movement_none) {
2449 			SetBattleActionState(BattleActionState_CBAInit);
2450 		} else {
2451 			SetBattleActionState(BattleActionState_PostAction);
2452 		}
2453 		return BattleActionReturn::eContinue;
2454 	}
2455 
2456 	auto* source = action->GetSource();
2457 	if (source->GetType() == Game_Battler::Type_Ally) {
2458 		auto* sprite = static_cast<Game_Actor*>(source)->GetActorBattleSprite();
2459 		if (sprite) {
2460 			sprite->SetAnimationLoop(Sprite_Actor::LoopState_DefaultAnimationAfterFinish);
2461 		}
2462 	}
2463 
2464 	action->Execute();
2465 	action->ApplyCustomEffect();
2466 	action->ApplySwitchEffect();
2467 
2468 	if (action->GetAffectedSwitch() > 0) {
2469 		SetBattleActionState(BattleActionState_SwitchEvents);
2470 	} else {
2471 		SetBattleActionState(BattleActionState_Apply);
2472 	}
2473 	return BattleActionReturn::eContinue;
2474 }
2475 
ProcessBattleActionSwitchEvents(Game_BattleAlgorithm::AlgorithmBase * action)2476 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionSwitchEvents(Game_BattleAlgorithm::AlgorithmBase* action) {
2477 	// RPG_RT always runs the interpreter before starting the action.
2478 	if (!CheckBattleEndAndScheduleEvents(EventTriggerType::eAfterBattleAction, action->GetSource())) {
2479 		return BattleActionReturn::eContinue;
2480 	}
2481 
2482 	SetBattleActionState(BattleActionState_Apply);
2483 	return BattleActionReturn::eContinue;
2484 }
2485 
ProcessBattleActionApply(Game_BattleAlgorithm::AlgorithmBase * action)2486 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionApply(Game_BattleAlgorithm::AlgorithmBase* action) {
2487 	auto* source = action->GetSource();
2488 	auto* target = action->GetTarget();
2489 
2490 	Sprite_Actor* target_sprite = nullptr;
2491 	if (target->GetType() == Game_Battler::Type_Ally) {
2492 		target_sprite = static_cast<Game_Actor*>(target)->GetActorBattleSprite();
2493 	}
2494 
2495 	const bool was_dead = target->IsDead();
2496 
2497 
2498 	const bool was_absorb_hp = action->IsAbsorbHp();
2499 	// Emulates an RPG_RT bug where inverted absorb damage doesn't absorb hp anymore.
2500 	// This bug only affects hp, not sp.
2501 	if (action->IsAbsorbHp() && action->GetAffectedHp() > 0) {
2502 		action->SetIsAbsorbHp(false);
2503 	}
2504 
2505 	action->ApplyHpEffect();
2506 
2507 	// Emulates an RPG_RT bug where damage which is reversed into healing due to negative attributes is applied twice.
2508 	// The displayed numbers are normal, but the actual effect is doubled.
2509 	if (!action->IsPositive() && !was_absorb_hp && action->IsAffectHp() && action->GetAffectedHp() > 0) {
2510 		action->ApplyHpEffect();
2511 	}
2512 
2513 	action->ApplySpEffect();
2514 	action->ApplyAtkEffect();
2515 	action->ApplyDefEffect();
2516 	action->ApplySpiEffect();
2517 	action->ApplyAgiEffect();
2518 	action->ApplyStateEffects();
2519 	action->ApplyAttributeShiftEffects();
2520 
2521 	if (action->IsSuccess() && action->IsAffectHp() && action->GetAffectedHp() <= 0) {
2522 		if (target->GetType() == Game_Battler::Type_Enemy) {
2523 			auto* enemy = static_cast<Game_Enemy*>(target);
2524 			enemy->SetBlinkTimer();
2525 		} else if (action->GetAffectedHp() < 0) {
2526 			if (!target->IsDead()) {
2527 				target_sprite->SetAnimationState(Sprite_Actor::AnimationState_Damage, Sprite_Actor::LoopState_DefaultAnimationAfterFinish);
2528 			}
2529 		}
2530 	}
2531 
2532 	if (!was_dead && target->GetType() == Game_Battler::Type_Ally && target->IsDead()) {
2533 		target_sprite->SetAnimationState(Sprite_Actor::AnimationState_Dead, Sprite_Actor::LoopState_WaitAfterFinish);
2534 	}
2535 
2536 	if (action->IsSuccess() && target->GetType() == Game_Battler::Type_Enemy) {
2537 		auto* enemy = static_cast<Game_Enemy*>(target);
2538 		if (!was_dead && enemy->IsDead()) {
2539 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyKill));
2540 			enemy->SetDeathTimer();
2541 			RefreshTargetWindow();
2542 		}
2543 	}
2544 
2545 	if (target_sprite) {
2546 		target_sprite->DetectStateChange();
2547 	}
2548 
2549 	if (action->IsSuccess()) {
2550 		if (action->IsCriticalHit()) {
2551 			Main_Data::game_screen->FlashOnce(28, 28, 28, 20, 8);
2552 		}
2553 		if (action->IsAffectHp()) {
2554 			const auto hp = action->GetAffectedHp();
2555 			if (hp != 0 || (!action->IsPositive() && !action->IsAbsorbHp())) {
2556 				DrawFloatText(
2557 						target->GetBattlePosition().x,
2558 						target->GetBattlePosition().y,
2559 						hp > 0 ? Font::ColorHeal : Font::ColorDefault,
2560 						std::to_string(std::abs(hp)));
2561 
2562 				if (action->IsAbsorbHp()) {
2563 					DrawFloatText(
2564 							source->GetBattlePosition().x,
2565 							source->GetBattlePosition().y,
2566 							hp > 0 ? Font::ColorDefault : Font::ColorHeal,
2567 							std::to_string(std::abs(hp)));
2568 				}
2569 			}
2570 
2571 			if (!action->IsPositive() && !action->IsAbsorbHp()) {
2572 				if (target->GetType() == Game_Battler::Type_Ally) {
2573 					Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_AllyDamage));
2574 				} else {
2575 					Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyDamage));
2576 				}
2577 			}
2578 		}
2579 	} else {
2580 		auto* se = action->GetFailureSe();
2581 		if (se) {
2582 			Main_Data::game_system->SePlay(*se);
2583 		}
2584 		DrawFloatText(
2585 				target->GetBattlePosition().x,
2586 				target->GetBattlePosition().y,
2587 				0,
2588 				lcf::Data::terms.miss);
2589 	}
2590 
2591 	status_window->Refresh();
2592 
2593 	// Repeat on next target
2594 	if (action->TargetNext()) {
2595 		SetBattleActionState(BattleActionState_Execute);
2596 		return BattleActionReturn::eContinue;
2597 	}
2598 
2599 	// If action does multiple attacks, repeat again.
2600 	if (action->RepeatNext(false)) {
2601 		SetBattleActionState(BattleActionState_StartAlgo);
2602 		return BattleActionReturn::eContinue;
2603 	}
2604 
2605 	if (source->GetType() == Game_Battler::Type_Ally) {
2606 		if (action->GetType() == Game_BattleAlgorithm::Type::Normal) {
2607 			auto* actor = static_cast<Game_Actor*>(source);
2608 			auto* source_sprite = actor->GetActorBattleSprite();
2609 			if (source_sprite) {
2610 				source_sprite->SetNormalAttacking(false);
2611 				auto* weapon = actor->GetWeaponSprite();
2612 				int weapon_animation_id = 0;
2613 				if (weapon) {
2614 					auto* weapon_animation_data = action->GetWeaponAnimationData();
2615 					if (weapon_animation_data) {
2616 						if (weapon_animation_data->type == lcf::rpg::BattlerAnimationItemSkill::AnimType_weapon) {
2617 							weapon->StopAttack();
2618 						}
2619 					}
2620 				}
2621 			}
2622 		}
2623 	}
2624 
2625 	if (action->GetCBAMovement() != lcf::rpg::BattlerAnimationItemSkill::Movement_none) {
2626 		SetBattleActionState(BattleActionState_CBAInit);
2627 	} else {
2628 		SetBattleActionState(BattleActionState_PostAction);
2629 	}
2630 	return BattleActionReturn::eContinue;
2631 }
2632 
ProcessBattleActionPostAction(Game_BattleAlgorithm::AlgorithmBase * action)2633 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionPostAction(Game_BattleAlgorithm::AlgorithmBase* action) {
2634 	auto* source = action->GetSource();
2635 	action->ProcessPostActionSwitches();
2636 
2637 	// RPG_RT bug: Final interpreter call is only done for normal and skill for actors
2638 	if (source->GetType() == Game_Battler::Type_Enemy
2639 			|| action->GetType() == Game_BattleAlgorithm::Type::Normal
2640 			|| action->GetType() == Game_BattleAlgorithm::Type::Skill) {
2641 		SetBattleActionState(BattleActionState_PostEvents);
2642 	} else {
2643 		SetBattleActionState(BattleActionState_Finished);
2644 	}
2645 
2646 	return BattleActionReturn::eContinue;
2647 }
2648 
ProcessBattleActionPostEvents(Game_BattleAlgorithm::AlgorithmBase * action)2649 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionPostEvents(Game_BattleAlgorithm::AlgorithmBase* action) {
2650 	// RPG_RT always runs the interpreter before starting the action.
2651 	if (!CheckBattleEndAndScheduleEvents(EventTriggerType::eAfterBattleAction, action->GetSource())) {
2652 		return BattleActionReturn::eContinue;
2653 	}
2654 
2655 	SetBattleActionState(BattleActionState_Finished);
2656 	return BattleActionReturn::eContinue;
2657 }
2658 
ProcessBattleActionFinished(Game_BattleAlgorithm::AlgorithmBase * action)2659 Scene_Battle_Rpg2k3::BattleActionReturn Scene_Battle_Rpg2k3::ProcessBattleActionFinished(Game_BattleAlgorithm::AlgorithmBase* action) {
2660 	(void)action;
2661 	first_strike = false;
2662 
2663 	return BattleActionReturn::eFinished;
2664 }
2665 
IsEscapeAllowedFromOptionWindow() const2666 bool Scene_Battle_Rpg2k3::IsEscapeAllowedFromOptionWindow() const {
2667 	auto cond = Game_Battle::GetBattleCondition();
2668 
2669 	return Scene_Battle::IsEscapeAllowed() && (Game_Battle::GetTurn() == 0)
2670 		&& (first_strike || cond == lcf::rpg::System::BattleCondition_initiative || cond == lcf::rpg::System::BattleCondition_surround);
2671 }
2672 
IsEscapeAllowedFromActorCommand() const2673 bool Scene_Battle_Rpg2k3::IsEscapeAllowedFromActorCommand() const {
2674 	auto cond = Game_Battle::GetBattleCondition();
2675 
2676 	return Scene_Battle::IsEscapeAllowed() && cond != lcf::rpg::System::BattleCondition_pincers;
2677 }
2678 
AttackSelected()2679 void Scene_Battle_Rpg2k3::AttackSelected() {
2680 	// RPG_RT still requires you to select an enemy target, even if your weapon has attack all.
2681 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
2682 	SetState(State_SelectEnemyTarget);
2683 }
2684 
SubskillSelected(int command)2685 void Scene_Battle_Rpg2k3::SubskillSelected(int command) {
2686 	auto idx = command - 1;
2687 	// Resolving a subskill battle command to skill id
2688 	int subskill = lcf::rpg::Skill::Type_subskill;
2689 
2690 	// Loop through all battle commands smaller then that ID and count subsets
2691 	for (int i = 0; i < static_cast<int>(lcf::Data::battlecommands.commands.size()); ++i) {
2692 		auto& cmd = lcf::Data::battlecommands.commands[i];
2693 		if (i >= idx) {
2694 			break;
2695 		}
2696 		if (cmd.type == lcf::rpg::BattleCommand::Type_subskill) {
2697 			++subskill;
2698 		}
2699 	}
2700 
2701 	// skill subset is 4 (Type_subskill) + counted subsets
2702 	skill_window->SetSubsetFilter(subskill);
2703 	SetState(State_SelectSkill);
2704 	RecreateSpWindow(active_actor);
2705 }
2706 
SpecialSelected()2707 void Scene_Battle_Rpg2k3::SpecialSelected() {
2708 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
2709 
2710 	active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::DoNothing>(active_actor));
2711 
2712 	ActionSelectedCallback(active_actor);
2713 }
2714 
EscapeSelected()2715 void Scene_Battle_Rpg2k3::EscapeSelected() {
2716 	if (!IsEscapeAllowedFromActorCommand()) {
2717 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
2718 		return;
2719 	}
2720 	Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
2721 	active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Escape>(active_actor));
2722 	ActionSelectedCallback(active_actor);
2723 }
2724 
RowSelected()2725 void Scene_Battle_Rpg2k3::RowSelected() {
2726 	// Switching rows is only possible if in back row or
2727 	// if at least 2 party members are in front row
2728 	int current_row = active_actor->GetBattleRow();
2729 	int front_row_battlers = 0;
2730 	if (current_row == active_actor->IsDirectionFlipped()) {
2731 		for (auto& actor: Main_Data::game_party->GetActors()) {
2732 			if (actor->GetBattleRow() == actor->IsDirectionFlipped()) front_row_battlers++;
2733 		}
2734 	}
2735 	if (current_row != active_actor->IsDirectionFlipped() || front_row_battlers >= 2) {
2736 		if (active_actor->GetBattleRow() == Game_Actor::RowType::RowType_front) {
2737 			active_actor->SetBattleRow(Game_Actor::RowType::RowType_back);
2738 		} else {
2739 			active_actor->SetBattleRow(Game_Actor::RowType::RowType_front);
2740 		}
2741 		active_actor->SetBattlePosition(Game_Battle::Calculate2k3BattlePosition(*active_actor));
2742 		active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::DoNothing>(active_actor));
2743 		ActionSelectedCallback(active_actor);
2744 	} else {
2745 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
2746 	}
2747 }
2748 
ActionSelectedCallback(Game_Battler * for_battler)2749 void Scene_Battle_Rpg2k3::ActionSelectedCallback(Game_Battler* for_battler) {
2750 	for_battler->SetAtbGauge(0);
2751 
2752 	if (for_battler == active_actor) {
2753 		auto idx = GetFirstReadyActor();
2754 		SetActiveActor(idx);
2755 	}
2756 
2757 	Scene_Battle::ActionSelectedCallback(for_battler);
2758 
2759 	if (for_battler->GetType() == Game_Battler::Type_Ally) {
2760 		ReturnToMainBattleState();
2761 	}
2762 
2763 #ifdef EP_DEBUG_BATTLE2K3_STATE_MACHINE
2764 	Output::Debug("Battle2k3 ScheduleAction {} name={} type={} frame={}",
2765 			((for_battler->GetType() == Game_Battler::Type_Ally) ? "Actor" : "Enemy"),
2766 			for_battler->GetName(), for_battler->GetBattleAlgorithm()->GetType(), Main_Data::game_system->GetFrameCounter());
2767 #endif
2768 }
2769 
ShowNotification(std::string text)2770 void Scene_Battle_Rpg2k3::ShowNotification(std::string text) {
2771 	if (text.empty()) {
2772 		return;
2773 	}
2774 	help_window->SetVisible(true);
2775 	help_window->SetText(std::move(text), Font::ColorDefault, Text::AlignLeft, false);
2776 }
2777 
EndNotification()2778 void Scene_Battle_Rpg2k3::EndNotification() {
2779 	help_window->SetVisible(false);
2780 }
2781 
CheckAnimFlip(Game_Battler * battler)2782 bool Scene_Battle_Rpg2k3::CheckAnimFlip(Game_Battler* battler) {
2783 	if (Main_Data::game_system->GetInvertAnimations()) {
2784 		return battler->IsDirectionFlipped() ^ (battler->GetType() == Game_Battler::Type_Enemy);
2785 	}
2786 	return false;
2787 }
2788 
SetWait(int min_wait,int max_wait)2789 void Scene_Battle_Rpg2k3::SetWait(int min_wait, int max_wait) {
2790         battle_action_wait = max_wait;
2791         battle_action_min_wait = max_wait - min_wait;
2792 }
2793 
CheckWait()2794 bool Scene_Battle_Rpg2k3::CheckWait() {
2795         if (battle_action_wait > 0) {
2796                 if (Input::IsPressed(Input::CANCEL)) {
2797                         return false;
2798                 }
2799                 --battle_action_wait;
2800                 if (battle_action_wait > battle_action_min_wait) {
2801                         return false;
2802                 }
2803                 if (!Input::IsPressed(Input::DECISION)
2804                         && !Input::IsPressed(Input::SHIFT)
2805                         && battle_action_wait > 0) {
2806                         return false;
2807                 }
2808                 battle_action_wait = 0;
2809         }
2810         return true;
2811 }
2812 
OnPartyChanged(Game_Actor * actor,bool added)2813 void Scene_Battle_Rpg2k3::OnPartyChanged(Game_Actor* actor, bool added) {
2814 	if (!added) {
2815 		actor->SetBattleSprite(nullptr);
2816 		return;
2817 	}
2818 
2819 	actor->SetBattleSprite(std::make_unique<Sprite_Actor>(actor));
2820 
2821 	// RPG_RT only does this when actors added to party
2822 	// Wait until sprites loaded
2823 	AsyncNext([this]() {
2824 			InitActors();
2825 			ResetAllBattlerZ();
2826 			});
2827 }
2828 
OnEventHpChanged(Game_Battler * battler,int hp)2829 void Scene_Battle_Rpg2k3::OnEventHpChanged(Game_Battler* battler, int hp) {
2830 	DrawFloatText(
2831 			battler->GetBattlePosition().x,
2832 			battler->GetBattlePosition().y,
2833 			hp < 0 ? Font::ColorDefault : Font::ColorHeal,
2834 			std::to_string(std::abs(hp)));
2835 }
2836 
RecreateSpWindow(Game_Battler * battler)2837 void Scene_Battle_Rpg2k3::RecreateSpWindow(Game_Battler* battler) {
2838 	bool small_window = (lcf::Data::battlecommands.window_size == lcf::rpg::BattleCommands::WindowSize_small);
2839 	int spwindow_size = 60;
2840 	int spwindow_height = (small_window ? 20 : 32);
2841 	if (battler && battler->MaxSpValue() >= 1000) {
2842 		spwindow_size = 72;
2843 	}
2844 	sp_window = std::make_unique<Window_ActorSp>(SCREEN_TARGET_WIDTH - spwindow_size, (small_window ? 154 : 136), spwindow_size, spwindow_height);
2845 	sp_window->SetVisible(false);
2846 	sp_window->SetBorderY(small_window ? 2 : 8);
2847 	sp_window->SetContents(Bitmap::Create(sp_window->GetWidth() - sp_window->GetBorderX() / 2, sp_window->GetHeight() - sp_window->GetBorderY() * 2));
2848 	sp_window->SetZ(Priority_Window + 2);
2849 	if (battler) {
2850 		sp_window->SetBattler(*battler);
2851 	}
2852 }
2853 
CBAInit()2854 void Scene_Battle_Rpg2k3::CBAInit() {
2855 	auto* source = cba_action->GetSource();
2856 	cba_move_frame = 0;
2857 
2858 	auto* actor = static_cast<Game_Actor*>(source);
2859 	auto* sprite = actor->GetActorBattleSprite();
2860 	if (!cba_direction_back) {
2861 		cba_start_pos = source->GetBattlePosition();
2862 		if (sprite) {
2863 			sprite->SetAnimationState(Sprite_Actor::AnimationState_WalkingLeft);
2864 		}
2865 		if (cba_action->GetCBAMovement() == lcf::rpg::BattlerAnimationItemSkill::Movement_move) {
2866 			auto* target = cba_action->GetTarget();
2867 			if (target != nullptr) {
2868 				auto* enemy = static_cast<Game_Enemy*>(target);
2869 				auto* enemysprite = enemy->GetEnemyBattleSprite();
2870 				if (enemysprite) {
2871 					cba_end_pos = Point(target->GetBattlePosition().x + (source->IsDirectionFlipped() ? -(enemysprite->GetWidth() / 2) : enemysprite->GetWidth() / 2), target->GetBattlePosition().y);
2872 				}
2873 			}
2874 		}
2875 	} else {
2876 		if (sprite) {
2877 			if (cba_action->GetType() == Game_BattleAlgorithm::Type::Normal || cba_action->GetCBAMovement() == lcf::rpg::BattlerAnimationItemSkill::Movement_move) {
2878 				sprite->SetAnimationState(Sprite_Actor::AnimationState_WalkingRight);
2879 			} else {
2880 				sprite->SetAnimationState(Sprite_Actor::AnimationState_WalkingLeft);
2881 			}
2882 		}
2883 	}
2884 
2885 	if (cba_action->GetCBAMovement() != lcf::rpg::BattlerAnimationItemSkill::Movement_none) {
2886 		sprite->SetAfterimageAmount(cba_action->GetCBAAfterimage() == lcf::rpg::BattlerAnimationItemSkill::Afterimage_add ? 3 : 0);
2887 	}
2888 }
2889 
CBAMove()2890 void Scene_Battle_Rpg2k3::CBAMove() {
2891 	auto* source = cba_action->GetSource();
2892 
2893 	if (cba_move_frame < cba_num_move_frames) {
2894 		// RPG_RT increments the frame counter twice per frame,
2895 		// so we emulate this behavior here
2896 		cba_move_frame += 2;
2897 		int frame = (cba_direction_back ? std::max(0, cba_num_move_frames - cba_move_frame) : std::min(cba_num_move_frames, cba_move_frame));
2898 		int move_dir_mult = (source->IsDirectionFlipped() ? 1 : -1);
2899 		int offset_x = 0;
2900 		int offset_y = 0;
2901 		if (cba_action->GetCBAMovement() == lcf::rpg::BattlerAnimationItemSkill::Movement_step || cba_action->GetCBAMovement() == lcf::rpg::BattlerAnimationItemSkill::Movement_jump) {
2902 			offset_x = 25 * move_dir_mult * frame / cba_num_move_frames;
2903 		}
2904 		if (cba_action->GetCBAMovement() == lcf::rpg::BattlerAnimationItemSkill::Movement_jump) {
2905 			offset_y = -25 * sin(M_PI * frame / cba_num_move_frames) / 2;
2906 		}
2907 		if (cba_action->GetCBAMovement() == lcf::rpg::BattlerAnimationItemSkill::Movement_move) {
2908 			offset_x = (cba_end_pos.x - cba_start_pos.x) * frame / cba_num_move_frames;
2909 			offset_y = (cba_end_pos.y - cba_start_pos.y) * frame / cba_num_move_frames;
2910 		}
2911 		source->SetBattlePosition(Point(cba_start_pos.x + offset_x, cba_start_pos.y + offset_y));
2912 
2913 		if (source->GetType() == Game_Battler::Type_Ally) {
2914 			auto* sprite = static_cast<Game_Actor*>(source)->GetActorBattleSprite();
2915 			if (sprite) {
2916 				sprite->ResetZ();
2917 			}
2918 		}
2919 	}
2920 
2921 	if (cba_move_frame >= cba_num_move_frames) {
2922 		auto* actor = static_cast<Game_Actor*>(source);
2923 		auto* sprite = actor->GetActorBattleSprite();
2924 		if (sprite) {
2925 			sprite->DoAfterimageFade();
2926 		}
2927 		if (cba_direction_back) {
2928 			if (sprite) {
2929 				sprite->DoIdleAnimation();
2930 			}
2931 			cba_action = nullptr;
2932 		}
2933 	}
2934 }
2935