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