1 /*
2 Copyright © 2012-2016 Justin Jacobs
3
4 This file is part of FLARE.
5
6 FLARE is free software: you can redistribute it and/or modify it under the terms
7 of the GNU General Public License as published by the Free Software Foundation,
8 either version 3 of the License, or (at your option) any later version.
9
10 FLARE is distributed in the hope that it will be useful, but WITHOUT ANY
11 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12 PARTICULAR PURPOSE. See the GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along with
15 FLARE. If not, see http://www.gnu.org/licenses/
16 */
17
18 /**
19 * class EffectManager
20 */
21
22 #include "Animation.h"
23 #include "AnimationManager.h"
24 #include "AnimationSet.h"
25 #include "EffectManager.h"
26 #include "EngineSettings.h"
27 #include "Hazard.h"
28 #include "PowerManager.h"
29 #include "Settings.h"
30 #include "SharedGameResources.h"
31 #include "SharedResources.h"
32 #include "Stats.h"
33
EffectDef()34 EffectDef::EffectDef()
35 : id("")
36 , type(Effect::NONE)
37 , name("")
38 , icon(-1)
39 , animation("")
40 , can_stack(true)
41 , max_stacks(-1)
42 , group_stack(false)
43 , render_above(false)
44 , color_mod(255, 255, 255)
45 , alpha_mod(255)
46 , attack_speed_anim("") {
47 }
48
Effect()49 Effect::Effect()
50 : id("")
51 , name("")
52 , icon(-1)
53 , timer()
54 , type(Effect::NONE)
55 , magnitude(0)
56 , magnitude_max(0)
57 , animation_name("")
58 , animation(NULL)
59 , item(false)
60 , trigger(-1)
61 , render_above(false)
62 , passive_id(0)
63 , source_type(Power::SOURCE_TYPE_HERO)
64 , group_stack(false)
65 , color_mod(Color(255,255,255).encodeRGBA())
66 , alpha_mod(255)
67 , attack_speed_anim("") {
68 }
69
Effect(const Effect & other)70 Effect::Effect(const Effect& other) {
71 animation = NULL;
72 *this = other;
73 }
74
operator =(const Effect & other)75 Effect& Effect::operator=(const Effect& other) {
76 if (this == &other)
77 return *this;
78
79 unloadAnimation();
80 animation_name = other.animation_name;
81 loadAnimation(animation_name);
82 if (animation && other.animation)
83 animation->syncTo(other.animation);
84
85 id = other.id;
86 name = other.name;
87 icon = other.icon;
88 timer = other.timer;
89 type = other.type;
90 magnitude = other.magnitude;
91 magnitude_max = other.magnitude_max;
92 item = other.item;
93 trigger = other.trigger;
94 render_above = other.render_above;
95 passive_id = other.passive_id;
96 source_type = other.source_type;
97 group_stack = other.group_stack;
98 color_mod = other.color_mod;
99 alpha_mod = other.alpha_mod;
100 attack_speed_anim = other.attack_speed_anim;
101
102 return *this;
103 }
104
~Effect()105 Effect::~Effect() {
106 unloadAnimation();
107 }
108
loadAnimation(const std::string & s)109 void Effect::loadAnimation(const std::string &s) {
110 if (!s.empty()) {
111 animation_name = s;
112 anim->increaseCount(animation_name);
113 AnimationSet *animationSet = anim->getAnimationSet(animation_name);
114 animation = animationSet->getAnimation("");
115 }
116 }
117
unloadAnimation()118 void Effect::unloadAnimation() {
119 if (animation) {
120 if (!animation_name.empty())
121 anim->decreaseCount(animation_name);
122 delete animation;
123 animation = NULL;
124 }
125 }
126
getTypeFromString(const std::string & type_str)127 int Effect::getTypeFromString(const std::string& type_str) {
128 if (type_str.empty()) return Effect::NONE;
129
130 if (type_str == "damage") return Effect::DAMAGE;
131 else if (type_str == "damage_percent") return Effect::DAMAGE_PERCENT;
132 else if (type_str == "hpot") return Effect::HPOT;
133 else if (type_str == "hpot_percent") return Effect::HPOT_PERCENT;
134 else if (type_str == "mpot") return Effect::MPOT;
135 else if (type_str == "mpot_percent") return Effect::MPOT_PERCENT;
136 else if (type_str == "speed") return Effect::SPEED;
137 else if (type_str == "attack_speed") return Effect::ATTACK_SPEED;
138 else if (type_str == "immunity") return Effect::IMMUNITY;
139 else if (type_str == "immunity_damage") return Effect::IMMUNITY_DAMAGE;
140 else if (type_str == "immunity_slow") return Effect::IMMUNITY_SLOW;
141 else if (type_str == "immunity_stun") return Effect::IMMUNITY_STUN;
142 else if (type_str == "immunity_hp_steal") return Effect::IMMUNITY_HP_STEAL;
143 else if (type_str == "immunity_mp_steal") return Effect::IMMUNITY_MP_STEAL;
144 else if (type_str == "immunity_knockback") return Effect::IMMUNITY_KNOCKBACK;
145 else if (type_str == "immunity_damage_reflect") return Effect::IMMUNITY_DAMAGE_REFLECT;
146 else if (type_str == "immunity_stat_debuff") return Effect::IMMUNITY_STAT_DEBUFF;
147 else if (type_str == "stun") return Effect::STUN;
148 else if (type_str == "revive") return Effect::REVIVE;
149 else if (type_str == "convert") return Effect::CONVERT;
150 else if (type_str == "fear") return Effect::FEAR;
151 else if (type_str == "death_sentence") return Effect::DEATH_SENTENCE;
152 else if (type_str == "shield") return Effect::SHIELD;
153 else if (type_str == "heal") return Effect::HEAL;
154 else if (type_str == "knockback") return Effect::KNOCKBACK;
155 else {
156 for (int i=0; i<Stats::COUNT; ++i) {
157 if (type_str == Stats::KEY[i]) {
158 return Effect::TYPE_COUNT + i;
159 }
160 }
161
162 for (size_t i=0; i<eset->damage_types.list.size(); ++i) {
163 if (type_str == eset->damage_types.list[i].min) {
164 return Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(i*2);
165 }
166 else if (type_str == eset->damage_types.list[i].max) {
167 return Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(i*2) + 1;
168 }
169 }
170
171 for (size_t i=0; i<eset->elements.list.size(); ++i) {
172 if (type_str == eset->elements.list[i].id + "_resist") {
173 return Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count + i);
174 }
175 }
176
177 for (size_t i=0; i<eset->primary_stats.list.size(); ++i) {
178 if (type_str == eset->primary_stats.list[i].id) {
179 return Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) + static_cast<int>(eset->elements.list.size() + i);
180 }
181 }
182 }
183
184 Utils::logError("EffectManager: '%s' is not a valid effect type.", type_str.c_str());
185 return Effect::NONE;
186 }
187
typeIsStat(int t)188 bool Effect::typeIsStat(int t) {
189 return t >= Effect::TYPE_COUNT && t < Effect::TYPE_COUNT + Stats::COUNT;
190 }
191
typeIsDmgMin(int t)192 bool Effect::typeIsDmgMin(int t) {
193 return t >= Effect::TYPE_COUNT + Stats::COUNT && t < Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) && (t - Stats::COUNT - Effect::TYPE_COUNT) % 2 == 0;
194 }
195
typeIsDmgMax(int t)196 bool Effect::typeIsDmgMax(int t) {
197 return t >= Effect::TYPE_COUNT + Stats::COUNT && t < Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) && (t - Stats::COUNT - Effect::TYPE_COUNT) % 2 == 1;
198 }
199
typeIsResist(int t)200 bool Effect::typeIsResist(int t) {
201 return t >= Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) && t < Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) + static_cast<int>(eset->elements.list.size());
202 }
203
typeIsPrimary(int t)204 bool Effect::typeIsPrimary(int t) {
205 return t >= Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) + static_cast<int>(eset->elements.list.size()) && t < Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) + static_cast<int>(eset->elements.list.size()) + static_cast<int>(eset->primary_stats.list.size());
206 }
207
getStatFromType(int t)208 int Effect::getStatFromType(int t) {
209 return t - Effect::TYPE_COUNT;
210 }
211
getDmgFromType(int t)212 size_t Effect::getDmgFromType(int t) {
213 return static_cast<size_t>(t - Effect::TYPE_COUNT - Stats::COUNT);
214 }
215
getResistFromType(int t)216 size_t Effect::getResistFromType(int t) {
217 return static_cast<size_t>(t - Effect::TYPE_COUNT - Stats::COUNT) - eset->damage_types.count;
218 }
219
getPrimaryFromType(int t)220 size_t Effect::getPrimaryFromType(int t) {
221 return static_cast<size_t>(t - Effect::TYPE_COUNT - Stats::COUNT) - eset->damage_types.count - eset->elements.list.size();
222 }
223
EffectManager()224 EffectManager::EffectManager()
225 : bonus(std::vector<int>(Stats::COUNT + eset->damage_types.count, 0))
226 , bonus_resist(std::vector<int>(eset->elements.list.size(), 0))
227 , bonus_primary(std::vector<int>(eset->primary_stats.list.size(), 0))
228 , triggered_others(false)
229 , triggered_block(false)
230 , triggered_hit(false)
231 , triggered_halfdeath(false)
232 , triggered_joincombat(false)
233 , triggered_death(false)
234 , refresh_stats(false) {
235 clearStatus();
236 }
237
~EffectManager()238 EffectManager::~EffectManager() {
239 }
240
clearStatus()241 void EffectManager::clearStatus() {
242 damage = 0;
243 damage_percent = 0;
244 hpot = 0;
245 hpot_percent = 0;
246 mpot = 0;
247 mpot_percent = 0;
248 speed = 100;
249 immunity_damage = false;
250 immunity_slow = false;
251 immunity_stun = false;
252 immunity_hp_steal = false;
253 immunity_mp_steal = false;
254 immunity_knockback = false;
255 immunity_damage_reflect = false;
256 immunity_stat_debuff = false;
257 stun = false;
258 revive = false;
259 convert = false;
260 death_sentence = false;
261 fear = false;
262 knockback_speed = 0;
263
264 for (unsigned i=0; i<Stats::COUNT + eset->damage_types.count; i++) {
265 bonus[i] = 0;
266 }
267
268 for (unsigned i=0; i<bonus_resist.size(); i++) {
269 bonus_resist[i] = 0;
270 }
271
272 for (unsigned i=0; i<bonus_primary.size(); i++) {
273 bonus_primary[i] = 0;
274 }
275 }
276
logic()277 void EffectManager::logic() {
278 clearStatus();
279
280 for (size_t i=0; i<effect_list.size(); ++i) {
281 Effect& ei = effect_list[i];
282
283 // @CLASS EffectManager|Description of "type" in powers/effects.txt
284 // expire timed effects and total up magnitudes of active effects
285 if (ei.timer.getDuration() > 0) {
286 if (ei.timer.isEnd()) {
287 //death sentence is only applied at the end of the timer
288 // @TYPE death_sentence|Causes sudden death at the end of the effect duration.
289 if (ei.type == Effect::DEATH_SENTENCE) death_sentence = true;
290 removeEffect(i);
291 i--;
292 continue;
293 }
294 }
295
296 bool do_timed_effect = ei.timer.isWholeSecond() || (ei.timer.getDuration() < settings->max_frames_per_sec && ei.timer.isBegin());
297
298 // @TYPE damage|Damage per second
299 if (ei.type == Effect::DAMAGE && do_timed_effect) damage += ei.magnitude;
300 // @TYPE damage_percent|Damage per second (percentage of max HP)
301 else if (ei.type == Effect::DAMAGE_PERCENT && do_timed_effect) damage_percent += ei.magnitude;
302 // @TYPE hpot|HP restored per second
303 else if (ei.type == Effect::HPOT && do_timed_effect) hpot += ei.magnitude;
304 // @TYPE hpot_percent|HP restored per second (percentage of max HP)
305 else if (ei.type == Effect::HPOT_PERCENT && do_timed_effect) hpot_percent += ei.magnitude;
306 // @TYPE mpot|MP restored per second
307 else if (ei.type == Effect::MPOT && do_timed_effect) mpot += ei.magnitude;
308 // @TYPE mpot_percent|MP restored per second (percentage of max MP)
309 else if (ei.type == Effect::MPOT_PERCENT && do_timed_effect) mpot_percent += ei.magnitude;
310 // @TYPE speed|Changes movement speed. A magnitude of 100 is 100% speed (aka normal speed).
311 else if (ei.type == Effect::SPEED) speed = (static_cast<float>(ei.magnitude) * speed) / 100.f;
312 // @TYPE attack_speed|Changes attack speed. A magnitude of 100 is 100% speed (aka normal speed).
313 // attack speed is calculated when getAttackSpeed() is called
314
315 // @TYPE immunity|Applies all immunity effects. Magnitude is ignored.
316 else if (ei.type == Effect::IMMUNITY) {
317 immunity_damage = true;
318 immunity_slow = true;
319 immunity_stun = true;
320 immunity_hp_steal = true;
321 immunity_mp_steal = true;
322 immunity_knockback = true;
323 immunity_damage_reflect = true;
324 immunity_stat_debuff = true;
325 }
326 // @TYPE immunity_damage|Removes and prevents damage over time. Magnitude is ignored.
327 else if (ei.type == Effect::IMMUNITY_DAMAGE) immunity_damage = true;
328 // @TYPE immunity_slow|Removes and prevents slow effects. Magnitude is ignored.
329 else if (ei.type == Effect::IMMUNITY_SLOW) immunity_slow = true;
330 // @TYPE immunity_stun|Removes and prevents stun effects. Magnitude is ignored.
331 else if (ei.type == Effect::IMMUNITY_STUN) immunity_stun = true;
332 // @TYPE immunity_hp_steal|Prevents HP stealing. Magnitude is ignored.
333 else if (ei.type == Effect::IMMUNITY_HP_STEAL) immunity_hp_steal = true;
334 // @TYPE immunity_mp_steal|Prevents MP stealing. Magnitude is ignored.
335 else if (ei.type == Effect::IMMUNITY_MP_STEAL) immunity_mp_steal = true;
336 // @TYPE immunity_knockback|Removes and prevents knockback effects. Magnitude is ignored.
337 else if (ei.type == Effect::IMMUNITY_KNOCKBACK) immunity_knockback = true;
338 // @TYPE immunity_damage_reflect|Prevents damage reflection. Magnitude is ignored.
339 else if (ei.type == Effect::IMMUNITY_DAMAGE_REFLECT) immunity_damage_reflect = true;
340 // @TYPE immunity_stat_debuff|Prevents stat value altering effects that have a magnitude less than 0. Magnitude is ignored.
341 else if (ei.type == Effect::IMMUNITY_STAT_DEBUFF) immunity_stat_debuff = true;
342
343 // @TYPE stun|Can't move or attack. Being attacked breaks stun.
344 else if (ei.type == Effect::STUN) stun = true;
345 // @TYPE revive|Revives the player. Typically attached to a power that triggers when the player dies.
346 else if (ei.type == Effect::REVIVE) revive = true;
347 // @TYPE convert|Causes an enemy or an ally to switch allegiance
348 else if (ei.type == Effect::CONVERT) convert = true;
349 // @TYPE fear|Causes enemies to run away
350 else if (ei.type == Effect::FEAR) fear = true;
351 // @TYPE knockback|Pushes the target away from the source caster. Speed is the given value divided by the framerate cap.
352 else if (ei.type == Effect::KNOCKBACK) knockback_speed = static_cast<float>(ei.magnitude)/static_cast<float>(settings->max_frames_per_sec);
353
354 // @TYPE ${STATNAME}|Increases ${STATNAME}, where ${STATNAME} is any of the base stats. Examples: hp, avoidance, xp_gain
355 // @TYPE ${DAMAGE_TYPE}|Increases a damage min or max, where ${DAMAGE_TYPE} is any 'min' or 'max' value found in engine/damage_types.txt. Example: dmg_melee_min
356 else if (ei.type >= Effect::TYPE_COUNT && ei.type < Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count)) {
357 bonus[ei.type - Effect::TYPE_COUNT] += ei.magnitude;
358 }
359 // @TYPE ${ELEMENT}_resist|Increase Resistance % to ${ELEMENT}, where ${ELEMENT} is any found in engine/elements.txt. Example: fire_resist
360 else if (ei.type >= Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) && ei.type < Effect::TYPE_COUNT + Stats::COUNT + static_cast<int>(eset->damage_types.count) + static_cast<int>(eset->elements.list.size())) {
361 bonus_resist[ei.type - Effect::TYPE_COUNT - Stats::COUNT - eset->damage_types.count] += ei.magnitude;
362 }
363 // @TYPE ${PRIMARYSTAT}|Increases ${PRIMARYSTAT}, where ${PRIMARYSTAT} is any of the primary stats defined in engine/primary_stats.txt. Example: physical
364 else if (ei.type >= Effect::TYPE_COUNT) {
365 bonus_primary[ei.type - Effect::TYPE_COUNT - Stats::COUNT - eset->damage_types.count - eset->elements.list.size()] += ei.magnitude;
366 }
367
368 ei.timer.tick();
369
370 // expire shield effects
371 if (ei.magnitude_max > 0 && ei.magnitude == 0) {
372 // @TYPE shield|Create a damage absorbing barrier based on Mental damage stat. Duration is ignored.
373 if (ei.type == Effect::SHIELD) {
374 removeEffect(i);
375 i--;
376 continue;
377 }
378 }
379 // expire effects based on animations
380 if ((ei.animation && ei.animation->isLastFrame()) || !ei.animation) {
381 // @TYPE heal|Restore HP based on Mental damage stat.
382 if (ei.type == Effect::HEAL) {
383 removeEffect(i);
384 i--;
385 continue;
386 }
387 }
388
389 // animate
390 if (ei.animation) {
391 if (!ei.animation->isCompleted())
392 ei.animation->advanceFrame();
393 }
394 }
395 }
396
addEffect(EffectDef & effect,int duration,int magnitude,int source_type,PowerID power_id)397 void EffectManager::addEffect(EffectDef &effect, int duration, int magnitude, int source_type, PowerID power_id) {
398 addEffectInternal(effect, duration, magnitude, source_type, false, power_id);
399 }
400
addItemEffect(EffectDef & effect,int duration,int magnitude)401 void EffectManager::addItemEffect(EffectDef &effect, int duration, int magnitude) {
402 // only the hero can wear items, so use Power::SOURCE_TYPE_HERO
403 addEffectInternal(effect, duration, magnitude, Power::SOURCE_TYPE_HERO, true, NO_POWER);
404 }
405
addEffectInternal(EffectDef & effect,int duration,int magnitude,int source_type,bool item,PowerID power_id)406 void EffectManager::addEffectInternal(EffectDef &effect, int duration, int magnitude, int source_type, bool item, PowerID power_id) {
407 refresh_stats = true;
408
409 // if we're already immune, don't add negative effects
410 if (immunity_damage && (effect.type == Effect::DAMAGE || effect.type == Effect::DAMAGE_PERCENT))
411 return;
412 else if (immunity_slow && effect.type == Effect::SPEED && magnitude < 100)
413 return;
414 else if (immunity_stun && effect.type == Effect::STUN)
415 return;
416 else if (immunity_knockback && effect.type == Effect::KNOCKBACK)
417 return;
418 else if (immunity_stat_debuff && effect.type > Effect::TYPE_COUNT && magnitude < 0)
419 return;
420
421 // only allow one knockback effect at a time
422 if (effect.type == Effect::KNOCKBACK && knockback_speed != 0)
423 return;
424
425 bool insert_effect = false;
426 size_t insert_pos;
427 int stacks_applied = 0;
428 int trigger = power_id > 0 ? powers->powers[power_id].passive_trigger : -1;
429 size_t passive_id = (power_id > 0 && powers->powers[power_id].passive) ? power_id : 0;
430
431 for (size_t i=effect_list.size(); i>0; i--) {
432 Effect& ei = effect_list[i-1];
433
434 // while checking only id would be sufficient, it is a slow string compare
435 // so we check the type first, which is an int compare, before taking the slow path
436 if (ei.type == effect.type && ei.id == effect.id) {
437 if (trigger > -1 && ei.trigger == trigger)
438 return; // trigger effects can only be cast once per trigger
439
440 if (!effect.can_stack) {
441 removeEffect(i-1);
442 }
443 else{
444 if (effect.type == Effect::SHIELD && effect.group_stack){
445 ei.magnitude += magnitude;
446
447 if (effect.max_stacks == -1
448 || (magnitude != 0 && ei.magnitude_max/magnitude < effect.max_stacks)){
449 ei.magnitude_max += magnitude;
450 }
451
452 if (ei.magnitude > ei.magnitude_max){
453 ei.magnitude = ei.magnitude_max;
454 }
455
456 return;
457 }
458
459 if (insert_effect == false && effect.max_stacks != -1) {
460 // to keep stackable effects together, they are inserted after the most recent matching effect
461 // otherwise, they are added to the end of the effect list
462 insert_effect = true;
463 insert_pos = i;
464 }
465
466 stacks_applied++;
467 }
468 }
469 // if we're adding an immunity effect, remove all negative effects
470 if (effect.type == Effect::IMMUNITY)
471 clearNegativeEffects();
472 else if (effect.type == Effect::IMMUNITY_DAMAGE)
473 clearNegativeEffects(Effect::IMMUNITY_DAMAGE);
474 else if (effect.type == Effect::IMMUNITY_SLOW)
475 clearNegativeEffects(Effect::IMMUNITY_SLOW);
476 else if (effect.type == Effect::IMMUNITY_STUN)
477 clearNegativeEffects(Effect::IMMUNITY_STUN);
478 else if (effect.type == Effect::IMMUNITY_KNOCKBACK)
479 clearNegativeEffects(Effect::IMMUNITY_KNOCKBACK);
480 }
481
482 Effect e;
483
484 e.id = effect.id;
485 e.name = effect.name;
486 e.icon = effect.icon;
487 e.type = effect.type;
488 e.render_above = effect.render_above;
489 e.group_stack = effect.group_stack;
490 e.color_mod = effect.color_mod.encodeRGBA();
491 e.alpha_mod = effect.alpha_mod;
492 e.attack_speed_anim = effect.attack_speed_anim;
493
494 if (!effect.animation.empty()) {
495 e.loadAnimation(effect.animation);
496 }
497
498 e.timer.setDuration(duration);
499 e.magnitude = e.magnitude_max = magnitude;
500 e.item = item;
501 e.trigger = trigger;
502 e.passive_id = passive_id;
503 e.source_type = source_type;
504
505 if (insert_effect) {
506 if (effect.max_stacks != -1 && stacks_applied >= effect.max_stacks){
507 //Remove the oldest effect of the type
508 removeEffect(insert_pos-stacks_applied);
509
510 //All elements have shifted to left
511 insert_pos--;
512 }
513
514 effect_list.insert(effect_list.begin() + insert_pos, e);
515 }
516 else {
517 effect_list.push_back(e);
518 }
519 }
520
removeEffect(size_t id)521 void EffectManager::removeEffect(size_t id) {
522 effect_list.erase(effect_list.begin()+id);
523 refresh_stats = true;
524 }
525
removeEffectType(const int type)526 void EffectManager::removeEffectType(const int type) {
527 for (size_t i=effect_list.size(); i > 0; i--) {
528 if (effect_list[i-1].type == type) removeEffect(i-1);
529 }
530 }
531
removeEffectPassive(size_t id)532 void EffectManager::removeEffectPassive(size_t id) {
533 for (size_t i=effect_list.size(); i > 0; i--) {
534 if (effect_list[i-1].passive_id == id) removeEffect(i-1);
535 }
536 }
537
removeEffectID(const std::vector<std::pair<std::string,int>> & remove_effects)538 void EffectManager::removeEffectID(const std::vector< std::pair<std::string, int> >& remove_effects) {
539 for (size_t i = 0; i < remove_effects.size(); i++) {
540 int count = remove_effects[i].second;
541 bool remove_all = (count == 0 ? true : false);
542
543 for (size_t j = effect_list.size(); j > 0; j--) {
544 if (!remove_all && count <= 0)
545 break;
546
547 if (effect_list[j-1].id == remove_effects[i].first) {
548 removeEffect(j-1);
549 count--;
550 }
551 }
552 }
553 }
554
clearEffects()555 void EffectManager::clearEffects() {
556 for (size_t i=effect_list.size(); i > 0; i--) {
557 removeEffect(i-1);
558 }
559
560 clearStatus();
561
562 // clear triggers
563 triggered_others = triggered_block = triggered_hit = triggered_halfdeath = triggered_joincombat = triggered_death = false;
564 }
565
clearNegativeEffects(int type)566 void EffectManager::clearNegativeEffects(int type) {
567 for (size_t i=effect_list.size(); i > 0; i--) {
568 if ((type == -1 || type == Effect::IMMUNITY_DAMAGE) && effect_list[i-1].type == Effect::DAMAGE)
569 removeEffect(i-1);
570 else if ((type == -1 || type == Effect::IMMUNITY_DAMAGE) && effect_list[i-1].type == Effect::DAMAGE_PERCENT)
571 removeEffect(i-1);
572 else if ((type == -1 || type == Effect::IMMUNITY_SLOW) && effect_list[i-1].type == Effect::SPEED && effect_list[i-1].magnitude_max < 100)
573 removeEffect(i-1);
574 else if ((type == -1 || type == Effect::IMMUNITY_STUN) && effect_list[i-1].type == Effect::STUN)
575 removeEffect(i-1);
576 else if ((type == -1 || type == Effect::IMMUNITY_KNOCKBACK) && effect_list[i-1].type == Effect::KNOCKBACK)
577 removeEffect(i-1);
578 else if ((type == -1 || type == Effect::IMMUNITY_STAT_DEBUFF) && effect_list[i-1].type > Effect::TYPE_COUNT && effect_list[i-1].magnitude_max < 0)
579 removeEffect(i-1);
580 }
581 }
582
clearItemEffects()583 void EffectManager::clearItemEffects() {
584 for (size_t i=effect_list.size(); i > 0; i--) {
585 if (effect_list[i-1].item) removeEffect(i-1);
586 }
587 }
588
clearTriggerEffects(int trigger)589 void EffectManager::clearTriggerEffects(int trigger) {
590 for (size_t i=effect_list.size(); i > 0; i--) {
591 if (effect_list[i-1].trigger > -1 && effect_list[i-1].trigger == trigger) removeEffect(i-1);
592 }
593 }
594
damageShields(int dmg)595 int EffectManager::damageShields(int dmg) {
596 int over_dmg = dmg;
597
598 for (unsigned i=0; i<effect_list.size(); i++) {
599 if (effect_list[i].magnitude_max > 0 && effect_list[i].type == Effect::SHIELD) {
600 effect_list[i].magnitude -= over_dmg;
601 if (effect_list[i].magnitude < 0) {
602 over_dmg = abs(effect_list[i].magnitude);
603 effect_list[i].magnitude = 0;
604 }
605 else {
606 return 0;
607 }
608 }
609 }
610
611 return over_dmg;
612 }
613
isDebuffed()614 bool EffectManager::isDebuffed() {
615 for (size_t i=effect_list.size(); i > 0; i--) {
616 if (effect_list[i-1].type == Effect::DAMAGE) return true;
617 else if (effect_list[i-1].type == Effect::DAMAGE_PERCENT) return true;
618 else if (effect_list[i-1].type == Effect::SPEED && effect_list[i-1].magnitude_max < 100) return true;
619 else if (effect_list[i-1].type == Effect::STUN) return true;
620 else if (effect_list[i-1].type == Effect::KNOCKBACK) return true;
621 else if (effect_list[i-1].type > Effect::TYPE_COUNT && effect_list[i-1].magnitude_max < 0) return true;
622 }
623 return false;
624 }
625
getCurrentColor(Color & color_mod)626 void EffectManager::getCurrentColor(Color& color_mod) {
627 uint32_t default_color = color_mod.encodeRGBA();
628 uint32_t no_color = Color(255, 255, 255).encodeRGBA();
629
630 for (size_t i=effect_list.size(); i > 0; i--) {
631 Effect& ei = effect_list[i-1];
632 if (ei.color_mod == no_color)
633 continue;
634
635 if (ei.color_mod != default_color) {
636 color_mod.decodeRGBA(ei.color_mod);
637 return;
638 }
639 }
640 }
641
getCurrentAlpha(uint8_t & alpha_mod)642 void EffectManager::getCurrentAlpha(uint8_t& alpha_mod) {
643 uint8_t default_alpha = alpha_mod;
644 uint8_t no_alpha = 255;
645
646 for (size_t i=effect_list.size(); i > 0; i--) {
647 Effect& ei = effect_list[i-1];
648 if (ei.alpha_mod == no_alpha)
649 continue;
650
651 if (ei.alpha_mod != default_alpha) {
652 alpha_mod = ei.alpha_mod;
653 return;
654 }
655 }
656 }
657
hasEffect(const std::string & id,int req_count)658 bool EffectManager::hasEffect(const std::string& id, int req_count) {
659 if (req_count <= 0)
660 return false;
661
662 int count = 0;
663
664 for (size_t i=effect_list.size(); i > 0; i--) {
665 if (effect_list[i-1].id == id)
666 count++;
667 }
668
669 return count >= req_count;
670 }
671
getAttackSpeed(const std::string & anim_name)672 float EffectManager::getAttackSpeed(const std::string& anim_name) {
673 float attack_speed = 100;
674
675 for (size_t i = 0; i < effect_list.size(); ++i) {
676 if (effect_list[i].type != Effect::ATTACK_SPEED)
677 continue;
678
679 if (effect_list[i].attack_speed_anim.empty() || effect_list[i].attack_speed_anim == anim_name) {
680 attack_speed = (static_cast<float>(effect_list[i].magnitude) * attack_speed) / 100.0f;
681 }
682 }
683
684 return attack_speed;
685 }
686
getDamageSourceType(int dmg_mode)687 int EffectManager::getDamageSourceType(int dmg_mode) {
688 if (!(dmg_mode == Effect::DAMAGE || dmg_mode == Effect::DAMAGE_PERCENT))
689 return -1;
690
691 int source_type = Power::SOURCE_TYPE_NEUTRAL;
692
693 for (size_t i = 0; i < effect_list.size(); ++i) {
694 Effect& ei = effect_list[i];
695 if (ei.type == dmg_mode) {
696 // anything other than ally source type take precedence, so we can return early
697 if (ei.source_type != Power::SOURCE_TYPE_ALLY)
698 return ei.source_type;
699
700 source_type = ei.source_type;
701 }
702 }
703
704 return source_type;
705 }
706