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 #include <algorithm>
19 #include <sstream>
20 #include "input.h"
21 #include "player.h"
22 #include "sprite.h"
23 #include "sprite_enemy.h"
24 #include "game_battler.h"
25 #include "game_system.h"
26 #include "game_party.h"
27 #include "game_enemy.h"
28 #include "game_enemyparty.h"
29 #include "game_message.h"
30 #include "game_battle.h"
31 #include "game_battlealgorithm.h"
32 #include "game_screen.h"
33 #include "battle_animation.h"
34 #include <lcf/reader_util.h>
35 #include "scene_battle_rpg2k.h"
36 #include "scene_battle.h"
37 #include "scene_gameover.h"
38 #include "game_interpreter_battle.h"
39 #include "output.h"
40 #include "rand.h"
41 #include "autobattle.h"
42 #include "enemyai.h"
43 #include "battle_message.h"
44
Scene_Battle_Rpg2k(const BattleArgs & args)45 Scene_Battle_Rpg2k::Scene_Battle_Rpg2k(const BattleArgs& args) :
46 Scene_Battle(args)
47 {
48 }
49
~Scene_Battle_Rpg2k()50 Scene_Battle_Rpg2k::~Scene_Battle_Rpg2k() {
51 }
52
Start()53 void Scene_Battle_Rpg2k::Start() {
54 Scene_Battle::Start();
55 CreateEnemySprites();
56 }
57
CreateUi()58 void Scene_Battle_Rpg2k::CreateUi() {
59 Scene_Battle::CreateUi();
60
61 status_window.reset(new Window_BattleStatus(0, (SCREEN_TARGET_HEIGHT-80), SCREEN_TARGET_WIDTH - option_command_mov, 80));
62
63 CreateBattleTargetWindow();
64 CreateBattleCommandWindow();
65
66 battle_message_window.reset(new Window_BattleMessage(0, (SCREEN_TARGET_HEIGHT - 80), SCREEN_TARGET_WIDTH, 80));
67
68 if (!IsEscapeAllowed()) {
69 auto it = std::find(battle_options.begin(), battle_options.end(), Escape);
70 if (it != battle_options.end()) {
71 options_window->DisableItem(std::distance(battle_options.begin(), it));
72 }
73 }
74
75 SetCommandWindows(0);
76
77 ResetWindows(true);
78 battle_message_window->SetVisible(true);
79 }
80
CreateEnemySprites()81 void Scene_Battle_Rpg2k::CreateEnemySprites() {
82 for (auto* enemy: Main_Data::game_enemyparty->GetEnemies()) {
83 auto sprite = std::make_unique<Sprite_Enemy>(enemy);
84 sprite->SetVisible(true);
85 enemy->SetBattleSprite(std::move(sprite));
86 }
87 }
88
GetEnemyTargetNames()89 static std::vector<std::string> GetEnemyTargetNames() {
90 std::vector<std::string> commands;
91
92 std::vector<Game_Battler*> enemies;
93 Main_Data::game_enemyparty->GetActiveBattlers(enemies);
94
95 for (auto& enemy: enemies) {
96 commands.push_back(ToString(enemy->GetName()));
97 }
98
99 return commands;
100 }
101
CreateBattleTargetWindow()102 void Scene_Battle_Rpg2k::CreateBattleTargetWindow() {
103 auto commands = GetEnemyTargetNames();
104 target_window.reset(new Window_Command(std::move(commands), 136, 4));
105 target_window->SetHeight(80);
106 target_window->SetY(SCREEN_TARGET_HEIGHT-80);
107 // Above other windows
108 target_window->SetZ(Priority_Window + 10);
109 }
110
RefreshTargetWindow()111 void Scene_Battle_Rpg2k::RefreshTargetWindow() {
112 auto commands = GetEnemyTargetNames();
113 target_window->ReplaceCommands(std::move(commands));
114 }
115
CreateBattleCommandWindow()116 void Scene_Battle_Rpg2k::CreateBattleCommandWindow() {
117 std::vector<std::string> commands = {
118 ToString(lcf::Data::terms.command_attack),
119 ToString(lcf::Data::terms.command_skill),
120 ToString(lcf::Data::terms.command_defend),
121 ToString(lcf::Data::terms.command_item)
122 };
123
124 command_window.reset(new Window_Command(std::move(commands), 76));
125 command_window->SetHeight(80);
126 command_window->SetX(SCREEN_TARGET_WIDTH - option_command_mov);
127 command_window->SetY(SCREEN_TARGET_HEIGHT-80);
128 }
129
RefreshCommandWindow()130 void Scene_Battle_Rpg2k::RefreshCommandWindow() {
131 command_window->SetItemText(1, active_actor->GetSkillName());
132 }
133
SetState(Scene_Battle::State new_state)134 void Scene_Battle_Rpg2k::SetState(Scene_Battle::State new_state) {
135 previous_state = state;
136 state = new_state;
137
138 SetSceneActionSubState(0);
139 }
140
UpdateBattleState()141 bool Scene_Battle_Rpg2k::UpdateBattleState() {
142 if (resume_from_debug_scene) {
143 resume_from_debug_scene = false;
144 return true;
145 }
146
147 UpdateScreen();
148 UpdateBattlers();
149 UpdateUi();
150 battle_message_window->Update();
151
152 if (!UpdateEvents()) {
153 return false;
154 }
155
156 if (!UpdateTimers()) {
157 return false;
158 }
159
160 if (Input::IsTriggered(Input::DEBUG_MENU)) {
161 if (this->CallDebug()) {
162 // Set this flag so that when we return and run update again, we resume exactly from after this point.
163 resume_from_debug_scene = true;
164 return false;
165 }
166 }
167 return true;
168 }
169
Update()170 void Scene_Battle_Rpg2k::Update() {
171 const auto process_scene = UpdateBattleState();
172
173 while (process_scene) {
174 // Something ended the battle.
175 if (Scene::instance.get() != this) {
176 break;
177 }
178
179 if (IsWindowMoving()) {
180 break;
181 }
182
183 if (Game_Message::IsMessageActive() || Game_Battle::GetInterpreter().IsRunning()) {
184 break;
185 }
186
187 if (!CheckWait()) {
188 break;
189 }
190
191 if (ProcessSceneAction() == SceneActionReturn::eWaitTillNextFrame) {
192 break;
193 }
194 }
195
196 Game_Battle::UpdateGraphics();
197 }
198
SetSceneActionSubState(int substate)199 void Scene_Battle_Rpg2k::SetSceneActionSubState(int substate) {
200 scene_action_substate = substate;
201 }
202
NextTurn()203 void Scene_Battle_Rpg2k::NextTurn() {
204 Main_Data::game_party->IncTurns();
205 Game_Battle::GetInterpreterBattle().ResetPagesExecuted();
206 }
207
CheckBattleEndAndScheduleEvents()208 bool Scene_Battle_Rpg2k::CheckBattleEndAndScheduleEvents() {
209 if (CheckBattleEndConditions()) {
210 return false;
211 }
212
213 auto& interp = Game_Battle::GetInterpreterBattle();
214
215 int page = interp.ScheduleNextPage(nullptr);
216 #ifdef EP_DEBUG_BATTLE2K_STATE_MACHINE
217 if (page) {
218 Output::Debug("Battle2k ScheduleNextEventPage Scheduled Page {} frame={}", page, Main_Data::game_system->GetFrameCounter());
219 } else {
220 Output::Debug("Battle2k ScheduleNextEventPage No Events to Run frame={}", Main_Data::game_system->GetFrameCounter());
221 }
222 #else
223 (void)page;
224 #endif
225
226 return !interp.IsRunning();
227 }
228
229
ProcessSceneAction()230 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneAction() {
231 #ifdef EP_DEBUG_BATTLE2K_STATE_MACHINE
232 static int last_state = -1;
233 static int last_substate = -1;
234 if (state != last_state || scene_action_substate != last_substate) {
235 Output::Debug("Battle2k ProcessSceneAction({},{}) frames={}", state, scene_action_substate, Main_Data::game_system->GetFrameCounter());
236 last_state = state;
237 last_substate = scene_action_substate;
238 }
239 #endif
240 switch (state) {
241 case State_Start:
242 return ProcessSceneActionStart();
243 case State_SelectOption:
244 return ProcessSceneActionFightAutoEscape();
245 case State_SelectActor:
246 return ProcessSceneActionActor();
247 case State_AutoBattle:
248 return ProcessSceneActionAutoBattle();
249 case State_SelectCommand:
250 return ProcessSceneActionCommand();
251 case State_SelectItem:
252 return ProcessSceneActionItem();
253 case State_SelectSkill:
254 return ProcessSceneActionSkill();
255 case State_SelectEnemyTarget:
256 return ProcessSceneActionEnemyTarget();
257 case State_SelectAllyTarget:
258 return ProcessSceneActionAllyTarget();
259 case State_Battle:
260 return ProcessSceneActionBattle();
261 case State_Victory:
262 return ProcessSceneActionVictory();
263 case State_Defeat:
264 return ProcessSceneActionDefeat();
265 case State_Escape:
266 return ProcessSceneActionEscape();
267 }
268 assert(false && "Invalid SceneActionState!");
269 return SceneActionReturn::eWaitTillNextFrame;
270 }
271
ProcessSceneActionStart()272 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionStart() {
273 enum SubState {
274 eBegin,
275 eDisplayMonsters,
276 eFirstStrike,
277 eClear
278 };
279
280 if (scene_action_substate == eBegin) {
281 ResetWindows(true);
282 battle_message_window->SetVisible(true);
283
284 std::vector<Game_Battler *> visible_enemies;
285 // First time entered, initialize.
286 Main_Data::game_enemyparty->GetActiveBattlers(visible_enemies);
287
288 for (auto& enemy: visible_enemies) {
289 // Format and wordwrap all messages, then pull them out and push them back 1 at a time.
290 battle_message_window->PushWithSubject(lcf::Data::terms.encounter, enemy->GetName());
291 }
292
293 battle_result_messages = battle_message_window->GetLines();
294 battle_result_messages_it = battle_result_messages.begin();
295 battle_message_window->Clear();
296
297 if (!visible_enemies.empty()) {
298 SetWait(4, 4);
299 }
300 SetSceneActionSubState(eDisplayMonsters);
301 return SceneActionReturn::eContinueThisFrame;
302 }
303
304 if (scene_action_substate == eDisplayMonsters) {
305 if (battle_result_messages_it == battle_result_messages.end()) {
306 SetSceneActionSubState(eFirstStrike);
307 return SceneActionReturn::eContinueThisFrame;
308 }
309
310 if (battle_message_window->IsPageFilled()) {
311 battle_message_window->Clear();
312 SetWait(4, 4);
313 return SceneActionReturn::eContinueThisFrame;
314 }
315
316 battle_message_window->Push(*battle_result_messages_it);
317 ++battle_result_messages_it;
318
319 if (battle_result_messages_it == battle_result_messages.end() ||
320 battle_message_window->IsPageFilled()) {
321 SetWait(30, 70);
322 }
323 else {
324 SetWait(8, 8);
325 }
326
327 return SceneActionReturn::eContinueThisFrame;
328 }
329
330 if (scene_action_substate == eFirstStrike) {
331 battle_message_window->Clear();
332 battle_result_messages.clear();
333 battle_result_messages_it = battle_result_messages.end();
334
335 if (first_strike) {
336 battle_message_window->Push(lcf::Data::terms.special_combat);
337 SetWait(30, 70);
338 }
339
340 SetSceneActionSubState(eClear);
341
342 return SceneActionReturn::eContinueThisFrame;
343 }
344
345 if (scene_action_substate == eClear) {
346 battle_message_window->Clear();
347 SetState(State_SelectOption);
348 }
349
350 return SceneActionReturn::eContinueThisFrame;
351 }
352
ResetWindows(bool make_invisible)353 void Scene_Battle_Rpg2k::ResetWindows(bool make_invisible) {
354 options_window->SetActive(false);
355 status_window->SetActive(false);
356 command_window->SetActive(false);
357 item_window->SetActive(false);
358 skill_window->SetActive(false);
359 target_window->SetActive(false);
360 battle_message_window->SetActive(false);
361
362 if (!make_invisible) {
363 return;
364 }
365
366 options_window->SetVisible(false);
367 status_window->SetVisible(false);
368 command_window->SetVisible(false);
369 target_window->SetVisible(false);
370 battle_message_window->SetVisible(false);
371 item_window->SetVisible(false);
372 skill_window->SetVisible(false);
373 help_window->SetVisible(false);
374 }
375
SetCommandWindows(int x)376 void Scene_Battle_Rpg2k::SetCommandWindows(int x) {
377 options_window->SetX(x);
378 x += options_window->GetWidth();
379 status_window->SetX(x);
380 x += status_window->GetWidth();
381 command_window->SetX(x);
382 }
383
MoveCommandWindows(int x,int frames)384 void Scene_Battle_Rpg2k::MoveCommandWindows(int x, int frames) {
385 options_window->InitMovement(options_window->GetX(), options_window->GetY(),
386 x, options_window->GetY(), frames);
387
388 x += options_window->GetWidth();
389 status_window->InitMovement(status_window->GetX(), status_window->GetY(),
390 x, status_window->GetY(), frames);
391
392 x += status_window->GetWidth();
393 command_window->InitMovement(command_window->GetX(), command_window->GetY(),
394 x, command_window->GetY(), frames);
395 }
396
ProcessSceneActionFightAutoEscape()397 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionFightAutoEscape() {
398 enum SubState {
399 eBegin,
400 eCheckEvents,
401 eMoveWindow,
402 eWaitForInput,
403 };
404
405 if (scene_action_substate == eBegin) {
406 ResetWindows(true);
407 battle_message_window->SetVisible(true);
408
409 SetSceneActionSubState(eCheckEvents);
410 }
411
412 if (scene_action_substate == eCheckEvents) {
413 if (!CheckBattleEndAndScheduleEvents()) {
414 return SceneActionReturn::eContinueThisFrame;
415 }
416
417 // No Auto battle/Escape when all actors are sleeping or similar
418 if (!Main_Data::game_party->IsAnyControllable()) {
419 SetState(State_SelectActor);
420 return SceneActionReturn::eContinueThisFrame;
421 }
422
423 SetSceneActionSubState(eMoveWindow);
424 }
425
426 if (scene_action_substate == eMoveWindow) {
427 options_window->SetVisible(true);
428 status_window->SetVisible(true);
429 status_window->SetIndex(-1);
430 command_window->SetIndex(-1);
431 command_window->SetVisible(true);
432 battle_message_window->SetVisible(false);
433 status_window->Refresh();
434
435 if (previous_state == State_SelectCommand) {
436 MoveCommandWindows(0, 8);
437 } else {
438 SetCommandWindows(0);
439 }
440 SetSceneActionSubState(eWaitForInput);
441 return SceneActionReturn::eContinueThisFrame;
442 }
443
444 if (scene_action_substate == eWaitForInput) {
445 options_window->SetActive(true);
446
447 if (Input::IsTriggered(Input::DECISION)) {
448 if (!message_window->IsVisible()) {
449 switch (battle_options[options_window->GetIndex()]) {
450 case Battle: // Battle
451 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
452 RefreshTargetWindow();
453 target_window->SetVisible(false);
454 SetState(State_SelectActor);
455 break;
456 case AutoBattle: // Auto Battle
457 SetState(State_AutoBattle);
458 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
459 break;
460 case Escape: // Escape
461 if (!IsEscapeAllowed()) {
462 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
463 }
464 else {
465 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
466 SetState(State_Escape);
467 }
468 break;
469 }
470 }
471 return SceneActionReturn::eWaitTillNextFrame;
472 }
473 }
474 return SceneActionReturn::eWaitTillNextFrame;
475 }
476
ProcessSceneActionActor()477 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionActor() {
478 SelectNextActor(false);
479 return SceneActionReturn::eContinueThisFrame;
480 }
481
ProcessSceneActionAutoBattle()482 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionAutoBattle() {
483 SelectNextActor(true);
484 return SceneActionReturn::eContinueThisFrame;
485 }
486
ProcessSceneActionCommand()487 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionCommand() {
488 enum SubState {
489 eMoveWindow,
490 eWaitForInput,
491 };
492
493 if (scene_action_substate == eMoveWindow) {
494 RefreshCommandWindow();
495
496 ResetWindows(true);
497
498 options_window->SetVisible(true);
499 status_window->SetVisible(true);
500 command_window->SetVisible(true);
501 if (previous_state == State_SelectActor) {
502 command_window->SetIndex(0);
503 }
504
505 MoveCommandWindows(-options_window->GetWidth(), 8);
506 SetSceneActionSubState(eWaitForInput);
507 return SceneActionReturn::eContinueThisFrame;
508 }
509
510 if (scene_action_substate == eWaitForInput) {
511 command_window->SetActive(true);
512 if (Input::IsTriggered(Input::DECISION)) {
513 switch (command_window->GetIndex()) {
514 case 0: // Attack
515 AttackSelected();
516 break;
517 case 1: // Skill
518 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
519 SetState(State_SelectSkill);
520 break;
521 case 2: // Defense
522 DefendSelected();
523 break;
524 case 3: // Item
525 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
526 SetState(State_SelectItem);
527 break;
528 default:
529 // no-op
530 break;
531 }
532 return SceneActionReturn::eWaitTillNextFrame;
533 }
534 if (Input::IsTriggered(Input::CANCEL)) {
535 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
536 --actor_index;
537 SelectPreviousActor();
538 return SceneActionReturn::eWaitTillNextFrame;
539 }
540 }
541 return SceneActionReturn::eWaitTillNextFrame;
542 }
543
ProcessSceneActionItem()544 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionItem() {
545 enum SubState {
546 eBegin,
547 eWaitForInput,
548 };
549
550 if (scene_action_substate == eBegin) {
551 ResetWindows(true);
552
553 item_window->SetVisible(true);
554 item_window->SetActive(true);
555 item_window->SetActor(active_actor);
556 item_window->Refresh();
557 item_window->SetHelpWindow(help_window.get());
558 help_window->SetVisible(true);
559
560 SetSceneActionSubState(eWaitForInput);
561 }
562
563 if (scene_action_substate == eWaitForInput) {
564 if (Input::IsTriggered(Input::DECISION)) {
565 ItemSelected();
566 return SceneActionReturn::eWaitTillNextFrame;
567 }
568 if (Input::IsTriggered(Input::CANCEL)) {
569 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
570 SetState(State_SelectCommand);
571 return SceneActionReturn::eWaitTillNextFrame;
572 }
573 }
574 return SceneActionReturn::eWaitTillNextFrame;
575 }
576
ProcessSceneActionSkill()577 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionSkill() {
578 enum SubState {
579 eBegin,
580 eWaitForInput,
581 };
582
583 if (scene_action_substate == eBegin) {
584 ResetWindows(true);
585
586 skill_window->SetActive(true);
587 skill_window->SetActor(active_actor->GetId());
588 if (previous_state == State_SelectCommand) {
589 skill_window->RestoreActorIndex(actor_index - 1);
590 }
591 skill_window->SetVisible(true);
592 skill_window->SetHelpWindow(help_window.get());
593 help_window->SetVisible(true);
594
595 SetSceneActionSubState(eWaitForInput);
596 }
597
598 if (scene_action_substate == eWaitForInput) {
599 if (Input::IsTriggered(Input::DECISION)) {
600 skill_window->SaveActorIndex(actor_index - 1);
601 SkillSelected();
602 return SceneActionReturn::eWaitTillNextFrame;
603 }
604 if (Input::IsTriggered(Input::CANCEL)) {
605 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
606 skill_window->SaveActorIndex(actor_index - 1);
607 SetState(State_SelectCommand);
608 return SceneActionReturn::eWaitTillNextFrame;
609 }
610 }
611 return SceneActionReturn::eWaitTillNextFrame;
612 }
613
ProcessSceneActionEnemyTarget()614 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionEnemyTarget() {
615 enum SubState {
616 eBegin,
617 eWaitForInput,
618 };
619
620 std::vector<Game_Battler*> enemies;
621 Main_Data::game_enemyparty->GetActiveBattlers(enemies);
622 Game_Enemy* target = static_cast<Game_Enemy*>(enemies[target_window->GetIndex()]);
623
624 if (scene_action_substate == eBegin) {
625 select_target_flash_count = 0;
626 ResetWindows(false);
627
628 target_window->SetVisible(true);
629 target_window->SetActive(true);
630 target_window->SetVisible(true);
631 target_window->SetIndex(0);
632
633 SetSceneActionSubState(eWaitForInput);
634 }
635
636 ++select_target_flash_count;
637
638 if (select_target_flash_count == 60) {
639 SelectionFlash(target);
640 select_target_flash_count = 0;
641 }
642
643 if (scene_action_substate == eWaitForInput) {
644 if (Input::IsTriggered(Input::DECISION)) {
645 EnemySelected();
646 return SceneActionReturn::eWaitTillNextFrame;
647 }
648 if (Input::IsTriggered(Input::CANCEL)) {
649 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
650 SetState(previous_state);
651 return SceneActionReturn::eWaitTillNextFrame;
652 }
653 }
654 return SceneActionReturn::eWaitTillNextFrame;
655 }
656
ProcessSceneActionAllyTarget()657 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionAllyTarget() {
658 enum SubState {
659 eBegin,
660 eWaitForInput,
661 };
662
663 if (scene_action_substate == eBegin) {
664 ResetWindows(false);
665 status_window->SetActive(true);
666 status_window->SetVisible(true);
667 status_window->SetIndex(0);
668
669 SetSceneActionSubState(eWaitForInput);
670 }
671
672 if (scene_action_substate == eWaitForInput) {
673 if (Input::IsTriggered(Input::DECISION)) {
674 AllySelected();
675 return SceneActionReturn::eWaitTillNextFrame;
676 }
677 if (Input::IsTriggered(Input::CANCEL)) {
678 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
679 SetState(previous_state);
680 return SceneActionReturn::eWaitTillNextFrame;
681 }
682 }
683 return SceneActionReturn::eWaitTillNextFrame;
684 }
685
ProcessSceneActionBattle()686 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionBattle() {
687 enum SubState {
688 eBegin,
689 ePreAction,
690 eBattleAction,
691 ePost,
692 };
693
694 if (scene_action_substate == eBegin) {
695 ResetWindows(true);
696 battle_message_window->SetVisible(true);
697
698 SetSceneActionSubState(ePreAction);
699 }
700
701 if (scene_action_substate == ePreAction) {
702 // Remove actions for battlers who were killed or removed from the battle.
703 while (!battle_actions.empty() && !battle_actions.front()->Exists()) {
704 RemoveCurrentAction();
705 }
706
707 // Check for end battle, and run events before action
708 // This happens before each battler acts and also right after the last battler acts.
709 if (!CheckBattleEndAndScheduleEvents()) {
710 return SceneActionReturn::eContinueThisFrame;
711 }
712
713 if (battle_actions.empty()) {
714 SetSceneActionSubState(ePost);
715 return SceneActionReturn::eContinueThisFrame;
716 }
717
718 auto* battler = battle_actions.front();
719 // If we will start a new battle action, first check for state changes
720 // such as death, paralyze, confuse, etc..
721 PrepareBattleAction(battler);
722 pending_battle_action = battler->GetBattleAlgorithm();
723
724 #ifdef EP_DEBUG_BATTLE2K_STATE_MACHINE
725 Output::Debug("Battle2k StartBattleAction battler={} frame={}", battler->GetName(), Main_Data::game_system->GetFrameCounter());
726 #endif
727
728 // Initialize battle state
729 battle_action_wait = 0;
730 SetBattleActionState(BattleActionState_Begin);
731 battle_action_start_index = 0;
732 battle_action_results_index = 0;
733 battle_action_dmg_index = 0;
734 battle_action_substate_index = 0;
735 pending_message = {};
736
737 SetSceneActionSubState(eBattleAction);
738 }
739
740 if (scene_action_substate == eBattleAction) {
741 if (ProcessBattleAction(pending_battle_action.get()) == BattleActionReturn::eContinue) {
742 return SceneActionReturn::eContinueThisFrame;
743 }
744
745 pending_battle_action = nullptr;
746 RemoveCurrentAction();
747 battle_message_window->Clear();
748
749 SetSceneActionSubState(ePreAction);
750 return SceneActionReturn::eContinueThisFrame;
751 }
752
753 if (scene_action_substate == ePost) {
754 // Everybody acted
755 actor_index = 0;
756 first_strike = false;
757
758 SetState(State_SelectOption);
759 return SceneActionReturn::eWaitTillNextFrame;
760 }
761 return SceneActionReturn::eWaitTillNextFrame;
762 }
763
ProcessSceneActionVictory()764 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionVictory() {
765 enum SubState {
766 eBegin = 0,
767 eEnd = 1,
768 };
769
770 if (scene_action_substate == eBegin) {
771 ResetWindows(true);
772 battle_message_window->Clear();
773 battle_message_window->SetVisible(true);
774
775 int exp = Main_Data::game_enemyparty->GetExp();
776 int money = Main_Data::game_enemyparty->GetMoney();
777 std::vector<int> drops;
778 Main_Data::game_enemyparty->GenerateDrops(drops);
779
780 auto pm = PendingMessage();
781 pm.SetEnableFace(false);
782
783 pm.SetWordWrapped(Player::IsRPG2kE());
784 pm.PushLine(ToString(lcf::Data::terms.victory) + Player::escape_symbol + "|");
785
786 std::stringstream ss;
787 if (exp > 0) {
788 PushExperienceGainedMessage(pm, exp);
789 }
790 if (money > 0) {
791 PushGoldReceivedMessage(pm, money);
792 }
793 PushItemRecievedMessages(pm, drops);
794
795 Main_Data::game_system->BgmPlay(Main_Data::game_system->GetSystemBGM(Main_Data::game_system->BGM_Victory));
796
797 // Update attributes
798 std::vector<Game_Battler*> ally_battlers;
799 Main_Data::game_party->GetActiveBattlers(ally_battlers);
800
801 pm.PushPageEnd();
802
803 for (auto& ally: ally_battlers) {
804 Game_Actor* actor = static_cast<Game_Actor*>(ally);
805 actor->ChangeExp(actor->GetExp() + exp, &pm);
806 }
807 Main_Data::game_party->GainGold(money);
808 for (auto& item: drops) {
809 Main_Data::game_party->AddItem(item, 1);
810 }
811
812 Game_Message::SetPendingMessage(std::move(pm));
813
814 SetSceneActionSubState(eEnd);
815 return SceneActionReturn::eContinueThisFrame;
816 }
817
818 EndBattle(BattleResult::Victory);
819 return SceneActionReturn::eWaitTillNextFrame;
820 }
821
ProcessSceneActionDefeat()822 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionDefeat() {
823 enum SubState {
824 eBegin = 0,
825 eEnd = 1,
826 };
827
828 if (scene_action_substate == eBegin) {
829 ResetWindows(true);
830 battle_message_window->Clear();
831 battle_message_window->SetVisible(true);
832
833 Main_Data::game_system->SetMessagePositionFixed(true);
834 Main_Data::game_system->SetMessagePosition(2);
835 Main_Data::game_system->SetMessageTransparent(false);
836
837 auto pm = PendingMessage();
838 pm.SetEnableFace(false);
839
840 pm.SetWordWrapped(Player::IsRPG2kE());
841
842 pm.PushLine(ToString(lcf::Data::terms.defeat));
843
844 Main_Data::game_system->BgmPlay(Main_Data::game_system->GetSystemBGM(Main_Data::game_system->BGM_GameOver));
845
846 Game_Message::SetPendingMessage(std::move(pm));
847 SetSceneActionSubState(eEnd);
848
849 return SceneActionReturn::eContinueThisFrame;
850 }
851
852 EndBattle(BattleResult::Defeat);
853 return SceneActionReturn::eWaitTillNextFrame;
854 }
855
ProcessSceneActionEscape()856 Scene_Battle_Rpg2k::SceneActionReturn Scene_Battle_Rpg2k::ProcessSceneActionEscape() {
857 enum SubState {
858 eBegin = 0,
859 eSuccess = 1,
860 eFailure = 2,
861 };
862
863 if (scene_action_substate == eBegin) {
864 ResetWindows(true);
865 battle_message_window->Clear();
866 battle_message_window->SetVisible(true);
867
868 auto next_ss = TryEscape() ? eSuccess : eFailure;
869
870 if (next_ss == eSuccess) {
871 battle_message_window->Push(lcf::Data::terms.escape_success);
872 } else {
873 battle_message_window->Push(lcf::Data::terms.escape_failure);
874 }
875 SetWait(10, 60);
876 SetSceneActionSubState(next_ss);
877 return SceneActionReturn::eContinueThisFrame;
878 }
879
880 if (scene_action_substate == eSuccess) {
881 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Escape));
882
883 EndBattle(BattleResult::Escape);
884 return SceneActionReturn::eContinueThisFrame;
885 }
886
887 if (scene_action_substate == eFailure) {
888 SetState(State_Battle);
889 NextTurn();
890
891 CreateEnemyActions();
892 CreateExecutionOrder();
893 return SceneActionReturn::eContinueThisFrame;
894 }
895 return SceneActionReturn::eWaitTillNextFrame;
896 }
897
SetBattleActionState(BattleActionState state)898 void Scene_Battle_Rpg2k::SetBattleActionState(BattleActionState state) {
899 battle_action_state = state;
900 SetBattleActionSubState(0);
901 }
902
SetBattleActionSubState(int substate,bool reset_index)903 void Scene_Battle_Rpg2k::SetBattleActionSubState(int substate, bool reset_index) {
904 battle_action_substate = substate;
905 if (reset_index) {
906 battle_action_substate_index = 0;
907 }
908 }
909
ProcessBattleAction(Game_BattleAlgorithm::AlgorithmBase * action)910 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleAction(Game_BattleAlgorithm::AlgorithmBase* action) {
911 if (action == nullptr) {
912 Output::Warning("ProcessBattleAction: Invalid battle action");
913 Output::Warning("Please report a bug!");
914 return BattleActionReturn::eFinished;
915 }
916
917 #ifdef EP_DEBUG_BATTLE2K_STATE_MACHINE
918 static int last_state = -1;
919 static int last_substate = -1;
920 static int last_substate_index = -1;
921 if (battle_action_state != last_state || battle_action_substate != last_substate || battle_action_substate_index != last_substate_index) {
922 Output::Debug("Battle2k ProcessBattleAction({}, {},{},{}) frames={}", action->GetSource()->GetName(), battle_action_state, battle_action_substate, battle_action_substate_index, Main_Data::game_system->GetFrameCounter());
923 last_state = battle_action_state;
924 last_substate = battle_action_substate;
925 last_substate_index = battle_action_substate_index;
926 }
927 #endif
928
929 switch (battle_action_state) {
930 case BattleActionState_Begin:
931 return ProcessBattleActionBegin(action);
932 case BattleActionState_Usage:
933 return ProcessBattleActionUsage(action);
934 case BattleActionState_Animation:
935 return ProcessBattleActionAnimation(action);
936 case BattleActionState_AnimationReflect:
937 return ProcessBattleActionAnimationReflect(action);
938 case BattleActionState_Execute:
939 return ProcessBattleActionExecute(action);
940 case BattleActionState_Critical:
941 return ProcessBattleActionCritical(action);
942 case BattleActionState_Apply:
943 return ProcessBattleActionApply(action);
944 case BattleActionState_Failure:
945 return ProcessBattleActionFailure(action);
946 case BattleActionState_Damage:
947 return ProcessBattleActionDamage(action);
948 case BattleActionState_Params:
949 return ProcessBattleActionParamEffects(action);
950 case BattleActionState_States:
951 return ProcessBattleActionStateEffects(action);
952 case BattleActionState_Attributes:
953 return ProcessBattleActionAttributeEffects(action);
954 case BattleActionState_Finished:
955 return ProcessBattleActionFinished(action);
956 }
957
958 assert(false && "Invalid BattleActionState!");
959
960 return BattleActionReturn::eFinished;
961 }
962
ProcessBattleActionBegin(Game_BattleAlgorithm::AlgorithmBase * action)963 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionBegin(Game_BattleAlgorithm::AlgorithmBase* action) {
964 enum SubState {
965 eBegin = 0,
966 eShowMessage,
967 ePost,
968 };
969
970 auto* src = action->GetSource();
971
972 if (battle_action_substate == eBegin) {
973 assert(src->Exists());
974 battle_message_window->Clear();
975
976 bool show_message = false;
977 src->NextBattleTurn();
978
979 std::vector<int16_t> states_to_heal = src->BattleStateHeal();
980 src->ApplyConditions();
981
982 const lcf::rpg::State* pri_state = nullptr;
983 bool pri_was_healed = false;
984 for (size_t id = 1; id <= lcf::Data::states.size(); ++id) {
985 auto was_healed = std::find(states_to_heal.begin(), states_to_heal.end(), id) != states_to_heal.end();
986 if (!was_healed && !src->HasState(id)) {
987 continue;
988 }
989
990 auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, id);
991 if (!pri_state || state->priority >= pri_state->priority) {
992 pri_state = state;
993 pri_was_healed = was_healed;
994 }
995 }
996
997 if (pri_state != nullptr) {
998 StringView msg = pri_was_healed
999 ? pri_state->message_recovery
1000 : pri_state->message_affected;
1001
1002 // RPG_RT behavior:
1003 // If state was healed, always prints.
1004 // If state is inflicted, only prints if msg not empty.
1005 if (pri_was_healed || !msg.empty()) {
1006 show_message = true;
1007 pending_message = ToString(msg);
1008 }
1009 }
1010
1011 if (action->GetType() != Game_BattleAlgorithm::Type::None || show_message) {
1012 action->GetSource()->Flash(31, 31, 31, 10, 10);
1013 }
1014
1015 if (show_message) {
1016 SetWait(4,4);
1017 SetBattleActionSubState(eShowMessage);
1018 return BattleActionReturn::eContinue;
1019 }
1020 battle_action_substate = ePost;
1021 }
1022
1023 if (battle_action_substate == eShowMessage) {
1024 battle_message_window->PushWithSubject(std::move(pending_message), action->GetSource()->GetName());
1025 SetWait(20, 60);
1026 pending_message.clear();
1027 SetBattleActionSubState(ePost);
1028 return BattleActionReturn::eContinue;
1029 }
1030
1031 if (battle_action_substate == ePost) {
1032 battle_message_window->Clear();
1033
1034 if (action->GetType() == Game_BattleAlgorithm::Type::None) {
1035 SetBattleActionState(BattleActionState_Finished);
1036 return BattleActionReturn::eContinue;
1037 }
1038
1039 SetWait(4,4);
1040 }
1041
1042 SetBattleActionState(BattleActionState_Usage);
1043 return BattleActionReturn::eContinue;
1044 }
1045
1046
ProcessBattleActionUsage(Game_BattleAlgorithm::AlgorithmBase * action)1047 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionUsage(Game_BattleAlgorithm::AlgorithmBase* action) {
1048 enum SubState {
1049 eBegin = 0,
1050 eMessages,
1051 eLastMessage,
1052 };
1053
1054 if (battle_action_substate == eBegin) {
1055 action->Start();
1056 battle_message_window->Clear();
1057
1058 pending_message = action->GetStartMessage(0);
1059
1060 SetBattleActionSubState(eMessages);
1061 }
1062
1063 if (battle_action_substate == eMessages) {
1064 if (!pending_message.empty()) {
1065 battle_message_window->Push(std::move(pending_message));
1066 battle_message_window->ScrollToEnd();
1067
1068 pending_message = action->GetStartMessage(++battle_action_substate_index);
1069
1070 if (!pending_message.empty()) {
1071 SetWaitForUsage(action->GetType(), 0);
1072 return BattleActionReturn::eContinue;
1073 }
1074 }
1075
1076 SetBattleActionSubState(eLastMessage);
1077 }
1078
1079 if (battle_action_substate == eLastMessage) {
1080 battle_action_start_index = battle_message_window->GetLineCount();
1081
1082 auto* se = action->GetStartSe();
1083 if (se) {
1084 Main_Data::game_system->SePlay(*se);
1085 }
1086 }
1087
1088 SetBattleActionState(BattleActionState_Animation);
1089 return BattleActionReturn::eContinue;
1090 }
1091
ProcessBattleActionAnimation(Game_BattleAlgorithm::AlgorithmBase * action)1092 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionAnimation(Game_BattleAlgorithm::AlgorithmBase* action) {
1093 return ProcessBattleActionAnimationImpl(action, false);
1094 }
1095
ProcessBattleActionAnimationReflect(Game_BattleAlgorithm::AlgorithmBase * action)1096 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionAnimationReflect(Game_BattleAlgorithm::AlgorithmBase* action) {
1097 return ProcessBattleActionAnimationImpl(action, true);
1098 }
1099
ProcessBattleActionAnimationImpl(Game_BattleAlgorithm::AlgorithmBase * action,bool reflect)1100 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionAnimationImpl(Game_BattleAlgorithm::AlgorithmBase* action, bool reflect) {
1101 int frames = 0;
1102 while(1) {
1103 const int cur_anim = action->GetAnimationId(battle_action_substate_index);
1104 ++battle_action_substate_index;
1105 int next_anim = 0;
1106
1107 if (cur_anim) {
1108 if (action->GetTarget()->GetType() == Game_Battler::Type_Enemy) {
1109 frames = action->PlayAnimation(cur_anim);
1110 } else {
1111 frames = action->PlayAnimation(cur_anim, true, 40);
1112 }
1113 next_anim = action->GetAnimationId(battle_action_substate_index);
1114 }
1115
1116 if (!next_anim) {
1117 break;
1118 }
1119
1120 if (frames) {
1121 SetWait(frames, frames);
1122 return BattleActionReturn::eContinue;
1123 }
1124 }
1125
1126 if (!reflect) {
1127 // Wait for last start message and last animation.
1128 SetWaitForUsage(action->GetType(), frames);
1129
1130 // EasyRPG extension: Support 2k3 reflect feature in 2k battle system.
1131 if (action->ReflectTargets()) {
1132 SetBattleActionState(BattleActionState_AnimationReflect);
1133 return BattleActionReturn::eContinue;
1134 }
1135 } else {
1136 // Wait for reflected animation - no message.
1137 SetWait(frames, frames);
1138 }
1139
1140 SetBattleActionState(BattleActionState_Execute);
1141 return BattleActionReturn::eContinue;
1142 }
1143
ProcessBattleActionExecute(Game_BattleAlgorithm::AlgorithmBase * action)1144 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionExecute(Game_BattleAlgorithm::AlgorithmBase* action) {
1145 action->Execute();
1146 if (action->GetType() == Game_BattleAlgorithm::Type::Normal
1147 || action->GetType() == Game_BattleAlgorithm::Type::SelfDestruct) {
1148 SetWait(4,4);
1149 if (action->IsSuccess() && action->IsCriticalHit()) {
1150 SetBattleActionState(BattleActionState_Critical);
1151 return BattleActionReturn::eContinue;
1152 }
1153 }
1154 SetBattleActionState(BattleActionState_Apply);
1155 return BattleActionReturn::eContinue;
1156 }
1157
ProcessBattleActionCritical(Game_BattleAlgorithm::AlgorithmBase * action)1158 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionCritical(Game_BattleAlgorithm::AlgorithmBase* action) {
1159 battle_message_window->Push(BattleMessage::GetCriticalHitMessage(*action->GetSource(), *action->GetTarget()));
1160 battle_message_window->ScrollToEnd();
1161 SetWait(10, 30);
1162
1163 SetBattleActionState(BattleActionState_Apply);
1164 return BattleActionReturn::eContinue;
1165 }
1166
ProcessBattleActionApply(Game_BattleAlgorithm::AlgorithmBase * action)1167 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionApply(Game_BattleAlgorithm::AlgorithmBase* action) {
1168 action->ApplyCustomEffect();
1169 action->ApplySwitchEffect();
1170
1171 battle_action_results_index = battle_message_window->GetLineCount();
1172
1173 if (!action->IsSuccess()) {
1174 SetBattleActionState(BattleActionState_Failure);
1175 return BattleActionReturn::eContinue;
1176 }
1177
1178 auto* target = action->GetTarget();
1179
1180 if (!target) {
1181 SetBattleActionState(BattleActionState_Finished);
1182 return BattleActionReturn::eContinue;
1183 }
1184
1185 SetBattleActionState(BattleActionState_Damage);
1186 return BattleActionReturn::eContinue;
1187 }
1188
ProcessBattleActionFailure(Game_BattleAlgorithm::AlgorithmBase * action)1189 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionFailure(Game_BattleAlgorithm::AlgorithmBase* action) {
1190 enum SubState {
1191 eBegin = 0,
1192 eProcess,
1193 };
1194
1195 if (battle_action_substate == eBegin) {
1196 SetWait(4,4);
1197 SetBattleActionSubState(eProcess);
1198 return BattleActionReturn::eContinue;
1199 }
1200
1201 auto* se = action->GetFailureSe();
1202 if (se) {
1203 Main_Data::game_system->SePlay(*se);
1204 }
1205
1206 const auto& fail_msg = action->GetFailureMessage();
1207 battle_message_window->Push(fail_msg);
1208 battle_message_window->ScrollToEnd();
1209 SetWait(20, 60);
1210
1211 SetBattleActionState(BattleActionState_Finished);
1212 return BattleActionReturn::eContinue;
1213 }
1214
ProcessBattleActionDamage(Game_BattleAlgorithm::AlgorithmBase * action)1215 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionDamage(Game_BattleAlgorithm::AlgorithmBase* action) {
1216 enum SubState {
1217 eBegin = 0,
1218 eMessage,
1219 eApply,
1220 ePreStates,
1221 eStates,
1222 ePost,
1223 };
1224
1225 if (battle_action_substate == eBegin) {
1226 if (!action->IsAffectHp() || action->GetAffectedHp() > 0 || ((action->IsPositive() || action->IsAbsorbHp()) && action->GetAffectedHp() == 0)) {
1227 SetBattleActionState(BattleActionState_Params);
1228 return BattleActionReturn::eContinue;
1229 }
1230
1231 SetWait(4,4);
1232 SetBattleActionSubState(eMessage);
1233 return BattleActionReturn::eContinue;
1234 }
1235
1236 if (battle_action_substate == eMessage) {
1237 auto* target = action->GetTarget();
1238 assert(target);
1239 auto dmg = action->GetAffectedHp();
1240
1241 if (!action->IsAbsorbHp()) {
1242 if (target->GetType() == Game_Battler::Type_Ally) {
1243 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_AllyDamage));
1244 if (dmg < 0) {
1245 Main_Data::game_screen->ShakeOnce(3, 5, 8);
1246 }
1247 } else {
1248 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyDamage));
1249 }
1250 if (target->GetType() == Game_Battler::Type_Enemy) {
1251 static_cast<Game_Enemy*>(target)->SetBlinkTimer();
1252 }
1253 }
1254
1255 std::string msg;
1256 if (action->IsAbsorbHp()) {
1257 msg = BattleMessage::GetHpAbsorbedMessage(*action->GetTarget(), *target, -dmg);
1258 } else {
1259 if (dmg == 0) {
1260 msg = BattleMessage::GetUndamagedMessage(*target);
1261 } else {
1262 msg = BattleMessage::GetDamagedMessage(*target, -dmg);
1263 }
1264 }
1265
1266 battle_message_window->Push(msg);
1267 battle_message_window->ScrollToEnd();
1268 if (action->IsAbsorbHp()) {
1269 SetWait(20, 60);
1270 } else {
1271 SetWait(20, 40);
1272 }
1273
1274 SetBattleActionSubState(eApply);
1275 return BattleActionReturn::eContinue;
1276 }
1277
1278 if (battle_action_substate == eApply) {
1279 // Hp damage is delayed until after the message, so that the target death animation
1280 // occurs at the right time.
1281 action->ApplyHpEffect();
1282
1283 auto* target = action->GetTarget();
1284 assert(target);
1285 if (target->IsDead()) {
1286 ProcessBattleActionDeath(action);
1287 }
1288
1289 battle_action_dmg_index = battle_message_window->GetLineCount();
1290
1291 SetBattleActionSubState(ePreStates);
1292 return BattleActionReturn::eContinue;
1293 }
1294
1295 if (battle_action_substate == ePreStates) {
1296 auto* target = action->GetTarget();
1297 const auto& states = action->GetStateEffects();
1298 auto& idx = battle_action_substate_index;
1299 for (;idx < static_cast<int>(states.size()); ++idx) {
1300 auto& se = states[idx];
1301 auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, se.state_id);
1302 if (!state || se.effect != Game_BattleAlgorithm::StateEffect::HealedByAttack) {
1303 continue;
1304 }
1305 action->ApplyStateEffect(se);
1306 pending_message = BattleMessage::GetStateRecoveryMessage(*target, *state);
1307 ++battle_action_substate_index;
1308
1309 battle_message_window->PopUntil(battle_action_dmg_index);
1310 battle_message_window->ScrollToEnd();
1311 SetWait(4,4);
1312
1313 SetBattleActionSubState(eStates, false);
1314 return BattleActionReturn::eContinue;
1315 }
1316 SetBattleActionSubState(ePost);
1317 return BattleActionReturn::eContinue;
1318 }
1319
1320 if (battle_action_substate == eStates) {
1321 battle_message_window->Push(pending_message);
1322 battle_message_window->ScrollToEnd();
1323 SetWait(20, 40);
1324
1325 SetBattleActionSubState(ePreStates, false);
1326 return BattleActionReturn::eContinue;
1327 }
1328
1329 if (battle_action_substate == ePost) {
1330 SetWait(0, 10);
1331 }
1332
1333 SetBattleActionState(BattleActionState_Params);
1334 return BattleActionReturn::eContinue;
1335 }
1336
ProcessBattleActionParamEffects(Game_BattleAlgorithm::AlgorithmBase * action)1337 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionParamEffects(Game_BattleAlgorithm::AlgorithmBase* action) {
1338 enum SubState {
1339 ePreHp,
1340 eHp,
1341 ePreSp,
1342 eSp,
1343 ePreAtk,
1344 eAtk,
1345 ePreDef,
1346 eDef,
1347 ePreSpi,
1348 eSpi,
1349 ePreAgi,
1350 eAgi,
1351 eDone
1352 };
1353
1354 const auto next_state = BattleActionState_States;
1355 auto* source = action->GetSource();
1356 auto* target = action->GetTarget();
1357
1358 // All of the "Pre" states are even numbers, so catch all Pre here.
1359 if ((battle_action_substate & 1) == 0) {
1360 pending_message.clear();
1361
1362 auto checkNext = [&]() {
1363 if (pending_message.empty()) {
1364 SetBattleActionSubState(battle_action_substate + 2);
1365 }
1366 };
1367
1368 if (battle_action_substate == ePreHp) {
1369 // Damage is handled by Damage state, so only check healing here.
1370 if (action->GetAffectedHp() > 0 && !action->IsRevived()) {
1371 auto hp = action->ApplyHpEffect();
1372 pending_message = BattleMessage::GetHpRecoveredMessage(*target, hp);
1373 }
1374 checkNext();
1375 }
1376
1377 if (battle_action_substate == ePreSp) {
1378 auto sp = action->ApplySpEffect();
1379 if (action->IsAbsorbSp()) {
1380 pending_message = BattleMessage::GetSpAbsorbedMessage(*source, *target, -sp);
1381 } else {
1382 if (sp > 0) {
1383 pending_message = BattleMessage::GetSpRecoveredMessage(*target, sp);
1384 }
1385 if (sp < 0) {
1386 pending_message = BattleMessage::GetSpReduceMessage(*target, -sp);
1387 }
1388 }
1389 checkNext();
1390 }
1391
1392 if (battle_action_substate == ePreAtk) {
1393 auto atk = action->ApplyAtkEffect();
1394 if (atk != 0) {
1395 if (action->IsAbsorbAtk()) {
1396 pending_message = BattleMessage::GetAtkAbsorbedMessage(*source, *target, -atk);
1397 } else {
1398 pending_message = BattleMessage::GetAtkChangeMessage(*target, atk);
1399 }
1400 }
1401 checkNext();
1402 }
1403
1404 if (battle_action_substate == ePreDef) {
1405 auto def = action->ApplyDefEffect();
1406 if (def != 0) {
1407 if (action->IsAbsorbDef()) {
1408 pending_message = BattleMessage::GetDefAbsorbedMessage(*source, *target, -def);
1409 } else {
1410 pending_message = BattleMessage::GetDefChangeMessage(*target, def);
1411 }
1412 }
1413 checkNext();
1414 }
1415
1416 if (battle_action_substate == ePreSpi) {
1417 auto spi = action->ApplySpiEffect();
1418 if (spi != 0) {
1419 if (action->IsAbsorbSpi()) {
1420 pending_message = BattleMessage::GetSpiAbsorbedMessage(*source, *target, -spi);
1421 } else {
1422 pending_message = BattleMessage::GetSpiChangeMessage(*target, spi);
1423 }
1424 }
1425 checkNext();
1426 }
1427
1428 if (battle_action_substate == ePreAgi) {
1429 auto agi = action->ApplyAgiEffect();
1430 if (agi != 0) {
1431 if (action->IsAbsorbAgi()) {
1432 pending_message = BattleMessage::GetAgiAbsorbedMessage(*source, *target, -agi);
1433 } else {
1434 pending_message = BattleMessage::GetAgiChangeMessage(*target, agi);
1435 }
1436 }
1437 checkNext();
1438 }
1439
1440 if (!pending_message.empty()) {
1441 battle_message_window->PopUntil(battle_action_results_index);
1442 battle_message_window->ScrollToEnd();
1443 SetWait(4,4);
1444
1445 SetBattleActionSubState(battle_action_substate + 1, false);
1446 return BattleActionReturn::eContinue;
1447 }
1448 }
1449
1450 // Use >= here so that the each "pre" stage above can just call
1451 // checkNext() to increment +2 without worrying about overflowing
1452 // past eDone.
1453 if (battle_action_substate >= eDone) {
1454 SetBattleActionState(next_state);
1455 return BattleActionReturn::eContinue;
1456 }
1457
1458 // All of the normal states are odd numbers.
1459 if ((battle_action_substate & 1) != 0) {
1460 battle_message_window->Push(pending_message);
1461 battle_message_window->ScrollToEnd();
1462 SetWait(20, 60);
1463
1464 SetBattleActionSubState(battle_action_substate + 1);
1465 return BattleActionReturn::eContinue;
1466 }
1467
1468 SetBattleActionState(next_state);
1469 return BattleActionReturn::eContinue;
1470 }
1471
ProcessBattleActionStateEffects(Game_BattleAlgorithm::AlgorithmBase * action)1472 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionStateEffects(Game_BattleAlgorithm::AlgorithmBase* action) {
1473 enum SubState {
1474 eApply,
1475 ePreWait,
1476 eMessage,
1477 eDone
1478 };
1479
1480 const auto next_state = BattleActionState_Attributes;
1481
1482 auto* target = action->GetTarget();
1483
1484 if (battle_action_substate == eApply) {
1485 pending_message.clear();
1486
1487 const auto was_dead = target->IsDead();
1488 const auto& states = action->GetStateEffects();
1489 auto& idx = battle_action_substate_index;
1490
1491 if (idx >= static_cast<int>(states.size())) {
1492 SetBattleActionState(next_state);
1493 return BattleActionReturn::eContinue;
1494 }
1495
1496 for (;idx < (int)states.size(); ++idx) {
1497 auto& se = states[idx];
1498 // Already applied earlier after damage
1499 if (se.effect == Game_BattleAlgorithm::StateEffect::HealedByAttack) {
1500 ++idx;
1501 return BattleActionReturn::eContinue;
1502 }
1503
1504 auto* state = lcf::ReaderUtil::GetElement(lcf::Data::states, se.state_id);
1505 if (!state) {
1506 continue;
1507 }
1508
1509 action->ApplyStateEffect(se);
1510 switch (se.effect) {
1511 case Game_BattleAlgorithm::StateEffect::Inflicted:
1512 pending_message = BattleMessage::GetStateInflictMessage(*target, *state);
1513 break;
1514 case Game_BattleAlgorithm::StateEffect::Healed:
1515 pending_message = BattleMessage::GetStateRecoveryMessage(*target, *state);
1516 break;
1517 case Game_BattleAlgorithm::StateEffect::AlreadyInflicted:
1518 pending_message = BattleMessage::GetStateAlreadyMessage(*target, *state);
1519 break;
1520 default:
1521 break;
1522 }
1523
1524 if ((!was_dead && target->IsDead()) || !pending_message.empty()) {
1525 break;
1526 }
1527 }
1528
1529 battle_message_window->PopUntil(battle_action_results_index);
1530 battle_message_window->ScrollToEnd();
1531
1532 // If we were killed by state
1533 if (!was_dead && target->IsDead()) {
1534 ProcessBattleActionDeath(action);
1535 SetBattleActionState(next_state);
1536 // FIXES an RPG_RT bug where RPG_RT does an extra SetWait(4,4), SetWait(20,60) on death state infliction
1537 return BattleActionReturn::eContinue;
1538 }
1539 SetBattleActionSubState(ePreWait, false);
1540 return BattleActionReturn::eContinue;
1541 }
1542
1543 if (battle_action_substate == ePreWait) {
1544 SetWait(4,4);
1545 SetBattleActionSubState(eMessage, false);
1546 return BattleActionReturn::eContinue;
1547 }
1548
1549 if (battle_action_substate == eMessage) {
1550 battle_message_window->Push(pending_message);
1551 battle_message_window->ScrollToEnd();
1552 SetWait(20, 60);
1553
1554 // Process the next state
1555 ++battle_action_substate_index;
1556 SetBattleActionSubState(eApply, false);
1557 return BattleActionReturn::eContinue;
1558 }
1559
1560 SetBattleActionState(next_state);
1561 return BattleActionReturn::eContinue;
1562 }
1563
ProcessBattleActionAttributeEffects(Game_BattleAlgorithm::AlgorithmBase * action)1564 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionAttributeEffects(Game_BattleAlgorithm::AlgorithmBase* action) {
1565 enum SubState {
1566 eApply,
1567 eMessage,
1568 };
1569
1570 const auto next_state = BattleActionState_Finished;
1571
1572 // All of the "Pre" states are even numbers, so catch all Pre here.
1573 if (battle_action_substate == eApply) {
1574 pending_message.clear();
1575
1576 const auto& attrs = action->GetShiftedAttributes();
1577 auto& idx = battle_action_substate_index;
1578 if (idx >= static_cast<int>(attrs.size())) {
1579 SetBattleActionState(next_state);
1580 return BattleActionReturn::eContinue;
1581 }
1582
1583 for (;idx < (int)attrs.size(); ++idx) {
1584 auto& ae = attrs[battle_action_substate_index];
1585 auto shifted = action->ApplyAttributeShiftEffect(ae);
1586 if (shifted != 0) {
1587 auto* attr = lcf::ReaderUtil::GetElement(lcf::Data::attributes, ae.attr_id);
1588 pending_message = BattleMessage::GetAttributeShiftMessage(*action->GetTarget(), shifted, *attr);
1589 break;
1590 }
1591 }
1592
1593 battle_message_window->PopUntil(battle_action_results_index);
1594 battle_message_window->ScrollToEnd();
1595 SetWait(4,4);
1596
1597 SetBattleActionSubState(eMessage, false);
1598 return BattleActionReturn::eContinue;
1599 }
1600
1601 // All of the normal states are odd numbers.
1602 if (battle_action_substate == eMessage) {
1603 battle_message_window->Push(pending_message);
1604 battle_message_window->ScrollToEnd();
1605 SetWait(20, 60);
1606
1607 ++battle_action_substate_index;
1608 SetBattleActionSubState(eApply, false);
1609 return BattleActionReturn::eContinue;
1610 }
1611
1612 SetBattleActionState(next_state);
1613 return BattleActionReturn::eContinue;
1614 }
1615
ProcessBattleActionDeath(Game_BattleAlgorithm::AlgorithmBase * action)1616 void Scene_Battle_Rpg2k::ProcessBattleActionDeath(Game_BattleAlgorithm::AlgorithmBase* action) {
1617 auto* target = action->GetTarget();
1618 assert(target);
1619
1620 battle_message_window->Push(BattleMessage::GetDeathMessage(*action->GetTarget()));
1621 battle_message_window->ScrollToEnd();
1622 SetWait(36, 60);
1623
1624 if (target->GetType() == Game_Battler::Type_Enemy) {
1625 static_cast<Game_Enemy*>(target)->SetDeathTimer();
1626 Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_EnemyKill));
1627 }
1628 }
1629
ProcessBattleActionFinished(Game_BattleAlgorithm::AlgorithmBase * action)1630 Scene_Battle_Rpg2k::BattleActionReturn Scene_Battle_Rpg2k::ProcessBattleActionFinished(Game_BattleAlgorithm::AlgorithmBase* action) {
1631 if (action->RepeatNext(true) || action->TargetNext()) {
1632 // Clear the console for the next target
1633 battle_message_window->PopUntil(battle_action_start_index);
1634 battle_message_window->ScrollToEnd();
1635
1636 SetBattleActionState(BattleActionState_Execute);
1637 return BattleActionReturn::eContinue;
1638 }
1639
1640 battle_message_window->Clear();
1641 action->ProcessPostActionSwitches();
1642 return BattleActionReturn::eFinished;
1643 }
1644
SelectNextActor(bool auto_battle)1645 void Scene_Battle_Rpg2k::SelectNextActor(bool auto_battle) {
1646 std::vector<Game_Actor*> allies = Main_Data::game_party->GetActors();
1647
1648 if ((size_t)actor_index == allies.size()) {
1649 // All actor actions decided, player turn ends
1650 SetState(State_Battle);
1651 NextTurn();
1652
1653 CreateEnemyActions();
1654 CreateExecutionOrder();
1655
1656 return;
1657 }
1658
1659 active_actor = allies[actor_index];
1660 status_window->SetIndex(actor_index);
1661 actor_index++;
1662
1663 Game_Battler* random_target = NULL;
1664
1665 if (!active_actor->CanAct()) {
1666 active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::None>(active_actor));
1667 battle_actions.push_back(active_actor);
1668 SelectNextActor(auto_battle);
1669 return;
1670 }
1671
1672 switch (active_actor->GetSignificantRestriction()) {
1673 case lcf::rpg::State::Restriction_attack_ally:
1674 random_target = Main_Data::game_party->GetRandomActiveBattler();
1675 break;
1676 case lcf::rpg::State::Restriction_attack_enemy:
1677 random_target = Main_Data::game_enemyparty->GetRandomActiveBattler();
1678 break;
1679 default:
1680 break;
1681 }
1682
1683 if (random_target) {
1684 // RPG_RT doesn't support "Attack All" weapons when battler is confused or provoked.
1685 active_actor->SetBattleAlgorithm(std::make_shared<Game_BattleAlgorithm::Normal>(active_actor, random_target));
1686 battle_actions.push_back(active_actor);
1687
1688 SelectNextActor(auto_battle);
1689 return;
1690 }
1691
1692 if (auto_battle || active_actor->GetAutoBattle()) {
1693 this->autobattle_algo->SetAutoBattleAction(*active_actor);
1694 assert(active_actor->GetBattleAlgorithm() != nullptr);
1695 battle_actions.push_back(active_actor);
1696
1697 SelectNextActor(auto_battle);
1698 return;
1699 }
1700
1701 SetState(Scene_Battle::State_SelectCommand);
1702 }
1703
SelectPreviousActor()1704 void Scene_Battle_Rpg2k::SelectPreviousActor() {
1705 std::vector<Game_Actor*> allies = Main_Data::game_party->GetActors();
1706
1707 if (allies[0] == active_actor) {
1708 SetState(State_SelectOption);
1709 actor_index = 0;
1710 return;
1711 }
1712
1713 actor_index--;
1714 active_actor = allies[actor_index];
1715
1716 battle_actions.back()->SetBattleAlgorithm(nullptr);
1717 battle_actions.pop_back();
1718
1719 if (!active_actor->IsControllable()) {
1720 SelectPreviousActor();
1721 return;
1722 }
1723
1724 SetState(State_SelectActor);
1725 }
1726
CreateExecutionOrder()1727 void Scene_Battle_Rpg2k::CreateExecutionOrder() {
1728 // Define random Agility. Must be done outside of the sort function because of the "strict weak ordering" property, so the sort is consistent
1729 for (auto battler : battle_actions) {
1730 int battle_order = battler->GetAgi() + Rand::GetRandomNumber(0, battler->GetAgi() / 4 + 3);
1731 if (battler->GetBattleAlgorithm()->GetType() == Game_BattleAlgorithm::Type::Normal && battler->HasPreemptiveAttack()) {
1732 // RPG_RT sets this value
1733 battle_order += 9999;
1734 }
1735 battler->SetBattleOrderAgi(battle_order);
1736 }
1737 std::sort(battle_actions.begin(), battle_actions.end(),
1738 [](Game_Battler* l, Game_Battler* r) {
1739 return l->GetBattleOrderAgi() > r->GetBattleOrderAgi();
1740 });
1741
1742 for (const auto& battler : battle_actions) {
1743 if (std::count(battle_actions.begin(), battle_actions.end(), battler) > 1) {
1744 Output::Warning("CreateExecutionOrder: Battler {} ({}) has multiple battle actions", battler->GetId(), battler->GetName());
1745 Output::Warning("Please report a bug!");
1746 break;
1747 }
1748 }
1749 }
1750
CreateEnemyActions()1751 void Scene_Battle_Rpg2k::CreateEnemyActions() {
1752 if (first_strike) {
1753 return;
1754 }
1755
1756 for (auto* enemy : Main_Data::game_enemyparty->GetEnemies()) {
1757 if (!EnemyAi::SetStateRestrictedAction(*enemy)) {
1758 enemyai_algo->SetEnemyAiAction(*enemy);
1759 }
1760 assert(enemy->GetBattleAlgorithm() != nullptr);
1761 ActionSelectedCallback(enemy);
1762 }
1763 }
1764
ActionSelectedCallback(Game_Battler * for_battler)1765 void Scene_Battle_Rpg2k::ActionSelectedCallback(Game_Battler* for_battler) {
1766 Scene_Battle::ActionSelectedCallback(for_battler);
1767
1768 if (for_battler->GetType() == Game_Battler::Type_Ally) {
1769 SetState(State_SelectActor);
1770 }
1771 }
1772
SetWait(int min_wait,int max_wait)1773 void Scene_Battle_Rpg2k::SetWait(int min_wait, int max_wait) {
1774 #if defined(EP_DEBUG_BATTLE2K_MESSAGE) || defined(EP_DEBUG_BATTLE2K_STATE_MACHINE)
1775 Output::Debug("Battle2k Wait({},{}) frame={}", min_wait, max_wait, Main_Data::game_system->GetFrameCounter());
1776 #endif
1777 battle_action_wait = max_wait;
1778 battle_action_min_wait = max_wait - min_wait;
1779 }
1780
SetWaitForUsage(Game_BattleAlgorithm::Type type,int anim_frames)1781 void Scene_Battle_Rpg2k::SetWaitForUsage(Game_BattleAlgorithm::Type type, int anim_frames) {
1782 int min_wait = 0;
1783 int max_wait = 0;
1784 switch (type) {
1785 case Game_BattleAlgorithm::Type::Normal:
1786 min_wait = 20;
1787 max_wait = 40;
1788 break;
1789 case Game_BattleAlgorithm::Type::Escape:
1790 min_wait = 36;
1791 max_wait = 60;
1792 break;
1793 case Game_BattleAlgorithm::Type::None:
1794 case Game_BattleAlgorithm::Type::DoNothing:
1795 min_wait = max_wait = 0;
1796 break;
1797 default:
1798 min_wait = 20;
1799 max_wait = 60;
1800 break;
1801 }
1802 SetWait(std::max(min_wait, anim_frames), std::max(max_wait, anim_frames));
1803 }
1804
CheckWait()1805 bool Scene_Battle_Rpg2k::CheckWait() {
1806 if (battle_action_wait > 0) {
1807 if (Input::IsPressed(Input::CANCEL)) {
1808 return false;
1809 }
1810 --battle_action_wait;
1811 if (battle_action_wait > battle_action_min_wait) {
1812 return false;
1813 }
1814 if (!Input::IsPressed(Input::DECISION)
1815 && !Input::IsPressed(Input::SHIFT)
1816 && battle_action_wait > 0) {
1817 return false;
1818 }
1819 battle_action_wait = 0;
1820 }
1821 return true;
1822 }
1823
PushExperienceGainedMessage(PendingMessage & pm,int exp)1824 void Scene_Battle_Rpg2k::PushExperienceGainedMessage(PendingMessage& pm, int exp) {
1825 if (Player::IsRPG2kE()) {
1826 pm.PushLine(
1827 Utils::ReplacePlaceholders(
1828 lcf::Data::terms.exp_received,
1829 Utils::MakeArray('V', 'U'),
1830 Utils::MakeSvArray(std::to_string(exp), lcf::Data::terms.exp_short)
1831 ) + Player::escape_symbol + "."
1832 );
1833 }
1834 else {
1835 std::stringstream ss;
1836 ss << exp << lcf::Data::terms.exp_received << Player::escape_symbol << ".";
1837 pm.PushLine(ss.str());
1838 }
1839 }
1840
PushGoldReceivedMessage(PendingMessage & pm,int money)1841 void Scene_Battle_Rpg2k::PushGoldReceivedMessage(PendingMessage& pm, int money) {
1842
1843 if (Player::IsRPG2kE()) {
1844 pm.PushLine(
1845 Utils::ReplacePlaceholders(
1846 lcf::Data::terms.gold_recieved_a,
1847 Utils::MakeArray('V', 'U'),
1848 Utils::MakeSvArray(std::to_string(money), lcf::Data::terms.gold)
1849 ) + Player::escape_symbol + "."
1850 );
1851 }
1852 else {
1853 std::stringstream ss;
1854 ss << lcf::Data::terms.gold_recieved_a << " " << money << lcf::Data::terms.gold << lcf::Data::terms.gold_recieved_b << Player::escape_symbol << ".";
1855 pm.PushLine(ss.str());
1856 }
1857 }
1858
PushItemRecievedMessages(PendingMessage & pm,std::vector<int> drops)1859 void Scene_Battle_Rpg2k::PushItemRecievedMessages(PendingMessage& pm, std::vector<int> drops) {
1860 std::stringstream ss;
1861
1862 for (std::vector<int>::iterator it = drops.begin(); it != drops.end(); ++it) {
1863 const lcf::rpg::Item* item = lcf::ReaderUtil::GetElement(lcf::Data::items, *it);
1864 // No Output::Warning needed here, reported later when the item is added
1865 StringView item_name = item ? StringView(item->name) : StringView("??? BAD ITEM ???");
1866
1867 if (Player::IsRPG2kE()) {
1868 pm.PushLine(
1869 Utils::ReplacePlaceholders(
1870 lcf::Data::terms.item_recieved,
1871 Utils::MakeArray('S'),
1872 Utils::MakeSvArray(item_name)
1873 ) + Player::escape_symbol + "."
1874 );
1875 }
1876 else {
1877 ss.str("");
1878 ss << item_name << lcf::Data::terms.item_recieved << Player::escape_symbol << ".";
1879 pm.PushLine(ss.str());
1880 }
1881 }
1882 }
1883
CheckBattleEndConditions()1884 bool Scene_Battle_Rpg2k::CheckBattleEndConditions() {
1885 if (state == State_Defeat || Game_Battle::CheckLose()) {
1886 if (state != State_Defeat) {
1887 SetState(State_Defeat);
1888 }
1889 return true;
1890 }
1891
1892 if (state == State_Victory || Game_Battle::CheckWin()) {
1893 if (state != State_Victory) {
1894 SetState(State_Victory);
1895 }
1896 return true;
1897 }
1898
1899 return false;
1900 }
1901
1902