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 "modes/battle/objects/battle_actor.h"
12 
13 #include "modes/battle/battle.h"
14 #include "modes/battle/status_effects/status_effects_supervisor.h"
15 #include "modes/battle/actions/skill_action.h"
16 #include "modes/battle/battle_target.h"
17 
18 #include "common/global/actors/global_attack_point.h"
19 #include "common/global/global_skills.h"
20 
21 #include "utils/utils_random.h"
22 
23 using namespace vt_common;
24 using namespace vt_global;
25 using namespace vt_script;
26 using namespace vt_utils;
27 using namespace vt_video;
28 
29 namespace vt_battle
30 {
31 
32 namespace private_battle
33 {
34 
35 //! \brief The X and Y position of the stamina bar
36 const float STAMINA_BAR_POSITION_X = 970.0f;
37 const float STAMINA_BAR_POSITION_Y = 640.0f;
38 
39 //! \brief The bottom most position of the stamina bar
40 const float STAMINA_LOCATION_BOTTOM = 640.0f;
41 
42 //! \brief The location where each actor is allowed to select a command
43 const float STAMINA_LOCATION_COMMAND = STAMINA_LOCATION_BOTTOM - 354.0f;
44 
45 //! \brief The top most position of the stamina bar where actors are ready to execute their actions
46 const float STAMINA_LOCATION_TOP = STAMINA_LOCATION_BOTTOM - 508.0f;
47 
BattleActor(GlobalActor * actor)48 BattleActor::BattleActor(GlobalActor *actor) :
49     GlobalActor(*actor),
50     BattleObject(),
51     _state(ACTOR_STATE_INVALID),
52     _global_actor(actor),
53     _action(nullptr),
54     _idle_state_time(0),
55     _hurt_timer(0),
56     _is_stunned(false),
57     _sprite_alpha(1.0f),
58     _animation_timer(0),
59     _stamina_location(0.0f, 0.0f),
60     _effects_supervisor(new BattleStatusEffectsSupervisor(this))
61 {
62     if(actor == nullptr) {
63         IF_PRINT_WARNING(BATTLE_DEBUG) << "constructor received nullptr argument" << std::endl;
64         return;
65     }
66 
67     _InitStats();
68     _LoadDeathAnimationScript();
69     _LoadAIScript();
70 }
71 
_InitStats()72 void BattleActor::_InitStats()
73 {
74     _char_phys_atk.SetBase(_global_actor->GetPhysAtk());
75     SetPhysAtkModifier(1.0f);
76 
77     _char_mag_atk.SetBase(_global_actor->GetMagAtk());
78     SetMagAtkModifier(1.0f);
79 
80     _char_phys_def.SetBase(_global_actor->GetPhysDef());
81     SetPhysDefModifier(1.0f);
82 
83     _char_mag_def.SetBase(_global_actor->GetMagDef());
84     SetMagDefModifier(1.0f);
85 
86     _stamina.SetBase(_global_actor->GetStamina());
87     SetStaminaModifier(1.0f);
88 
89     _evade.SetBase(_global_actor->GetEvade());
90     SetEvadeModifier(1.0f);
91 
92     // debug
93     //std::cout << "Name: " << MakeStandardString(_global_actor->GetName()) << std::endl;
94     //std::cout << "phys atk base: " << _char_phys_atk.GetBase() << "should be equal to global actor total phys atk: " << _global_actor->GetPhysAtk() << std::endl;
95     //std::cout << "phys atk mod (should be 1.0f): " << _char_phys_atk.GetModifier() << ", global actor phys atk mod (independant): " << _global_actor->GetPhysAtkModifier() << std::endl;
96     //std::cout << "phys atk value: " << GetPhysAtk() << std::endl << std::endl;
97 }
98 
~BattleActor()99 BattleActor::~BattleActor()
100 {
101     // Reset the luabind objects so their lua counterparts can be freed
102     // when the lua script coroutine is removed from stack,
103     // to avoid a potential segfault.
104     _ai_decide_action = luabind::object();
105     _death_init = luabind::object();
106     _death_update = luabind::object();
107     _death_draw_on_sprite = luabind::object();
108 
109     // If the actor did not get a chance to execute their action, delete it
110     if(_action != nullptr) {
111         delete _action;
112         _action = nullptr;
113     }
114 
115     delete _effects_supervisor;
116 }
117 
ResetActor()118 void BattleActor::ResetActor()
119 {
120     _effects_supervisor->RemoveAllActiveStatusEffects();
121 
122     ResetHitPoints();
123     ResetSkillPoints();
124     ResetPhysAtk();
125     ResetMagAtk();
126     ResetPhysDef();
127     ResetMagDef();
128     ResetStamina();
129     ResetEvade();
130 
131     // If the actor did not get a chance to execute their action, delete it
132     if(_action != nullptr) {
133         delete _action;
134         _action = nullptr;
135     }
136 
137     // Invalidate the actor state to force the reinit of the idle or dead state
138     _state = ACTOR_STATE_INVALID;
139 
140     if(GetHitPoints() > 0)
141         ChangeState(ACTOR_STATE_IDLE);
142     else
143         ChangeState(ACTOR_STATE_DEAD);
144 }
145 
ChangeState(ACTOR_STATE new_state)146 void BattleActor::ChangeState(ACTOR_STATE new_state)
147 {
148     if(_state == new_state) {
149         IF_PRINT_WARNING(BATTLE_DEBUG) << "actor was already in new state: " << new_state << std::endl;
150         return;
151     }
152 
153     _state = new_state;
154     _state_timer.Reset();
155     switch(_state) {
156     case ACTOR_STATE_COMMAND:
157         // If an AI is used, it will change itself the actor state.
158         if (_ai_decide_action.is_valid()) {
159             try {
160                 luabind::call_function<void>(_ai_decide_action, BattleMode::CurrentInstance(), this);
161             } catch(const luabind::error &e) {
162                 PRINT_ERROR << "Error while triggering DecideAction() function of actor id: " << _global_actor->GetID() << std::endl;
163                 ScriptManager->HandleLuaError(e);
164                 // Make the actor keep on anyway.
165                 _DecideAction();
166             } catch(const luabind::cast_failed &e) {
167                 PRINT_ERROR << "Error while triggering DecideAction() function of actor id: " << _global_actor->GetID() << std::endl;
168                 ScriptManager->HandleCastError(e);
169                 // Make the actor keep on anyway.
170                 _DecideAction();
171             }
172         }
173         else if (!_global_actor->GetBattleAIScriptFilename().empty()) {
174             // Hardcoded fallback behaviour for AI-based actors.
175             _DecideAction();
176         }
177         break;
178     case ACTOR_STATE_IDLE:
179         if(_action != nullptr) {
180             delete _action;
181             _action = nullptr;
182         }
183         _state_timer.Initialize(_idle_state_time);
184         _state_timer.Run();
185         break;
186     case ACTOR_STATE_WARM_UP:
187         if(_action == nullptr) {
188             IF_PRINT_WARNING(BATTLE_DEBUG) << "no action available during state change: " << _state << std::endl;
189         } else {
190             _state_timer.Initialize(_action->GetWarmUpTime() * GetStaminaModifier());
191             _state_timer.Run();
192             _action->Warmup();
193         }
194         break;
195     case ACTOR_STATE_READY:
196         if(_action == nullptr) {
197             IF_PRINT_WARNING(BATTLE_DEBUG) << "no action available during state change: " << _state << std::endl;
198         } else {
199             BattleMode::CurrentInstance()->NotifyActorReady(this);
200         }
201         break;
202     case ACTOR_STATE_COOL_DOWN:
203     {
204         uint32_t cool_down_time = 1000; // Default value, overridden by valid actions
205         if(_action)
206             cool_down_time = _action->GetCoolDownTime() * GetStaminaModifier();
207 
208         _state_timer.Initialize(cool_down_time);
209         _state_timer.Run();
210         break;
211     }
212     case ACTOR_STATE_DYING:
213         ChangeSpriteAnimation("dying");
214         // Note that the state timer is initialized in Battle Character
215         // or In BattleEnemy
216 
217         // Make the battle engine aware of the actor death
218         _effects_supervisor->RemoveAllActiveStatusEffects();
219         BattleMode::CurrentInstance()->NotifyActorDeath(this);
220 
221         // Init the death animation script when valid.
222         if (_death_init.is_valid()) {
223             try {
224                 luabind::call_function<void>(_death_init, BattleMode::CurrentInstance(), this);
225             } catch(const luabind::error &e) {
226                 PRINT_ERROR << "Error while triggering Initialize() function of actor id: " << _global_actor->GetID() << std::endl;
227                 ScriptManager->HandleLuaError(e);
228             } catch(const luabind::cast_failed &e) {
229                 PRINT_ERROR << "Error while triggering Initialize() function of actor id: " << _global_actor->GetID() << std::endl;
230                 ScriptManager->HandleCastError(e);
231             }
232         }
233         break;
234     default:
235         break;
236     }
237 }
238 
RegisterDamage(uint32_t amount)239 void BattleActor::RegisterDamage(uint32_t amount)
240 {
241     RegisterDamage(amount, nullptr);
242 }
243 
RegisterDamage(uint32_t amount,BattleTarget * target)244 void BattleActor::RegisterDamage(uint32_t amount, BattleTarget *target)
245 {
246     if(amount == 0) {
247         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called with a zero value argument" << std::endl;
248         RegisterMiss(true);
249         return;
250     }
251     if(_state == ACTOR_STATE_DYING || _state == ACTOR_STATE_DEAD) {
252         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called when actor state was dead" << std::endl;
253         RegisterMiss();
254         return;
255     }
256 
257     SubtractHitPoints(amount);
258 
259     // Set the indicator parameters
260     BattleMode* BM = BattleMode::CurrentInstance();
261     vt_mode_manager::IndicatorSupervisor& indicator = BM->GetIndicatorSupervisor();
262     indicator.AddDamageIndicator(GetXLocation(), GetYLocation(), amount, _GetDamageTextStyle(amount, false));
263 
264     if(GetHitPoints() == 0) {
265         ChangeState(ACTOR_STATE_DYING);
266         return;
267     }
268 
269     ChangeSpriteAnimation("hurt");
270 
271     // Apply a stun to the actor timer depending on the amount of damage dealt
272     float damage_percent = static_cast<float>(amount) / static_cast<float>(GetMaxHitPoints());
273     uint32_t hurt_time = 0;
274     if(damage_percent < 0.10f)
275         hurt_time = 250;
276     else if(damage_percent < 0.25f)
277         hurt_time = 500;
278     else if(damage_percent < 0.50f)
279         hurt_time = 750;
280     else // (damage_percent >= 0.50f)
281         hurt_time = 1000;
282 
283     // Run a shake effect for the same time.
284     _hurt_timer.Initialize(hurt_time);
285     _hurt_timer.Run();
286 
287     // If the damage dealt was to a point target type, check for and apply any status effects triggered by this point hit
288     if((target != nullptr) && (IsTargetPoint(target->GetType()))) {
289         GlobalAttackPoint* damaged_point = _global_actor->GetAttackPoint(target->GetAttackPoint());
290         if(damaged_point == nullptr) {
291             IF_PRINT_WARNING(BATTLE_DEBUG) << "target argument contained an invalid point index: " << target->GetAttackPoint() << std::endl;
292         } else {
293             std::vector<std::pair<GLOBAL_STATUS, float> > status_effects = damaged_point->GetStatusEffects();
294             for(std::vector<std::pair<GLOBAL_STATUS, float> >::const_iterator i = status_effects.begin(); i != status_effects.end(); ++i) {
295                 if(RandomFloat(0.0f, 100.0f) <= i->second) {
296                     ApplyActiveStatusEffect(i->first, GLOBAL_INTENSITY_NEG_MODERATE, 15000);
297                 }
298             }
299         }
300     }
301 }
302 
_GetDamageTextStyle(uint32_t amount,bool is_sp_damage)303 vt_video::TextStyle BattleActor::_GetDamageTextStyle(uint32_t amount, bool is_sp_damage)
304 {
305     const Color low_red(1.0f, 0.75f, 0.0f, 1.0f);
306     const Color mid_red(1.0f, 0.50f, 0.0f, 1.0f);
307     const Color high_red(1.0f, 0.25f, 0.0f, 1.0f);
308 
309     const Color low_blue(0.0f, 0.75f, 1.0f, 1.0f);
310     const Color mid_blue(0.0f, 0.50f, 1.0f, 1.0f);
311     const Color high_blue(0.0f, 0.25f, 1.0f, 1.0f);
312 
313     TextStyle style;
314 
315     float damage_percent = static_cast<float>(amount) / static_cast<float>(GetMaxHitPoints());
316     if(damage_percent < 0.10f) {
317         style.SetColor(is_sp_damage ? low_blue : low_red);
318     } else if(damage_percent < 0.20f) {
319         style.SetColor(is_sp_damage ? mid_blue : mid_red);
320     } else if(damage_percent < 0.30f) {
321         style.SetColor(is_sp_damage ? high_blue : high_red);
322     } else { // (damage_percent >= 0.30f)
323         style.SetColor(is_sp_damage ? Color::blue : Color::red);
324     }
325     style.SetShadowStyle(VIDEO_TEXT_SHADOW_BLACK);
326     style.SetShadowOffsets(1, -2);
327 
328     // Set the text size depending on the amount of pure damage.
329     std::string font;
330     if(amount < 50) {
331         font = "text24"; // text24
332     } else if(amount < 100) {
333         font = "text24.2";
334     } else if(amount < 250) {
335         font = "text26";
336     } else if(amount < 500) {
337         font = "text28";
338     } else if(amount < 1000) {
339         font = "text36";
340     } else {
341         font = "text48";
342     }
343     style.SetFont(font);
344 
345     return style;
346 }
347 
RegisterSPDamage(uint32_t amount)348 void BattleActor::RegisterSPDamage(uint32_t amount)
349 {
350     if(amount == 0) {
351         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called with a zero value argument" << std::endl;
352         RegisterMiss(true);
353         return;
354     }
355     if(_state == ACTOR_STATE_DYING || _state == ACTOR_STATE_DEAD) {
356         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called when actor state was dead" << std::endl;
357         RegisterMiss();
358         return;
359     }
360 
361     SubtractSkillPoints(amount);
362 
363     // Set the indicator parameters
364     BattleMode* BM = BattleMode::CurrentInstance();
365     vt_mode_manager::IndicatorSupervisor& indicator = BM->GetIndicatorSupervisor();
366     indicator.AddDamageIndicator(GetXLocation(), GetYLocation(), amount, _GetDamageTextStyle(amount, true));
367 }
368 
RegisterHealing(uint32_t amount,bool hit_points)369 void BattleActor::RegisterHealing(uint32_t amount, bool hit_points)
370 {
371     if(amount == 0) {
372         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called with a zero value argument" << std::endl;
373         RegisterMiss();
374         return;
375     }
376     if(_state == ACTOR_STATE_DYING || _state == ACTOR_STATE_DEAD) {
377         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called when actor state was dead" << std::endl;
378         RegisterMiss();
379         return;
380     }
381 
382     if(hit_points)
383         AddHitPoints(amount);
384     else
385         AddSkillPoints(amount);
386 
387     // Set the indicator parameters
388     float y_pos = GetYLocation() - (GetSpriteHeight() / 3 * 2);
389     BattleMode* BM = BattleMode::CurrentInstance();
390     vt_mode_manager::IndicatorSupervisor& indicator = BM->GetIndicatorSupervisor();
391     indicator.AddHealingIndicator(GetXLocation(), y_pos, amount, _GetHealingTextStyle(amount, hit_points));
392 }
393 
RegisterRevive(uint32_t amount)394 void BattleActor::RegisterRevive(uint32_t amount)
395 {
396     if(amount == 0) {
397         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called with a zero value argument" << std::endl;
398         RegisterMiss();
399         return;
400     }
401     if(_state != ACTOR_STATE_DYING && _state != ACTOR_STATE_DEAD) {
402         IF_PRINT_WARNING(BATTLE_DEBUG) << "function called when actor state wasn't dead" << std::endl;
403         RegisterMiss();
404         return;
405     }
406 
407     AddHitPoints(amount);
408 
409     // Set the indicator parameters
410     float y_pos = GetYLocation() - (GetSpriteHeight() / 3 * 2);
411     BattleMode* BM = BattleMode::CurrentInstance();
412     vt_mode_manager::IndicatorSupervisor& indicator = BM->GetIndicatorSupervisor();
413     indicator.AddHealingIndicator(GetXLocation(), y_pos, amount, _GetHealingTextStyle(amount, true));
414 
415     // Reset the stamina icon position and battle state time to the minimum
416     BM->SetActorIdleStateTime(this);
417 
418     ChangeState(ACTOR_STATE_REVIVE);
419 }
420 
_GetHealingTextStyle(uint32_t amount,bool is_hp)421 vt_video::TextStyle BattleActor::_GetHealingTextStyle(uint32_t amount, bool is_hp)
422 {
423     const Color low_green(0.0f, 1.0f, 0.60f, 1.0f);
424     const Color mid_green(0.0f, 1.0f, 0.30f, 1.0f);
425     const Color high_green(0.0f, 1.0f, 0.15f, 1.0f);
426 
427     const Color low_blue(0.0f, 0.60f, 1.0f, 1.0f);
428     const Color mid_blue(0.0f, 0.30f, 1.0f, 1.0f);
429     const Color high_blue(0.0f, 0.15f, 1.0f, 1.0f);
430 
431     TextStyle style;
432 
433     // Use different colors/shades of green/blue for different degrees of healing
434     std::string font;
435     float healing_percent = static_cast<float>(amount / GetMaxHitPoints());
436     if(healing_percent < 0.10f) {
437         font = "text24";
438         style.SetColor(is_hp ? low_green : low_blue);
439     } else if(healing_percent < 0.20f) {
440         font = "text24";
441         style.SetColor(is_hp ? mid_green : mid_blue);
442     } else if(healing_percent < 0.30f) {
443         font = "text26";
444         style.SetColor(is_hp ? high_green : high_blue);
445     } else { // (healing_percent >= 0.30f)
446         font = "text26";
447         style.SetColor(is_hp ? Color::green : Color::blue);
448     }
449     style.SetFont(font);
450     style.SetShadowStyle(VIDEO_TEXT_SHADOW_BLACK);
451     style.SetShadowOffsets(1, -2);
452 
453     return style;
454 }
455 
RegisterMiss(bool was_attacked)456 void BattleActor::RegisterMiss(bool was_attacked)
457 {
458     // Set the indicator parameters
459     float y_pos = GetYLocation() - (GetSpriteHeight() / 2);
460     BattleMode* BM = BattleMode::CurrentInstance();
461     vt_mode_manager::IndicatorSupervisor& indicator = BM->GetIndicatorSupervisor();
462     indicator.AddMissIndicator(GetXLocation(), y_pos);
463 
464     if(was_attacked && IsAlive())
465         ChangeSpriteAnimation("dodge");
466 }
467 
ApplyActiveStatusEffect(GLOBAL_STATUS status,GLOBAL_INTENSITY intensity,uint32_t duration)468 void BattleActor::ApplyActiveStatusEffect(GLOBAL_STATUS status, GLOBAL_INTENSITY intensity, uint32_t duration)
469 {
470     _effects_supervisor->ChangeActiveStatusEffect(status, intensity, duration);
471 }
472 
GetActiveStatusEffectIntensity(vt_global::GLOBAL_STATUS status)473 vt_global::GLOBAL_INTENSITY BattleActor::GetActiveStatusEffectIntensity(vt_global::GLOBAL_STATUS status)
474 {
475     return _effects_supervisor->GetActiveStatusIntensity(status);
476 }
477 
RemoveActiveStatusEffect(GLOBAL_STATUS status_effect)478 void BattleActor::RemoveActiveStatusEffect(GLOBAL_STATUS status_effect)
479 {
480     _effects_supervisor->RemoveActiveStatusEffect(status_effect);
481 }
482 
_UpdateState()483 void BattleActor::_UpdateState()
484 {
485     // Permits the actor to die even in pause mode,
486     // so that the sprite fade out is properly done.
487     if (_state == ACTOR_STATE_DYING) {
488         _state_timer.Update();
489 
490         // Updates the scripted death animation if any.
491         if (_death_init.is_valid() && _death_update.is_valid()) {
492             // Change the state when the animation has finished.
493             try {
494                 if (luabind::call_function<bool>(_death_update))
495                     ChangeState(ACTOR_STATE_DEAD);
496             } catch(const luabind::error &e) {
497                 PRINT_ERROR << "Error while triggering Update() function of actor id: " << _global_actor->GetID() << std::endl;
498                 ScriptManager->HandleLuaError(e);
499                 // Do not block the player
500                 ChangeState(ACTOR_STATE_DEAD);
501             } catch(const luabind::cast_failed &e) {
502                 PRINT_ERROR << "Error while triggering Update() function of actor id: " << _global_actor->GetID() << std::endl;
503                 ScriptManager->HandleCastError(e);
504                 // Do not block the player
505                 ChangeState(ACTOR_STATE_DEAD);
506             }
507         }
508     }
509 
510     // Don't elapse the status effect time when paused.
511     BattleMode* BM = BattleMode::CurrentInstance();
512     if (BM->IsInSceneMode())
513         return;
514 
515     // Paused any states except the show notice related ones
516     switch(_state) {
517     case ACTOR_STATE_SHOWNOTICE:
518     case ACTOR_STATE_NOTICEDONE:
519     case ACTOR_STATE_ACTING:
520         break;
521     default:
522         if (BM->AreActorStatesPaused())
523             return;
524         break;
525     }
526 
527     if (IsAlive())
528         _effects_supervisor->Update();
529 
530     // Don't update the state_timer if the character is hurt.
531     if (_hurt_timer.IsRunning())
532         return;
533 
534     switch(_state) {
535     // Check the stun effect only when in idle, warm up or cool down state
536     case ACTOR_STATE_IDLE:
537     case ACTOR_STATE_WARM_UP:
538     case ACTOR_STATE_COOL_DOWN:
539         if (!_is_stunned)
540             _state_timer.Update();
541         break;
542     default:
543         _state_timer.Update();
544         break;
545     }
546 }
547 
Update()548 void BattleActor::Update()
549 {
550     BattleMode* BM = BattleMode::CurrentInstance();
551 
552     // Avoid updating the battle logic when finishing.
553     // This might break the character's animation.
554     switch (BM->GetState()) {
555     default:
556         break;
557     case BATTLE_STATE_VICTORY:
558     case BATTLE_STATE_DEFEAT:
559     case BATTLE_STATE_EXITING:
560         return;
561     }
562 
563     _UpdateState();
564 
565     // The shaking updates even in pause mode, so that the shaking
566     // doesn't last indefinitely in that state.
567     _hurt_timer.Update();
568 
569     _UpdateStaminaIconPosition();
570 
571     if (_state_timer.IsFinished()) {
572         switch(_state) {
573         case ACTOR_STATE_IDLE:
574             // If an action is already set for the actor,
575             // skip the command state and immediately begin the warm up state.
576             if (_action == nullptr)
577                 ChangeState(ACTOR_STATE_COMMAND);
578             else
579                 ChangeState(ACTOR_STATE_WARM_UP);
580             break;
581         case ACTOR_STATE_WARM_UP:
582             ChangeState(ACTOR_STATE_READY);
583             break;
584         case ACTOR_STATE_SHOWNOTICE:
585             ChangeState(ACTOR_STATE_NOTICEDONE);
586             break;
587         case ACTOR_STATE_COOL_DOWN:
588             ChangeState(ACTOR_STATE_IDLE);
589             break;
590         case ACTOR_STATE_DYING:
591             ChangeState(ACTOR_STATE_DEAD);
592             break;
593         case ACTOR_STATE_REVIVE:
594             ChangeState(ACTOR_STATE_IDLE);
595             break;
596         default:
597             break;
598         }
599     }
600 }
601 
_UpdateStaminaIconPosition()602 void BattleActor::_UpdateStaminaIconPosition()
603 {
604     Position2D pos = _stamina_location;
605 
606     if(CanFight()) {
607         if(IsEnemy())
608             pos.x = STAMINA_BAR_POSITION_X + 25.0f;
609         else
610             pos.x = STAMINA_BAR_POSITION_X - 25.0f;
611     }
612 
613     switch(_state) {
614     case ACTOR_STATE_IDLE:
615         pos.y = STAMINA_LOCATION_BOTTOM + ((STAMINA_LOCATION_COMMAND - STAMINA_LOCATION_BOTTOM) *
616                                            _state_timer.PercentComplete());
617         break;
618     case ACTOR_STATE_COMMAND:
619         pos.y = STAMINA_LOCATION_COMMAND;
620         break;
621     case ACTOR_STATE_WARM_UP:
622         pos.y = STAMINA_LOCATION_COMMAND + ((STAMINA_LOCATION_TOP - STAMINA_LOCATION_COMMAND) *
623                                             _state_timer.PercentComplete());
624         break;
625     case ACTOR_STATE_READY:
626         pos.y = STAMINA_LOCATION_TOP;
627         break;
628     case ACTOR_STATE_SHOWNOTICE:
629     case ACTOR_STATE_NOTICEDONE:
630     case ACTOR_STATE_ACTING:
631         pos.y = STAMINA_LOCATION_TOP - 25.0f;
632         break;
633     case ACTOR_STATE_COOL_DOWN:
634         pos.y = STAMINA_LOCATION_BOTTOM;
635         break;
636     case ACTOR_STATE_DYING:
637         // Make the icon fall while disappearing...
638         pos.y += _state_timer.PercentComplete();
639         break;
640     default:
641         pos.y = STAMINA_LOCATION_BOTTOM + 50.0f;
642         break;
643     }
644 
645     // Add a shake effect when the battle actor has received damages
646     if(_hurt_timer.IsRunning())
647         pos.x += RandomFloat(-4.0f, 4.0f);
648 
649     _stamina_location = pos;
650 }
651 
DrawStaminaIcon(const vt_video::Color & color) const652 void BattleActor::DrawStaminaIcon(const vt_video::Color &color) const
653 {
654     if(!IsAlive())
655         return;
656 
657     VideoManager->Move(_stamina_location.x, _stamina_location.y);
658     // Make the stamina icon fade away when dying
659     if(_state == ACTOR_STATE_DYING)
660         _stamina_icon.Draw(Color(color.GetRed(), color.GetGreen(),
661                                  color.GetBlue(), color.GetAlpha() - _state_timer.PercentComplete()));
662     else
663         _stamina_icon.Draw(color);
664 }
665 
SetAction(BattleAction * action)666 void BattleActor::SetAction(BattleAction* action)
667 {
668     if(!action) {
669         IF_PRINT_WARNING(BATTLE_DEBUG) << "function received nullptr argument" << std::endl;
670         return;
671     }
672 
673     // If a previous action exists, we cancel and delete it.
674     if (_action) {
675         // Note: we do not display any warning if we are overwriting a previously set action in idle or command states.
676         // This is a valid operation in those states
677         if((_state != ACTOR_STATE_IDLE) && (_state != ACTOR_STATE_COMMAND)) {
678             IF_PRINT_WARNING(BATTLE_DEBUG) << "overwriting previously set action while in actor state: " << _state << std::endl;
679         }
680 
681         _action->Cancel();
682         delete _action;
683     }
684 
685     _action = action;
686 }
687 
SetAction(uint32_t skill_id,BattleActor * target_actor)688 void BattleActor::SetAction(uint32_t skill_id, BattleActor* target_actor)
689 {
690     const std::vector<GlobalSkill *>& actor_skills = _global_actor->GetSkills();
691 
692     GlobalSkill* skill = nullptr;
693 
694     for (uint32_t i = 0; i < actor_skills.size(); ++i) {
695         if (actor_skills[i]->GetID() == skill_id && actor_skills[i]->IsExecutableInBattle()) {
696             skill = actor_skills[i];
697             break;
698         }
699     }
700 
701     if (!skill) {
702         PRINT_WARNING << "The actor has got no usable skill with ID: " << skill_id
703             << ". Its battle action failed." << std::endl;
704         ChangeState(ACTOR_STATE_IDLE);
705         return;
706     }
707 
708     if (skill->GetSPRequired() > GetSkillPoints()) {
709         PRINT_WARNING << "The skill cost of this skill: " << skill_id
710             << " was too high. The battle action failed" << std::endl;
711         ChangeState(ACTOR_STATE_IDLE);
712         return;
713     }
714 
715     BattleTarget target;
716     GLOBAL_TARGET target_type = skill->GetTargetType();
717 
718     // Auto-adjust target for self directed skills.
719     if (target_type == GLOBAL_TARGET_SELF || target_type == GLOBAL_TARGET_SELF_POINT)
720         target_actor = this;
721 
722     switch(target_type) {
723     case GLOBAL_TARGET_ALL_FOES:
724     case GLOBAL_TARGET_ALL_ALLIES:
725         target.SetTarget(this, target_type);
726         break;
727 
728     case GLOBAL_TARGET_FOE:
729     case GLOBAL_TARGET_SELF:
730     case GLOBAL_TARGET_ALLY:
731     case GLOBAL_TARGET_ALLY_EVEN_DEAD:
732     case GLOBAL_TARGET_DEAD_ALLY_ONLY:
733         target.SetTarget(this, target_type, target_actor);
734         break;
735 
736     case GLOBAL_TARGET_SELF_POINT:
737     case GLOBAL_TARGET_FOE_POINT:
738     case GLOBAL_TARGET_ALLY_POINT: {
739         // Select a random attack point on the target
740         uint32_t num_points = target_actor->GetAttackPoints().size();
741         uint32_t point_target = 0;
742         if(num_points == 1)
743             point_target = 0;
744         else
745             point_target = RandomBoundedInteger(0, num_points - 1);
746 
747         target.SetTarget(this, target_type, target_actor, point_target);
748         break;
749     }
750 
751     default:
752         PRINT_ERROR << "Invalid target type in SetAction()" << std::endl;
753         ChangeState(ACTOR_STATE_IDLE);
754         return;
755         break;
756     }
757 
758     SetAction(new SkillAction(this, target, skill));
759     ChangeState(ACTOR_STATE_WARM_UP);
760 }
761 
SetStamina(uint32_t base)762 void BattleActor::SetStamina(uint32_t base)
763 {
764     GlobalActor::SetStamina(base);
765     BattleMode::CurrentInstance()->SetActorIdleStateTime(this);
766 }
767 
SetStaminaModifier(float modifier)768 void BattleActor::SetStaminaModifier(float modifier)
769 {
770     GlobalActor::SetStaminaModifier(modifier);
771     BattleMode::CurrentInstance()->SetActorIdleStateTime(this);
772 }
773 
_LoadDeathAnimationScript()774 void BattleActor::_LoadDeathAnimationScript()
775 {
776     // Loads potential death animation script functions
777     if (_global_actor->GetDeathScriptFilename().empty())
778         return;
779 
780     std::string filename = _global_actor->GetDeathScriptFilename();
781 
782     std::string tablespace = ScriptEngine::GetTableSpace(filename);
783     ScriptManager->DropGlobalTable(tablespace);
784 
785     if(!_death_script.OpenFile(filename))
786         return;
787 
788     if(_death_script.OpenTablespace().empty()) {
789         PRINT_ERROR << "The actor death script file: " << filename
790                     << "has got no valid namespace" << std::endl;
791         _death_script.CloseFile();
792         return;
793     }
794 
795     _death_init = _death_script.ReadFunctionPointer("Initialize");
796     _death_update = _death_script.ReadFunctionPointer("Update");
797     _death_draw_on_sprite = _death_script.ReadFunctionPointer("DrawOnSprite");
798 }
799 
_LoadAIScript()800 void BattleActor::_LoadAIScript()
801 {
802     std::string filename = _global_actor->GetBattleAIScriptFilename();
803 
804     if (filename.empty())
805         return;
806 
807     std::string tablespace = ScriptEngine::GetTableSpace(filename);
808     ScriptManager->DropGlobalTable(tablespace);
809 
810     if(!_ai_script.OpenFile(filename))
811         return;
812 
813     if(_ai_script.OpenTablespace().empty()) {
814         PRINT_ERROR << "The actor battle AI script file: " << filename
815                     << "has got no valid namespace" << std::endl;
816         _ai_script.CloseFile();
817         return;
818     }
819 
820     _ai_decide_action = _ai_script.ReadFunctionPointer("DecideAction");
821 }
822 
_DecideAction()823 void BattleActor::_DecideAction()
824 {
825     const std::vector<GlobalSkill *>& actor_skills = _global_actor->GetSkills();
826     std::vector<GlobalSkill *> usable_skills;
827     std::vector<GlobalSkill*>::const_iterator skill_it = actor_skills.begin();
828     while(skill_it != actor_skills.end()) {
829         if((*skill_it)->IsExecutableInBattle() && (*skill_it)->GetSPRequired() <= GetSkillPoints())
830             usable_skills.push_back(*skill_it);
831         ++skill_it;
832     }
833 
834     if(usable_skills.empty()) {
835         IF_PRINT_WARNING(BATTLE_DEBUG) << "The actor had no usable skills" << std::endl;
836         ChangeState(ACTOR_STATE_IDLE);
837         return;
838     }
839 
840     BattleMode* BM = BattleMode::CurrentInstance();
841     // If this function is used by an enemy, then enemies and characters must be swapped,
842     // as the roles are inversed.
843     std::deque<BattleActor *>& characters = IsEnemy() ? BM->GetEnemyParty() : BM->GetCharacterParty();
844     std::deque<BattleActor *>& enemies = IsEnemy() ? BM->GetCharacterParty() : BM->GetEnemyParty();
845 
846     std::deque<BattleActor *> alive_characters;
847     std::deque<BattleActor *> dead_characters;
848     std::deque<BattleActor *>::const_iterator it = characters.begin();
849     while(it != characters.end()) {
850         if((*it)->IsAlive())
851             alive_characters.push_back(*it);
852         else
853             dead_characters.push_back(*it);
854         ++it;
855     }
856     if(alive_characters.empty()) {
857         ChangeState(ACTOR_STATE_IDLE);
858         return;
859     }
860 
861     // and the enemies depending on their state
862     std::deque<BattleActor *> alive_enemies;
863     it = enemies.begin();
864     while(it != enemies.end()) {
865         if((*it)->IsAlive())
866             alive_enemies.push_back(*it);
867         ++it;
868     }
869 
870     if(alive_enemies.empty()) {
871         ChangeState(ACTOR_STATE_IDLE);
872         return;
873     }
874 
875     // Targeting members
876     BattleTarget target;
877     BattleActor* actor_target = nullptr;
878 
879     // Select a random skill to use
880     uint32_t skill_index = 0;
881     if(usable_skills.size() > 1)
882         skill_index = RandomBoundedInteger(0, usable_skills.size() - 1);
883     GlobalSkill* skill = usable_skills.at(skill_index);
884 
885     // Select the target
886     GLOBAL_TARGET target_type = skill->GetTargetType();
887     switch(target_type) {
888     case GLOBAL_TARGET_FOE_POINT:
889     case GLOBAL_TARGET_FOE:
890         // Select a random living enemy
891         if(alive_enemies.size() == 1)
892             actor_target = alive_enemies[0];
893         else
894             actor_target = alive_enemies[RandomBoundedInteger(0, alive_enemies.size() - 1)];
895         break;
896     case GLOBAL_TARGET_SELF_POINT:
897     case GLOBAL_TARGET_SELF:
898         actor_target = this;
899         break;
900     case GLOBAL_TARGET_ALLY_POINT:
901     case GLOBAL_TARGET_ALLY:
902         // Select a random living character
903         if(alive_characters.size() == 1)
904             actor_target = alive_characters[0];
905         else
906             actor_target = alive_characters[RandomBoundedInteger(0, alive_characters.size() - 1)];
907         break;
908     case GLOBAL_TARGET_ALLY_EVEN_DEAD:
909         // Select a random ally, living or not
910         if(characters.size() == 1)
911             actor_target = characters[0];
912         else
913             actor_target = characters[RandomBoundedInteger(0, characters.size() - 1)];
914         break;
915     case GLOBAL_TARGET_DEAD_ALLY_ONLY:
916         if (dead_characters.empty()) {
917             // Abort the skill since there is no valid targets.
918             ChangeState(ACTOR_STATE_IDLE);
919             return;
920         }
921 
922         // Select a random ally, living or not
923         if(dead_characters.size() == 1)
924             actor_target = dead_characters[0];
925         else
926             actor_target = dead_characters[RandomBoundedInteger(0, dead_characters.size() - 1)];
927         break;
928     case GLOBAL_TARGET_ALL_FOES:
929     case GLOBAL_TARGET_ALL_ALLIES:
930         // Nothing to do here, the party deques are ready
931         break;
932     default:
933         PRINT_WARNING << "Unsupported enemy skill type found." << std::endl;
934         ChangeState(ACTOR_STATE_IDLE);
935         return;
936         break;
937     }
938 
939     // Potentially select the target point and finish targeting
940     switch(target_type) {
941     case GLOBAL_TARGET_SELF_POINT:
942     case GLOBAL_TARGET_FOE_POINT:
943     case GLOBAL_TARGET_ALLY_POINT: {
944         // Select a random attack point on the target
945         uint32_t num_points = actor_target->GetAttackPoints().size();
946         uint32_t point_target = 0;
947         if(num_points == 1)
948             point_target = 0;
949         else
950             point_target = RandomBoundedInteger(0, num_points - 1);
951 
952         target.SetTarget(this, target_type, actor_target, point_target);
953         break;
954     }
955 
956     case GLOBAL_TARGET_FOE:
957     case GLOBAL_TARGET_SELF:
958     case GLOBAL_TARGET_ALLY:
959     case GLOBAL_TARGET_ALLY_EVEN_DEAD:
960     case GLOBAL_TARGET_DEAD_ALLY_ONLY:
961         target.SetTarget(this, target_type, actor_target);
962         break;
963     case GLOBAL_TARGET_ALL_FOES: // Supported at script level
964         target.SetTarget(this, target_type, enemies.at(0));
965         break;
966     case GLOBAL_TARGET_ALL_ALLIES: // Supported at script level
967         target.SetTarget(this, target_type, characters.at(0));
968         break;
969     default:
970         PRINT_WARNING << "Unsupported enemy skill type found." << std::endl;
971         ChangeState(ACTOR_STATE_IDLE);
972         return;
973         break;
974     }
975 
976     SetAction(new SkillAction(this, target, skill));
977     ChangeState(ACTOR_STATE_WARM_UP);
978 }
979 
980 } // namespace private_battle
981 
982 } // namespace vt_battle
983