Constificiation.
[gemrb.git] / gemrb / core / EffectQueue.cpp
blobe40ab43a08c5fa5653d533488a0287d1b16cd7c4
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 <stdio.h>
22 #include "Interface.h"
23 #include "Actor.h"
24 #include "Effect.h"
25 #include "EffectQueue.h"
26 #include "SymbolMgr.h"
27 #include "Game.h"
28 #include "Map.h"
30 static struct {
31 const char* Name;
32 EffectFunction Function;
33 int Strref;
34 } Opcodes[MAX_EFFECTS];
36 static int initialized = 0;
38 static EffectRef *effectnames = NULL;
39 static int effectnames_count = 0;
41 bool EffectQueue::match_ids(Actor *target, int table, ieDword value)
43 if( value == 0) {
44 return true;
47 int a, stat;
49 switch (table) {
50 case 2: //EA
51 stat = IE_EA; break;
52 case 3: //GENERAL
53 stat = IE_GENERAL; break;
54 case 4: //RACE
55 stat = IE_RACE; break;
56 case 5: //CLASS
57 stat = IE_CLASS; break;
58 case 6: //SPECIFIC
59 stat = IE_SPECIFIC; break;
60 case 7: //GENDER
61 stat = IE_SEX; break;
62 case 8: //ALIGNMENT
63 stat = target->GetStat(IE_ALIGNMENT);
64 a = value&15;
65 if( a) {
66 if( a != ( stat & 15 )) {
67 return false;
70 a = value & 0xf0;
71 if( a) {
72 if( a != ( stat & 0xf0 )) {
73 return false;
76 return true;
77 default:
78 return false;
80 if( target->GetStat(stat)==value) {
81 return true;
83 return false;
86 static const bool fx_instant[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,true};
88 inline bool IsInstant(ieByte timingmode)
90 if( timingmode>=MAX_TIMING_MODE) return false;
91 return fx_instant[timingmode];
94 static const bool fx_equipped[MAX_TIMING_MODE]={false,false,true,false,false,true,false,false,true,false,false};
96 inline bool IsEquipped(ieByte timingmode)
98 if( timingmode>=MAX_TIMING_MODE) return false;
99 return fx_equipped[timingmode];
102 // 0 1 2 3 4 5 6 7 8 9 10
103 static const bool fx_relative[MAX_TIMING_MODE]={true,false,false,true,true,true,false,false,false,false,false};
105 inline bool NeedPrepare(ieWord timingmode)
107 if( timingmode>=MAX_TIMING_MODE) return false;
108 return fx_relative[timingmode];
111 #define INVALID -1
112 #define PERMANENT 0
113 #define DELAYED 1
114 #define DURATION 2
116 static const int fx_prepared[MAX_TIMING_MODE]={DURATION,PERMANENT,PERMANENT,DELAYED, //0-3
117 DELAYED,DELAYED,DELAYED,DELAYED,PERMANENT,PERMANENT,PERMANENT}; //4-7
119 inline int DelayType(ieByte timingmode)
121 if( timingmode>=MAX_TIMING_MODE) return INVALID;
122 return fx_prepared[timingmode];
125 //which effects are removable
126 static const bool fx_removable[MAX_TIMING_MODE]={true,true,false,true,true,false,true,true,false,false,true};
128 inline int IsRemovable(ieByte timingmode)
130 if( timingmode>=MAX_TIMING_MODE) return INVALID;
131 return fx_removable[timingmode];
134 //change the timing method after the effect triggered
135 static const ieByte fx_triggered[MAX_TIMING_MODE]={FX_DURATION_JUST_EXPIRED,FX_DURATION_INSTANT_PERMANENT,//0,1
136 FX_DURATION_INSTANT_WHILE_EQUIPPED,FX_DURATION_DELAY_LIMITED_PENDING,//2,3
137 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
138 FX_DURATION_INSTANT_LIMITED,FX_DURATION_JUST_EXPIRED,FX_DURATION_PERMANENT_UNSAVED,//6,8
139 FX_DURATION_INSTANT_PERMANENT_AFTER_BONUSES,FX_DURATION_JUST_EXPIRED};//9,10
141 //change the timing method for effect that should trigger after this effect expired
142 static const ieDword fx_to_delayed[]={FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,
143 FX_DURATION_PERMANENT_UNSAVED,FX_DURATION_DELAY_LIMITED_PENDING,
144 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
145 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,//6,8
146 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED};//9,10
148 inline ieByte TriggeredEffect(ieByte timingmode)
150 if( timingmode>=MAX_TIMING_MODE) return false;
151 return fx_triggered[timingmode];
154 int compare_effects(const void *a, const void *b)
156 return stricmp(((EffectRef *) a)->Name,((EffectRef *) b)->Name);
159 int find_effect(const void *a, const void *b)
161 return stricmp((const char *) a,((const EffectRef *) b)->Name);
164 static EffectRef* FindEffect(const char* effectname)
166 if( !effectname || !effectnames) {
167 return NULL;
169 void *tmp = bsearch(effectname, effectnames, effectnames_count, sizeof(EffectRef), find_effect);
170 if( !tmp) {
171 printMessage( "EffectQueue", "", YELLOW);
172 printf("Couldn't assign effect: %s\n", effectname );
174 return (EffectRef *) tmp;
177 static EffectRef fx_protection_from_display_string_ref={"Protection:String",NULL,-1};
179 //special effects without level check (but with damage dices precalculated)
180 static EffectRef diced_effects[] = {
181 //core effects
182 {"Damage",NULL,-1},
183 {"CurrentHPModifier",NULL,-1},
184 {"MaximumHPModifier",NULL,-1},
185 //iwd effects
186 {"BurningBlood",NULL,-1}, //iwd
187 {"ColdDamage",NULL,-1},
188 {"CrushingDamage",NULL,-1},
189 {"VampiricTouch",NULL,-1},
190 {"VitriolicSphere",NULL,-1},
191 //pst effects
192 {"TransferHP",NULL,-1},
193 {NULL,NULL,0} };
195 //special effects without level check (but with damage dices not precalculated)
196 static EffectRef diced_effects2[] = {
197 {"BurningBlood2",NULL,-1}, //how/iwd2
198 {"StaticCharge",NULL,-1}, //how/iwd2
199 {"LichTouch",NULL,-1}, //how
200 {NULL,NULL,0} };
202 inline static void ResolveEffectRef(EffectRef &effect_reference)
204 if( effect_reference.opcode==-1) {
205 EffectRef* ref = FindEffect(effect_reference.Name);
206 if( ref && ref->opcode>=0) {
207 effect_reference.opcode = ref->opcode;
208 return;
210 effect_reference.opcode = -2;
214 bool Init_EffectQueue()
216 int i;
218 if( initialized) {
219 return true;
221 memset( Opcodes, 0, sizeof( Opcodes ) );
222 for(i=0;i<MAX_EFFECTS;i++) {
223 Opcodes[i].Strref=-1;
226 initialized = 1;
228 AutoTable efftextTable("efftext");
230 int eT = core->LoadSymbol( "effects" );
231 if( eT < 0) {
232 printMessage( "EffectQueue","A critical scripting file is missing!\n",LIGHT_RED );
233 return false;
235 Holder<SymbolMgr> effectsTable = core->GetSymbol( eT );
236 if( !effectsTable) {
237 printMessage( "EffectQueue","A critical scripting file is damaged!\n",LIGHT_RED );
238 return false;
241 for (i = 0; i < MAX_EFFECTS; i++) {
242 const char* effectname = effectsTable->GetValue( i );
243 if( efftextTable) {
244 int row = efftextTable->GetRowCount();
245 while (row--) {
246 const char* ret = efftextTable->GetRowName( row );
247 long val;
248 if( valid_number( ret, val ) && (i == val) ) {
249 Opcodes[i].Strref = atoi( efftextTable->QueryField( row, 1 ) );
254 EffectRef* poi = FindEffect( effectname );
255 if( poi != NULL) {
256 Opcodes[i].Function = poi->Function;
257 Opcodes[i].Name = poi->Name;
258 //reverse linking opcode number
259 //using this unused field
260 if( (poi->opcode!=-1) && effectname[0]!='*') {
261 printf("Clashing Opcodes FN: %d vs. %d, %s\n", i, poi->opcode, effectname);
262 abort();
264 poi->opcode = i;
266 //printf("-------- FN: %d, %s\n", i, effectname);
268 core->DelSymbol( eT );
270 //additional initialisations
271 for (i=0;diced_effects[i].Name;i++) {
272 ResolveEffectRef(diced_effects[i]);
274 for (i=0;diced_effects2[i].Name;i++) {
275 ResolveEffectRef(diced_effects2[i]);
278 return true;
281 void EffectQueue_ReleaseMemory()
283 if( effectnames) {
284 free (effectnames);
286 effectnames_count = 0;
287 effectnames = NULL;
290 void EffectQueue_RegisterOpcodes(int count, const EffectRef* opcodes)
292 if( ! effectnames) {
293 effectnames = (EffectRef*) malloc( (count+1) * sizeof( EffectRef ) );
294 } else {
295 effectnames = (EffectRef*) realloc( effectnames, (effectnames_count + count + 1) * sizeof( EffectRef ) );
298 memcpy( effectnames + effectnames_count, opcodes, count * sizeof( EffectRef ));
299 effectnames_count += count;
300 effectnames[effectnames_count].Name = NULL;
301 //if we merge two effect lists, then we need to sort their effect tables
302 //actually, we might always want to sort this list, so there is no
303 //need to do it manually (sorted table is needed if we use bsearch)
304 qsort(effectnames, effectnames_count, sizeof(EffectRef), compare_effects);
307 EffectQueue::EffectQueue()
309 Owner = NULL;
312 EffectQueue::~EffectQueue()
314 std::list< Effect* >::iterator f;
316 for ( f = effects.begin(); f != effects.end(); f++ ) {
317 delete (*f);
321 Effect *EffectQueue::CreateEffect(ieDword opcode, ieDword param1, ieDword param2, ieWord timing)
323 if( opcode==0xffffffff) {
324 return NULL;
326 Effect *fx = new Effect();
327 if( !fx) {
328 return NULL;
330 memset(fx,0,sizeof(Effect));
331 fx->Target = FX_TARGET_SELF;
332 fx->Opcode = opcode;
333 //probability2 is the low number (by effectqueue 331)
334 fx->Probability1 = 100;
335 fx->Parameter1 = param1;
336 fx->Parameter2 = param2;
337 fx->TimingMode = timing;
338 fx->PosX = 0xffffffff;
339 fx->PosY = 0xffffffff;
340 return fx;
343 //return the count of effects with matching parameters
344 //useful for effects where there is no separate stat to see
345 ieDword EffectQueue::CountEffects(EffectRef &effect_reference, ieDword param1, ieDword param2, const char *resource) const
347 ResolveEffectRef(effect_reference);
348 if( effect_reference.opcode<0) {
349 return 0;
351 return CountEffects(effect_reference.opcode, param1, param2, resource);
354 //Change the location of an existing effect
355 //this is used when some external code needs to adjust the effect's location
356 //used when the gui sets the effect's final target
357 void EffectQueue::ModifyEffectPoint(EffectRef &effect_reference, ieDword x, ieDword y) const
359 ResolveEffectRef(effect_reference);
360 if( effect_reference.opcode<0) {
361 return;
363 ModifyEffectPoint(effect_reference.opcode, x, y);
366 Effect *EffectQueue::CreateEffect(EffectRef &effect_reference, ieDword param1, ieDword param2, ieWord timing)
368 ResolveEffectRef(effect_reference);
369 if( effect_reference.opcode<0) {
370 return NULL;
372 return CreateEffect(effect_reference.opcode, param1, param2, timing);
375 //copies the whole effectqueue (area projectiles use it)
376 EffectQueue *EffectQueue::CopySelf() const
378 EffectQueue *effects;
380 effects = new EffectQueue();
381 std::list< Effect* >::const_iterator fxit = GetFirstEffect();
382 Effect *fx;
384 while( (fx = GetNextEffect(fxit))) {
385 effects->AddEffect(fx, false);
387 effects->SetOwner(GetOwner());
388 return effects;
391 //create a new effect with most of the characteristics of the old effect
392 //only opcode and parameters are changed
393 //This is used mostly inside effects, when an effect needs to spawn
394 //other effects with the same coordinates, source, duration, etc.
395 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, ieDword opcode, ieDword param1, ieDword param2)
397 if( opcode==0xffffffff) {
398 return NULL;
400 Effect *fx = new Effect();
401 if( !fx) {
402 return NULL;
404 memcpy(fx,oldfx,sizeof(Effect) );
405 fx->Opcode=opcode;
406 fx->Parameter1=param1;
407 fx->Parameter2=param2;
408 return fx;
411 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, EffectRef &effect_reference, ieDword param1, ieDword param2)
413 ResolveEffectRef(effect_reference);
414 if( effect_reference.opcode<0) {
415 return NULL;
417 return CreateEffectCopy(oldfx, effect_reference.opcode, param1, param2);
420 static EffectRef fx_unsummon_creature_ref={"UnsummonCreature",NULL,-1};
422 Effect *EffectQueue::CreateUnsummonEffect(Effect *fx)
424 Effect *newfx = NULL;
425 if( (fx->TimingMode&0xff) == FX_DURATION_INSTANT_LIMITED) {
426 newfx = CreateEffectCopy(fx, fx_unsummon_creature_ref, 0, 0);
427 newfx->TimingMode = FX_DURATION_DELAY_PERMANENT;
428 if( newfx->Resource3[0]) {
429 strnuprcpy(newfx->Resource,newfx->Resource3, sizeof(ieResRef)-1 );
430 } else {
431 strnuprcpy(newfx->Resource,"SPGFLSH1", sizeof(ieResRef)-1 );
433 if( fx->TimingMode == FX_DURATION_ABSOLUTE) {
434 //unprepare duration
435 newfx->Duration = (newfx->Duration-core->GetGame()->GameTime)/AI_UPDATE_TIME;
439 return newfx;
442 void EffectQueue::AddEffect(Effect* fx, bool insert)
444 Effect* new_fx = new Effect;
445 memcpy( new_fx, fx, sizeof( Effect ) );
446 if( insert) {
447 effects.insert( effects.begin(), new_fx );
448 } else {
449 effects.push_back( new_fx );
453 //This method can remove an effect described by a pointer to it, or
454 //an exact matching effect
455 bool EffectQueue::RemoveEffect(Effect* fx)
457 int invariant_size = offsetof( Effect, random_value );
459 for (std::list< Effect* >::iterator f = effects.begin(); f != effects.end(); f++ ) {
460 Effect* fx2 = *f;
462 if( (fx==fx2) || !memcmp( fx, fx2, invariant_size)) {
463 delete fx2;
464 effects.erase( f );
465 return true;
468 return false;
471 //this is where we reapply all effects when loading a saved game
472 //The effects are already in the fxqueue of the target
473 void EffectQueue::ApplyAllEffects(Actor* target) const
475 std::list< Effect* >::const_iterator f;
476 for ( f = effects.begin(); f != effects.end(); f++ ) {
477 ApplyEffect( target, *f, 0 );
481 void EffectQueue::Cleanup()
483 std::list< Effect* >::iterator f;
485 for ( f = effects.begin(); f != effects.end(); ) {
486 if( (*f)->TimingMode == FX_DURATION_JUST_EXPIRED) {
487 delete *f;
488 effects.erase(f++);
489 } else {
490 f++;
495 //Handle the target flag when the effect is applied first
496 int EffectQueue::AddEffect(Effect* fx, Scriptable* self, Actor* pretarget, const Point &dest) const
498 int i;
499 Game *game;
500 Map *map;
501 int flg;
502 ieDword spec = 0;
503 Actor *st = (self && (self->Type==ST_ACTOR)) ?(Actor *) self:NULL;
505 switch (fx->Target) {
506 case FX_TARGET_ORIGINAL:
507 fx->SetPosition(self->Pos);
509 flg = ApplyEffect( st, fx, 1 );
510 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
511 if( st) {
512 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
515 break;
516 case FX_TARGET_SELF:
517 fx->SetPosition(dest);
519 flg = ApplyEffect( st, fx, 1 );
520 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
521 if( st) {
522 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
525 break;
527 case FX_TARGET_ALL_BUT_SELF:
528 map=self->GetCurrentArea();
529 i= map->GetActorCount(true);
530 while(i--) {
531 Actor* actor = map->GetActor( i, true );
532 //don't pick ourselves
533 if( st==actor) {
534 continue;
536 fx->SetPosition(actor->Pos);
538 flg = ApplyEffect( actor, fx, 1 );
539 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
540 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
543 flg = FX_APPLIED;
544 break;
546 case FX_TARGET_OWN_SIDE:
547 if( !st || st->InParty) {
548 goto all_party;
550 map = self->GetCurrentArea();
551 spec = st->GetStat(IE_SPECIFIC);
553 //GetActorCount(false) returns all nonparty critters
554 i = map->GetActorCount(false);
555 while(i--) {
556 Actor* actor = map->GetActor( i, false );
557 if( actor->GetStat(IE_SPECIFIC)!=spec) {
558 continue;
560 fx->SetPosition(actor->Pos);
562 flg = ApplyEffect( actor, fx, 1 );
563 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
564 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
567 flg = FX_APPLIED;
568 break;
569 case FX_TARGET_OTHER_SIDE:
570 if( !pretarget || pretarget->InParty) {
571 goto all_party;
573 map = self->GetCurrentArea();
574 spec = pretarget->GetStat(IE_SPECIFIC);
576 //GetActorCount(false) returns all nonparty critters
577 i = map->GetActorCount(false);
578 while(i--) {
579 Actor* actor = map->GetActor( i, false );
580 if( actor->GetStat(IE_SPECIFIC)!=spec) {
581 continue;
583 fx->SetPosition(actor->Pos);
585 flg = ApplyEffect( actor, fx, 1 );
586 //GetActorCount can now return all nonparty critters
587 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
588 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
591 flg = FX_APPLIED;
592 break;
593 case FX_TARGET_PRESET:
594 fx->SetPosition(pretarget->Pos);
596 flg = ApplyEffect( pretarget, fx, 1 );
597 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
598 if( pretarget) {
599 pretarget->fxqueue.AddEffect( fx, flg==FX_INSERT );
602 break;
604 case FX_TARGET_PARTY:
605 all_party:
606 game = core->GetGame();
607 i = game->GetPartySize(true);
608 while(i--) {
609 Actor* actor = game->GetPC( i, true );
610 fx->SetPosition(actor->Pos);
612 flg = ApplyEffect( actor, fx, 1 );
613 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
614 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
617 flg = FX_APPLIED;
618 break;
620 case FX_TARGET_ALL:
621 map = self->GetCurrentArea();
622 i = map->GetActorCount(true);
623 while(i--) {
624 Actor* actor = map->GetActor( i, true );
625 fx->SetPosition(actor->Pos);
627 flg = ApplyEffect( actor, fx, 1 );
628 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
629 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
632 flg = FX_APPLIED;
633 break;
635 case FX_TARGET_ALL_BUT_PARTY:
636 map = self->GetCurrentArea();
637 i = map->GetActorCount(false);
638 while(i--) {
639 Actor* actor = map->GetActor( i, false );
640 fx->SetPosition(actor->Pos);
642 flg = ApplyEffect( actor, fx, 1 );
643 //GetActorCount can now return all nonparty critters
644 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
645 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
648 flg = FX_APPLIED;
649 break;
651 case FX_TARGET_UNKNOWN:
652 default:
653 printf( "Unknown FX target type: %d\n", fx->Target);
654 flg = FX_ABORT;
655 break;
658 return flg;
661 //this is where effects from spells first get in touch with the target
662 //the effects are currently NOT in the target's fxqueue, those that stick
663 //will get copied (hence the fxqueue.AddEffect call)
664 //if this returns FX_NOT_APPLIED, then the whole stack was resisted
665 //or expired
666 int EffectQueue::AddAllEffects(Actor* target, const Point &destination) const
668 int res = FX_NOT_APPLIED;
669 // pre-roll dice for fx needing them and stow them in the effect
670 ieDword random_value = core->Roll( 1, 100, 0 );
672 if( target) {
673 target->RollSaves();
675 std::list< Effect* >::const_iterator f;
676 for ( f = effects.begin(); f != effects.end(); f++ ) {
677 //handle resistances and saving throws here
678 (*f)->random_value = random_value;
679 //if applyeffect returns true, we stop adding the future effects
680 //this is to simulate iwd2's on the fly spell resistance
682 int tmp = AddEffect(*f, Owner, target, destination);
683 //lets try without Owner, any crash?
684 //If yes, then try to fix the individual effect
685 //If you use target for Owner here, the wand in chateau irenicus will work
686 //the same way as Imoen's monster summoning, which is a BAD THING (TM)
687 //int tmp = AddEffect(*f, Owner?Owner:target, target, destination);
688 if( tmp == FX_ABORT) {
689 res = FX_NOT_APPLIED;
690 break;
692 if( tmp != FX_NOT_APPLIED) {
693 res = FX_APPLIED;
696 return res;
699 //check if an effect has no level based resistance, but instead the dice sizes/count
700 //adjusts Parameter1 (like a damage causing effect)
701 inline static bool IsDicedEffect(int opcode)
703 int i;
705 for(i=0;diced_effects[i].Name;i++) {
706 if( diced_effects[i].opcode==opcode) {
707 return true;
710 return false;
713 //there is no level based resistance, but Parameter1 cannot be precalculated
714 //these effects use the Dice fields in a special way
715 inline static bool IsDicedEffect2(int opcode)
717 int i;
719 for(i=0;diced_effects2[i].Name;i++) {
720 if( diced_effects2[i].opcode==opcode) {
721 return true;
724 return false;
727 //resisted effect based on level
728 inline bool check_level(Actor *target, Effect *fx)
730 //skip non level based effects
731 if( IsDicedEffect((int) fx->Opcode)) {
732 fx->Parameter1 = DICE_ROLL((signed)fx->Parameter1);
733 //this is a hack for PST style diced effects
734 if( core->HasFeature(GF_SAVE_FOR_HALF) ) {
735 if( memcmp(fx->Resource,"NEG",4) ) {
736 fx->IsSaveForHalfDamage=1;
738 } else {
739 if( (fx->Parameter2&3)==3) {
740 fx->IsSaveForHalfDamage=1;
743 return false;
745 if( IsDicedEffect2((int) fx->Opcode)) {
746 return false;
749 if( !target) {
750 return false;
753 ieDword level = (ieDword) target->GetXPLevel( true );
754 //return true if resisted
755 //level resistance is checked when DiceSides or DiceThrown
756 //are greater than 0 (sometimes they used -1 for our amusement)
757 //if level>than maximum affected or level<than minimum affected, then the
758 //effect is resisted
759 if( (fx->DiceSides > 0 || fx->DiceThrown > 0) && (level > fx->DiceSides || level < fx->DiceThrown)) {
760 return true;
762 return false;
765 //roll for the effect probability, there is a high and a low treshold, the d100
766 //roll should hit in the middle
767 inline bool check_probability(Effect* fx)
769 //watch for this, probability1 is the high number
770 //probability2 is the low number
771 //random value is 1-100
772 if( fx->random_value<=fx->Probability2 || fx->random_value>fx->Probability1) {
773 return false;
775 return true;
778 //immunity effects
779 static EffectRef fx_level_immunity_ref={"Protection:Spelllevel",NULL,-1};
780 static EffectRef fx_opcode_immunity_ref={"Protection:Opcode",NULL,-1}; //bg2
781 static EffectRef fx_opcode_immunity2_ref={"Protection:Opcode2",NULL,-1};//iwd
782 static EffectRef fx_spell_immunity_ref={"Protection:Spell",NULL,-1}; //bg2
783 static EffectRef fx_spell_immunity2_ref={"Protection:Spell2",NULL,-1};//iwd
784 static EffectRef fx_school_immunity_ref={"Protection:School",NULL,-1};
785 static EffectRef fx_secondary_type_immunity_ref={"Protection:SecondaryType",NULL,-1};
787 //decrementing immunity effects
788 static EffectRef fx_level_immunity_dec_ref={"Protection:SpellLevelDec",NULL,-1};
789 static EffectRef fx_spell_immunity_dec_ref={"Protection:SpellDec",NULL,-1};
790 static EffectRef fx_school_immunity_dec_ref={"Protection:SchoolDec",NULL,-1};
791 static EffectRef fx_secondary_type_immunity_dec_ref={"Protection:SecondaryTypeDec",NULL,-1};
793 //bounce effects
794 static EffectRef fx_level_bounce_ref={"Bounce:SpellLevel",NULL,-1};
795 //static EffectRef fx_opcode_bounce_ref={"Bounce:Opcode",NULL,-1};
796 static EffectRef fx_spell_bounce_ref={"Bounce:Spell",NULL,-1};
797 static EffectRef fx_school_bounce_ref={"Bounce:School",NULL,-1};
798 static EffectRef fx_secondary_type_bounce_ref={"Bounce:SecondaryType",NULL,-1};
800 //decrementing bounce effects
801 static EffectRef fx_level_bounce_dec_ref={"Bounce:SpellLevelDec",NULL,-1};
802 static EffectRef fx_spell_bounce_dec_ref={"Bounce:SpellDec",NULL,-1};
803 static EffectRef fx_school_bounce_dec_ref={"Bounce:SchoolDec",NULL,-1};
804 static EffectRef fx_secondary_type_bounce_dec_ref={"Bounce:SecondaryTypeDec",NULL,-1};
806 //spelltrap (multiple decrementing immunity)
807 static EffectRef fx_spelltrap={"SpellTrap", NULL,-1};
809 //this is for whole spell immunity/bounce
810 inline static void DecreaseEffect(Effect *efx)
812 efx->Parameter1--;
813 if( (int) efx->Parameter1<1) {
814 //don't remove effects directly!!!
815 efx->TimingMode = FX_DURATION_JUST_EXPIRED;
819 //lower decreasing immunities/bounces
820 static int check_type(Actor* actor, Effect* fx)
822 //the protective effect (if any)
823 Effect *efx;
825 ieDword bounce = actor->GetStat(IE_BOUNCE);
827 //immunity checks
828 /*opcode immunity is in the per opcode checks
829 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
830 return 0;
832 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
833 return 0;
836 //spell level immunity
837 if(fx->Power && actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_ref, fx->Power, 0) ) {
838 return 0;
841 //source immunity (spell name)
842 //if source is unspecified, don't resist it
843 if( fx->Source[0]) {
844 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity_ref, fx->Source) ) {
845 return 0;
847 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity2_ref, fx->Source) ) {
848 return 0;
852 //primary type immunity (school)
853 if( fx->PrimaryType) {
854 if( actor->fxqueue.HasEffectWithParam(fx_school_immunity_ref, fx->PrimaryType)) {
855 return 0;
859 //secondary type immunity (usage)
860 if( fx->SecondaryType) {
861 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_ref, fx->SecondaryType) ) {
862 return 0;
866 //decrementing immunity checks
867 //decrementing level immunity
868 efx = actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_dec_ref, fx->Power, 0);
869 if( efx ) {
870 DecreaseEffect(efx);
871 return 0;
874 //decrementing spell immunity
875 if( fx->Source[0]) {
876 efx = actor->fxqueue.HasEffectWithResource(fx_spell_immunity_dec_ref, fx->Source);
877 if( efx) {
878 DecreaseEffect(efx);
879 return 0;
882 //decrementing primary type immunity (school)
883 if( fx->PrimaryType) {
884 efx = actor->fxqueue.HasEffectWithParam(fx_school_immunity_dec_ref, fx->PrimaryType);
885 if( efx) {
886 DecreaseEffect(efx);
887 return 0;
891 //decrementing secondary type immunity (usage)
892 if( fx->SecondaryType) {
893 efx = actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_dec_ref, fx->SecondaryType);
894 if( efx) {
895 DecreaseEffect(efx);
896 return 0;
900 //spelltrap (absorb)
901 //FIXME:
902 //if the spelltrap effect already absorbed enough levels
903 //but still didn't get removed, it will absorb levels it shouldn't
904 //it will also absorb multiple spells in a single round
905 efx=actor->fxqueue.HasEffectWithParamPair(fx_spelltrap, 0, fx->Power);
906 if( efx) {
907 //storing the absorbed spell level
908 efx->Parameter3+=fx->Power;
909 //instead of a single effect, they had to create an effect for each level
910 //HOW DAMN LAME
911 //if decrease needs the spell level, use fx->Power here
912 actor->fxqueue.DecreaseParam1OfEffect(fx_spelltrap, 1);
913 //efx->Parameter1--;
914 return 0;
917 //bounce checks
918 if( (bounce&BNC_LEVEL) && actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_ref, fx->Power, 0) ) {
919 return 0;
922 if( fx->Source[0] && (bounce&BNC_RESOURCE) && actor->fxqueue.HasEffectWithResource(fx_spell_bounce_ref, fx->Source) ) {
923 return -1;
926 if( fx->PrimaryType && (bounce&BNC_SCHOOL) ) {
927 if( actor->fxqueue.HasEffectWithParam(fx_school_bounce_ref, fx->PrimaryType)) {
928 return -1;
932 if( fx->SecondaryType && (bounce&BNC_SECTYPE) ) {
933 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_ref, fx->SecondaryType)) {
934 return -1;
937 //decrementing bounce checks
939 //level decrementing bounce check
940 if( (bounce&BNC_LEVEL_DEC)) {
941 efx=actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_dec_ref, fx->Power, 0);
942 if( efx) {
943 DecreaseEffect(efx);
944 return -1;
948 if( fx->Source[0] && (bounce&BNC_RESOURCE_DEC)) {
949 efx=actor->fxqueue.HasEffectWithResource(fx_spell_bounce_dec_ref, fx->Resource);
950 if( efx) {
951 DecreaseEffect(efx);
952 return -1;
956 if( fx->PrimaryType && (bounce&BNC_SCHOOL_DEC) ) {
957 efx=actor->fxqueue.HasEffectWithParam(fx_school_bounce_dec_ref, fx->PrimaryType);
958 if( efx) {
959 DecreaseEffect(efx);
960 return -1;
964 if( fx->SecondaryType && (bounce&BNC_SECTYPE_DEC) ) {
965 efx=actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_dec_ref, fx->SecondaryType);
966 if( efx) {
967 DecreaseEffect(efx);
968 return -1;
972 return 1;
975 //check resistances, saving throws
976 static bool check_resistance(Actor* actor, Effect* fx)
978 if( !actor) {
979 return false;
982 //opcode immunity
983 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
984 printf ("immune to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
985 return true;
987 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
988 printf ("immune2 to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
989 return true;
992 /* opcode bouncing isn't implemented?
993 //opcode bouncing
994 if( actor->fxqueue.HasEffectWithParam(fx_opcode_bounce_ref, fx->Opcode) ) {
995 return false;
999 //not resistable (no saves either?)
1000 if( fx->Resistance != FX_CAN_RESIST_CAN_DISPEL) {
1001 return false;
1004 //don't resist self
1005 if (fx->Target==FX_TARGET_SELF) {
1006 if (core->HasFeature(GF_SELECTIVE_MAGIC_RES) ) {
1007 return false;
1011 //magic immunity
1012 ieDword val = actor->GetStat(IE_RESISTMAGIC);
1013 if( fx->random_value < val) {
1014 printf ("effect resisted: %s\n", (char*) Opcodes[fx->Opcode].Name);
1015 return true;
1018 //saving throws
1019 bool saved = false;
1020 for (int i=0;i<5;i++) {
1021 if( fx->SavingThrowType&(1<<i)) {
1022 saved = actor->GetSavingThrow(i, fx->SavingThrowBonus);
1023 if( saved) {
1024 break;
1028 if( saved) {
1029 if( fx->IsSaveForHalfDamage) {
1030 fx->Parameter1/=2;
1031 } else {
1032 printf ("%s saved against effect: %s\n", actor->GetName(1), (char*) Opcodes[fx->Opcode].Name);
1033 return true;
1036 return false;
1039 // this function is called two different ways
1040 // when FirstApply is set, then the effect isn't stuck on the target
1041 // this happens when a new effect comes in contact with the target.
1042 // if the effect returns FX_DURATION_JUST_EXPIRED then it won't stick
1043 // when first_apply is unset, the effect is already on the target
1044 // this happens on load time too!
1045 // returns FX_NOT_APPLIED if the process shouldn't be calling applyeffect anymore
1046 // returns FX_ABORT if the whole spell this effect is in should be aborted
1047 // it will disable all future effects of same source (only on first apply)
1049 int EffectQueue::ApplyEffect(Actor* target, Effect* fx, ieDword first_apply) const
1051 //printf( "FX 0x%02x: %s(%d, %d)\n", fx->Opcode, effectnames[fx->Opcode].Name, fx->Parameter1, fx->Parameter2 );
1052 if( fx->Opcode >= MAX_EFFECTS) {
1053 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1054 return FX_NOT_APPLIED;
1057 ieDword GameTime = core->GetGame()->GameTime;
1059 fx->FirstApply=first_apply;
1060 if( first_apply) {
1061 if( (fx->PosX==0xffffffff) && (fx->PosY==0xffffffff)) {
1062 fx->PosX = target->Pos.x;
1063 fx->PosY = target->Pos.y;
1065 //the effect didn't pass the probability check
1066 if( !check_probability(fx) ) {
1067 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1068 return FX_NOT_APPLIED;
1071 //the effect didn't pass the target level check
1072 if( check_level(target, fx) ) {
1073 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1074 return FX_NOT_APPLIED;
1077 //the effect didn't pass the resistance check
1078 if( check_resistance(target, fx) ) {
1079 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1080 return FX_NOT_APPLIED;
1083 if( NeedPrepare(fx->TimingMode) ) {
1084 //save delay for later
1085 fx->SecondaryDelay = fx->Duration;
1086 if( fx->TimingMode == FX_DURATION_INSTANT_LIMITED) {
1087 fx->TimingMode = FX_DURATION_ABSOLUTE;
1089 PrepareDuration(fx);
1092 //check if the effect has triggered or expired
1093 switch (DelayType(fx->TimingMode&0xff) ) {
1094 case DELAYED:
1095 if( fx->Duration>GameTime) {
1096 return FX_NOT_APPLIED;
1098 //effect triggered
1099 //delayed duration (3)
1100 if( NeedPrepare(fx->TimingMode) ) {
1101 //prepare for delayed duration effects
1102 fx->Duration = fx->SecondaryDelay;
1103 PrepareDuration(fx);
1105 fx->TimingMode=TriggeredEffect(fx->TimingMode);
1106 break;
1107 case DURATION:
1108 if( fx->Duration<=GameTime) {
1109 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1110 //add a return here, if 0 duration effects shouldn't work
1112 break;
1113 //permanent effect (so there is no warning)
1114 case PERMANENT:
1115 break;
1116 //this shouldn't happen
1117 default:
1118 abort();
1121 EffectFunction fn = 0;
1122 if( fx->Opcode<MAX_EFFECTS) {
1123 fn = Opcodes[fx->Opcode].Function;
1125 int res = FX_ABORT;
1126 if( fn) {
1127 if( target && first_apply ) {
1128 if( !target->fxqueue.HasEffectWithParamPair(fx_protection_from_display_string_ref, fx->Parameter1, 0) ) {
1129 core->DisplayStringName( Opcodes[fx->Opcode].Strref, 0xf0f0f0,
1130 target, IE_STR_SOUND);
1134 res=fn( Owner, target, fx );
1136 //if there is no owner, we assume it is the target
1137 switch( res ) {
1138 case FX_APPLIED:
1139 //normal effect with duration
1140 break;
1141 case FX_NOT_APPLIED:
1142 //instant effect, pending removal
1143 //for example, a damage effect
1144 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1145 break;
1146 case FX_INSERT:
1147 //put this effect in the beginning of the queue
1148 //all known insert effects are 'permanent' too
1149 //that is the AC effect only
1150 //actually, permanent effects seem to be
1151 //inserted by the game engine too
1152 case FX_PERMANENT:
1153 //don't stick around if it was executed permanently
1154 //for example, a permanent strength modifier effect
1155 if( (fx->TimingMode == FX_DURATION_INSTANT_PERMANENT) ) {
1156 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1158 break;
1159 case FX_ABORT:
1160 break;
1161 default:
1162 abort();
1164 } else {
1165 //effect not found, it is going to be discarded
1166 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1168 return res;
1171 // looks for opcode with param2
1173 #define MATCH_OPCODE() if((*f)->Opcode!=opcode) { continue; }
1175 // useful for: remove equipped item
1176 #define MATCH_SLOTCODE() if((*f)->InventorySlot!=slotcode) { continue; }
1178 // useful for: remove projectile type
1179 #define MATCH_PROJECTILE() if((*f)->Projectile!=projectile) { continue; }
1181 static const bool fx_live[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,false};
1182 inline bool IsLive(ieByte timingmode)
1184 if( timingmode>=MAX_TIMING_MODE) return false;
1185 return fx_live[timingmode];
1188 #define MATCH_LIVE_FX() if(!IsLive((*f)->TimingMode)) { continue; }
1189 #define MATCH_PARAM1() if((*f)->Parameter1!=param1) { continue; }
1190 #define MATCH_PARAM2() if((*f)->Parameter2!=param2) { continue; }
1191 #define MATCH_RESOURCE() if( strnicmp( (*f)->Resource, resource, 8) ) { continue; }
1192 #define MATCH_SOURCE() if( strnicmp( (*f)->Source, Removed, 8) ) { continue; }
1193 #define MATCH_TIMING() if( (*f)->TimingMode!=timing) { continue; }
1195 //call this from an applied effect, after it returns, these effects
1196 //will be killed along with it
1197 void EffectQueue::RemoveAllEffects(ieDword opcode) const
1199 std::list< Effect* >::const_iterator f;
1200 for ( f = effects.begin(); f != effects.end(); f++ ) {
1201 MATCH_OPCODE();
1202 MATCH_LIVE_FX();
1204 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1208 //removes all equipping effects that match slotcode
1209 void EffectQueue::RemoveEquippingEffects(ieDwordSigned slotcode) const
1211 std::list< Effect* >::const_iterator f;
1212 for ( f = effects.begin(); f != effects.end(); f++ ) {
1213 if( !IsEquipped((*f)->TimingMode)) continue;
1214 MATCH_SLOTCODE();
1216 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1220 //removes all effects that match projectile
1221 void EffectQueue::RemoveAllEffectsWithProjectile(ieDword projectile) const
1223 std::list< Effect* >::const_iterator f;
1224 for ( f = effects.begin(); f != effects.end(); f++ ) {
1225 MATCH_PROJECTILE();
1227 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1231 //remove effects belonging to a given spell
1232 void EffectQueue::RemoveAllEffects(const ieResRef Removed) const
1234 std::list< Effect* >::const_iterator f;
1235 for ( f = effects.begin(); f != effects.end(); f++ ) {
1236 MATCH_LIVE_FX();
1237 MATCH_SOURCE();
1239 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1243 //remove effects belonging to a given spell, but only if they match timing method x
1244 void EffectQueue::RemoveAllEffects(const ieResRef Removed, ieByte timing) const
1246 std::list< Effect* >::const_iterator f;
1247 for ( f = effects.begin(); f != effects.end(); f++ ) {
1248 MATCH_TIMING();
1249 MATCH_SOURCE();
1251 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1255 //this will modify effect reference
1256 void EffectQueue::RemoveAllEffects(EffectRef &effect_reference) const
1258 ResolveEffectRef(effect_reference);
1259 if( effect_reference.opcode<0) {
1260 return;
1262 RemoveAllEffects(effect_reference.opcode);
1265 //Removes all effects with a matching resource field
1266 void EffectQueue::RemoveAllEffectsWithResource(ieDword opcode, const ieResRef resource) const
1268 std::list< Effect* >::const_iterator f;
1269 for ( f = effects.begin(); f != effects.end(); f++ ) {
1270 MATCH_OPCODE();
1271 MATCH_LIVE_FX();
1272 MATCH_RESOURCE();
1274 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1278 void EffectQueue::RemoveAllEffectsWithResource(EffectRef &effect_reference, const ieResRef resource) const
1280 ResolveEffectRef(effect_reference);
1281 RemoveAllEffectsWithResource(effect_reference.opcode, resource);
1284 //This method could be used to remove stat modifiers that would lower a stat
1285 //(works only if a higher stat means good for the target)
1286 void EffectQueue::RemoveAllDetrimentalEffects(ieDword opcode, ieDword current) const
1288 std::list< Effect* >::const_iterator f;
1289 for ( f = effects.begin(); f != effects.end(); f++ ) {
1290 MATCH_OPCODE();
1291 MATCH_LIVE_FX();
1292 switch((*f)->Parameter2) {
1293 case 0:case 3:
1294 if( ((signed) (*f)->Parameter1)>=0) continue;
1295 break;
1296 case 1:case 4:
1297 if( ((signed) (*f)->Parameter1)>=(signed) current) continue;
1298 break;
1299 case 2:case 5:
1300 if( ((signed) (*f)->Parameter1)>=100) continue;
1301 break;
1302 default:
1303 break;
1305 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1309 //Removes all effects with a matching param2
1310 //param2 is usually an effect's subclass (quality) while param1 is more like quantity.
1311 //So opcode+param2 usually pinpoints an effect better when not all effects of a given
1312 //opcode need to be removed (see removal of portrait icon)
1313 void EffectQueue::RemoveAllEffectsWithParam(ieDword opcode, ieDword param2) const
1315 std::list< Effect* >::const_iterator f;
1316 for ( f = effects.begin(); f != effects.end(); f++ ) {
1317 MATCH_OPCODE();
1318 MATCH_LIVE_FX();
1319 MATCH_PARAM2();
1321 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1325 //this function is called by FakeEffectExpiryCheck
1326 //probably also called by rest
1327 void EffectQueue::RemoveExpiredEffects(ieDword futuretime) const
1329 ieDword GameTime = core->GetGame()->GameTime;
1330 if( GameTime+futuretime*AI_UPDATE_TIME<GameTime) {
1331 GameTime=0xffffffff;
1332 } else {
1333 GameTime+=futuretime*AI_UPDATE_TIME;
1336 std::list< Effect* >::const_iterator f;
1337 for ( f = effects.begin(); f != effects.end(); f++ ) {
1338 //FIXME: how this method handles delayed effects???
1339 //it should remove them as well, i think
1340 if( DelayType( ((*f)->TimingMode) )!=PERMANENT ) {
1341 if( (*f)->Duration<=GameTime) {
1342 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1348 //this effect will expire all effects that are not truly permanent
1349 //which i call permanent after death (iesdp calls it permanent after bonuses)
1350 void EffectQueue::RemoveAllNonPermanentEffects() const
1352 std::list< Effect* >::const_iterator f;
1353 for ( f = effects.begin(); f != effects.end(); f++ ) {
1354 if( IsRemovable((*f)->TimingMode) ) {
1355 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1360 //this will modify effect reference
1362 void EffectQueue::RemoveAllDetrimentalEffects(EffectRef &effect_reference, ieDword current) const
1364 ResolveEffectRef(effect_reference);
1365 RemoveAllDetrimentalEffects(effect_reference.opcode, current);
1368 void EffectQueue::RemoveAllEffectsWithParam(EffectRef &effect_reference, ieDword param2) const
1370 ResolveEffectRef(effect_reference);
1371 RemoveAllEffectsWithParam(effect_reference.opcode, param2);
1374 //remove certain levels of effects, possibly matching school/secondary type
1375 //this method removes whole spells (tied together by their source)
1376 //FIXME: probably this isn't perfect
1377 void EffectQueue::RemoveLevelEffects(ieDword level, ieDword Flags, ieDword match) const
1379 ieResRef Removed;
1381 Removed[0]=0;
1382 std::list< Effect* >::const_iterator f;
1383 for ( f = effects.begin(); f != effects.end(); f++ ) {
1384 if( (*f)->Power>level) {
1385 continue;
1388 if( Removed[0]) {
1389 MATCH_SOURCE();
1391 if( Flags&RL_MATCHSCHOOL) {
1392 if( (*f)->PrimaryType!=match) {
1393 continue;
1396 if( Flags&RL_MATCHSECTYPE) {
1397 if( (*f)->SecondaryType!=match) {
1398 continue;
1401 //if dispellable was not set, or the effect is dispellable
1402 //then remove it
1403 if( Flags&RL_DISPELLABLE) {
1404 if( !((*f)->Resistance&FX_CAN_DISPEL)) {
1405 continue;
1408 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1409 if( Flags&RL_REMOVEFIRST) {
1410 memcpy(Removed,(*f)->Source, sizeof(Removed));
1415 Effect *EffectQueue::HasOpcode(ieDword opcode) const
1417 std::list< Effect* >::const_iterator f;
1418 for ( f = effects.begin(); f != effects.end(); f++ ) {
1419 MATCH_OPCODE();
1420 MATCH_LIVE_FX();
1422 return (*f);
1424 return NULL;
1427 Effect *EffectQueue::HasEffect(EffectRef &effect_reference) const
1429 ResolveEffectRef(effect_reference);
1430 if( effect_reference.opcode<0) {
1431 return NULL;
1433 return HasOpcode(effect_reference.opcode);
1436 Effect *EffectQueue::HasOpcodeWithParam(ieDword opcode, ieDword param2) const
1438 std::list< Effect* >::const_iterator f;
1439 for ( f = effects.begin(); f != effects.end(); f++ ) {
1440 MATCH_OPCODE();
1441 MATCH_LIVE_FX();
1442 MATCH_PARAM2();
1444 return (*f);
1446 return NULL;
1449 Effect *EffectQueue::HasEffectWithParam(EffectRef &effect_reference, ieDword param2) const
1451 ResolveEffectRef(effect_reference);
1452 if( effect_reference.opcode<0) {
1453 return NULL;
1455 return HasOpcodeWithParam(effect_reference.opcode, param2);
1458 //looks for opcode with pairs of parameters (useful for protection against creature, extra damage or extra thac0 against creature)
1459 //generally an IDS targeting
1461 Effect *EffectQueue::HasOpcodeWithParamPair(ieDword opcode, ieDword param1, ieDword param2) const
1463 std::list< Effect* >::const_iterator f;
1464 for ( f = effects.begin(); f != effects.end(); f++ ) {
1465 MATCH_OPCODE();
1466 MATCH_LIVE_FX();
1467 MATCH_PARAM2();
1468 //0 is always accepted as first parameter
1469 if( param1) {
1470 MATCH_PARAM1();
1473 return (*f);
1475 return NULL;
1478 Effect *EffectQueue::HasEffectWithParamPair(EffectRef &effect_reference, ieDword param1, ieDword param2) const
1480 ResolveEffectRef(effect_reference);
1481 if( effect_reference.opcode<0) {
1482 return NULL;
1484 return HasOpcodeWithParamPair(effect_reference.opcode, param1, param2);
1487 // sums all the values of the specific damage bonus effects of the passed "damage type"
1488 int EffectQueue::SpecificDamageBonus(ieDword opcode, ieDword param2) const
1490 int bonus = 0;
1491 std::list< Effect* >::const_iterator f;
1492 for ( f = effects.begin(); f != effects.end(); f++ ) {
1493 MATCH_OPCODE();
1494 MATCH_LIVE_FX();
1495 MATCH_PARAM2();
1496 bonus += (signed) (*f)->Parameter1;
1498 return bonus;
1501 static EffectRef fx_damage_bonus_modifier_ref={"DamageBonusModifier",NULL,-1};
1502 int EffectQueue::SpecificDamageBonus(ieDword damage_type) const
1504 ResolveEffectRef(fx_damage_bonus_modifier_ref);
1505 if(fx_damage_bonus_modifier_ref.opcode < 0) {
1506 return 0;
1508 return SpecificDamageBonus(fx_damage_bonus_modifier_ref.opcode, damage_type);
1511 //this could be used for stoneskins and mirror images as well
1512 void EffectQueue::DecreaseParam1OfEffect(ieDword opcode, ieDword amount) const
1514 std::list< Effect* >::const_iterator f;
1515 for ( f = effects.begin(); f != effects.end(); f++ ) {
1516 MATCH_OPCODE();
1517 MATCH_LIVE_FX();
1518 ieDword value = (*f)->Parameter1;
1519 if( value>amount) value-=amount;
1520 else value = 0;
1521 (*f)->Parameter1=value;
1525 void EffectQueue::DecreaseParam1OfEffect(EffectRef &effect_reference, ieDword amount) const
1527 ResolveEffectRef(effect_reference);
1528 if( effect_reference.opcode<0) {
1529 return;
1531 DecreaseParam1OfEffect(effect_reference.opcode, amount);
1535 //this function does IDS targeting for effects (extra damage/thac0 against creature)
1536 static const int ids_stats[7]={IE_EA, IE_GENERAL, IE_RACE, IE_CLASS, IE_SPECIFIC, IE_SEX, IE_ALIGNMENT};
1538 int EffectQueue::BonusAgainstCreature(ieDword opcode, Actor *actor) const
1540 int sum = 0;
1541 std::list< Effect* >::const_iterator f;
1542 for ( f = effects.begin(); f != effects.end(); f++ ) {
1543 MATCH_OPCODE();
1544 MATCH_LIVE_FX();
1545 ieDword ids = (*f)->Parameter2;
1546 if( ids<2 || ids>9) {
1547 ids=2;
1549 ieDword param1 = actor->GetStat(ids_stats[ids-2]);
1550 if( (*f)->Parameter1) {
1551 MATCH_PARAM1();
1553 int val = (int) (*f)->Parameter3;
1554 if( !val) val = 2;
1555 sum += val;
1557 return sum;
1560 int EffectQueue::BonusAgainstCreature(EffectRef &effect_reference, Actor *actor) const
1562 ResolveEffectRef(effect_reference);
1563 if( effect_reference.opcode<0) {
1564 return 0;
1566 return BonusAgainstCreature(effect_reference.opcode, actor);
1569 bool EffectQueue::WeaponImmunity(ieDword opcode, int enchantment, ieDword weapontype) const
1571 std::list< Effect* >::const_iterator f;
1572 for ( f = effects.begin(); f != effects.end(); f++ ) {
1573 MATCH_OPCODE();
1574 MATCH_LIVE_FX();
1576 int magic = (int) (*f)->Parameter1;
1577 ieDword mask = (*f)->Parameter3;
1578 ieDword value = (*f)->Parameter4;
1579 if( magic==0) {
1580 if( enchantment) continue;
1581 } else if( magic>0) {
1582 if( enchantment>magic) continue;
1585 if( (weapontype&mask) != value) {
1586 continue;
1588 return true;
1590 return false;
1593 static EffectRef fx_weapon_immunity_ref={"Protection:Weapons",NULL,-1};
1595 bool EffectQueue::WeaponImmunity(int enchantment, ieDword weapontype) const
1597 ResolveEffectRef(fx_weapon_immunity_ref);
1598 if( fx_weapon_immunity_ref.opcode<0) {
1599 return 0;
1601 return WeaponImmunity(fx_weapon_immunity_ref.opcode, enchantment, weapontype);
1604 static EffectRef fx_disable_spellcasting_ref={ "DisableCasting", NULL, -1 };
1605 int EffectQueue::DisabledSpellcasting(int types) const
1607 ResolveEffectRef(fx_disable_spellcasting_ref);
1608 if( fx_disable_spellcasting_ref.opcode < 0) {
1609 return 0;
1612 unsigned int spelltype_mask = 0;
1613 bool iwd2 = !!core->HasFeature(GF_ENHANCED_EFFECTS);
1614 ieDword opcode = fx_disable_spellcasting_ref.opcode;
1615 std::list< Effect* >::const_iterator f;
1616 for ( f = effects.begin(); f != effects.end(); f++ ) {
1617 MATCH_OPCODE();
1618 MATCH_LIVE_FX();
1620 if (iwd2) {
1621 switch((*f)->Parameter2) {
1622 case 0: // all
1623 spelltype_mask |= 7;
1624 break;
1625 case 1: // mage and cleric
1626 spelltype_mask |= 3;
1627 break;
1628 case 2: // mage
1629 spelltype_mask |= 2;
1630 break;
1631 case 3: // cleric
1632 spelltype_mask |= 1;
1633 break;
1634 case 4: // innate
1635 spelltype_mask |= 4;
1636 break;
1638 } else {
1639 switch((*f)->Parameter2) {
1640 case 0: // mage
1641 spelltype_mask |= 2;
1642 break;
1643 case 1: // cleric
1644 spelltype_mask |= 1;
1645 break;
1646 case 2: // innate
1647 spelltype_mask |= 4;
1648 break;
1652 return spelltype_mask & types;
1655 //useful for immunity vs spell, can't use item, etc.
1656 Effect *EffectQueue::HasOpcodeWithResource(ieDword opcode, const ieResRef resource) const
1658 std::list< Effect* >::const_iterator f;
1659 for ( f = effects.begin(); f != effects.end(); f++ ) {
1660 MATCH_OPCODE();
1661 MATCH_LIVE_FX();
1662 MATCH_RESOURCE();
1664 return (*f);
1666 return NULL;
1669 Effect *EffectQueue::HasEffectWithResource(EffectRef &effect_reference, const ieResRef resource) const
1671 ResolveEffectRef(effect_reference);
1672 return HasOpcodeWithResource(effect_reference.opcode, resource);
1675 //used in contingency/sequencer code (cannot have the same contingency twice)
1676 Effect *EffectQueue::HasOpcodeWithSource(ieDword opcode, const ieResRef Removed) const
1678 std::list< Effect* >::const_iterator f;
1679 for ( f = effects.begin(); f != effects.end(); f++ ) {
1680 MATCH_OPCODE();
1681 MATCH_LIVE_FX();
1682 MATCH_SOURCE();
1684 return (*f);
1686 return NULL;
1689 Effect *EffectQueue::HasEffectWithSource(EffectRef &effect_reference, const ieResRef resource) const
1691 ResolveEffectRef(effect_reference);
1692 return HasOpcodeWithSource(effect_reference.opcode, resource);
1695 bool EffectQueue::HasAnyDispellableEffect() const
1697 std::list< Effect* >::const_iterator f;
1698 for ( f = effects.begin(); f != effects.end(); f++ ) {
1699 if( (*f)->Resistance&FX_CAN_DISPEL) {
1700 return true;
1703 return false;
1706 void EffectQueue::dump() const
1708 printf( "EFFECT QUEUE:\n" );
1709 int i = 0;
1710 std::list< Effect* >::const_iterator f;
1711 for ( f = effects.begin(); f != effects.end(); f++ ) {
1712 Effect* fx = *f;
1713 if( fx) {
1714 char *Name = NULL;
1715 if( fx->Opcode < MAX_EFFECTS)
1716 Name = (char*) Opcodes[fx->Opcode].Name;
1718 printf( " %2d: 0x%02x: %s (%d, %d) S:%s\n", i++, fx->Opcode, Name, fx->Parameter1, fx->Parameter2, fx->Source );
1723 Effect *EffectQueue::GetEffect(ieDword idx) const
1725 if( effects.size()<=idx) {
1726 return NULL;
1728 return effects[idx];
1732 //returns true if the effect supports simplified duration
1733 bool EffectQueue::HasDuration(Effect *fx)
1735 switch(fx->TimingMode) {
1736 case FX_DURATION_INSTANT_LIMITED: //simple duration
1737 case FX_DURATION_DELAY_LIMITED: //delayed duration
1738 case FX_DURATION_DELAY_PERMANENT: //simple delayed
1739 return true;
1741 return false;
1744 static EffectRef fx_variable_ref={"Variable:StoreLocalVariable",NULL,-1};
1746 //returns true if the effect must be saved
1747 //variables are saved differently
1748 bool EffectQueue::Persistent(Effect* fx)
1750 //we save this as variable
1751 if( fx->Opcode==(ieDword) ResolveEffect(fx_variable_ref)) {
1752 return false;
1755 switch (fx->TimingMode) {
1756 //normal equipping fx of items
1757 case FX_DURATION_INSTANT_WHILE_EQUIPPED:
1758 //delayed effect not saved
1759 case FX_DURATION_DELAY_UNSAVED:
1760 //permanent effect not saved
1761 case FX_DURATION_PERMANENT_UNSAVED:
1762 //just expired effect
1763 case FX_DURATION_JUST_EXPIRED:
1764 return false;
1766 return true;
1769 //alter the color effect in case the item is equipped in the shield slot
1770 void EffectQueue::HackColorEffects(Actor *Owner, Effect *fx)
1772 if( fx->InventorySlot!=Owner->inventory.GetShieldSlot()) return;
1774 unsigned int gradienttype = fx->Parameter2 & 0xF0;
1775 if( gradienttype == 0x10) {
1776 gradienttype = 0x20; // off-hand
1777 fx->Parameter2 &= ~0xF0;
1778 fx->Parameter2 |= gradienttype;
1782 //iterate through saved effects
1783 const Effect *EffectQueue::GetNextSavedEffect(std::list< Effect* >::const_iterator &f) const
1785 while(f!=effects.end()) {
1786 Effect *effect = *f;
1787 f++;
1788 if( Persistent(effect)) {
1789 return effect;
1792 return NULL;
1795 Effect *EffectQueue::GetNextEffect(std::list< Effect* >::const_iterator &f) const
1797 if( f!=effects.end()) return *f++;
1798 return NULL;
1801 ieDword EffectQueue::CountEffects(ieDword opcode, ieDword param1, ieDword param2, const char *resource) const
1803 ieDword cnt = 0;
1805 std::list< Effect* >::const_iterator f;
1807 for ( f = effects.begin(); f != effects.end(); f++ ) {
1808 MATCH_OPCODE();
1809 if( param1!=0xffffffff)
1810 MATCH_PARAM1();
1811 if( param2!=0xffffffff)
1812 MATCH_PARAM2();
1813 if( resource) {
1814 MATCH_RESOURCE();
1816 cnt++;
1818 return cnt;
1821 void EffectQueue::ModifyEffectPoint(ieDword opcode, ieDword x, ieDword y) const
1823 std::list< Effect* >::const_iterator f;
1825 for ( f = effects.begin(); f != effects.end(); f++ ) {
1826 MATCH_OPCODE();
1827 (*f)->PosX=x;
1828 (*f)->PosY=y;
1829 (*f)->Parameter3=0;
1830 return;
1834 //count effects that get saved
1835 ieDword EffectQueue::GetSavedEffectsCount() const
1837 ieDword cnt = 0;
1839 std::list< Effect* >::const_iterator f;
1841 for ( f = effects.begin(); f != effects.end(); f++ ) {
1842 Effect* fx = *f;
1843 if( Persistent(fx))
1844 cnt++;
1846 return cnt;
1849 void EffectQueue::TransformToDelay(ieByte &TimingMode)
1851 if( TimingMode<MAX_TIMING_MODE) {;
1852 TimingMode = fx_to_delayed[TimingMode];
1853 } else {
1854 TimingMode = FX_DURATION_JUST_EXPIRED;
1858 int EffectQueue::ResolveEffect(EffectRef &effect_reference)
1860 ResolveEffectRef(effect_reference);
1861 return effect_reference.opcode;
1864 // this check goes for the whole effect block, not individual effects
1865 // But it takes the first effect of the block for the common fields
1867 //returns 1 if effect block applicable
1868 //returns 0 if effect block disabled
1869 //returns -1 if effect block bounced
1870 int EffectQueue::CheckImmunity(Actor *target) const
1872 //don't resist if target is non living
1873 if( !target) {
1874 return 1;
1877 if( effects.size() ) {
1878 Effect* fx = *effects.begin();
1880 //projectile immunity
1881 if( target->ImmuneToProjectile(fx->Projectile)) return 0;
1883 //don't resist item projectile payloads based on spell school, bounce, etc.
1884 if( fx->InventorySlot) {
1885 return 1;
1888 //check level resistances
1889 //check specific spell immunity
1890 //check school/sectype immunity
1891 return check_type(target, fx);
1893 return 0;
1896 void EffectQueue::AffectAllInRange(Map *map, const Point &pos, int idstype, int idsvalue,
1897 unsigned int range, Actor *except)
1899 int cnt = map->GetActorCount(true);
1900 while(cnt--) {
1901 Actor *actor = map->GetActor(cnt,true);
1902 if( except==actor) {
1903 continue;
1905 //distance
1906 if( Distance(pos, actor)>range) {
1907 continue;
1909 //ids targeting
1910 if( !match_ids(actor, idstype, idsvalue)) {
1911 continue;
1913 //line of sight
1914 if( !map->IsVisible(actor->Pos, pos)) {
1915 continue;
1917 AddAllEffects(actor, actor->Pos);