/
battle.cpp
1240 lines (989 loc) · 41.7 KB
/
battle.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2004-2010 by The Allacrost Project
// All Rights Reserved
//
// This code is licensed under the GNU GPL version 2. It is free software and
// you may modify it and/or redistribute it under the terms of this license.
// See http://www.gnu.org/copyleft/gpl.html for details.
////////////////////////////////////////////////////////////////////////////////
/** ****************************************************************************
*** \file battle.cpp
*** \author Tyler Olsen, roots@allacrost.org
*** \author Viljami Korhonen, mindflayer@allacrost.org
*** \author Corey Hoffstein, visage@allacrost.org
*** \author Andy Gardner, chopperdave@allacrost.org
*** \brief Source file for battle mode interface.
*** ***************************************************************************/
#include "engine/audio/audio.h"
#include "engine/input.h"
#include "engine/mode_manager.h"
#include "engine/script/script.h"
#include "engine/video/video.h"
#include "modes/pause.h"
#include "modes/battle/battle.h"
#include "modes/battle/battle_actors.h"
#include "modes/battle/battle_actions.h"
#include "modes/battle/battle_command.h"
#include "modes/battle/battle_dialogue.h"
#include "modes/battle/battle_finish.h"
#include "modes/battle/battle_sequence.h"
#include "modes/battle/battle_utils.h"
using namespace std;
using namespace hoa_utils;
using namespace hoa_audio;
using namespace hoa_video;
using namespace hoa_mode_manager;
using namespace hoa_input;
using namespace hoa_system;
using namespace hoa_global;
using namespace hoa_script;
using namespace hoa_pause;
using namespace hoa_battle::private_battle;
namespace hoa_battle {
bool BATTLE_DEBUG = false;
// Initialize static class variable
BattleMode* BattleMode::_current_instance = NULL;
namespace private_battle {
////////////////////////////////////////////////////////////////////////////////
// BattleMedia class
////////////////////////////////////////////////////////////////////////////////
// Filenames of the default music that is played when no specific music is requested
//@{
const char* DEFAULT_BATTLE_MUSIC = "mus/Confrontation.ogg";
const char* DEFAULT_VICTORY_MUSIC = "mus/Fanfare.ogg";
const char* DEFAULT_DEFEAT_MUSIC = "mus/Intermission.ogg";
//@}
BattleMedia::BattleMedia() {
if (!background_image.Load("img/backdrops/battle/desert_cave/desert_cave.png"))
PRINT_ERROR << "failed to load default background image" << endl;
if (stamina_icon_selected.Load("img/menus/stamina_icon_selected.png") == false)
PRINT_ERROR << "failed to load stamina icon selected image" << endl;
attack_point_indicator.SetDimensions(16.0f, 16.0f);
if (attack_point_indicator.LoadFromFrameGrid("img/icons/battle/attack_point_target.png", vector<uint32>(4, 100), 1, 4) == false)
PRINT_ERROR << "failed to load attack point indicator." << endl;
if (stamina_meter.Load("img/menus/stamina_bar.png") == false)
PRINT_ERROR << "failed to load time meter." << endl;
if (actor_selection_image.Load("img/icons/battle/character_selector.png") == false)
PRINT_ERROR << "unable to load player selector image" << endl;
if (character_selected_highlight.Load("img/menus/battle_character_selection.png") == false)
PRINT_ERROR << "failed to load character selection highlight image" << endl;
if (character_command_highlight.Load("img/menus/battle_character_command.png") == false)
PRINT_ERROR << "failed to load character command highlight image" << endl;
if (character_bar_covers.Load("img/menus/battle_character_bars.png") == false)
PRINT_ERROR << "failed to load character bars image" << endl;
if (bottom_menu_image.Load("img/menus/battle_bottom_menu.png") == false)
PRINT_ERROR << "failed to load bottom menu image" << endl;
if (swap_icon.Load("img/icons/battle/swap_icon.png") == false)
PRINT_ERROR << "failed to load swap icon" << endl;
if (swap_card.Load("img/icons/battle/swap_card.png") == false)
PRINT_ERROR << "failed to load swap card" << endl;
if (ImageDescriptor::LoadMultiImageFromElementGrid(character_action_buttons, "img/menus/battle_command_buttons.png", 2, 5) == false)
PRINT_ERROR << "failed to load character action buttons" << endl;
if (ImageDescriptor::LoadMultiImageFromElementGrid(_target_type_icons, "img/icons/effects/targets.png", 1, 8) == false)
PRINT_ERROR << "failed to load character action buttons" << endl;
if (ImageDescriptor::LoadMultiImageFromElementSize(_status_icons, "img/icons/effects/status.png", 25, 25) == false)
PRINT_ERROR << "failed to load status icon images" << endl;
if (victory_music.LoadAudio(DEFAULT_VICTORY_MUSIC) == false)
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load victory music file: " << DEFAULT_VICTORY_MUSIC << endl;
if (defeat_music.LoadAudio(DEFAULT_DEFEAT_MUSIC) == false)
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load defeat music file: " << DEFAULT_DEFEAT_MUSIC << endl;
if (confirm_sound.LoadAudio("snd/confirm.wav") == false)
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load confirm sound" << endl;
if (cancel_sound.LoadAudio("snd/cancel.wav") == false)
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load cancel sound" << endl;
if (cursor_sound.LoadAudio("snd/confirm.wav") == false)
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load cursor sound" << endl;
if (invalid_sound.LoadAudio("snd/cancel.wav") == false)
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load invalid sound" << endl;
if (finish_sound.LoadAudio("snd/confirm.wav") == false)
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load finish sound" << endl;
// Determine which status effects correspond to which icons and store the result in the _status_indices container
ReadScriptDescriptor& script_file = GlobalManager->GetStatusEffectsScript();
vector<int32> status_types;
script_file.ReadTableKeys(status_types);
for (uint32 i = 0; i < status_types.size(); i++) {
GLOBAL_STATUS status = static_cast<GLOBAL_STATUS>(status_types[i]);
// Check for duplicate entries of the same status effect
if (_status_indeces.find(status) != _status_indeces.end()) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "duplicate entry found in file " << script_file.GetFilename() <<
" for status type: " << status_types[i] << endl;
continue;
}
script_file.OpenTable(status_types[i]);
if (script_file.DoesIntExist("icon_index") == true) {
uint32 icon_index = script_file.ReadUInt("icon_index");
_status_indeces.insert(pair<GLOBAL_STATUS, uint32>(status, icon_index));
}
else {
IF_PRINT_WARNING(BATTLE_DEBUG) << "no icon_index member was found for status effect: " << status_types[i] << endl;
}
script_file.CloseTable();
}
}
BattleMedia::~BattleMedia() {
battle_music.FreeAudio();
victory_music.FreeAudio();
defeat_music.FreeAudio();
}
void BattleMedia::Update() {
attack_point_indicator.Update();
}
void BattleMedia::SetBackgroundImage(const string& filename) {
if (background_image.Load(filename) == false) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load background image: " << filename << endl;
}
}
void BattleMedia::SetBattleMusic(const string& filename) {
if (battle_music.LoadAudio(filename) == false) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load music file: " << filename << endl;
}
}
StillImage* BattleMedia::GetCharacterActionButton(uint32 index) {
if (index >= character_action_buttons.size()) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "function received invalid index argument: " << index << endl;
return NULL;
}
return &(character_action_buttons[index]);
}
StillImage* BattleMedia::GetTargetTypeIcon(hoa_global::GLOBAL_TARGET target_type) {
switch (target_type) {
case GLOBAL_TARGET_SELF_POINT:
return &_target_type_icons[0];
case GLOBAL_TARGET_ALLY_POINT:
return &_target_type_icons[1];
case GLOBAL_TARGET_FOE_POINT:
return &_target_type_icons[2];
case GLOBAL_TARGET_SELF:
return &_target_type_icons[3];
case GLOBAL_TARGET_ALLY:
case GLOBAL_TARGET_ALLY_EVEN_DEAD:
return &_target_type_icons[4];
case GLOBAL_TARGET_FOE:
return &_target_type_icons[5];
case GLOBAL_TARGET_ALL_ALLIES:
return &_target_type_icons[6];
case GLOBAL_TARGET_ALL_FOES:
return &_target_type_icons[7];
default:
IF_PRINT_WARNING(BATTLE_DEBUG) << "function received invalid target type argument: " << target_type << endl;
return NULL;
}
}
StillImage* BattleMedia::GetStatusIcon(GLOBAL_STATUS type, GLOBAL_INTENSITY intensity) {
if ((type <= GLOBAL_STATUS_INVALID) || (type >= GLOBAL_STATUS_TOTAL)) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "type argument was invalid: " << type << endl;
return NULL;
}
if ((intensity < GLOBAL_INTENSITY_NEUTRAL) || (intensity >= GLOBAL_INTENSITY_TOTAL)) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "type argument was invalid: " << intensity << endl;
return NULL;
}
map<GLOBAL_STATUS, uint32>::iterator status_entry = _status_indeces.find(type);
if (status_entry == _status_indeces.end()) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "no entry in the status icon index for status type: " << type << endl;
return NULL;
}
uint32 status_index = status_entry->second;
uint32 intensity_index = static_cast<uint32>(intensity);
return &(_status_icons[(status_index * 5) + intensity_index]); // TODO: use an appropriate constant instead of the "5" value here
}
} // namespace private_battle
////////////////////////////////////////////////////////////////////////////////
// BattleMode class -- primary methods
////////////////////////////////////////////////////////////////////////////////
BattleMode::BattleMode() :
_state(BATTLE_STATE_INVALID),
_sequence_supervisor(NULL),
_command_supervisor(NULL),
_dialogue_supervisor(NULL),
_finish_supervisor(NULL),
_current_number_swaps(0),
_last_enemy_dying(false),
_stamina_icon_alpha(1.0f)
{
IF_PRINT_DEBUG(BATTLE_DEBUG) << "constructor invoked" << endl;
mode_type = MODE_MANAGER_BATTLE_MODE;
// Check that the global manager has a valid battle setting stored.
if ((GlobalManager->GetBattleSetting() <= GLOBAL_BATTLE_INVALID) || (GlobalManager->GetBattleSetting() >= GLOBAL_BATTLE_TOTAL)) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "global manager had invalid battle setting active, changing setting to GLOBAL_BATTLE_WAIT" << endl;
GlobalManager->SetBattleSetting(GLOBAL_BATTLE_WAIT);
}
_sequence_supervisor = new SequenceSupervisor(this);
_command_supervisor = new CommandSupervisor();
_dialogue_supervisor = new DialogueSupervisor();
_finish_supervisor = new FinishSupervisor();
} // BattleMode::BattleMode()
BattleMode::~BattleMode() {
delete _sequence_supervisor;
delete _command_supervisor;
delete _dialogue_supervisor;
delete _finish_supervisor;
// Delete all character and enemy actors
for (uint32 i = 0; i < _character_actors.size(); i++) {
delete _character_actors[i];
}
_character_actors.clear();
_character_party.clear();
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
delete _enemy_actors[i];
}
_enemy_actors.clear();
_enemy_party.clear();
_ready_queue.clear();
if (_current_instance == this) {
_current_instance = NULL;
}
} // BattleMode::~BattleMode()
void BattleMode::Reset() {
_current_instance = this;
// Disable potential previous overlay effects
VideoManager->DisableFadeEffect();
VideoManager->SetCoordSys(0.0f, VIDEO_STANDARD_RES_WIDTH, 0.0f, VIDEO_STANDARD_RES_HEIGHT);
// Load the default battle music track if no other music has been added
if (_battle_media.battle_music.GetState() == AUDIO_STATE_UNLOADED) {
if (_battle_media.battle_music.LoadAudio(DEFAULT_BATTLE_MUSIC) == false) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "failed to load default battle music: " << DEFAULT_BATTLE_MUSIC << endl;
}
}
_battle_media.battle_music.Play();
_last_enemy_dying = false;
if (_state == BATTLE_STATE_INVALID) {
_Initialize();
}
UnFreezeTimers();
// Reset potential battle scripts
GetScriptSupervisor().Reset();
}
bool CompareActorsYCoord(BattleActor* one, BattleActor* other) {
// Compares the Y-coordinates of the actors, used to sort the actors before draw calls
return (one->GetYLocation() > other->GetYLocation());
}
void BattleMode::Update() {
// Update potential battle animations
_battle_media.Update();
GameMode::Update();
// Pause/quit requests take priority
if (InputManager->QuitPress()) {
ModeManager->Push(new PauseMode(true));
return;
}
if (InputManager->PausePress()) {
ModeManager->Push(new PauseMode(false));
return;
}
if (_dialogue_supervisor->IsDialogueActive() == true) {
_dialogue_supervisor->Update();
// Because the dialogue may have ended in the call to Update(), we have to check it again here.
if (_dialogue_supervisor->IsDialogueActive() == true) {
if (_dialogue_supervisor->GetCurrentDialogue()->IsHaltBattleAction() == true) {
return;
}
}
}
// Update all actors animations and y-sorting
_battle_sprites.clear();
for (uint32 i = 0; i < _character_actors.size(); i++) {
_character_actors[i]->Update();
_battle_sprites.push_back(_character_actors[i]);
}
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
_enemy_actors[i]->Update();
_battle_sprites.push_back(_enemy_actors[i]);
}
std::sort(_battle_sprites.begin(), _battle_sprites.end(), CompareActorsYCoord);
// If the battle is transitioning to/from a different mode, the sequence supervisor has control
if (_state == BATTLE_STATE_INITIAL || _state == BATTLE_STATE_EXITING) {
_sequence_supervisor->Update();
return;
}
// If the battle is in its typical state and player is not selecting a command, check for player input
else if (_state == BATTLE_STATE_NORMAL) {
// Check for victory or defeat conditions
if (_NumberCharactersAlive() == 0) {
ChangeState(BATTLE_STATE_DEFEAT);
}
else if (_NumberEnemiesAlive() == 0) {
ChangeState(BATTLE_STATE_VICTORY);
}
// Check whether the last enemy is dying
if (_last_enemy_dying == false && _NumberValidEnemies() == 0)
_last_enemy_dying = true;
// Holds a pointer to the character to select an action for
BattleCharacter* character_selection = NULL;
// The four keys below (up/down/left/right) correspond to each character, from top to bottom. Since the character party
// does not always have four characters, for all but the first key we have to check that a character exists for the
// corresponding key. If a character does exist, we then have to check whether or not the player is allowed to select a command
// for it (characters can only have commands selected during certain states). If command selection is permitted, then we begin
// the command supervisor.
if (InputManager->UpPress()) {
if (_character_actors.size() >= 1) { // Should always evaluate to true
character_selection = _character_actors[0];
}
}
else if (InputManager->DownPress()) {
if (_character_actors.size() >= 2) {
character_selection = _character_actors[1];
}
}
else if (InputManager->LeftPress()) {
if (_character_actors.size() >= 3) {
character_selection = _character_actors[2];
}
}
else if (InputManager->RightPress()) {
if (_character_actors.size() >= 4) {
character_selection = _character_actors[3];
}
}
if (!_last_enemy_dying && character_selection != NULL) {
OpenCommandMenu(character_selection);
}
}
// If the player is selecting a command for a character, the command supervisor has control
else if (_state == BATTLE_STATE_COMMAND) {
// If the last enemy is dying, there is no need to process command further
if (!_last_enemy_dying)
_command_supervisor->Update();
else
ChangeState(BATTLE_STATE_NORMAL);
}
// If the battle is in either finish state, the finish supervisor has control
else if ((_state == BATTLE_STATE_VICTORY) || (_state == BATTLE_STATE_DEFEAT)) {
_finish_supervisor->Update();
// Make the heroes stamina icons fade out
if (_stamina_icon_alpha > 0.0f)
_stamina_icon_alpha -= (float)SystemManager->GetUpdateTime() / 800.0f;
return;
}
// If the battle is running in the "wait" setting, we need to pause the battle whenever any character reaches the
// command state to allow the player to enter a command for that character before resuming. We also want to make sure
// that the command menu is open whenever we find a character in the command state. If the command menu is not open, we
// forcibly open it and make the player choose a command for the character so that the battle may continue.
if (!_last_enemy_dying && GlobalManager->GetBattleSetting() == GLOBAL_BATTLE_WAIT) {
for (uint32 i = 0; i < _character_actors.size(); i++) {
if (_character_actors[i]->GetState() == ACTOR_STATE_COMMAND) {
if (_state != BATTLE_STATE_COMMAND) {
OpenCommandMenu(_character_actors[i]);
}
return;
}
}
}
// Process the actor ready queue
if (!_last_enemy_dying && !_ready_queue.empty()) {
// Only the acting actor is examined in the ready queue. If this actor is in the READY state,
// that means it has been waiting for BattleMode to allow it to begin its action and thus
// we set it to the ACTING state. We do nothing while it is in the ACTING state, allowing the
// actor to completely finish its action. When the actor enters any other state, it is presumed
// to be finished with the action or otherwise incapacitated and is removed from the queue.
BattleActor* acting_actor = _ready_queue.front();
switch (acting_actor->GetState()) {
case ACTOR_STATE_READY:
acting_actor->ChangeState(ACTOR_STATE_ACTING);
break;
case ACTOR_STATE_ACTING:
break;
default:
_ready_queue.pop_front();
break;
}
}
} // void BattleMode::Update()
void BattleMode::Draw() {
// Use custom display metrics
VideoManager->SetCoordSys(0.0f, VIDEO_STANDARD_RES_WIDTH, 0.0f, VIDEO_STANDARD_RES_HEIGHT);
if (_state == BATTLE_STATE_INITIAL || _state == BATTLE_STATE_EXITING) {
_sequence_supervisor->Draw();
return;
}
_DrawBackgroundGraphics();
_DrawSprites();
_DrawForegroundGraphics();
}
void BattleMode::DrawPostEffects() {
// Use custom display metrics
VideoManager->SetCoordSys(0.0f, VIDEO_STANDARD_RES_WIDTH, 0.0f, VIDEO_STANDARD_RES_HEIGHT);
GetScriptSupervisor().DrawPostEffects();
if (_state == BATTLE_STATE_INITIAL || _state == BATTLE_STATE_EXITING) {
_sequence_supervisor->DrawPostEffects();
return;
}
_DrawGUI();
}
////////////////////////////////////////////////////////////////////////////////
// BattleMode class -- secondary methods
////////////////////////////////////////////////////////////////////////////////
void BattleMode::AddEnemy(GlobalEnemy* new_enemy) {
// Don't add the enemy if it has an invalid ID or an experience level that is not zero
if (new_enemy->GetID() == 0) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "attempted to add a new enemy with an invalid id: " << new_enemy->GetID() << endl;
return;
}
if (new_enemy->GetExperienceLevel() != 0) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "attempted to add a new enemy that had already been initialized: " << new_enemy->GetID() << endl;
return;
}
new_enemy->Initialize();
BattleEnemy* new_battle_enemy = new BattleEnemy(new_enemy);
_enemy_actors.push_back(new_battle_enemy);
_enemy_party.push_back(new_battle_enemy);
}
void BattleMode::RestartBattle() {
// Disable potential previous light effects
VideoManager->DisableFadeEffect();
// Reset the state of all characters and enemies
for (uint32 i = 0; i < _character_actors.size(); i++) {
_character_actors[i]->ResetActor();
}
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
_enemy_actors[i]->ResetActor();
}
_battle_media.battle_music.Rewind();
_battle_media.battle_music.Play();
_last_enemy_dying = false;
ChangeState(BATTLE_STATE_INITIAL);
}
void BattleMode::FreezeTimers() {
// Pause character and enemy state timers
for (uint32 i = 0; i < _character_actors.size(); i++) {
_character_actors[i]->GetStateTimer().Pause();
}
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
_enemy_actors[i]->GetStateTimer().Pause();
}
}
void BattleMode::UnFreezeTimers() {
// FIXME: Do not unpause timers for paralyzed actors
// Unpause character and enemy state timers
for (uint32 i = 0; i < _character_actors.size(); i++) {
_character_actors[i]->GetStateTimer().Run();
}
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
_enemy_actors[i]->GetStateTimer().Run();
}
}
void BattleMode::ChangeState(BATTLE_STATE new_state) {
if (_state == new_state) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "battle was already in the state to change to: " << _state << endl;
return;
}
_state = new_state;
switch (_state) {
case BATTLE_STATE_INITIAL:
break;
case BATTLE_STATE_NORMAL:
break;
case BATTLE_STATE_COMMAND:
if (_command_supervisor->GetCommandCharacter() == NULL) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "no character was selected when changing battle to the command state" << endl;
_state = BATTLE_STATE_NORMAL;
}
break;
case BATTLE_STATE_EVENT:
// TODO
break;
case BATTLE_STATE_VICTORY:
// Official victory:
// Cancel all character actions to free possible involved objects
for (uint32 i = 0; i < _character_actors.size(); ++i) {
BattleAction *action = _character_actors[i]->GetAction();
if (action)
action->Cancel();
}
// Remove the items used in battle from inventory.
_command_supervisor->CommitChangesToInventory();
_battle_media.victory_music.Play();
_finish_supervisor->Initialize(true);
break;
case BATTLE_STATE_DEFEAT:
//Apply scene lighting if the battle has finished badly
GetEffectSupervisor().EnableLightingOverlay(Color(1.0f, 0.0f, 0.0f, 0.4f));
_battle_media.defeat_music.Play();
_finish_supervisor->Initialize(false);
break;
default:
IF_PRINT_WARNING(BATTLE_DEBUG) << "changed to invalid battle state: " << _state << endl;
break;
}
}
bool BattleMode::OpenCommandMenu(BattleCharacter* character) {
if (character == NULL) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "function received NULL argument" << endl;
return false;
}
if (_state == BATTLE_STATE_COMMAND) {
return false;
}
if (character->CanSelectCommand() == true) {
_command_supervisor->Initialize(character);
ChangeState(BATTLE_STATE_COMMAND);
return true;
}
return false;
}
void BattleMode::NotifyCommandCancel() {
if (_state != BATTLE_STATE_COMMAND) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "battle was not in command state when function was called" << endl;
return;
}
else if (_command_supervisor->GetCommandCharacter() != NULL) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "command supervisor still had a character selected when function was called" << endl;
return;
}
ChangeState(BATTLE_STATE_NORMAL);
}
void BattleMode::NotifyCharacterCommandComplete(BattleCharacter* character) {
if (character == NULL) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "function received NULL argument" << endl;
return;
}
// Update the action text to reflect the action and target now set for the character
character->ChangeActionText();
// If the character was in the command state when it had its command set, the actor needs to move on the the warmup state to prepare to
// execute the command. Otherwise if the character was in any other state (likely the idle state), the character should remain in that state.
if (character->GetState() == ACTOR_STATE_COMMAND) {
character->ChangeState(ACTOR_STATE_WARM_UP);
}
ChangeState(BATTLE_STATE_NORMAL);
}
void BattleMode::NotifyActorReady(BattleActor* actor) {
for (list<BattleActor*>::iterator i = _ready_queue.begin(); i != _ready_queue.end(); i++) {
if (actor == (*i)) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "actor was already present in the ready queue" << endl;
return;
}
}
_ready_queue.push_back(actor);
}
void BattleMode::NotifyActorDeath(BattleActor* actor) {
if (actor == NULL) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "function received NULL argument" << endl;
return;
}
// Remove the actor from the ready queue if it is there
_ready_queue.remove(actor);
// Notify the command supervisor about the death event if it is active
if (_state == BATTLE_STATE_COMMAND) {
_command_supervisor->NotifyActorDeath(actor);
// If the actor who died was the character that the player was selecting a command for, this will cause the
// command supervisor will return to the invalid state.
if (_command_supervisor->GetState() == COMMAND_STATE_INVALID)
ChangeState(BATTLE_STATE_NORMAL);
}
// Determine if the battle should proceed to the victory or defeat state
if (IsBattleFinished())
IF_PRINT_WARNING(BATTLE_DEBUG) << "actor death occurred after battle was finished" << endl;
}
bool BattleMode::isOneCharacterDead() const {
for (std::deque<private_battle::BattleCharacter*>::const_iterator it = _character_actors.begin();
it != _character_actors.end(); ++it) {
if (!(*it)->IsAlive())
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// BattleMode class -- private methods
////////////////////////////////////////////////////////////////////////////////
void BattleMode::_Initialize() {
// Unset a possible last enemy dying sequence.
_last_enemy_dying = false;
// (1): Construct all character battle actors from the active party, as well as the menus that populate the command supervisor
GlobalParty* active_party = GlobalManager->GetActiveParty();
if (active_party->GetPartySize() == 0) {
IF_PRINT_WARNING(BATTLE_DEBUG) << "no characters in the active party, exiting battle" << endl;
ModeManager->Pop();
return;
}
for (uint32 i = 0; i < active_party->GetPartySize(); i++) {
BattleCharacter* new_actor = new BattleCharacter(dynamic_cast<GlobalCharacter*>(active_party->GetActorAtIndex(i)));
_character_actors.push_back(new_actor);
_character_party.push_back(new_actor);
// Check whether the character is alive
if (new_actor->GetHitPoints() == 0)
new_actor->ChangeState(ACTOR_STATE_DEAD);
}
_command_supervisor->ConstructMenus();
// (2): Determine the origin position for all characters and enemies
_DetermineActorLocations();
// (3): Find the actor with the highext agility rating
uint32 highest_agility = 0;
for (uint32 i = 0; i < _character_actors.size(); i++) {
if (_character_actors[i]->GetAgility() > highest_agility)
highest_agility = _character_actors[i]->GetAgility();
}
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
if (_enemy_actors[i]->GetAgility() > highest_agility)
highest_agility = _enemy_actors[i]->GetAgility();
}
// Andy: Once every game loop, the SystemManager's timers are updated
// However, in between calls, battle mode is constructed. As part
// of battle mode's construction, each actor is given a wait timer
// that is triggered on initialization. But the moving of the stamina
// portrait uses the update time from SystemManager. Therefore, the
// amount of time since SystemManager last updated is greater than
// the amount of time that has expired on the actors' wait timers
// during the first orund of battle mode. This gives the portrait an
// extra boost, so once the wait time expires for an actor, his portrait
// is past the designated stopping point
// <-- time -->
// A----------X-----------B
// If the SystemManager has its timers updated at A and B, and battle mode is
// constructed and initialized at X, you can see the amount of time between
// X and B (how much time passed on the wait timers in round 1) is significantly
// smaller than the time between A and B. Hence the extra boost to the stamina
// portrait's location
// FIX ME This will not work in the future (i.e. paralysis)...realized this
// after writing all the above crap
// CD: Had to move this to before timers are initalized, otherwise this call will give
// our timers a little extra nudge with regards to time elapsed, thus making the portraits
// stop before they reach they yellow/orange line
// TODO: This should be fixed once battles have a little smoother start (characters run in from
// off screen to their positions, and stamina icons do not move until they are ready in their
// battle positions). Once that feature is available, remove this call.
SystemManager->UpdateTimers();
// (4): Adjust each actor's idle state time based on their agility proportion to the fastest actor
// If an actor's agility is half that of the actor with the highest agility, then they will have an
// idle state time that is twice that of the slowest actor.
float proportion;
for (uint32 i = 0; i < _character_actors.size(); i++) {
if (_character_actors[i]->IsAlive()) {
proportion = static_cast<float>(highest_agility) / static_cast<float>(_character_actors[i]->GetAgility());
_character_actors[i]->SetIdleStateTime(static_cast<uint32>(MIN_IDLE_WAIT_TIME * proportion));
_character_actors[i]->ChangeState(ACTOR_STATE_IDLE); // Needed to set up the stamina icon position.
}
}
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
proportion = static_cast<float>(highest_agility) / static_cast<float>(_enemy_actors[i]->GetAgility());
_enemy_actors[i]->SetIdleStateTime(static_cast<uint32>(MIN_IDLE_WAIT_TIME * proportion));
_enemy_actors[i]->ChangeState(ACTOR_STATE_IDLE);
}
// Randomize each actor's initial idle state progress to be somewhere in the lower half of their total
// idle state time. This is performed so that every battle doesn't start will all stamina icons piled on top
// of one another at the bottom of the stamina bar
for (uint32 i = 0; i < _character_actors.size(); i++) {
if (_character_actors[i]->IsAlive()) {
uint32 max_init_timer = _character_actors[i]->GetIdleStateTime() / 2;
_character_actors[i]->GetStateTimer().Update(RandomBoundedInteger(0, max_init_timer));
}
}
for (uint32 i = 0; i < _enemy_actors.size(); i++) {
uint32 max_init_timer = _enemy_actors[i]->GetIdleStateTime() / 2;
_enemy_actors[i]->GetStateTimer().Update(RandomBoundedInteger(0, max_init_timer));
}
// Init the script component.
GetScriptSupervisor().Initialize(this);
ChangeState(BATTLE_STATE_INITIAL);
} // void BattleMode::_Initialize()
void BattleMode::_DetermineActorLocations() {
// Temporary static positions for enemies
const float TEMP_ENEMY_LOCATIONS[][2] = {
{ 515.0f, 768.0f - 600.0f }, // 768.0f - because of reverse Y-coordinate system
{ 494.0f, 768.0f - 450.0f },
{ 560.0f, 768.0f - 550.0f },
{ 580.0f, 768.0f - 630.0f },
{ 675.0f, 768.0f - 390.0f },
{ 655.0f, 768.0f - 494.0f },
{ 793.0f, 768.0f - 505.0f },
{ 730.0f, 768.0f - 600.0f }
};
float position_x, position_y;
// Determine the position of the first character in the party, who will be drawn at the top
switch (_character_actors.size()) {
case 1:
position_x = 80.0f;
position_y = 288.0f;
break;
case 2:
position_x = 118.0f;
position_y = 343.0f;
break;
case 3:
position_x = 122.0f;
position_y = 393.0f;
break;
case 4:
default:
position_x = 160.0f;
position_y = 448.0f;
break;
}
// Set all characters in their proper positions
for (uint32 i = 0; i < _character_actors.size(); i++) {
_character_actors[i]->SetXOrigin(position_x);
_character_actors[i]->SetYOrigin(position_y);
_character_actors[i]->SetXLocation(position_x);
_character_actors[i]->SetYLocation(position_y);
position_x -= 32.0f;
position_y -= 105.0f;
}
// TEMP: assign static locations to enemies
uint32 temp_pos = 0;
for (uint32 i = 0; i < _enemy_actors.size(); i++, temp_pos++) {
position_x = TEMP_ENEMY_LOCATIONS[temp_pos][0];
position_y = TEMP_ENEMY_LOCATIONS[temp_pos][1];
_enemy_actors[i]->SetXOrigin(position_x);
_enemy_actors[i]->SetYOrigin(position_y);
_enemy_actors[i]->SetXLocation(position_x);
_enemy_actors[i]->SetYLocation(position_y);
}
} // void BattleMode::_DetermineActorLocations()
uint32 BattleMode::_NumberEnemiesAlive() const {
uint32 enemy_count = 0;
for (uint32 i = 0; i < _enemy_actors.size(); ++i) {
if (_enemy_actors[i]->IsAlive()) {
++enemy_count;
}
}
return enemy_count;
}
uint32 BattleMode::_NumberValidEnemies() const {
uint32 enemy_count = 0;
for (uint32 i = 0; i < _enemy_actors.size(); ++i) {
if (_enemy_actors[i]->IsValid()) {
++enemy_count;
}
}
return enemy_count;
}
uint32 BattleMode::_NumberCharactersAlive() const {
uint32 character_count = 0;
for (uint32 i = 0; i < _character_actors.size(); ++i) {
if (_character_actors[i]->IsAlive()) {
++character_count;
}
}
return character_count;
}
void BattleMode::_DrawBackgroundGraphics() {
VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_NO_BLEND, 0);
VideoManager->Move(0.0f, 0.0f);
_battle_media.background_image.Draw();
VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_TOP, VIDEO_BLEND, 0);
VideoManager->SetCoordSys(0.0f, VIDEO_STANDARD_RES_WIDTH, 0.0f, VIDEO_STANDARD_RES_HEIGHT);
GetScriptSupervisor().DrawBackground();
}
void BattleMode::_DrawForegroundGraphics() {
VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_TOP, VIDEO_BLEND, 0);
VideoManager->SetCoordSys(0.0f, VIDEO_STANDARD_RES_WIDTH, 0.0f, VIDEO_STANDARD_RES_HEIGHT);
GetScriptSupervisor().DrawForeground();
}
void BattleMode::_DrawSprites() {
// Booleans used to determine whether or not the actor selector and attack point selector graphics should be drawn
bool draw_actor_selection = false;
bool draw_point_selection = false;
BattleTarget target = _command_supervisor->GetSelectedTarget(); // The target that the player has selected
BattleActor* actor_target = target.GetActor(); // A pointer to an actor being targetted (value may be NULL if target is party)
// Determine if selector graphics should be drawn
if ((_state == BATTLE_STATE_COMMAND)
&& ((_command_supervisor->GetState() == COMMAND_STATE_ACTOR) || (_command_supervisor->GetState() == COMMAND_STATE_POINT))) {
draw_actor_selection = true;
if ((_command_supervisor->GetState() == COMMAND_STATE_POINT) && (IsTargetPoint(target.GetType()) == true))
draw_point_selection = true;
}
// Draw the actor selector graphic
if (draw_actor_selection == true) {
VideoManager->SetDrawFlags(VIDEO_X_CENTER, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
if (actor_target != NULL) {
VideoManager->Move(actor_target->GetXLocation(), actor_target->GetYLocation());
VideoManager->MoveRelative(0.0f, -20.0f);
_battle_media.actor_selection_image.Draw();
}
else if (IsTargetParty(target.GetType()) == true) {
deque<BattleActor*>& party_target = *(target.GetParty());
for (uint32 i = 0; i < party_target.size(); i++) {
VideoManager->Move(party_target[i]->GetXLocation(), party_target[i]->GetYLocation());
VideoManager->MoveRelative(0.0f, -20.0f);
_battle_media.actor_selection_image.Draw();
}
actor_target = NULL;
// TODO: add support for drawing graphic under multiple actors if the target is a party