Move EventMgr to GUI.
[gemrb.git] / gemrb / core / EffectQueue.cpp
blob37900aa826cb14f9ad44bdf524c3378cae410c68
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
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.
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.
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.
21 #include "EffectQueue.h"
23 #include "DisplayMessage.h"
24 #include "Effect.h"
25 #include "Game.h"
26 #include "Interface.h"
27 #include "Map.h"
28 #include "SymbolMgr.h"
29 #include "Scriptable/Actor.h"
31 #include <cstdio>
33 static struct {
34 const char* Name;
35 EffectFunction Function;
36 int Strref;
37 } Opcodes[MAX_EFFECTS];
39 static int initialized = 0;
41 static EffectRef *effectnames = NULL;
42 static int effectnames_count = 0;
44 bool EffectQueue::match_ids(Actor *target, int table, ieDword value)
46 if( value == 0) {
47 return true;
50 int a, stat;
52 switch (table) {
53 case 2: //EA
54 stat = IE_EA; break;
55 case 3: //GENERAL
56 stat = IE_GENERAL; break;
57 case 4: //RACE
58 stat = IE_RACE; break;
59 case 5: //CLASS
60 stat = IE_CLASS; break;
61 case 6: //SPECIFIC
62 stat = IE_SPECIFIC; break;
63 case 7: //GENDER
64 stat = IE_SEX; break;
65 case 8: //ALIGNMENT
66 stat = target->GetStat(IE_ALIGNMENT);
67 a = value&15;
68 if( a) {
69 if( a != ( stat & 15 )) {
70 return false;
73 a = value & 0xf0;
74 if( a) {
75 if( a != ( stat & 0xf0 )) {
76 return false;
79 return true;
80 default:
81 return false;
83 if( target->GetStat(stat)==value) {
84 return true;
86 return false;
89 static const bool fx_instant[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,true};
91 inline bool IsInstant(ieByte timingmode)
93 if( timingmode>=MAX_TIMING_MODE) return false;
94 return fx_instant[timingmode];
97 static const bool fx_equipped[MAX_TIMING_MODE]={false,false,true,false,false,true,false,false,true,false,false};
99 inline bool IsEquipped(ieByte timingmode)
101 if( timingmode>=MAX_TIMING_MODE) return false;
102 return fx_equipped[timingmode];
105 // 0 1 2 3 4 5 6 7 8 9 10
106 static const bool fx_relative[MAX_TIMING_MODE]={true,false,false,true,true,true,false,false,false,false,false};
108 inline bool NeedPrepare(ieWord timingmode)
110 if( timingmode>=MAX_TIMING_MODE) return false;
111 return fx_relative[timingmode];
114 #define INVALID -1
115 #define PERMANENT 0
116 #define DELAYED 1
117 #define DURATION 2
119 static const int fx_prepared[MAX_TIMING_MODE]={DURATION,PERMANENT,PERMANENT,DELAYED, //0-3
120 DELAYED,DELAYED,DELAYED,DELAYED,PERMANENT,PERMANENT,PERMANENT}; //4-7
122 inline int DelayType(ieByte timingmode)
124 if( timingmode>=MAX_TIMING_MODE) return INVALID;
125 return fx_prepared[timingmode];
128 //which effects are removable
129 static const bool fx_removable[MAX_TIMING_MODE]={true,true,false,true,true,false,true,true,false,false,true};
131 inline int IsRemovable(ieByte timingmode)
133 if( timingmode>=MAX_TIMING_MODE) return INVALID;
134 return fx_removable[timingmode];
137 //change the timing method after the effect triggered
138 static const ieByte fx_triggered[MAX_TIMING_MODE]={FX_DURATION_JUST_EXPIRED,FX_DURATION_INSTANT_PERMANENT,//0,1
139 FX_DURATION_INSTANT_WHILE_EQUIPPED,FX_DURATION_DELAY_LIMITED_PENDING,//2,3
140 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
141 FX_DURATION_INSTANT_LIMITED,FX_DURATION_JUST_EXPIRED,FX_DURATION_PERMANENT_UNSAVED,//6,8
142 FX_DURATION_INSTANT_PERMANENT_AFTER_BONUSES,FX_DURATION_JUST_EXPIRED};//9,10
144 //change the timing method for effect that should trigger after this effect expired
145 static const ieDword fx_to_delayed[]={FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,
146 FX_DURATION_PERMANENT_UNSAVED,FX_DURATION_DELAY_LIMITED_PENDING,
147 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
148 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,//6,8
149 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED};//9,10
151 inline ieByte TriggeredEffect(ieByte timingmode)
153 if( timingmode>=MAX_TIMING_MODE) return false;
154 return fx_triggered[timingmode];
157 int compare_effects(const void *a, const void *b)
159 return stricmp(((EffectRef *) a)->Name,((EffectRef *) b)->Name);
162 int find_effect(const void *a, const void *b)
164 return stricmp((const char *) a,((const EffectRef *) b)->Name);
167 static EffectRef* FindEffect(const char* effectname)
169 if( !effectname || !effectnames) {
170 return NULL;
172 void *tmp = bsearch(effectname, effectnames, effectnames_count, sizeof(EffectRef), find_effect);
173 if( !tmp) {
174 printMessage( "EffectQueue", "", YELLOW);
175 printf("Couldn't assign effect: %s\n", effectname );
177 return (EffectRef *) tmp;
180 static EffectRef fx_protection_from_display_string_ref={"Protection:String",NULL,-1};
182 //special effects without level check (but with damage dices precalculated)
183 static EffectRef diced_effects[] = {
184 //core effects
185 {"Damage",NULL,-1},
186 {"CurrentHPModifier",NULL,-1},
187 {"MaximumHPModifier",NULL,-1},
188 //iwd effects
189 {"BurningBlood",NULL,-1}, //iwd
190 {"ColdDamage",NULL,-1},
191 {"CrushingDamage",NULL,-1},
192 {"VampiricTouch",NULL,-1},
193 {"VitriolicSphere",NULL,-1},
194 //pst effects
195 {"TransferHP",NULL,-1},
196 {NULL,NULL,0} };
198 //special effects without level check (but with damage dices not precalculated)
199 static EffectRef diced_effects2[] = {
200 {"BurningBlood2",NULL,-1}, //how/iwd2
201 {"StaticCharge",NULL,-1}, //how/iwd2
202 {"LichTouch",NULL,-1}, //how
203 {NULL,NULL,0} };
205 inline static void ResolveEffectRef(EffectRef &effect_reference)
207 if( effect_reference.opcode==-1) {
208 EffectRef* ref = FindEffect(effect_reference.Name);
209 if( ref && ref->opcode>=0) {
210 effect_reference.opcode = ref->opcode;
211 return;
213 effect_reference.opcode = -2;
217 bool Init_EffectQueue()
219 int i;
221 if( initialized) {
222 return true;
224 memset( Opcodes, 0, sizeof( Opcodes ) );
225 for(i=0;i<MAX_EFFECTS;i++) {
226 Opcodes[i].Strref=-1;
229 initialized = 1;
231 AutoTable efftextTable("efftext");
233 int eT = core->LoadSymbol( "effects" );
234 if( eT < 0) {
235 printMessage( "EffectQueue","A critical scripting file is missing!\n",LIGHT_RED );
236 return false;
238 Holder<SymbolMgr> effectsTable = core->GetSymbol( eT );
239 if( !effectsTable) {
240 printMessage( "EffectQueue","A critical scripting file is damaged!\n",LIGHT_RED );
241 return false;
244 for (i = 0; i < MAX_EFFECTS; i++) {
245 const char* effectname = effectsTable->GetValue( i );
246 if( efftextTable) {
247 int row = efftextTable->GetRowCount();
248 while (row--) {
249 const char* ret = efftextTable->GetRowName( row );
250 long val;
251 if( valid_number( ret, val ) && (i == val) ) {
252 Opcodes[i].Strref = atoi( efftextTable->QueryField( row, 1 ) );
257 EffectRef* poi = FindEffect( effectname );
258 if( poi != NULL) {
259 Opcodes[i].Function = poi->Function;
260 Opcodes[i].Name = poi->Name;
261 //reverse linking opcode number
262 //using this unused field
263 if( (poi->opcode!=-1) && effectname[0]!='*') {
264 printf("Clashing Opcodes FN: %d vs. %d, %s\n", i, poi->opcode, effectname);
265 abort();
267 poi->opcode = i;
269 //printf("-------- FN: %d, %s\n", i, effectname);
271 core->DelSymbol( eT );
273 //additional initialisations
274 for (i=0;diced_effects[i].Name;i++) {
275 ResolveEffectRef(diced_effects[i]);
277 for (i=0;diced_effects2[i].Name;i++) {
278 ResolveEffectRef(diced_effects2[i]);
281 return true;
284 void EffectQueue_ReleaseMemory()
286 if( effectnames) {
287 free (effectnames);
289 effectnames_count = 0;
290 effectnames = NULL;
293 void EffectQueue_RegisterOpcodes(int count, const EffectRef* opcodes)
295 if( ! effectnames) {
296 effectnames = (EffectRef*) malloc( (count+1) * sizeof( EffectRef ) );
297 } else {
298 effectnames = (EffectRef*) realloc( effectnames, (effectnames_count + count + 1) * sizeof( EffectRef ) );
301 memcpy( effectnames + effectnames_count, opcodes, count * sizeof( EffectRef ));
302 effectnames_count += count;
303 effectnames[effectnames_count].Name = NULL;
304 //if we merge two effect lists, then we need to sort their effect tables
305 //actually, we might always want to sort this list, so there is no
306 //need to do it manually (sorted table is needed if we use bsearch)
307 qsort(effectnames, effectnames_count, sizeof(EffectRef), compare_effects);
310 EffectQueue::EffectQueue()
312 Owner = NULL;
315 EffectQueue::~EffectQueue()
317 std::list< Effect* >::iterator f;
319 for ( f = effects.begin(); f != effects.end(); f++ ) {
320 delete (*f);
324 Effect *EffectQueue::CreateEffect(ieDword opcode, ieDword param1, ieDword param2, ieWord timing)
326 if( opcode==0xffffffff) {
327 return NULL;
329 Effect *fx = new Effect();
330 if( !fx) {
331 return NULL;
333 memset(fx,0,sizeof(Effect));
334 fx->Target = FX_TARGET_SELF;
335 fx->Opcode = opcode;
336 //probability2 is the low number (by effectqueue 331)
337 fx->Probability1 = 100;
338 fx->Parameter1 = param1;
339 fx->Parameter2 = param2;
340 fx->TimingMode = timing;
341 fx->PosX = 0xffffffff;
342 fx->PosY = 0xffffffff;
343 return fx;
346 //return the count of effects with matching parameters
347 //useful for effects where there is no separate stat to see
348 ieDword EffectQueue::CountEffects(EffectRef &effect_reference, ieDword param1, ieDword param2, const char *resource) const
350 ResolveEffectRef(effect_reference);
351 if( effect_reference.opcode<0) {
352 return 0;
354 return CountEffects(effect_reference.opcode, param1, param2, resource);
357 //Change the location of an existing effect
358 //this is used when some external code needs to adjust the effect's location
359 //used when the gui sets the effect's final target
360 void EffectQueue::ModifyEffectPoint(EffectRef &effect_reference, ieDword x, ieDword y) const
362 ResolveEffectRef(effect_reference);
363 if( effect_reference.opcode<0) {
364 return;
366 ModifyEffectPoint(effect_reference.opcode, x, y);
369 Effect *EffectQueue::CreateEffect(EffectRef &effect_reference, ieDword param1, ieDword param2, ieWord timing)
371 ResolveEffectRef(effect_reference);
372 if( effect_reference.opcode<0) {
373 return NULL;
375 return CreateEffect(effect_reference.opcode, param1, param2, timing);
378 //copies the whole effectqueue (area projectiles use it)
379 EffectQueue *EffectQueue::CopySelf() const
381 EffectQueue *effects;
383 effects = new EffectQueue();
384 std::list< Effect* >::const_iterator fxit = GetFirstEffect();
385 Effect *fx;
387 while( (fx = GetNextEffect(fxit))) {
388 effects->AddEffect(fx, false);
390 effects->SetOwner(GetOwner());
391 return effects;
394 //create a new effect with most of the characteristics of the old effect
395 //only opcode and parameters are changed
396 //This is used mostly inside effects, when an effect needs to spawn
397 //other effects with the same coordinates, source, duration, etc.
398 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, ieDword opcode, ieDword param1, ieDword param2)
400 if( opcode==0xffffffff) {
401 return NULL;
403 Effect *fx = new Effect();
404 if( !fx) {
405 return NULL;
407 memcpy(fx,oldfx,sizeof(Effect) );
408 fx->Opcode=opcode;
409 fx->Parameter1=param1;
410 fx->Parameter2=param2;
411 return fx;
414 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, EffectRef &effect_reference, ieDword param1, ieDword param2)
416 ResolveEffectRef(effect_reference);
417 if( effect_reference.opcode<0) {
418 return NULL;
420 return CreateEffectCopy(oldfx, effect_reference.opcode, param1, param2);
423 static EffectRef fx_unsummon_creature_ref={"UnsummonCreature",NULL,-1};
425 Effect *EffectQueue::CreateUnsummonEffect(Effect *fx)
427 Effect *newfx = NULL;
428 if( (fx->TimingMode&0xff) == FX_DURATION_INSTANT_LIMITED) {
429 newfx = CreateEffectCopy(fx, fx_unsummon_creature_ref, 0, 0);
430 newfx->TimingMode = FX_DURATION_DELAY_PERMANENT;
431 if( newfx->Resource3[0]) {
432 strnuprcpy(newfx->Resource,newfx->Resource3, sizeof(ieResRef)-1 );
433 } else {
434 strnuprcpy(newfx->Resource,"SPGFLSH1", sizeof(ieResRef)-1 );
436 if( fx->TimingMode == FX_DURATION_ABSOLUTE) {
437 //unprepare duration
438 newfx->Duration = (newfx->Duration-core->GetGame()->GameTime)/AI_UPDATE_TIME;
442 return newfx;
445 void EffectQueue::AddEffect(Effect* fx, bool insert)
447 Effect* new_fx = new Effect;
448 memcpy( new_fx, fx, sizeof( Effect ) );
449 if( insert) {
450 effects.insert( effects.begin(), new_fx );
451 } else {
452 effects.push_back( new_fx );
456 //This method can remove an effect described by a pointer to it, or
457 //an exact matching effect
458 bool EffectQueue::RemoveEffect(Effect* fx)
460 int invariant_size = offsetof( Effect, random_value );
462 for (std::list< Effect* >::iterator f = effects.begin(); f != effects.end(); f++ ) {
463 Effect* fx2 = *f;
465 if( (fx==fx2) || !memcmp( fx, fx2, invariant_size)) {
466 delete fx2;
467 effects.erase( f );
468 return true;
471 return false;
474 //this is where we reapply all effects when loading a saved game
475 //The effects are already in the fxqueue of the target
476 void EffectQueue::ApplyAllEffects(Actor* target) const
478 std::list< Effect* >::const_iterator f;
479 for ( f = effects.begin(); f != effects.end(); f++ ) {
480 ApplyEffect( target, *f, 0 );
484 void EffectQueue::Cleanup()
486 std::list< Effect* >::iterator f;
488 for ( f = effects.begin(); f != effects.end(); ) {
489 if( (*f)->TimingMode == FX_DURATION_JUST_EXPIRED) {
490 delete *f;
491 effects.erase(f++);
492 } else {
493 f++;
498 //Handle the target flag when the effect is applied first
499 int EffectQueue::AddEffect(Effect* fx, Scriptable* self, Actor* pretarget, const Point &dest) const
501 int i;
502 Game *game;
503 Map *map;
504 int flg;
505 ieDword spec = 0;
506 Actor *st = (self && (self->Type==ST_ACTOR)) ?(Actor *) self:NULL;
508 switch (fx->Target) {
509 case FX_TARGET_ORIGINAL:
510 fx->SetPosition(self->Pos);
512 flg = ApplyEffect( st, fx, 1 );
513 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
514 if( st) {
515 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
518 break;
519 case FX_TARGET_SELF:
520 fx->SetPosition(dest);
522 flg = ApplyEffect( st, fx, 1 );
523 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
524 if( st) {
525 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
528 break;
530 case FX_TARGET_ALL_BUT_SELF:
531 map=self->GetCurrentArea();
532 i= map->GetActorCount(true);
533 while(i--) {
534 Actor* actor = map->GetActor( i, true );
535 //don't pick ourselves
536 if( st==actor) {
537 continue;
539 fx->SetPosition(actor->Pos);
541 flg = ApplyEffect( actor, fx, 1 );
542 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
543 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
546 flg = FX_APPLIED;
547 break;
549 case FX_TARGET_OWN_SIDE:
550 if( !st || st->InParty) {
551 goto all_party;
553 map = self->GetCurrentArea();
554 spec = st->GetStat(IE_SPECIFIC);
556 //GetActorCount(false) returns all nonparty critters
557 i = map->GetActorCount(false);
558 while(i--) {
559 Actor* actor = map->GetActor( i, false );
560 if( actor->GetStat(IE_SPECIFIC)!=spec) {
561 continue;
563 fx->SetPosition(actor->Pos);
565 flg = ApplyEffect( actor, fx, 1 );
566 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
567 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
570 flg = FX_APPLIED;
571 break;
572 case FX_TARGET_OTHER_SIDE:
573 if( !pretarget || pretarget->InParty) {
574 goto all_party;
576 map = self->GetCurrentArea();
577 spec = pretarget->GetStat(IE_SPECIFIC);
579 //GetActorCount(false) returns all nonparty critters
580 i = map->GetActorCount(false);
581 while(i--) {
582 Actor* actor = map->GetActor( i, false );
583 if( actor->GetStat(IE_SPECIFIC)!=spec) {
584 continue;
586 fx->SetPosition(actor->Pos);
588 flg = ApplyEffect( actor, fx, 1 );
589 //GetActorCount can now return all nonparty critters
590 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
591 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
594 flg = FX_APPLIED;
595 break;
596 case FX_TARGET_PRESET:
597 fx->SetPosition(pretarget->Pos);
599 flg = ApplyEffect( pretarget, fx, 1 );
600 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
601 if( pretarget) {
602 pretarget->fxqueue.AddEffect( fx, flg==FX_INSERT );
605 break;
607 case FX_TARGET_PARTY:
608 all_party:
609 game = core->GetGame();
610 i = game->GetPartySize(true);
611 while(i--) {
612 Actor* actor = game->GetPC( i, true );
613 fx->SetPosition(actor->Pos);
615 flg = ApplyEffect( actor, fx, 1 );
616 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
617 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
620 flg = FX_APPLIED;
621 break;
623 case FX_TARGET_ALL:
624 map = self->GetCurrentArea();
625 i = map->GetActorCount(true);
626 while(i--) {
627 Actor* actor = map->GetActor( i, true );
628 fx->SetPosition(actor->Pos);
630 flg = ApplyEffect( actor, fx, 1 );
631 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
632 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
635 flg = FX_APPLIED;
636 break;
638 case FX_TARGET_ALL_BUT_PARTY:
639 map = self->GetCurrentArea();
640 i = map->GetActorCount(false);
641 while(i--) {
642 Actor* actor = map->GetActor( i, false );
643 fx->SetPosition(actor->Pos);
645 flg = ApplyEffect( actor, fx, 1 );
646 //GetActorCount can now return all nonparty critters
647 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
648 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
651 flg = FX_APPLIED;
652 break;
654 case FX_TARGET_UNKNOWN:
655 default:
656 printf( "Unknown FX target type: %d\n", fx->Target);
657 flg = FX_ABORT;
658 break;
661 return flg;
664 //this is where effects from spells first get in touch with the target
665 //the effects are currently NOT in the target's fxqueue, those that stick
666 //will get copied (hence the fxqueue.AddEffect call)
667 //if this returns FX_NOT_APPLIED, then the whole stack was resisted
668 //or expired
669 int EffectQueue::AddAllEffects(Actor* target, const Point &destination) const
671 int res = FX_NOT_APPLIED;
672 // pre-roll dice for fx needing them and stow them in the effect
673 ieDword random_value = core->Roll( 1, 100, 0 );
675 if( target) {
676 target->RollSaves();
678 std::list< Effect* >::const_iterator f;
679 for ( f = effects.begin(); f != effects.end(); f++ ) {
680 //handle resistances and saving throws here
681 (*f)->random_value = random_value;
682 //if applyeffect returns true, we stop adding the future effects
683 //this is to simulate iwd2's on the fly spell resistance
685 int tmp = AddEffect(*f, Owner, target, destination);
686 //lets try without Owner, any crash?
687 //If yes, then try to fix the individual effect
688 //If you use target for Owner here, the wand in chateau irenicus will work
689 //the same way as Imoen's monster summoning, which is a BAD THING (TM)
690 //int tmp = AddEffect(*f, Owner?Owner:target, target, destination);
691 if( tmp == FX_ABORT) {
692 res = FX_NOT_APPLIED;
693 break;
695 if( tmp != FX_NOT_APPLIED) {
696 res = FX_APPLIED;
699 return res;
702 //check if an effect has no level based resistance, but instead the dice sizes/count
703 //adjusts Parameter1 (like a damage causing effect)
704 inline static bool IsDicedEffect(int opcode)
706 int i;
708 for(i=0;diced_effects[i].Name;i++) {
709 if( diced_effects[i].opcode==opcode) {
710 return true;
713 return false;
716 //there is no level based resistance, but Parameter1 cannot be precalculated
717 //these effects use the Dice fields in a special way
718 inline static bool IsDicedEffect2(int opcode)
720 int i;
722 for(i=0;diced_effects2[i].Name;i++) {
723 if( diced_effects2[i].opcode==opcode) {
724 return true;
727 return false;
730 //resisted effect based on level
731 inline bool check_level(Actor *target, Effect *fx)
733 //skip non level based effects
734 if( IsDicedEffect((int) fx->Opcode)) {
735 fx->Parameter1 = DICE_ROLL((signed)fx->Parameter1);
736 //this is a hack for PST style diced effects
737 if( core->HasFeature(GF_SAVE_FOR_HALF) ) {
738 if( memcmp(fx->Resource,"NEG",4) ) {
739 fx->IsSaveForHalfDamage=1;
741 } else {
742 if( (fx->Parameter2&3)==3) {
743 fx->IsSaveForHalfDamage=1;
746 return false;
748 if( IsDicedEffect2((int) fx->Opcode)) {
749 return false;
752 if( !target) {
753 return false;
756 ieDword level = (ieDword) target->GetXPLevel( true );
757 //return true if resisted
758 //level resistance is checked when DiceSides or DiceThrown
759 //are greater than 0 (sometimes they used -1 for our amusement)
760 //if level>than maximum affected or level<than minimum affected, then the
761 //effect is resisted
762 if( (fx->DiceSides > 0 || fx->DiceThrown > 0) && (level > fx->DiceSides || level < fx->DiceThrown)) {
763 return true;
765 return false;
768 //roll for the effect probability, there is a high and a low treshold, the d100
769 //roll should hit in the middle
770 inline bool check_probability(Effect* fx)
772 //watch for this, probability1 is the high number
773 //probability2 is the low number
774 //random value is 1-100
775 if( fx->random_value<=fx->Probability2 || fx->random_value>fx->Probability1) {
776 return false;
778 return true;
781 //immunity effects
782 static EffectRef fx_level_immunity_ref={"Protection:Spelllevel",NULL,-1};
783 static EffectRef fx_opcode_immunity_ref={"Protection:Opcode",NULL,-1}; //bg2
784 static EffectRef fx_opcode_immunity2_ref={"Protection:Opcode2",NULL,-1};//iwd
785 static EffectRef fx_spell_immunity_ref={"Protection:Spell",NULL,-1}; //bg2
786 static EffectRef fx_spell_immunity2_ref={"Protection:Spell2",NULL,-1};//iwd
787 static EffectRef fx_school_immunity_ref={"Protection:School",NULL,-1};
788 static EffectRef fx_secondary_type_immunity_ref={"Protection:SecondaryType",NULL,-1};
790 //decrementing immunity effects
791 static EffectRef fx_level_immunity_dec_ref={"Protection:SpellLevelDec",NULL,-1};
792 static EffectRef fx_spell_immunity_dec_ref={"Protection:SpellDec",NULL,-1};
793 static EffectRef fx_school_immunity_dec_ref={"Protection:SchoolDec",NULL,-1};
794 static EffectRef fx_secondary_type_immunity_dec_ref={"Protection:SecondaryTypeDec",NULL,-1};
796 //bounce effects
797 static EffectRef fx_level_bounce_ref={"Bounce:SpellLevel",NULL,-1};
798 //static EffectRef fx_opcode_bounce_ref={"Bounce:Opcode",NULL,-1};
799 static EffectRef fx_spell_bounce_ref={"Bounce:Spell",NULL,-1};
800 static EffectRef fx_school_bounce_ref={"Bounce:School",NULL,-1};
801 static EffectRef fx_secondary_type_bounce_ref={"Bounce:SecondaryType",NULL,-1};
803 //decrementing bounce effects
804 static EffectRef fx_level_bounce_dec_ref={"Bounce:SpellLevelDec",NULL,-1};
805 static EffectRef fx_spell_bounce_dec_ref={"Bounce:SpellDec",NULL,-1};
806 static EffectRef fx_school_bounce_dec_ref={"Bounce:SchoolDec",NULL,-1};
807 static EffectRef fx_secondary_type_bounce_dec_ref={"Bounce:SecondaryTypeDec",NULL,-1};
809 //spelltrap (multiple decrementing immunity)
810 static EffectRef fx_spelltrap={"SpellTrap", NULL,-1};
812 //this is for whole spell immunity/bounce
813 inline static void DecreaseEffect(Effect *efx)
815 efx->Parameter1--;
816 if( (int) efx->Parameter1<1) {
817 //don't remove effects directly!!!
818 efx->TimingMode = FX_DURATION_JUST_EXPIRED;
822 //lower decreasing immunities/bounces
823 static int check_type(Actor* actor, Effect* fx)
825 //the protective effect (if any)
826 Effect *efx;
828 ieDword bounce = actor->GetStat(IE_BOUNCE);
830 //immunity checks
831 /*opcode immunity is in the per opcode checks
832 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
833 return 0;
835 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
836 return 0;
839 //spell level immunity
840 if(fx->Power && actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_ref, fx->Power, 0) ) {
841 return 0;
844 //source immunity (spell name)
845 //if source is unspecified, don't resist it
846 if( fx->Source[0]) {
847 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity_ref, fx->Source) ) {
848 return 0;
850 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity2_ref, fx->Source) ) {
851 return 0;
855 //primary type immunity (school)
856 if( fx->PrimaryType) {
857 if( actor->fxqueue.HasEffectWithParam(fx_school_immunity_ref, fx->PrimaryType)) {
858 return 0;
862 //secondary type immunity (usage)
863 if( fx->SecondaryType) {
864 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_ref, fx->SecondaryType) ) {
865 return 0;
869 //decrementing immunity checks
870 //decrementing level immunity
871 efx = actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_dec_ref, fx->Power, 0);
872 if( efx ) {
873 DecreaseEffect(efx);
874 return 0;
877 //decrementing spell immunity
878 if( fx->Source[0]) {
879 efx = actor->fxqueue.HasEffectWithResource(fx_spell_immunity_dec_ref, fx->Source);
880 if( efx) {
881 DecreaseEffect(efx);
882 return 0;
885 //decrementing primary type immunity (school)
886 if( fx->PrimaryType) {
887 efx = actor->fxqueue.HasEffectWithParam(fx_school_immunity_dec_ref, fx->PrimaryType);
888 if( efx) {
889 DecreaseEffect(efx);
890 return 0;
894 //decrementing secondary type immunity (usage)
895 if( fx->SecondaryType) {
896 efx = actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_dec_ref, fx->SecondaryType);
897 if( efx) {
898 DecreaseEffect(efx);
899 return 0;
903 //spelltrap (absorb)
904 //FIXME:
905 //if the spelltrap effect already absorbed enough levels
906 //but still didn't get removed, it will absorb levels it shouldn't
907 //it will also absorb multiple spells in a single round
908 efx=actor->fxqueue.HasEffectWithParamPair(fx_spelltrap, 0, fx->Power);
909 if( efx) {
910 //storing the absorbed spell level
911 efx->Parameter3+=fx->Power;
912 //instead of a single effect, they had to create an effect for each level
913 //HOW DAMN LAME
914 //if decrease needs the spell level, use fx->Power here
915 actor->fxqueue.DecreaseParam1OfEffect(fx_spelltrap, 1);
916 //efx->Parameter1--;
917 return 0;
920 //bounce checks
921 if( (bounce&BNC_LEVEL) && actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_ref, fx->Power, 0) ) {
922 return 0;
925 if( fx->Source[0] && (bounce&BNC_RESOURCE) && actor->fxqueue.HasEffectWithResource(fx_spell_bounce_ref, fx->Source) ) {
926 return -1;
929 if( fx->PrimaryType && (bounce&BNC_SCHOOL) ) {
930 if( actor->fxqueue.HasEffectWithParam(fx_school_bounce_ref, fx->PrimaryType)) {
931 return -1;
935 if( fx->SecondaryType && (bounce&BNC_SECTYPE) ) {
936 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_ref, fx->SecondaryType)) {
937 return -1;
940 //decrementing bounce checks
942 //level decrementing bounce check
943 if( (bounce&BNC_LEVEL_DEC)) {
944 efx=actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_dec_ref, fx->Power, 0);
945 if( efx) {
946 DecreaseEffect(efx);
947 return -1;
951 if( fx->Source[0] && (bounce&BNC_RESOURCE_DEC)) {
952 efx=actor->fxqueue.HasEffectWithResource(fx_spell_bounce_dec_ref, fx->Resource);
953 if( efx) {
954 DecreaseEffect(efx);
955 return -1;
959 if( fx->PrimaryType && (bounce&BNC_SCHOOL_DEC) ) {
960 efx=actor->fxqueue.HasEffectWithParam(fx_school_bounce_dec_ref, fx->PrimaryType);
961 if( efx) {
962 DecreaseEffect(efx);
963 return -1;
967 if( fx->SecondaryType && (bounce&BNC_SECTYPE_DEC) ) {
968 efx=actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_dec_ref, fx->SecondaryType);
969 if( efx) {
970 DecreaseEffect(efx);
971 return -1;
975 return 1;
978 //check resistances, saving throws
979 static bool check_resistance(Actor* actor, Effect* fx)
981 if( !actor) {
982 return false;
985 //opcode immunity
986 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
987 printf ("immune to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
988 return true;
990 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
991 printf ("immune2 to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
992 return true;
995 /* opcode bouncing isn't implemented?
996 //opcode bouncing
997 if( actor->fxqueue.HasEffectWithParam(fx_opcode_bounce_ref, fx->Opcode) ) {
998 return false;
1002 //not resistable (no saves either?)
1003 if( fx->Resistance != FX_CAN_RESIST_CAN_DISPEL) {
1004 return false;
1007 //don't resist self
1008 if (fx->Target==FX_TARGET_SELF) {
1009 if (core->HasFeature(GF_SELECTIVE_MAGIC_RES) ) {
1010 return false;
1014 //magic immunity
1015 ieDword val = actor->GetStat(IE_RESISTMAGIC);
1016 if( fx->random_value < val) {
1017 printf ("effect resisted: %s\n", (char*) Opcodes[fx->Opcode].Name);
1018 return true;
1021 //saving throws
1022 bool saved = false;
1023 for (int i=0;i<5;i++) {
1024 if( fx->SavingThrowType&(1<<i)) {
1025 saved = actor->GetSavingThrow(i, fx->SavingThrowBonus);
1026 if( saved) {
1027 break;
1031 if( saved) {
1032 if( fx->IsSaveForHalfDamage) {
1033 fx->Parameter1/=2;
1034 } else {
1035 printf ("%s saved against effect: %s\n", actor->GetName(1), (char*) Opcodes[fx->Opcode].Name);
1036 return true;
1039 return false;
1042 // this function is called two different ways
1043 // when FirstApply is set, then the effect isn't stuck on the target
1044 // this happens when a new effect comes in contact with the target.
1045 // if the effect returns FX_DURATION_JUST_EXPIRED then it won't stick
1046 // when first_apply is unset, the effect is already on the target
1047 // this happens on load time too!
1048 // returns FX_NOT_APPLIED if the process shouldn't be calling applyeffect anymore
1049 // returns FX_ABORT if the whole spell this effect is in should be aborted
1050 // it will disable all future effects of same source (only on first apply)
1052 int EffectQueue::ApplyEffect(Actor* target, Effect* fx, ieDword first_apply) const
1054 //printf( "FX 0x%02x: %s(%d, %d)\n", fx->Opcode, effectnames[fx->Opcode].Name, fx->Parameter1, fx->Parameter2 );
1055 if( fx->Opcode >= MAX_EFFECTS) {
1056 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1057 return FX_NOT_APPLIED;
1060 ieDword GameTime = core->GetGame()->GameTime;
1062 fx->FirstApply=first_apply;
1063 if( first_apply) {
1064 if( (fx->PosX==0xffffffff) && (fx->PosY==0xffffffff)) {
1065 fx->PosX = target->Pos.x;
1066 fx->PosY = target->Pos.y;
1068 //the effect didn't pass the probability check
1069 if( !check_probability(fx) ) {
1070 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1071 return FX_NOT_APPLIED;
1074 //the effect didn't pass the target level check
1075 if( check_level(target, fx) ) {
1076 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1077 return FX_NOT_APPLIED;
1080 //the effect didn't pass the resistance check
1081 if( check_resistance(target, fx) ) {
1082 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1083 return FX_NOT_APPLIED;
1086 if( NeedPrepare(fx->TimingMode) ) {
1087 //save delay for later
1088 fx->SecondaryDelay = fx->Duration;
1089 if( fx->TimingMode == FX_DURATION_INSTANT_LIMITED) {
1090 fx->TimingMode = FX_DURATION_ABSOLUTE;
1092 PrepareDuration(fx);
1095 //check if the effect has triggered or expired
1096 switch (DelayType(fx->TimingMode&0xff) ) {
1097 case DELAYED:
1098 if( fx->Duration>GameTime) {
1099 return FX_NOT_APPLIED;
1101 //effect triggered
1102 //delayed duration (3)
1103 if( NeedPrepare(fx->TimingMode) ) {
1104 //prepare for delayed duration effects
1105 fx->Duration = fx->SecondaryDelay;
1106 PrepareDuration(fx);
1108 fx->TimingMode=TriggeredEffect(fx->TimingMode);
1109 break;
1110 case DURATION:
1111 if( fx->Duration<=GameTime) {
1112 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1113 //add a return here, if 0 duration effects shouldn't work
1115 break;
1116 //permanent effect (so there is no warning)
1117 case PERMANENT:
1118 break;
1119 //this shouldn't happen
1120 default:
1121 abort();
1124 EffectFunction fn = 0;
1125 if( fx->Opcode<MAX_EFFECTS) {
1126 fn = Opcodes[fx->Opcode].Function;
1128 int res = FX_ABORT;
1129 if( fn) {
1130 if( target && first_apply ) {
1131 if( !target->fxqueue.HasEffectWithParamPair(fx_protection_from_display_string_ref, fx->Parameter1, 0) ) {
1132 displaymsg->DisplayStringName( Opcodes[fx->Opcode].Strref, 0xf0f0f0,
1133 target, IE_STR_SOUND);
1137 res=fn( Owner, target, fx );
1139 //if there is no owner, we assume it is the target
1140 switch( res ) {
1141 case FX_APPLIED:
1142 //normal effect with duration
1143 break;
1144 case FX_NOT_APPLIED:
1145 //instant effect, pending removal
1146 //for example, a damage effect
1147 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1148 break;
1149 case FX_INSERT:
1150 //put this effect in the beginning of the queue
1151 //all known insert effects are 'permanent' too
1152 //that is the AC effect only
1153 //actually, permanent effects seem to be
1154 //inserted by the game engine too
1155 case FX_PERMANENT:
1156 //don't stick around if it was executed permanently
1157 //for example, a permanent strength modifier effect
1158 if( (fx->TimingMode == FX_DURATION_INSTANT_PERMANENT) ) {
1159 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1161 break;
1162 case FX_ABORT:
1163 break;
1164 default:
1165 abort();
1167 } else {
1168 //effect not found, it is going to be discarded
1169 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1171 return res;
1174 // looks for opcode with param2
1176 #define MATCH_OPCODE() if((*f)->Opcode!=opcode) { continue; }
1178 // useful for: remove equipped item
1179 #define MATCH_SLOTCODE() if((*f)->InventorySlot!=slotcode) { continue; }
1181 // useful for: remove projectile type
1182 #define MATCH_PROJECTILE() if((*f)->Projectile!=projectile) { continue; }
1184 static const bool fx_live[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,false};
1185 inline bool IsLive(ieByte timingmode)
1187 if( timingmode>=MAX_TIMING_MODE) return false;
1188 return fx_live[timingmode];
1191 #define MATCH_LIVE_FX() if(!IsLive((*f)->TimingMode)) { continue; }
1192 #define MATCH_PARAM1() if((*f)->Parameter1!=param1) { continue; }
1193 #define MATCH_PARAM2() if((*f)->Parameter2!=param2) { continue; }
1194 #define MATCH_RESOURCE() if( strnicmp( (*f)->Resource, resource, 8) ) { continue; }
1195 #define MATCH_SOURCE() if( strnicmp( (*f)->Source, Removed, 8) ) { continue; }
1196 #define MATCH_TIMING() if( (*f)->TimingMode!=timing) { continue; }
1198 //call this from an applied effect, after it returns, these effects
1199 //will be killed along with it
1200 void EffectQueue::RemoveAllEffects(ieDword opcode) const
1202 std::list< Effect* >::const_iterator f;
1203 for ( f = effects.begin(); f != effects.end(); f++ ) {
1204 MATCH_OPCODE();
1205 MATCH_LIVE_FX();
1207 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1211 //removes all equipping effects that match slotcode
1212 void EffectQueue::RemoveEquippingEffects(ieDwordSigned slotcode) const
1214 std::list< Effect* >::const_iterator f;
1215 for ( f = effects.begin(); f != effects.end(); f++ ) {
1216 if( !IsEquipped((*f)->TimingMode)) continue;
1217 MATCH_SLOTCODE();
1219 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1223 //removes all effects that match projectile
1224 void EffectQueue::RemoveAllEffectsWithProjectile(ieDword projectile) const
1226 std::list< Effect* >::const_iterator f;
1227 for ( f = effects.begin(); f != effects.end(); f++ ) {
1228 MATCH_PROJECTILE();
1230 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1234 //remove effects belonging to a given spell
1235 void EffectQueue::RemoveAllEffects(const ieResRef Removed) const
1237 std::list< Effect* >::const_iterator f;
1238 for ( f = effects.begin(); f != effects.end(); f++ ) {
1239 MATCH_LIVE_FX();
1240 MATCH_SOURCE();
1242 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1246 //remove effects belonging to a given spell, but only if they match timing method x
1247 void EffectQueue::RemoveAllEffects(const ieResRef Removed, ieByte timing) const
1249 std::list< Effect* >::const_iterator f;
1250 for ( f = effects.begin(); f != effects.end(); f++ ) {
1251 MATCH_TIMING();
1252 MATCH_SOURCE();
1254 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1258 //this will modify effect reference
1259 void EffectQueue::RemoveAllEffects(EffectRef &effect_reference) const
1261 ResolveEffectRef(effect_reference);
1262 if( effect_reference.opcode<0) {
1263 return;
1265 RemoveAllEffects(effect_reference.opcode);
1268 //Removes all effects with a matching resource field
1269 void EffectQueue::RemoveAllEffectsWithResource(ieDword opcode, const ieResRef resource) const
1271 std::list< Effect* >::const_iterator f;
1272 for ( f = effects.begin(); f != effects.end(); f++ ) {
1273 MATCH_OPCODE();
1274 MATCH_LIVE_FX();
1275 MATCH_RESOURCE();
1277 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1281 void EffectQueue::RemoveAllEffectsWithResource(EffectRef &effect_reference, const ieResRef resource) const
1283 ResolveEffectRef(effect_reference);
1284 RemoveAllEffectsWithResource(effect_reference.opcode, resource);
1287 //This method could be used to remove stat modifiers that would lower a stat
1288 //(works only if a higher stat means good for the target)
1289 void EffectQueue::RemoveAllDetrimentalEffects(ieDword opcode, ieDword current) const
1291 std::list< Effect* >::const_iterator f;
1292 for ( f = effects.begin(); f != effects.end(); f++ ) {
1293 MATCH_OPCODE();
1294 MATCH_LIVE_FX();
1295 switch((*f)->Parameter2) {
1296 case 0:case 3:
1297 if( ((signed) (*f)->Parameter1)>=0) continue;
1298 break;
1299 case 1:case 4:
1300 if( ((signed) (*f)->Parameter1)>=(signed) current) continue;
1301 break;
1302 case 2:case 5:
1303 if( ((signed) (*f)->Parameter1)>=100) continue;
1304 break;
1305 default:
1306 break;
1308 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1312 //Removes all effects with a matching param2
1313 //param2 is usually an effect's subclass (quality) while param1 is more like quantity.
1314 //So opcode+param2 usually pinpoints an effect better when not all effects of a given
1315 //opcode need to be removed (see removal of portrait icon)
1316 void EffectQueue::RemoveAllEffectsWithParam(ieDword opcode, ieDword param2) const
1318 std::list< Effect* >::const_iterator f;
1319 for ( f = effects.begin(); f != effects.end(); f++ ) {
1320 MATCH_OPCODE();
1321 MATCH_LIVE_FX();
1322 MATCH_PARAM2();
1324 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1328 //this function is called by FakeEffectExpiryCheck
1329 //probably also called by rest
1330 void EffectQueue::RemoveExpiredEffects(ieDword futuretime) const
1332 ieDword GameTime = core->GetGame()->GameTime;
1333 if( GameTime+futuretime*AI_UPDATE_TIME<GameTime) {
1334 GameTime=0xffffffff;
1335 } else {
1336 GameTime+=futuretime*AI_UPDATE_TIME;
1339 std::list< Effect* >::const_iterator f;
1340 for ( f = effects.begin(); f != effects.end(); f++ ) {
1341 //FIXME: how this method handles delayed effects???
1342 //it should remove them as well, i think
1343 if( DelayType( ((*f)->TimingMode) )!=PERMANENT ) {
1344 if( (*f)->Duration<=GameTime) {
1345 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1351 //this effect will expire all effects that are not truly permanent
1352 //which i call permanent after death (iesdp calls it permanent after bonuses)
1353 void EffectQueue::RemoveAllNonPermanentEffects() const
1355 std::list< Effect* >::const_iterator f;
1356 for ( f = effects.begin(); f != effects.end(); f++ ) {
1357 if( IsRemovable((*f)->TimingMode) ) {
1358 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1363 //this will modify effect reference
1365 void EffectQueue::RemoveAllDetrimentalEffects(EffectRef &effect_reference, ieDword current) const
1367 ResolveEffectRef(effect_reference);
1368 RemoveAllDetrimentalEffects(effect_reference.opcode, current);
1371 void EffectQueue::RemoveAllEffectsWithParam(EffectRef &effect_reference, ieDword param2) const
1373 ResolveEffectRef(effect_reference);
1374 RemoveAllEffectsWithParam(effect_reference.opcode, param2);
1377 //remove certain levels of effects, possibly matching school/secondary type
1378 //this method removes whole spells (tied together by their source)
1379 //FIXME: probably this isn't perfect
1380 void EffectQueue::RemoveLevelEffects(ieDword level, ieDword Flags, ieDword match) const
1382 ieResRef Removed;
1384 Removed[0]=0;
1385 std::list< Effect* >::const_iterator f;
1386 for ( f = effects.begin(); f != effects.end(); f++ ) {
1387 if( (*f)->Power>level) {
1388 continue;
1391 if( Removed[0]) {
1392 MATCH_SOURCE();
1394 if( Flags&RL_MATCHSCHOOL) {
1395 if( (*f)->PrimaryType!=match) {
1396 continue;
1399 if( Flags&RL_MATCHSECTYPE) {
1400 if( (*f)->SecondaryType!=match) {
1401 continue;
1404 //if dispellable was not set, or the effect is dispellable
1405 //then remove it
1406 if( Flags&RL_DISPELLABLE) {
1407 if( !((*f)->Resistance&FX_CAN_DISPEL)) {
1408 continue;
1411 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1412 if( Flags&RL_REMOVEFIRST) {
1413 memcpy(Removed,(*f)->Source, sizeof(Removed));
1418 Effect *EffectQueue::HasOpcode(ieDword opcode) const
1420 std::list< Effect* >::const_iterator f;
1421 for ( f = effects.begin(); f != effects.end(); f++ ) {
1422 MATCH_OPCODE();
1423 MATCH_LIVE_FX();
1425 return (*f);
1427 return NULL;
1430 Effect *EffectQueue::HasEffect(EffectRef &effect_reference) const
1432 ResolveEffectRef(effect_reference);
1433 if( effect_reference.opcode<0) {
1434 return NULL;
1436 return HasOpcode(effect_reference.opcode);
1439 Effect *EffectQueue::HasOpcodeWithParam(ieDword opcode, ieDword param2) const
1441 std::list< Effect* >::const_iterator f;
1442 for ( f = effects.begin(); f != effects.end(); f++ ) {
1443 MATCH_OPCODE();
1444 MATCH_LIVE_FX();
1445 MATCH_PARAM2();
1447 return (*f);
1449 return NULL;
1452 Effect *EffectQueue::HasEffectWithParam(EffectRef &effect_reference, ieDword param2) const
1454 ResolveEffectRef(effect_reference);
1455 if( effect_reference.opcode<0) {
1456 return NULL;
1458 return HasOpcodeWithParam(effect_reference.opcode, param2);
1461 //looks for opcode with pairs of parameters (useful for protection against creature, extra damage or extra thac0 against creature)
1462 //generally an IDS targeting
1464 Effect *EffectQueue::HasOpcodeWithParamPair(ieDword opcode, ieDword param1, ieDword param2) const
1466 std::list< Effect* >::const_iterator f;
1467 for ( f = effects.begin(); f != effects.end(); f++ ) {
1468 MATCH_OPCODE();
1469 MATCH_LIVE_FX();
1470 MATCH_PARAM2();
1471 //0 is always accepted as first parameter
1472 if( param1) {
1473 MATCH_PARAM1();
1476 return (*f);
1478 return NULL;
1481 Effect *EffectQueue::HasEffectWithParamPair(EffectRef &effect_reference, ieDword param1, ieDword param2) const
1483 ResolveEffectRef(effect_reference);
1484 if( effect_reference.opcode<0) {
1485 return NULL;
1487 return HasOpcodeWithParamPair(effect_reference.opcode, param1, param2);
1490 // sums all the values of the specific damage bonus effects of the passed "damage type"
1491 int EffectQueue::SpecificDamageBonus(ieDword opcode, ieDword param2) const
1493 int bonus = 0;
1494 std::list< Effect* >::const_iterator f;
1495 for ( f = effects.begin(); f != effects.end(); f++ ) {
1496 MATCH_OPCODE();
1497 MATCH_LIVE_FX();
1498 MATCH_PARAM2();
1499 bonus += (signed) (*f)->Parameter1;
1501 return bonus;
1504 static EffectRef fx_damage_bonus_modifier_ref={"DamageBonusModifier",NULL,-1};
1505 int EffectQueue::SpecificDamageBonus(ieDword damage_type) const
1507 ResolveEffectRef(fx_damage_bonus_modifier_ref);
1508 if(fx_damage_bonus_modifier_ref.opcode < 0) {
1509 return 0;
1511 return SpecificDamageBonus(fx_damage_bonus_modifier_ref.opcode, damage_type);
1514 //this could be used for stoneskins and mirror images as well
1515 void EffectQueue::DecreaseParam1OfEffect(ieDword opcode, ieDword amount) const
1517 std::list< Effect* >::const_iterator f;
1518 for ( f = effects.begin(); f != effects.end(); f++ ) {
1519 MATCH_OPCODE();
1520 MATCH_LIVE_FX();
1521 ieDword value = (*f)->Parameter1;
1522 if( value>amount) value-=amount;
1523 else value = 0;
1524 (*f)->Parameter1=value;
1528 void EffectQueue::DecreaseParam1OfEffect(EffectRef &effect_reference, ieDword amount) const
1530 ResolveEffectRef(effect_reference);
1531 if( effect_reference.opcode<0) {
1532 return;
1534 DecreaseParam1OfEffect(effect_reference.opcode, amount);
1538 //this function does IDS targeting for effects (extra damage/thac0 against creature)
1539 static const int ids_stats[7]={IE_EA, IE_GENERAL, IE_RACE, IE_CLASS, IE_SPECIFIC, IE_SEX, IE_ALIGNMENT};
1541 int EffectQueue::BonusAgainstCreature(ieDword opcode, Actor *actor) const
1543 int sum = 0;
1544 std::list< Effect* >::const_iterator f;
1545 for ( f = effects.begin(); f != effects.end(); f++ ) {
1546 MATCH_OPCODE();
1547 MATCH_LIVE_FX();
1548 ieDword ids = (*f)->Parameter2;
1549 if( ids<2 || ids>9) {
1550 ids=2;
1552 ieDword param1 = actor->GetStat(ids_stats[ids-2]);
1553 if( (*f)->Parameter1) {
1554 MATCH_PARAM1();
1556 int val = (int) (*f)->Parameter3;
1557 if( !val) val = 2;
1558 sum += val;
1560 return sum;
1563 int EffectQueue::BonusAgainstCreature(EffectRef &effect_reference, Actor *actor) const
1565 ResolveEffectRef(effect_reference);
1566 if( effect_reference.opcode<0) {
1567 return 0;
1569 return BonusAgainstCreature(effect_reference.opcode, actor);
1572 bool EffectQueue::WeaponImmunity(ieDword opcode, int enchantment, ieDword weapontype) const
1574 std::list< Effect* >::const_iterator f;
1575 for ( f = effects.begin(); f != effects.end(); f++ ) {
1576 MATCH_OPCODE();
1577 MATCH_LIVE_FX();
1579 int magic = (int) (*f)->Parameter1;
1580 ieDword mask = (*f)->Parameter3;
1581 ieDword value = (*f)->Parameter4;
1582 if( magic==0) {
1583 if( enchantment) continue;
1584 } else if( magic>0) {
1585 if( enchantment>magic) continue;
1588 if( (weapontype&mask) != value) {
1589 continue;
1591 return true;
1593 return false;
1596 static EffectRef fx_weapon_immunity_ref={"Protection:Weapons",NULL,-1};
1598 bool EffectQueue::WeaponImmunity(int enchantment, ieDword weapontype) const
1600 ResolveEffectRef(fx_weapon_immunity_ref);
1601 if( fx_weapon_immunity_ref.opcode<0) {
1602 return 0;
1604 return WeaponImmunity(fx_weapon_immunity_ref.opcode, enchantment, weapontype);
1607 static EffectRef fx_disable_spellcasting_ref={ "DisableCasting", NULL, -1 };
1608 int EffectQueue::DisabledSpellcasting(int types) const
1610 ResolveEffectRef(fx_disable_spellcasting_ref);
1611 if( fx_disable_spellcasting_ref.opcode < 0) {
1612 return 0;
1615 unsigned int spelltype_mask = 0;
1616 bool iwd2 = !!core->HasFeature(GF_ENHANCED_EFFECTS);
1617 ieDword opcode = fx_disable_spellcasting_ref.opcode;
1618 std::list< Effect* >::const_iterator f;
1619 for ( f = effects.begin(); f != effects.end(); f++ ) {
1620 MATCH_OPCODE();
1621 MATCH_LIVE_FX();
1623 if (iwd2) {
1624 switch((*f)->Parameter2) {
1625 case 0: // all
1626 spelltype_mask |= 7;
1627 break;
1628 case 1: // mage and cleric
1629 spelltype_mask |= 3;
1630 break;
1631 case 2: // mage
1632 spelltype_mask |= 2;
1633 break;
1634 case 3: // cleric
1635 spelltype_mask |= 1;
1636 break;
1637 case 4: // innate
1638 spelltype_mask |= 4;
1639 break;
1641 } else {
1642 switch((*f)->Parameter2) {
1643 case 0: // mage
1644 spelltype_mask |= 2;
1645 break;
1646 case 1: // cleric
1647 spelltype_mask |= 1;
1648 break;
1649 case 2: // innate
1650 spelltype_mask |= 4;
1651 break;
1655 return spelltype_mask & types;
1658 //useful for immunity vs spell, can't use item, etc.
1659 Effect *EffectQueue::HasOpcodeWithResource(ieDword opcode, const ieResRef resource) const
1661 std::list< Effect* >::const_iterator f;
1662 for ( f = effects.begin(); f != effects.end(); f++ ) {
1663 MATCH_OPCODE();
1664 MATCH_LIVE_FX();
1665 MATCH_RESOURCE();
1667 return (*f);
1669 return NULL;
1672 Effect *EffectQueue::HasEffectWithResource(EffectRef &effect_reference, const ieResRef resource) const
1674 ResolveEffectRef(effect_reference);
1675 return HasOpcodeWithResource(effect_reference.opcode, resource);
1678 //used in contingency/sequencer code (cannot have the same contingency twice)
1679 Effect *EffectQueue::HasOpcodeWithSource(ieDword opcode, const ieResRef Removed) const
1681 std::list< Effect* >::const_iterator f;
1682 for ( f = effects.begin(); f != effects.end(); f++ ) {
1683 MATCH_OPCODE();
1684 MATCH_LIVE_FX();
1685 MATCH_SOURCE();
1687 return (*f);
1689 return NULL;
1692 Effect *EffectQueue::HasEffectWithSource(EffectRef &effect_reference, const ieResRef resource) const
1694 ResolveEffectRef(effect_reference);
1695 return HasOpcodeWithSource(effect_reference.opcode, resource);
1698 bool EffectQueue::HasAnyDispellableEffect() const
1700 std::list< Effect* >::const_iterator f;
1701 for ( f = effects.begin(); f != effects.end(); f++ ) {
1702 if( (*f)->Resistance&FX_CAN_DISPEL) {
1703 return true;
1706 return false;
1709 void EffectQueue::dump() const
1711 printf( "EFFECT QUEUE:\n" );
1712 int i = 0;
1713 std::list< Effect* >::const_iterator f;
1714 for ( f = effects.begin(); f != effects.end(); f++ ) {
1715 Effect* fx = *f;
1716 if( fx) {
1717 char *Name = NULL;
1718 if( fx->Opcode < MAX_EFFECTS)
1719 Name = (char*) Opcodes[fx->Opcode].Name;
1721 printf( " %2d: 0x%02x: %s (%d, %d) S:%s\n", i++, fx->Opcode, Name, fx->Parameter1, fx->Parameter2, fx->Source );
1726 Effect *EffectQueue::GetEffect(ieDword idx) const
1728 if( effects.size()<=idx) {
1729 return NULL;
1731 return effects[idx];
1735 //returns true if the effect supports simplified duration
1736 bool EffectQueue::HasDuration(Effect *fx)
1738 switch(fx->TimingMode) {
1739 case FX_DURATION_INSTANT_LIMITED: //simple duration
1740 case FX_DURATION_DELAY_LIMITED: //delayed duration
1741 case FX_DURATION_DELAY_PERMANENT: //simple delayed
1742 return true;
1744 return false;
1747 static EffectRef fx_variable_ref={"Variable:StoreLocalVariable",NULL,-1};
1749 //returns true if the effect must be saved
1750 //variables are saved differently
1751 bool EffectQueue::Persistent(Effect* fx)
1753 //we save this as variable
1754 if( fx->Opcode==(ieDword) ResolveEffect(fx_variable_ref)) {
1755 return false;
1758 switch (fx->TimingMode) {
1759 //normal equipping fx of items
1760 case FX_DURATION_INSTANT_WHILE_EQUIPPED:
1761 //delayed effect not saved
1762 case FX_DURATION_DELAY_UNSAVED:
1763 //permanent effect not saved
1764 case FX_DURATION_PERMANENT_UNSAVED:
1765 //just expired effect
1766 case FX_DURATION_JUST_EXPIRED:
1767 return false;
1769 return true;
1772 //alter the color effect in case the item is equipped in the shield slot
1773 void EffectQueue::HackColorEffects(Actor *Owner, Effect *fx)
1775 if( fx->InventorySlot!=Owner->inventory.GetShieldSlot()) return;
1777 unsigned int gradienttype = fx->Parameter2 & 0xF0;
1778 if( gradienttype == 0x10) {
1779 gradienttype = 0x20; // off-hand
1780 fx->Parameter2 &= ~0xF0;
1781 fx->Parameter2 |= gradienttype;
1785 //iterate through saved effects
1786 const Effect *EffectQueue::GetNextSavedEffect(std::list< Effect* >::const_iterator &f) const
1788 while(f!=effects.end()) {
1789 Effect *effect = *f;
1790 f++;
1791 if( Persistent(effect)) {
1792 return effect;
1795 return NULL;
1798 Effect *EffectQueue::GetNextEffect(std::list< Effect* >::const_iterator &f) const
1800 if( f!=effects.end()) return *f++;
1801 return NULL;
1804 ieDword EffectQueue::CountEffects(ieDword opcode, ieDword param1, ieDword param2, const char *resource) const
1806 ieDword cnt = 0;
1808 std::list< Effect* >::const_iterator f;
1810 for ( f = effects.begin(); f != effects.end(); f++ ) {
1811 MATCH_OPCODE();
1812 if( param1!=0xffffffff)
1813 MATCH_PARAM1();
1814 if( param2!=0xffffffff)
1815 MATCH_PARAM2();
1816 if( resource) {
1817 MATCH_RESOURCE();
1819 cnt++;
1821 return cnt;
1824 void EffectQueue::ModifyEffectPoint(ieDword opcode, ieDword x, ieDword y) const
1826 std::list< Effect* >::const_iterator f;
1828 for ( f = effects.begin(); f != effects.end(); f++ ) {
1829 MATCH_OPCODE();
1830 (*f)->PosX=x;
1831 (*f)->PosY=y;
1832 (*f)->Parameter3=0;
1833 return;
1837 //count effects that get saved
1838 ieDword EffectQueue::GetSavedEffectsCount() const
1840 ieDword cnt = 0;
1842 std::list< Effect* >::const_iterator f;
1844 for ( f = effects.begin(); f != effects.end(); f++ ) {
1845 Effect* fx = *f;
1846 if( Persistent(fx))
1847 cnt++;
1849 return cnt;
1852 void EffectQueue::TransformToDelay(ieByte &TimingMode)
1854 if( TimingMode<MAX_TIMING_MODE) {;
1855 TimingMode = fx_to_delayed[TimingMode];
1856 } else {
1857 TimingMode = FX_DURATION_JUST_EXPIRED;
1861 int EffectQueue::ResolveEffect(EffectRef &effect_reference)
1863 ResolveEffectRef(effect_reference);
1864 return effect_reference.opcode;
1867 // this check goes for the whole effect block, not individual effects
1868 // But it takes the first effect of the block for the common fields
1870 //returns 1 if effect block applicable
1871 //returns 0 if effect block disabled
1872 //returns -1 if effect block bounced
1873 int EffectQueue::CheckImmunity(Actor *target) const
1875 //don't resist if target is non living
1876 if( !target) {
1877 return 1;
1880 if( effects.size() ) {
1881 Effect* fx = *effects.begin();
1883 //projectile immunity
1884 if( target->ImmuneToProjectile(fx->Projectile)) return 0;
1886 //don't resist item projectile payloads based on spell school, bounce, etc.
1887 if( fx->InventorySlot) {
1888 return 1;
1891 //check level resistances
1892 //check specific spell immunity
1893 //check school/sectype immunity
1894 return check_type(target, fx);
1896 return 0;
1899 void EffectQueue::AffectAllInRange(Map *map, const Point &pos, int idstype, int idsvalue,
1900 unsigned int range, Actor *except)
1902 int cnt = map->GetActorCount(true);
1903 while(cnt--) {
1904 Actor *actor = map->GetActor(cnt,true);
1905 if( except==actor) {
1906 continue;
1908 //distance
1909 if( Distance(pos, actor)>range) {
1910 continue;
1912 //ids targeting
1913 if( !match_ids(actor, idstype, idsvalue)) {
1914 continue;
1916 //line of sight
1917 if( !map->IsVisible(actor->Pos, pos)) {
1918 continue;
1920 AddAllEffects(actor, actor->Pos);