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