1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 */
20
21 #include "EffectQueue.h"
22
23 #include "ie_feats.h"
24 #include "overlays.h"
25 #include "strrefs.h"
26
27 #include "DisplayMessage.h"
28 #include "Effect.h"
29 #include "Game.h"
30 #include "GameScript/GameScript.h" // only for ID_Allegiance
31 #include "Interface.h"
32 #include "Map.h"
33 #include "SymbolMgr.h"
34 #include "Scriptable/Actor.h"
35 #include "Spell.h" //needs for the source flags bitfield
36 #include "TableMgr.h"
37 #include "System/StringBuffer.h"
38
39 #include <cstdio>
40 #include "GameData.h"
41
42 #define KIT_BASECLASS 0x4000
43
44 namespace GemRB {
45
46 static EffectDesc Opcodes[MAX_EFFECTS];
47
48 static int initialized = 0;
49 static EffectDesc *effectnames = NULL;
50 static int effectnames_count = 0;
51 static int pstflags = false;
52 static bool iwd2fx = false;
53
54 static EffectRef fx_unsummon_creature_ref = { "UnsummonCreature", -1 };
55 static EffectRef fx_ac_vs_creature_type_ref = { "ACVsCreatureType", -1 };
56 static EffectRef fx_spell_focus_ref = { "SpellFocus", -1 };
57 static EffectRef fx_spell_resistance_ref = { "SpellResistance", -1 };
58 static EffectRef fx_protection_from_display_string_ref = { "Protection:String", -1 };
59 static EffectRef fx_variable_ref = { "Variable:StoreLocalVariable", -1 };
60 static EffectRef fx_activate_spell_sequencer_ref = { "Sequencer:Activate", -1 };
61
62 // immunity effects (setters of IE_IMMUNITY)
63 static EffectRef fx_level_immunity_ref = { "Protection:SpellLevel", -1 };
64 static EffectRef fx_opcode_immunity_ref = { "Protection:Opcode", -1 }; //bg2
65 static EffectRef fx_opcode_immunity2_ref = { "Protection:Opcode2", -1 };//iwd
66 static EffectRef fx_spell_immunity_ref = { "Protection:Spell", -1 }; //bg2
67 static EffectRef fx_spell_immunity2_ref = { "Protection:Spell2", -1 };//iwd
68 static EffectRef fx_school_immunity_ref = { "Protection:School", -1 };
69 static EffectRef fx_secondary_type_immunity_ref = { "Protection:SecondaryType", -1 };
70
71 //decrementing immunity effects
72 static EffectRef fx_level_immunity_dec_ref = { "Protection:SpellLevelDec", -1 };
73 static EffectRef fx_spell_immunity_dec_ref = { "Protection:SpellDec", -1 };
74 static EffectRef fx_school_immunity_dec_ref = { "Protection:SchoolDec", -1 };
75 static EffectRef fx_secondary_type_immunity_dec_ref = { "Protection:SecondaryTypeDec", -1 };
76
77 //bounce effects
78 static EffectRef fx_projectile_bounce_ref = { "Bounce:Projectile", -1 };
79 static EffectRef fx_level_bounce_ref = { "Bounce:SpellLevel", -1 };
80 //static EffectRef fx_opcode_bounce_ref = { "Bounce:Opcode", -1 };
81 static EffectRef fx_spell_bounce_ref = { "Bounce:Spell", -1 };
82 static EffectRef fx_school_bounce_ref = { "Bounce:School", -1 };
83 static EffectRef fx_secondary_type_bounce_ref = { "Bounce:SecondaryType", -1 };
84
85 //decrementing bounce effects
86 static EffectRef fx_level_bounce_dec_ref = { "Bounce:SpellLevelDec", -1 };
87 static EffectRef fx_spell_bounce_dec_ref = { "Bounce:SpellDec", -1 };
88 static EffectRef fx_school_bounce_dec_ref = { "Bounce:SchoolDec", -1 };
89 static EffectRef fx_secondary_type_bounce_dec_ref = { "Bounce:SecondaryTypeDec", -1 };
90
91 //spelltrap (multiple decrementing immunity)
92 static EffectRef fx_spelltrap = { "SpellTrap", -1 };
93
94 //weapon immunity
95 static EffectRef fx_weapon_immunity_ref = { "Protection:Weapons", -1 };
96
match_ids(const Actor * target,int table,ieDword value)97 bool EffectQueue::match_ids(const Actor *target, int table, ieDword value)
98 {
99 if( value == 0) {
100 return true;
101 }
102
103 int a, stat;
104
105 switch (table) {
106 case 0:
107 stat = IE_FACTION; break;
108 case 1:
109 stat = IE_TEAM; break;
110 case 2: //EA
111 stat = IE_EA;
112 return GameScript::ID_Allegiance(target, value);
113 case 3: //GENERAL
114 //this is a hack to support dead only projectiles in PST
115 //if it interferes with something feel free to remove
116 if (value==GEN_DEAD) {
117 if (target->GetStat(IE_STATE_ID)&STATE_DEAD) {
118 return true;
119 }
120 }
121 stat = IE_GENERAL; break;
122 case 4: //RACE
123 stat = IE_RACE; break;
124 case 5: //CLASS
125 stat = IE_CLASS; break;
126 case 6: //SPECIFIC
127 stat = IE_SPECIFIC; break;
128 case 7: //GENDER
129 stat = IE_SEX; break;
130 case 8: //ALIGNMENT
131 stat = target->GetStat(IE_ALIGNMENT);
132 a = value&15;
133 if( a) {
134 if( a != ( stat & 15 )) {
135 return false;
136 }
137 }
138 a = value & 0xf0;
139 if( a) {
140 if( a != ( stat & 0xf0 )) {
141 return false;
142 }
143 }
144 return true;
145 case 9:
146 stat = target->GetClassMask();
147 if (value&stat) return true;
148 return false;
149 default:
150 return false;
151 }
152
153 if (stat == IE_CLASS) {
154 return target->GetActiveClass() == value;
155 }
156 return target->GetStat(stat) == value;
157 }
158
159 /*
160 static const bool fx_instant[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,true,true};
161
162 static inline bool IsInstant(ieByte timingmode)
163 {
164 if( timingmode>=MAX_TIMING_MODE) return false;
165 return fx_instant[timingmode];
166 }*/
167
168 static const bool fx_equipped[MAX_TIMING_MODE] = { false, false, true, false, false, true, false, false, true, false, false, false };
169
IsEquipped(ieByte timingmode)170 static inline bool IsEquipped(ieByte timingmode)
171 {
172 if( timingmode>=MAX_TIMING_MODE) return false;
173 return fx_equipped[timingmode];
174 }
175
176 static const bool fx_relative[MAX_TIMING_MODE] = { true, false, false, true, true, true, false, false, false, false, true, false };
177
NeedPrepare(ieWord timingmode)178 static inline bool NeedPrepare(ieWord timingmode)
179 {
180 if( timingmode>=MAX_TIMING_MODE) return false;
181 return fx_relative[timingmode];
182 }
183
184 #define INVALID -1
185 #define PERMANENT 0
186 #define DELAYED 1
187 #define DURATION 2
188
189 static const int fx_prepared[MAX_TIMING_MODE]={DURATION,PERMANENT,PERMANENT,DELAYED, //0-3
190 DELAYED, DELAYED, DELAYED, DELAYED, PERMANENT, PERMANENT, DURATION, PERMANENT}; //4-11
191
DelayType(ieByte timingmode)192 static inline int DelayType(ieByte timingmode)
193 {
194 if( timingmode>=MAX_TIMING_MODE) return INVALID;
195 return fx_prepared[timingmode];
196 }
197
198 //which effects are removable
199 static const bool fx_removable[MAX_TIMING_MODE] = { true, true, false, true, true, false, true, true, false, false, true, true };
200
IsRemovable(ieByte timingmode)201 static inline int IsRemovable(ieByte timingmode)
202 {
203 if( timingmode>=MAX_TIMING_MODE) return INVALID;
204 return fx_removable[timingmode];
205 }
206
207 //change the timing method after the effect triggered
208 static const ieByte fx_triggered[MAX_TIMING_MODE]={FX_DURATION_JUST_EXPIRED,FX_DURATION_INSTANT_PERMANENT,//0,1
209 FX_DURATION_INSTANT_WHILE_EQUIPPED,FX_DURATION_INSTANT_LIMITED,//2,3
210 FX_DURATION_INSTANT_PERMANENT,FX_DURATION_PERMANENT_UNSAVED, //4,5
211 FX_DURATION_INSTANT_LIMITED,FX_DURATION_JUST_EXPIRED,FX_DURATION_PERMANENT_UNSAVED,//6,8
212 FX_DURATION_INSTANT_PERMANENT_AFTER_BONUSES, FX_DURATION_JUST_EXPIRED, //9,10
213 FX_DURATION_JUST_EXPIRED}; //11
214
TriggeredEffect(ieByte timingmode)215 static inline ieByte TriggeredEffect(ieByte timingmode)
216 {
217 if( timingmode>=MAX_TIMING_MODE) return false;
218 return fx_triggered[timingmode];
219 }
220
compare_effects(const void * a,const void * b)221 static int compare_effects(const void *a, const void *b)
222 {
223 return stricmp(((const EffectDesc *) a)->Name,((const EffectDesc *) b)->Name);
224 }
225
find_effect(const void * a,const void * b)226 static int find_effect(const void *a, const void *b)
227 {
228 return stricmp((const char *) a,((const EffectDesc *) b)->Name);
229 }
230
FindEffect(const char * effectname)231 static EffectDesc* FindEffect(const char* effectname)
232 {
233 if( !effectname || !effectnames) {
234 return NULL;
235 }
236 void *tmp = bsearch(effectname, effectnames, effectnames_count, sizeof(EffectDesc), find_effect);
237 if( !tmp) {
238 Log(WARNING, "EffectQueue", "Couldn't assign effect: %s", effectname);
239 }
240 return (EffectDesc *) tmp;
241 }
242
ResolveEffectRef(EffectRef & effect_reference)243 static inline void ResolveEffectRef(EffectRef &effect_reference)
244 {
245 if( effect_reference.opcode==-1) {
246 EffectDesc* ref = FindEffect(effect_reference.Name);
247 if( ref && ref->opcode>=0) {
248 effect_reference.opcode = ref->opcode;
249 return;
250 }
251 effect_reference.opcode = -2;
252 }
253 }
254
Init_EffectQueue()255 bool Init_EffectQueue()
256 {
257 if( initialized) {
258 return true;
259 }
260 pstflags = !!core->HasFeature(GF_PST_STATE_FLAGS);
261 iwd2fx = !!core->HasFeature(GF_ENHANCED_EFFECTS);
262 initialized = 1;
263
264 AutoTable efftextTable("efftext");
265
266 int eT = core->LoadSymbol( "effects" );
267 if (eT < 0) {
268 Log(ERROR, "EffectQueue", "A critical scripting file is missing!");
269 return false;
270 }
271 Holder<SymbolMgr> effectsTable = core->GetSymbol( eT );
272 if (!effectsTable) {
273 Log(ERROR, "EffectQueue", "A critical scripting file is damaged!");
274 return false;
275 }
276
277 for (long i = 0; i < MAX_EFFECTS; i++) {
278 const char* effectname = effectsTable->GetValue( i );
279
280 EffectDesc* poi = FindEffect( effectname );
281 if( poi != NULL) {
282 Opcodes[i] = *poi;
283
284 //reverse linking opcode number
285 //using this unused field
286 if( (poi->opcode!=-1) && effectname[0]!='*') {
287 error("EffectQueue", "Clashing Opcodes FN: %ld vs. %d, %s\n", i, poi->opcode, effectname);
288 }
289 poi->opcode = i;
290 }
291
292 if (efftextTable) {
293 int row = efftextTable->GetRowCount();
294 while (row--) {
295 const char* ret = efftextTable->GetRowName( row );
296 long val;
297 if( valid_number( ret, val ) && (i == val) ) {
298 Opcodes[i].Strref = atoi( efftextTable->QueryField( row, 1 ) );
299 } else {
300 Opcodes[i].Strref = -1;
301 }
302 }
303 }
304 }
305 core->DelSymbol( eT );
306
307 return true;
308 }
309
EffectQueue_ReleaseMemory()310 void EffectQueue_ReleaseMemory()
311 {
312 if( effectnames) {
313 free (effectnames);
314 }
315 effectnames_count = 0;
316 effectnames = NULL;
317 }
318
EffectQueue_RegisterOpcodes(int count,const EffectDesc * opcodes)319 void EffectQueue_RegisterOpcodes(int count, const EffectDesc* opcodes)
320 {
321 if( ! effectnames) {
322 effectnames = (EffectDesc*) malloc( (count+1) * sizeof( EffectDesc ) );
323 } else {
324 effectnames = (EffectDesc*) realloc( effectnames, (effectnames_count + count + 1) * sizeof( EffectDesc ) );
325 }
326
327 std::copy(opcodes, opcodes + count, effectnames + effectnames_count);
328 effectnames_count += count;
329 effectnames[effectnames_count].Name = NULL;
330
331 //if we merge two effect lists, then we need to sort their effect tables
332 //actually, we might always want to sort this list, so there is no
333 //need to do it manually (sorted table is needed if we use bsearch)
334 qsort(effectnames, effectnames_count, sizeof(EffectDesc), compare_effects);
335 }
336
EffectQueue()337 EffectQueue::EffectQueue()
338 {
339 Owner = NULL;
340 }
341
~EffectQueue()342 EffectQueue::~EffectQueue()
343 {
344 std::list< Effect* >::iterator f;
345 for (f = effects.begin(); f != effects.end(); ++f) {
346 delete (*f);
347 }
348 }
349
CreateEffect(ieDword opcode,ieDword param1,ieDword param2,ieWord timing)350 Effect *EffectQueue::CreateEffect(ieDword opcode, ieDword param1, ieDword param2, ieWord timing)
351 {
352 if( opcode==0xffffffff) {
353 return NULL;
354 }
355 Effect *fx = new Effect();
356 if( !fx) {
357 return NULL;
358 }
359 memset(fx,0,sizeof(Effect));
360 fx->Target = FX_TARGET_SELF;
361 fx->Opcode = opcode;
362 fx->ProbabilityRangeMax = 100;
363 fx->Parameter1 = param1;
364 fx->Parameter2 = param2;
365 fx->TimingMode = timing;
366 fx->PosX = 0xffffffff;
367 fx->PosY = 0xffffffff;
368 return fx;
369 }
370
371 //return the count of effects with matching parameters
372 //useful for effects where there is no separate stat to see
CountEffects(EffectRef & effect_reference,ieDword param1,ieDword param2,const char * resource) const373 ieDword EffectQueue::CountEffects(EffectRef &effect_reference, ieDword param1, ieDword param2, const char *resource) const
374 {
375 ResolveEffectRef(effect_reference);
376 if( effect_reference.opcode<0) {
377 return 0;
378 }
379 return CountEffects(effect_reference.opcode, param1, param2, resource);
380 }
381
382 //Change the location of an existing effect
383 //this is used when some external code needs to adjust the effect's location
384 //used when the gui sets the effect's final target
ModifyEffectPoint(EffectRef & effect_reference,ieDword x,ieDword y) const385 void EffectQueue::ModifyEffectPoint(EffectRef &effect_reference, ieDword x, ieDword y) const
386 {
387 ResolveEffectRef(effect_reference);
388 if( effect_reference.opcode<0) {
389 return;
390 }
391 ModifyEffectPoint(effect_reference.opcode, x, y);
392 }
393
ModifyAllEffectSources(const Point & source)394 void EffectQueue::ModifyAllEffectSources(const Point &source)
395 {
396 std::list< Effect* >::const_iterator f;
397
398 for (f = effects.begin(); f != effects.end(); ++f) {
399 (*f)->SourceX = source.x;
400 (*f)->SourceY = source.y;
401 }
402 }
403
CreateEffect(EffectRef & effect_reference,ieDword param1,ieDword param2,ieWord timing)404 Effect *EffectQueue::CreateEffect(EffectRef &effect_reference, ieDword param1, ieDword param2, ieWord timing)
405 {
406 ResolveEffectRef(effect_reference);
407 if( effect_reference.opcode<0) {
408 return NULL;
409 }
410 return CreateEffect(effect_reference.opcode, param1, param2, timing);
411 }
412
413 //copies the whole effectqueue (area projectiles use it)
CopySelf() const414 EffectQueue *EffectQueue::CopySelf() const
415 {
416 EffectQueue *effects;
417
418 effects = new EffectQueue();
419 std::list< Effect* >::const_iterator fxit = GetFirstEffect();
420 Effect *fx;
421
422 while( (fx = GetNextEffect(fxit))) {
423 effects->AddEffect(fx, false);
424 }
425 effects->SetOwner(GetOwner());
426 return effects;
427 }
428
429 //create a new effect with most of the characteristics of the old effect
430 //only opcode and parameters are changed
431 //This is used mostly inside effects, when an effect needs to spawn
432 //other effects with the same coordinates, source, duration, etc.
CreateEffectCopy(const Effect * oldfx,ieDword opcode,ieDword param1,ieDword param2)433 Effect *EffectQueue::CreateEffectCopy(const Effect *oldfx, ieDword opcode, ieDword param1, ieDword param2)
434 {
435 if( opcode==0xffffffff) {
436 return NULL;
437 }
438 Effect *fx = new Effect();
439 if( !fx) {
440 return NULL;
441 }
442 memcpy(fx,oldfx,sizeof(Effect) );
443 fx->Opcode=opcode;
444 fx->Parameter1=param1;
445 fx->Parameter2=param2;
446 return fx;
447 }
448
CreateEffectCopy(const Effect * oldfx,EffectRef & effect_reference,ieDword param1,ieDword param2)449 Effect *EffectQueue::CreateEffectCopy(const Effect *oldfx, EffectRef &effect_reference, ieDword param1, ieDword param2)
450 {
451 ResolveEffectRef(effect_reference);
452 if( effect_reference.opcode<0) {
453 return NULL;
454 }
455 return CreateEffectCopy(oldfx, effect_reference.opcode, param1, param2);
456 }
457
CreateUnsummonEffect(const Effect * fx)458 Effect *EffectQueue::CreateUnsummonEffect(const Effect *fx)
459 {
460 Effect *newfx = NULL;
461 if( (fx->TimingMode&0xff) == FX_DURATION_INSTANT_LIMITED) {
462 newfx = CreateEffectCopy(fx, fx_unsummon_creature_ref, 0, 0);
463 newfx->TimingMode = FX_DURATION_DELAY_PERMANENT;
464 newfx->Target = FX_TARGET_PRESET;
465 strnuprcpy(newfx->Resource, newfx->Resource3[0] ? newfx->Resource3 : "SPGFLSH1", sizeof(ieResRef) - 1);
466 if( fx->TimingMode == FX_DURATION_ABSOLUTE) {
467 //unprepare duration
468 newfx->Duration = (newfx->Duration-core->GetGame()->GameTime)/AI_UPDATE_TIME;
469 }
470 }
471
472 return newfx;
473 }
474
AddEffect(const Effect * fx,bool insert)475 void EffectQueue::AddEffect(const Effect* fx, bool insert)
476 {
477 Effect* new_fx = new Effect;
478 memcpy( new_fx, fx, sizeof( Effect ) );
479 if( insert) {
480 effects.insert( effects.begin(), new_fx );
481 } else {
482 effects.push_back( new_fx );
483 }
484 }
485
486 //This method can remove an effect described by a pointer to it, or
487 //an exact matching effect
RemoveEffect(const Effect * fx)488 bool EffectQueue::RemoveEffect(const Effect* fx)
489 {
490 int invariant_size = offsetof( Effect, random_value );
491
492 for (std::list<Effect*>::iterator f = effects.begin(); f != effects.end(); ++f) {
493 Effect* fx2 = *f;
494
495 if( (fx==fx2) || !memcmp( fx, fx2, invariant_size)) {
496 delete fx2;
497 effects.erase( f );
498 return true;
499 }
500 }
501 return false;
502 }
503
504 //this is where we reapply all effects when loading a saved game
505 //The effects are already in the fxqueue of the target
506 //... but some require reinitialisation
ApplyAllEffects(Actor * target) const507 void EffectQueue::ApplyAllEffects(Actor* target) const
508 {
509 for (auto fx : effects) {
510 if (Opcodes[fx->Opcode].Flags & EFFECT_REINIT_ON_LOAD) {
511 // pretend to be the first application (FirstApply==1)
512 ApplyEffect(target, fx, 1);
513 } else {
514 ApplyEffect(target, fx, 0);
515 }
516 }
517 }
518
Cleanup()519 void EffectQueue::Cleanup()
520 {
521 std::list< Effect* >::iterator f;
522
523 for ( f = effects.begin(); f != effects.end(); ) {
524 if( (*f)->TimingMode == FX_DURATION_JUST_EXPIRED) {
525 delete *f;
526 effects.erase(f++);
527 } else {
528 ++f;
529 }
530 }
531 }
532
533 //Handle the target flag when the effect is applied first
AddEffect(Effect * fx,Scriptable * self,Actor * pretarget,const Point & dest) const534 int EffectQueue::AddEffect(Effect* fx, Scriptable* self, Actor* pretarget, const Point &dest) const
535 {
536 int i;
537 const Game *game;
538 const Map *map;
539 int flg;
540 ieDword spec = 0;
541 Actor *st = (self && (self->Type==ST_ACTOR)) ?(Actor *) self:NULL;
542 Effect* new_fx;
543 // HACK: 00p2229.baf in ar1006 does this silly thing, crashing later
544 if (!st && self && (self->Type==ST_CONTAINER) && (fx->Target == FX_TARGET_SELF)) {
545 fx->Target = FX_TARGET_PRESET;
546 }
547
548 if (self) {
549 fx->CasterID = self->GetGlobalID();
550 fx->SetSourcePosition(self->Pos);
551 } else if (Owner) {
552 fx->CasterID = Owner->GetGlobalID();
553 fx->SetSourcePosition(Owner->Pos);
554 }
555 if (!fx->CasterLevel) {
556 // happens for effects that we apply directly from within, not through a spell/item
557 // for example through GemRB_ApplyEffect
558 const Actor *caster = GetCasterObject();
559 if (caster) {
560 // FIXME: guessing, will be fine most of the time
561 fx->CasterLevel = caster->GetAnyActiveCasterLevel();
562 }
563 }
564
565 switch (fx->Target) {
566 case FX_TARGET_ORIGINAL:
567 assert(self != NULL);
568 fx->SetPosition(self->Pos);
569
570 flg = ApplyEffect( st, fx, 1 );
571 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
572 if( st) {
573 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
574 }
575 }
576 break;
577 case FX_TARGET_SELF:
578 fx->SetPosition(dest);
579
580 flg = ApplyEffect( st, fx, 1 );
581 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
582 if( st) {
583 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
584 }
585 }
586 break;
587
588 case FX_TARGET_ALL_BUT_SELF:
589 assert(self != NULL);
590 new_fx = new Effect;
591 map=self->GetCurrentArea();
592 i= map->GetActorCount(true);
593 while(i--) {
594 Actor* actor = map->GetActor( i, true );
595 //don't pick ourselves
596 if( st==actor) {
597 continue;
598 }
599 memcpy( new_fx, fx, sizeof( Effect ) );
600 new_fx->SetPosition(actor->Pos);
601
602 flg = ApplyEffect( actor, new_fx, 1 );
603 if( new_fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
604 actor->fxqueue.AddEffect( new_fx, flg==FX_INSERT );
605 }
606 }
607 delete new_fx;
608 flg = FX_APPLIED;
609 break;
610
611 case FX_TARGET_OWN_SIDE:
612 if( !st || st->InParty) {
613 goto all_party;
614 }
615 map = self->GetCurrentArea();
616 spec = st->GetStat(IE_SPECIFIC);
617
618 new_fx = new Effect;
619 //GetActorCount(false) returns all nonparty critters
620 i = map->GetActorCount(false);
621 while(i--) {
622 Actor* actor = map->GetActor( i, false );
623 if( actor->GetStat(IE_SPECIFIC)!=spec) {
624 continue;
625 }
626 memcpy( new_fx, fx, sizeof( Effect ) );
627 new_fx->SetPosition(actor->Pos);
628
629 flg = ApplyEffect( actor, new_fx, 1 );
630 if( new_fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
631 actor->fxqueue.AddEffect( new_fx, flg==FX_INSERT );
632 }
633 }
634 delete new_fx;
635 flg = FX_APPLIED;
636 break;
637 case FX_TARGET_OTHER_SIDE:
638 if( !pretarget || pretarget->InParty) {
639 goto all_party;
640 }
641 assert(self != NULL);
642 map = self->GetCurrentArea();
643 spec = pretarget->GetStat(IE_SPECIFIC);
644
645 new_fx = new Effect;
646 //GetActorCount(false) returns all nonparty critters
647 i = map->GetActorCount(false);
648 while(i--) {
649 Actor* actor = map->GetActor( i, false );
650 if( actor->GetStat(IE_SPECIFIC)!=spec) {
651 continue;
652 }
653 memcpy( new_fx, fx, sizeof( Effect ) );
654 new_fx->SetPosition(actor->Pos);
655
656 flg = ApplyEffect( actor, new_fx, 1 );
657 //GetActorCount can now return all nonparty critters
658 if( new_fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
659 actor->fxqueue.AddEffect( new_fx, flg==FX_INSERT );
660 }
661 }
662 delete new_fx;
663 flg = FX_APPLIED;
664 break;
665 case FX_TARGET_PRESET:
666 //fx->SetPosition(pretarget->Pos);
667 //knock needs this
668 fx->SetPosition(dest);
669
670 flg = ApplyEffect( pretarget, fx, 1 );
671 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
672 if( pretarget) {
673 pretarget->fxqueue.AddEffect( fx, flg==FX_INSERT );
674 }
675 }
676 break;
677
678 case FX_TARGET_PARTY:
679 all_party:
680 new_fx = new Effect;
681 game = core->GetGame();
682 i = game->GetPartySize(false);
683 while(i--) {
684 Actor* actor = game->GetPC( i, false );
685 memcpy( new_fx, fx, sizeof( Effect ) );
686 new_fx->SetPosition(actor->Pos);
687
688 flg = ApplyEffect( actor, new_fx, 1 );
689 if( new_fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
690 actor->fxqueue.AddEffect( new_fx, flg==FX_INSERT );
691 }
692 }
693 delete new_fx;
694 flg = FX_APPLIED;
695 break;
696
697 case FX_TARGET_ALL:
698 assert(self != NULL);
699 new_fx = new Effect;
700 map = self->GetCurrentArea();
701 i = map->GetActorCount(true);
702 while(i--) {
703 Actor* actor = map->GetActor( i, true );
704 memcpy( new_fx, fx, sizeof( Effect ) );
705 new_fx->SetPosition(actor->Pos);
706
707 flg = ApplyEffect( actor, new_fx, 1 );
708 if( new_fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
709 actor->fxqueue.AddEffect( new_fx, flg==FX_INSERT );
710 }
711 }
712 delete new_fx;
713 flg = FX_APPLIED;
714 break;
715
716 case FX_TARGET_ALL_BUT_PARTY:
717 assert(self != NULL);
718 new_fx = new Effect;
719 map = self->GetCurrentArea();
720 i = map->GetActorCount(false);
721 while(i--) {
722 Actor* actor = map->GetActor( i, false );
723 memcpy( new_fx, fx, sizeof( Effect ) );
724 new_fx->SetPosition(actor->Pos);
725
726 flg = ApplyEffect( actor, new_fx, 1 );
727 //GetActorCount can now return all nonparty critters
728 if( new_fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
729 actor->fxqueue.AddEffect( new_fx, flg==FX_INSERT );
730 }
731 }
732 delete new_fx;
733 flg = FX_APPLIED;
734 break;
735
736 case FX_TARGET_UNKNOWN:
737 default:
738 Log(MESSAGE, "EffectQueue", "Unknown FX target type: %d", fx->Target);
739 flg = FX_ABORT;
740 break;
741 }
742
743 return flg;
744 }
745
746 //this is where effects from spells first get in touch with the target
747 //the effects are currently NOT in the target's fxqueue, those that stick
748 //will get copied (hence the fxqueue.AddEffect call)
749 //if this returns FX_NOT_APPLIED, then the whole stack was resisted
750 //or expired
AddAllEffects(Actor * target,const Point & destination) const751 int EffectQueue::AddAllEffects(Actor* target, const Point &destination) const
752 {
753 int res = FX_NOT_APPLIED;
754 // pre-roll dice for fx needing them and stow them in the effect
755 ieDword random_value = core->Roll( 1, 100, -1 );
756
757 if( target) {
758 target->RollSaves();
759 }
760 std::list< Effect* >::const_iterator f;
761 for ( f = effects.begin(); f != effects.end(); f++ ) {
762 //handle resistances and saving throws here
763 (*f)->random_value = random_value;
764 //if applyeffect returns true, we stop adding the future effects
765 //this is to simulate iwd2's on the fly spell resistance
766
767 int tmp = AddEffect(*f, Owner, target, destination);
768 //lets try without Owner, any crash?
769 //If yes, then try to fix the individual effect
770 //If you use target for Owner here, the wand in chateau irenicus will work
771 //the same way as Imoen's monster summoning, which is a BAD THING (TM)
772 //int tmp = AddEffect(*f, Owner?Owner:target, target, destination);
773 if( tmp == FX_ABORT) {
774 res = FX_NOT_APPLIED;
775 break;
776 }
777 if( tmp != FX_NOT_APPLIED) {
778 res = FX_APPLIED;
779 }
780 }
781 return res;
782 }
783
784 //resisted effect based on level
check_level(const Actor * target,Effect * fx)785 static inline bool check_level(const Actor *target, Effect *fx)
786 {
787 //skip non level based effects
788 //check if an effect has no level based resistance, but instead the dice sizes/count
789 //adjusts Parameter1 (like a damage causing effect)
790 if( Opcodes[fx->Opcode].Flags & EFFECT_DICED ) {
791 //add the caster level to the dice count
792 if (fx->IsVariable) {
793 fx->DiceThrown+=fx->CasterLevel;
794 }
795 fx->Parameter1 = DICE_ROLL((signed)fx->Parameter1);
796 //this is a hack for PST style diced effects
797 if( core->HasFeature(GF_SAVE_FOR_HALF) ) {
798 if( memcmp(fx->Resource,"NEG",4) ) {
799 fx->IsSaveForHalfDamage=1;
800 }
801 } else {
802 if( (fx->Parameter2&3)==3) {
803 fx->IsSaveForHalfDamage=1;
804 }
805 }
806 return false;
807 }
808 //there is no level based resistance, but Parameter1 cannot be precalculated
809 //these effects use the Dice fields in a special way
810 if( Opcodes[fx->Opcode].Flags & EFFECT_NO_LEVEL_CHECK ) {
811 return false;
812 }
813
814 if( !target) {
815 return false;
816 }
817 if(fx->Target == FX_TARGET_SELF) {
818 return false;
819 }
820
821 ieDword level = target->GetXPLevel(true);
822 //return true if resisted
823 if ((signed)fx->MinAffectedLevel > 0) {
824 if ((signed)level < (signed)fx->MinAffectedLevel) {
825 return true;
826 }
827 }
828
829 if ((signed)fx->MaxAffectedLevel > 0) {
830 if ((signed)level > (signed)fx->MaxAffectedLevel) {
831 return true;
832 }
833 }
834 return false;
835 }
836
837 //roll for the effect probability, there is a high and a low treshold, the d100
838 //roll should hit in the middle
check_probability(const Effect * fx)839 static inline bool check_probability(const Effect* fx)
840 {
841 //random value is 0-99
842 if (fx->random_value<fx->ProbabilityRangeMin || fx->random_value>fx->ProbabilityRangeMax) {
843 return false;
844 }
845 return true;
846 }
847
848 //this is for whole spell immunity/bounce
DecreaseEffect(Effect * efx)849 static inline int DecreaseEffect(Effect *efx)
850 {
851 if (efx->Parameter1) {
852 efx->Parameter1--;
853 return true;
854 }
855 return false;
856 }
857
858 //lower decreasing immunities/bounces
check_type(const Actor * actor,const Effect * fx)859 static int check_type(const Actor *actor, const Effect *fx)
860 {
861 //the protective effect (if any)
862 Effect *efx;
863
864 ieDword bounce = actor->GetStat(IE_BOUNCE);
865
866 //spell level immunity
867 // but ignore it if we're casting beneficial stuff on ourselves
868 if(fx->Power && actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_ref, fx->Power, 0) ) {
869 const Actor *caster = core->GetGame()->GetActorByGlobalID(fx->CasterID);
870 if (caster != actor || (fx->SourceFlags & SF_HOSTILE)) {
871 Log(DEBUG, "EffectQueue", "Resisted by level immunity");
872 return 0;
873 }
874 }
875
876 //source immunity (spell name)
877 //if source is unspecified, don't resist it
878 if( fx->Source[0]) {
879 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity_ref, fx->Source) ) {
880 Log(DEBUG, "EffectQueue", "Resisted by spell immunity (%s)", fx->Source);
881 return 0;
882 }
883 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity2_ref, fx->Source) ) {
884 if (strnicmp(fx->Source, "detect", 6)) { // our secret door pervasive effect
885 Log(DEBUG, "EffectQueue", "Resisted by spell immunity2 (%s)", fx->Source);
886 }
887 return 0;
888 }
889 }
890
891 //primary type immunity (school)
892 if( fx->PrimaryType) {
893 if( actor->fxqueue.HasEffectWithParam(fx_school_immunity_ref, fx->PrimaryType)) {
894 Log(DEBUG, "EffectQueue", "Resisted by school/primary type");
895 return 0;
896 }
897 }
898
899 //secondary type immunity (usage)
900 if( fx->SecondaryType) {
901 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_ref, fx->SecondaryType) ) {
902 Log(DEBUG, "EffectQueue", "Resisted by usage/secondary type");
903 return 0;
904 }
905 }
906
907 //decrementing immunity checks
908 //decrementing level immunity
909 if (fx->Power) {
910 efx = actor->fxqueue.HasEffectWithParam(fx_level_immunity_dec_ref, fx->Power);
911 if (efx && DecreaseEffect(efx)) {
912 Log(DEBUG, "EffectQueue", "Resisted by level immunity (decrementing)");
913 return 0;
914 }
915 }
916
917 //decrementing spell immunity
918 if( fx->Source[0]) {
919 efx = actor->fxqueue.HasEffectWithResource(fx_spell_immunity_dec_ref, fx->Source);
920 if (efx && DecreaseEffect(efx)) {
921 Log(DEBUG, "EffectQueue", "Resisted by spell immunity (decrementing)");
922 return 0;
923 }
924 }
925 //decrementing primary type immunity (school)
926 if( fx->PrimaryType) {
927 efx = actor->fxqueue.HasEffectWithParam(fx_school_immunity_dec_ref, fx->PrimaryType);
928 if (efx && DecreaseEffect(efx)) {
929 Log(DEBUG, "EffectQueue", "Resisted by school immunity (decrementing)");
930 return 0;
931 }
932 }
933
934 //decrementing secondary type immunity (usage)
935 if( fx->SecondaryType) {
936 efx = actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_dec_ref, fx->SecondaryType);
937 if (efx && DecreaseEffect(efx)) {
938 Log(DEBUG, "EffectQueue", "Resisted by usage/sectype immunity (decrementing)");
939 return 0;
940 }
941 }
942
943 //spelltrap (absorb)
944 //FIXME:
945 //if the spelltrap effect already absorbed enough levels
946 //but still didn't get removed, it will absorb levels it shouldn't
947 //it will also absorb multiple spells in a single round
948 if (fx->Power) {
949 efx=actor->fxqueue.HasEffectWithParamPair(fx_spelltrap, 0, fx->Power);
950 if( efx) {
951 //storing the absorbed spell level
952 efx->Parameter3+=fx->Power;
953 //instead of a single effect, they had to create an effect for each level
954 //HOW DAMN LAME
955 //if decrease needs the spell level, use fx->Power here
956 actor->fxqueue.DecreaseParam1OfEffect(fx_spelltrap, 1);
957 //efx->Parameter1--;
958 Log(DEBUG, "EffectQueue", "Absorbed by spelltrap");
959 return 0;
960 }
961 }
962
963 //bounce checks
964 if (fx->Power) {
965 if( (bounce&BNC_LEVEL) && actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_ref, 0, fx->Power) ) {
966 Log(DEBUG, "EffectQueue", "Bounced by level");
967 return -1;
968 }
969 }
970
971 if((bounce&BNC_PROJECTILE) && actor->fxqueue.HasEffectWithParam(fx_projectile_bounce_ref, fx->Projectile)) {
972 Log(DEBUG, "EffectQueue", "Bounced by projectile");
973 return -1;
974 }
975
976 if( fx->Source[0] && (bounce&BNC_RESOURCE) && actor->fxqueue.HasEffectWithResource(fx_spell_bounce_ref, fx->Source) ) {
977 Log(DEBUG, "EffectQueue", "Bounced by resource");
978 return -1;
979 }
980
981 if( fx->PrimaryType && (bounce&BNC_SCHOOL) ) {
982 if( actor->fxqueue.HasEffectWithParam(fx_school_bounce_ref, fx->PrimaryType)) {
983 Log(DEBUG, "EffectQueue", "Bounced by school");
984 return -1;
985 }
986 }
987
988 if( fx->SecondaryType && (bounce&BNC_SECTYPE) ) {
989 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_ref, fx->SecondaryType)) {
990 Log(DEBUG, "EffectQueue", "Bounced by usage/sectype");
991 return -1;
992 }
993 }
994 //decrementing bounce checks
995
996 //level decrementing bounce check
997 if (fx->Power) {
998 if (bounce & BNC_LEVEL_DEC) {
999 efx=actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_dec_ref, 0, fx->Power);
1000 if (efx && DecreaseEffect(efx)) {
1001 Log(DEBUG, "EffectQueue", "Bounced by level (decrementing)");
1002 return -1;
1003 }
1004 }
1005 }
1006
1007 if( fx->Source[0] && (bounce&BNC_RESOURCE_DEC)) {
1008 efx=actor->fxqueue.HasEffectWithResource(fx_spell_bounce_dec_ref, fx->Resource);
1009 if (efx && DecreaseEffect(efx)) {
1010 Log(DEBUG, "EffectQueue", "Bounced by resource (decrementing)");
1011 return -1;
1012 }
1013 }
1014
1015 if( fx->PrimaryType && (bounce&BNC_SCHOOL_DEC) ) {
1016 efx=actor->fxqueue.HasEffectWithParam(fx_school_bounce_dec_ref, fx->PrimaryType);
1017 if (efx && DecreaseEffect(efx)) {
1018 Log(DEBUG, "EffectQueue", "Bounced by school (decrementing)");
1019 return -1;
1020 }
1021 }
1022
1023 if( fx->SecondaryType && (bounce&BNC_SECTYPE_DEC) ) {
1024 efx=actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_dec_ref, fx->SecondaryType);
1025 if (efx && DecreaseEffect(efx)) {
1026 Log(DEBUG, "EffectQueue", "Bounced by usage (decrementing)");
1027 return -1;
1028 }
1029 }
1030
1031 return 1;
1032 }
1033
1034 // pure magic resistance
check_magic_res(const Actor * actor,const Effect * fx,const Actor * caster)1035 static inline int check_magic_res(const Actor *actor, const Effect *fx, const Actor *caster)
1036 {
1037 //don't resist self
1038 bool selective_mr = core->HasFeature(GF_SELECTIVE_MAGIC_RES);
1039 if (fx->CasterID == actor->GetGlobalID() && selective_mr) {
1040 return -1;
1041 }
1042
1043 //magic immunity
1044 ieDword val = actor->GetStat(IE_RESISTMAGIC);
1045 bool resisted = false;
1046
1047 if (iwd2fx) {
1048 // 3ed style check
1049 int roll = core->Roll(1, 20, 0);
1050 ieDword check = fx->CasterLevel + roll;
1051 int penetration = 0;
1052 // +2/+4 level bonus from the (greater) spell penetration feat
1053 if (caster && caster->HasFeat(FEAT_SPELL_PENETRATION)) {
1054 penetration += 2 * caster->GetStat(IE_FEAT_SPELL_PENETRATION);
1055 }
1056 check += penetration;
1057 resisted = (signed) check < (signed) val;
1058 // ~Spell Resistance check (Spell resistance:) %d vs. (d20 + caster level + spell resistance mod) = %d + %d + %d.~
1059 displaymsg->DisplayRollStringName(39673, DMC_LIGHTGREY, actor, val, roll, fx->CasterLevel, penetration);
1060 } else {
1061 // 2.5 style check
1062 resisted = (signed) fx->random_value < (signed) val;
1063 }
1064 if (resisted) {
1065 // we take care of irresistible spells a few checks above, so selective mr has no impact here anymore
1066 displaymsg->DisplayConstantStringName(STR_MAGIC_RESISTED, DMC_WHITE, actor);
1067 Log(MESSAGE, "EffectQueue", "effect resisted: %s", Opcodes[fx->Opcode].Name);
1068 return FX_NOT_APPLIED;
1069 }
1070 return -1;
1071 }
1072
1073 //check resistances, saving throws
check_resistance(Actor * actor,Effect * fx)1074 static int check_resistance(Actor* actor, Effect* fx)
1075 {
1076 if (!actor) return -1;
1077
1078 const Scriptable *cob = GetCasterObject();
1079 const Actor *caster = nullptr;
1080 if (cob && cob->Type==ST_ACTOR) {
1081 caster = (const Actor *) cob;
1082 }
1083
1084 //opcode immunity
1085 // TODO: research, maybe the whole check_resistance should be skipped on caster != actor (selfapplication)
1086 if (caster != actor && actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode)) {
1087 Log(MESSAGE, "EffectQueue", "%s is immune to effect: %s", actor->GetName(1), Opcodes[fx->Opcode].Name);
1088 return FX_NOT_APPLIED;
1089 }
1090 if (caster != actor && actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode)) {
1091 Log(MESSAGE, "EffectQueue", "%s is immune2 to effect: %s", actor->GetName(1), Opcodes[fx->Opcode].Name);
1092 // totlm's spin166 should be wholly blocked by spwi210, but only blocks its third effect, so make it fatal
1093 return FX_ABORT;
1094 }
1095
1096 //not resistable (but check saves - for chromatic orb instakill)
1097 // bg2 sequencer trigger spells have bad resistance set, so ignore them
1098 if (fx->Resistance == FX_CAN_RESIST_CAN_DISPEL && signed(fx->Opcode) != EffectQueue::ResolveEffect(fx_activate_spell_sequencer_ref)) {
1099 return check_magic_res(actor, fx, caster);
1100 }
1101
1102 if (pstflags && (actor->GetSafeStat(IE_STATE_ID) & STATE_ANTIMAGIC)) {
1103 return -1;
1104 }
1105
1106 //saving throws, bonus can be improved by school specific bonus
1107 int bonus = fx->SavingThrowBonus + actor->fxqueue.BonusForParam2(fx_spell_resistance_ref, fx->PrimaryType);
1108 if (caster) {
1109 bonus += actor->fxqueue.BonusAgainstCreature(fx_ac_vs_creature_type_ref,caster);
1110 //saving throw could be made difficult by caster's school specific bonus
1111 bonus -= caster->fxqueue.BonusForParam2(fx_spell_focus_ref, fx->PrimaryType);
1112 }
1113
1114 // handle modifiers of specialist mages
1115 if (!core->HasFeature(GF_PST_STATE_FLAGS)) {
1116 int specialist = KIT_BASECLASS;
1117 if (caster) specialist = caster->GetStat(IE_KIT);
1118 if (caster && caster->GetMageLevel() && specialist != KIT_BASECLASS) {
1119 // specialist mage's enemies get a -2 penalty to saves vs the specialist's school
1120 if (specialist & (1 << (fx->PrimaryType+5))) {
1121 bonus -= 2;
1122 }
1123 }
1124
1125 // specialist mages get a +2 bonus to saves to spells of the same school used against them
1126 specialist = actor->GetStat(IE_KIT);
1127 if (actor->GetMageLevel() && specialist != KIT_BASECLASS) {
1128 if (specialist & (1 << (fx->PrimaryType+5))) {
1129 bonus += 2;
1130 }
1131 }
1132 }
1133
1134 bool saved = false;
1135 for (int i=0;i<5;i++) {
1136 if( fx->SavingThrowType&(1<<i)) {
1137 // FIXME: first bonus handling for iwd2 is just a guess
1138 if (iwd2fx) {
1139 saved = actor->GetSavingThrow(i, bonus - fx->SavingThrowBonus, fx);
1140 } else {
1141 saved = actor->GetSavingThrow(i, bonus);
1142 }
1143 if( saved) {
1144 break;
1145 }
1146 }
1147 }
1148 if( saved) {
1149 if( fx->IsSaveForHalfDamage) {
1150 // if we have evasion, we take no damage
1151 // sadly there's no feat or stat for it
1152 if (iwd2fx && (actor->GetThiefLevel() > 1 || actor->GetMonkLevel())) {
1153 fx->Parameter1 = 0;
1154 return FX_NOT_APPLIED;
1155 } else {
1156 fx->Parameter1 /= 2;
1157 }
1158 } else {
1159 Log(MESSAGE, "EffectQueue", "%s saved against effect: %s", actor->GetName(1), Opcodes[fx->Opcode].Name);
1160 return FX_NOT_APPLIED;
1161 }
1162 } else {
1163 // improved evasion: take only half damage even though we failed the save
1164 if (iwd2fx && fx->IsSaveForHalfDamage && actor->HasFeat(FEAT_IMPROVED_EVASION)) {
1165 fx->Parameter1 /= 2;
1166 }
1167 }
1168 return -1;
1169 }
1170
1171 // this function is called two different ways
1172 // when FirstApply is set, then the effect isn't stuck on the target
1173 // this happens when a new effect comes in contact with the target.
1174 // if the effect returns FX_DURATION_JUST_EXPIRED then it won't stick
1175 // when first_apply is unset, the effect is already on the target
1176 // this happens on load time too!
1177 // returns FX_NOT_APPLIED if the process shouldn't be calling applyeffect anymore
1178 // returns FX_ABORT if the whole spell this effect is in should be aborted
1179 // it will disable all future effects of same source (only on first apply)
1180
ApplyEffect(Actor * target,Effect * fx,ieDword first_apply,ieDword resistance) const1181 int EffectQueue::ApplyEffect(Actor* target, Effect* fx, ieDword first_apply, ieDword resistance) const
1182 {
1183 if (fx->TimingMode == FX_DURATION_JUST_EXPIRED) {
1184 return FX_NOT_APPLIED;
1185 }
1186 if( fx->Opcode >= MAX_EFFECTS) {
1187 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1188 return FX_NOT_APPLIED;
1189 }
1190
1191 ieDword GameTime = core->GetGame()->GameTime;
1192
1193 if (first_apply) {
1194 fx->FirstApply = 1;
1195 // we do proper target vs targetless checks below
1196 if (target) fx->SetPosition(target->Pos);
1197
1198 //gemrb specific, stat based chance
1199 if ((fx->ProbabilityRangeMin == 100) && Owner && (Owner->Type==ST_ACTOR) ) {
1200 fx->ProbabilityRangeMin = 0;
1201 fx->ProbabilityRangeMax = ((Actor *) Owner)->GetSafeStat(fx->ProbabilityRangeMax);
1202 }
1203
1204 if (resistance) {
1205 //the effect didn't pass the probability check
1206 if( !check_probability(fx) ) {
1207 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1208 return FX_NOT_APPLIED;
1209 }
1210
1211 //the effect didn't pass the target level check
1212 if( check_level(target, fx) ) {
1213 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1214 return FX_NOT_APPLIED;
1215 }
1216
1217 //the effect didn't pass the resistance check
1218 int resisted = check_resistance(target, fx);
1219 if (resisted != -1) {
1220 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1221 return resisted;
1222 }
1223 }
1224
1225 //Same as in items and spells
1226 if (fx->SourceFlags & SF_HOSTILE) {
1227 if (target && (target != Owner) && Owner && (Owner->Type==ST_ACTOR) ) {
1228 target->AttackedBy((Actor *) Owner);
1229 }
1230 }
1231
1232 if( NeedPrepare(fx->TimingMode) ) {
1233 //save delay for later
1234 fx->SecondaryDelay = fx->Duration;
1235 if (fx->TimingMode == FX_DURATION_INSTANT_LIMITED) {
1236 fx->TimingMode = FX_DURATION_ABSOLUTE;
1237 }
1238 bool inTicks = false;
1239 if (fx->TimingMode == FX_DURATION_INSTANT_LIMITED_TICKS) {
1240 fx->TimingMode = FX_DURATION_ABSOLUTE;
1241 inTicks = true;
1242 }
1243 if (pstflags && !(fx->SourceFlags & SF_SIMPLIFIED_DURATION)) {
1244 // pst stored the delay in ticks already, so we use a variant of PrepareDuration
1245 // unless it's our unhardcoded spells which use iwd2-style simplified duration in rounds per level
1246 inTicks = true;
1247 }
1248 if (inTicks) {
1249 fx->Duration = (fx->Duration ? fx->Duration : 1) + GameTime;
1250 } else {
1251 PrepareDuration(fx);
1252 }
1253 }
1254 }
1255 //check if the effect has triggered or expired
1256 switch (DelayType(fx->TimingMode&0xff) ) {
1257 case DELAYED:
1258 if( fx->Duration>GameTime) {
1259 return FX_NOT_APPLIED;
1260 }
1261 //effect triggered
1262 //delayed duration (3)
1263 if( NeedPrepare(fx->TimingMode) ) {
1264 //prepare for delayed duration effects
1265 fx->Duration = fx->SecondaryDelay;
1266 PrepareDuration(fx);
1267 }
1268 fx->TimingMode=TriggeredEffect(fx->TimingMode);
1269 break;
1270 case DURATION:
1271 if( fx->Duration<=GameTime) {
1272 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1273 //add a return here, if 0 duration effects shouldn't work
1274 }
1275 break;
1276 //permanent effect (so there is no warning)
1277 case PERMANENT:
1278 break;
1279 //this shouldn't happen
1280 default:
1281 error("EffectQueue", "Unknown delay type: %d (from %d)\n", DelayType(fx->TimingMode&0xff), fx->TimingMode);
1282 }
1283
1284 int res = FX_ABORT;
1285 if( fx->Opcode<MAX_EFFECTS) {
1286 if (!(target || (Opcodes[fx->Opcode].Flags & EFFECT_NO_ACTOR))) {
1287 Log(MESSAGE, "EffectQueue", "targetless opcode without EFFECT_NO_ACTOR: %d, skipping", fx->Opcode);
1288 return FX_NOT_APPLIED;
1289 }
1290
1291 const EffectDesc& ed = Opcodes[fx->Opcode];
1292 if (ed) {
1293 if( target && fx->FirstApply ) {
1294 if( !target->fxqueue.HasEffectWithParamPair(fx_protection_from_display_string_ref, fx->Parameter1, 0) ) {
1295 displaymsg->DisplayStringName( Opcodes[fx->Opcode].Strref, DMC_WHITE,
1296 target, IE_STR_SOUND);
1297 }
1298 }
1299
1300 res = ed(Owner, target, fx);
1301 fx->FirstApply = 0;
1302
1303 switch(res) {
1304 case FX_APPLIED:
1305 //normal effect with duration
1306 break;
1307 case FX_ABORT:
1308 case FX_NOT_APPLIED:
1309 //instant effect, pending removal
1310 //for example, a damage effect
1311 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1312 break;
1313 case FX_INSERT:
1314 //put this effect in the beginning of the queue
1315 //all known insert effects are 'permanent' too
1316 //that is the AC effect only
1317 //actually, permanent effects seem to be
1318 //inserted by the game engine too
1319 case FX_PERMANENT:
1320 //don't stick around if it was executed permanently
1321 //for example, a permanent strength modifier effect
1322 if(fx->TimingMode == FX_DURATION_INSTANT_PERMANENT) {
1323 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1324 }
1325 break;
1326 default:
1327 error("EffectQueue", "Unknown effect result '%x', aborting ...\n", res);
1328 }
1329 }
1330 }
1331
1332 return res;
1333 }
1334
1335 // looks for opcode with param2
1336
1337 #define MATCH_OPCODE() if((*f)->Opcode!=opcode) { continue; }
1338
1339 // useful for: remove equipped item
1340 #define MATCH_SLOTCODE() if((*f)->InventorySlot!=slotcode) { continue; }
1341
1342 // useful for: remove projectile type
1343 #define MATCH_PROJECTILE() if((*f)->Projectile!=projectile) { continue; }
1344
1345 static const bool fx_live[MAX_TIMING_MODE] = { true, true, true, false, false, false, false, false, true, true, true, false };
IsLive(ieByte timingmode)1346 static inline bool IsLive(ieByte timingmode)
1347 {
1348 if( timingmode>=MAX_TIMING_MODE) return false;
1349 return fx_live[timingmode];
1350 }
1351
1352 #define MATCH_LIVE_FX() if(!IsLive((*f)->TimingMode)) { continue; }
1353 #define MATCH_PARAM1() if((*f)->Parameter1!=param1) { continue; }
1354 #define MATCH_PARAM2() if((*f)->Parameter2!=param2) { continue; }
1355 #define MATCH_RESOURCE() if( strnicmp( (*f)->Resource, resource, 8) ) { continue; }
1356 #define MATCH_SOURCE() if( strnicmp( (*f)->Source, Removed, 8) ) { continue; }
1357 #define MATCH_TIMING() if( (*f)->TimingMode!=timing) { continue; }
1358
1359 //call this from an applied effect, after it returns, these effects
1360 //will be killed along with it
RemoveAllEffects(ieDword opcode) const1361 void EffectQueue::RemoveAllEffects(ieDword opcode) const
1362 {
1363 std::list< Effect* >::const_iterator f;
1364 for ( f = effects.begin(); f != effects.end(); f++ ) {
1365 MATCH_OPCODE()
1366 MATCH_LIVE_FX()
1367
1368 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1369 }
1370 }
1371
1372 //removes all equipping effects that match slotcode
RemoveEquippingEffects(ieDwordSigned slotcode) const1373 bool EffectQueue::RemoveEquippingEffects(ieDwordSigned slotcode) const
1374 {
1375 bool removed = false;
1376 std::list< Effect* >::const_iterator f;
1377 for ( f = effects.begin(); f != effects.end(); f++ ) {
1378 if( !IsEquipped((*f)->TimingMode)) continue;
1379 MATCH_SLOTCODE()
1380
1381 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1382 removed = true;
1383 }
1384 return removed;
1385 }
1386
1387 //removes all effects that match projectile
RemoveAllEffectsWithProjectile(ieDword projectile) const1388 void EffectQueue::RemoveAllEffectsWithProjectile(ieDword projectile) const
1389 {
1390 std::list< Effect* >::const_iterator f;
1391 for ( f = effects.begin(); f != effects.end(); f++ ) {
1392 MATCH_PROJECTILE()
1393
1394 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1395 }
1396 }
1397
1398 //remove effects belonging to a given spell
RemoveAllEffects(const ieResRef Removed) const1399 void EffectQueue::RemoveAllEffects(const ieResRef Removed) const
1400 {
1401 std::list< Effect* >::const_iterator f;
1402 for ( f = effects.begin(); f != effects.end(); f++ ) {
1403 MATCH_LIVE_FX()
1404 MATCH_SOURCE()
1405
1406 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1407 }
1408
1409 if (!Owner || (Owner->Type != ST_ACTOR)) return;
1410
1411 // we didn't catch effects that don't persist — they still need to be undone
1412 // FX_PERMANENT returners aren't part of the queue, so permanent stat mods can't be detected
1413 // good test case is the Oozemaster druid kit from Divine remix, which decreases charisma in its clab
1414 Spell *spell = gamedata->GetSpell(Removed, true);
1415 if (!spell) return; // can be hit until all the iwd2 clabs are implemented
1416 if (spell->ExtHeaderCount > 1) {
1417 Log(WARNING, "EffectQueue", "Spell %s has more than one extended header, removing only first!", Removed);
1418 }
1419 const SPLExtHeader *sph = spell->GetExtHeader(0);
1420 if (!sph) return; // some iwd2 clabs are only markers
1421 for (int i=0; i < sph->FeatureCount; i++) {
1422 const Effect *origfx = sph->features+i;
1423
1424 if (origfx->TimingMode != FX_DURATION_INSTANT_PERMANENT) continue;
1425 if (!(Opcodes[origfx->Opcode].Flags & EFFECT_SPECIAL_UNDO)) continue;
1426
1427 // unapply the effect by applying the reverse — if feasible
1428 // but don't alter the spell itself or other users won't get what they asked for
1429 Effect *fx = CreateEffectCopy(origfx, origfx->Opcode, origfx->Parameter1, origfx->Parameter2);
1430
1431 // state setting effects are idempotent, so wouldn't cause problems during clab reapplication
1432 // ...they would during disabled dualclass levels, but it would be too annoying to try, since
1433 // not all have cure variants (eg. fx_set_blur_state) and if there were two sources, we'd kill
1434 // the effect just the same.
1435
1436 // further ignore a few more complicated-to-undo opcodes
1437 // fx_ac_vs_damage_type_modifier, fx_ac_vs_damage_type_modifier_iwd2, fx_ids_modifier, fx_attacks_per_round_modifier
1438
1439 // fx_pause_target is a one-tick shot pony, nothing to do
1440
1441 fx->Parameter1 = -fx->Parameter1;
1442
1443 Log(DEBUG, "EffectQueue", "Manually removing effect %d (from %s)", fx->Opcode, Removed);
1444 ApplyEffect((Actor *)Owner, fx, 1, 0);
1445 delete fx;
1446 }
1447 gamedata->FreeSpell(spell, Removed, false);
1448 }
1449
1450 //remove effects belonging to a given spell, but only if they match timing method x
RemoveAllEffects(const ieResRef Removed,ieByte timing) const1451 void EffectQueue::RemoveAllEffects(const ieResRef Removed, ieByte timing) const
1452 {
1453 std::list< Effect* >::const_iterator f;
1454 for ( f = effects.begin(); f != effects.end(); f++ ) {
1455 MATCH_TIMING()
1456 MATCH_SOURCE()
1457
1458 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1459 }
1460 }
1461
1462 //this will modify effect reference
RemoveAllEffects(EffectRef & effect_reference) const1463 void EffectQueue::RemoveAllEffects(EffectRef &effect_reference) const
1464 {
1465 ResolveEffectRef(effect_reference);
1466 if( effect_reference.opcode<0) {
1467 return;
1468 }
1469 RemoveAllEffects(effect_reference.opcode);
1470 }
1471
1472 //Removes all effects with a matching resource field
RemoveAllEffectsWithResource(ieDword opcode,const ieResRef resource) const1473 void EffectQueue::RemoveAllEffectsWithResource(ieDword opcode, const ieResRef resource) const
1474 {
1475 std::list< Effect* >::const_iterator f;
1476 for ( f = effects.begin(); f != effects.end(); f++ ) {
1477 MATCH_OPCODE()
1478 MATCH_LIVE_FX()
1479 MATCH_RESOURCE()
1480
1481 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1482 }
1483 }
1484
1485 //this will modify effect reference
RemoveAllEffectsWithResource(EffectRef & effect_reference,const ieResRef resource) const1486 void EffectQueue::RemoveAllEffectsWithResource(EffectRef &effect_reference, const ieResRef resource) const
1487 {
1488 ResolveEffectRef(effect_reference);
1489 RemoveAllEffectsWithResource(effect_reference.opcode, resource);
1490 }
1491
1492 //This method could be used to remove stat modifiers that would lower a stat
1493 //(works only if a higher stat means good for the target)
RemoveAllDetrimentalEffects(ieDword opcode,ieDword current) const1494 void EffectQueue::RemoveAllDetrimentalEffects(ieDword opcode, ieDword current) const
1495 {
1496 std::list< Effect* >::const_iterator f;
1497 for ( f = effects.begin(); f != effects.end(); f++ ) {
1498 MATCH_OPCODE()
1499 MATCH_LIVE_FX()
1500 switch((*f)->Parameter2) {
1501 case 0:case 3:
1502 if( ((signed) (*f)->Parameter1)>=0) continue;
1503 break;
1504 case 1:case 4:
1505 if( ((signed) (*f)->Parameter1)>=(signed) current) continue;
1506 break;
1507 case 2:case 5:
1508 if( ((signed) (*f)->Parameter1)>=100) continue;
1509 break;
1510 default:
1511 break;
1512 }
1513 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1514 }
1515 }
1516
1517 //this will modify effect reference
RemoveAllDetrimentalEffects(EffectRef & effect_reference,ieDword current) const1518 void EffectQueue::RemoveAllDetrimentalEffects(EffectRef &effect_reference, ieDword current) const
1519 {
1520 ResolveEffectRef(effect_reference);
1521 RemoveAllDetrimentalEffects(effect_reference.opcode, current);
1522 }
1523
1524 //Removes all effects with a matching param2
1525 //param2 is usually an effect's subclass (quality) while param1 is more like quantity.
1526 //So opcode+param2 usually pinpoints an effect better when not all effects of a given
1527 //opcode need to be removed (see removal of portrait icon)
RemoveAllEffectsWithParam(ieDword opcode,ieDword param2) const1528 void EffectQueue::RemoveAllEffectsWithParam(ieDword opcode, ieDword param2) const
1529 {
1530 std::list< Effect* >::const_iterator f;
1531 for ( f = effects.begin(); f != effects.end(); f++ ) {
1532 MATCH_OPCODE()
1533 MATCH_LIVE_FX()
1534 MATCH_PARAM2()
1535
1536 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1537 }
1538 }
1539
1540 //this will modify effect reference
RemoveAllEffectsWithParam(EffectRef & effect_reference,ieDword param2) const1541 void EffectQueue::RemoveAllEffectsWithParam(EffectRef &effect_reference, ieDword param2) const
1542 {
1543 ResolveEffectRef(effect_reference);
1544 RemoveAllEffectsWithParam(effect_reference.opcode, param2);
1545 }
1546
1547 //Removes all effects with a matching resource field
RemoveAllEffectsWithParamAndResource(ieDword opcode,ieDword param2,const ieResRef resource) const1548 void EffectQueue::RemoveAllEffectsWithParamAndResource(ieDword opcode, ieDword param2, const ieResRef resource) const
1549 {
1550 std::list< Effect* >::const_iterator f;
1551 for ( f = effects.begin(); f != effects.end(); f++ ) {
1552 MATCH_OPCODE()
1553 MATCH_LIVE_FX()
1554 MATCH_PARAM2()
1555 if(resource[0]) {
1556 MATCH_RESOURCE()
1557 }
1558
1559 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1560 }
1561 }
1562
1563 //this will modify effect reference
RemoveAllEffectsWithParamAndResource(EffectRef & effect_reference,ieDword param2,const ieResRef resource) const1564 void EffectQueue::RemoveAllEffectsWithParamAndResource(EffectRef &effect_reference, ieDword param2, const ieResRef resource) const
1565 {
1566 ResolveEffectRef(effect_reference);
1567 RemoveAllEffectsWithParamAndResource(effect_reference.opcode, param2, resource);
1568 }
1569
1570 //this function is called by FakeEffectExpiryCheck
1571 //probably also called by rest
RemoveExpiredEffects(ieDword futuretime) const1572 void EffectQueue::RemoveExpiredEffects(ieDword futuretime) const
1573 {
1574 ieDword GameTime = core->GetGame()->GameTime;
1575 // prevent overflows, since we pass the max futuretime for guaranteed expiry
1576 if (GameTime + futuretime < GameTime) {
1577 GameTime=0xffffffff;
1578 } else {
1579 GameTime += futuretime;
1580 }
1581
1582 std::list< Effect* >::const_iterator f;
1583 for ( f = effects.begin(); f != effects.end(); f++ ) {
1584 //FIXME: how this method handles delayed effects???
1585 //it should remove them as well, i think
1586 if( DelayType( ((*f)->TimingMode) )!=PERMANENT ) {
1587 if( (*f)->Duration<=GameTime) {
1588 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1589 }
1590 }
1591 }
1592 }
1593
1594 //this effect will expire all effects that are not truly permanent
1595 //which i call permanent after death (iesdp calls it permanent after bonuses)
RemoveAllNonPermanentEffects() const1596 void EffectQueue::RemoveAllNonPermanentEffects() const
1597 {
1598 std::list< Effect* >::const_iterator f;
1599 for ( f = effects.begin(); f != effects.end(); f++ ) {
1600 if( IsRemovable((*f)->TimingMode) ) {
1601 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1602 }
1603 }
1604 }
1605
1606 //remove certain levels of effects, possibly matching school/secondary type
1607 //this method removes whole spells (tied together by their source)
1608 //FIXME: probably this isn't perfect
RemoveLevelEffects(ieResRef & Removed,ieDword level,ieDword Flags,ieDword match) const1609 void EffectQueue::RemoveLevelEffects(ieResRef &Removed, ieDword level, ieDword Flags, ieDword match) const
1610 {
1611 Removed[0]=0;
1612 std::list< Effect* >::const_iterator f;
1613 for ( f = effects.begin(); f != effects.end(); f++ ) {
1614 if( (*f)->Power>level) {
1615 continue;
1616 }
1617
1618 if( Removed[0]) {
1619 MATCH_SOURCE()
1620 }
1621 if( Flags&RL_MATCHSCHOOL) {
1622 if( (*f)->PrimaryType!=match) {
1623 continue;
1624 }
1625 }
1626 if( Flags&RL_MATCHSECTYPE) {
1627 if( (*f)->SecondaryType!=match) {
1628 continue;
1629 }
1630 }
1631 //if dispellable was not set, or the effect is dispellable
1632 //then remove it
1633 if( Flags&RL_DISPELLABLE) {
1634 if( !((*f)->Resistance&FX_CAN_DISPEL)) {
1635 continue;
1636 }
1637 }
1638 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1639 if( Flags&RL_REMOVEFIRST) {
1640 memcpy(Removed,(*f)->Source, sizeof(Removed));
1641 }
1642 }
1643 }
1644
DispelEffects(Effect * dispeller,ieDword level) const1645 void EffectQueue::DispelEffects(Effect *dispeller, ieDword level) const
1646 {
1647 for (Effect *fx : effects) {
1648 if (fx == dispeller) continue;
1649
1650 // this should also ignore all equipping effects
1651 if(!(fx->Resistance & FX_CAN_DISPEL)) {
1652 continue;
1653 }
1654
1655 // 50% base chance of success; always at least 1% chance of failure or success
1656 // positive level diff modifies the base chance by 5%, negative by -10%
1657 int diff = level - fx->CasterLevel;
1658 if (diff > 0) {
1659 diff *= 5;
1660 } else if (diff < 0) {
1661 diff *= 10;
1662 }
1663 diff += 50;
1664
1665 int roll = core->Roll(1, 100, 0);
1666 if (roll == 1) continue;
1667 if (roll == 100 || roll < diff) {
1668 // finally dispel
1669 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1670 }
1671 }
1672 }
1673
HasOpcode(ieDword opcode) const1674 Effect *EffectQueue::HasOpcode(ieDword opcode) const
1675 {
1676 std::list< Effect* >::const_iterator f;
1677 for ( f = effects.begin(); f != effects.end(); f++ ) {
1678 MATCH_OPCODE()
1679 MATCH_LIVE_FX()
1680
1681 return (*f);
1682 }
1683 return NULL;
1684 }
1685
HasEffect(EffectRef & effect_reference) const1686 Effect *EffectQueue::HasEffect(EffectRef &effect_reference) const
1687 {
1688 ResolveEffectRef(effect_reference);
1689 if( effect_reference.opcode<0) {
1690 return NULL;
1691 }
1692 return HasOpcode(effect_reference.opcode);
1693 }
1694
HasOpcodeWithParam(ieDword opcode,ieDword param2) const1695 Effect *EffectQueue::HasOpcodeWithParam(ieDword opcode, ieDword param2) const
1696 {
1697 std::list< Effect* >::const_iterator f;
1698 for ( f = effects.begin(); f != effects.end(); f++ ) {
1699 MATCH_OPCODE()
1700 MATCH_LIVE_FX()
1701 MATCH_PARAM2()
1702
1703 return (*f);
1704 }
1705 return NULL;
1706 }
1707
1708 //this will modify effect reference
HasEffectWithParam(EffectRef & effect_reference,ieDword param2) const1709 Effect *EffectQueue::HasEffectWithParam(EffectRef &effect_reference, ieDword param2) const
1710 {
1711 ResolveEffectRef(effect_reference);
1712 if( effect_reference.opcode<0) {
1713 return NULL;
1714 }
1715 return HasOpcodeWithParam(effect_reference.opcode, param2);
1716 }
1717
1718 //looks for opcode with pairs of parameters (useful for protection against creature, extra damage or extra thac0 against creature)
1719 //generally an IDS targeting
1720
HasOpcodeWithParamPair(ieDword opcode,ieDword param1,ieDword param2) const1721 Effect *EffectQueue::HasOpcodeWithParamPair(ieDword opcode, ieDword param1, ieDword param2) const
1722 {
1723 std::list< Effect* >::const_iterator f;
1724 for ( f = effects.begin(); f != effects.end(); f++ ) {
1725 MATCH_OPCODE()
1726 MATCH_LIVE_FX()
1727 MATCH_PARAM2()
1728 //0 is always accepted as first parameter
1729 if( param1) {
1730 MATCH_PARAM1()
1731 }
1732
1733 return (*f);
1734 }
1735 return NULL;
1736 }
1737
1738 //this will modify effect reference
HasEffectWithParamPair(EffectRef & effect_reference,ieDword param1,ieDword param2) const1739 Effect *EffectQueue::HasEffectWithParamPair(EffectRef &effect_reference, ieDword param1, ieDword param2) const
1740 {
1741 ResolveEffectRef(effect_reference);
1742 if( effect_reference.opcode<0) {
1743 return NULL;
1744 }
1745 return HasOpcodeWithParamPair(effect_reference.opcode, param1, param2);
1746 }
1747
1748 //this could be used for stoneskins and mirror images as well
DecreaseParam1OfEffect(ieDword opcode,ieDword amount) const1749 void EffectQueue::DecreaseParam1OfEffect(ieDword opcode, ieDword amount) const
1750 {
1751 std::list< Effect* >::const_iterator f;
1752 for ( f = effects.begin(); f != effects.end(); f++ ) {
1753 MATCH_OPCODE()
1754 MATCH_LIVE_FX()
1755 ieDword value = (*f)->Parameter1;
1756 if( value>amount) {
1757 value -= amount;
1758 amount = 0;
1759 } else {
1760 amount -= value;
1761 value = 0;
1762 }
1763 (*f)->Parameter1=value;
1764 if (value) {
1765 return;
1766 }
1767 }
1768 }
1769
DecreaseParam1OfEffect(EffectRef & effect_reference,ieDword amount) const1770 void EffectQueue::DecreaseParam1OfEffect(EffectRef &effect_reference, ieDword amount) const
1771 {
1772 ResolveEffectRef(effect_reference);
1773 if( effect_reference.opcode<0) {
1774 return;
1775 }
1776 DecreaseParam1OfEffect(effect_reference.opcode, amount);
1777 }
1778
1779 //this is only used for Cloak of Warding Overlay in PST
1780 //returns the damage amount NOT soaked
DecreaseParam3OfEffect(ieDword opcode,ieDword amount,ieDword param2) const1781 int EffectQueue::DecreaseParam3OfEffect(ieDword opcode, ieDword amount, ieDword param2) const
1782 {
1783 std::list< Effect* >::const_iterator f;
1784 for ( f = effects.begin(); f != effects.end(); f++ ) {
1785 MATCH_OPCODE()
1786 MATCH_LIVE_FX()
1787 MATCH_PARAM2()
1788 ieDword value = (*f)->Parameter3;
1789 if( value>amount) {
1790 value -= amount;
1791 amount = 0;
1792 } else {
1793 amount -= value;
1794 value = 0;
1795 }
1796 (*f)->Parameter3=value;
1797 if (value) {
1798 return 0;
1799 }
1800 }
1801 return amount;
1802 }
1803
1804 //this is only used for Cloak of Warding Overlay in PST
1805 //returns the damage amount NOT soaked
DecreaseParam3OfEffect(EffectRef & effect_reference,ieDword amount,ieDword param2) const1806 int EffectQueue::DecreaseParam3OfEffect(EffectRef &effect_reference, ieDword amount, ieDword param2) const
1807 {
1808 ResolveEffectRef(effect_reference);
1809 if( effect_reference.opcode<0) {
1810 return amount;
1811 }
1812 return DecreaseParam3OfEffect(effect_reference.opcode, amount, param2);
1813 }
1814
1815 //this function does IDS targeting for effects (extra damage/thac0 against creature)
1816 //faction/team may be useful for grouping creatures differently, without messing with existing general/specific values
1817 static const int ids_stats[9]={IE_FACTION, IE_TEAM, IE_EA, IE_GENERAL, IE_RACE, IE_CLASS, IE_SPECIFIC, IE_SEX, IE_ALIGNMENT};
1818
1819 //0,1 and 9 are only in GemRB
BonusAgainstCreature(ieDword opcode,const Actor * actor) const1820 int EffectQueue::BonusAgainstCreature(ieDword opcode, const Actor *actor) const
1821 {
1822 int sum = 0;
1823 std::list< Effect* >::const_iterator f;
1824 for ( f = effects.begin(); f != effects.end(); f++ ) {
1825 MATCH_OPCODE()
1826 MATCH_LIVE_FX()
1827 if( (*f)->Parameter1) {
1828 ieDword param1;
1829 ieDword ids = (*f)->Parameter2;
1830 switch(ids) {
1831 case 0:
1832 case 1:
1833 case 2:
1834 case 3:
1835 case 4:
1836 case 6:
1837 case 7:
1838 case 8:
1839 param1 = actor->GetStat(ids_stats[ids]);
1840 MATCH_PARAM1()
1841 break;
1842 case 5:
1843 param1 = actor->GetActiveClass();
1844 MATCH_PARAM1()
1845 break;
1846 case 9:
1847 //pseudo stat/classmask
1848 param1 = actor->GetClassMask() & (*f)->Parameter1;
1849 if (!param1) continue;
1850 break;
1851 default:
1852 break;
1853 }
1854 }
1855 int val = (int) (*f)->Parameter3;
1856 //we are really lucky with this, most of these boni are using +2 (including fiendslayer feat)
1857 //it would be much more inconvenient if we had to use v2 effect files
1858 if( !val) val = 2;
1859 sum += val;
1860 }
1861 return sum;
1862 }
1863
BonusAgainstCreature(EffectRef & effect_reference,const Actor * actor) const1864 int EffectQueue::BonusAgainstCreature(EffectRef &effect_reference, const Actor *actor) const
1865 {
1866 ResolveEffectRef(effect_reference);
1867 if( effect_reference.opcode<0) {
1868 return 0;
1869 }
1870 return BonusAgainstCreature(effect_reference.opcode, actor);
1871 }
1872
BonusForParam2(ieDword opcode,ieDword param2) const1873 int EffectQueue::BonusForParam2(ieDword opcode, ieDword param2) const
1874 {
1875 int sum = 0;
1876 std::list< Effect* >::const_iterator f;
1877 for ( f = effects.begin(); f != effects.end(); f++ ) {
1878 MATCH_OPCODE()
1879 MATCH_LIVE_FX()
1880 MATCH_PARAM2()
1881 sum += (*f)->Parameter1;
1882 }
1883 return sum;
1884 }
1885
BonusForParam2(EffectRef & effect_reference,ieDword param2) const1886 int EffectQueue::BonusForParam2(EffectRef &effect_reference, ieDword param2) const
1887 {
1888 ResolveEffectRef(effect_reference);
1889 if( effect_reference.opcode<0) {
1890 return 0;
1891 }
1892 return BonusForParam2(effect_reference.opcode, param2);
1893 }
1894
MaxParam1(ieDword opcode,bool positive) const1895 int EffectQueue::MaxParam1(ieDword opcode, bool positive) const
1896 {
1897 int max = 0;
1898 ieDwordSigned param1 = 0;
1899 std::list< Effect* >::const_iterator f;
1900 for (f = effects.begin(); f != effects.end(); f++) {
1901 MATCH_OPCODE()
1902 MATCH_LIVE_FX()
1903
1904 param1 = signed((*f)->Parameter1);
1905 if ((positive && param1 > max) || (!positive && param1 < max)) {
1906 max = param1;
1907 }
1908 }
1909 return max;
1910 }
1911
MaxParam1(EffectRef & effect_reference,bool positive) const1912 int EffectQueue::MaxParam1(EffectRef &effect_reference, bool positive) const
1913 {
1914 ResolveEffectRef(effect_reference);
1915 if( effect_reference.opcode<0) {
1916 return 0;
1917 }
1918 return MaxParam1(effect_reference.opcode, positive);
1919 }
1920
WeaponImmunity(ieDword opcode,int enchantment,ieDword weapontype) const1921 bool EffectQueue::WeaponImmunity(ieDword opcode, int enchantment, ieDword weapontype) const
1922 {
1923 std::list< Effect* >::const_iterator f;
1924 for (f = effects.begin(); f != effects.end(); f++) {
1925 MATCH_OPCODE()
1926 MATCH_LIVE_FX()
1927
1928 int magic = (int) (*f)->Parameter1;
1929 ieDword mask = (*f)->Parameter3;
1930 ieDword value = (*f)->Parameter4;
1931 if (magic == 0) {
1932 if (enchantment) continue;
1933 } else if (magic > 0) {
1934 if (enchantment > magic) continue;
1935 }
1936
1937 if ((weapontype&mask) != value) {
1938 continue;
1939 }
1940 return true;
1941 }
1942 return false;
1943 }
1944
WeaponImmunity(int enchantment,ieDword weapontype) const1945 bool EffectQueue::WeaponImmunity(int enchantment, ieDword weapontype) const
1946 {
1947 ResolveEffectRef(fx_weapon_immunity_ref);
1948 if (fx_weapon_immunity_ref.opcode < 0) {
1949 return 0;
1950 }
1951 return WeaponImmunity(fx_weapon_immunity_ref.opcode, enchantment, weapontype);
1952 }
1953
AddWeaponEffects(EffectQueue * fxqueue,EffectRef & fx_ref) const1954 void EffectQueue::AddWeaponEffects(EffectQueue *fxqueue, EffectRef &fx_ref) const
1955 {
1956 ResolveEffectRef(fx_ref);
1957 if( fx_ref.opcode<0) {
1958 return;
1959 }
1960
1961 ieDword opcode = fx_ref.opcode;
1962 Point p(-1,-1);
1963
1964 std::list< Effect* >::const_iterator f;
1965 for ( f = effects.begin(); f != effects.end(); f++ ) {
1966 MATCH_OPCODE()
1967 MATCH_LIVE_FX()
1968 //
1969 Effect *fx = core->GetEffect( (*f)->Resource, (*f)->Power, p);
1970 if (!fx) continue;
1971 fx->Target = FX_TARGET_PRESET;
1972 fxqueue->AddEffect(fx, true);
1973 }
1974 }
1975
1976 // figure out how much damage reduction applies for a given weapon enchantment and damage type
SumDamageReduction(EffectRef & effect_reference,ieDword weaponEnchantment,int & total) const1977 int EffectQueue::SumDamageReduction(EffectRef &effect_reference, ieDword weaponEnchantment, int &total) const
1978 {
1979 ResolveEffectRef(effect_reference);
1980 ieDword opcode = effect_reference.opcode;
1981 int remaining = 0;
1982 int count = 0;
1983
1984 std::list< Effect* >::const_iterator f;
1985 for (f = effects.begin(); f != effects.end(); f++) {
1986 MATCH_OPCODE()
1987 MATCH_LIVE_FX()
1988
1989 Effect* fx = *f;
1990 if (!fx) continue;
1991 count++;
1992 // add up if the effect has enough enchantment (or ignores it)
1993 if (!fx->Parameter2 || fx->Parameter2 > weaponEnchantment) {
1994 remaining += fx->Parameter1;
1995 }
1996 total += fx->Parameter1;
1997 }
1998 if (count) {
1999 return remaining;
2000 } else {
2001 return -1;
2002 }
2003 }
2004
2005 //useful for immunity vs spell, can't use item, etc.
HasOpcodeWithResource(ieDword opcode,const ieResRef resource) const2006 Effect *EffectQueue::HasOpcodeWithResource(ieDword opcode, const ieResRef resource) const
2007 {
2008 std::list< Effect* >::const_iterator f;
2009 for ( f = effects.begin(); f != effects.end(); f++ ) {
2010 MATCH_OPCODE()
2011 MATCH_LIVE_FX()
2012 MATCH_RESOURCE()
2013
2014 return (*f);
2015 }
2016 return NULL;
2017 }
2018
HasEffectWithResource(EffectRef & effect_reference,const ieResRef resource) const2019 Effect *EffectQueue::HasEffectWithResource(EffectRef &effect_reference, const ieResRef resource) const
2020 {
2021 ResolveEffectRef(effect_reference);
2022 return HasOpcodeWithResource(effect_reference.opcode, resource);
2023 }
2024
2025 // for tobex bounce triggers
HasEffectWithPower(EffectRef & effect_reference,ieDword power) const2026 Effect *EffectQueue::HasEffectWithPower(EffectRef &effect_reference, ieDword power) const
2027 {
2028 ResolveEffectRef(effect_reference);
2029 return HasOpcodeWithPower(effect_reference.opcode, power);
2030 }
2031
HasOpcodeWithPower(ieDword opcode,ieDword power) const2032 Effect *EffectQueue::HasOpcodeWithPower(ieDword opcode, ieDword power) const
2033 {
2034 std::list< Effect* >::const_iterator f;
2035 for (f = effects.begin(); f != effects.end(); f++) {
2036 MATCH_OPCODE()
2037 MATCH_LIVE_FX()
2038 // NOTE: matching greater or equals!
2039 if ((*f)->Power < power) { continue; }
2040
2041 return (*f);
2042 }
2043 return NULL;
2044 }
2045
2046 //returns the first effect with source 'Removed'
HasSource(const ieResRef Removed) const2047 Effect *EffectQueue::HasSource(const ieResRef Removed) const
2048 {
2049 std::list< Effect* >::const_iterator f;
2050 for ( f = effects.begin(); f != effects.end(); f++ ) {
2051 MATCH_LIVE_FX()
2052 MATCH_SOURCE()
2053
2054 return (*f);
2055 }
2056 return NULL;
2057 }
2058
2059 //used in contingency/sequencer code (cannot have the same contingency twice)
HasOpcodeWithSource(ieDword opcode,const ieResRef Removed) const2060 Effect *EffectQueue::HasOpcodeWithSource(ieDword opcode, const ieResRef Removed) const
2061 {
2062 std::list< Effect* >::const_iterator f;
2063 for ( f = effects.begin(); f != effects.end(); f++ ) {
2064 MATCH_OPCODE()
2065 MATCH_LIVE_FX()
2066 MATCH_SOURCE()
2067
2068 return (*f);
2069 }
2070 return NULL;
2071 }
2072
HasEffectWithSource(EffectRef & effect_reference,const ieResRef resource) const2073 Effect *EffectQueue::HasEffectWithSource(EffectRef &effect_reference, const ieResRef resource) const
2074 {
2075 ResolveEffectRef(effect_reference);
2076 return HasOpcodeWithSource(effect_reference.opcode, resource);
2077 }
2078
HasAnyDispellableEffect() const2079 bool EffectQueue::HasAnyDispellableEffect() const
2080 {
2081 for (const Effect *fx : effects) {
2082 if (fx->Resistance & FX_CAN_DISPEL) {
2083 return true;
2084 }
2085 }
2086 return false;
2087 }
2088
dump() const2089 void EffectQueue::dump() const
2090 {
2091 StringBuffer buffer;
2092 dump(buffer);
2093 Log(DEBUG, "EffectQueue", buffer);
2094 }
2095
dump(StringBuffer & buffer) const2096 void EffectQueue::dump(StringBuffer& buffer) const
2097 {
2098 buffer.append("EFFECT QUEUE:\n");
2099 int i = 0;
2100 for (const Effect *fx : effects) {
2101 if (fx->Opcode >= MAX_EFFECTS) {
2102 Log(FATAL, "EffectQueue", "Encountered opcode off the charts: %d! Report this immediately!", fx->Opcode);
2103 return;
2104 }
2105 buffer.appendFormatted(" %2d: 0x%02x: %s (%d, %d) S:%s\n", i++, fx->Opcode, Opcodes[fx->Opcode].Name, fx->Parameter1, fx->Parameter2, fx->Source);
2106 }
2107 }
2108 /*
2109 Effect *EffectQueue::GetEffect(ieDword idx) const
2110 {
2111 if( effects.size()<=idx) {
2112 return NULL;
2113 }
2114 return effects[idx];
2115 }
2116 */
2117
2118 //returns true if the effect supports simplified duration
HasDuration(const Effect * fx)2119 bool EffectQueue::HasDuration(const Effect *fx)
2120 {
2121 switch(fx->TimingMode) {
2122 case FX_DURATION_INSTANT_LIMITED: //simple duration
2123 case FX_DURATION_DELAY_LIMITED: //delayed duration
2124 case FX_DURATION_DELAY_PERMANENT: //simple delayed
2125 // not supporting FX_DURATION_INSTANT_LIMITED_TICKS, since it's in ticks
2126 return true;
2127 }
2128 return false;
2129 }
2130
2131 //returns true if the effect must be saved
Persistent(const Effect * fx)2132 bool EffectQueue::Persistent(const Effect* fx)
2133 {
2134 // local variable effects self-destruct if they were processed already
2135 // but if they weren't processed, e.g. in a global actor, we must save them
2136 // TODO: do we really need to special-case this? leaving it for now - fuzzie
2137 if( fx->Opcode==(ieDword) ResolveEffect(fx_variable_ref)) {
2138 return true;
2139 }
2140
2141 switch (fx->TimingMode) {
2142 //normal equipping fx of items
2143 case FX_DURATION_INSTANT_WHILE_EQUIPPED:
2144 //delayed effect not saved
2145 case FX_DURATION_DELAY_UNSAVED:
2146 //permanent effect not saved
2147 case FX_DURATION_PERMANENT_UNSAVED:
2148 //just expired effect
2149 case FX_DURATION_JUST_EXPIRED:
2150 return false;
2151 }
2152 return true;
2153 }
2154
2155 //alter the color effect in case the item is equipped in the shield slot
HackColorEffects(const Actor * Owner,Effect * fx)2156 void EffectQueue::HackColorEffects(const Actor *Owner, Effect *fx)
2157 {
2158 if( fx->InventorySlot!=Owner->inventory.GetShieldSlot()) return;
2159
2160 unsigned int gradienttype = fx->Parameter2 & 0xF0;
2161 if( gradienttype == 0x10) {
2162 gradienttype = 0x20; // off-hand
2163 fx->Parameter2 &= ~0xF0;
2164 fx->Parameter2 |= gradienttype;
2165 }
2166 }
2167
2168 //iterate through saved effects
GetNextSavedEffect(std::list<Effect * >::const_iterator & f) const2169 const Effect *EffectQueue::GetNextSavedEffect(std::list< Effect* >::const_iterator &f) const
2170 {
2171 while(f!=effects.end()) {
2172 const Effect *effect = *f;
2173 f++;
2174 if( Persistent(effect)) {
2175 return effect;
2176 }
2177 }
2178 return NULL;
2179 }
2180
GetNextEffect(std::list<Effect * >::const_iterator & f) const2181 Effect *EffectQueue::GetNextEffect(std::list< Effect* >::const_iterator &f) const
2182 {
2183 if( f!=effects.end()) return *f++;
2184 return NULL;
2185 }
2186
CountEffects(ieDword opcode,ieDword param1,ieDword param2,const char * resource) const2187 ieDword EffectQueue::CountEffects(ieDword opcode, ieDword param1, ieDword param2, const char *resource) const
2188 {
2189 ieDword cnt = 0;
2190
2191 std::list< Effect* >::const_iterator f;
2192
2193 for ( f = effects.begin(); f != effects.end(); f++ ) {
2194 MATCH_OPCODE()
2195 if( param1!=0xffffffff)
2196 MATCH_PARAM1()
2197 if( param2!=0xffffffff)
2198 MATCH_PARAM2()
2199 if( resource) {
2200 MATCH_RESOURCE()
2201 }
2202 cnt++;
2203 }
2204 return cnt;
2205 }
2206
GetEffectOrder(EffectRef & effect_reference,const Effect * fx) const2207 unsigned int EffectQueue::GetEffectOrder(EffectRef& effect_reference, const Effect* fx) const
2208 {
2209 ieDword cnt = 1;
2210 ieDword opcode = ResolveEffect(effect_reference);
2211
2212 std::list< Effect* >::const_iterator f;
2213 for (f = effects.begin(); f != effects.end(); ++f) {
2214 MATCH_OPCODE()
2215 MATCH_LIVE_FX()
2216 if (*f == fx) break;
2217 cnt++;
2218 }
2219 return cnt;
2220 }
2221
ModifyEffectPoint(ieDword opcode,ieDword x,ieDword y) const2222 void EffectQueue::ModifyEffectPoint(ieDword opcode, ieDword x, ieDword y) const
2223 {
2224 std::list< Effect* >::const_iterator f;
2225
2226 for ( f = effects.begin(); f != effects.end(); f++ ) {
2227 MATCH_OPCODE()
2228 (*f)->PosX=x;
2229 (*f)->PosY=y;
2230 (*f)->Parameter3=0;
2231 return;
2232 }
2233 }
2234
2235 //count effects that get saved
GetSavedEffectsCount() const2236 ieDword EffectQueue::GetSavedEffectsCount() const
2237 {
2238 ieDword cnt = 0;
2239
2240 for (const Effect *fx : effects) {
2241 if (Persistent(fx)) cnt++;
2242 }
2243 return cnt;
2244 }
2245
ResolveEffect(EffectRef & effect_reference)2246 int EffectQueue::ResolveEffect(EffectRef &effect_reference)
2247 {
2248 ResolveEffectRef(effect_reference);
2249 return effect_reference.opcode;
2250 }
2251
2252 // this check goes for the whole effect block, not individual effects
2253 // But it takes the first effect of the block for the common fields
2254
2255 //returns 1 if effect block applicable
2256 //returns 0 if effect block disabled
2257 //returns -1 if effect block bounced
CheckImmunity(Actor * target) const2258 int EffectQueue::CheckImmunity(Actor *target) const
2259 {
2260 //don't resist if target is non living
2261 if( !target) {
2262 return 1;
2263 }
2264
2265 if (!effects.empty()) {
2266 const Effect* fx = *effects.begin();
2267
2268 //projectile immunity
2269 if( target->ImmuneToProjectile(fx->Projectile)) return 0;
2270
2271 //Allegedly, the book of infinite spells needed this, but irresistable by level
2272 //spells got fx->Power = 0, so i added those exceptions and removed returning here for fx->InventorySlot
2273
2274 //check level resistances
2275 //check specific spell immunity
2276 //check school/sectype immunity
2277 int ret = check_type(target, fx);
2278 if (ret<0) {
2279 if (target->Modified[IE_SANCTUARY]&(1<<OV_BOUNCE) ) {
2280 target->Modified[IE_SANCTUARY]|=(1<<OV_BOUNCE2);
2281 }
2282 }
2283 return ret;
2284 }
2285 return 0;
2286 }
2287
AffectAllInRange(const Map * map,const Point & pos,int idstype,int idsvalue,unsigned int range,const Actor * except)2288 void EffectQueue::AffectAllInRange(const Map *map, const Point &pos, int idstype, int idsvalue,
2289 unsigned int range, const Actor *except)
2290 {
2291 int cnt = map->GetActorCount(true);
2292 while(cnt--) {
2293 Actor *actor = map->GetActor(cnt,true);
2294 if( except==actor) {
2295 continue;
2296 }
2297 //distance
2298 if( Distance(pos, actor)>range) {
2299 continue;
2300 }
2301 //ids targeting
2302 if( !match_ids(actor, idstype, idsvalue)) {
2303 continue;
2304 }
2305 //line of sight
2306 if( !map->IsVisibleLOS(actor->Pos, pos)) {
2307 continue;
2308 }
2309 AddAllEffects(actor, actor->Pos);
2310 }
2311 }
2312
OverrideTarget(const Effect * fx)2313 bool EffectQueue::OverrideTarget(const Effect *fx)
2314 {
2315 if (!fx) return false;
2316 return (Opcodes[fx->Opcode].Flags & EFFECT_PRESET_TARGET);
2317 }
2318
HasHostileEffects() const2319 bool EffectQueue::HasHostileEffects() const
2320 {
2321 bool hostile = false;
2322
2323 for (const Effect* fx : effects) {
2324 if (fx->SourceFlags&SF_HOSTILE) {
2325 hostile = true;
2326 break;
2327 }
2328 }
2329
2330 return hostile;
2331 }
2332
2333 }
2334