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