Sort include order.
[gemrb.git] / gemrb / core / EffectQueue.cpp
blob2f524595081db0838fe9f95198019a43d889cff8
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 "Actor.h"
24 #include "Effect.h"
25 #include "Game.h"
26 #include "Interface.h"
27 #include "Map.h"
28 #include "SymbolMgr.h"
30 #include <cstdio>
32 static struct {
33 const char* Name;
34 EffectFunction Function;
35 int Strref;
36 } Opcodes[MAX_EFFECTS];
38 static int initialized = 0;
40 static EffectRef *effectnames = NULL;
41 static int effectnames_count = 0;
43 bool EffectQueue::match_ids(Actor *target, int table, ieDword value)
45 if( value == 0) {
46 return true;
49 int a, stat;
51 switch (table) {
52 case 2: //EA
53 stat = IE_EA; break;
54 case 3: //GENERAL
55 stat = IE_GENERAL; break;
56 case 4: //RACE
57 stat = IE_RACE; break;
58 case 5: //CLASS
59 stat = IE_CLASS; break;
60 case 6: //SPECIFIC
61 stat = IE_SPECIFIC; break;
62 case 7: //GENDER
63 stat = IE_SEX; break;
64 case 8: //ALIGNMENT
65 stat = target->GetStat(IE_ALIGNMENT);
66 a = value&15;
67 if( a) {
68 if( a != ( stat & 15 )) {
69 return false;
72 a = value & 0xf0;
73 if( a) {
74 if( a != ( stat & 0xf0 )) {
75 return false;
78 return true;
79 default:
80 return false;
82 if( target->GetStat(stat)==value) {
83 return true;
85 return false;
88 static const bool fx_instant[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,true};
90 inline bool IsInstant(ieByte timingmode)
92 if( timingmode>=MAX_TIMING_MODE) return false;
93 return fx_instant[timingmode];
96 static const bool fx_equipped[MAX_TIMING_MODE]={false,false,true,false,false,true,false,false,true,false,false};
98 inline bool IsEquipped(ieByte timingmode)
100 if( timingmode>=MAX_TIMING_MODE) return false;
101 return fx_equipped[timingmode];
104 // 0 1 2 3 4 5 6 7 8 9 10
105 static const bool fx_relative[MAX_TIMING_MODE]={true,false,false,true,true,true,false,false,false,false,false};
107 inline bool NeedPrepare(ieWord timingmode)
109 if( timingmode>=MAX_TIMING_MODE) return false;
110 return fx_relative[timingmode];
113 #define INVALID -1
114 #define PERMANENT 0
115 #define DELAYED 1
116 #define DURATION 2
118 static const int fx_prepared[MAX_TIMING_MODE]={DURATION,PERMANENT,PERMANENT,DELAYED, //0-3
119 DELAYED,DELAYED,DELAYED,DELAYED,PERMANENT,PERMANENT,PERMANENT}; //4-7
121 inline int DelayType(ieByte timingmode)
123 if( timingmode>=MAX_TIMING_MODE) return INVALID;
124 return fx_prepared[timingmode];
127 //which effects are removable
128 static const bool fx_removable[MAX_TIMING_MODE]={true,true,false,true,true,false,true,true,false,false,true};
130 inline int IsRemovable(ieByte timingmode)
132 if( timingmode>=MAX_TIMING_MODE) return INVALID;
133 return fx_removable[timingmode];
136 //change the timing method after the effect triggered
137 static const ieByte fx_triggered[MAX_TIMING_MODE]={FX_DURATION_JUST_EXPIRED,FX_DURATION_INSTANT_PERMANENT,//0,1
138 FX_DURATION_INSTANT_WHILE_EQUIPPED,FX_DURATION_DELAY_LIMITED_PENDING,//2,3
139 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
140 FX_DURATION_INSTANT_LIMITED,FX_DURATION_JUST_EXPIRED,FX_DURATION_PERMANENT_UNSAVED,//6,8
141 FX_DURATION_INSTANT_PERMANENT_AFTER_BONUSES,FX_DURATION_JUST_EXPIRED};//9,10
143 //change the timing method for effect that should trigger after this effect expired
144 static const ieDword fx_to_delayed[]={FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,
145 FX_DURATION_PERMANENT_UNSAVED,FX_DURATION_DELAY_LIMITED_PENDING,
146 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
147 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,//6,8
148 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED};//9,10
150 inline ieByte TriggeredEffect(ieByte timingmode)
152 if( timingmode>=MAX_TIMING_MODE) return false;
153 return fx_triggered[timingmode];
156 int compare_effects(const void *a, const void *b)
158 return stricmp(((EffectRef *) a)->Name,((EffectRef *) b)->Name);
161 int find_effect(const void *a, const void *b)
163 return stricmp((const char *) a,((const EffectRef *) b)->Name);
166 static EffectRef* FindEffect(const char* effectname)
168 if( !effectname || !effectnames) {
169 return NULL;
171 void *tmp = bsearch(effectname, effectnames, effectnames_count, sizeof(EffectRef), find_effect);
172 if( !tmp) {
173 printMessage( "EffectQueue", "", YELLOW);
174 printf("Couldn't assign effect: %s\n", effectname );
176 return (EffectRef *) tmp;
179 static EffectRef fx_protection_from_display_string_ref={"Protection:String",NULL,-1};
181 //special effects without level check (but with damage dices precalculated)
182 static EffectRef diced_effects[] = {
183 //core effects
184 {"Damage",NULL,-1},
185 {"CurrentHPModifier",NULL,-1},
186 {"MaximumHPModifier",NULL,-1},
187 //iwd effects
188 {"BurningBlood",NULL,-1}, //iwd
189 {"ColdDamage",NULL,-1},
190 {"CrushingDamage",NULL,-1},
191 {"VampiricTouch",NULL,-1},
192 {"VitriolicSphere",NULL,-1},
193 //pst effects
194 {"TransferHP",NULL,-1},
195 {NULL,NULL,0} };
197 //special effects without level check (but with damage dices not precalculated)
198 static EffectRef diced_effects2[] = {
199 {"BurningBlood2",NULL,-1}, //how/iwd2
200 {"StaticCharge",NULL,-1}, //how/iwd2
201 {"LichTouch",NULL,-1}, //how
202 {NULL,NULL,0} };
204 inline static void ResolveEffectRef(EffectRef &effect_reference)
206 if( effect_reference.opcode==-1) {
207 EffectRef* ref = FindEffect(effect_reference.Name);
208 if( ref && ref->opcode>=0) {
209 effect_reference.opcode = ref->opcode;
210 return;
212 effect_reference.opcode = -2;
216 bool Init_EffectQueue()
218 int i;
220 if( initialized) {
221 return true;
223 memset( Opcodes, 0, sizeof( Opcodes ) );
224 for(i=0;i<MAX_EFFECTS;i++) {
225 Opcodes[i].Strref=-1;
228 initialized = 1;
230 AutoTable efftextTable("efftext");
232 int eT = core->LoadSymbol( "effects" );
233 if( eT < 0) {
234 printMessage( "EffectQueue","A critical scripting file is missing!\n",LIGHT_RED );
235 return false;
237 Holder<SymbolMgr> effectsTable = core->GetSymbol( eT );
238 if( !effectsTable) {
239 printMessage( "EffectQueue","A critical scripting file is damaged!\n",LIGHT_RED );
240 return false;
243 for (i = 0; i < MAX_EFFECTS; i++) {
244 const char* effectname = effectsTable->GetValue( i );
245 if( efftextTable) {
246 int row = efftextTable->GetRowCount();
247 while (row--) {
248 const char* ret = efftextTable->GetRowName( row );
249 long val;
250 if( valid_number( ret, val ) && (i == val) ) {
251 Opcodes[i].Strref = atoi( efftextTable->QueryField( row, 1 ) );
256 EffectRef* poi = FindEffect( effectname );
257 if( poi != NULL) {
258 Opcodes[i].Function = poi->Function;
259 Opcodes[i].Name = poi->Name;
260 //reverse linking opcode number
261 //using this unused field
262 if( (poi->opcode!=-1) && effectname[0]!='*') {
263 printf("Clashing Opcodes FN: %d vs. %d, %s\n", i, poi->opcode, effectname);
264 abort();
266 poi->opcode = i;
268 //printf("-------- FN: %d, %s\n", i, effectname);
270 core->DelSymbol( eT );
272 //additional initialisations
273 for (i=0;diced_effects[i].Name;i++) {
274 ResolveEffectRef(diced_effects[i]);
276 for (i=0;diced_effects2[i].Name;i++) {
277 ResolveEffectRef(diced_effects2[i]);
280 return true;
283 void EffectQueue_ReleaseMemory()
285 if( effectnames) {
286 free (effectnames);
288 effectnames_count = 0;
289 effectnames = NULL;
292 void EffectQueue_RegisterOpcodes(int count, const EffectRef* opcodes)
294 if( ! effectnames) {
295 effectnames = (EffectRef*) malloc( (count+1) * sizeof( EffectRef ) );
296 } else {
297 effectnames = (EffectRef*) realloc( effectnames, (effectnames_count + count + 1) * sizeof( EffectRef ) );
300 memcpy( effectnames + effectnames_count, opcodes, count * sizeof( EffectRef ));
301 effectnames_count += count;
302 effectnames[effectnames_count].Name = NULL;
303 //if we merge two effect lists, then we need to sort their effect tables
304 //actually, we might always want to sort this list, so there is no
305 //need to do it manually (sorted table is needed if we use bsearch)
306 qsort(effectnames, effectnames_count, sizeof(EffectRef), compare_effects);
309 EffectQueue::EffectQueue()
311 Owner = NULL;
314 EffectQueue::~EffectQueue()
316 std::list< Effect* >::iterator f;
318 for ( f = effects.begin(); f != effects.end(); f++ ) {
319 delete (*f);
323 Effect *EffectQueue::CreateEffect(ieDword opcode, ieDword param1, ieDword param2, ieWord timing)
325 if( opcode==0xffffffff) {
326 return NULL;
328 Effect *fx = new Effect();
329 if( !fx) {
330 return NULL;
332 memset(fx,0,sizeof(Effect));
333 fx->Target = FX_TARGET_SELF;
334 fx->Opcode = opcode;
335 //probability2 is the low number (by effectqueue 331)
336 fx->Probability1 = 100;
337 fx->Parameter1 = param1;
338 fx->Parameter2 = param2;
339 fx->TimingMode = timing;
340 fx->PosX = 0xffffffff;
341 fx->PosY = 0xffffffff;
342 return fx;
345 //return the count of effects with matching parameters
346 //useful for effects where there is no separate stat to see
347 ieDword EffectQueue::CountEffects(EffectRef &effect_reference, ieDword param1, ieDword param2, const char *resource) const
349 ResolveEffectRef(effect_reference);
350 if( effect_reference.opcode<0) {
351 return 0;
353 return CountEffects(effect_reference.opcode, param1, param2, resource);
356 //Change the location of an existing effect
357 //this is used when some external code needs to adjust the effect's location
358 //used when the gui sets the effect's final target
359 void EffectQueue::ModifyEffectPoint(EffectRef &effect_reference, ieDword x, ieDword y) const
361 ResolveEffectRef(effect_reference);
362 if( effect_reference.opcode<0) {
363 return;
365 ModifyEffectPoint(effect_reference.opcode, x, y);
368 Effect *EffectQueue::CreateEffect(EffectRef &effect_reference, ieDword param1, ieDword param2, ieWord timing)
370 ResolveEffectRef(effect_reference);
371 if( effect_reference.opcode<0) {
372 return NULL;
374 return CreateEffect(effect_reference.opcode, param1, param2, timing);
377 //copies the whole effectqueue (area projectiles use it)
378 EffectQueue *EffectQueue::CopySelf() const
380 EffectQueue *effects;
382 effects = new EffectQueue();
383 std::list< Effect* >::const_iterator fxit = GetFirstEffect();
384 Effect *fx;
386 while( (fx = GetNextEffect(fxit))) {
387 effects->AddEffect(fx, false);
389 effects->SetOwner(GetOwner());
390 return effects;
393 //create a new effect with most of the characteristics of the old effect
394 //only opcode and parameters are changed
395 //This is used mostly inside effects, when an effect needs to spawn
396 //other effects with the same coordinates, source, duration, etc.
397 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, ieDword opcode, ieDword param1, ieDword param2)
399 if( opcode==0xffffffff) {
400 return NULL;
402 Effect *fx = new Effect();
403 if( !fx) {
404 return NULL;
406 memcpy(fx,oldfx,sizeof(Effect) );
407 fx->Opcode=opcode;
408 fx->Parameter1=param1;
409 fx->Parameter2=param2;
410 return fx;
413 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, EffectRef &effect_reference, ieDword param1, ieDword param2)
415 ResolveEffectRef(effect_reference);
416 if( effect_reference.opcode<0) {
417 return NULL;
419 return CreateEffectCopy(oldfx, effect_reference.opcode, param1, param2);
422 static EffectRef fx_unsummon_creature_ref={"UnsummonCreature",NULL,-1};
424 Effect *EffectQueue::CreateUnsummonEffect(Effect *fx)
426 Effect *newfx = NULL;
427 if( (fx->TimingMode&0xff) == FX_DURATION_INSTANT_LIMITED) {
428 newfx = CreateEffectCopy(fx, fx_unsummon_creature_ref, 0, 0);
429 newfx->TimingMode = FX_DURATION_DELAY_PERMANENT;
430 if( newfx->Resource3[0]) {
431 strnuprcpy(newfx->Resource,newfx->Resource3, sizeof(ieResRef)-1 );
432 } else {
433 strnuprcpy(newfx->Resource,"SPGFLSH1", sizeof(ieResRef)-1 );
435 if( fx->TimingMode == FX_DURATION_ABSOLUTE) {
436 //unprepare duration
437 newfx->Duration = (newfx->Duration-core->GetGame()->GameTime)/AI_UPDATE_TIME;
441 return newfx;
444 void EffectQueue::AddEffect(Effect* fx, bool insert)
446 Effect* new_fx = new Effect;
447 memcpy( new_fx, fx, sizeof( Effect ) );
448 if( insert) {
449 effects.insert( effects.begin(), new_fx );
450 } else {
451 effects.push_back( new_fx );
455 //This method can remove an effect described by a pointer to it, or
456 //an exact matching effect
457 bool EffectQueue::RemoveEffect(Effect* fx)
459 int invariant_size = offsetof( Effect, random_value );
461 for (std::list< Effect* >::iterator f = effects.begin(); f != effects.end(); f++ ) {
462 Effect* fx2 = *f;
464 if( (fx==fx2) || !memcmp( fx, fx2, invariant_size)) {
465 delete fx2;
466 effects.erase( f );
467 return true;
470 return false;
473 //this is where we reapply all effects when loading a saved game
474 //The effects are already in the fxqueue of the target
475 void EffectQueue::ApplyAllEffects(Actor* target) const
477 std::list< Effect* >::const_iterator f;
478 for ( f = effects.begin(); f != effects.end(); f++ ) {
479 ApplyEffect( target, *f, 0 );
483 void EffectQueue::Cleanup()
485 std::list< Effect* >::iterator f;
487 for ( f = effects.begin(); f != effects.end(); ) {
488 if( (*f)->TimingMode == FX_DURATION_JUST_EXPIRED) {
489 delete *f;
490 effects.erase(f++);
491 } else {
492 f++;
497 //Handle the target flag when the effect is applied first
498 int EffectQueue::AddEffect(Effect* fx, Scriptable* self, Actor* pretarget, const Point &dest) const
500 int i;
501 Game *game;
502 Map *map;
503 int flg;
504 ieDword spec = 0;
505 Actor *st = (self && (self->Type==ST_ACTOR)) ?(Actor *) self:NULL;
507 switch (fx->Target) {
508 case FX_TARGET_ORIGINAL:
509 fx->SetPosition(self->Pos);
511 flg = ApplyEffect( st, fx, 1 );
512 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
513 if( st) {
514 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
517 break;
518 case FX_TARGET_SELF:
519 fx->SetPosition(dest);
521 flg = ApplyEffect( st, fx, 1 );
522 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
523 if( st) {
524 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
527 break;
529 case FX_TARGET_ALL_BUT_SELF:
530 map=self->GetCurrentArea();
531 i= map->GetActorCount(true);
532 while(i--) {
533 Actor* actor = map->GetActor( i, true );
534 //don't pick ourselves
535 if( st==actor) {
536 continue;
538 fx->SetPosition(actor->Pos);
540 flg = ApplyEffect( actor, fx, 1 );
541 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
542 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
545 flg = FX_APPLIED;
546 break;
548 case FX_TARGET_OWN_SIDE:
549 if( !st || st->InParty) {
550 goto all_party;
552 map = self->GetCurrentArea();
553 spec = st->GetStat(IE_SPECIFIC);
555 //GetActorCount(false) returns all nonparty critters
556 i = map->GetActorCount(false);
557 while(i--) {
558 Actor* actor = map->GetActor( i, false );
559 if( actor->GetStat(IE_SPECIFIC)!=spec) {
560 continue;
562 fx->SetPosition(actor->Pos);
564 flg = ApplyEffect( actor, fx, 1 );
565 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
566 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
569 flg = FX_APPLIED;
570 break;
571 case FX_TARGET_OTHER_SIDE:
572 if( !pretarget || pretarget->InParty) {
573 goto all_party;
575 map = self->GetCurrentArea();
576 spec = pretarget->GetStat(IE_SPECIFIC);
578 //GetActorCount(false) returns all nonparty critters
579 i = map->GetActorCount(false);
580 while(i--) {
581 Actor* actor = map->GetActor( i, false );
582 if( actor->GetStat(IE_SPECIFIC)!=spec) {
583 continue;
585 fx->SetPosition(actor->Pos);
587 flg = ApplyEffect( actor, fx, 1 );
588 //GetActorCount can now return all nonparty critters
589 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
590 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
593 flg = FX_APPLIED;
594 break;
595 case FX_TARGET_PRESET:
596 fx->SetPosition(pretarget->Pos);
598 flg = ApplyEffect( pretarget, fx, 1 );
599 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
600 if( pretarget) {
601 pretarget->fxqueue.AddEffect( fx, flg==FX_INSERT );
604 break;
606 case FX_TARGET_PARTY:
607 all_party:
608 game = core->GetGame();
609 i = game->GetPartySize(true);
610 while(i--) {
611 Actor* actor = game->GetPC( i, true );
612 fx->SetPosition(actor->Pos);
614 flg = ApplyEffect( actor, fx, 1 );
615 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
616 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
619 flg = FX_APPLIED;
620 break;
622 case FX_TARGET_ALL:
623 map = self->GetCurrentArea();
624 i = map->GetActorCount(true);
625 while(i--) {
626 Actor* actor = map->GetActor( i, true );
627 fx->SetPosition(actor->Pos);
629 flg = ApplyEffect( actor, fx, 1 );
630 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
631 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
634 flg = FX_APPLIED;
635 break;
637 case FX_TARGET_ALL_BUT_PARTY:
638 map = self->GetCurrentArea();
639 i = map->GetActorCount(false);
640 while(i--) {
641 Actor* actor = map->GetActor( i, false );
642 fx->SetPosition(actor->Pos);
644 flg = ApplyEffect( actor, fx, 1 );
645 //GetActorCount can now return all nonparty critters
646 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
647 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
650 flg = FX_APPLIED;
651 break;
653 case FX_TARGET_UNKNOWN:
654 default:
655 printf( "Unknown FX target type: %d\n", fx->Target);
656 flg = FX_ABORT;
657 break;
660 return flg;
663 //this is where effects from spells first get in touch with the target
664 //the effects are currently NOT in the target's fxqueue, those that stick
665 //will get copied (hence the fxqueue.AddEffect call)
666 //if this returns FX_NOT_APPLIED, then the whole stack was resisted
667 //or expired
668 int EffectQueue::AddAllEffects(Actor* target, const Point &destination) const
670 int res = FX_NOT_APPLIED;
671 // pre-roll dice for fx needing them and stow them in the effect
672 ieDword random_value = core->Roll( 1, 100, 0 );
674 if( target) {
675 target->RollSaves();
677 std::list< Effect* >::const_iterator f;
678 for ( f = effects.begin(); f != effects.end(); f++ ) {
679 //handle resistances and saving throws here
680 (*f)->random_value = random_value;
681 //if applyeffect returns true, we stop adding the future effects
682 //this is to simulate iwd2's on the fly spell resistance
684 int tmp = AddEffect(*f, Owner, target, destination);
685 //lets try without Owner, any crash?
686 //If yes, then try to fix the individual effect
687 //If you use target for Owner here, the wand in chateau irenicus will work
688 //the same way as Imoen's monster summoning, which is a BAD THING (TM)
689 //int tmp = AddEffect(*f, Owner?Owner:target, target, destination);
690 if( tmp == FX_ABORT) {
691 res = FX_NOT_APPLIED;
692 break;
694 if( tmp != FX_NOT_APPLIED) {
695 res = FX_APPLIED;
698 return res;
701 //check if an effect has no level based resistance, but instead the dice sizes/count
702 //adjusts Parameter1 (like a damage causing effect)
703 inline static bool IsDicedEffect(int opcode)
705 int i;
707 for(i=0;diced_effects[i].Name;i++) {
708 if( diced_effects[i].opcode==opcode) {
709 return true;
712 return false;
715 //there is no level based resistance, but Parameter1 cannot be precalculated
716 //these effects use the Dice fields in a special way
717 inline static bool IsDicedEffect2(int opcode)
719 int i;
721 for(i=0;diced_effects2[i].Name;i++) {
722 if( diced_effects2[i].opcode==opcode) {
723 return true;
726 return false;
729 //resisted effect based on level
730 inline bool check_level(Actor *target, Effect *fx)
732 //skip non level based effects
733 if( IsDicedEffect((int) fx->Opcode)) {
734 fx->Parameter1 = DICE_ROLL((signed)fx->Parameter1);
735 //this is a hack for PST style diced effects
736 if( core->HasFeature(GF_SAVE_FOR_HALF) ) {
737 if( memcmp(fx->Resource,"NEG",4) ) {
738 fx->IsSaveForHalfDamage=1;
740 } else {
741 if( (fx->Parameter2&3)==3) {
742 fx->IsSaveForHalfDamage=1;
745 return false;
747 if( IsDicedEffect2((int) fx->Opcode)) {
748 return false;
751 if( !target) {
752 return false;
755 ieDword level = (ieDword) target->GetXPLevel( true );
756 //return true if resisted
757 //level resistance is checked when DiceSides or DiceThrown
758 //are greater than 0 (sometimes they used -1 for our amusement)
759 //if level>than maximum affected or level<than minimum affected, then the
760 //effect is resisted
761 if( (fx->DiceSides > 0 || fx->DiceThrown > 0) && (level > fx->DiceSides || level < fx->DiceThrown)) {
762 return true;
764 return false;
767 //roll for the effect probability, there is a high and a low treshold, the d100
768 //roll should hit in the middle
769 inline bool check_probability(Effect* fx)
771 //watch for this, probability1 is the high number
772 //probability2 is the low number
773 //random value is 1-100
774 if( fx->random_value<=fx->Probability2 || fx->random_value>fx->Probability1) {
775 return false;
777 return true;
780 //immunity effects
781 static EffectRef fx_level_immunity_ref={"Protection:Spelllevel",NULL,-1};
782 static EffectRef fx_opcode_immunity_ref={"Protection:Opcode",NULL,-1}; //bg2
783 static EffectRef fx_opcode_immunity2_ref={"Protection:Opcode2",NULL,-1};//iwd
784 static EffectRef fx_spell_immunity_ref={"Protection:Spell",NULL,-1}; //bg2
785 static EffectRef fx_spell_immunity2_ref={"Protection:Spell2",NULL,-1};//iwd
786 static EffectRef fx_school_immunity_ref={"Protection:School",NULL,-1};
787 static EffectRef fx_secondary_type_immunity_ref={"Protection:SecondaryType",NULL,-1};
789 //decrementing immunity effects
790 static EffectRef fx_level_immunity_dec_ref={"Protection:SpellLevelDec",NULL,-1};
791 static EffectRef fx_spell_immunity_dec_ref={"Protection:SpellDec",NULL,-1};
792 static EffectRef fx_school_immunity_dec_ref={"Protection:SchoolDec",NULL,-1};
793 static EffectRef fx_secondary_type_immunity_dec_ref={"Protection:SecondaryTypeDec",NULL,-1};
795 //bounce effects
796 static EffectRef fx_level_bounce_ref={"Bounce:SpellLevel",NULL,-1};
797 //static EffectRef fx_opcode_bounce_ref={"Bounce:Opcode",NULL,-1};
798 static EffectRef fx_spell_bounce_ref={"Bounce:Spell",NULL,-1};
799 static EffectRef fx_school_bounce_ref={"Bounce:School",NULL,-1};
800 static EffectRef fx_secondary_type_bounce_ref={"Bounce:SecondaryType",NULL,-1};
802 //decrementing bounce effects
803 static EffectRef fx_level_bounce_dec_ref={"Bounce:SpellLevelDec",NULL,-1};
804 static EffectRef fx_spell_bounce_dec_ref={"Bounce:SpellDec",NULL,-1};
805 static EffectRef fx_school_bounce_dec_ref={"Bounce:SchoolDec",NULL,-1};
806 static EffectRef fx_secondary_type_bounce_dec_ref={"Bounce:SecondaryTypeDec",NULL,-1};
808 //spelltrap (multiple decrementing immunity)
809 static EffectRef fx_spelltrap={"SpellTrap", NULL,-1};
811 //this is for whole spell immunity/bounce
812 inline static void DecreaseEffect(Effect *efx)
814 efx->Parameter1--;
815 if( (int) efx->Parameter1<1) {
816 //don't remove effects directly!!!
817 efx->TimingMode = FX_DURATION_JUST_EXPIRED;
821 //lower decreasing immunities/bounces
822 static int check_type(Actor* actor, Effect* fx)
824 //the protective effect (if any)
825 Effect *efx;
827 ieDword bounce = actor->GetStat(IE_BOUNCE);
829 //immunity checks
830 /*opcode immunity is in the per opcode checks
831 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
832 return 0;
834 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
835 return 0;
838 //spell level immunity
839 if(fx->Power && actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_ref, fx->Power, 0) ) {
840 return 0;
843 //source immunity (spell name)
844 //if source is unspecified, don't resist it
845 if( fx->Source[0]) {
846 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity_ref, fx->Source) ) {
847 return 0;
849 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity2_ref, fx->Source) ) {
850 return 0;
854 //primary type immunity (school)
855 if( fx->PrimaryType) {
856 if( actor->fxqueue.HasEffectWithParam(fx_school_immunity_ref, fx->PrimaryType)) {
857 return 0;
861 //secondary type immunity (usage)
862 if( fx->SecondaryType) {
863 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_ref, fx->SecondaryType) ) {
864 return 0;
868 //decrementing immunity checks
869 //decrementing level immunity
870 efx = actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_dec_ref, fx->Power, 0);
871 if( efx ) {
872 DecreaseEffect(efx);
873 return 0;
876 //decrementing spell immunity
877 if( fx->Source[0]) {
878 efx = actor->fxqueue.HasEffectWithResource(fx_spell_immunity_dec_ref, fx->Source);
879 if( efx) {
880 DecreaseEffect(efx);
881 return 0;
884 //decrementing primary type immunity (school)
885 if( fx->PrimaryType) {
886 efx = actor->fxqueue.HasEffectWithParam(fx_school_immunity_dec_ref, fx->PrimaryType);
887 if( efx) {
888 DecreaseEffect(efx);
889 return 0;
893 //decrementing secondary type immunity (usage)
894 if( fx->SecondaryType) {
895 efx = actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_dec_ref, fx->SecondaryType);
896 if( efx) {
897 DecreaseEffect(efx);
898 return 0;
902 //spelltrap (absorb)
903 //FIXME:
904 //if the spelltrap effect already absorbed enough levels
905 //but still didn't get removed, it will absorb levels it shouldn't
906 //it will also absorb multiple spells in a single round
907 efx=actor->fxqueue.HasEffectWithParamPair(fx_spelltrap, 0, fx->Power);
908 if( efx) {
909 //storing the absorbed spell level
910 efx->Parameter3+=fx->Power;
911 //instead of a single effect, they had to create an effect for each level
912 //HOW DAMN LAME
913 //if decrease needs the spell level, use fx->Power here
914 actor->fxqueue.DecreaseParam1OfEffect(fx_spelltrap, 1);
915 //efx->Parameter1--;
916 return 0;
919 //bounce checks
920 if( (bounce&BNC_LEVEL) && actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_ref, fx->Power, 0) ) {
921 return 0;
924 if( fx->Source[0] && (bounce&BNC_RESOURCE) && actor->fxqueue.HasEffectWithResource(fx_spell_bounce_ref, fx->Source) ) {
925 return -1;
928 if( fx->PrimaryType && (bounce&BNC_SCHOOL) ) {
929 if( actor->fxqueue.HasEffectWithParam(fx_school_bounce_ref, fx->PrimaryType)) {
930 return -1;
934 if( fx->SecondaryType && (bounce&BNC_SECTYPE) ) {
935 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_ref, fx->SecondaryType)) {
936 return -1;
939 //decrementing bounce checks
941 //level decrementing bounce check
942 if( (bounce&BNC_LEVEL_DEC)) {
943 efx=actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_dec_ref, fx->Power, 0);
944 if( efx) {
945 DecreaseEffect(efx);
946 return -1;
950 if( fx->Source[0] && (bounce&BNC_RESOURCE_DEC)) {
951 efx=actor->fxqueue.HasEffectWithResource(fx_spell_bounce_dec_ref, fx->Resource);
952 if( efx) {
953 DecreaseEffect(efx);
954 return -1;
958 if( fx->PrimaryType && (bounce&BNC_SCHOOL_DEC) ) {
959 efx=actor->fxqueue.HasEffectWithParam(fx_school_bounce_dec_ref, fx->PrimaryType);
960 if( efx) {
961 DecreaseEffect(efx);
962 return -1;
966 if( fx->SecondaryType && (bounce&BNC_SECTYPE_DEC) ) {
967 efx=actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_dec_ref, fx->SecondaryType);
968 if( efx) {
969 DecreaseEffect(efx);
970 return -1;
974 return 1;
977 //check resistances, saving throws
978 static bool check_resistance(Actor* actor, Effect* fx)
980 if( !actor) {
981 return false;
984 //opcode immunity
985 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
986 printf ("immune to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
987 return true;
989 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
990 printf ("immune2 to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
991 return true;
994 /* opcode bouncing isn't implemented?
995 //opcode bouncing
996 if( actor->fxqueue.HasEffectWithParam(fx_opcode_bounce_ref, fx->Opcode) ) {
997 return false;
1001 //not resistable (no saves either?)
1002 if( fx->Resistance != FX_CAN_RESIST_CAN_DISPEL) {
1003 return false;
1006 //don't resist self
1007 if (fx->Target==FX_TARGET_SELF) {
1008 if (core->HasFeature(GF_SELECTIVE_MAGIC_RES) ) {
1009 return false;
1013 //magic immunity
1014 ieDword val = actor->GetStat(IE_RESISTMAGIC);
1015 if( fx->random_value < val) {
1016 printf ("effect resisted: %s\n", (char*) Opcodes[fx->Opcode].Name);
1017 return true;
1020 //saving throws
1021 bool saved = false;
1022 for (int i=0;i<5;i++) {
1023 if( fx->SavingThrowType&(1<<i)) {
1024 saved = actor->GetSavingThrow(i, fx->SavingThrowBonus);
1025 if( saved) {
1026 break;
1030 if( saved) {
1031 if( fx->IsSaveForHalfDamage) {
1032 fx->Parameter1/=2;
1033 } else {
1034 printf ("%s saved against effect: %s\n", actor->GetName(1), (char*) Opcodes[fx->Opcode].Name);
1035 return true;
1038 return false;
1041 // this function is called two different ways
1042 // when FirstApply is set, then the effect isn't stuck on the target
1043 // this happens when a new effect comes in contact with the target.
1044 // if the effect returns FX_DURATION_JUST_EXPIRED then it won't stick
1045 // when first_apply is unset, the effect is already on the target
1046 // this happens on load time too!
1047 // returns FX_NOT_APPLIED if the process shouldn't be calling applyeffect anymore
1048 // returns FX_ABORT if the whole spell this effect is in should be aborted
1049 // it will disable all future effects of same source (only on first apply)
1051 int EffectQueue::ApplyEffect(Actor* target, Effect* fx, ieDword first_apply) const
1053 //printf( "FX 0x%02x: %s(%d, %d)\n", fx->Opcode, effectnames[fx->Opcode].Name, fx->Parameter1, fx->Parameter2 );
1054 if( fx->Opcode >= MAX_EFFECTS) {
1055 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1056 return FX_NOT_APPLIED;
1059 ieDword GameTime = core->GetGame()->GameTime;
1061 fx->FirstApply=first_apply;
1062 if( first_apply) {
1063 if( (fx->PosX==0xffffffff) && (fx->PosY==0xffffffff)) {
1064 fx->PosX = target->Pos.x;
1065 fx->PosY = target->Pos.y;
1067 //the effect didn't pass the probability check
1068 if( !check_probability(fx) ) {
1069 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1070 return FX_NOT_APPLIED;
1073 //the effect didn't pass the target level check
1074 if( check_level(target, fx) ) {
1075 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1076 return FX_NOT_APPLIED;
1079 //the effect didn't pass the resistance check
1080 if( check_resistance(target, fx) ) {
1081 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1082 return FX_NOT_APPLIED;
1085 if( NeedPrepare(fx->TimingMode) ) {
1086 //save delay for later
1087 fx->SecondaryDelay = fx->Duration;
1088 if( fx->TimingMode == FX_DURATION_INSTANT_LIMITED) {
1089 fx->TimingMode = FX_DURATION_ABSOLUTE;
1091 PrepareDuration(fx);
1094 //check if the effect has triggered or expired
1095 switch (DelayType(fx->TimingMode&0xff) ) {
1096 case DELAYED:
1097 if( fx->Duration>GameTime) {
1098 return FX_NOT_APPLIED;
1100 //effect triggered
1101 //delayed duration (3)
1102 if( NeedPrepare(fx->TimingMode) ) {
1103 //prepare for delayed duration effects
1104 fx->Duration = fx->SecondaryDelay;
1105 PrepareDuration(fx);
1107 fx->TimingMode=TriggeredEffect(fx->TimingMode);
1108 break;
1109 case DURATION:
1110 if( fx->Duration<=GameTime) {
1111 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1112 //add a return here, if 0 duration effects shouldn't work
1114 break;
1115 //permanent effect (so there is no warning)
1116 case PERMANENT:
1117 break;
1118 //this shouldn't happen
1119 default:
1120 abort();
1123 EffectFunction fn = 0;
1124 if( fx->Opcode<MAX_EFFECTS) {
1125 fn = Opcodes[fx->Opcode].Function;
1127 int res = FX_ABORT;
1128 if( fn) {
1129 if( target && first_apply ) {
1130 if( !target->fxqueue.HasEffectWithParamPair(fx_protection_from_display_string_ref, fx->Parameter1, 0) ) {
1131 core->DisplayStringName( Opcodes[fx->Opcode].Strref, 0xf0f0f0,
1132 target, IE_STR_SOUND);
1136 res=fn( Owner, target, fx );
1138 //if there is no owner, we assume it is the target
1139 switch( res ) {
1140 case FX_APPLIED:
1141 //normal effect with duration
1142 break;
1143 case FX_NOT_APPLIED:
1144 //instant effect, pending removal
1145 //for example, a damage effect
1146 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1147 break;
1148 case FX_INSERT:
1149 //put this effect in the beginning of the queue
1150 //all known insert effects are 'permanent' too
1151 //that is the AC effect only
1152 //actually, permanent effects seem to be
1153 //inserted by the game engine too
1154 case FX_PERMANENT:
1155 //don't stick around if it was executed permanently
1156 //for example, a permanent strength modifier effect
1157 if( (fx->TimingMode == FX_DURATION_INSTANT_PERMANENT) ) {
1158 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1160 break;
1161 case FX_ABORT:
1162 break;
1163 default:
1164 abort();
1166 } else {
1167 //effect not found, it is going to be discarded
1168 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1170 return res;
1173 // looks for opcode with param2
1175 #define MATCH_OPCODE() if((*f)->Opcode!=opcode) { continue; }
1177 // useful for: remove equipped item
1178 #define MATCH_SLOTCODE() if((*f)->InventorySlot!=slotcode) { continue; }
1180 // useful for: remove projectile type
1181 #define MATCH_PROJECTILE() if((*f)->Projectile!=projectile) { continue; }
1183 static const bool fx_live[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,false};
1184 inline bool IsLive(ieByte timingmode)
1186 if( timingmode>=MAX_TIMING_MODE) return false;
1187 return fx_live[timingmode];
1190 #define MATCH_LIVE_FX() if(!IsLive((*f)->TimingMode)) { continue; }
1191 #define MATCH_PARAM1() if((*f)->Parameter1!=param1) { continue; }
1192 #define MATCH_PARAM2() if((*f)->Parameter2!=param2) { continue; }
1193 #define MATCH_RESOURCE() if( strnicmp( (*f)->Resource, resource, 8) ) { continue; }
1194 #define MATCH_SOURCE() if( strnicmp( (*f)->Source, Removed, 8) ) { continue; }
1195 #define MATCH_TIMING() if( (*f)->TimingMode!=timing) { continue; }
1197 //call this from an applied effect, after it returns, these effects
1198 //will be killed along with it
1199 void EffectQueue::RemoveAllEffects(ieDword opcode) const
1201 std::list< Effect* >::const_iterator f;
1202 for ( f = effects.begin(); f != effects.end(); f++ ) {
1203 MATCH_OPCODE();
1204 MATCH_LIVE_FX();
1206 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1210 //removes all equipping effects that match slotcode
1211 void EffectQueue::RemoveEquippingEffects(ieDwordSigned slotcode) const
1213 std::list< Effect* >::const_iterator f;
1214 for ( f = effects.begin(); f != effects.end(); f++ ) {
1215 if( !IsEquipped((*f)->TimingMode)) continue;
1216 MATCH_SLOTCODE();
1218 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1222 //removes all effects that match projectile
1223 void EffectQueue::RemoveAllEffectsWithProjectile(ieDword projectile) const
1225 std::list< Effect* >::const_iterator f;
1226 for ( f = effects.begin(); f != effects.end(); f++ ) {
1227 MATCH_PROJECTILE();
1229 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1233 //remove effects belonging to a given spell
1234 void EffectQueue::RemoveAllEffects(const ieResRef Removed) const
1236 std::list< Effect* >::const_iterator f;
1237 for ( f = effects.begin(); f != effects.end(); f++ ) {
1238 MATCH_LIVE_FX();
1239 MATCH_SOURCE();
1241 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1245 //remove effects belonging to a given spell, but only if they match timing method x
1246 void EffectQueue::RemoveAllEffects(const ieResRef Removed, ieByte timing) const
1248 std::list< Effect* >::const_iterator f;
1249 for ( f = effects.begin(); f != effects.end(); f++ ) {
1250 MATCH_TIMING();
1251 MATCH_SOURCE();
1253 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1257 //this will modify effect reference
1258 void EffectQueue::RemoveAllEffects(EffectRef &effect_reference) const
1260 ResolveEffectRef(effect_reference);
1261 if( effect_reference.opcode<0) {
1262 return;
1264 RemoveAllEffects(effect_reference.opcode);
1267 //Removes all effects with a matching resource field
1268 void EffectQueue::RemoveAllEffectsWithResource(ieDword opcode, const ieResRef resource) const
1270 std::list< Effect* >::const_iterator f;
1271 for ( f = effects.begin(); f != effects.end(); f++ ) {
1272 MATCH_OPCODE();
1273 MATCH_LIVE_FX();
1274 MATCH_RESOURCE();
1276 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1280 void EffectQueue::RemoveAllEffectsWithResource(EffectRef &effect_reference, const ieResRef resource) const
1282 ResolveEffectRef(effect_reference);
1283 RemoveAllEffectsWithResource(effect_reference.opcode, resource);
1286 //This method could be used to remove stat modifiers that would lower a stat
1287 //(works only if a higher stat means good for the target)
1288 void EffectQueue::RemoveAllDetrimentalEffects(ieDword opcode, ieDword current) const
1290 std::list< Effect* >::const_iterator f;
1291 for ( f = effects.begin(); f != effects.end(); f++ ) {
1292 MATCH_OPCODE();
1293 MATCH_LIVE_FX();
1294 switch((*f)->Parameter2) {
1295 case 0:case 3:
1296 if( ((signed) (*f)->Parameter1)>=0) continue;
1297 break;
1298 case 1:case 4:
1299 if( ((signed) (*f)->Parameter1)>=(signed) current) continue;
1300 break;
1301 case 2:case 5:
1302 if( ((signed) (*f)->Parameter1)>=100) continue;
1303 break;
1304 default:
1305 break;
1307 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1311 //Removes all effects with a matching param2
1312 //param2 is usually an effect's subclass (quality) while param1 is more like quantity.
1313 //So opcode+param2 usually pinpoints an effect better when not all effects of a given
1314 //opcode need to be removed (see removal of portrait icon)
1315 void EffectQueue::RemoveAllEffectsWithParam(ieDword opcode, ieDword param2) const
1317 std::list< Effect* >::const_iterator f;
1318 for ( f = effects.begin(); f != effects.end(); f++ ) {
1319 MATCH_OPCODE();
1320 MATCH_LIVE_FX();
1321 MATCH_PARAM2();
1323 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1327 //this function is called by FakeEffectExpiryCheck
1328 //probably also called by rest
1329 void EffectQueue::RemoveExpiredEffects(ieDword futuretime) const
1331 ieDword GameTime = core->GetGame()->GameTime;
1332 if( GameTime+futuretime*AI_UPDATE_TIME<GameTime) {
1333 GameTime=0xffffffff;
1334 } else {
1335 GameTime+=futuretime*AI_UPDATE_TIME;
1338 std::list< Effect* >::const_iterator f;
1339 for ( f = effects.begin(); f != effects.end(); f++ ) {
1340 //FIXME: how this method handles delayed effects???
1341 //it should remove them as well, i think
1342 if( DelayType( ((*f)->TimingMode) )!=PERMANENT ) {
1343 if( (*f)->Duration<=GameTime) {
1344 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1350 //this effect will expire all effects that are not truly permanent
1351 //which i call permanent after death (iesdp calls it permanent after bonuses)
1352 void EffectQueue::RemoveAllNonPermanentEffects() const
1354 std::list< Effect* >::const_iterator f;
1355 for ( f = effects.begin(); f != effects.end(); f++ ) {
1356 if( IsRemovable((*f)->TimingMode) ) {
1357 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1362 //this will modify effect reference
1364 void EffectQueue::RemoveAllDetrimentalEffects(EffectRef &effect_reference, ieDword current) const
1366 ResolveEffectRef(effect_reference);
1367 RemoveAllDetrimentalEffects(effect_reference.opcode, current);
1370 void EffectQueue::RemoveAllEffectsWithParam(EffectRef &effect_reference, ieDword param2) const
1372 ResolveEffectRef(effect_reference);
1373 RemoveAllEffectsWithParam(effect_reference.opcode, param2);
1376 //remove certain levels of effects, possibly matching school/secondary type
1377 //this method removes whole spells (tied together by their source)
1378 //FIXME: probably this isn't perfect
1379 void EffectQueue::RemoveLevelEffects(ieDword level, ieDword Flags, ieDword match) const
1381 ieResRef Removed;
1383 Removed[0]=0;
1384 std::list< Effect* >::const_iterator f;
1385 for ( f = effects.begin(); f != effects.end(); f++ ) {
1386 if( (*f)->Power>level) {
1387 continue;
1390 if( Removed[0]) {
1391 MATCH_SOURCE();
1393 if( Flags&RL_MATCHSCHOOL) {
1394 if( (*f)->PrimaryType!=match) {
1395 continue;
1398 if( Flags&RL_MATCHSECTYPE) {
1399 if( (*f)->SecondaryType!=match) {
1400 continue;
1403 //if dispellable was not set, or the effect is dispellable
1404 //then remove it
1405 if( Flags&RL_DISPELLABLE) {
1406 if( !((*f)->Resistance&FX_CAN_DISPEL)) {
1407 continue;
1410 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1411 if( Flags&RL_REMOVEFIRST) {
1412 memcpy(Removed,(*f)->Source, sizeof(Removed));
1417 Effect *EffectQueue::HasOpcode(ieDword opcode) const
1419 std::list< Effect* >::const_iterator f;
1420 for ( f = effects.begin(); f != effects.end(); f++ ) {
1421 MATCH_OPCODE();
1422 MATCH_LIVE_FX();
1424 return (*f);
1426 return NULL;
1429 Effect *EffectQueue::HasEffect(EffectRef &effect_reference) const
1431 ResolveEffectRef(effect_reference);
1432 if( effect_reference.opcode<0) {
1433 return NULL;
1435 return HasOpcode(effect_reference.opcode);
1438 Effect *EffectQueue::HasOpcodeWithParam(ieDword opcode, ieDword param2) const
1440 std::list< Effect* >::const_iterator f;
1441 for ( f = effects.begin(); f != effects.end(); f++ ) {
1442 MATCH_OPCODE();
1443 MATCH_LIVE_FX();
1444 MATCH_PARAM2();
1446 return (*f);
1448 return NULL;
1451 Effect *EffectQueue::HasEffectWithParam(EffectRef &effect_reference, ieDword param2) const
1453 ResolveEffectRef(effect_reference);
1454 if( effect_reference.opcode<0) {
1455 return NULL;
1457 return HasOpcodeWithParam(effect_reference.opcode, param2);
1460 //looks for opcode with pairs of parameters (useful for protection against creature, extra damage or extra thac0 against creature)
1461 //generally an IDS targeting
1463 Effect *EffectQueue::HasOpcodeWithParamPair(ieDword opcode, ieDword param1, ieDword param2) const
1465 std::list< Effect* >::const_iterator f;
1466 for ( f = effects.begin(); f != effects.end(); f++ ) {
1467 MATCH_OPCODE();
1468 MATCH_LIVE_FX();
1469 MATCH_PARAM2();
1470 //0 is always accepted as first parameter
1471 if( param1) {
1472 MATCH_PARAM1();
1475 return (*f);
1477 return NULL;
1480 Effect *EffectQueue::HasEffectWithParamPair(EffectRef &effect_reference, ieDword param1, ieDword param2) const
1482 ResolveEffectRef(effect_reference);
1483 if( effect_reference.opcode<0) {
1484 return NULL;
1486 return HasOpcodeWithParamPair(effect_reference.opcode, param1, param2);
1489 // sums all the values of the specific damage bonus effects of the passed "damage type"
1490 int EffectQueue::SpecificDamageBonus(ieDword opcode, ieDword param2) const
1492 int bonus = 0;
1493 std::list< Effect* >::const_iterator f;
1494 for ( f = effects.begin(); f != effects.end(); f++ ) {
1495 MATCH_OPCODE();
1496 MATCH_LIVE_FX();
1497 MATCH_PARAM2();
1498 bonus += (signed) (*f)->Parameter1;
1500 return bonus;
1503 static EffectRef fx_damage_bonus_modifier_ref={"DamageBonusModifier",NULL,-1};
1504 int EffectQueue::SpecificDamageBonus(ieDword damage_type) const
1506 ResolveEffectRef(fx_damage_bonus_modifier_ref);
1507 if(fx_damage_bonus_modifier_ref.opcode < 0) {
1508 return 0;
1510 return SpecificDamageBonus(fx_damage_bonus_modifier_ref.opcode, damage_type);
1513 //this could be used for stoneskins and mirror images as well
1514 void EffectQueue::DecreaseParam1OfEffect(ieDword opcode, ieDword amount) const
1516 std::list< Effect* >::const_iterator f;
1517 for ( f = effects.begin(); f != effects.end(); f++ ) {
1518 MATCH_OPCODE();
1519 MATCH_LIVE_FX();
1520 ieDword value = (*f)->Parameter1;
1521 if( value>amount) value-=amount;
1522 else value = 0;
1523 (*f)->Parameter1=value;
1527 void EffectQueue::DecreaseParam1OfEffect(EffectRef &effect_reference, ieDword amount) const
1529 ResolveEffectRef(effect_reference);
1530 if( effect_reference.opcode<0) {
1531 return;
1533 DecreaseParam1OfEffect(effect_reference.opcode, amount);
1537 //this function does IDS targeting for effects (extra damage/thac0 against creature)
1538 static const int ids_stats[7]={IE_EA, IE_GENERAL, IE_RACE, IE_CLASS, IE_SPECIFIC, IE_SEX, IE_ALIGNMENT};
1540 int EffectQueue::BonusAgainstCreature(ieDword opcode, Actor *actor) const
1542 int sum = 0;
1543 std::list< Effect* >::const_iterator f;
1544 for ( f = effects.begin(); f != effects.end(); f++ ) {
1545 MATCH_OPCODE();
1546 MATCH_LIVE_FX();
1547 ieDword ids = (*f)->Parameter2;
1548 if( ids<2 || ids>9) {
1549 ids=2;
1551 ieDword param1 = actor->GetStat(ids_stats[ids-2]);
1552 if( (*f)->Parameter1) {
1553 MATCH_PARAM1();
1555 int val = (int) (*f)->Parameter3;
1556 if( !val) val = 2;
1557 sum += val;
1559 return sum;
1562 int EffectQueue::BonusAgainstCreature(EffectRef &effect_reference, Actor *actor) const
1564 ResolveEffectRef(effect_reference);
1565 if( effect_reference.opcode<0) {
1566 return 0;
1568 return BonusAgainstCreature(effect_reference.opcode, actor);
1571 bool EffectQueue::WeaponImmunity(ieDword opcode, int enchantment, ieDword weapontype) const
1573 std::list< Effect* >::const_iterator f;
1574 for ( f = effects.begin(); f != effects.end(); f++ ) {
1575 MATCH_OPCODE();
1576 MATCH_LIVE_FX();
1578 int magic = (int) (*f)->Parameter1;
1579 ieDword mask = (*f)->Parameter3;
1580 ieDword value = (*f)->Parameter4;
1581 if( magic==0) {
1582 if( enchantment) continue;
1583 } else if( magic>0) {
1584 if( enchantment>magic) continue;
1587 if( (weapontype&mask) != value) {
1588 continue;
1590 return true;
1592 return false;
1595 static EffectRef fx_weapon_immunity_ref={"Protection:Weapons",NULL,-1};
1597 bool EffectQueue::WeaponImmunity(int enchantment, ieDword weapontype) const
1599 ResolveEffectRef(fx_weapon_immunity_ref);
1600 if( fx_weapon_immunity_ref.opcode<0) {
1601 return 0;
1603 return WeaponImmunity(fx_weapon_immunity_ref.opcode, enchantment, weapontype);
1606 static EffectRef fx_disable_spellcasting_ref={ "DisableCasting", NULL, -1 };
1607 int EffectQueue::DisabledSpellcasting(int types) const
1609 ResolveEffectRef(fx_disable_spellcasting_ref);
1610 if( fx_disable_spellcasting_ref.opcode < 0) {
1611 return 0;
1614 unsigned int spelltype_mask = 0;
1615 bool iwd2 = !!core->HasFeature(GF_ENHANCED_EFFECTS);
1616 ieDword opcode = fx_disable_spellcasting_ref.opcode;
1617 std::list< Effect* >::const_iterator f;
1618 for ( f = effects.begin(); f != effects.end(); f++ ) {
1619 MATCH_OPCODE();
1620 MATCH_LIVE_FX();
1622 if (iwd2) {
1623 switch((*f)->Parameter2) {
1624 case 0: // all
1625 spelltype_mask |= 7;
1626 break;
1627 case 1: // mage and cleric
1628 spelltype_mask |= 3;
1629 break;
1630 case 2: // mage
1631 spelltype_mask |= 2;
1632 break;
1633 case 3: // cleric
1634 spelltype_mask |= 1;
1635 break;
1636 case 4: // innate
1637 spelltype_mask |= 4;
1638 break;
1640 } else {
1641 switch((*f)->Parameter2) {
1642 case 0: // mage
1643 spelltype_mask |= 2;
1644 break;
1645 case 1: // cleric
1646 spelltype_mask |= 1;
1647 break;
1648 case 2: // innate
1649 spelltype_mask |= 4;
1650 break;
1654 return spelltype_mask & types;
1657 //useful for immunity vs spell, can't use item, etc.
1658 Effect *EffectQueue::HasOpcodeWithResource(ieDword opcode, const ieResRef resource) const
1660 std::list< Effect* >::const_iterator f;
1661 for ( f = effects.begin(); f != effects.end(); f++ ) {
1662 MATCH_OPCODE();
1663 MATCH_LIVE_FX();
1664 MATCH_RESOURCE();
1666 return (*f);
1668 return NULL;
1671 Effect *EffectQueue::HasEffectWithResource(EffectRef &effect_reference, const ieResRef resource) const
1673 ResolveEffectRef(effect_reference);
1674 return HasOpcodeWithResource(effect_reference.opcode, resource);
1677 //used in contingency/sequencer code (cannot have the same contingency twice)
1678 Effect *EffectQueue::HasOpcodeWithSource(ieDword opcode, const ieResRef Removed) const
1680 std::list< Effect* >::const_iterator f;
1681 for ( f = effects.begin(); f != effects.end(); f++ ) {
1682 MATCH_OPCODE();
1683 MATCH_LIVE_FX();
1684 MATCH_SOURCE();
1686 return (*f);
1688 return NULL;
1691 Effect *EffectQueue::HasEffectWithSource(EffectRef &effect_reference, const ieResRef resource) const
1693 ResolveEffectRef(effect_reference);
1694 return HasOpcodeWithSource(effect_reference.opcode, resource);
1697 bool EffectQueue::HasAnyDispellableEffect() const
1699 std::list< Effect* >::const_iterator f;
1700 for ( f = effects.begin(); f != effects.end(); f++ ) {
1701 if( (*f)->Resistance&FX_CAN_DISPEL) {
1702 return true;
1705 return false;
1708 void EffectQueue::dump() const
1710 printf( "EFFECT QUEUE:\n" );
1711 int i = 0;
1712 std::list< Effect* >::const_iterator f;
1713 for ( f = effects.begin(); f != effects.end(); f++ ) {
1714 Effect* fx = *f;
1715 if( fx) {
1716 char *Name = NULL;
1717 if( fx->Opcode < MAX_EFFECTS)
1718 Name = (char*) Opcodes[fx->Opcode].Name;
1720 printf( " %2d: 0x%02x: %s (%d, %d) S:%s\n", i++, fx->Opcode, Name, fx->Parameter1, fx->Parameter2, fx->Source );
1725 Effect *EffectQueue::GetEffect(ieDword idx) const
1727 if( effects.size()<=idx) {
1728 return NULL;
1730 return effects[idx];
1734 //returns true if the effect supports simplified duration
1735 bool EffectQueue::HasDuration(Effect *fx)
1737 switch(fx->TimingMode) {
1738 case FX_DURATION_INSTANT_LIMITED: //simple duration
1739 case FX_DURATION_DELAY_LIMITED: //delayed duration
1740 case FX_DURATION_DELAY_PERMANENT: //simple delayed
1741 return true;
1743 return false;
1746 static EffectRef fx_variable_ref={"Variable:StoreLocalVariable",NULL,-1};
1748 //returns true if the effect must be saved
1749 //variables are saved differently
1750 bool EffectQueue::Persistent(Effect* fx)
1752 //we save this as variable
1753 if( fx->Opcode==(ieDword) ResolveEffect(fx_variable_ref)) {
1754 return false;
1757 switch (fx->TimingMode) {
1758 //normal equipping fx of items
1759 case FX_DURATION_INSTANT_WHILE_EQUIPPED:
1760 //delayed effect not saved
1761 case FX_DURATION_DELAY_UNSAVED:
1762 //permanent effect not saved
1763 case FX_DURATION_PERMANENT_UNSAVED:
1764 //just expired effect
1765 case FX_DURATION_JUST_EXPIRED:
1766 return false;
1768 return true;
1771 //alter the color effect in case the item is equipped in the shield slot
1772 void EffectQueue::HackColorEffects(Actor *Owner, Effect *fx)
1774 if( fx->InventorySlot!=Owner->inventory.GetShieldSlot()) return;
1776 unsigned int gradienttype = fx->Parameter2 & 0xF0;
1777 if( gradienttype == 0x10) {
1778 gradienttype = 0x20; // off-hand
1779 fx->Parameter2 &= ~0xF0;
1780 fx->Parameter2 |= gradienttype;
1784 //iterate through saved effects
1785 const Effect *EffectQueue::GetNextSavedEffect(std::list< Effect* >::const_iterator &f) const
1787 while(f!=effects.end()) {
1788 Effect *effect = *f;
1789 f++;
1790 if( Persistent(effect)) {
1791 return effect;
1794 return NULL;
1797 Effect *EffectQueue::GetNextEffect(std::list< Effect* >::const_iterator &f) const
1799 if( f!=effects.end()) return *f++;
1800 return NULL;
1803 ieDword EffectQueue::CountEffects(ieDword opcode, ieDword param1, ieDword param2, const char *resource) const
1805 ieDword cnt = 0;
1807 std::list< Effect* >::const_iterator f;
1809 for ( f = effects.begin(); f != effects.end(); f++ ) {
1810 MATCH_OPCODE();
1811 if( param1!=0xffffffff)
1812 MATCH_PARAM1();
1813 if( param2!=0xffffffff)
1814 MATCH_PARAM2();
1815 if( resource) {
1816 MATCH_RESOURCE();
1818 cnt++;
1820 return cnt;
1823 void EffectQueue::ModifyEffectPoint(ieDword opcode, ieDword x, ieDword y) const
1825 std::list< Effect* >::const_iterator f;
1827 for ( f = effects.begin(); f != effects.end(); f++ ) {
1828 MATCH_OPCODE();
1829 (*f)->PosX=x;
1830 (*f)->PosY=y;
1831 (*f)->Parameter3=0;
1832 return;
1836 //count effects that get saved
1837 ieDword EffectQueue::GetSavedEffectsCount() const
1839 ieDword cnt = 0;
1841 std::list< Effect* >::const_iterator f;
1843 for ( f = effects.begin(); f != effects.end(); f++ ) {
1844 Effect* fx = *f;
1845 if( Persistent(fx))
1846 cnt++;
1848 return cnt;
1851 void EffectQueue::TransformToDelay(ieByte &TimingMode)
1853 if( TimingMode<MAX_TIMING_MODE) {;
1854 TimingMode = fx_to_delayed[TimingMode];
1855 } else {
1856 TimingMode = FX_DURATION_JUST_EXPIRED;
1860 int EffectQueue::ResolveEffect(EffectRef &effect_reference)
1862 ResolveEffectRef(effect_reference);
1863 return effect_reference.opcode;
1866 // this check goes for the whole effect block, not individual effects
1867 // But it takes the first effect of the block for the common fields
1869 //returns 1 if effect block applicable
1870 //returns 0 if effect block disabled
1871 //returns -1 if effect block bounced
1872 int EffectQueue::CheckImmunity(Actor *target) const
1874 //don't resist if target is non living
1875 if( !target) {
1876 return 1;
1879 if( effects.size() ) {
1880 Effect* fx = *effects.begin();
1882 //projectile immunity
1883 if( target->ImmuneToProjectile(fx->Projectile)) return 0;
1885 //don't resist item projectile payloads based on spell school, bounce, etc.
1886 if( fx->InventorySlot) {
1887 return 1;
1890 //check level resistances
1891 //check specific spell immunity
1892 //check school/sectype immunity
1893 return check_type(target, fx);
1895 return 0;
1898 void EffectQueue::AffectAllInRange(Map *map, const Point &pos, int idstype, int idsvalue,
1899 unsigned int range, Actor *except)
1901 int cnt = map->GetActorCount(true);
1902 while(cnt--) {
1903 Actor *actor = map->GetActor(cnt,true);
1904 if( except==actor) {
1905 continue;
1907 //distance
1908 if( Distance(pos, actor)>range) {
1909 continue;
1911 //ids targeting
1912 if( !match_ids(actor, idstype, idsvalue)) {
1913 continue;
1915 //line of sight
1916 if( !map->IsVisible(actor->Pos, pos)) {
1917 continue;
1919 AddAllEffects(actor, actor->Pos);