1 ////////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2004-2011 by The Allacrost Project
3 // Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 // All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software and
7 // you may modify it and/or redistribute it under the terms of this license.
8 // See https://www.gnu.org/copyleft/gpl.html for details.
9 ////////////////////////////////////////////////////////////////////////////////
10
11 #include "battle_character.h"
12
13 #include "modes/battle/battle.h"
14 #include "modes/battle/actions/item_action.h"
15 #include "modes/battle/status_effects/status_effects_supervisor.h"
16 #include "modes/battle/command/command_supervisor.h"
17
18 #include "common/global/global.h"
19 #include "common/global/actors/global_character.h"
20 #include "common/global/objects/global_weapon.h"
21
22 #include "engine/video/text.h"
23
24 #include "utils/utils_random.h"
25 #include "utils/utils_strings.h"
26
27 using namespace vt_global;
28 using namespace vt_script;
29 using namespace vt_system;
30 using namespace vt_utils;
31 using namespace vt_video;
32
33 namespace vt_battle
34 {
35
36 namespace private_battle
37 {
38
BattleCharacter(GlobalCharacter * character)39 BattleCharacter::BattleCharacter(GlobalCharacter *character) :
40 BattleActor(character),
41 _global_character(character),
42 _last_rendered_hp(0),
43 _last_rendered_sp(0),
44 _sprite_animation_alias("idle")
45 {
46 _last_rendered_hp = GetHitPoints();
47 _last_rendered_sp = GetSkillPoints();
48
49 _name_text.SetStyle(TextStyle("title22"));
50 _name_text.SetText(GetName());
51 _hit_points_text.SetStyle(TextStyle("text24", VIDEO_TEXT_SHADOW_BLACK));
52 _hit_points_text.SetText(NumberToString(_last_rendered_hp));
53 _skill_points_text.SetStyle(TextStyle("text24", VIDEO_TEXT_SHADOW_BLACK));
54 _skill_points_text.SetText(NumberToString(_last_rendered_sp));
55
56 _action_selection_text.SetStyle(TextStyle("text20"));
57 _action_selection_text.SetText("");
58
59 // Init the battle animation pointers
60 _current_sprite_animation = _global_character->RetrieveBattleAnimation(_sprite_animation_alias);
61 // Add custom weapon animation
62 std::string weapon_animation;
63 if (_global_character->GetEquippedWeapon())
64 weapon_animation = _global_character->GetEquippedWeapon()->GetWeaponAnimationFile(_global_character->GetID(), _sprite_animation_alias);
65 if (weapon_animation.empty() || !_current_weapon_animation.LoadFromAnimationScript(weapon_animation))
66 _current_weapon_animation.Clear();
67
68 // Load the potential the ammo image filename
69 _ammo_animation_file = _global_character->GetEquippedWeapon() ?
70 _global_character->GetEquippedWeapon()->GetAmmoAnimationFile() : std::string();
71
72 // Apply passive status effect from equipment
73 const std::vector<GLOBAL_INTENSITY>& passive_effects = _global_character->GetEquipementStatusEffects();
74 for (uint32_t i = 0; i < passive_effects.size(); ++i) {
75 GLOBAL_INTENSITY intensity = passive_effects.at(i);
76
77 if (intensity == GLOBAL_INTENSITY_NEUTRAL)
78 continue;
79
80 GLOBAL_STATUS status_effect = (GLOBAL_STATUS) i;
81 _effects_supervisor->AddPassiveStatusEffect(status_effect, intensity);
82 }
83
84 // Apply currently active status effects
85 const std::vector<ActiveStatusEffect>& active_effects = character->GetActiveStatusEffects();
86 for(std::vector<ActiveStatusEffect>::const_iterator it = active_effects.begin();
87 it != active_effects.end(); ++it) {
88 const ActiveStatusEffect& effect = (*it);
89 _effects_supervisor->ChangeActiveStatusEffect(effect.GetEffect(), effect.GetIntensity(),
90 effect.GetEffectTime(), effect.GetElapsedTime());
91 }
92 }
93
~BattleCharacter()94 BattleCharacter::~BattleCharacter()
95 {
96 // If the character finished the battle alive, we set the active
97 // status effects on its global alter ego.
98 if (IsAlive())
99 _effects_supervisor->SetActiveStatusEffects(_global_character);
100 else // Otherwise, we just reset those.
101 _global_character->ResetActiveStatusEffects();
102 }
103
ChangeState(ACTOR_STATE new_state)104 void BattleCharacter::ChangeState(ACTOR_STATE new_state)
105 {
106 BattleActor::ChangeState(new_state);
107
108 switch(_state) {
109 case ACTOR_STATE_COMMAND:
110 // The battle action should pause whenever a character enters the command state in the WAIT battle type
111 BattleMode::CurrentInstance()->SetActorStatePaused(true);
112 break;
113 case ACTOR_STATE_WARM_UP: {
114 // BattleActor::Update() changes to the warm up state if the actor has an action set when the idle time is expired. However for characters, we do not
115 // want to proceed forward in this case if the player is currently setting a different action for that same character. Instead we place the character
116 // in the command state and wait until the player exits the command menu before moving on to the warm up state.
117 if(BattleMode::CurrentInstance()->GetCommandSupervisor()->GetCommandCharacter() == this)
118 ChangeState(ACTOR_STATE_COMMAND);
119
120 std::string animation_name = _action->GetWarmupActionName();
121 // Set the default animation name when it is empty.
122 if (animation_name.empty()) {
123 if(GetHitPoints() < (GetMaxHitPoints() / 4))
124 animation_name = "poor";
125 else
126 animation_name = "idle";
127 }
128
129 ChangeSpriteAnimation(animation_name);
130 break;
131 }
132 case ACTOR_STATE_SHOWNOTICE:
133 if (_action && _action->ShouldShowSkillNotice()) {
134 _state_timer.Initialize(1000);
135 _state_timer.Run();
136
137 // Determine the current weapon icon if existing...
138 std::string icon_filename;
139 if (_action->GetIconFilename() == "weapon") {
140 std::shared_ptr<GlobalWeapon> wpn = _global_character->GetEquippedWeapon();
141 if (wpn) {
142 icon_filename = _global_character->GetEquippedWeapon()->GetIconImage().GetFilename();
143 if (icon_filename.empty())
144 icon_filename = "data/gui/battle/default_weapon.png";
145 }
146 else {
147 icon_filename = "data/inventory/weapons/fist-human.png";
148 }
149 }
150 else {
151 icon_filename = _action->GetIconFilename();
152 }
153 BattleMode::CurrentInstance()->GetIndicatorSupervisor().AddShortNotice(_action->GetName(),
154 icon_filename,
155 _state_timer.GetDuration());
156 }
157 else {
158 _state = ACTOR_STATE_NOTICEDONE;
159 }
160 break;
161 case ACTOR_STATE_ACTING: {
162 _action->Initialize();
163 if(_action->IsScripted())
164 return;
165
166 // Trigger the action animation
167 std::string animation_name = _action->GetActionName().empty() ? "idle" : _action->GetActionName();
168 ChangeSpriteAnimation(animation_name);
169 // Reset state timer
170 _state_timer.Initialize(_current_sprite_animation->GetAnimationLength());
171 _state_timer.Run();
172 break;
173 }
174 case ACTOR_STATE_DYING:
175 // Cancel possible previous actions in progress.
176 if(_action)
177 _action->Cancel();
178
179 // use the default death sequence when there is no scripts.
180 if (!_death_init.is_valid()) {
181 ChangeSpriteAnimation("dying");
182 _state_timer.Initialize(_current_sprite_animation->GetAnimationLength());
183 _state_timer.Run();
184 }
185 break;
186 case ACTOR_STATE_DEAD:
187 ChangeSpriteAnimation("dead");
188 break;
189 case ACTOR_STATE_REVIVE:
190 ChangeSpriteAnimation("revive");
191 _state_timer.Initialize(_current_sprite_animation->GetAnimationLength());
192 _state_timer.Run();
193 break;
194 default:
195 break;
196 }
197
198 // The action/target text for the character is always updated when the character's state changes. Technically we do not need to update
199 // this text display for every possible state change, but we do it anyway just to be safe and to not add unnecessary code complexity.
200 ChangeActionText();
201 }
202
Update()203 void BattleCharacter::Update()
204 {
205 BattleActor::Update();
206
207 _animation_timer.Update();
208
209 // Update the active sprite animation
210 _current_sprite_animation->Update();
211 _current_weapon_animation.Update();
212
213 // Update hit and skill points after drawing to reduce GPU stall.
214 if(_last_rendered_hp != GetHitPoints()) {
215 _last_rendered_hp = GetHitPoints();
216 _hit_points_text.SetText(NumberToString(_last_rendered_hp));
217 }
218 if(_last_rendered_sp != GetSkillPoints()) {
219 _last_rendered_sp = GetSkillPoints();
220 _skill_points_text.SetText(NumberToString(_last_rendered_sp));
221 }
222
223 BattleMode* BM = BattleMode::CurrentInstance();
224
225 // Avoid updating the battle logic when finishing.
226 // This might break the character's animation.
227 switch (BM->GetState()) {
228 default:
229 break;
230 case BATTLE_STATE_VICTORY:
231 case BATTLE_STATE_DEFEAT:
232 case BATTLE_STATE_EXITING:
233 return;
234 }
235
236 // In scene mode, only the animations are updated
237 if (BM->IsInSceneMode())
238 return;
239
240 // Update potential scripted Battle action without hardcoded logic in that case
241 if(_action && _action->IsScripted() && _state == ACTOR_STATE_ACTING) {
242 if(!_action->Update())
243 return;
244 else
245 ChangeState(ACTOR_STATE_COOL_DOWN);
246 }
247
248 // Only set the origin when actor are in normal battle mode,
249 // Otherwise the battle sequence manager will take care of them.
250 if(BM->GetState() == BATTLE_STATE_NORMAL) {
251 _location = _origin;
252 }
253
254 if(_sprite_animation_alias == "idle") {
255 // Check whether character HP are low
256 if (_is_stunned || GetHitPoints() < (GetMaxHitPoints() / 4))
257 ChangeSpriteAnimation("poor");
258 } else if(_sprite_animation_alias == "run") {
259 // no need to do anything
260 } else if(_sprite_animation_alias == "run_after_victory") {
261 // Returns now as the battle is ending to prevent the animation
262 // timer to reset a potential previous battle animation
263 // if finishing while the heroes are running.
264 return;
265 } else if(_sprite_animation_alias == "dying") {
266 // no need to do anything, the change state will handle it
267 } else if(_sprite_animation_alias == "poor") {
268 // Check whether character HP are not low anymore
269 if (!_is_stunned && GetHitPoints() > (GetMaxHitPoints() / 4))
270 ChangeSpriteAnimation("idle");
271 } else if(_sprite_animation_alias == "dead") {
272 // no need to do anything
273 } else if(_sprite_animation_alias == "revive") {
274 // no need to do anything
275 } else if(_sprite_animation_alias == "victory") {
276 // no need to do anything
277 } else if(_sprite_animation_alias == "magic_prepare") {
278 // no need to do anything
279 }
280 // Makes the action listed below be set back to idle once done.
281 else if(_animation_timer.IsFinished()) {
282 if(_sprite_animation_alias == "hurt" || _sprite_animation_alias == "dodge")
283 ChangeSpriteAnimation(_before_attack_sprite_animation);
284 else
285 ChangeSpriteAnimation("idle");
286 } else if(_sprite_animation_alias == "attack") {
287 uint32_t dist = _state_timer.GetDuration() > 0 ?
288 120 * _state_timer.GetTimeExpired() / _state_timer.GetDuration() :
289 0;
290 _location.x = _origin.x + dist;
291 } else if(_sprite_animation_alias == "dodge") {
292 _location.x = _origin.x - 20.0f;
293 }
294
295 // Add a shake effect when the battle actor has received damages
296 if(_hurt_timer.IsRunning())
297 _location.x = _origin.x + RandomFloat(-6.0f, 6.0f);
298
299 // If the character has finished to execute its battle action,
300 if(_state == ACTOR_STATE_ACTING && _state_timer.IsFinished()) {
301 // Triggers here the skill or item action
302 // and set the actor to cool down mode.
303 if(!_action->Execute())
304 // Indicate the the skill execution failed to the user.
305 RegisterMiss();
306
307 // If it was an item action, show the item used.
308 if(_action->IsItemAction()) {
309 ItemAction* item_action = static_cast<ItemAction *>(_action);
310
311 // Creates an item indicator
312 float y_pos = GetYLocation() - GetSpriteHeight();
313 vt_mode_manager::IndicatorSupervisor& indicator = BM->GetIndicatorSupervisor();
314 indicator.AddItemIndicator(GetXLocation(), y_pos, item_action->GetBattleItem()->GetGlobalItem());
315 }
316
317 ChangeState(ACTOR_STATE_COOL_DOWN);
318 }
319 }
320
DrawSprite()321 void BattleCharacter::DrawSprite()
322 {
323 VideoManager->Move(_location.x, _location.y);
324 _current_sprite_animation->Draw(Color(1.0f, 1.0f, 1.0f, _sprite_alpha));
325 _current_weapon_animation.Draw(Color(1.0f, 1.0f, 1.0f, _sprite_alpha));
326
327 BattleMode *BM = BattleMode::CurrentInstance();
328
329 //! Don't display effects on characters when the battle is over
330 if (BM->GetState() != BATTLE_STATE_NORMAL && BM->GetState() != BATTLE_STATE_COMMAND)
331 return;
332
333 if(_state == ACTOR_STATE_DYING) {
334 try {
335 if (_death_draw_on_sprite.is_valid())
336 luabind::call_function<void>(_death_draw_on_sprite);
337 } catch(const luabind::error &e) {
338 PRINT_ERROR << "Error while triggering DrawOnSprite() function of actor id: " << _global_actor->GetID() << std::endl;
339 ScriptManager->HandleLuaError(e);
340 } catch(const luabind::cast_failed &e) {
341 PRINT_ERROR << "Error while triggering DrawOnSprite() function of actor id: " << _global_actor->GetID() << std::endl;
342 ScriptManager->HandleCastError(e);
343 }
344
345 }
346 else if(_is_stunned && (_state == ACTOR_STATE_COMMAND || _state == ACTOR_STATE_IDLE ||
347 _state == ACTOR_STATE_WARM_UP || _state == ACTOR_STATE_COOL_DOWN)) {
348 VideoManager->MoveRelative(0, -GetSpriteHeight());
349 GlobalManager->GetBattleMedia().GetStunnedIcon().Draw();
350 }
351 }
352
ChangeSpriteAnimation(const std::string & alias)353 void BattleCharacter::ChangeSpriteAnimation(const std::string &alias)
354 {
355 // Retains the previous animation when being hurt or dodging.
356 if((alias == "hurt" || alias == "dodge")
357 && (_sprite_animation_alias != "hurt" && _sprite_animation_alias != "dodge"))
358 _before_attack_sprite_animation = _sprite_animation_alias;
359
360 _sprite_animation_alias = alias;
361 _current_sprite_animation = _global_character->RetrieveBattleAnimation(_sprite_animation_alias);
362
363 // Change the weapon animation as well
364 // Add custom weapon animation
365 std::string weapon_animation;
366 if (_global_character->GetEquippedWeapon())
367 weapon_animation = _global_character->GetEquippedWeapon()->GetWeaponAnimationFile(_global_character->GetID(), _sprite_animation_alias);
368 if (weapon_animation.empty() || !_current_weapon_animation.LoadFromAnimationScript(weapon_animation))
369 _current_weapon_animation.Clear();
370
371 _current_sprite_animation->ResetAnimation();
372 _current_weapon_animation.ResetAnimation();
373 uint32_t timer_length = _current_sprite_animation->GetAnimationLength();
374
375 _animation_timer.Reset();
376 _animation_timer.SetDuration(timer_length);
377 _animation_timer.Run();
378 }
379
ChangeActionText()380 void BattleCharacter::ChangeActionText()
381 {
382 if(_action) {
383 ustring action_text = _action->GetName() + MakeUnicodeString(" -> ") + _action->GetTarget().GetName();
384 _action_selection_text.SetText(action_text);
385 if (_action->GetIconFilename().empty()) {
386 _action_selection_icon.Clear();
387 }
388 else {
389 // Determine the weapon icon according to the current skill
390 std::string icon_file = _action->GetIconFilename();
391 if (icon_file == "weapon") { // Alias used to trigger the loading of the weapon icon.
392 std::shared_ptr<GlobalWeapon> char_wpn = GetGlobalCharacter()->GetEquippedWeapon();
393 icon_file = char_wpn ?
394 char_wpn->GetIconImage().GetFilename() :
395 "data/inventory/weapons/fist-human.png";
396 }
397 _action_selection_icon.Clear();
398 _action_selection_icon.Load(icon_file, 24, 24);
399 }
400 return;
401 }
402
403 // If the character is able to have an action selected, notify the player
404 if((_state == ACTOR_STATE_IDLE) || (_state == ACTOR_STATE_COMMAND))
405 _action_selection_text.SetText(Translate("[Select Action]"));
406 else
407 _action_selection_text.Clear();
408
409 _action_selection_icon.Clear();
410 }
411
DrawPortrait()412 void BattleCharacter::DrawPortrait()
413 {
414 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
415 VideoManager->Move(5.0f, 762.0f);
416
417 std::vector<StillImage>& portrait_frames = *(_global_character->GetBattlePortraits());
418 float hp_percent = static_cast<float>(GetHitPoints()) / static_cast<float>(GetMaxHitPoints());
419
420 if(GetHitPoints() == GetMaxHitPoints()) {
421 portrait_frames[0].Draw();
422 } else if(GetHitPoints() == 0) {
423 portrait_frames[4].Draw();
424 } else if(hp_percent > 0.75f) {
425 portrait_frames[0].Draw();
426 float alpha = 1.0f - ((hp_percent - 0.75f) * 4.0f);
427 portrait_frames[1].Draw(Color(1.0f, 1.0f, 1.0f, alpha));
428 } else if(hp_percent > 0.50f) {
429 portrait_frames[1].Draw();
430 float alpha = 1.0f - ((hp_percent - 0.50f) * 4.0f);
431 portrait_frames[2].Draw(Color(1.0f, 1.0f, 1.0f, alpha));
432 } else if(hp_percent > 0.25f) {
433 portrait_frames[2].Draw();
434 float alpha = 1.0f - ((hp_percent - 0.25f) * 4.0f);
435 portrait_frames[3].Draw(Color(1.0f, 1.0f, 1.0f, alpha));
436 } else { // (hp_precent > 0.0f)
437 portrait_frames[3].Draw();
438 float alpha = 1.0f - (hp_percent * 4.0f);
439 portrait_frames[4].Draw(Color(1.0f, 1.0f, 1.0f, alpha));
440 }
441 }
442
DrawStatus(uint32_t order,BattleCharacter * character_command)443 void BattleCharacter::DrawStatus(uint32_t order, BattleCharacter* character_command)
444 {
445 BattleMedia& battle_media = GlobalManager->GetBattleMedia();
446 GlobalMedia& media = GlobalManager->Media();
447 // Used to determine where to draw the character's status
448 float y_offset = 0.0f;
449
450 // Determine what vertical order the character is in and set the y_offset accordingly
451 switch(order) {
452 case 0:
453 y_offset = 0.0f;
454 break;
455 case 1:
456 y_offset = 25.0f;
457 break;
458 case 2:
459 y_offset = 50.0f;
460 break;
461 case 3:
462 y_offset = 75.0f;
463 break;
464 default:
465 IF_PRINT_WARNING(BATTLE_DEBUG) << "invalid order argument: " << order << std::endl;
466 y_offset = 0.0f;
467 }
468
469 // Draw the character's name
470 VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
471 VideoManager->Move(280.0f, 686.0f + y_offset);
472 _name_text.Draw();
473
474 if (!character_command) {
475 // Draw each characters active status effect.
476 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
477 VideoManager->MoveRelative(-273.0f, 0.0f);
478 _effects_supervisor->Draw();
479 } else if (this == character_command) {
480 // Draw the active character status effect at bottom.
481 // Draw each characters active status effect.
482 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
483 VideoManager->Move(7.0f, 678.0f);
484 VideoManager->MoveRelative(0.0f, -25.0f * _effects_supervisor->GetDisplayedStatusEffectNumber());
485 _effects_supervisor->DrawVertical();
486 }
487
488 // Draw the status, HP and SP bars (bars are 88 pixels wide and 6 pixels high).
489 const float BAR_BASE_SIZE_X = 88.0f;
490 const float BAR_BASE_SIZE_Y = 6.0f;
491 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_NO_BLEND, 0);
492
493 // Draw the HP bar in green.
494 float bar_size = static_cast<float>(BAR_BASE_SIZE_X * GetHitPoints()) / static_cast<float>(GetMaxHitPoints());
495 VideoManager->Move(313.0f, 678.0f + y_offset);
496
497 // Add HP Bar Shadow
498 VideoManager->DrawRectangle(BAR_BASE_SIZE_X, BAR_BASE_SIZE_Y, Color::dark_green_hp);
499
500 if (GetHitPoints() > 0) {
501 if (bar_size < BAR_BASE_SIZE_X / 4.0f)
502 VideoManager->DrawRectangle(bar_size, BAR_BASE_SIZE_Y, Color::orange);
503 else
504 VideoManager->DrawRectangle(bar_size, BAR_BASE_SIZE_Y, Color::green_hp);
505 }
506
507 // Draw the SP bar in blue.
508 bar_size = static_cast<float>(BAR_BASE_SIZE_X * GetSkillPoints()) / static_cast<float>(GetMaxSkillPoints());
509 VideoManager->Move(425.0f, 678.0f + y_offset);
510
511 // Add SP bar shadow
512 VideoManager->DrawRectangle(BAR_BASE_SIZE_X, BAR_BASE_SIZE_Y, Color::dark_blue_sp);
513
514 if (GetSkillPoints() > 0)
515 VideoManager->DrawRectangle(bar_size, BAR_BASE_SIZE_Y, Color::blue_sp);
516
517 // Draw the cover image over the top of the bar.
518 VideoManager->SetDrawFlags(VIDEO_BLEND, 0);
519 VideoManager->Move(289.0f, 684.0f + y_offset);
520 media.GetStatusIcon(vt_global::GLOBAL_STATUS_HP, vt_global::GLOBAL_INTENSITY_NEUTRAL)->Draw();
521 VideoManager->MoveRelative(114.0f, 0.0f);
522 media.GetStatusIcon(vt_global::GLOBAL_STATUS_SP, vt_global::GLOBAL_INTENSITY_NEUTRAL)->Draw();
523
524 VideoManager->SetDrawFlags(VIDEO_X_CENTER, 0);
525 // Draw the character's current health on top of the middle of the HP bar.
526 VideoManager->Move(356.0f, 687.0f + y_offset);
527 _hit_points_text.Draw();
528
529 // Draw the character's current skill points on top of the middle of the SP bar.
530 VideoManager->MoveRelative(113.0f, 0.0f);
531 _skill_points_text.Draw();
532
533 // Note: if the command menu is visible, it will be drawn over all of the components that follow below. We still perform these draw calls
534 // regardless because sometimes even if the battle is in the command state, the command menu may not be drawn if a dialogue is active or if
535 // a scripted scene is taking place. Its easier (and not costly) to just always draw this information rather than check for all possible
536 // conditions where the command menu is not drawn.
537 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_CENTER, VIDEO_BLEND, 0);
538
539 // Move to the position wher command button icons are drawn
540 VideoManager->Move(545.0f, 673.0f + y_offset);
541
542 // If this character can be issued a command, draw the appropriate command button to indicate this. The type of button drawn depends on
543 // whether or not the character already has an action set. Characters that can not be issued a command have no button drawn
544 if(CanSelectCommand()) {
545 uint32_t button_index = 0;
546 if(IsActionSet() == false)
547 button_index = 1;
548 else
549 button_index = 6;
550 button_index += order;
551 battle_media.GetCharacterActionButton(button_index)->Draw();
552 }
553
554 // Draw the action icon and text
555 VideoManager->MoveRelative(40.0f, 0.0f);
556 _action_selection_icon.Draw();
557 VideoManager->MoveRelative(28.0f, 0.0f);
558 _action_selection_text.Draw();
559 }
560
561 } // namespace private_battle
562
563 } // namespace vt_battle
564