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