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
7 // and 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 "status_effects_supervisor.h"
12 
13 #include "modes/battle/battle.h"
14 #include "modes/battle/objects/battle_actor.h"
15 
16 #include "common/global/actors/global_character.h"
17 
18 #include "engine/system.h"
19 #include "engine/video/video.h"
20 
21 #include "script/script.h"
22 
23 using namespace vt_global;
24 using namespace vt_script;
25 using namespace vt_system;
26 using namespace vt_video;
27 
28 namespace vt_battle
29 {
30 
31 namespace private_battle
32 {
33 
BattleStatusEffectsSupervisor(BattleActor * actor)34 BattleStatusEffectsSupervisor::BattleStatusEffectsSupervisor(BattleActor* actor) :
35     _actor(actor)
36 {
37     // Reserve space for potential status effects,
38     _active_status_effects.resize(GLOBAL_STATUS_TOTAL, ActiveBattleStatusEffect());
39 
40     if(!actor)
41         PRINT_WARNING << "Invalid BattleActor* when initializing the Battle status effects supervisor." << std::endl;
42 
43     _infinite_text.SetText(" ∞ ");
44 }
45 
SetActiveStatusEffects(GlobalCharacter * character)46 void BattleStatusEffectsSupervisor::SetActiveStatusEffects(GlobalCharacter* character)
47 {
48     if (!character)
49         return;
50 
51     character->ResetActiveStatusEffects();
52     for(std::vector<ActiveBattleStatusEffect>::iterator it = _active_status_effects.begin();
53             it != _active_status_effects.end(); ++it) {
54         ActiveBattleStatusEffect& effect = (*it);
55         if (!effect.IsActive())
56             continue;
57 
58         // Copy the active status effect state
59         SystemTimer* timer = effect.GetTimer();
60         character->SetActiveStatusEffect(effect.GetType(), effect.GetIntensity(),
61                                          timer->GetDuration(), timer->GetTimeExpired());
62     }
63 }
64 
GetDisplayedStatusEffectNumber()65 uint32_t BattleStatusEffectsSupervisor::GetDisplayedStatusEffectNumber()
66 {
67     uint32_t applied_effects = 0;
68     for(uint32_t i = 0; i < _equipment_status_effects.size(); ++i) {
69         PassiveBattleStatusEffect& effect = _equipment_status_effects.at(i);
70         if(!effect.IsActive())
71             continue;
72         ++applied_effects;
73     }
74 
75     for(uint32_t i = 0; i < _active_status_effects.size(); ++i) {
76         ActiveBattleStatusEffect& effect = _active_status_effects[i];
77         if(!effect.IsActive())
78             continue;
79         ++applied_effects;
80     }
81     return applied_effects;
82 }
83 
_UpdatePassive()84 void BattleStatusEffectsSupervisor::_UpdatePassive()
85 {
86     for(uint32_t i = 0; i < _equipment_status_effects.size(); ++i) {
87         PassiveBattleStatusEffect& effect = _equipment_status_effects.at(i);
88 
89         if (!effect.GetUpdatePassiveFunction().is_valid())
90             continue;
91 
92         // Update the update timer if it is running
93         vt_system::SystemTimer* update_timer = effect.GetUpdateTimer();
94         bool use_update_timer = effect.IsUsingUpdateTimer();
95         if (use_update_timer) {
96             update_timer->Update();
97         }
98 
99         if (!use_update_timer || update_timer->IsFinished()) {
100 
101             // Call the update passive function
102             try {
103                 luabind::call_function<void>(effect.GetUpdatePassiveFunction(), _actor, effect.GetIntensity());
104             } catch(const luabind::error& e) {
105                 PRINT_ERROR << "Error while loading status effect BattleUpdatePassive() function" << std::endl;
106                 ScriptManager->HandleLuaError(e);
107             } catch(const luabind::cast_failed& e) {
108                 PRINT_ERROR << "Error while loading status effect BattleUpdatePassive() function" << std::endl;
109                 ScriptManager->HandleCastError(e);
110             }
111 
112             // Restart the update timer when needed
113             if (use_update_timer) {
114                 update_timer->Reset();
115                 update_timer->Run();
116             }
117         }
118     }
119 }
120 
Update()121 void BattleStatusEffectsSupervisor::Update()
122 {
123     // Do not update when states are paused
124     BattleMode* BM = BattleMode::CurrentInstance();
125     if (BM->IsInSceneMode() || BM->AreActorStatesPaused())
126         return;
127 
128     // Update the timers and state for all active status effects
129     for(uint32_t i = 0; i < _active_status_effects.size(); ++i) {
130         ActiveBattleStatusEffect& effect = _active_status_effects[i];
131         if(!effect.IsActive())
132             continue;
133 
134         bool effect_removed = false;
135 
136         vt_system::SystemTimer* effect_timer = effect.GetTimer();
137         vt_system::SystemTimer* update_timer = effect.GetUpdateTimer();
138 
139         // Update the effect time while taking in account the battle speed
140         effect_timer->Update();
141 
142         // Update the update timer if it is running
143         bool use_update_timer = effect.IsUsingUpdateTimer();
144         if (use_update_timer)
145             update_timer->Update();
146 
147         // Decrease the intensity of the status by one level when its timer expires. This may result in
148         // the status effect being removed from the actor if its intensity changes to the neutral level.
149         if(effect_timer->IsFinished()) {
150 
151             // If the intensity of the effect is at its weakest, the call that follows will remove the effect from the actor
152             effect_removed = (effect.GetIntensity() == GLOBAL_INTENSITY_POS_LESSER
153                               || effect.GetIntensity() == GLOBAL_INTENSITY_NEG_LESSER);
154 
155             // As the effect is fading, we divide the effect duration time per 2, with at least 1 second of duration.
156             // This is done to give more a fading out style onto the effect and not to advantage/disadvantage the target
157             // too much.
158             uint32_t duration = effect_timer->GetDuration() / 2;
159             effect_timer->SetDuration(duration < 1000 ? 1000 : duration);
160 
161             if (effect.GetIntensity() > GLOBAL_INTENSITY_NEUTRAL)
162                 ChangeActiveStatusEffect(effect.GetType(), GLOBAL_INTENSITY_NEG_LESSER, duration);
163             else
164                 ChangeActiveStatusEffect(effect.GetType(), GLOBAL_INTENSITY_POS_LESSER, duration);
165         }
166 
167         if (effect_removed)
168             continue;
169 
170         // Update the time left text
171         effect.UpdateTimeLeftText();
172 
173         // Update the effect according to the script function
174         if (!use_update_timer || update_timer->IsFinished()) {
175             if (effect.GetUpdateFunction().is_valid()) {
176 
177                 try {
178                     luabind::call_function<void>(effect.GetUpdateFunction(), _actor, effect);
179                 } catch(const luabind::error& e) {
180                     PRINT_ERROR << "Error while loading status effect BattleUpdate() function" << std::endl;
181                     ScriptManager->HandleLuaError(e);
182                 } catch(const luabind::cast_failed& e) {
183                     PRINT_ERROR << "Error while loading status effect BattleUpdate() function" << std::endl;
184                     ScriptManager->HandleCastError(e);
185                 }
186             }
187 
188             // If the character has his effects removed because of the effect update (when dying)
189             // The effect isn't active anymore, so we have to check this here.
190             if (!effect.IsActive())
191                 continue;
192 
193             effect.ResetIntensityChanged();
194 
195             // Restart the update timer when needed
196             if (use_update_timer) {
197                 update_timer->Reset();
198                 update_timer->Run();
199             }
200         }
201     }
202 
203     _UpdatePassive();
204 }
205 
Draw()206 void BattleStatusEffectsSupervisor::Draw()
207 {
208     // Draw in reverse to not overlap the arrow symbol
209     VideoManager->MoveRelative(6.0f * 16.0f, 0.0f);
210 
211     for(std::vector<PassiveBattleStatusEffect>::iterator it = _equipment_status_effects.begin();
212             it != _equipment_status_effects.end(); ++it) {
213         PassiveBattleStatusEffect& effect = *it;
214         if (!effect.IsActive())
215             continue;
216 
217         effect.GetIconImage()->Draw();
218         VideoManager->MoveRelative(0.0f, 5.0f);
219         _infinite_text.Draw();
220         VideoManager->MoveRelative(0.0f, -5.0f);
221         VideoManager->MoveRelative(-16.0f, 0.0f);
222     }
223 
224     for(std::vector<ActiveBattleStatusEffect>::iterator it = _active_status_effects.begin();
225             it != _active_status_effects.end(); ++it) {
226         ActiveBattleStatusEffect& effect = *it;
227         if (!effect.IsActive())
228             continue;
229 
230         effect.GetIconImage()->Draw();
231 
232         // Draw remaining effect time
233         vt_system::SystemTimer* effect_timer = effect.GetTimer();
234         uint32_t duration = effect_timer->GetDuration();
235         uint32_t time_left = effect_timer->TimeLeft();
236         VideoManager->DrawRectangle(20.0f, 2.0f, Color::white);
237         uint32_t length_left = 20.0f / duration * time_left;
238         VideoManager->DrawRectangle(length_left, 5.0f, Color::blue);
239         VideoManager->MoveRelative(-16.0f, 0.0f);
240 
241     }
242 }
243 
DrawVertical()244 void BattleStatusEffectsSupervisor::DrawVertical()
245 {
246     for(std::vector<PassiveBattleStatusEffect>::reverse_iterator it = _equipment_status_effects.rbegin();
247             it != _equipment_status_effects.rend(); ++it) {
248         PassiveBattleStatusEffect& effect = *it;
249         if (!effect.IsActive())
250             continue;
251 
252         effect.GetIconImage()->Draw();
253         VideoManager->MoveRelative(0.0f, 5.0f);
254         _infinite_text.Draw();
255         VideoManager->MoveRelative(20.0f, -5.0f);
256         effect.GetName().Draw();
257         VideoManager->MoveRelative(-20.0f, 25.0f);
258     }
259 
260     for(std::vector<ActiveBattleStatusEffect>::reverse_iterator it = _active_status_effects.rbegin();
261             it != _active_status_effects.rend(); ++it) {
262         ActiveBattleStatusEffect& effect = *it;
263         if (!effect.IsActive())
264             continue;
265 
266         effect.GetTimeLeftText().Draw();
267         VideoManager->MoveRelative(35.0f, 0.0f);
268         effect.GetIconImage()->Draw();
269 
270         // Draw remaining effect time
271         vt_system::SystemTimer* effect_timer = effect.GetTimer();
272         uint32_t duration = effect_timer->GetDuration();
273         uint32_t time_left = effect_timer->TimeLeft();
274         VideoManager->DrawRectangle(20.0f, 2.0f, Color::white);
275         uint32_t length_left = 20.0f / duration * time_left;
276         VideoManager->DrawRectangle(length_left, 5.0f, Color::blue);
277 
278         VideoManager->MoveRelative(20.0f, 0.0f);
279         effect.GetName().Draw();
280         VideoManager->MoveRelative(-55.0f, 25.0f);
281     }
282 }
283 
RemoveAllActiveStatusEffects()284 void BattleStatusEffectsSupervisor::RemoveAllActiveStatusEffects()
285 {
286     for(uint32_t i = 0; i < _active_status_effects.size(); ++i) {
287         RemoveActiveStatusEffect((GLOBAL_STATUS)i);
288     }
289 }
290 
ChangeActiveStatusEffect(GLOBAL_STATUS status,GLOBAL_INTENSITY intensity,uint32_t duration,uint32_t elapsed_time)291 bool BattleStatusEffectsSupervisor::ChangeActiveStatusEffect(GLOBAL_STATUS status, GLOBAL_INTENSITY intensity,
292                                                  uint32_t duration, uint32_t elapsed_time)
293 {
294     if((status <= GLOBAL_STATUS_INVALID) || (status >= GLOBAL_STATUS_TOTAL)) {
295         IF_PRINT_WARNING(BATTLE_DEBUG) << "function received invalid status argument: " << status << std::endl;
296         return false;
297     }
298 
299     // Determine if we are attempting to increment or decrement the intensity of this status
300     bool increase_intensity;
301     if((intensity < GLOBAL_INTENSITY_NEUTRAL) && (intensity >= GLOBAL_INTENSITY_NEG_EXTREME)) {
302         increase_intensity = false;
303     } else if((intensity <= GLOBAL_INTENSITY_POS_EXTREME) && (intensity > GLOBAL_INTENSITY_NEUTRAL)) {
304         increase_intensity = true;
305     } else {
306         IF_PRINT_WARNING(BATTLE_DEBUG) << "function received invalid intensity argument: " << intensity << std::endl;
307         return false;
308     }
309 
310     // Holds the unsigned amount of change in intensity in either a positive or negative degree
311     uint8_t intensity_change = abs(static_cast<int8_t>(intensity));
312 
313     // Determine if this status (or its opposite) is already active on the actor
314     // Holds a reference to the active status
315     ActiveBattleStatusEffect& active_effect = _active_status_effects[status];
316 
317     // variables used to determine the intensity change of the effect.
318     GLOBAL_INTENSITY previous_intensity = active_effect.GetIntensity();
319     GLOBAL_INTENSITY new_intensity = GLOBAL_INTENSITY_INVALID;
320 
321     BattleMode* BM = BattleMode::CurrentInstance();
322     vt_mode_manager::IndicatorSupervisor& indicator = BM->GetIndicatorSupervisor();
323     float x_pos = _actor->GetXLocation();
324     float y_pos = _actor->GetYLocation() - (_actor->GetSpriteHeight() / 3 * 2);
325 
326     // Perform status changes according to the previously determined information
327     if(active_effect.IsActive()) {
328         if (increase_intensity)
329             active_effect.IncrementIntensity(intensity_change);
330         else
331             active_effect.DecrementIntensity(intensity_change);
332 
333         new_intensity = active_effect.GetIntensity();
334 
335         // If the status was decremented to the neutral level, this means it is no longer active and should be removed
336         if(new_intensity == GLOBAL_INTENSITY_NEUTRAL)
337             RemoveActiveStatusEffect(status, true);
338 
339         indicator.AddStatusIndicator(x_pos, y_pos, status, previous_intensity, new_intensity);
340         return true;
341     } else {
342         _CreateNewStatus(status, intensity, duration, elapsed_time);
343         new_intensity = intensity;
344 
345         indicator.AddStatusIndicator(x_pos, y_pos, status, previous_intensity, new_intensity);
346     }
347     return false;
348 }
349 
AddPassiveStatusEffect(vt_global::GLOBAL_STATUS status_effect,vt_global::GLOBAL_INTENSITY intensity)350 void BattleStatusEffectsSupervisor::AddPassiveStatusEffect(vt_global::GLOBAL_STATUS status_effect, vt_global::GLOBAL_INTENSITY intensity)
351 {
352     PassiveBattleStatusEffect effect(status_effect, intensity);
353     _equipment_status_effects.push_back(effect);
354 }
355 
_CreateNewStatus(GLOBAL_STATUS status,GLOBAL_INTENSITY intensity,uint32_t duration,uint32_t elapsed_time)356 void BattleStatusEffectsSupervisor::_CreateNewStatus(GLOBAL_STATUS status, GLOBAL_INTENSITY intensity,
357                                          uint32_t duration, uint32_t elapsed_time)
358 {
359     if((status <= GLOBAL_STATUS_INVALID) || (status >= GLOBAL_STATUS_TOTAL)) {
360         IF_PRINT_WARNING(BATTLE_DEBUG) << "function received invalid status argument: " << status << std::endl;
361         return;
362     }
363 
364     if((intensity <= GLOBAL_INTENSITY_INVALID) || (intensity >= GLOBAL_INTENSITY_TOTAL)) {
365         IF_PRINT_WARNING(BATTLE_DEBUG) << "function received invalid intensity argument: " << intensity << std::endl;
366         return;
367     }
368 
369     // First, delete the potential former one.
370     RemoveActiveStatusEffect(status);
371 
372     _active_status_effects[status] = ActiveBattleStatusEffect(status, intensity, duration);
373     ActiveBattleStatusEffect& new_effect = _active_status_effects[status];
374 
375     // If there is already some elapsed time, we restore it
376     if (elapsed_time > 0 && elapsed_time <= duration)
377         new_effect.GetTimer()->SetTimeExpired(elapsed_time);
378 
379     if (!new_effect.GetApplyFunction().is_valid())
380         return;
381 
382     // Call the apply script function now that this new status is active on the actor
383     try {
384         luabind::call_function<void>(new_effect.GetApplyFunction(), _actor, new_effect);
385     } catch(const luabind::error& e) {
386         PRINT_ERROR << "Error while loading status effect BattleApply() function" << std::endl;
387         ScriptManager->HandleLuaError(e);
388     } catch(const luabind::cast_failed& e) {
389         PRINT_ERROR << "Error while loading status effect BattleApply() function" << std::endl;
390         ScriptManager->HandleCastError(e);
391     }
392 }
393 
RemoveActiveStatusEffect(GLOBAL_STATUS status_effect_type,bool remove_anyway)394 void BattleStatusEffectsSupervisor::RemoveActiveStatusEffect(GLOBAL_STATUS status_effect_type, bool remove_anyway)
395 {
396     ActiveBattleStatusEffect& status_effect = _active_status_effects[status_effect_type];
397 
398     if(!remove_anyway && !status_effect.IsActive())
399         return;
400 
401     if (status_effect.GetRemoveFunction().is_valid()) {
402         try {
403             luabind::call_function<void>(status_effect.GetRemoveFunction(), _actor, status_effect);
404         } catch(const luabind::error& e) {
405             PRINT_ERROR << "Error while loading status effect BattleRemove() function" << std::endl;
406             ScriptManager->HandleLuaError(e);
407         } catch(const luabind::cast_failed& e) {
408             PRINT_ERROR << "Error while loading status effect BattleRemove() function" << std::endl;
409             ScriptManager->HandleCastError(e);
410         }
411     }
412 
413     status_effect.Disable();
414 }
415 
416 } // namespace private_battle
417 
418 } // namespace vt_battle
419