factored out the EFFv2 saving into EFFImporter
[gemrb.git] / gemrb / core / EffectQueue.cpp
blobb1cb67f6280b3a03953521691507c622eb9b707b
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "EffectQueue.h"
23 #include "DisplayMessage.h"
24 #include "Effect.h"
25 #include "Game.h"
26 #include "Interface.h"
27 #include "Map.h"
28 #include "SymbolMgr.h"
29 #include "Scriptable/Actor.h"
30 #include "Spell.h" //needs for the source flags bitfield
32 #include <cstdio>
34 static struct {
35 const char* Name;
36 EffectFunction Function;
37 int Strref;
38 } Opcodes[MAX_EFFECTS];
40 static int initialized = 0;
41 static EffectRef *effectnames = NULL;
42 static int effectnames_count = 0;
43 static int pstflags = false;
45 bool EffectQueue::match_ids(Actor *target, int table, ieDword value)
47 if( value == 0) {
48 return true;
51 int a, stat;
53 switch (table) {
54 case 2: //EA
55 stat = IE_EA; break;
56 case 3: //GENERAL
57 stat = IE_GENERAL; break;
58 case 4: //RACE
59 stat = IE_RACE; break;
60 case 5: //CLASS
61 stat = IE_CLASS; break;
62 case 6: //SPECIFIC
63 stat = IE_SPECIFIC; break;
64 case 7: //GENDER
65 stat = IE_SEX; break;
66 case 8: //ALIGNMENT
67 stat = target->GetStat(IE_ALIGNMENT);
68 a = value&15;
69 if( a) {
70 if( a != ( stat & 15 )) {
71 return false;
74 a = value & 0xf0;
75 if( a) {
76 if( a != ( stat & 0xf0 )) {
77 return false;
80 return true;
81 default:
82 return false;
84 if( target->GetStat(stat)==value) {
85 return true;
87 return false;
90 static const bool fx_instant[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,true};
92 inline bool IsInstant(ieByte timingmode)
94 if( timingmode>=MAX_TIMING_MODE) return false;
95 return fx_instant[timingmode];
98 static const bool fx_equipped[MAX_TIMING_MODE]={false,false,true,false,false,true,false,false,true,false,false};
100 inline bool IsEquipped(ieByte timingmode)
102 if( timingmode>=MAX_TIMING_MODE) return false;
103 return fx_equipped[timingmode];
106 // 0 1 2 3 4 5 6 7 8 9 10
107 static const bool fx_relative[MAX_TIMING_MODE]={true,false,false,true,true,true,false,false,false,false,false};
109 inline bool NeedPrepare(ieWord timingmode)
111 if( timingmode>=MAX_TIMING_MODE) return false;
112 return fx_relative[timingmode];
115 #define INVALID -1
116 #define PERMANENT 0
117 #define DELAYED 1
118 #define DURATION 2
120 static const int fx_prepared[MAX_TIMING_MODE]={DURATION,PERMANENT,PERMANENT,DELAYED, //0-3
121 DELAYED,DELAYED,DELAYED,DELAYED,PERMANENT,PERMANENT,PERMANENT}; //4-7
123 inline int DelayType(ieByte timingmode)
125 if( timingmode>=MAX_TIMING_MODE) return INVALID;
126 return fx_prepared[timingmode];
129 //which effects are removable
130 static const bool fx_removable[MAX_TIMING_MODE]={true,true,false,true,true,false,true,true,false,false,true};
132 inline int IsRemovable(ieByte timingmode)
134 if( timingmode>=MAX_TIMING_MODE) return INVALID;
135 return fx_removable[timingmode];
138 //change the timing method after the effect triggered
139 static const ieByte fx_triggered[MAX_TIMING_MODE]={FX_DURATION_JUST_EXPIRED,FX_DURATION_INSTANT_PERMANENT,//0,1
140 FX_DURATION_INSTANT_WHILE_EQUIPPED,FX_DURATION_DELAY_LIMITED_PENDING,//2,3
141 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
142 FX_DURATION_INSTANT_LIMITED,FX_DURATION_JUST_EXPIRED,FX_DURATION_PERMANENT_UNSAVED,//6,8
143 FX_DURATION_INSTANT_PERMANENT_AFTER_BONUSES,FX_DURATION_JUST_EXPIRED};//9,10
145 //change the timing method for effect that should trigger after this effect expired
146 static const ieDword fx_to_delayed[]={FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,
147 FX_DURATION_PERMANENT_UNSAVED,FX_DURATION_DELAY_LIMITED_PENDING,
148 FX_DURATION_AFTER_EXPIRES,FX_DURATION_PERMANENT_UNSAVED, //4,5
149 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED,//6,8
150 FX_DURATION_JUST_EXPIRED,FX_DURATION_JUST_EXPIRED};//9,10
152 inline ieByte TriggeredEffect(ieByte timingmode)
154 if( timingmode>=MAX_TIMING_MODE) return false;
155 return fx_triggered[timingmode];
158 int compare_effects(const void *a, const void *b)
160 return stricmp(((EffectRef *) a)->Name,((EffectRef *) b)->Name);
163 int find_effect(const void *a, const void *b)
165 return stricmp((const char *) a,((const EffectRef *) b)->Name);
168 static EffectRef* FindEffect(const char* effectname)
170 if( !effectname || !effectnames) {
171 return NULL;
173 void *tmp = bsearch(effectname, effectnames, effectnames_count, sizeof(EffectRef), find_effect);
174 if( !tmp) {
175 printMessage( "EffectQueue", "", YELLOW);
176 printf("Couldn't assign effect: %s\n", effectname );
178 return (EffectRef *) tmp;
181 static EffectRef fx_protection_from_display_string_ref={"Protection:String",NULL,-1};
183 //special effects without level check (but with damage dices precalculated)
184 static EffectRef diced_effects[] = {
185 //core effects
186 {"Damage",NULL,-1},
187 {"CurrentHPModifier",NULL,-1},
188 {"MaximumHPModifier",NULL,-1},
189 //iwd effects
190 {"BurningBlood",NULL,-1}, //iwd
191 {"ColdDamage",NULL,-1},
192 {"CrushingDamage",NULL,-1},
193 {"VampiricTouch",NULL,-1},
194 {"VitriolicSphere",NULL,-1},
195 //pst effects
196 {"TransferHP",NULL,-1},
197 {NULL,NULL,0} };
199 //special effects without level check (but with damage dices not precalculated)
200 static EffectRef diced_effects2[] = {
201 {"BurningBlood2",NULL,-1}, //how/iwd2
202 {"StaticCharge",NULL,-1}, //how/iwd2
203 {"SoulEater",NULL,-1}, //how/iwd2
204 {"LichTouch",NULL,-1}, //how
205 {NULL,NULL,0} };
207 inline static void ResolveEffectRef(EffectRef &effect_reference)
209 if( effect_reference.opcode==-1) {
210 EffectRef* ref = FindEffect(effect_reference.Name);
211 if( ref && ref->opcode>=0) {
212 effect_reference.opcode = ref->opcode;
213 return;
215 effect_reference.opcode = -2;
219 bool Init_EffectQueue()
221 int i;
223 if( initialized) {
224 return true;
226 pstflags = !!core->HasFeature(GF_PST_STATE_FLAGS);
228 memset( Opcodes, 0, sizeof( Opcodes ) );
229 for(i=0;i<MAX_EFFECTS;i++) {
230 Opcodes[i].Strref=-1;
233 initialized = 1;
235 AutoTable efftextTable("efftext");
237 int eT = core->LoadSymbol( "effects" );
238 if( eT < 0) {
239 printMessage( "EffectQueue","A critical scripting file is missing!\n",LIGHT_RED );
240 return false;
242 Holder<SymbolMgr> effectsTable = core->GetSymbol( eT );
243 if( !effectsTable) {
244 printMessage( "EffectQueue","A critical scripting file is damaged!\n",LIGHT_RED );
245 return false;
248 for (i = 0; i < MAX_EFFECTS; i++) {
249 const char* effectname = effectsTable->GetValue( i );
250 if( efftextTable) {
251 int row = efftextTable->GetRowCount();
252 while (row--) {
253 const char* ret = efftextTable->GetRowName( row );
254 long val;
255 if( valid_number( ret, val ) && (i == val) ) {
256 Opcodes[i].Strref = atoi( efftextTable->QueryField( row, 1 ) );
261 EffectRef* poi = FindEffect( effectname );
262 if( poi != NULL) {
263 Opcodes[i].Function = poi->Function;
264 Opcodes[i].Name = poi->Name;
265 //reverse linking opcode number
266 //using this unused field
267 if( (poi->opcode!=-1) && effectname[0]!='*') {
268 printf("Clashing Opcodes FN: %d vs. %d, %s\n", i, poi->opcode, effectname);
269 abort();
271 poi->opcode = i;
273 //printf("-------- FN: %d, %s\n", i, effectname);
275 core->DelSymbol( eT );
277 //additional initialisations
278 for (i=0;diced_effects[i].Name;i++) {
279 ResolveEffectRef(diced_effects[i]);
281 for (i=0;diced_effects2[i].Name;i++) {
282 ResolveEffectRef(diced_effects2[i]);
285 return true;
288 void EffectQueue_ReleaseMemory()
290 if( effectnames) {
291 free (effectnames);
293 effectnames_count = 0;
294 effectnames = NULL;
297 void EffectQueue_RegisterOpcodes(int count, const EffectRef* opcodes)
299 if( ! effectnames) {
300 effectnames = (EffectRef*) malloc( (count+1) * sizeof( EffectRef ) );
301 } else {
302 effectnames = (EffectRef*) realloc( effectnames, (effectnames_count + count + 1) * sizeof( EffectRef ) );
305 memcpy( effectnames + effectnames_count, opcodes, count * sizeof( EffectRef ));
306 effectnames_count += count;
307 effectnames[effectnames_count].Name = NULL;
308 //if we merge two effect lists, then we need to sort their effect tables
309 //actually, we might always want to sort this list, so there is no
310 //need to do it manually (sorted table is needed if we use bsearch)
311 qsort(effectnames, effectnames_count, sizeof(EffectRef), compare_effects);
314 EffectQueue::EffectQueue()
316 Owner = NULL;
319 EffectQueue::~EffectQueue()
321 std::list< Effect* >::iterator f;
323 for ( f = effects.begin(); f != effects.end(); f++ ) {
324 delete (*f);
328 Effect *EffectQueue::CreateEffect(ieDword opcode, ieDword param1, ieDword param2, ieWord timing)
330 if( opcode==0xffffffff) {
331 return NULL;
333 Effect *fx = new Effect();
334 if( !fx) {
335 return NULL;
337 memset(fx,0,sizeof(Effect));
338 fx->Target = FX_TARGET_SELF;
339 fx->Opcode = opcode;
340 //probability2 is the low number (by effectqueue 331)
341 fx->Probability1 = 100;
342 fx->Parameter1 = param1;
343 fx->Parameter2 = param2;
344 fx->TimingMode = timing;
345 fx->PosX = 0xffffffff;
346 fx->PosY = 0xffffffff;
347 return fx;
350 //return the count of effects with matching parameters
351 //useful for effects where there is no separate stat to see
352 ieDword EffectQueue::CountEffects(EffectRef &effect_reference, ieDword param1, ieDword param2, const char *resource) const
354 ResolveEffectRef(effect_reference);
355 if( effect_reference.opcode<0) {
356 return 0;
358 return CountEffects(effect_reference.opcode, param1, param2, resource);
361 //Change the location of an existing effect
362 //this is used when some external code needs to adjust the effect's location
363 //used when the gui sets the effect's final target
364 void EffectQueue::ModifyEffectPoint(EffectRef &effect_reference, ieDword x, ieDword y) const
366 ResolveEffectRef(effect_reference);
367 if( effect_reference.opcode<0) {
368 return;
370 ModifyEffectPoint(effect_reference.opcode, x, y);
373 Effect *EffectQueue::CreateEffect(EffectRef &effect_reference, ieDword param1, ieDword param2, ieWord timing)
375 ResolveEffectRef(effect_reference);
376 if( effect_reference.opcode<0) {
377 return NULL;
379 return CreateEffect(effect_reference.opcode, param1, param2, timing);
382 //copies the whole effectqueue (area projectiles use it)
383 EffectQueue *EffectQueue::CopySelf() const
385 EffectQueue *effects;
387 effects = new EffectQueue();
388 std::list< Effect* >::const_iterator fxit = GetFirstEffect();
389 Effect *fx;
391 while( (fx = GetNextEffect(fxit))) {
392 effects->AddEffect(fx, false);
394 effects->SetOwner(GetOwner());
395 return effects;
398 //create a new effect with most of the characteristics of the old effect
399 //only opcode and parameters are changed
400 //This is used mostly inside effects, when an effect needs to spawn
401 //other effects with the same coordinates, source, duration, etc.
402 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, ieDword opcode, ieDword param1, ieDword param2)
404 if( opcode==0xffffffff) {
405 return NULL;
407 Effect *fx = new Effect();
408 if( !fx) {
409 return NULL;
411 memcpy(fx,oldfx,sizeof(Effect) );
412 fx->Opcode=opcode;
413 fx->Parameter1=param1;
414 fx->Parameter2=param2;
415 return fx;
418 Effect *EffectQueue::CreateEffectCopy(Effect *oldfx, EffectRef &effect_reference, ieDword param1, ieDword param2)
420 ResolveEffectRef(effect_reference);
421 if( effect_reference.opcode<0) {
422 return NULL;
424 return CreateEffectCopy(oldfx, effect_reference.opcode, param1, param2);
427 static EffectRef fx_unsummon_creature_ref={"UnsummonCreature",NULL,-1};
429 Effect *EffectQueue::CreateUnsummonEffect(Effect *fx)
431 Effect *newfx = NULL;
432 if( (fx->TimingMode&0xff) == FX_DURATION_INSTANT_LIMITED) {
433 newfx = CreateEffectCopy(fx, fx_unsummon_creature_ref, 0, 0);
434 newfx->TimingMode = FX_DURATION_DELAY_PERMANENT;
435 if( newfx->Resource3[0]) {
436 strnuprcpy(newfx->Resource,newfx->Resource3, sizeof(ieResRef)-1 );
437 } else {
438 strnuprcpy(newfx->Resource,"SPGFLSH1", sizeof(ieResRef)-1 );
440 if( fx->TimingMode == FX_DURATION_ABSOLUTE) {
441 //unprepare duration
442 newfx->Duration = (newfx->Duration-core->GetGame()->GameTime)/AI_UPDATE_TIME;
446 return newfx;
449 void EffectQueue::AddEffect(Effect* fx, bool insert)
451 Effect* new_fx = new Effect;
452 memcpy( new_fx, fx, sizeof( Effect ) );
453 if( insert) {
454 effects.insert( effects.begin(), new_fx );
455 } else {
456 effects.push_back( new_fx );
460 //This method can remove an effect described by a pointer to it, or
461 //an exact matching effect
462 bool EffectQueue::RemoveEffect(Effect* fx)
464 int invariant_size = offsetof( Effect, random_value );
466 for (std::list< Effect* >::iterator f = effects.begin(); f != effects.end(); f++ ) {
467 Effect* fx2 = *f;
469 if( (fx==fx2) || !memcmp( fx, fx2, invariant_size)) {
470 delete fx2;
471 effects.erase( f );
472 return true;
475 return false;
478 //this is where we reapply all effects when loading a saved game
479 //The effects are already in the fxqueue of the target
480 void EffectQueue::ApplyAllEffects(Actor* target) const
482 std::list< Effect* >::const_iterator f;
483 for ( f = effects.begin(); f != effects.end(); f++ ) {
484 ApplyEffect( target, *f, 0 );
488 void EffectQueue::Cleanup()
490 std::list< Effect* >::iterator f;
492 for ( f = effects.begin(); f != effects.end(); ) {
493 if( (*f)->TimingMode == FX_DURATION_JUST_EXPIRED) {
494 delete *f;
495 effects.erase(f++);
496 } else {
497 f++;
502 //Handle the target flag when the effect is applied first
503 int EffectQueue::AddEffect(Effect* fx, Scriptable* self, Actor* pretarget, const Point &dest) const
505 int i;
506 Game *game;
507 Map *map;
508 int flg;
509 ieDword spec = 0;
510 Actor *st = (self && (self->Type==ST_ACTOR)) ?(Actor *) self:NULL;
512 switch (fx->Target) {
513 case FX_TARGET_ORIGINAL:
514 fx->SetPosition(self->Pos);
516 flg = ApplyEffect( st, fx, 1 );
517 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
518 if( st) {
519 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
522 break;
523 case FX_TARGET_SELF:
524 fx->SetPosition(dest);
526 flg = ApplyEffect( st, fx, 1 );
527 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
528 if( st) {
529 st->fxqueue.AddEffect( fx, flg==FX_INSERT );
532 break;
534 case FX_TARGET_ALL_BUT_SELF:
535 map=self->GetCurrentArea();
536 i= map->GetActorCount(true);
537 while(i--) {
538 Actor* actor = map->GetActor( i, true );
539 //don't pick ourselves
540 if( st==actor) {
541 continue;
543 fx->SetPosition(actor->Pos);
545 flg = ApplyEffect( actor, fx, 1 );
546 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
547 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
550 flg = FX_APPLIED;
551 break;
553 case FX_TARGET_OWN_SIDE:
554 if( !st || st->InParty) {
555 goto all_party;
557 map = self->GetCurrentArea();
558 spec = st->GetStat(IE_SPECIFIC);
560 //GetActorCount(false) returns all nonparty critters
561 i = map->GetActorCount(false);
562 while(i--) {
563 Actor* actor = map->GetActor( i, false );
564 if( actor->GetStat(IE_SPECIFIC)!=spec) {
565 continue;
567 fx->SetPosition(actor->Pos);
569 flg = ApplyEffect( actor, fx, 1 );
570 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
571 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
574 flg = FX_APPLIED;
575 break;
576 case FX_TARGET_OTHER_SIDE:
577 if( !pretarget || pretarget->InParty) {
578 goto all_party;
580 map = self->GetCurrentArea();
581 spec = pretarget->GetStat(IE_SPECIFIC);
583 //GetActorCount(false) returns all nonparty critters
584 i = map->GetActorCount(false);
585 while(i--) {
586 Actor* actor = map->GetActor( i, false );
587 if( actor->GetStat(IE_SPECIFIC)!=spec) {
588 continue;
590 fx->SetPosition(actor->Pos);
592 flg = ApplyEffect( actor, fx, 1 );
593 //GetActorCount can now return all nonparty critters
594 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
595 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
598 flg = FX_APPLIED;
599 break;
600 case FX_TARGET_PRESET:
601 fx->SetPosition(pretarget->Pos);
603 flg = ApplyEffect( pretarget, fx, 1 );
604 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
605 if( pretarget) {
606 pretarget->fxqueue.AddEffect( fx, flg==FX_INSERT );
609 break;
611 case FX_TARGET_PARTY:
612 all_party:
613 game = core->GetGame();
614 i = game->GetPartySize(true);
615 while(i--) {
616 Actor* actor = game->GetPC( i, true );
617 fx->SetPosition(actor->Pos);
619 flg = ApplyEffect( actor, fx, 1 );
620 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
621 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
624 flg = FX_APPLIED;
625 break;
627 case FX_TARGET_ALL:
628 map = self->GetCurrentArea();
629 i = map->GetActorCount(true);
630 while(i--) {
631 Actor* actor = map->GetActor( i, true );
632 fx->SetPosition(actor->Pos);
634 flg = ApplyEffect( actor, fx, 1 );
635 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
636 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
639 flg = FX_APPLIED;
640 break;
642 case FX_TARGET_ALL_BUT_PARTY:
643 map = self->GetCurrentArea();
644 i = map->GetActorCount(false);
645 while(i--) {
646 Actor* actor = map->GetActor( i, false );
647 fx->SetPosition(actor->Pos);
649 flg = ApplyEffect( actor, fx, 1 );
650 //GetActorCount can now return all nonparty critters
651 if( fx->TimingMode != FX_DURATION_JUST_EXPIRED) {
652 actor->fxqueue.AddEffect( fx, flg==FX_INSERT );
655 flg = FX_APPLIED;
656 break;
658 case FX_TARGET_UNKNOWN:
659 default:
660 printf( "Unknown FX target type: %d\n", fx->Target);
661 flg = FX_ABORT;
662 break;
665 return flg;
668 //this is where effects from spells first get in touch with the target
669 //the effects are currently NOT in the target's fxqueue, those that stick
670 //will get copied (hence the fxqueue.AddEffect call)
671 //if this returns FX_NOT_APPLIED, then the whole stack was resisted
672 //or expired
673 int EffectQueue::AddAllEffects(Actor* target, const Point &destination) const
675 int res = FX_NOT_APPLIED;
676 // pre-roll dice for fx needing them and stow them in the effect
677 ieDword random_value = core->Roll( 1, 100, 0 );
679 if( target) {
680 target->RollSaves();
682 std::list< Effect* >::const_iterator f;
683 for ( f = effects.begin(); f != effects.end(); f++ ) {
684 //handle resistances and saving throws here
685 (*f)->random_value = random_value;
686 //if applyeffect returns true, we stop adding the future effects
687 //this is to simulate iwd2's on the fly spell resistance
689 int tmp = AddEffect(*f, Owner, target, destination);
690 //lets try without Owner, any crash?
691 //If yes, then try to fix the individual effect
692 //If you use target for Owner here, the wand in chateau irenicus will work
693 //the same way as Imoen's monster summoning, which is a BAD THING (TM)
694 //int tmp = AddEffect(*f, Owner?Owner:target, target, destination);
695 if( tmp == FX_ABORT) {
696 res = FX_NOT_APPLIED;
697 break;
699 if( tmp != FX_NOT_APPLIED) {
700 res = FX_APPLIED;
703 return res;
706 //check if an effect has no level based resistance, but instead the dice sizes/count
707 //adjusts Parameter1 (like a damage causing effect)
708 inline static bool IsDicedEffect(int opcode)
710 int i;
712 for(i=0;diced_effects[i].Name;i++) {
713 if( diced_effects[i].opcode==opcode) {
714 return true;
717 return false;
720 //there is no level based resistance, but Parameter1 cannot be precalculated
721 //these effects use the Dice fields in a special way
722 inline static bool IsDicedEffect2(int opcode)
724 int i;
726 for(i=0;diced_effects2[i].Name;i++) {
727 if( diced_effects2[i].opcode==opcode) {
728 return true;
731 return false;
734 //resisted effect based on level
735 inline bool check_level(Actor *target, Effect *fx)
737 //skip non level based effects
738 if( IsDicedEffect((int) fx->Opcode)) {
739 fx->Parameter1 = DICE_ROLL((signed)fx->Parameter1);
740 //this is a hack for PST style diced effects
741 if( core->HasFeature(GF_SAVE_FOR_HALF) ) {
742 if( memcmp(fx->Resource,"NEG",4) ) {
743 fx->IsSaveForHalfDamage=1;
745 } else {
746 if( (fx->Parameter2&3)==3) {
747 fx->IsSaveForHalfDamage=1;
750 return false;
752 if( IsDicedEffect2((int) fx->Opcode)) {
753 return false;
756 if( !target) {
757 return false;
759 if(fx->Target == FX_TARGET_SELF) {
760 return false;
763 ieDword level = (ieDword) target->GetXPLevel( true );
764 //return true if resisted
765 if (fx->MinAffectedLevel > 0 || fx->MaxAffectedLevel > 0) {
766 if (level < fx->MinAffectedLevel || level > fx->MaxAffectedLevel) {
767 return true;
770 return false;
773 //roll for the effect probability, there is a high and a low treshold, the d100
774 //roll should hit in the middle
775 inline bool check_probability(Effect* fx)
777 //watch for this, probability1 is the high number
778 //probability2 is the low number
779 //random value is 1-100
780 if( fx->random_value<=fx->Probability2 || fx->random_value>fx->Probability1) {
781 return false;
783 return true;
786 //immunity effects
787 static EffectRef fx_level_immunity_ref={"Protection:Spelllevel",NULL,-1};
788 static EffectRef fx_opcode_immunity_ref={"Protection:Opcode",NULL,-1}; //bg2
789 static EffectRef fx_opcode_immunity2_ref={"Protection:Opcode2",NULL,-1};//iwd
790 static EffectRef fx_spell_immunity_ref={"Protection:Spell",NULL,-1}; //bg2
791 static EffectRef fx_spell_immunity2_ref={"Protection:Spell2",NULL,-1};//iwd
792 static EffectRef fx_store_spell_sequencer_ref={"Sequencer:Store",NULL,-1}; //bg2, works against sequencers
793 static EffectRef fx_school_immunity_ref={"Protection:School",NULL,-1};
794 static EffectRef fx_secondary_type_immunity_ref={"Protection:SecondaryType",NULL,-1};
796 //decrementing immunity effects
797 static EffectRef fx_level_immunity_dec_ref={"Protection:SpellLevelDec",NULL,-1};
798 static EffectRef fx_spell_immunity_dec_ref={"Protection:SpellDec",NULL,-1};
799 static EffectRef fx_school_immunity_dec_ref={"Protection:SchoolDec",NULL,-1};
800 static EffectRef fx_secondary_type_immunity_dec_ref={"Protection:SecondaryTypeDec",NULL,-1};
802 //bounce effects
803 static EffectRef fx_level_bounce_ref={"Bounce:SpellLevel",NULL,-1};
804 //static EffectRef fx_opcode_bounce_ref={"Bounce:Opcode",NULL,-1};
805 static EffectRef fx_spell_bounce_ref={"Bounce:Spell",NULL,-1};
806 static EffectRef fx_school_bounce_ref={"Bounce:School",NULL,-1};
807 static EffectRef fx_secondary_type_bounce_ref={"Bounce:SecondaryType",NULL,-1};
809 //decrementing bounce effects
810 static EffectRef fx_level_bounce_dec_ref={"Bounce:SpellLevelDec",NULL,-1};
811 static EffectRef fx_spell_bounce_dec_ref={"Bounce:SpellDec",NULL,-1};
812 static EffectRef fx_school_bounce_dec_ref={"Bounce:SchoolDec",NULL,-1};
813 static EffectRef fx_secondary_type_bounce_dec_ref={"Bounce:SecondaryTypeDec",NULL,-1};
815 //spelltrap (multiple decrementing immunity)
816 static EffectRef fx_spelltrap={"SpellTrap", NULL,-1};
818 //this is for whole spell immunity/bounce
819 inline static void DecreaseEffect(Effect *efx)
821 efx->Parameter1--;
822 if( (int) efx->Parameter1<1) {
823 //don't remove effects directly!!!
824 efx->TimingMode = FX_DURATION_JUST_EXPIRED;
828 //lower decreasing immunities/bounces
829 static int check_type(Actor* actor, Effect* fx)
831 //the protective effect (if any)
832 Effect *efx;
834 ieDword bounce = actor->GetStat(IE_BOUNCE);
836 //immunity checks
837 /*opcode immunity is in the per opcode checks
838 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
839 return 0;
841 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
842 return 0;
845 //spell level immunity
846 if(fx->Power && actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_ref, fx->Power, 0) ) {
847 return 0;
850 //source immunity (spell name)
851 //if source is unspecified, don't resist it
852 if( fx->Source[0]) {
853 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity_ref, fx->Source) ) {
854 return 0;
856 if( actor->fxqueue.HasEffectWithResource(fx_spell_immunity2_ref, fx->Source) ) {
857 return 0;
859 if (actor->fxqueue.HasEffectWithResource(fx_store_spell_sequencer_ref, fx->Source) ) {
860 //TODO: display strref 0x806C
861 return 0;
865 //primary type immunity (school)
866 if( fx->PrimaryType) {
867 if( actor->fxqueue.HasEffectWithParam(fx_school_immunity_ref, fx->PrimaryType)) {
868 return 0;
872 //secondary type immunity (usage)
873 if( fx->SecondaryType) {
874 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_ref, fx->SecondaryType) ) {
875 return 0;
879 //decrementing immunity checks
880 //decrementing level immunity
881 efx = actor->fxqueue.HasEffectWithParamPair(fx_level_immunity_dec_ref, fx->Power, 0);
882 if( efx ) {
883 DecreaseEffect(efx);
884 return 0;
887 //decrementing spell immunity
888 if( fx->Source[0]) {
889 efx = actor->fxqueue.HasEffectWithResource(fx_spell_immunity_dec_ref, fx->Source);
890 if( efx) {
891 DecreaseEffect(efx);
892 return 0;
895 //decrementing primary type immunity (school)
896 if( fx->PrimaryType) {
897 efx = actor->fxqueue.HasEffectWithParam(fx_school_immunity_dec_ref, fx->PrimaryType);
898 if( efx) {
899 DecreaseEffect(efx);
900 return 0;
904 //decrementing secondary type immunity (usage)
905 if( fx->SecondaryType) {
906 efx = actor->fxqueue.HasEffectWithParam(fx_secondary_type_immunity_dec_ref, fx->SecondaryType);
907 if( efx) {
908 DecreaseEffect(efx);
909 return 0;
913 //spelltrap (absorb)
914 //FIXME:
915 //if the spelltrap effect already absorbed enough levels
916 //but still didn't get removed, it will absorb levels it shouldn't
917 //it will also absorb multiple spells in a single round
918 efx=actor->fxqueue.HasEffectWithParamPair(fx_spelltrap, 0, fx->Power);
919 if( efx) {
920 //storing the absorbed spell level
921 efx->Parameter3+=fx->Power;
922 //instead of a single effect, they had to create an effect for each level
923 //HOW DAMN LAME
924 //if decrease needs the spell level, use fx->Power here
925 actor->fxqueue.DecreaseParam1OfEffect(fx_spelltrap, 1);
926 //efx->Parameter1--;
927 return 0;
930 //bounce checks
931 if( (bounce&BNC_LEVEL) && actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_ref, fx->Power, 0) ) {
932 return 0;
935 if( fx->Source[0] && (bounce&BNC_RESOURCE) && actor->fxqueue.HasEffectWithResource(fx_spell_bounce_ref, fx->Source) ) {
936 return -1;
939 if( fx->PrimaryType && (bounce&BNC_SCHOOL) ) {
940 if( actor->fxqueue.HasEffectWithParam(fx_school_bounce_ref, fx->PrimaryType)) {
941 return -1;
945 if( fx->SecondaryType && (bounce&BNC_SECTYPE) ) {
946 if( actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_ref, fx->SecondaryType)) {
947 return -1;
950 //decrementing bounce checks
952 //level decrementing bounce check
953 if( (bounce&BNC_LEVEL_DEC)) {
954 efx=actor->fxqueue.HasEffectWithParamPair(fx_level_bounce_dec_ref, fx->Power, 0);
955 if( efx) {
956 DecreaseEffect(efx);
957 return -1;
961 if( fx->Source[0] && (bounce&BNC_RESOURCE_DEC)) {
962 efx=actor->fxqueue.HasEffectWithResource(fx_spell_bounce_dec_ref, fx->Resource);
963 if( efx) {
964 DecreaseEffect(efx);
965 return -1;
969 if( fx->PrimaryType && (bounce&BNC_SCHOOL_DEC) ) {
970 efx=actor->fxqueue.HasEffectWithParam(fx_school_bounce_dec_ref, fx->PrimaryType);
971 if( efx) {
972 DecreaseEffect(efx);
973 return -1;
977 if( fx->SecondaryType && (bounce&BNC_SECTYPE_DEC) ) {
978 efx=actor->fxqueue.HasEffectWithParam(fx_secondary_type_bounce_dec_ref, fx->SecondaryType);
979 if( efx) {
980 DecreaseEffect(efx);
981 return -1;
985 return 1;
988 //check resistances, saving throws
989 static bool check_resistance(Actor* actor, Effect* fx)
991 if( !actor) {
992 return false;
995 //opcode immunity
996 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity_ref, fx->Opcode) ) {
997 printf ("immune to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
998 return true;
1000 if( actor->fxqueue.HasEffectWithParam(fx_opcode_immunity2_ref, fx->Opcode) ) {
1001 printf ("immune2 to effect: %s\n", (char*) Opcodes[fx->Opcode].Name);
1002 return true;
1005 /* opcode bouncing isn't implemented?
1006 //opcode bouncing
1007 if( actor->fxqueue.HasEffectWithParam(fx_opcode_bounce_ref, fx->Opcode) ) {
1008 return false;
1012 //not resistable (no saves either?)
1013 if( fx->Resistance != FX_CAN_RESIST_CAN_DISPEL) {
1014 return false;
1017 if (pstflags && (actor->GetSafeStat(IE_STATE_ID) & (STATE_ANTIMAGIC) ) ) {
1018 return false;
1021 //don't resist self
1022 bool selective_mr = core->HasFeature(GF_SELECTIVE_MAGIC_RES);
1023 if (fx->Target==FX_TARGET_SELF) {
1024 if (selective_mr) {
1025 return false;
1029 //magic immunity
1030 ieDword val = actor->GetStat(IE_RESISTMAGIC);
1031 if( fx->random_value < val) {
1032 // when using biased magic resistance non-hostile spells aren't resisted
1033 if ((selective_mr && (fx->SourceFlags&SF_HOSTILE)) || !selective_mr) {
1034 printf ("effect resisted: %s\n", (char*) Opcodes[fx->Opcode].Name);
1035 return true;
1039 //saving throws
1040 bool saved = false;
1041 for (int i=0;i<5;i++) {
1042 if( fx->SavingThrowType&(1<<i)) {
1043 saved = actor->GetSavingThrow(i, fx->SavingThrowBonus);
1044 if( saved) {
1045 break;
1049 if( saved) {
1050 if( fx->IsSaveForHalfDamage) {
1051 fx->Parameter1/=2;
1052 } else {
1053 printf ("%s saved against effect: %s\n", actor->GetName(1), (char*) Opcodes[fx->Opcode].Name);
1054 return true;
1057 return false;
1060 // this function is called two different ways
1061 // when FirstApply is set, then the effect isn't stuck on the target
1062 // this happens when a new effect comes in contact with the target.
1063 // if the effect returns FX_DURATION_JUST_EXPIRED then it won't stick
1064 // when first_apply is unset, the effect is already on the target
1065 // this happens on load time too!
1066 // returns FX_NOT_APPLIED if the process shouldn't be calling applyeffect anymore
1067 // returns FX_ABORT if the whole spell this effect is in should be aborted
1068 // it will disable all future effects of same source (only on first apply)
1070 int EffectQueue::ApplyEffect(Actor* target, Effect* fx, ieDword first_apply) const
1072 //printf( "FX 0x%02x: %s(%d, %d)\n", fx->Opcode, effectnames[fx->Opcode].Name, fx->Parameter1, fx->Parameter2 );
1073 if( fx->Opcode >= MAX_EFFECTS) {
1074 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1075 return FX_NOT_APPLIED;
1078 ieDword GameTime = core->GetGame()->GameTime;
1080 fx->FirstApply=first_apply;
1081 if( first_apply) {
1082 if( (fx->PosX==0xffffffff) && (fx->PosY==0xffffffff)) {
1083 fx->PosX = target->Pos.x;
1084 fx->PosY = target->Pos.y;
1087 //gemrb specific, stat based chance
1088 if ((fx->Probability2 == 100) && Owner && (Owner->Type==ST_ACTOR) ) {
1089 fx->Probability2 = 0;
1090 fx->Probability1 = ((Actor *) Owner)->GetSafeStat(fx->Probability1);
1093 //the effect didn't pass the probability check
1094 if( !check_probability(fx) ) {
1095 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1096 return FX_NOT_APPLIED;
1099 //the effect didn't pass the target level check
1100 if( check_level(target, fx) ) {
1101 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1102 return FX_NOT_APPLIED;
1105 //the effect didn't pass the resistance check
1106 if( check_resistance(target, fx) ) {
1107 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1108 return FX_NOT_APPLIED;
1111 //Same as in items and spells
1112 if (fx->SourceFlags & SF_HOSTILE) {
1113 if (target && (target != Owner) && Owner && (Owner->Type==ST_ACTOR) ) {
1114 target->AttackedBy((Actor *) Owner);
1118 if( NeedPrepare(fx->TimingMode) ) {
1119 //save delay for later
1120 fx->SecondaryDelay = fx->Duration;
1121 if( fx->TimingMode == FX_DURATION_INSTANT_LIMITED) {
1122 fx->TimingMode = FX_DURATION_ABSOLUTE;
1124 PrepareDuration(fx);
1127 //check if the effect has triggered or expired
1128 switch (DelayType(fx->TimingMode&0xff) ) {
1129 case DELAYED:
1130 if( fx->Duration>GameTime) {
1131 return FX_NOT_APPLIED;
1133 //effect triggered
1134 //delayed duration (3)
1135 if( NeedPrepare(fx->TimingMode) ) {
1136 //prepare for delayed duration effects
1137 fx->Duration = fx->SecondaryDelay;
1138 PrepareDuration(fx);
1140 fx->TimingMode=TriggeredEffect(fx->TimingMode);
1141 break;
1142 case DURATION:
1143 if( fx->Duration<=GameTime) {
1144 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1145 //add a return here, if 0 duration effects shouldn't work
1147 break;
1148 //permanent effect (so there is no warning)
1149 case PERMANENT:
1150 break;
1151 //this shouldn't happen
1152 default:
1153 printf("Unknown delay type: %d (from %d)\n", DelayType(fx->TimingMode&0xff), fx->TimingMode);
1154 abort();
1157 EffectFunction fn = 0;
1158 if( fx->Opcode<MAX_EFFECTS) {
1159 fn = Opcodes[fx->Opcode].Function;
1161 int res = FX_ABORT;
1162 if( fn) {
1163 if( target && first_apply ) {
1164 if( !target->fxqueue.HasEffectWithParamPair(fx_protection_from_display_string_ref, fx->Parameter1, 0) ) {
1165 displaymsg->DisplayStringName( Opcodes[fx->Opcode].Strref, 0xf0f0f0,
1166 target, IE_STR_SOUND);
1170 res=fn( Owner, target, fx );
1172 //if there is no owner, we assume it is the target
1173 switch( res ) {
1174 case FX_APPLIED:
1175 //normal effect with duration
1176 break;
1177 case FX_NOT_APPLIED:
1178 //instant effect, pending removal
1179 //for example, a damage effect
1180 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1181 break;
1182 case FX_INSERT:
1183 //put this effect in the beginning of the queue
1184 //all known insert effects are 'permanent' too
1185 //that is the AC effect only
1186 //actually, permanent effects seem to be
1187 //inserted by the game engine too
1188 case FX_PERMANENT:
1189 //don't stick around if it was executed permanently
1190 //for example, a permanent strength modifier effect
1191 if( (fx->TimingMode == FX_DURATION_INSTANT_PERMANENT) ) {
1192 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1194 break;
1195 case FX_ABORT:
1196 break;
1197 default:
1198 abort();
1200 } else {
1201 //effect not found, it is going to be discarded
1202 fx->TimingMode = FX_DURATION_JUST_EXPIRED;
1204 return res;
1207 // looks for opcode with param2
1209 #define MATCH_OPCODE() if((*f)->Opcode!=opcode) { continue; }
1211 // useful for: remove equipped item
1212 #define MATCH_SLOTCODE() if((*f)->InventorySlot!=slotcode) { continue; }
1214 // useful for: remove projectile type
1215 #define MATCH_PROJECTILE() if((*f)->Projectile!=projectile) { continue; }
1217 static const bool fx_live[MAX_TIMING_MODE]={true,true,true,false,false,false,false,false,true,true,false};
1218 inline bool IsLive(ieByte timingmode)
1220 if( timingmode>=MAX_TIMING_MODE) return false;
1221 return fx_live[timingmode];
1224 #define MATCH_LIVE_FX() if(!IsLive((*f)->TimingMode)) { continue; }
1225 #define MATCH_PARAM1() if((*f)->Parameter1!=param1) { continue; }
1226 #define MATCH_PARAM2() if((*f)->Parameter2!=param2) { continue; }
1227 #define MATCH_RESOURCE() if( strnicmp( (*f)->Resource, resource, 8) ) { continue; }
1228 #define MATCH_SOURCE() if( strnicmp( (*f)->Source, Removed, 8) ) { continue; }
1229 #define MATCH_TIMING() if( (*f)->TimingMode!=timing) { continue; }
1231 //call this from an applied effect, after it returns, these effects
1232 //will be killed along with it
1233 void EffectQueue::RemoveAllEffects(ieDword opcode) const
1235 std::list< Effect* >::const_iterator f;
1236 for ( f = effects.begin(); f != effects.end(); f++ ) {
1237 MATCH_OPCODE();
1238 MATCH_LIVE_FX();
1240 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1244 //removes all equipping effects that match slotcode
1245 void EffectQueue::RemoveEquippingEffects(ieDwordSigned slotcode) const
1247 std::list< Effect* >::const_iterator f;
1248 for ( f = effects.begin(); f != effects.end(); f++ ) {
1249 if( !IsEquipped((*f)->TimingMode)) continue;
1250 MATCH_SLOTCODE();
1252 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1256 //removes all effects that match projectile
1257 void EffectQueue::RemoveAllEffectsWithProjectile(ieDword projectile) const
1259 std::list< Effect* >::const_iterator f;
1260 for ( f = effects.begin(); f != effects.end(); f++ ) {
1261 MATCH_PROJECTILE();
1263 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1267 //remove effects belonging to a given spell
1268 void EffectQueue::RemoveAllEffects(const ieResRef Removed) const
1270 std::list< Effect* >::const_iterator f;
1271 for ( f = effects.begin(); f != effects.end(); f++ ) {
1272 MATCH_LIVE_FX();
1273 MATCH_SOURCE();
1275 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1279 //remove effects belonging to a given spell, but only if they match timing method x
1280 void EffectQueue::RemoveAllEffects(const ieResRef Removed, ieByte timing) const
1282 std::list< Effect* >::const_iterator f;
1283 for ( f = effects.begin(); f != effects.end(); f++ ) {
1284 MATCH_TIMING();
1285 MATCH_SOURCE();
1287 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1291 //this will modify effect reference
1292 void EffectQueue::RemoveAllEffects(EffectRef &effect_reference) const
1294 ResolveEffectRef(effect_reference);
1295 if( effect_reference.opcode<0) {
1296 return;
1298 RemoveAllEffects(effect_reference.opcode);
1301 //Removes all effects with a matching resource field
1302 void EffectQueue::RemoveAllEffectsWithResource(ieDword opcode, const ieResRef resource) const
1304 std::list< Effect* >::const_iterator f;
1305 for ( f = effects.begin(); f != effects.end(); f++ ) {
1306 MATCH_OPCODE();
1307 MATCH_LIVE_FX();
1308 MATCH_RESOURCE();
1310 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1314 void EffectQueue::RemoveAllEffectsWithResource(EffectRef &effect_reference, const ieResRef resource) const
1316 ResolveEffectRef(effect_reference);
1317 RemoveAllEffectsWithResource(effect_reference.opcode, resource);
1320 //This method could be used to remove stat modifiers that would lower a stat
1321 //(works only if a higher stat means good for the target)
1322 void EffectQueue::RemoveAllDetrimentalEffects(ieDword opcode, ieDword current) const
1324 std::list< Effect* >::const_iterator f;
1325 for ( f = effects.begin(); f != effects.end(); f++ ) {
1326 MATCH_OPCODE();
1327 MATCH_LIVE_FX();
1328 switch((*f)->Parameter2) {
1329 case 0:case 3:
1330 if( ((signed) (*f)->Parameter1)>=0) continue;
1331 break;
1332 case 1:case 4:
1333 if( ((signed) (*f)->Parameter1)>=(signed) current) continue;
1334 break;
1335 case 2:case 5:
1336 if( ((signed) (*f)->Parameter1)>=100) continue;
1337 break;
1338 default:
1339 break;
1341 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1345 //Removes all effects with a matching param2
1346 //param2 is usually an effect's subclass (quality) while param1 is more like quantity.
1347 //So opcode+param2 usually pinpoints an effect better when not all effects of a given
1348 //opcode need to be removed (see removal of portrait icon)
1349 void EffectQueue::RemoveAllEffectsWithParam(ieDword opcode, ieDword param2) const
1351 std::list< Effect* >::const_iterator f;
1352 for ( f = effects.begin(); f != effects.end(); f++ ) {
1353 MATCH_OPCODE();
1354 MATCH_LIVE_FX();
1355 MATCH_PARAM2();
1357 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1361 //this function is called by FakeEffectExpiryCheck
1362 //probably also called by rest
1363 void EffectQueue::RemoveExpiredEffects(ieDword futuretime) const
1365 ieDword GameTime = core->GetGame()->GameTime;
1366 if( GameTime+futuretime*AI_UPDATE_TIME<GameTime) {
1367 GameTime=0xffffffff;
1368 } else {
1369 GameTime+=futuretime*AI_UPDATE_TIME;
1372 std::list< Effect* >::const_iterator f;
1373 for ( f = effects.begin(); f != effects.end(); f++ ) {
1374 //FIXME: how this method handles delayed effects???
1375 //it should remove them as well, i think
1376 if( DelayType( ((*f)->TimingMode) )!=PERMANENT ) {
1377 if( (*f)->Duration<=GameTime) {
1378 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1384 //this effect will expire all effects that are not truly permanent
1385 //which i call permanent after death (iesdp calls it permanent after bonuses)
1386 void EffectQueue::RemoveAllNonPermanentEffects() const
1388 std::list< Effect* >::const_iterator f;
1389 for ( f = effects.begin(); f != effects.end(); f++ ) {
1390 if( IsRemovable((*f)->TimingMode) ) {
1391 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1396 //this will modify effect reference
1398 void EffectQueue::RemoveAllDetrimentalEffects(EffectRef &effect_reference, ieDword current) const
1400 ResolveEffectRef(effect_reference);
1401 RemoveAllDetrimentalEffects(effect_reference.opcode, current);
1404 void EffectQueue::RemoveAllEffectsWithParam(EffectRef &effect_reference, ieDword param2) const
1406 ResolveEffectRef(effect_reference);
1407 RemoveAllEffectsWithParam(effect_reference.opcode, param2);
1410 //remove certain levels of effects, possibly matching school/secondary type
1411 //this method removes whole spells (tied together by their source)
1412 //FIXME: probably this isn't perfect
1413 void EffectQueue::RemoveLevelEffects(ieResRef &Removed, ieDword level, ieDword Flags, ieDword match) const
1415 Removed[0]=0;
1416 std::list< Effect* >::const_iterator f;
1417 for ( f = effects.begin(); f != effects.end(); f++ ) {
1418 if( (*f)->Power>level) {
1419 continue;
1422 if( Removed[0]) {
1423 MATCH_SOURCE();
1425 if( Flags&RL_MATCHSCHOOL) {
1426 if( (*f)->PrimaryType!=match) {
1427 continue;
1430 if( Flags&RL_MATCHSECTYPE) {
1431 if( (*f)->SecondaryType!=match) {
1432 continue;
1435 //if dispellable was not set, or the effect is dispellable
1436 //then remove it
1437 if( Flags&RL_DISPELLABLE) {
1438 if( !((*f)->Resistance&FX_CAN_DISPEL)) {
1439 continue;
1442 (*f)->TimingMode = FX_DURATION_JUST_EXPIRED;
1443 if( Flags&RL_REMOVEFIRST) {
1444 memcpy(Removed,(*f)->Source, sizeof(Removed));
1449 Effect *EffectQueue::HasOpcode(ieDword opcode) const
1451 std::list< Effect* >::const_iterator f;
1452 for ( f = effects.begin(); f != effects.end(); f++ ) {
1453 MATCH_OPCODE();
1454 MATCH_LIVE_FX();
1456 return (*f);
1458 return NULL;
1461 Effect *EffectQueue::HasEffect(EffectRef &effect_reference) const
1463 ResolveEffectRef(effect_reference);
1464 if( effect_reference.opcode<0) {
1465 return NULL;
1467 return HasOpcode(effect_reference.opcode);
1470 Effect *EffectQueue::HasOpcodeWithParam(ieDword opcode, ieDword param2) const
1472 std::list< Effect* >::const_iterator f;
1473 for ( f = effects.begin(); f != effects.end(); f++ ) {
1474 MATCH_OPCODE();
1475 MATCH_LIVE_FX();
1476 MATCH_PARAM2();
1478 return (*f);
1480 return NULL;
1483 Effect *EffectQueue::HasEffectWithParam(EffectRef &effect_reference, ieDword param2) const
1485 ResolveEffectRef(effect_reference);
1486 if( effect_reference.opcode<0) {
1487 return NULL;
1489 return HasOpcodeWithParam(effect_reference.opcode, param2);
1492 //looks for opcode with pairs of parameters (useful for protection against creature, extra damage or extra thac0 against creature)
1493 //generally an IDS targeting
1495 Effect *EffectQueue::HasOpcodeWithParamPair(ieDword opcode, ieDword param1, ieDword param2) const
1497 std::list< Effect* >::const_iterator f;
1498 for ( f = effects.begin(); f != effects.end(); f++ ) {
1499 MATCH_OPCODE();
1500 MATCH_LIVE_FX();
1501 MATCH_PARAM2();
1502 //0 is always accepted as first parameter
1503 if( param1) {
1504 MATCH_PARAM1();
1507 return (*f);
1509 return NULL;
1512 Effect *EffectQueue::HasEffectWithParamPair(EffectRef &effect_reference, ieDword param1, ieDword param2) const
1514 ResolveEffectRef(effect_reference);
1515 if( effect_reference.opcode<0) {
1516 return NULL;
1518 return HasOpcodeWithParamPair(effect_reference.opcode, param1, param2);
1521 // sums all the values of the specific damage bonus effects of the passed "damage type"
1522 int EffectQueue::SpecificDamageBonus(ieDword opcode, ieDword param2) const
1524 int bonus = 0;
1525 std::list< Effect* >::const_iterator f;
1526 for ( f = effects.begin(); f != effects.end(); f++ ) {
1527 MATCH_OPCODE();
1528 MATCH_LIVE_FX();
1529 MATCH_PARAM2();
1530 bonus += (signed) (*f)->Parameter1;
1532 return bonus;
1535 static EffectRef fx_damage_bonus_modifier_ref={"DamageBonusModifier",NULL,-1};
1536 int EffectQueue::SpecificDamageBonus(ieDword damage_type) const
1538 ResolveEffectRef(fx_damage_bonus_modifier_ref);
1539 if(fx_damage_bonus_modifier_ref.opcode < 0) {
1540 return 0;
1542 return SpecificDamageBonus(fx_damage_bonus_modifier_ref.opcode, damage_type);
1545 //this could be used for stoneskins and mirror images as well
1546 void EffectQueue::DecreaseParam1OfEffect(ieDword opcode, ieDword amount) const
1548 std::list< Effect* >::const_iterator f;
1549 for ( f = effects.begin(); f != effects.end(); f++ ) {
1550 MATCH_OPCODE();
1551 MATCH_LIVE_FX();
1552 ieDword value = (*f)->Parameter1;
1553 if( value>amount) value-=amount;
1554 else value = 0;
1555 (*f)->Parameter1=value;
1559 void EffectQueue::DecreaseParam1OfEffect(EffectRef &effect_reference, ieDword amount) const
1561 ResolveEffectRef(effect_reference);
1562 if( effect_reference.opcode<0) {
1563 return;
1565 DecreaseParam1OfEffect(effect_reference.opcode, amount);
1569 //this function does IDS targeting for effects (extra damage/thac0 against creature)
1570 static const int ids_stats[7]={IE_EA, IE_GENERAL, IE_RACE, IE_CLASS, IE_SPECIFIC, IE_SEX, IE_ALIGNMENT};
1572 int EffectQueue::BonusAgainstCreature(ieDword opcode, Actor *actor) const
1574 int sum = 0;
1575 std::list< Effect* >::const_iterator f;
1576 for ( f = effects.begin(); f != effects.end(); f++ ) {
1577 MATCH_OPCODE();
1578 MATCH_LIVE_FX();
1579 if( (*f)->Parameter1) {
1580 ieDword ids = (*f)->Parameter2;
1581 if( ids<2 || ids>8) {
1582 ids=2;
1584 ieDword param1 = actor->GetStat(ids_stats[ids-2]);
1585 MATCH_PARAM1();
1587 int val = (int) (*f)->Parameter3;
1588 if( !val) val = 2;
1589 sum += val;
1591 return sum;
1594 int EffectQueue::BonusAgainstCreature(EffectRef &effect_reference, Actor *actor) const
1596 ResolveEffectRef(effect_reference);
1597 if( effect_reference.opcode<0) {
1598 return 0;
1600 return BonusAgainstCreature(effect_reference.opcode, actor);
1603 bool EffectQueue::WeaponImmunity(ieDword opcode, int enchantment, ieDword weapontype) const
1605 std::list< Effect* >::const_iterator f;
1606 for ( f = effects.begin(); f != effects.end(); f++ ) {
1607 MATCH_OPCODE();
1608 MATCH_LIVE_FX();
1610 int magic = (int) (*f)->Parameter1;
1611 ieDword mask = (*f)->Parameter3;
1612 ieDword value = (*f)->Parameter4;
1613 if( magic==0) {
1614 if( enchantment) continue;
1615 } else if( magic>0) {
1616 if( enchantment>magic) continue;
1619 if( (weapontype&mask) != value) {
1620 continue;
1622 return true;
1624 return false;
1627 static EffectRef fx_weapon_immunity_ref={"Protection:Weapons",NULL,-1};
1629 bool EffectQueue::WeaponImmunity(int enchantment, ieDword weapontype) const
1631 ResolveEffectRef(fx_weapon_immunity_ref);
1632 if( fx_weapon_immunity_ref.opcode<0) {
1633 return 0;
1635 return WeaponImmunity(fx_weapon_immunity_ref.opcode, enchantment, weapontype);
1638 static EffectRef fx_disable_spellcasting_ref={ "DisableCasting", NULL, -1 };
1639 int EffectQueue::DisabledSpellcasting(int types) const
1641 ResolveEffectRef(fx_disable_spellcasting_ref);
1642 if( fx_disable_spellcasting_ref.opcode < 0) {
1643 return 0;
1646 unsigned int spelltype_mask = 0;
1647 bool iwd2 = !!core->HasFeature(GF_ENHANCED_EFFECTS);
1648 ieDword opcode = fx_disable_spellcasting_ref.opcode;
1649 std::list< Effect* >::const_iterator f;
1650 for ( f = effects.begin(); f != effects.end(); f++ ) {
1651 MATCH_OPCODE();
1652 MATCH_LIVE_FX();
1654 if (iwd2) {
1655 switch((*f)->Parameter2) {
1656 case 0: // all
1657 spelltype_mask |= 7;
1658 break;
1659 case 1: // mage and cleric
1660 spelltype_mask |= 3;
1661 break;
1662 case 2: // mage
1663 spelltype_mask |= 2;
1664 break;
1665 case 3: // cleric
1666 spelltype_mask |= 1;
1667 break;
1668 case 4: // innate
1669 spelltype_mask |= 4;
1670 break;
1672 } else {
1673 switch((*f)->Parameter2) {
1674 case 0: // mage
1675 spelltype_mask |= 2;
1676 break;
1677 case 1: // cleric
1678 spelltype_mask |= 1;
1679 break;
1680 case 2: // innate
1681 spelltype_mask |= 4;
1682 break;
1686 return spelltype_mask & types;
1689 //useful for immunity vs spell, can't use item, etc.
1690 Effect *EffectQueue::HasOpcodeWithResource(ieDword opcode, const ieResRef resource) const
1692 std::list< Effect* >::const_iterator f;
1693 for ( f = effects.begin(); f != effects.end(); f++ ) {
1694 MATCH_OPCODE();
1695 MATCH_LIVE_FX();
1696 MATCH_RESOURCE();
1698 return (*f);
1700 return NULL;
1703 Effect *EffectQueue::HasEffectWithResource(EffectRef &effect_reference, const ieResRef resource) const
1705 ResolveEffectRef(effect_reference);
1706 return HasOpcodeWithResource(effect_reference.opcode, resource);
1709 //used in contingency/sequencer code (cannot have the same contingency twice)
1710 Effect *EffectQueue::HasOpcodeWithSource(ieDword opcode, const ieResRef Removed) const
1712 std::list< Effect* >::const_iterator f;
1713 for ( f = effects.begin(); f != effects.end(); f++ ) {
1714 MATCH_OPCODE();
1715 MATCH_LIVE_FX();
1716 MATCH_SOURCE();
1718 return (*f);
1720 return NULL;
1723 Effect *EffectQueue::HasEffectWithSource(EffectRef &effect_reference, const ieResRef resource) const
1725 ResolveEffectRef(effect_reference);
1726 return HasOpcodeWithSource(effect_reference.opcode, resource);
1729 bool EffectQueue::HasAnyDispellableEffect() const
1731 std::list< Effect* >::const_iterator f;
1732 for ( f = effects.begin(); f != effects.end(); f++ ) {
1733 if( (*f)->Resistance&FX_CAN_DISPEL) {
1734 return true;
1737 return false;
1740 void EffectQueue::dump() const
1742 printf( "EFFECT QUEUE:\n" );
1743 int i = 0;
1744 std::list< Effect* >::const_iterator f;
1745 for ( f = effects.begin(); f != effects.end(); f++ ) {
1746 Effect* fx = *f;
1747 if( fx) {
1748 char *Name = NULL;
1749 if( fx->Opcode < MAX_EFFECTS)
1750 Name = (char*) Opcodes[fx->Opcode].Name;
1752 printf( " %2d: 0x%02x: %s (%d, %d) S:%s\n", i++, fx->Opcode, Name, fx->Parameter1, fx->Parameter2, fx->Source );
1757 Effect *EffectQueue::GetEffect(ieDword idx) const
1759 if( effects.size()<=idx) {
1760 return NULL;
1762 return effects[idx];
1766 //returns true if the effect supports simplified duration
1767 bool EffectQueue::HasDuration(Effect *fx)
1769 switch(fx->TimingMode) {
1770 case FX_DURATION_INSTANT_LIMITED: //simple duration
1771 case FX_DURATION_DELAY_LIMITED: //delayed duration
1772 case FX_DURATION_DELAY_PERMANENT: //simple delayed
1773 return true;
1775 return false;
1778 static EffectRef fx_variable_ref={"Variable:StoreLocalVariable",NULL,-1};
1780 //returns true if the effect must be saved
1781 //variables are saved differently
1782 bool EffectQueue::Persistent(Effect* fx)
1784 //we save this as variable
1785 if( fx->Opcode==(ieDword) ResolveEffect(fx_variable_ref)) {
1786 return false;
1789 switch (fx->TimingMode) {
1790 //normal equipping fx of items
1791 case FX_DURATION_INSTANT_WHILE_EQUIPPED:
1792 //delayed effect not saved
1793 case FX_DURATION_DELAY_UNSAVED:
1794 //permanent effect not saved
1795 case FX_DURATION_PERMANENT_UNSAVED:
1796 //just expired effect
1797 case FX_DURATION_JUST_EXPIRED:
1798 return false;
1800 return true;
1803 //alter the color effect in case the item is equipped in the shield slot
1804 void EffectQueue::HackColorEffects(Actor *Owner, Effect *fx)
1806 if( fx->InventorySlot!=Owner->inventory.GetShieldSlot()) return;
1808 unsigned int gradienttype = fx->Parameter2 & 0xF0;
1809 if( gradienttype == 0x10) {
1810 gradienttype = 0x20; // off-hand
1811 fx->Parameter2 &= ~0xF0;
1812 fx->Parameter2 |= gradienttype;
1816 //iterate through saved effects
1817 const Effect *EffectQueue::GetNextSavedEffect(std::list< Effect* >::const_iterator &f) const
1819 while(f!=effects.end()) {
1820 Effect *effect = *f;
1821 f++;
1822 if( Persistent(effect)) {
1823 return effect;
1826 return NULL;
1829 Effect *EffectQueue::GetNextEffect(std::list< Effect* >::const_iterator &f) const
1831 if( f!=effects.end()) return *f++;
1832 return NULL;
1835 ieDword EffectQueue::CountEffects(ieDword opcode, ieDword param1, ieDword param2, const char *resource) const
1837 ieDword cnt = 0;
1839 std::list< Effect* >::const_iterator f;
1841 for ( f = effects.begin(); f != effects.end(); f++ ) {
1842 MATCH_OPCODE();
1843 if( param1!=0xffffffff)
1844 MATCH_PARAM1();
1845 if( param2!=0xffffffff)
1846 MATCH_PARAM2();
1847 if( resource) {
1848 MATCH_RESOURCE();
1850 cnt++;
1852 return cnt;
1855 void EffectQueue::ModifyEffectPoint(ieDword opcode, ieDword x, ieDword y) const
1857 std::list< Effect* >::const_iterator f;
1859 for ( f = effects.begin(); f != effects.end(); f++ ) {
1860 MATCH_OPCODE();
1861 (*f)->PosX=x;
1862 (*f)->PosY=y;
1863 (*f)->Parameter3=0;
1864 return;
1868 //count effects that get saved
1869 ieDword EffectQueue::GetSavedEffectsCount() const
1871 ieDword cnt = 0;
1873 std::list< Effect* >::const_iterator f;
1875 for ( f = effects.begin(); f != effects.end(); f++ ) {
1876 Effect* fx = *f;
1877 if( Persistent(fx))
1878 cnt++;
1880 return cnt;
1883 void EffectQueue::TransformToDelay(ieByte &TimingMode)
1885 if( TimingMode<MAX_TIMING_MODE) {;
1886 TimingMode = fx_to_delayed[TimingMode];
1887 } else {
1888 TimingMode = FX_DURATION_JUST_EXPIRED;
1892 int EffectQueue::ResolveEffect(EffectRef &effect_reference)
1894 ResolveEffectRef(effect_reference);
1895 return effect_reference.opcode;
1898 // this check goes for the whole effect block, not individual effects
1899 // But it takes the first effect of the block for the common fields
1901 //returns 1 if effect block applicable
1902 //returns 0 if effect block disabled
1903 //returns -1 if effect block bounced
1904 int EffectQueue::CheckImmunity(Actor *target) const
1906 //don't resist if target is non living
1907 if( !target) {
1908 return 1;
1911 if( effects.size() ) {
1912 Effect* fx = *effects.begin();
1914 //projectile immunity
1915 if( target->ImmuneToProjectile(fx->Projectile)) return 0;
1917 //don't resist item projectile payloads based on spell school, bounce, etc.
1918 if( fx->InventorySlot) {
1919 return 1;
1922 //check level resistances
1923 //check specific spell immunity
1924 //check school/sectype immunity
1925 return check_type(target, fx);
1927 return 0;
1930 void EffectQueue::AffectAllInRange(Map *map, const Point &pos, int idstype, int idsvalue,
1931 unsigned int range, Actor *except)
1933 int cnt = map->GetActorCount(true);
1934 while(cnt--) {
1935 Actor *actor = map->GetActor(cnt,true);
1936 if( except==actor) {
1937 continue;
1939 //distance
1940 if( Distance(pos, actor)>range) {
1941 continue;
1943 //ids targeting
1944 if( !match_ids(actor, idstype, idsvalue)) {
1945 continue;
1947 //line of sight
1948 if( !map->IsVisible(actor->Pos, pos)) {
1949 continue;
1951 AddAllEffects(actor, actor->Pos);