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