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 //This class represents the .cre (creature) files.
22 //Any player or non-player character is a creature.
23 //Actor is a scriptable object (Scriptable). See ActorBlock.cpp
25 #include "Scriptable/Actor.h"
29 #include "opcode_params.h"
32 #include "Audio.h" //pst (react to death sounds)
35 #include "DisplayMessage.h"
36 #include "Interface.h"
38 #include "Projectile.h"
39 #include "ProjectileServer.h"
40 #include "ScriptEngine.h"
45 #include "GameScript/GSUtils.h" //needed for DisplayStringCore
46 #include "GameScript/GameScript.h"
47 #include "GUI/GameControl.h" //checking for dialog
48 #include "PolymorphCache.h" // stupid polymorph cache hack
53 ieDword ref_lightness
= 43;
55 static const Color white
= {
56 0xff, 0xff, 0xff, 0xff
58 static const Color green
= {
59 0x00, 0xff, 0x00, 0xff
61 static const Color red
= {
62 0xff, 0x00, 0x00, 0xff
64 static const Color yellow
= {
65 0xff, 0xff, 0x00, 0xff
67 static const Color cyan
= {
68 0x00, 0xff, 0xff, 0xff
70 static const Color magenta
= {
71 0xff, 0x00, 0xff, 0xff
74 static int sharexp
= SX_DIVIDE
;
75 static int classcount
= -1;
76 static int extraslots
= -1;
77 static char **clericspelltables
= NULL
;
78 static char **druidspelltables
= NULL
;
79 static char **wizardspelltables
= NULL
;
80 static char **classabilities
= NULL
;
81 static int *turnlevels
= NULL
;
82 static int *booktypes
= NULL
;
83 static int *xpbonus
= NULL
;
84 static int xpbonustypes
= -1;
85 static int xpbonuslevels
= -1;
86 static int **levelslots
= NULL
;
87 static int *dualswap
= NULL
;
88 static int *maxhpconbon
= NULL
;
89 static ieVariable CounterNames
[4]={"GOOD","LAW","LADY","MURDER"};
91 static int FistRows
= -1;
93 typedef ieResRef FistResType
[MAX_LEVEL
+1];
95 static FistResType
*fistres
= NULL
;
96 static ieResRef DefaultFist
= {"FIST"};
98 static int VCMap
[VCONST_COUNT
];
100 //item usability array
102 ieResRef table
; //which table contains the stat usability flags
103 ieByte stat
; //which actor stat we talk about
104 ieByte mcol
; //which column should be matched against the stat
105 ieByte vcol
; //which column has the bit value for it
106 ieByte which
; //which item dword should be used (1 = kit)
109 static ItemUseType
*itemuse
= NULL
;
110 static int usecount
= -1;
112 //item animation override array
113 struct ItemAnimType
{
118 static ItemAnimType
*itemanim
= NULL
;
119 static int animcount
= -1;
121 static int fiststat
= IE_CLASS
;
123 //conversion for 3rd ed
124 static int isclass
[11]={0,0,0,0,0,0,0,0,0,0,0};
126 static const int mcwasflags
[11] = {
127 MC_WAS_FIGHTER
, MC_WAS_MAGE
, MC_WAS_THIEF
, 0, 0, MC_WAS_CLERIC
,
128 MC_WAS_DRUID
, 0, 0, MC_WAS_RANGER
, 0};
129 static const char *isclassnames
[11] = {
130 "FIGHTER", "MAGE", "THIEF", "BARBARIAN", "BARD", "CLERIC",
131 "DRUID", "MONK", "PALADIN", "RANGER", "SORCERER" };
133 //fighter is the default level here
134 //fixme, make this externalized
135 static const int levelslotsbg
[21]={ISFIGHTER
, ISMAGE
, ISFIGHTER
, ISCLERIC
, ISTHIEF
,
136 ISBARD
, ISPALADIN
, 0, 0, 0, 0, ISDRUID
, ISRANGER
, 0,0,0,0,0,0,ISSORCERER
, ISMONK
};
137 static const int levelslotsiwd2
[11]={IE_LEVELFIGHTER
,IE_LEVELMAGE
,IE_LEVELTHIEF
,
138 IE_LEVELBARBARIAN
,IE_LEVELBARD
,IE_LEVELCLERIC
,IE_LEVELDRUID
,IE_LEVELMONK
,
139 IE_LEVELPALADIN
,IE_LEVELRANGER
,IE_LEVELSORCEROR
};
141 //stat values are 0-255, so a byte is enough
142 static ieByte featstats
[MAX_FEATS
]={0
145 //holds the wspecial table for weapon prof bonuses
146 #define WSPECIAL_COLS 3
147 static int wspecial_max
= 0;
148 static int wspattack_rows
= 0;
149 static int wspattack_cols
= 0;
150 static int **wspecial
= NULL
;
151 static int **wspattack
= NULL
;
153 //holds the weapon style bonuses
155 static int **wsdualwield
= NULL
;
156 static int **wstwohanded
= NULL
;
157 static int **wsswordshield
= NULL
;
158 static int **wssingle
= NULL
;
160 //unhardcoded monk bonuses
161 static int **monkbon
= NULL
;
162 static unsigned int monkbon_cols
= 0;
163 static unsigned int monkbon_rows
= 0;
165 // reputation modifiers
166 #define CLASS_PCCUTOFF 32
167 #define CLASS_INNOCENT 155
168 #define CLASS_FLAMINGFIST 156
170 static ActionButtonRow
*GUIBTDefaults
= NULL
; //qslots row count
171 static ActionButtonRow2
*OtherGUIButtons
= NULL
;
172 ActionButtonRow DefaultButtons
= {ACT_TALK
, ACT_WEAPON1
, ACT_WEAPON2
,
173 ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
,
174 ACT_NONE
, ACT_INNATE
};
175 static int QslotTranslation
= false;
176 static int DeathOnZeroStat
= true;
177 static ieDword TranslucentShadows
= 0;
178 static int ProjectileSize
= 0; //the size of the projectile immunity bitfield (dwords)
180 static const char iwd2gemrb
[32] = {
182 15,23,13,0,1,24,8,21,
186 static const char gemrb2iwd
[32] = {
187 11,12,3,71,72,73,0,0, //0
188 14,80,83,82,81,10,7,8, //8
189 0,0,0,0,2,15,4,9, //16
190 13,5,0,0,0,0,0,0 //24
193 //letters for char sound resolution bg1/bg2
194 static char csound
[VCONST_COUNT
];
196 static void InitActorTables();
198 #define DAMAGE_LEVELS 19
199 #define ATTACKROLL 20
204 #define TURN_PANIC_LVL_MOD 3
205 #define TURN_DEATH_LVL_MOD 7
207 static ieResRef d_main
[DAMAGE_LEVELS
] = {
208 //slot 0 is not used in the original engine
209 "BLOODCR","BLOODS","BLOODM","BLOODL", //blood
210 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //fire
211 "SPSHKIMP","SPSHKIMP","SPSHKIMP", //spark
212 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //ice
213 "SHACID","SHACID","SHACID", //acid
214 "SPDUSTY2","SPDUSTY2","SPDUSTY2" //disintegrate
216 static ieResRef d_splash
[DAMAGE_LEVELS
] = {
218 "SPBURN","SPBURN","SPBURN", //flames
219 "SPSPARKS","SPSPARKS","SPSPARKS", //sparks
225 #define BLOOD_GRADIENT 19
226 #define FIRE_GRADIENT 19
227 #define ICE_GRADIENT 71
228 #define STONE_GRADIENT 93
230 static int d_gradient
[DAMAGE_LEVELS
] = {
231 BLOOD_GRADIENT
,BLOOD_GRADIENT
,BLOOD_GRADIENT
,BLOOD_GRADIENT
,
232 FIRE_GRADIENT
,FIRE_GRADIENT
,FIRE_GRADIENT
,
234 ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,
239 static ieResRef hc_overlays
[OVERLAY_COUNT
]={"SANCTRY","SPENTACI","SPMAGGLO","SPSHIELD",
240 "GREASED","WEBENTD","MINORGLB","","","","","","","","","","","","","","",
241 "","","","SPTURNI2","SPTURNI","","","","","",""};
242 static ieDword hc_locations
=0x2ba80030;
244 static int *mxsplwis
= NULL
;
245 static int spllevels
;
247 //for every game except IWD2 we need to reverse TOHIT
248 static int ReverseToHit
=true;
249 static int CheckAbilities
=false;
251 //internal flags for calculating to hit
252 #define WEAPON_FIST 0
253 #define WEAPON_MELEE 1
254 #define WEAPON_RANGED 2
255 #define WEAPON_STYLEMASK 15
256 #define WEAPON_LEFTHAND 16
257 #define WEAPON_USESTRENGTH 32
259 /* counts the on bits in a number */
260 ieDword
bitcount (ieDword n
)
270 void ReleaseMemoryActor()
273 //calloc'd x*y integer matrix
296 : Movable( ST_ACTOR
)
300 for (i
= 0; i
< MAX_STATS
; i
++) {
305 SmallPortrait
[0] = 0;
306 LargePortrait
[0] = 0;
312 for (i
= 0; i
< EXTRA_ACTORCOVERS
; ++i
)
313 extraCovers
[i
] = NULL
;
317 LongStrRef
= (ieStrRef
) -1;
318 ShortStrRef
= (ieStrRef
) -1;
329 LastCommand
= 0; //used by order
330 LastShout
= 0; //used by heard
342 attackProjectile
= NULL
;
347 inventory
.SetInventoryType(INVENTORY_CREATURE
);
351 fxqueue
.SetOwner( this );
352 inventory
.SetOwner( this );
354 //This block is executed only once, when the first actor is loaded
357 TranslucentShadows
= 0;
358 core
->GetDictionary()->Lookup("Translucent Shadows", TranslucentShadows
);
359 //get the needed size to store projectile immunity bitflags in Dwords
360 ProjectileSize
= (core
->GetProjectileServer()->GetHighestProjectileNumber()+31)/32;
361 //allowing 1024 bits (1024 projectiles ought to be enough for everybody)
362 //the rest of the projectiles would still work, but couldn't be resisted
363 if (ProjectileSize
>32) {
367 projectileImmunity
= (ieDword
*) calloc(ProjectileSize
,sizeof(ieDword
));
369 SetDeathVar
= IncKillCount
= UnknownField
= 0;
370 memset( DeathCounters
, 0, sizeof(DeathCounters
) );
373 InteractCount
= 0; //numtimesinteracted depends on this
374 appearance
= 0xffffff; //might be important for created creatures
377 //these are used only in iwd2 so we have to default them
379 BaseStats
[IE_HATEDRACE2
+i
]=0xff;
381 //this one is saved only for PC's
383 //set it to a neutral value
385 //this one is saved, but not loaded?
386 localID
= globalID
= 0;
387 //this one is not saved
388 GotLUFeedback
= false;
391 polymorphCache
= NULL
;
400 core
->FreeString( LongName
);
401 core
->FreeString( ShortName
);
405 for (i
= 0; i
< vvcOverlays
.size(); i
++) {
406 if (vvcOverlays
[i
]) {
407 delete vvcOverlays
[i
];
408 vvcOverlays
[i
] = NULL
;
411 for (i
= 0; i
< vvcShields
.size(); i
++) {
413 delete vvcShields
[i
];
414 vvcShields
[i
] = NULL
;
417 for (i
= 0; i
< EXTRA_ACTORCOVERS
; i
++)
418 delete extraCovers
[i
];
420 delete attackProjectile
;
421 delete polymorphCache
;
423 free(projectileImmunity
);
426 void Actor::SetFistStat(ieDword stat
)
431 void Actor::SetDefaultActions(int qslot
, ieByte slot1
, ieByte slot2
, ieByte slot3
)
433 QslotTranslation
=qslot
;
434 DefaultButtons
[0]=slot1
;
435 DefaultButtons
[1]=slot2
;
436 DefaultButtons
[2]=slot3
;
439 void Actor::SetName(const char* ptr
, unsigned char type
)
441 size_t len
= strlen( ptr
) + 1;
442 //32 is the maximum possible length of the actor name in the original games
445 LongName
= ( char * ) realloc( LongName
, len
);
446 memcpy( LongName
, ptr
, len
);
447 core
->StripLine( LongName
, len
);
450 ShortName
= ( char * ) realloc( ShortName
, len
);
451 memcpy( ShortName
, ptr
, len
);
452 core
->StripLine( ShortName
, len
);
456 void Actor::SetName(int strref
, unsigned char type
)
459 if (LongName
) free(LongName
);
460 LongName
= core
->GetString( strref
, IE_STR_REMOVE_NEWLINE
);
463 if (ShortName
) free(ShortName
);
464 ShortName
= core
->GetString( strref
, IE_STR_REMOVE_NEWLINE
);
468 void Actor::SetAnimationID(unsigned int AnimID
)
470 //if the palette is locked, then it will be transferred to the new animation
471 Palette
*recover
= NULL
;
474 if (anims
->lockPalette
) {
475 recover
= anims
->palette
[PAL_MAIN
];
477 // Take ownership so the palette won't be deleted
483 //hacking PST no palette
484 if (core
->HasFeature(GF_ONE_BYTE_ANIMID
) ) {
485 if ((AnimID
&0xf000)==0xe000) {
486 if (BaseStats
[IE_COLORCOUNT
]) {
487 printMessage("Actor"," ",YELLOW
);
488 printf("Animation ID %x is supposed to be real colored (no recoloring), patched creature\n", AnimID
);
490 BaseStats
[IE_COLORCOUNT
]=0;
493 anims
= new CharAnimations( AnimID
&0xffff, BaseStats
[IE_ARMOR_TYPE
]);
494 if(anims
->ResRef
[0] == 0) {
497 printMessage("Actor", " ",LIGHT_RED
);
498 printf("Missing animation for %s\n",LongName
);
501 anims
->SetOffhandRef(ShieldRef
);
502 anims
->SetHelmetRef(HelmetRef
);
503 anims
->SetWeaponRef(WeaponRef
);
505 //if we have a recovery palette, then set it back
506 assert(anims
->palette
[PAL_MAIN
] == 0);
507 anims
->palette
[PAL_MAIN
] = recover
;
509 anims
->lockPalette
= true;
511 //bird animations are not hindered by searchmap
512 //only animtype==7 (bird) uses this feature
513 //this is a hardcoded hack, but works for all engine type
514 if (anims
->GetAnimType()!=IE_ANI_BIRD
) {
515 BaseStats
[IE_DONOTJUMP
]=0;
517 BaseStats
[IE_DONOTJUMP
]=DNJ_BIRD
;
520 anims
->SetColors(BaseStats
+IE_COLORS
);
522 //Speed is determined by the number of frames in each cycle of its animation
523 // (beware! GetAnimation has side effects!)
524 // TODO: we should have a more efficient way to look this up
525 Animation
** anim
= anims
->GetAnimation(IE_ANI_WALK
, 0);
526 if (anim
&& anim
[0]) {
527 SetBase(IE_MOVEMENTRATE
, anim
[0]->GetFrameCount()) ;
529 printMessage("Actor", "Unable to determine movement rate for animation ", YELLOW
);
530 printf("%04x!\n", AnimID
);
535 CharAnimations
* Actor::GetAnims()
540 /** Returns a Stat value (Base Value + Mod) */
541 ieDword
Actor::GetStat(unsigned int StatIndex
) const
543 if (StatIndex
>= MAX_STATS
) {
546 return Modified
[StatIndex
];
549 void Actor::SetCircleSize()
557 GameControl
*gc
= core
->GetGameControl();
558 if (UnselectableTimer
) {
561 } else if (Modified
[IE_STATE_ID
] & STATE_PANIC
) {
564 } else if (gc
&& gc
->targetID
== globalID
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
)) {
566 color_index
= 3; //?? made up
568 switch (Modified
[IE_EA
]) {
574 case EA_EVILBUTGREEN
:
593 int csize
= anims
->GetCircleSize() - 1;
594 if (csize
>= MAX_CIRCLE_SIZE
)
595 csize
= MAX_CIRCLE_SIZE
- 1;
597 SetCircle( anims
->GetCircleSize(), *color
, core
->GroundCircles
[csize
][color_index
], core
->GroundCircles
[csize
][(color_index
== 0) ? 3 : color_index
] );
600 void ApplyClab(Actor
*actor
, const char *clab
, int level
, bool remove
)
602 AutoTable
table(clab
);
604 int row
= table
->GetRowCount();
605 for(int i
=0;i
<level
;i
++) {
606 for (int j
=0;j
<row
;j
++) {
607 const char *res
= table
->QueryField(j
,i
);
608 if (res
[0]=='*') continue;
610 if (!memcmp(res
,"AP_",3)) {
612 actor
->fxqueue
.RemoveAllEffects(res
+3);
614 core
->ApplySpell(res
+3, actor
, actor
, 0);
617 else if (!memcmp(res
,"GA_",3)) {
619 actor
->spellbook
.RemoveSpell(res
+3);
621 actor
->LearnSpell(res
+3, LS_MEMO
);
624 else if (!memcmp(res
,"FA_",3)) {//iwd2 only
628 ResolveSpellName(resref
, x
);
629 actor
->LearnSpell(resref
, LS_MEMO
);
631 else if (!memcmp(res
,"FS_",3)) {//iwd2 only
632 //don't memorize these
635 ResolveSpellName(resref
, x
);
636 actor
->LearnSpell(resref
, 0);
638 else if (!memcmp(res
,"RA_",3)) {//iwd2 only
641 actor
->spellbook
.RemoveSpell(x
);
648 #define BG2_KITMASK 0xffffc000
649 #define KIT_BARBARIAN 0x4000
650 #define KIT_BASECLASS 0x40000000
652 //applies a kit on the character (only bg2)
653 bool Actor::ApplyKit(ieDword Value
, bool remove
)
655 AutoTable
table("kitlist");
661 //find row by unusability
662 row
= table
->GetRowCount();
665 ieDword Unusability
= (ieDword
) strtol(table
->QueryField(row
, 6),NULL
,0);
666 if (Value
== Unusability
) {
670 //if it wasn't found, try the bg2 kit format
671 if ((Value
&BG2_KITMASK
)==KIT_BARBARIAN
) {
675 if (table
->GetRowCount()<=row
) {
680 ieDword cls
= (ieDword
) atoi(table
->QueryField(row
, 7));
681 if (!cls
|| cls
>=sizeof(levelslotsbg
) ) {
684 const char *clab
= table
->QueryField(row
, 4);
690 ieDword max
= GetClassLevel(levelslotsbg
[cls
]);
694 ApplyClab(this, clab
, max
, true);
696 ApplyClab(this, clab
, max
, true);
697 ApplyClab(this, clab
, max
, false);
703 void Actor::ApplyClassClab(bool remove
)
708 for(int i
=1;(i
<32) && (msk
<=multiclass
);i
++) {
709 if (multiclass
& msk
) {
710 ApplyClassClab(i
, remove
);
717 ApplyClassClab((int) GetBase(IE_CLASS
), remove
);
720 void Actor::ApplyClassClab(int cls
, bool remove
)
723 return; //can this happen?
726 if (cls
>classcount
) {
729 const char *clab
= classabilities
[cls
];
731 ieDword max
= GetClassLevel(levelslotsbg
[cls
]);
735 ApplyClab(this, clab
, max
, true);
737 ApplyClab(this, clab
, max
, true);
738 ApplyClab(this, clab
, max
, false);
744 //call this when morale or moralebreak changed
745 //cannot use old or new value, because it is called two ways
746 void pcf_morale (Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
748 if ((actor
->Modified
[IE_MORALE
]<=actor
->Modified
[IE_MORALEBREAK
]) && (actor
->Modified
[IE_MORALEBREAK
] != 0) ) {
752 actor
->SetCircleSize();
755 void pcf_ea (Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
757 if (actor
->Selected
&& (newValue
>EA_GOODCUTOFF
) ) {
758 core
->GetGame()->SelectActor(actor
, false, SELECT_NORMAL
);
760 actor
->SetCircleSize();
763 //this is a good place to recalculate level up stuff
764 void pcf_level (Actor
*actor
, ieDword oldValue
, ieDword newValue
)
767 actor
->GetFighterLevel()+
768 actor
->GetMageLevel()+
769 actor
->GetThiefLevel()+
770 actor
->GetBarbarianLevel()+
771 actor
->GetBardLevel()+
772 actor
->GetClericLevel()+
773 actor
->GetDruidLevel()+
774 actor
->GetMonkLevel()+
775 actor
->GetPaladinLevel()+
776 actor
->GetRangerLevel()+
777 actor
->GetSorcererLevel();
778 actor
->SetBase(IE_CLASSLEVELSUM
,sum
);
780 if (newValue
!=oldValue
) {
781 actor
->ApplyKit(actor
->GetBase(IE_KIT
), false);
782 actor
->ApplyClassClab(false);
784 actor
->GotLUFeedback
= false;
787 void pcf_class (Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
789 actor
->InitButtons(newValue
, false);
792 if (newValue
<(ieDword
) classcount
) {
793 switch(booktypes
[newValue
]) {
794 case 2: sorcerer
= 1<<IE_SPELL_TYPE_WIZARD
; break;
795 case 3: sorcerer
= 1<<IE_SPELL_TYPE_PRIEST
; break;
799 actor
->spellbook
.SetBookType(sorcerer
);
802 void pcf_animid(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
804 actor
->SetAnimationID(newValue
);
807 static const ieDword fullwhite
[7]={ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
};
809 static const ieDword fullstone
[7]={STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
};
811 void pcf_state(Actor
*actor
, ieDword
/*oldValue*/, ieDword State
)
813 if (actor
->InParty
) core
->SetEventFlag(EF_PORTRAIT
);
814 if (State
& STATE_PETRIFIED
) {
815 actor
->SetLockedPalette(fullstone
);
818 if (State
& STATE_FROZEN
) {
819 actor
->SetLockedPalette(fullwhite
);
822 //it is not enough to check the new state
823 core
->GetGame()->Infravision();
824 actor
->UnlockPalette();
827 //changes based on extended state bits, right now it is only the seven eyes
828 //animation (used in how/iwd2)
829 void pcf_extstate(Actor
*actor
, ieDword oldValue
, ieDword State
)
831 if ((oldValue
^State
)&EXTSTATE_SEVEN_EYES
) {
832 ieDword mask
= EXTSTATE_EYE_MIND
;
834 for (int i
=0;i
<7;i
++)
836 if (State
&mask
) eyeCount
--;
839 ScriptedAnimation
*sca
= actor
->FindOverlay(OV_SEVENEYES
);
841 sca
->SetOrientation(eyeCount
);
843 sca
= actor
->FindOverlay(OV_SEVENEYES2
);
845 sca
->SetOrientation(eyeCount
);
850 void pcf_hitpoint(Actor
*actor
, ieDword
/*oldValue*/, ieDword hp
)
852 if ((signed) actor
->BaseStats
[IE_HITPOINTS
]>(signed) actor
->Modified
[IE_MAXHITPOINTS
]) {
853 actor
->BaseStats
[IE_HITPOINTS
]=actor
->Modified
[IE_MAXHITPOINTS
];
856 int hptmp
= (signed) actor
->Modified
[IE_MAXHITPOINTS
];
857 if ((signed) hp
>hptmp
) {
861 hptmp
= (signed) actor
->Modified
[IE_MINHITPOINTS
];
862 if (hptmp
&& (signed) hp
<hptmp
) {
865 if ((signed) hp
<=0) {
868 actor
->BaseStats
[IE_HITPOINTS
]=hp
;
869 actor
->Modified
[IE_HITPOINTS
]=hp
;
870 if (actor
->InParty
) core
->SetEventFlag(EF_PORTRAIT
);
873 void pcf_maxhitpoint(Actor
*actor
, ieDword
/*oldValue*/, ieDword hp
)
875 if ((signed) hp
<(signed) actor
->BaseStats
[IE_HITPOINTS
]) {
876 actor
->BaseStats
[IE_HITPOINTS
]=hp
;
877 //passing 0 because it is ignored anyway
878 pcf_hitpoint(actor
, 0, hp
);
882 void pcf_minhitpoint(Actor
*actor
, ieDword
/*oldValue*/, ieDword hp
)
884 if ((signed) hp
>(signed) actor
->BaseStats
[IE_HITPOINTS
]) {
885 actor
->BaseStats
[IE_HITPOINTS
]=hp
;
886 //passing 0 because it is ignored anyway
887 pcf_hitpoint(actor
, 0, hp
);
891 void pcf_stat(Actor
*actor
, ieDword newValue
, ieDword stat
)
893 if ((signed) newValue
<=0) {
894 if (DeathOnZeroStat
) {
897 actor
->Modified
[stat
]=1;
902 void pcf_stat_str(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
904 pcf_stat(actor
, newValue
, IE_STR
);
907 void pcf_stat_int(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
909 pcf_stat(actor
, newValue
, IE_INT
);
912 void pcf_stat_wis(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
914 pcf_stat(actor
, newValue
, IE_WIS
);
917 void pcf_stat_dex(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
919 pcf_stat(actor
, newValue
, IE_DEX
);
922 void pcf_stat_con(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
924 pcf_stat(actor
, newValue
, IE_CON
);
925 pcf_hitpoint(actor
, 0, actor
->BaseStats
[IE_HITPOINTS
]);
928 void pcf_stat_cha(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
930 pcf_stat(actor
, newValue
, IE_CHR
);
933 void pcf_xp(Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
935 // check if we reached a new level
936 unsigned int pc
= actor
->InParty
;
937 if (pc
&& !actor
->GotLUFeedback
) {
939 sprintf(varname
, "CheckLevelUp%d", pc
);
940 core
->GetGUIScriptEngine()->RunFunction("GUICommonWindows", "CheckLevelUp", true, pc
);
941 ieDword NeedsLevelUp
= 0;
942 core
->GetDictionary()->Lookup(varname
, NeedsLevelUp
);
943 if (NeedsLevelUp
== 1) {
944 displaymsg
->DisplayConstantStringName(STR_LEVELUP
, 0xffffff, actor
);
945 actor
->GotLUFeedback
= true;
950 void pcf_gold(Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
952 //this function will make a party member automatically donate their
953 //gold to the party pool, not the same as in the original engine
954 if (actor
->InParty
) {
955 Game
*game
= core
->GetGame();
956 game
->AddGold ( actor
->BaseStats
[IE_GOLD
] );
957 actor
->BaseStats
[IE_GOLD
]=0;
961 static void handle_overlay(Actor
*actor
, ieDword idx
)
963 if (actor
->FindOverlay(idx
))
965 ieDword flag
= hc_locations
&(1<<idx
);
966 ScriptedAnimation
*sca
= gamedata
->GetScriptedAnimation(hc_overlays
[idx
], false);
971 actor
->AddVVCell(sca
);
975 //de/activates the entangle overlay
976 void pcf_entangle(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
979 handle_overlay(actor
, OV_ENTANGLE
);
982 actor
->RemoveVVCell(hc_overlays
[OV_ENTANGLE
], true);
986 //de/activates the sanctuary and other overlays
987 //unlike IE, gemrb uses this stat for other overlay fields
988 //see the complete list in overlay.2da
989 //it loosely follows the internal representation of overlays in IWD2
990 void pcf_sanctuary(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
992 ieDword changed
= newValue
^oldValue
;
994 for (int i
=0;i
<32;i
++) {
997 handle_overlay(actor
, i
);
999 actor
->RemoveVVCell(hc_overlays
[i
], true);
1006 //de/activates the prot from missiles overlay
1007 void pcf_shieldglobe(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1010 handle_overlay(actor
, OV_SHIELDGLOBE
);
1014 actor
->RemoveVVCell(hc_overlays
[OV_SHIELDGLOBE
], true);
1018 //de/activates the globe of invul. overlay
1019 void pcf_minorglobe(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1022 handle_overlay(actor
, OV_MINORGLOBE
);
1026 actor
->RemoveVVCell(hc_overlays
[OV_MINORGLOBE
], true);
1030 //de/activates the grease background
1031 void pcf_grease(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1034 handle_overlay(actor
, OV_GREASE
);
1038 actor
->RemoveVVCell(hc_overlays
[OV_GREASE
], true);
1042 //de/activates the web overlay
1043 //the web effect also immobilizes the actor!
1044 void pcf_web(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1047 handle_overlay(actor
, OV_WEB
);
1051 actor
->RemoveVVCell(hc_overlays
[OV_WEB
], true);
1055 //de/activates the spell bounce background
1056 void pcf_bounce(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1059 handle_overlay(actor
, OV_BOUNCE
);
1063 //it seems we have to remove it abruptly
1064 actor
->RemoveVVCell(hc_overlays
[OV_BOUNCE
], false);
1068 //no separate values (changes are permanent)
1069 void pcf_fatigue(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
1071 actor
->BaseStats
[IE_FATIGUE
]=newValue
;
1074 //no separate values (changes are permanent)
1075 void pcf_intoxication(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
1077 actor
->BaseStats
[IE_INTOXICATION
]=newValue
;
1080 void pcf_color(Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
1082 CharAnimations
*anims
= actor
->GetAnims();
1084 anims
->SetColors(actor
->Modified
+IE_COLORS
);
1088 void pcf_armorlevel(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
1090 CharAnimations
*anims
= actor
->GetAnims();
1092 anims
->SetArmourLevel(newValue
);
1096 static int maximum_values
[MAX_STATS
]={
1097 32767,32767,20,100,100,100,100,25,10,25,25,25,25,25,100,100,//0f
1098 100,100,100,100,100,100,100,100,100,100,255,255,255,255,100,100,//1f
1099 200,200,MAX_LEVEL
,255,25,100,25,25,25,25,25,999999999,999999999,999999999,25,25,//2f
1100 200,255,200,100,100,200,200,25,5,100,1,1,100,1,1,0,//3f
1101 511,1,1,1,MAX_LEVEL
,MAX_LEVEL
,1,9999,25,100,100,255,1,20,20,25,//4f
1102 25,1,1,255,25,25,255,255,25,255,255,255,255,255,255,255,//5f
1103 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//6f
1104 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//7f
1105 255,255,255,255,255,255,255,100,100,100,255,5,5,255,1,1,//8f
1106 1,25,25,30,1,1,1,25,0,100,100,1,255,255,255,255,//9f
1107 255,255,255,255,255,255,20,255,255,1,20,255,999999999,999999999,1,1,//af
1108 999999999,999999999,0,0,20,0,0,0,0,0,0,0,0,0,0,0,//bf
1109 0,0,0,0,0,0,0,25,25,255,255,255,255,65535,0,0,//cf - 207
1110 0,0,0,0,0,0,0,0,MAX_LEVEL
,255,65535,3,255,255,255,255,//df - 223
1111 255,255,255,255,255,255,255,255,255,255,255,255,65535,65535,15,0,//ef - 239
1112 MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
, MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
, //0xf7 - 247
1113 MAX_LEVEL
,MAX_LEVEL
,0,0,0,0,0,0//ff
1116 typedef void (*PostChangeFunctionType
)(Actor
*actor
, ieDword oldValue
, ieDword newValue
);
1117 static PostChangeFunctionType post_change_functions
[MAX_STATS
]={
1118 pcf_hitpoint
, pcf_maxhitpoint
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
1119 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //0f
1120 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1121 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, pcf_fatigue
, pcf_intoxication
, //1f
1122 NULL
,NULL
,pcf_level
,NULL
, pcf_stat_str
, NULL
, pcf_stat_int
, pcf_stat_wis
,
1123 pcf_stat_dex
,pcf_stat_con
,pcf_stat_cha
,NULL
, pcf_xp
, pcf_gold
, pcf_morale
, NULL
, //2f
1124 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1125 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, pcf_entangle
, pcf_sanctuary
, //3f
1126 pcf_minorglobe
, pcf_shieldglobe
, pcf_grease
, pcf_web
, pcf_level
, pcf_level
, NULL
, NULL
,
1127 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //4f
1128 NULL
,NULL
,NULL
,pcf_minhitpoint
, NULL
, NULL
, NULL
, NULL
,
1129 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //5f
1130 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1131 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //6f
1132 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1133 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //7f
1134 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1135 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //8f
1136 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1137 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //9f
1138 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1139 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //af
1140 NULL
,NULL
,NULL
,NULL
, pcf_morale
, pcf_bounce
, NULL
, NULL
,
1141 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //bf
1142 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1143 NULL
,NULL
,NULL
,NULL
, NULL
, pcf_animid
,pcf_state
, pcf_extstate
, //cf
1144 pcf_color
,pcf_color
,pcf_color
,pcf_color
, pcf_color
, pcf_color
, pcf_color
, NULL
,
1145 NULL
,NULL
,NULL
,pcf_armorlevel
, NULL
, NULL
, NULL
, NULL
, //df
1146 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1147 pcf_class
,NULL
,pcf_ea
,NULL
, NULL
, NULL
, NULL
, NULL
, //ef
1148 pcf_level
,pcf_level
,pcf_level
,pcf_level
, pcf_level
, pcf_level
, pcf_level
, pcf_level
,
1149 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
//ff
1152 /** call this from ~Interface() */
1153 void Actor::ReleaseMemory()
1157 if (classcount
>=0) {
1158 if (clericspelltables
) {
1159 for (i
=0;i
<classcount
;i
++) {
1160 if (clericspelltables
[i
]) {
1161 free (clericspelltables
[i
]);
1164 free(clericspelltables
);
1165 clericspelltables
=NULL
;
1167 if (druidspelltables
) {
1168 for (i
=0;i
<classcount
;i
++) {
1169 if (druidspelltables
[i
]) {
1170 free (druidspelltables
[i
]);
1173 free(druidspelltables
);
1174 druidspelltables
=NULL
;
1176 if (wizardspelltables
) {
1177 for (i
=0;i
<classcount
;i
++) {
1178 if (wizardspelltables
[i
]) {
1179 free(wizardspelltables
[i
]);
1182 free(wizardspelltables
);
1183 wizardspelltables
=NULL
;
1185 if (classabilities
) {
1186 for (i
=0;i
<classcount
;i
++) {
1187 if (classabilities
[i
]) {
1188 free (classabilities
[i
]);
1191 free(classabilities
);
1192 classabilities
=NULL
;
1211 for (i
=0; i
<classcount
; i
++) {
1212 if (levelslots
[i
]) {
1213 free(levelslots
[i
]);
1228 for (i
=0; i
<=wspecial_max
; i
++) {
1237 for (i
=0; i
<wspattack_rows
; i
++) {
1246 for (i
=0; i
<=STYLE_MAX
; i
++) {
1247 if (wsdualwield
[i
]) {
1248 free(wsdualwield
[i
]);
1255 for (i
=0; i
<=STYLE_MAX
; i
++) {
1256 if (wstwohanded
[i
]) {
1257 free(wstwohanded
[i
]);
1263 if (wsswordshield
) {
1264 for (i
=0; i
<=STYLE_MAX
; i
++) {
1265 if (wsswordshield
[i
]) {
1266 free(wsswordshield
[i
]);
1269 free(wsswordshield
);
1273 for (i
=0; i
<=STYLE_MAX
; i
++) {
1282 for (unsigned i
=0; i
<monkbon_rows
; i
++) {
1295 if (GUIBTDefaults
) {
1296 free (GUIBTDefaults
);
1299 if (OtherGUIButtons
) {
1300 free (OtherGUIButtons
);
1305 #define COL_HATERACE 0 //ranger type racial enemy
1306 #define COL_CLERIC_SPELL 1 //cleric spells
1307 #define COL_MAGE_SPELL 2 //mage spells
1308 #define COL_STARTXP 3 //starting xp
1309 #define COL_BARD_SKILL 4 //bard skills
1310 #define COL_THIEF_SKILL 5 //thief skills
1313 #define COL_SPARKS 1
1314 #define COL_GRADIENT 2
1316 /* returns the ISCLASS for the class based on name */
1317 int IsClassFromName (const char* name
)
1319 //TODO: is there a better way of doing this?
1320 for (int i
=0; i
<ISCLASSES
; i
++) {
1321 if (strcmp(name
, isclassnames
[i
]) == 0)
1327 static void InitActorTables()
1331 //if (core->HasFeature(GF_IWD_DEATHVARFORMAT)) {
1332 // memcpy(DeathVarFormat, IWDDeathVarFormat, sizeof(ieVariable));
1335 if (core
->HasFeature(GF_CHALLENGERATING
)) {
1336 sharexp
=SX_DIVIDE
|SX_CR
;
1340 ReverseToHit
= core
->HasFeature(GF_REVERSE_TOHIT
);
1341 CheckAbilities
= core
->HasFeature(GF_CHECK_ABILITIES
);
1342 DeathOnZeroStat
= core
->HasFeature(GF_DEATH_ON_ZERO_STAT
);
1344 //this table lists various level based xp bonuses
1345 AutoTable
tm("xpbonus");
1347 xpbonustypes
= tm
->GetRowCount();
1348 if (xpbonustypes
== 0) {
1351 xpbonuslevels
= tm
->GetColumnCount(0);
1352 xpbonus
= (int *) calloc(xpbonuslevels
*xpbonustypes
, sizeof(int));
1353 for (i
= 0; i
<xpbonustypes
; i
++) {
1354 for(j
= 0; j
<xpbonuslevels
; j
++) {
1355 xpbonus
[i
*xpbonuslevels
+j
] = atoi(tm
->QueryField(i
,j
));
1363 //this table lists skill groups assigned to classes
1364 //it is theoretically possible to create hybrid classes
1365 tm
.load("clskills");
1367 classcount
= tm
->GetRowCount();
1368 memset (isclass
,0,sizeof(isclass
));
1369 clericspelltables
= (char **) calloc(classcount
, sizeof(char*));
1370 druidspelltables
= (char **) calloc(classcount
, sizeof(char*));
1371 wizardspelltables
= (char **) calloc(classcount
, sizeof(char*));
1372 turnlevels
= (int *) calloc(classcount
, sizeof(int));
1373 booktypes
= (int *) calloc(classcount
, sizeof(int));
1374 classabilities
= (char **) calloc(classcount
, sizeof(char*));
1376 ieDword bitmask
= 1;
1378 for(i
= 0; i
<classcount
; i
++) {
1380 int turnlevel
= atoi(tm
->QueryField( i
, 7));
1381 turnlevels
[i
]=turnlevel
;
1383 field
= tm
->QueryField( i
, 0 );
1384 if (field
[0]!='*') {
1385 isclass
[ISDRUID
] |= bitmask
;
1386 druidspelltables
[i
]=strdup(field
);
1388 field
= tm
->QueryField( i
, 1 );
1389 if (field
[0]!='*') {
1390 isclass
[ISCLERIC
] |= bitmask
;
1391 clericspelltables
[i
]=strdup(field
);
1394 field
= tm
->QueryField( i
, 2 );
1395 if (field
[0]!='*') {
1396 isclass
[ISMAGE
] |= bitmask
;
1397 wizardspelltables
[i
]=strdup(field
);
1400 // field 3 holds the starting xp
1402 field
= tm
->QueryField( i
, 4 );
1403 if (field
[0]!='*') {
1404 isclass
[ISBARD
] |= bitmask
;
1407 field
= tm
->QueryField( i
, 5 );
1408 if (field
[0]!='*') {
1409 isclass
[ISTHIEF
] |= bitmask
;
1412 field
= tm
->QueryField( i
, 6 );
1413 if (field
[0]!='*') {
1414 isclass
[ISPALADIN
] |= bitmask
;
1417 // field 7 holds the turn undead level
1419 field
= tm
->QueryField( i
, 8 );
1420 booktypes
[i
]=atoi(field
);
1421 //if booktype == 3 then it is a 'divine sorceror' class
1422 //we shouldn't hardcode iwd2 classes this heavily
1423 if (booktypes
[i
]==2) {
1424 isclass
[ISSORCERER
] |= bitmask
;
1427 field
= tm
->QueryField( i
, 9 );
1428 if (field
[0]!='*') {
1429 isclass
[ISRANGER
] |= bitmask
;
1432 field
= tm
->QueryField( i
, 10 );
1433 if (!strnicmp(field
, "CLABMO", 6)) {
1434 isclass
[ISMONK
] |= bitmask
;
1436 classabilities
[i
]=strdup(field
);
1440 classcount
= 0; //well
1443 i
= core
->GetMaximumAbility();
1444 maximum_values
[IE_STR
]=i
;
1445 maximum_values
[IE_INT
]=i
;
1446 maximum_values
[IE_DEX
]=i
;
1447 maximum_values
[IE_CON
]=i
;
1448 maximum_values
[IE_CHR
]=i
;
1449 maximum_values
[IE_WIS
]=i
;
1451 //all games except iwd2
1452 maximum_values
[IE_ARMORCLASS
]=20;
1455 maximum_values
[IE_ARMORCLASS
]=199;
1458 //initializing the vvc resource references
1461 for (i
=0;i
<DAMAGE_LEVELS
;i
++) {
1462 const char *tmp
= tm
->QueryField( i
, COL_MAIN
);
1463 strnlwrcpy(d_main
[i
], tmp
, 8);
1464 if (d_main
[i
][0]=='*') {
1467 tmp
= tm
->QueryField( i
, COL_SPARKS
);
1468 strnlwrcpy(d_splash
[i
], tmp
, 8);
1469 if (d_splash
[i
][0]=='*') {
1472 tmp
= tm
->QueryField( i
, COL_GRADIENT
);
1473 d_gradient
[i
]=atoi(tmp
);
1480 for (i
=0;i
<OVERLAY_COUNT
;i
++) {
1481 const char *tmp
= tm
->QueryField( i
, 0 );
1482 strnlwrcpy(hc_overlays
[i
], tmp
, 8);
1483 if (atoi(tm
->QueryField( i
, 1))) {
1490 //csound for bg1/bg2
1491 memset(csound
,0,sizeof(csound
));
1492 if (!core
->HasFeature(GF_SOUNDFOLDERS
)) {
1495 for(i
=0;i
<VCONST_COUNT
;i
++) {
1496 const char *tmp
= tm
->QueryField( i
, 0 );
1499 //I have no idea what this ! mean
1500 case '!': csound
[i
]=tmp
[1]; break;
1501 default: csound
[i
]=tmp
[0]; break;
1508 GUIBTDefaults
= (ActionButtonRow
*) calloc( classcount
,sizeof(ActionButtonRow
) );
1510 for (i
= 0; i
< classcount
; i
++) {
1511 memcpy(GUIBTDefaults
+i
, &DefaultButtons
, sizeof(ActionButtonRow
));
1513 for (int j
=0;j
<MAX_QSLOTS
;j
++) {
1514 GUIBTDefaults
[i
][j
+3]=(ieByte
) atoi( tm
->QueryField(i
,j
) );
1521 extraslots
= tm
->GetRowCount();
1522 OtherGUIButtons
= (ActionButtonRow2
*) calloc( extraslots
, sizeof (ActionButtonRow2
) );
1524 for (i
=0; i
<usecount
; i
++) {
1525 OtherGUIButtons
[i
].clss
= (ieByte
) atoi( tm
->QueryField(i
,0) );
1526 memcpy(OtherGUIButtons
[i
].buttons
, &DefaultButtons
, sizeof(ActionButtonRow
));
1527 for (int j
=0;j
<MAX_QSLOTS
;j
++) {
1528 OtherGUIButtons
[i
].buttons
[j
+3]=(ieByte
) atoi( tm
->QueryField(i
,j
+1) );
1535 usecount
= tm
->GetRowCount();
1536 itemuse
= new ItemUseType
[usecount
];
1537 for (i
= 0; i
< usecount
; i
++) {
1538 itemuse
[i
].stat
= (ieByte
) core
->TranslateStat( tm
->QueryField(i
,0) );
1539 strnlwrcpy(itemuse
[i
].table
, tm
->QueryField(i
,1),8 );
1540 itemuse
[i
].mcol
= (ieByte
) atoi( tm
->QueryField(i
,2) );
1541 itemuse
[i
].vcol
= (ieByte
) atoi( tm
->QueryField(i
,3) );
1542 itemuse
[i
].which
= (ieByte
) atoi( tm
->QueryField(i
,4) );
1543 //limiting it to 0 or 1 to avoid crashes
1544 if (itemuse
[i
].which
!=1) {
1550 tm
.load("itemanim");
1552 animcount
= tm
->GetRowCount();
1553 itemanim
= new ItemAnimType
[animcount
];
1554 for (i
= 0; i
< animcount
; i
++) {
1555 strnlwrcpy(itemanim
[i
].itemname
, tm
->QueryField(i
,0),8 );
1556 itemanim
[i
].animation
= (ieByte
) atoi( tm
->QueryField(i
,1) );
1560 tm
.load("mxsplwis");
1562 spllevels
= tm
->GetColumnCount(0);
1563 int max
= core
->GetMaximumAbility();
1564 mxsplwis
= (int *) calloc(max
*spllevels
, sizeof(int));
1565 for (i
= 0; i
< spllevels
; i
++) {
1566 for(int j
= 0; j
< max
; j
++) {
1567 int k
= atoi(tm
->GetRowName(j
))-1;
1568 if (k
>=0 && k
<max
) {
1569 mxsplwis
[k
*spllevels
+i
]=atoi(tm
->QueryField(j
,i
));
1579 for(i
=0;i
<MAX_FEATS
;i
++) {
1580 //we need the MULTIPLE column only
1581 //it stores the FEAT_* stat index, and could be taken multiple
1583 tmp
= core
->TranslateStat(tm
->QueryField(i
,0));
1584 if (tmp
>=MAX_STATS
) {
1585 printMessage("Actor","Invalid stat value in featreq.2da",YELLOW
);
1587 featstats
[i
] = (ieByte
) tmp
;
1591 //default all hp con bonuses to 9; this should be updated below
1593 maxhpconbon
= (int *) calloc(classcount
, sizeof(int));
1594 for (i
= 0; i
< classcount
; i
++) {
1598 if (tm
&& !core
->HasFeature(GF_LEVELSLOT_PER_CLASS
)) {
1600 //iwd2 just uses levelslotsiwd2 instead
1601 printf("Examining classes.2da\n");
1603 //when searching the levelslots, you must search for
1604 //levelslots[BaseStats[IE_CLASS]-1] as there is no class id of 0
1605 levelslots
= (int **) calloc(classcount
, sizeof(int*));
1606 dualswap
= (int *) calloc(classcount
, sizeof(int));
1608 for (i
=0; i
<classcount
; i
++) {
1609 //make sure we have a valid classid, then decrement
1610 //it to get the correct array index
1611 tmpindex
= atoi(tm
->QueryField(i
, 5));
1616 printf("\tID: %d ", tmpindex
);
1617 //only create the array if it isn't yet made
1618 //i.e. barbarians would overwrite fighters in bg2
1619 if (levelslots
[tmpindex
]) {
1620 printf ("Already Found!\n");
1624 const char* classname
= tm
->GetRowName(i
);
1625 printf("Name: %s ", classname
);
1627 //default all levelslots to 0
1628 levelslots
[tmpindex
] = (int *) calloc(ISCLASSES
, sizeof(int));
1630 //single classes only worry about IE_LEVEL
1631 ieDword tmpclass
= atoi(tm
->QueryField(i
, 4));
1633 classis
= IsClassFromName(classname
);
1635 printf("Classis: %d ", classis
);
1636 levelslots
[tmpindex
][classis
] = IE_LEVEL
;
1637 //get the max hp con bonus
1638 hptm
.load(tm
->QueryField(i
, 6));
1641 int rollscolumn
= hptm
->GetColumnIndex("ROLLS");
1642 while (atoi(hptm
->QueryField(tmphp
, rollscolumn
)))
1644 printf("TmpHP: %d ", tmphp
);
1645 if (tmphp
) maxhpconbon
[tmpindex
] = tmphp
;
1651 //we have to account for dual-swap in the multiclass field
1652 ieDword numfound
= 1;
1653 ieDword tmpbits
= bitcount (tmpclass
);
1655 //we need all the classnames of the multi to compare with the order we load them in
1656 //because the original game set the levels based on name order, not bit order
1657 char **classnames
= (char **) calloc(tmpbits
, sizeof(char *));
1658 classnames
[0] = (char*)strtok(strdup((char*)classname
), "_");
1659 while (numfound
<tmpbits
&& (classnames
[numfound
] = strdup(strtok(NULL
, "_")))) {
1663 bool foundwarrior
= false;
1664 for (int j
=0; j
<classcount
; j
++) {
1665 //no sense continuing if we've found all to be found
1666 if (numfound
==tmpbits
)
1668 if ((1<<j
)&tmpclass
) {
1669 //save the IE_LEVEL information
1670 const char* currentname
= tm
->GetRowName((ieDword
)(tm
->FindTableValue(5, j
+1)));
1671 classis
= IsClassFromName(currentname
);
1673 //search for the current class in the split of the names to get it's
1675 for (ieDword k
=0; k
<tmpbits
; k
++) {
1676 if (strcmp(classnames
[k
], currentname
) == 0) {
1678 if (k
==0) tmplevel
= IE_LEVEL
;
1679 else if (k
==1) tmplevel
= IE_LEVEL2
;
1680 else tmplevel
= IE_LEVEL3
;
1681 levelslots
[tmpindex
][classis
] = tmplevel
;
1684 printf("Classis: %d ", classis
);
1686 //warrior take presedence
1687 if (!foundwarrior
) {
1688 foundwarrior
= (classis
==ISFIGHTER
||classis
==ISRANGER
||classis
==ISPALADIN
||
1689 classis
==ISBARBARIAN
);
1690 hptm
.load(tm
->QueryField(currentname
, "HP"));
1693 int rollscolumn
= hptm
->GetColumnIndex("ROLLS");
1694 while (atoi(hptm
->QueryField(tmphp
, rollscolumn
)))
1696 //make sure we at least set the first class
1697 if ((tmphp
>maxhpconbon
[tmpindex
])||foundwarrior
||numfound
==0)
1698 maxhpconbon
[tmpindex
]=tmphp
;
1703 //save the MC_WAS_ID of the first class in the dual-class
1704 if (numfound
==0 && tmpbits
==2) {
1705 if (strcmp(classnames
[0], currentname
) == 0) {
1706 dualswap
[tmpindex
] = strtol(tm
->QueryField(classname
, "MC_WAS_ID"), NULL
, 0);
1708 } else if (numfound
==1 && tmpbits
==2 && !dualswap
[tmpindex
]) {
1709 dualswap
[tmpindex
] = strtol(tm
->QueryField(classname
, "MC_WAS_ID"), NULL
, 0);
1715 for (ieDword j
=0; j
<tmpbits
; j
++) {
1716 if (classnames
[j
]) {
1717 free(classnames
[j
]);
1723 printf("HPCON: %d ", maxhpconbon
[tmpindex
]);
1724 printf("DS: %d\n", dualswap
[tmpindex
]);
1726 /*this could be enabled to ensure all levelslots are filled with at least 0's;
1727 *however, the access code should ensure this never happens
1728 for (i=0; i<classcount; i++) {
1729 if (!levelslots[i]) {
1730 levelslots[i] = (int *) calloc(ISCLASSES, sizeof(int *));
1734 printf("Finished examining classes.2da\n");
1736 //pre-cache hit/damage/speed bonuses for weapons
1737 tm
.load("wspecial");
1739 //load in the identifiers
1740 wspecial_max
= tm
->GetRowCount()-1;
1741 int cols
= tm
->GetColumnCount();
1742 wspecial
= (int **) calloc(wspecial_max
+1, sizeof(int *));
1744 for (i
=0; i
<=wspecial_max
; i
++) {
1745 wspecial
[i
] = (int *) calloc(WSPECIAL_COLS
, sizeof(int));
1746 for (int j
=0; j
<cols
; j
++) {
1747 wspecial
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1752 //pre-cache attack per round bonuses
1755 wspattack_rows
= tm
->GetRowCount();
1756 wspattack_cols
= tm
->GetColumnCount();
1757 wspattack
= (int **) calloc(wspattack_rows
, sizeof(int *));
1760 for (i
=0; i
<wspattack_rows
; i
++) {
1761 wspattack
[i
] = (int *) calloc(wspattack_cols
, sizeof(int));
1762 for (int j
=0; j
<wspattack_cols
; j
++) {
1763 tmp
= atoi(tm
->QueryField(i
, j
));
1764 //negative values relate to x/2, so we adjust them
1765 //positive values relate to x, so we must times by 2
1766 if (tmp
<0) tmp
= -2*tmp
-1;
1768 wspattack
[i
][j
] = tmp
;
1773 //dual-wielding table
1774 tm
.load("wstwowpn");
1776 wsdualwield
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1777 int cols
= tm
->GetColumnCount();
1778 for (i
=0; i
<=STYLE_MAX
; i
++) {
1779 wsdualwield
[i
] = (int *) calloc(cols
, sizeof(int));
1780 for (int j
=0; j
<cols
; j
++) {
1781 wsdualwield
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1787 tm
.load("wstwohnd");
1789 wstwohanded
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1790 int cols
= tm
->GetColumnCount();
1791 for (i
=0; i
<=STYLE_MAX
; i
++) {
1792 wstwohanded
[i
] = (int *) calloc(cols
, sizeof(int));
1793 for (int j
=0; j
<cols
; j
++) {
1794 wstwohanded
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1800 tm
.load("wsshield");
1802 wsswordshield
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1803 int cols
= tm
->GetColumnCount();
1804 for (i
=0; i
<=STYLE_MAX
; i
++) {
1805 wsswordshield
[i
] = (int *) calloc(cols
, sizeof(int));
1806 for (int j
=0; j
<cols
; j
++) {
1807 wsswordshield
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1813 tm
.load("wssingle");
1815 wssingle
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1816 int cols
= tm
->GetColumnCount();
1817 for (i
=0; i
<=STYLE_MAX
; i
++) {
1818 wssingle
[i
] = (int *) calloc(cols
, sizeof(int));
1819 for (int j
=0; j
<cols
; j
++) {
1820 wssingle
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1825 //unhardcoded monk bonus table
1828 monkbon_rows
= tm
->GetRowCount();
1829 monkbon_cols
= tm
->GetColumnCount();
1830 monkbon
= (int **) calloc(monkbon_rows
, sizeof(int *));
1831 for (unsigned i
=0; i
<monkbon_rows
; i
++) {
1832 monkbon
[i
] = (int *) calloc(monkbon_cols
, sizeof(int));
1833 for (unsigned j
=0; j
<monkbon_cols
; j
++) {
1834 monkbon
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1839 //wild magic level modifiers
1841 wmlevels
[i
]=(int *) calloc(MAX_LEVEL
,sizeof(int) );
1843 tm
.load("lvlmodwm");
1845 int maxrow
= tm
->GetRowCount();
1846 for (i
=0;i
<20;i
++) {
1847 for(j
=0;j
<MAX_LEVEL
;j
++) {
1850 wmlevels
[i
][j
]=strtol(tm
->QueryField(row
,i
), NULL
, 0);
1855 // verbal constant remapping, if omitted, it is an 1-1 mapping
1856 // TODO: allow disabled VC slots
1857 for (i
=0;i
<VCONST_COUNT
;i
++) {
1862 int rows
= tm
->GetRowCount();
1864 for (i
=0;i
<rows
;i
++) {
1865 int row
= atoi(tm
->QueryField(i
,0));
1866 if (row
<0 || row
>=VCONST_COUNT
) continue;
1867 int value
= atoi(tm
->QueryField(i
,1));
1868 if (value
<0 || value
>=VCONST_COUNT
) continue;
1874 void Actor::SetLockedPalette(const ieDword
*gradients
)
1876 if (!anims
) return; //cannot apply it (yet)
1877 anims
->LockPalette(gradients
);
1880 void Actor::UnlockPalette()
1883 anims
->lockPalette
=false;
1884 anims
->SetColors(Modified
+IE_COLORS
);
1887 void Actor::AddAnimation(const ieResRef resource
, int gradient
, int height
, int flags
)
1889 ScriptedAnimation
*sca
= gamedata
->GetScriptedAnimation(resource
, false);
1893 if (flags
&AA_PLAYONCE
) {
1896 if (flags
&&AA_BLEND
) {
1897 //pst anims need this?
1901 sca
->SetPalette(gradient
, 4);
1906 void Actor::PlayDamageAnimation(int type
, bool hit
)
1910 printf("Damage animation type: %d\n", type
);
1913 case 0: case 1: case 2: case 3: //blood
1914 i
= (int) GetStat(IE_ANIMATION_ID
)>>16;
1915 if (!i
) i
= d_gradient
[type
];
1917 AddAnimation(d_main
[type
], i
, 0, AA_PLAYONCE
);
1920 case 4: case 5: case 6: //fire
1922 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1924 for(i
=DL_FIRE
;i
<=type
;i
++) {
1925 AddAnimation(d_splash
[i
], d_gradient
[i
], 0, AA_PLAYONCE
);
1928 case 7: case 8: case 9: //electricity
1930 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1932 for(i
=DL_ELECTRICITY
;i
<=type
;i
++) {
1933 AddAnimation(d_splash
[i
], d_gradient
[i
], 0, AA_PLAYONCE
);
1936 case 10: case 11: case 12://cold
1938 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1941 case 13: case 14: case 15://acid
1943 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1946 case 16: case 17: case 18://disintegrate
1948 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1954 bool Actor::SetStat(unsigned int StatIndex
, ieDword Value
, int pcf
)
1956 if (StatIndex
>= MAX_STATS
) {
1959 if ( (signed) Value
<-100) {
1960 Value
= (ieDword
) -100;
1963 if ( maximum_values
[StatIndex
]>0) {
1964 if ( (signed) Value
>maximum_values
[StatIndex
]) {
1965 Value
= (ieDword
) maximum_values
[StatIndex
];
1970 unsigned int previous
= Modified
[StatIndex
];
1971 if (Modified
[StatIndex
]!=Value
) {
1972 Modified
[StatIndex
] = Value
;
1974 PostChangeFunctionType f
= post_change_functions
[StatIndex
];
1975 if (f
) (*f
)(this, previous
, Value
);
1981 int Actor::GetMod(unsigned int StatIndex
)
1983 if (StatIndex
>= MAX_STATS
) {
1986 return (signed) Modified
[StatIndex
] - (signed) BaseStats
[StatIndex
];
1988 /** Returns a Stat Base Value */
1989 ieDword
Actor::GetBase(unsigned int StatIndex
)
1991 if (StatIndex
>= MAX_STATS
) {
1994 return BaseStats
[StatIndex
];
1997 /** Sets a Stat Base Value */
1998 /** If required, modify the modified value and run the pcf function */
1999 bool Actor::SetBase(unsigned int StatIndex
, ieDword Value
)
2001 if (StatIndex
>= MAX_STATS
) {
2004 ieDword diff
= Modified
[StatIndex
]-BaseStats
[StatIndex
];
2006 //maximize the base stat
2007 if ( maximum_values
[StatIndex
]) {
2008 if ( (signed) Value
>maximum_values
[StatIndex
]) {
2009 Value
= (ieDword
) maximum_values
[StatIndex
];
2013 BaseStats
[StatIndex
] = Value
;
2015 //if already initialized, then the modified stats
2016 //might need to run the post change function (stat change can kill actor)
2017 SetStat (StatIndex
, Value
+diff
, InternalFlags
&IF_INITIALIZED
);
2021 bool Actor::SetBaseNoPCF(unsigned int StatIndex
, ieDword Value
)
2023 if (StatIndex
>= MAX_STATS
) {
2026 ieDword diff
= Modified
[StatIndex
]-BaseStats
[StatIndex
];
2028 //maximize the base stat
2029 if ( maximum_values
[StatIndex
]) {
2030 if ( (signed) Value
>maximum_values
[StatIndex
]) {
2031 Value
= (ieDword
) maximum_values
[StatIndex
];
2035 BaseStats
[StatIndex
] = Value
;
2037 //if already initialized, then the modified stats
2038 //might need to run the post change function (stat change can kill actor)
2039 SetStat (StatIndex
, Value
+diff
, 0);
2043 bool Actor::SetBaseBit(unsigned int StatIndex
, ieDword Value
, bool setreset
)
2045 if (StatIndex
>= MAX_STATS
) {
2049 BaseStats
[StatIndex
] |= Value
;
2051 BaseStats
[StatIndex
] &= ~Value
;
2053 //if already initialized, then the modified stats
2054 //need to run the post change function (stat change can kill actor)
2056 SetStat (StatIndex
, Modified
[StatIndex
]|Value
, InternalFlags
&IF_INITIALIZED
);
2058 SetStat (StatIndex
, Modified
[StatIndex
]&~Value
, InternalFlags
&IF_INITIALIZED
);
2063 const unsigned char *Actor::GetStateString()
2068 ieByte
*tmp
= PCStats
->PortraitIconString
;
2069 ieWord
*Icons
= PCStats
->PortraitIcons
;
2071 for (int i
=0;i
<MAX_PORTRAIT_ICONS
;i
++) {
2072 if (!(Icons
[i
]&0xff00)) {
2073 tmp
[j
++]=(ieByte
) ((Icons
[i
]&0xff)+66);
2080 void Actor::AddPortraitIcon(ieByte icon
)
2085 ieWord
*Icons
= PCStats
->PortraitIcons
;
2087 for(int i
=0;i
<MAX_PORTRAIT_ICONS
;i
++) {
2088 if (Icons
[i
]==0xffff) {
2092 if (icon
== (Icons
[i
]&0xff)) {
2098 void Actor::DisablePortraitIcon(ieByte icon
)
2103 ieWord
*Icons
= PCStats
->PortraitIcons
;
2106 for(i
=0;i
<MAX_PORTRAIT_ICONS
;i
++) {
2107 if (icon
== (Icons
[i
]&0xff)) {
2108 Icons
[i
]=0xff00|icon
;
2114 /** call this after load, to apply effects */
2115 void Actor::RefreshEffects(EffectQueue
*fx
)
2117 ieDword previous
[MAX_STATS
];
2119 //put all special cleanup calls here
2120 CharAnimations
* anims
= GetAnims();
2122 anims
->GlobalColorMod
.type
= RGBModifier::NONE
;
2123 anims
->GlobalColorMod
.speed
= 0;
2124 unsigned int location
;
2125 for (location
= 0; location
< 32; ++location
) {
2126 anims
->ColorMods
[location
].type
= RGBModifier::NONE
;
2127 anims
->ColorMods
[location
].speed
= 0;
2130 spellbook
.ClearBonus();
2131 memset(applyWhenHittingMelee
,0,sizeof(ieResRef
));
2132 memset(applyWhenHittingRanged
,0,sizeof(ieResRef
));
2133 memset(applyWhenNearLiving
,0,sizeof(ieResRef
));
2134 memset(applyWhen50Damage
,0,sizeof(ieResRef
));
2135 memset(applyWhen90Damage
,0,sizeof(ieResRef
));
2136 memset(applyWhenEnemySighted
,0,sizeof(ieResRef
));
2137 memset(applyWhenPoisoned
,0,sizeof(ieResRef
));
2138 memset(applyWhenHelpless
,0,sizeof(ieResRef
));
2139 memset(applyWhenAttacked
,0,sizeof(ieResRef
));
2140 memset(applyWhenBeingHit
,0,sizeof(ieResRef
));
2141 memset(projectileImmunity
,0,ProjectileSize
*sizeof(ieDword
));
2143 //initialize base stats
2144 bool first
= !(InternalFlags
&IF_INITIALIZED
);
2147 InternalFlags
|=IF_INITIALIZED
;
2148 memcpy( previous
, BaseStats
, MAX_STATS
* sizeof( ieDword
) );
2150 memcpy( previous
, Modified
, MAX_STATS
* sizeof( ieDword
) );
2152 memcpy( Modified
, BaseStats
, MAX_STATS
* sizeof( ieDword
) );
2153 if (PCStats
) memset( PCStats
->PortraitIcons
, -1, sizeof(PCStats
->PortraitIcons
) );
2157 fx
->AddAllEffects(this, Pos
);
2159 //copy back the original stats, because the effects
2160 //will be reapplied in ApplyAllEffects again
2161 memcpy( Modified
, BaseStats
, MAX_STATS
* sizeof( ieDword
) );
2162 //also clear the spell bonuses just given, they will be
2163 //recalculated below again
2164 spellbook
.ClearBonus();
2167 // some VVCs are controlled by stats (and so by PCFs), the rest have 'effect_owned' set
2168 for (unsigned int i
= 0; i
< vvcOverlays
.size(); i
++) {
2169 if (vvcOverlays
[i
] && vvcOverlays
[i
]->effect_owned
) vvcOverlays
[i
]->active
= false;
2171 for (unsigned int i
= 0; i
< vvcShields
.size(); i
++) {
2172 if (vvcShields
[i
] && vvcShields
[i
]->effect_owned
) vvcShields
[i
]->active
= false;
2175 fxqueue
.ApplyAllEffects( this );
2177 // IE_CLASS is >classcount for non-PCs/NPCs
2178 if (BaseStats
[IE_CLASS
] <= (ieDword
)classcount
)
2181 for (unsigned int i
=0;i
<MAX_STATS
;i
++) {
2182 if (first
|| Modified
[i
]!=previous
[i
]) {
2183 PostChangeFunctionType f
= post_change_functions
[i
];
2185 (*f
)(this, previous
[i
], Modified
[i
]);
2189 //add wisdom bonus spells
2190 if (!spellbook
.IsIWDSpellBook() && mxsplwis
) {
2191 int level
= Modified
[IE_WIS
];
2193 spellbook
.BonusSpells(IE_SPELL_TYPE_PRIEST
, spllevels
, mxsplwis
+spllevels
*level
);
2197 // check if any new portrait icon was removed or added
2199 if (memcmp(PCStats
->PreviousPortraitIcons
, PCStats
->PortraitIcons
, sizeof(PCStats
->PreviousPortraitIcons
))) {
2200 core
->SetEventFlag(EF_PORTRAIT
);
2201 memcpy( PCStats
->PreviousPortraitIcons
, PCStats
->PortraitIcons
, sizeof(PCStats
->PreviousPortraitIcons
) );
2206 // refresh stats on creatures (PC or NPC) with a valid class (not animals etc)
2207 // internal use only, and this is maybe a stupid name :)
2208 void Actor::RefreshPCStats() {
2209 //calculate hp bonus
2211 int bonlevel
= GetXPLevel(true);
2212 int oldlevel
, oldbonus
;
2213 oldlevel
= oldbonus
= 0;
2214 ieDword bonindex
= BaseStats
[IE_CLASS
]-1;
2216 //we must limit the levels to the max allowable
2217 if (bonlevel
>maxhpconbon
[bonindex
])
2218 bonlevel
= maxhpconbon
[bonindex
];
2220 if (IsDualInactive()) {
2221 //we apply the inactive hp bonus if it's better than the new hp bonus, so that we
2222 //never lose hp, only gain, on leveling
2223 oldlevel
= IsDualSwap() ? BaseStats
[IE_LEVEL
] : BaseStats
[IE_LEVEL2
];
2224 bonlevel
= IsDualSwap() ? BaseStats
[IE_LEVEL2
] : BaseStats
[IE_LEVEL
];
2225 oldlevel
= (oldlevel
> maxhpconbon
[bonindex
]) ? maxhpconbon
[bonindex
] : oldlevel
;
2226 if (Modified
[IE_MC_FLAGS
] & (MC_WAS_FIGHTER
|MC_WAS_RANGER
)) {
2227 oldbonus
= core
->GetConstitutionBonus(STAT_CON_HP_WARRIOR
, Modified
[IE_CON
]);
2229 oldbonus
= core
->GetConstitutionBonus(STAT_CON_HP_NORMAL
, Modified
[IE_CON
]);
2233 // warrior (fighter, barbarian, ranger, or paladin) or not
2234 // GetClassLevel now takes into consideration inactive dual-classes
2236 bonus
= core
->GetConstitutionBonus(STAT_CON_HP_WARRIOR
,Modified
[IE_CON
]);
2239 bonus
= core
->GetConstitutionBonus(STAT_CON_HP_NORMAL
,Modified
[IE_CON
]);
2242 oldbonus
*= oldlevel
;
2243 bonus
= (oldbonus
> bonus
) ? oldbonus
: bonus
;
2245 //morale recovery every xth AI cycle
2246 int mrec
= GetStat(IE_MORALERECOVERYTIME
);
2248 if (!(core
->GetGame()->GameTime
%mrec
)) {
2249 NewBase(IE_MORALE
,1,MOD_ADDITIVE
);
2253 if (bonus
<0 && (Modified
[IE_MAXHITPOINTS
]+bonus
)<=0) {
2254 bonus
=1-Modified
[IE_MAXHITPOINTS
];
2257 //get the wspattack bonuses for proficiencies
2259 ITMExtHeader
*header
= GetWeapon(wi
, false);
2261 int dualwielding
= IsDualWielding();
2262 if (header
&& (wi
.prof
<= MAX_STATS
)) {
2263 stars
= GetStat(wi
.prof
)&PROFS_MASK
;
2264 if (stars
>= (unsigned)wspattack_rows
) {
2265 stars
= wspattack_rows
-1;
2268 int tmplevel
= GetWarriorLevel();
2269 if (tmplevel
>= wspattack_cols
) {
2270 tmplevel
= wspattack_cols
-1;
2271 } else if (tmplevel
< 0) {
2275 //HACK: attacks per round bonus for monks should only apply to fists
2276 if (isclass
[ISMONK
]&(1<<BaseStats
[IE_CLASS
])) {
2277 unsigned int level
= GetMonkLevel()-1;
2278 if (level
< monkbon_cols
) {
2279 SetBase(IE_NUMBEROFATTACKS
, 2 + monkbon
[0][level
]);
2282 //wspattack appears to only effect warriors
2283 int defaultattacks
= 2 + 2*dualwielding
;
2285 SetBase(IE_NUMBEROFATTACKS
, defaultattacks
+wspattack
[stars
][tmplevel
]);
2287 SetBase(IE_NUMBEROFATTACKS
, defaultattacks
);
2292 //we still apply the maximum bonus to dead characters, but don't apply
2293 //to current HP, or we'd have dead characters showing as having hp
2294 //Modified[IE_MAXHITPOINTS]+=bonus;
2295 //if(BaseStats[IE_STATE_ID]&STATE_DEAD)
2297 // BaseStats[IE_HITPOINTS]+=bonus;
2299 // apply the intelligence and wisdom bonus to lore
2300 Modified
[IE_LORE
] += core
->GetLoreBonus(0, Modified
[IE_INT
]) + core
->GetLoreBonus(0, Modified
[IE_WIS
]);
2303 void Actor::RollSaves()
2305 if (InternalFlags
&IF_USEDSAVE
) {
2306 SavingThrow
[0]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2307 SavingThrow
[1]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2308 SavingThrow
[2]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2309 SavingThrow
[3]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2310 SavingThrow
[4]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2311 InternalFlags
&=~IF_USEDSAVE
;
2316 //type bits in file order in stats
2323 //iwd2 (luckily they use the same bits as it would be with bg2):
2331 static int savingthrows
[SAVECOUNT
]={IE_SAVEVSSPELL
, IE_SAVEVSBREATH
, IE_SAVEVSDEATH
, IE_SAVEVSWANDS
, IE_SAVEVSPOLY
};
2333 /** returns true if actor made the save against saving throw type */
2334 bool Actor::GetSavingThrow(ieDword type
, int modifier
)
2336 assert(type
<SAVECOUNT
);
2337 InternalFlags
|=IF_USEDSAVE
;
2338 int ret
= SavingThrow
[type
];
2339 if (ret
== 1) return false;
2340 if (ret
== SAVEROLL
) return true;
2341 ret
+= modifier
+ GetStat(IE_LUCK
);
2342 return ret
> (int) GetStat(savingthrows
[type
]);
2345 /** implements a generic opcode function, modify modified stats
2348 int Actor::NewStat(unsigned int StatIndex
, ieDword ModifierValue
, ieDword ModifierType
)
2350 int oldmod
= Modified
[StatIndex
];
2352 switch (ModifierType
) {
2354 //flat point modifier
2355 SetStat(StatIndex
, Modified
[StatIndex
]+ModifierValue
, 0);
2358 //straight stat change
2359 SetStat(StatIndex
, ModifierValue
, 0);
2363 SetStat(StatIndex
, BaseStats
[StatIndex
] * ModifierValue
/ 100, 0);
2366 return Modified
[StatIndex
] - oldmod
;
2369 int Actor::NewBase(unsigned int StatIndex
, ieDword ModifierValue
, ieDword ModifierType
)
2371 int oldmod
= BaseStats
[StatIndex
];
2373 switch (ModifierType
) {
2375 //flat point modifier
2376 SetBase(StatIndex
, BaseStats
[StatIndex
]+ModifierValue
);
2379 //straight stat change
2380 SetBase(StatIndex
, ModifierValue
);
2384 SetBase(StatIndex
, BaseStats
[StatIndex
] * ModifierValue
/ 100);
2387 return BaseStats
[StatIndex
] - oldmod
;
2390 inline int CountElements(const char *s
, char separator
)
2394 if (*s
==separator
) ret
++;
2400 void Actor::Interact(int type
)
2406 case I_INSULT
: start
=VB_INSULT
; count
=3; break;
2407 case I_COMPLIMENT
: start
=VB_COMPLIMENT
; count
=3; break;
2408 case I_SPECIAL
: start
=VB_SPECIAL
; count
=3; break;
2412 VerbalConstant(start
, count
);
2415 ieStrRef
Actor::GetVerbalConstant(int index
) const
2417 if (index
<0 || index
>=VCONST_COUNT
) {
2418 return (ieStrRef
) -1;
2421 int idx
= VCMap
[index
];
2423 if (idx
<0 || idx
>=VCONST_COUNT
) {
2424 return (ieStrRef
) -1;
2426 return StrRefs
[idx
];
2429 void Actor::VerbalConstant(int start
, int count
)
2435 while(count
>=0 && (!(vc
= GetVerbalConstant(start
+count
)) || (vc
==(ieStrRef
) -1)) ) {
2439 DisplayStringCore(this, start
+count
, DS_CONSOLE
|DS_CONST
);
2443 void Actor::Response(int type
)
2450 case I_INSULT
: start
=VB_RESP_INS
; count
=3; break;
2451 case I_COMPLIMENT
: start
=VB_RESP_COMP
; count
=3; break;
2457 while(count
&& ((vc
= GetVerbalConstant(start
+count
))!=(ieStrRef
) -1)) {
2461 DisplayStringCore(this, start
+count
, DS_CONSOLE
|DS_CONST
);
2465 void Actor::ReactToDeath(const char * deadname
)
2467 AutoTable
tm("death");
2469 // lookup value based on died's scriptingname and ours
2470 // if value is 0 - use reactdeath
2471 // if value is 1 - use reactspecial
2472 // if value is string - use playsound instead (pst)
2473 const char *value
= tm
->QueryField (scriptName
, deadname
);
2476 DisplayStringCore(this, VB_REACT
, DS_CONSOLE
|DS_CONST
);
2479 DisplayStringCore(this, VB_REACT_S
, DS_CONSOLE
|DS_CONST
);
2483 int count
= CountElements(value
,',');
2484 if (count
<=0) break;
2485 count
= core
->Roll(1,count
,-1);
2488 while(*value
&& *value
!=',') value
++;
2489 if (*value
==',') value
++;
2491 strncpy(resref
, value
, 8);
2492 for(count
=0;count
<8 && resref
[count
]!=',';count
++) {};
2495 ieDword len
= core
->GetAudioDrv()->Play( resref
);
2496 ieDword counter
= ( AI_UPDATE_TIME
* len
) / 1000;
2504 //call this only from gui selects
2505 void Actor::SelectActor()
2507 DisplayStringCore(this, VB_SELECT
, DS_CONSOLE
|DS_CONST
);
2512 if (GetStat(IE_STATE_ID
)&STATE_PANIC
) {
2516 if (InParty
) core
->GetGame()->SelectActor(this, false, SELECT_NORMAL
);
2517 SetBaseBit(IE_STATE_ID
, STATE_PANIC
, true);
2518 DisplayStringCore(this, VB_PANIC
, DS_CONSOLE
|DS_CONST
);
2521 void Actor::SetMCFlag(ieDword arg
, int op
)
2523 ieDword tmp
= BaseStats
[IE_MC_FLAGS
];
2525 case BM_SET
: tmp
= arg
; break;
2526 case BM_OR
: tmp
|= arg
; break;
2527 case BM_NAND
: tmp
&= ~arg
; break;
2528 case BM_XOR
: tmp
^= arg
; break;
2529 case BM_AND
: tmp
&= arg
; break;
2531 SetBase(IE_MC_FLAGS
, tmp
);
2534 void Actor::DialogInterrupt()
2536 //if dialoginterrupt was set, no verbal constant
2537 if ( Modified
[IE_MC_FLAGS
]&MC_NO_TALK
)
2540 /* this part is unsure */
2541 if (Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
2542 DisplayStringCore(this, VB_HOSTILE
, DS_CONSOLE
|DS_CONST
);
2544 DisplayStringCore(this, VB_DIALOG
, DS_CONSOLE
|DS_CONST
);
2548 static EffectRef fx_cure_sleep_ref
={"Cure:Sleep",NULL
,-1};
2550 void Actor::GetHit()
2552 SetStance( IE_ANI_DAMAGE
);
2553 DisplayStringCore(this, VB_DAMAGE
, DS_CONSOLE
|DS_CONST
);
2554 if (Modified
[IE_STATE_ID
]&STATE_SLEEP
) {
2555 if (Modified
[IE_EXTSTATE_ID
]&EXTSTATE_NO_WAKEUP
) {
2558 Effect
*fx
= EffectQueue::CreateEffect(fx_cure_sleep_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
2559 fxqueue
.AddEffect(fx
);
2563 bool Actor::HandleCastingStance(const ieResRef SpellResRef
, bool deplete
)
2566 if (! spellbook
.HaveSpell( SpellResRef
, HS_DEPLETE
)) {
2567 SetStance(IE_ANI_READY
);
2571 SetStance(IE_ANI_CAST
);
2575 static EffectRef fx_sleep_ref
={"State:Helpless", NULL
, -1};
2577 //returns actual damage
2578 int Actor::Damage(int damage
, int damagetype
, Scriptable
*hitter
, int modtype
)
2580 //won't get any more hurt
2581 if (InternalFlags
& IF_REALLYDIED
) {
2585 //add lastdamagetype up ? maybe
2586 LastDamageType
|=damagetype
;
2587 if(hitter
&& hitter
->Type
==ST_ACTOR
) {
2588 LastHitter
=((Actor
*) hitter
)->GetID();
2590 //Maybe it should be something impossible like 0xffff, and use 'Someone'
2599 damage
= GetBase(IE_HITPOINTS
) - damage
;
2602 damage
= GetStat(IE_MAXHITPOINTS
) * 100 / damage
;
2605 //this shouldn't happen
2606 printMessage("Actor","Invalid damagetype!\n",RED
);
2611 ModifyDamage (this, hitter
, damage
, resisted
, damagetype
, NULL
, false);
2614 core
->SetEventFlag(EF_CLOSECONTAINER
);
2619 DisplayCombatFeedback(damage
, resisted
, damagetype
, hitter
);
2621 if (BaseStats
[IE_HITPOINTS
] <= (ieDword
) damage
) {
2622 // common fists do normal damage, but cause sleeping for a round instead of death
2623 if ((damagetype
& DAMAGE_STUNNING
) && Modified
[IE_MINHITPOINTS
] <= 0) {
2624 NewBase(IE_HITPOINTS
, 1, MOD_ABSOLUTE
);
2625 Effect
*fx
= EffectQueue::CreateEffect(fx_sleep_ref
, 0, 0, FX_DURATION_INSTANT_LIMITED
);
2626 fx
->Duration
= core
->Time
.round_sec
; // 1 round
2627 core
->ApplyEffect(fx
, this, this);
2630 NewBase(IE_HITPOINTS
, (ieDword
) -damage
, MOD_ADDITIVE
);
2633 NewBase(IE_HITPOINTS
, (ieDword
) -damage
, MOD_ADDITIVE
);
2635 // also apply reputation damage if we hurt (but not killed) an innocent
2636 if (Modified
[IE_CLASS
] == CLASS_INNOCENT
) {
2637 core
->GetGame()->SetReputation(core
->GetGame()->Reputation
+ core
->GetReputationMod(1));
2642 InternalFlags
|=IF_ACTIVE
;
2643 int chp
= (signed) BaseStats
[IE_HITPOINTS
];
2644 int damagelevel
= 0;
2648 NewBase(IE_MORALE
, (ieDword
) -1, MOD_ADDITIVE
);
2652 if (damagetype
& (DAMAGE_FIRE
|DAMAGE_MAGICFIRE
) ) {
2653 PlayDamageAnimation(DL_FIRE
+damagelevel
);
2654 } else if (damagetype
& (DAMAGE_COLD
|DAMAGE_MAGICCOLD
) ) {
2655 PlayDamageAnimation(DL_COLD
+damagelevel
);
2656 } else if (damagetype
& (DAMAGE_ELECTRICITY
) ) {
2657 PlayDamageAnimation(DL_ELECTRICITY
+damagelevel
);
2658 } else if (damagetype
& (DAMAGE_ACID
) ) {
2659 PlayDamageAnimation(DL_ACID
+damagelevel
);
2660 } else if (damagetype
& (DAMAGE_MAGIC
) ) {
2661 PlayDamageAnimation(DL_DISINTEGRATE
+damagelevel
);
2664 PlayDamageAnimation(DL_CRITICAL
);
2666 PlayDamageAnimation(DL_BLOOD
+damagelevel
);
2671 if (chp
<(signed) Modified
[IE_MAXHITPOINTS
]/10) {
2672 core
->Autopause(AP_WOUNDED
);
2675 core
->Autopause(AP_HIT
);
2676 core
->SetEventFlag(EF_PORTRAIT
);
2683 void Actor::DisplayCombatFeedback (unsigned int damage
, int resisted
, int damagetype
, Scriptable
*hitter
)
2685 bool detailed
= false;
2686 const char *type_name
= "unknown";
2687 if (displaymsg
->HasStringReference(STR_DMG_SLASHING
)) { // how and iwd2
2688 std::multimap
<ieDword
, DamageInfoStruct
>::iterator it
;
2689 it
= core
->DamageInfoMap
.find(damagetype
);
2690 if (it
!= core
->DamageInfoMap
.end()) {
2691 type_name
= core
->GetString(it
->second
.strref
, 0);
2696 if (damage
> 0 && resisted
!= DR_IMMUNE
) {
2697 printMessage("Actor", " ", GREEN
);
2698 printf("%d damage taken.\n", damage
);
2701 // 3 choices depending on resistance and boni
2702 // iwd2 also has two Tortoise Shell (spell) absorption strings
2703 core
->GetTokenDictionary()->SetAtCopy( "TYPE", type_name
);
2704 core
->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage
);
2705 if (hitter
&& hitter
->Type
== ST_ACTOR
) {
2706 core
->GetTokenDictionary()->SetAtCopy( "DAMAGER", hitter
->GetName(1) );
2708 core
->GetTokenDictionary()->SetAtCopy( "DAMAGER", "trap" );
2711 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage bonus)
2712 core
->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted
));
2713 displaymsg
->DisplayConstantStringName(STR_DAMAGE3
, 0xffffff, this);
2714 } else if (resisted
> 0) {
2715 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage resisted)
2716 core
->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted
));
2717 displaymsg
->DisplayConstantStringName(STR_DAMAGE2
, 0xffffff, this);
2719 //Takes <AMOUNT> <TYPE> damage from <DAMAGER>
2720 displaymsg
->DisplayConstantStringName(STR_DAMAGE1
, 0xffffff, this);
2722 } else if (stricmp( core
->GameType
, "pst" ) == 0) {
2723 if(0) printf("TODO: pst floating text\n");
2724 } else if (displaymsg
->HasStringReference(STR_DAMAGE1
) || !hitter
|| hitter
->Type
!= ST_ACTOR
) {
2726 // or traps: "Damage Taken (damage)", but there's no token
2728 snprintf(tmp
, sizeof(tmp
), "Damage Taken (%d)", damage
);
2729 displaymsg
->DisplayStringName(tmp
, 0xffffff, this);
2731 //<DAMAGER> did <AMOUNT> damage to <DAMAGEE>
2732 core
->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2733 // wipe the DAMAGER token, so we can color it
2734 core
->GetTokenDictionary()->SetAtCopy( "DAMAGER", "" );
2735 core
->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage
);
2736 displaymsg
->DisplayConstantStringName(STR_DAMAGE1
, 0xffffff, hitter
);
2739 if (resisted
== DR_IMMUNE
) {
2740 printMessage("Actor", " ", GREEN
);
2741 printf("is immune to damage type: %s.\n", type_name
);
2742 if (hitter
&& hitter
->Type
== ST_ACTOR
) {
2744 //<DAMAGEE> was immune to my <TYPE> damage
2745 core
->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2746 core
->GetTokenDictionary()->SetAtCopy( "TYPE", type_name
);
2747 displaymsg
->DisplayConstantStringName(STR_DAMAGE_IMMUNITY
, 0xffffff, hitter
);
2748 } else if (displaymsg
->HasStringReference(STR_DAMAGE_IMMUNITY
) && displaymsg
->HasStringReference(STR_DAMAGE1
)) {
2750 //<DAMAGEE> was immune to my damage.
2751 core
->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2752 displaymsg
->DisplayConstantStringName(STR_DAMAGE_IMMUNITY
, 0xffffff, hitter
);
2753 } // else: other games don't display anything
2756 // mirror image or stoneskin: no message
2761 DataFileMgr
*resdata
= core
->GetResDataINI();
2763 PlayHitSound(resdata
, damagetype
, false);
2767 //Play PST specific hit sounds (HIT_0<dtype><armor>)
2768 void Actor::PlayHitSound(DataFileMgr
*resdata
, int damagetype
, bool suffix
)
2772 switch(damagetype
) {
2773 case DAMAGE_SLASHING
: type
= 1; break; //slashing
2774 case DAMAGE_PIERCING
: type
= 2; break; //piercing
2775 case DAMAGE_CRUSHING
: type
= 3; break; //crushing
2776 case DAMAGE_MISSILE
: type
= 4; break; //missile
2777 default: return; //other
2782 unsigned int animid
=BaseStats
[IE_ANIMATION_ID
];
2783 if(core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
2787 snprintf(section
,10,"%d", animid
);
2789 int armor
= resdata
->GetKeyAsInt(section
, "armor",0);
2790 if (armor
<0 || armor
>35) return;
2792 snprintf(Sound
,8,"HIT_0%d%c%c",type
, armor
+'A', suffix
?'1':0);
2794 core
->GetAudioDrv()->Play( Sound
,Pos
.x
,Pos
.y
,0 );
2797 //Just to quickly inspect debug maximum values
2799 void Actor::DumpMaxValues()
2801 int symbol
= core
->LoadSymbol( "stats" );
2802 SymbolMgr
*sym
= core
->GetSymbol( symbol
);
2804 for(int i
=0;i
<MAX_STATS
;i
++) {
2805 printf("%d (%s) %d\n", i
, sym
->GetValue(i
), maximum_values
[i
]);
2810 void Actor::DebugDump()
2814 printf( "Debugdump of Actor %s (%s, %s):\n", LongName
, ShortName
, GetName(-1) );
2815 printf ("Scripts:");
2816 for (i
= 0; i
< MAX_SCRIPTS
; i
++) {
2817 const char* poi
= "<none>";
2819 poi
= Scripts
[i
]->GetName();
2821 printf( " %.8s", poi
);
2823 printf( "\nArea: %.8s ", Area
);
2824 printf( "Dialog: %.8s\n", Dialog
);
2825 printf( "Global ID: %d Local ID: %d\n", globalID
, localID
);
2826 printf( "Script name:%.32s\n", scriptName
);
2827 printf( "TalkCount: %d ", TalkCount
);
2828 printf( "PartySlot: %d\n", InParty
);
2829 printf( "Allegiance: %d current allegiance:%d\n", BaseStats
[IE_EA
], Modified
[IE_EA
] );
2830 printf( "Class: %d current class:%d\n", BaseStats
[IE_CLASS
], Modified
[IE_CLASS
] );
2831 printf( "Race: %d current race:%d\n", BaseStats
[IE_RACE
], Modified
[IE_RACE
] );
2832 printf( "Gender: %d current gender:%d\n", BaseStats
[IE_SEX
], Modified
[IE_SEX
] );
2833 printf( "Specifics: %d current specifics:%d\n", BaseStats
[IE_SPECIFIC
], Modified
[IE_SPECIFIC
] );
2834 printf( "Alignment: %x current alignment:%x\n", BaseStats
[IE_ALIGNMENT
], Modified
[IE_ALIGNMENT
] );
2835 printf( "Morale: %d current morale:%d\n", BaseStats
[IE_MORALE
], Modified
[IE_MORALE
] );
2836 printf( "Moralebreak:%d Morale recovery:%d\n", Modified
[IE_MORALEBREAK
], Modified
[IE_MORALERECOVERYTIME
] );
2837 printf( "Visualrange:%d (Explorer: %d)\n", Modified
[IE_VISUALRANGE
], Modified
[IE_EXPLORE
] );
2838 printf( "current HP:%d\n", BaseStats
[IE_HITPOINTS
] );
2839 printf( "Mod[IE_ANIMATION_ID]: 0x%04X\n", Modified
[IE_ANIMATION_ID
] );
2840 printf( "Colors: ");
2841 if (core
->HasFeature(GF_ONE_BYTE_ANIMID
) ) {
2842 for(i
=0;i
<Modified
[IE_COLORCOUNT
];i
++) {
2843 printf(" %d", Modified
[IE_COLORS
+i
]);
2848 printf(" %d", Modified
[IE_COLORS
+i
]);
2851 printf( "\nWaitCounter: %d\n", (int) GetWait());
2852 printf( "LastTarget: %d %s\n", LastTarget
, GetActorNameByID(LastTarget
));
2853 printf( "LastTalked: %d %s\n", LastTalkedTo
, GetActorNameByID(LastTalkedTo
));
2862 const char* Actor::GetActorNameByID(ieDword ID
) const
2864 Actor
*actor
= GetCurrentArea()->GetActorByGlobalID(ID
);
2868 return actor
->GetScriptName();
2871 void Actor::SetMap(Map
*map
, ieWord LID
, ieWord GID
)
2873 //Did we have an area?
2874 bool effinit
=!GetCurrentArea();
2875 //now we have an area
2876 Scriptable::SetMap(map
);
2877 //unless we just lost it, in that case clear up some fields and leave
2879 //the local ID is surely illegal after the map is nulled
2881 //more bits may or may not be needed
2882 InternalFlags
&=~IF_CLEANUP
;
2889 //These functions are called once when the actor is first put in
2890 //the area. It already has all the items (including fist) at this
2891 //time and it is safe to call effects.
2892 //This hack is to delay the equipping effects until the actor has
2893 //an area (and the game object is also existing)
2895 int SlotCount
= inventory
.GetSlotCount();
2896 for (int Slot
= 0; Slot
<SlotCount
;Slot
++) {
2897 int slottype
= core
->QuerySlotEffects( Slot
);
2899 case SLOT_EFFECT_NONE
:
2900 case SLOT_EFFECT_MELEE
:
2903 inventory
.EquipItem( Slot
);
2907 //We need to convert this to signed 16 bits, because
2908 //it is actually a 16 bit number.
2909 //It is signed to have the correct math
2910 //when adding it to the base slot (SLOT_WEAPON) in
2911 //case of quivers. (weird IE magic)
2912 //The other word is the equipped header.
2913 //find a quiver for the bow, etc
2914 if (Equipped
!=IW_NO_EQUIPPED
) {
2915 inventory
.EquipItem( Equipped
+inventory
.GetWeaponSlot());
2916 SetEquippedQuickSlot( inventory
.GetEquipped(), EquippedHeader
);
2921 void Actor::SetPosition(const Point
&position
, int jump
, int radius
)
2926 p
.x
= position
.x
/16;
2927 p
.y
= position
.y
/12;
2928 if (jump
&& !(Modified
[IE_DONOTJUMP
] & DNJ_FIT
) && size
) {
2929 GetCurrentArea()->AdjustPosition( p
, radius
);
2936 /* this is returning the level of the character for xp calculations
2937 and the average level for dual/multiclass (rounded up),
2938 also with iwd2's 3rd ed rules, this is why it is a separate function */
2939 ieDword
Actor::GetXPLevel(int modified
) const
2941 const ieDword
*stats
;
2950 float classcount
= 0;
2952 if (core
->HasFeature(GF_LEVELSLOT_PER_CLASS
)) {
2954 for (int i
=0; i
< 11; i
++) {
2955 if (stats
[levelslotsiwd2
[i
]] > 0) classcount
++;
2957 average
= stats
[IE_CLASSLEVELSUM
] / classcount
+ 0.5;
2960 int levels
[3]={stats
[IE_LEVEL
], stats
[IE_LEVEL2
], stats
[IE_LEVEL3
]};
2961 average
= levels
[0];
2963 if (IsDualClassed()) {
2965 if (levels
[1] > 0) {
2967 average
+= levels
[1];
2970 else if (IsMultiClassed()) {
2971 //classcount is the number of on bits in the MULTI field
2972 classcount
= bitcount (multiclass
);
2973 for (int i
=1; i
<classcount
; i
++)
2974 average
+= levels
[i
];
2975 } //else single classed
2976 average
= average
/ classcount
+ 0.5;
2978 return ieDword(average
);
2981 int Actor::GetWildMod(int level
) const
2983 if(GetStat(IE_KIT
)&0x8000) {
2984 if (level
>=MAX_LEVEL
) level
=MAX_LEVEL
;
2985 if(level
<1) level
=1;
2986 return wmlevels
[core
->Roll(1,20,-1)][level
-1];
2991 int Actor::CastingLevelBonus(int level
, int type
) const
2997 bonus
= GetStat(IE_CASTINGLEVELBONUSCLERIC
);
3000 bonus
= GetWildMod(level
) + GetStat(IE_CASTINGLEVELBONUSMAGE
);
3007 core
->GetTokenDictionary()->SetAtCopy("LEVELDIF", bonus
);
3010 displaymsg
->DisplayConstantStringName(STR_CASTER_LVL_INC
, 0xffffff, this);
3012 displaymsg
->DisplayConstantStringName(STR_CASTER_LVL_DEC
, 0xffffff, this);
3018 /** maybe this would be more useful if we calculate with the strength too
3020 int Actor::GetEncumbrance()
3022 return inventory
.GetWeight();
3025 EffectRef control_undead_ref
= { "ControlUndead2", NULL
, -1};
3028 void Actor::Turn(Scriptable
*cleric
, ieDword turnlevel
)
3030 //this is safely hardcoded i guess
3031 if (Modified
[IE_GENERAL
]!=GEN_UNDEAD
) {
3039 //determine if we see the cleric (distance)
3040 if (!CanSee(cleric
, this, true, GA_NO_DEAD
)) {
3044 //determine alignment (if equals, then no turning)
3046 LastTurner
= cleric
->GetGlobalID();
3048 //determine panic or destruction/control
3049 //we get the modified level
3050 if (turnlevel
- TURN_DEATH_LVL_MOD
>= GetXPLevel(true)) {
3051 if (cleric
->Type
== ST_ACTOR
&& ((Actor
*)cleric
)->MatchesAlignmentMask(AL_EVIL
)) {
3052 Effect
*fx
= fxqueue
.CreateEffect(control_undead_ref
, GEN_UNDEAD
, 3, FX_DURATION_INSTANT_LIMITED
);
3053 fx
->Duration
= core
->Time
.round_sec
;
3054 fx
->Target
= FX_TARGET_PRESET
;
3055 core
->ApplyEffect(fx
, this, cleric
);
3060 } else if (turnlevel
- TURN_PANIC_LVL_MOD
>= GetXPLevel(true)) {
3065 //TODO: needs a way to respawn at a point
3066 void Actor::Resurrect()
3068 if (!(Modified
[IE_STATE_ID
] & STATE_DEAD
)) {
3071 InternalFlags
&=IF_FROMGAME
; //keep these flags (what about IF_INITIALIZED)
3072 InternalFlags
|=IF_ACTIVE
|IF_VISIBLE
; //set these flags
3073 SetBase(IE_STATE_ID
, 0);
3074 SetBase(IE_MORALE
, 10);
3075 SetBase(IE_HITPOINTS
, BaseStats
[IE_MAXHITPOINTS
]);
3078 SetStance(IE_ANI_EMERGE
);
3079 //readjust death variable on resurrection
3080 if (core
->HasFeature(GF_HAS_KAPUTZ
) && (AppearanceFlags
&APP_DEATHVAR
)) {
3081 ieVariable DeathVar
;
3083 snprintf(DeathVar
,sizeof(ieVariable
),"%s_DEAD",scriptName
);
3084 Game
*game
=core
->GetGame();
3087 game
->kaputz
->Lookup(DeathVar
, value
);
3089 game
->kaputz
->SetAt(DeathVar
, value
-1);
3095 static EffectRef fx_cure_poisoned_state_ref
={"Cure:Poison",NULL
,-1};
3096 static EffectRef fx_cure_hold_state_ref
={"Cure:Hold",NULL
,-1};
3097 static EffectRef fx_cure_stun_state_ref
={"Cure:Stun",NULL
,-1};
3098 static EffectRef fx_remove_portrait_icon_ref
={"Icon:Remove",NULL
,-1};
3099 static EffectRef fx_unpause_caster_ref
={"Cure:CasterHold",NULL
,-1};
3101 void Actor::Die(Scriptable
*killer
)
3105 if (InternalFlags
&IF_REALLYDIED
) {
3106 return; //can die only once
3109 int minhp
= (signed) Modified
[IE_MINHITPOINTS
];
3110 if (minhp
> 0) { //can't die
3111 SetBase(IE_HITPOINTS
, minhp
);
3115 //Can't simply set Selected to false, game has its own little list
3116 Game
*game
= core
->GetGame();
3117 game
->SelectActor(this, false, SELECT_NORMAL
);
3118 game
->OutAttack(GetID());
3120 displaymsg
->DisplayConstantStringName(STR_DEATH
, 0xffffff, this);
3121 DisplayStringCore(this, VB_DIE
, DS_CONSOLE
|DS_CONST
);
3123 // remove poison, hold, casterhold, stun and its icon
3125 newfx
= EffectQueue::CreateEffect(fx_cure_poisoned_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
3126 core
->ApplyEffect(newfx
, this, this);
3128 newfx
= EffectQueue::CreateEffect(fx_cure_hold_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
3129 core
->ApplyEffect(newfx
, this, this);
3131 newfx
= EffectQueue::CreateEffect(fx_unpause_caster_ref
, 0, 100, FX_DURATION_INSTANT_PERMANENT
);
3132 core
->ApplyEffect(newfx
, this, this);
3134 newfx
= EffectQueue::CreateEffect(fx_cure_stun_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
3135 core
->ApplyEffect(newfx
, this, this);
3137 newfx
= EffectQueue::CreateEffect(fx_remove_portrait_icon_ref
, 0, 37, FX_DURATION_INSTANT_PERMANENT
);
3138 core
->ApplyEffect(newfx
, this, this);
3141 // clearing the search map here means it's not blocked during death animations
3142 // this is perhaps not ideal, but matches other searchmap code which uses
3143 // GA_NO_DEAD to exclude IF_JUSTDIED actors as well as dead ones
3145 area
->ClearSearchMapFor(this);
3147 //JUSTDIED will be removed when the Die() trigger executed
3148 //otherwise it is the same as REALLYDIED
3149 InternalFlags
|=IF_REALLYDIED
|IF_JUSTDIED
;
3150 SetStance( IE_ANI_DIE
);
3153 game
->PartyMemberDied(this);
3154 core
->Autopause(AP_DEAD
);
3158 killer
= area
->GetActorByGlobalID(LastHitter
);
3162 if (killer
->Type
==ST_ACTOR
) {
3163 act
= (Actor
*) killer
;
3169 //adjust kill statistics here
3170 PCStatsStruct
*stat
= act
->PCStats
;
3172 stat
->NotifyKill(Modified
[IE_XPVALUE
], ShortStrRef
);
3174 InternalFlags
|=IF_GIVEXP
;
3177 // friendly party summons' kills also grant xp
3178 if (act
->Modified
[IE_SEX
] == SEX_SUMMON
&& act
->Modified
[IE_EA
] == EA_CONTROLLED
) {
3179 InternalFlags
|=IF_GIVEXP
;
3180 } else if (act
->Modified
[IE_EA
] == EA_FAMILIAR
) {
3181 // familiar's kills also grant xp
3182 InternalFlags
|=IF_GIVEXP
;
3187 // XP seems to be handed at out at the moment of death
3188 if (InternalFlags
&IF_GIVEXP
) {
3189 //give experience to party
3190 game
->ShareXP(Modified
[IE_XPVALUE
], sharexp
);
3193 // adjust reputation if the corpse was:
3194 // an innocent, a member of the Flaming Fist or something evil
3196 if (Modified
[IE_CLASS
] == CLASS_INNOCENT
) {
3197 repmod
= core
->GetReputationMod(0);
3198 } else if (Modified
[IE_CLASS
] == CLASS_FLAMINGFIST
) {
3199 repmod
= core
->GetReputationMod(3);
3201 if (MatchesAlignmentMask(AL_EVIL
)) {
3202 repmod
+= core
->GetReputationMod(7);
3205 game
->SetReputation(game
->Reputation
+ repmod
);
3210 //moved clear actions after xp is given
3211 //it clears some triggers, including the LastHitter
3214 SetModal( MS_NONE
);
3219 // death variables are updated at the moment of death
3221 if (core
->HasFeature(GF_HAS_KAPUTZ
) ) {
3222 game
->kaputz
->Lookup(KillVar
, value
);
3223 game
->kaputz
->SetAt(KillVar
, value
+1);
3225 // iwd/iwd2 path *sets* this var, so i changed it, not sure about pst above
3226 game
->locals
->SetAt(KillVar
, 1);
3229 if (IncKillVar
[0]) {
3231 game
->locals
->Lookup(IncKillVar
, value
);
3232 game
->locals
->SetAt(IncKillVar
, value
+ 1);
3235 if (scriptName
[0]) {
3237 if (core
->HasFeature(GF_HAS_KAPUTZ
) ) {
3238 if (AppearanceFlags
&APP_DEATHVAR
) {
3239 snprintf(varname
, 32, "%s_DEAD", scriptName
);
3240 game
->kaputz
->Lookup(varname
, value
);
3241 game
->kaputz
->SetAt(varname
, value
+1);
3243 if (AppearanceFlags
&APP_DEATHTYPE
) {
3244 snprintf(varname
, 32, "KILL_%s", KillVar
);
3245 game
->kaputz
->Lookup(varname
, value
);
3246 game
->kaputz
->SetAt(varname
, value
+1);
3249 snprintf(varname
, 32, core
->GetDeathVarFormat(), scriptName
);
3250 game
->locals
->Lookup(varname
, value
);
3251 game
->locals
->SetAt(varname
, value
+1);
3256 snprintf(varname
, 32, "%s_DEAD", scriptName
);
3257 game
->locals
->Lookup(varname
, value
);
3258 game
->locals
->SetAt(varname
, 1);
3260 snprintf(varname
, 32, "%s_KILL_CNT", scriptName
);
3262 game
->locals
->Lookup(varname
, value
);
3263 game
->locals
->SetAt(varname
, value
+ 1);
3269 // racial dead count
3271 int racetable
= core
->LoadSymbol("race");
3272 if (racetable
!= -1) {
3273 Holder
<SymbolMgr
> race
= core
->GetSymbol(racetable
);
3274 const char *raceName
= race
->GetValue(Modified
[IE_RACE
]);
3276 // todo: should probably not set this for humans in iwd?
3277 snprintf(varname
, 32, "KILL_%s_CNT", raceName
);
3278 game
->locals
->Lookup(varname
, value
);
3279 game
->locals
->SetAt(varname
, value
+1);
3284 //death counters for PST
3287 if (AppearanceFlags
&j
) {
3289 game
->locals
->Lookup(CounterNames
[i
], value
);
3290 game
->locals
->SetAt(CounterNames
[i
], value
+DeathCounters
[i
]);
3295 // EXTRACOUNT is updated at the moment of death
3296 if (Modified
[IE_SEX
] == SEX_EXTRA
|| (Modified
[IE_SEX
] >= SEX_EXTRA2
&& Modified
[IE_SEX
] <= SEX_MAXEXTRA
)) {
3297 // if gender is set to one of the EXTRA values, then at death, we have to decrease
3298 // the relevant EXTRACOUNT area variable. scripts use this to check how many actors
3299 // of this extra id are still alive (for example, see the ToB challenge scripts)
3301 if (Modified
[IE_SEX
] == SEX_EXTRA
) {
3302 snprintf(varname
, 32, "EXTRACOUNT");
3304 snprintf(varname
, 32, "EXTRACOUNT%d", 2 + (Modified
[IE_SEX
] - SEX_EXTRA2
));
3307 Map
*area
= GetCurrentArea();
3310 area
->locals
->Lookup(varname
, value
);
3311 // i am guessing that we shouldn't decrease below 0
3313 area
->locals
->SetAt(varname
, value
-1);
3318 //a plot critical creature has died (iwd2)
3319 if (BaseStats
[IE_MC_FLAGS
]&MC_PLOT_CRITICAL
) {
3320 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindowPlot", false);
3322 //ensure that the scripts of the actor will run as soon as possible
3326 void Actor::SetPersistent(int partyslot
)
3328 InParty
= (ieByte
) partyslot
;
3329 InternalFlags
|=IF_FROMGAME
;
3330 //if an actor is coming from a game, it should have these too
3334 void Actor::DestroySelf()
3336 InternalFlags
|=IF_CLEANUP
;
3337 // clear search map so that a new actor can immediately go there
3338 // (via ChangeAnimationCore)
3340 area
->ClearSearchMapFor(this);
3343 bool Actor::CheckOnDeath()
3345 if (InternalFlags
&IF_CLEANUP
) {
3348 if (InternalFlags
&IF_JUSTDIED
) {
3349 if (lastRunTime
== 0 || CurrentAction
|| GetNextAction()) {
3350 return false; //actor is currently dying, let him die first
3353 if (!(InternalFlags
&IF_REALLYDIED
) ) {
3356 //don't mess with the already deceased
3357 if (BaseStats
[IE_STATE_ID
]&STATE_DEAD
) {
3360 // don't destroy actors currently in a dialog
3361 GameControl
*gc
= core
->GetGameControl();
3362 if (gc
&& (globalID
== gc
->targetID
|| globalID
== gc
->speakerID
)) {
3366 //we need to check animID here, if it has not played the death
3367 //sequence yet, then we could return now
3369 //missed the opportunity of Died()
3370 InternalFlags
&=~IF_JUSTDIED
;
3372 // items seem to be dropped at the moment of death
3373 // .. but this can't go in Die() because that is called
3374 // from effects and dropping items might change effects!
3377 //remove all effects that are not 'permanent after death' here
3378 //permanent after death type is 9
3379 SetBaseBit(IE_STATE_ID
, STATE_DEAD
, true);
3381 // party actors are never removed
3382 if (Persistent()) return false;
3384 if (Modified
[IE_MC_FLAGS
]&MC_REMOVE_CORPSE
) return true;
3385 if (Modified
[IE_MC_FLAGS
]&MC_KEEP_CORPSE
) return false;
3386 //if chunked death, then return true
3387 if (LastDamageType
&DAMAGE_CHUNKING
) {
3388 //play chunky animation
3389 //chunks are projectiles
3395 /* this will create a heap at location, and transfer the item(s) */
3396 void Actor::DropItem(const ieResRef resref
, unsigned int flags
)
3398 if (inventory
.DropItemAtLocation( resref
, flags
, area
, Pos
)) {
3403 void Actor::DropItem(int slot
, unsigned int flags
)
3405 if (inventory
.DropItemAtLocation( slot
, flags
, area
, Pos
)) {
3410 /** returns quick item data */
3411 /** if header==-1 which is a 'use quickitem' action */
3412 /** if header is set, then which is the absolute slot index, */
3413 /** and header is the header index */
3414 void Actor::GetItemSlotInfo(ItemExtHeader
*item
, int which
, int header
)
3419 memset(item
, 0, sizeof(ItemExtHeader
) );
3421 if (!PCStats
) return; //not a player character
3422 PCStats
->GetSlotAndIndex(which
,idx
,headerindex
);
3423 if (headerindex
==0xffff) return; //headerindex is invalid
3426 headerindex
=(ieWord
) header
;
3428 const CREItem
*slot
= inventory
.GetSlotItem(idx
);
3429 if (!slot
) return; //quick item slot is empty
3430 Item
*itm
= gamedata
->GetItem(slot
->ItemResRef
);
3431 if (!itm
) return; //quick item slot contains invalid item resref
3432 ITMExtHeader
*ext_header
= itm
->GetExtHeader(headerindex
);
3433 //item has no extended header, or header index is wrong
3434 if (!ext_header
) return;
3435 memcpy(item
->itemname
, slot
->ItemResRef
, sizeof(ieResRef
) );
3437 item
->headerindex
= headerindex
;
3438 memcpy(&(item
->AttackType
), &(ext_header
->AttackType
),
3439 ((char *) &(item
->itemname
)) -((char *) &(item
->AttackType
)) );
3440 if (headerindex
>=CHARGE_COUNTERS
) {
3443 item
->Charges
=slot
->Usages
[headerindex
];
3445 gamedata
->FreeItem(itm
,slot
->ItemResRef
, false);
3448 void Actor::ReinitQuickSlots()
3454 // Note: (wjp, 20061226)
3455 // This function needs some rethinking.
3456 // It tries to satisfy two things at the moment:
3457 // Fill quickslots when they are empty and an item is placed in the
3458 // inventory slot corresponding to the quickslot
3459 // Reset quickslots when an item is removed
3460 // Currently, it resets all slots when items are removed,
3461 // but it only refills the ACT_QSLOTn slots, not the ACT_WEAPONx slots.
3463 // Refilling a weapon slot is possible, but essentially duplicates a lot
3464 // of code from Inventory::EquipItem() which performs the same steps for
3465 // the Inventory::Equipped slot.
3466 // Hopefully, weapons/arrows are never added to inventory slots without
3467 // EquipItem being called.
3469 int i
=sizeof(PCStats
->QSlots
);
3473 if (i
<0) which
= ACT_WEAPON4
+i
+1;
3474 else which
= PCStats
->QSlots
[i
];
3480 CheckWeaponQuickSlot(which
-ACT_WEAPON1
);
3483 //WARNING:this cannot be condensed, because the symbols don't come in order!!!
3484 case ACT_QSLOT1
: slot
= inventory
.GetQuickSlot(); break;
3485 case ACT_QSLOT2
: slot
= inventory
.GetQuickSlot()+1; break;
3486 case ACT_QSLOT3
: slot
= inventory
.GetQuickSlot()+2; break;
3487 case ACT_QSLOT4
: slot
= inventory
.GetQuickSlot()+3; break;
3488 case ACT_QSLOT5
: slot
= inventory
.GetQuickSlot()+4; break;
3492 if (!slot
) continue;
3493 //if magic items are equipped the equipping info doesn't change
3496 // Note: we're now in the QSLOTn case
3497 // If slot is empty, reset quickslot to 0xffff/0xffff
3499 if (!inventory
.HasItemInSlot("", slot
)) {
3500 SetupQuickSlot(which
, 0xffff, 0xffff);
3504 PCStats
->GetSlotAndIndex(which
,idx
,headerindex
);
3505 if (idx
!= slot
|| headerindex
== 0xffff) {
3506 // If slot just became filled, set it to filled
3507 SetupQuickSlot(which
,slot
,0);
3512 //these are always present
3513 CheckWeaponQuickSlot(0);
3514 CheckWeaponQuickSlot(1);
3515 //disabling quick weapon slots for certain classes
3517 int which
= ACT_WEAPON3
+i
;
3518 // Assuming that ACT_WEAPON3 and 4 are always in the first two spots
3519 if (PCStats
->QSlots
[i
]!=which
) {
3520 SetupQuickSlot(which
, 0xffff, 0xffff);
3525 void Actor::CheckWeaponQuickSlot(unsigned int which
)
3527 if (!PCStats
) return;
3530 // If current quickweaponslot doesn't contain an item, reset it to fist
3531 int slot
= PCStats
->QuickWeaponSlots
[which
];
3532 int header
= PCStats
->QuickWeaponHeaders
[which
];
3533 if (!inventory
.HasItemInSlot("", slot
) || header
== 0xffff) {
3534 //a quiver just went dry, falling back to fist
3537 // If current quickweaponslot contains ammo, and bow not found, reset
3539 if (core
->QuerySlotEffects(slot
) == SLOT_EFFECT_MISSILE
) {
3540 const CREItem
*slotitm
= inventory
.GetSlotItem(slot
);
3542 Item
*itm
= gamedata
->GetItem(slotitm
->ItemResRef
);
3544 ITMExtHeader
*ext_header
= itm
->GetExtHeader(header
);
3546 int type
= ext_header
->ProjectileQualifier
;
3547 int weaponslot
= inventory
.FindTypedRangedWeapon(type
);
3548 if (weaponslot
== inventory
.GetFistSlot()) {
3554 gamedata
->FreeItem(itm
,slotitm
->ItemResRef
, false);
3559 SetupQuickSlot(ACT_WEAPON1
+which
, inventory
.GetFistSlot(), 0);
3563 void Actor::SetupQuickSlot(unsigned int which
, int slot
, int headerindex
)
3565 if (!PCStats
) return;
3566 PCStats
->InitQuickSlot(which
, slot
, headerindex
);
3567 //something changed about the quick items
3568 core
->SetEventFlag(EF_ACTION
);
3571 bool Actor::ValidTarget(int ga_flags
) const
3573 //scripts can still see this type of actor
3575 if (ga_flags
&GA_NO_HIDDEN
) {
3576 if (Modified
[IE_AVATARREMOVAL
]) return false;
3577 if (Modified
[IE_EA
]>EA_GOODCUTOFF
&& Modified
[IE_STATE_ID
]&STATE_INVISIBLE
) return false;
3580 if (ga_flags
&GA_NO_ALLY
) {
3581 if(InParty
) return false;
3582 if(Modified
[IE_EA
]<=EA_GOODCUTOFF
) return false;
3585 if (ga_flags
&GA_NO_ENEMY
) {
3586 if(!InParty
&& (Modified
[IE_EA
]>=EA_EVILCUTOFF
) ) return false;
3589 if (ga_flags
&GA_NO_NEUTRAL
) {
3590 if((Modified
[IE_EA
]>EA_GOODCUTOFF
) && (Modified
[IE_EA
]<EA_EVILCUTOFF
) ) return false;
3593 switch(ga_flags
&GA_ACTION
) {
3595 if (Modified
[IE_STATE_ID
] & STATE_CANTSTEAL
) return false;
3598 //can't talk to dead
3599 if (Modified
[IE_STATE_ID
] & STATE_CANTLISTEN
) return false;
3600 //can't talk to hostile
3601 if (Modified
[IE_EA
]>=EA_EVILCUTOFF
) return false;
3604 if (ga_flags
&GA_NO_DEAD
) {
3605 if (InternalFlags
&IF_JUSTDIED
) return false;
3606 if (Modified
[IE_STATE_ID
] & STATE_DEAD
) return false;
3608 if (ga_flags
&GA_SELECT
) {
3609 if (UnselectableTimer
) return false;
3610 if (Immobile()) return false;
3615 //returns true if it won't be destroyed with an area
3616 //in this case it shouldn't be saved with the area either
3617 //it will be saved in the savegame
3618 bool Actor::Persistent() const
3620 if (InParty
) return true;
3621 if (InternalFlags
&IF_FROMGAME
) return true;
3625 //this is a reimplementation of cheatkey a/s of bg2
3626 //cycling through animation/stance
3627 // a - get next animation, s - get next stance
3629 void Actor::GetNextAnimation()
3631 int RowNum
= anims
->AvatarsRowNum
- 1;
3633 RowNum
= CharAnimations::GetAvatarsCount() - 1;
3634 int NewAnimID
= CharAnimations::GetAvatarStruct(RowNum
)->AnimID
;
3635 printf ("AnimID: %04X\n", NewAnimID
);
3636 SetBase( IE_ANIMATION_ID
, NewAnimID
);
3637 //SetAnimationID ( NewAnimID );
3640 void Actor::GetPrevAnimation()
3642 int RowNum
= anims
->AvatarsRowNum
+ 1;
3643 if (RowNum
>=CharAnimations::GetAvatarsCount() )
3645 int NewAnimID
= CharAnimations::GetAvatarStruct(RowNum
)->AnimID
;
3646 printf ("AnimID: %04X\n", NewAnimID
);
3647 SetBase( IE_ANIMATION_ID
, NewAnimID
);
3648 //SetAnimationID ( NewAnimID );
3651 //slot is the projectile slot
3652 //This will return the projectile item.
3653 ITMExtHeader
*Actor::GetRangedWeapon(WeaponInfo
&wi
) const
3655 //EquippedSlot is the projectile. To get the weapon, use inventory.GetUsedWeapon()
3656 wi
.slot
= inventory
.GetEquippedSlot();
3657 const CREItem
*wield
= inventory
.GetSlotItem(wi
.slot
);
3661 Item
*item
= gamedata
->GetItem(wield
->ItemResRef
);
3665 //The magic of the bow and the arrow add up?
3666 wi
.enchantment
+= item
->Enchantment
;
3667 wi
.itemflags
= wield
->Flags
;
3668 //wi.range is not set, the projectile has no effect on range?
3670 ITMExtHeader
*which
= item
->GetWeaponHeader(true);
3671 gamedata
->FreeItem(item
, wield
->ItemResRef
, false);
3675 int Actor::IsDualWielding() const
3678 //if the shield slot is a weapon, we're dual wielding
3679 const CREItem
*wield
= inventory
.GetUsedWeapon(true, slot
);
3684 Item
*itm
= gamedata
->GetItem( wield
->ItemResRef
);
3689 //if the item is usable in weapon slot, then it is weapon
3690 int weapon
= core
->CanUseItemType( SLOT_WEAPON
, itm
);
3691 gamedata
->FreeItem( itm
, wield
->ItemResRef
, false );
3692 //is just weapon>0 ok?
3693 return (weapon
>0)?1:0;
3696 //returns weapon header currently used (bow in case of bow+arrow)
3697 //if range is nonzero, then the returned header is valid
3698 ITMExtHeader
*Actor::GetWeapon(WeaponInfo
&wi
, bool leftorright
)
3700 //only use the shield slot if we are dual wielding
3701 leftorright
= leftorright
&& IsDualWielding();
3703 const CREItem
*wield
= inventory
.GetUsedWeapon(leftorright
, wi
.slot
);
3707 Item
*item
= gamedata
->GetItem(wield
->ItemResRef
);
3712 wi
.enchantment
= item
->Enchantment
;
3713 wi
.itemflags
= wield
->Flags
;
3714 wi
.prof
= item
->WeaProf
;
3716 //select first weapon header
3717 ITMExtHeader
*which
;
3718 if (GetAttackStyle() == WEAPON_RANGED
) {
3719 which
= item
->GetWeaponHeader(true);
3720 wi
.backstabbing
= false;
3722 which
= item
->GetWeaponHeader(false);
3723 // any melee weapon usable by a single class thief is game (UAI does not affect this)
3724 wi
.backstabbing
= !(item
->UsabilityBitmask
& 0x400000);
3727 //make sure we use 'false' in this freeitem
3728 //so 'which' won't point into invalid memory
3729 gamedata
->FreeItem(item
, wield
->ItemResRef
, false);
3733 if (which
->Location
!=ITEM_LOC_WEAPON
) {
3736 wi
.range
= which
->Range
+1;
3738 //return which->Range+1;
3741 void Actor::GetNextStance()
3743 static int Stance
= IE_ANI_AWAKE
;
3745 if (--Stance
< 0) Stance
= MAX_ANIMS
-1;
3746 printf ("StanceID: %d\n", Stance
);
3747 SetStance( Stance
);
3750 int Actor::LearnSpell(const ieResRef spellname
, ieDword flags
)
3752 //don't fail if the spell is also memorized (for innates)
3753 if (! (flags
&LS_MEMO
)) {
3754 if (spellbook
.HaveSpell(spellname
, 0) ) {
3758 Spell
*spell
= gamedata
->GetSpell(spellname
);
3760 return LSR_INVALID
; //not existent spell
3763 if (flags
& LS_STATS
) {
3764 // chance to learn roll
3765 if (LuckyRoll(1,100,0) > core
->GetIntelligenceBonus(0, GetStat(IE_INT
))) {
3770 int explev
= spellbook
.LearnSpell(spell
, flags
&LS_MEMO
);
3771 int tmp
= spell
->SpellName
;
3772 if (flags
&LS_LEARN
) {
3773 core
->GetTokenDictionary()->SetAt("SPECIALABILITYNAME", core
->GetString(tmp
));
3774 switch (spell
->SpellType
) {
3776 tmp
= STR_GOTABILITY
;
3786 gamedata
->FreeSpell(spell
, spellname
, false);
3791 displaymsg
->DisplayConstantStringName(tmp
, 0xbcefbc, this);
3793 if (flags
&LS_ADDXP
) {
3794 int xp
= CalculateExperience(XP_LEARNSPELL
, explev
);
3795 Game
*game
= core
->GetGame();
3796 game
->ShareXP(xp
, SX_DIVIDE
);
3801 const char *Actor::GetDialog(int flags
) const
3806 if (Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
3810 if ( (InternalFlags
& IF_NOINT
) && CurrentAction
) {
3812 displaymsg
->DisplayConstantString(STR_TARGETBUSY
,0xff0000);
3819 void Actor::CreateStats()
3822 PCStats
= new PCStatsStruct();
3826 const char* Actor::GetScript(int ScriptIndex
) const
3828 return Scripts
[ScriptIndex
]->GetName();
3831 void Actor::SetModal(ieDword newstate
, bool force
)
3838 case MS_DETECTTRAPS
:
3849 // display the turning-off message
3850 if (ModalState
!= MS_NONE
) {
3851 displaymsg
->DisplayStringName(core
->ModalStates
[ModalState
].leaving_str
, 0xffffff, this, IE_STR_SOUND
|IE_STR_SPEECH
);
3854 // when called with the same state twice, toggle to MS_NONE
3855 if (!force
&& ModalState
== newstate
) {
3856 ModalState
= MS_NONE
;
3858 ModalState
= newstate
;
3861 //update the action bar
3862 core
->SetEventFlag(EF_ACTION
);
3864 ModalState
= newstate
;
3868 void Actor::SetModalSpell(ieDword state
, const char *spell
)
3871 strnlwrcpy(ModalSpell
, spell
, 8);
3873 if (state
>= core
->ModalStates
.size()) {
3876 strnlwrcpy(ModalSpell
, core
->ModalStates
[state
].spell
, 8);
3881 //this is just a stub function for now, attackstyle could be melee/ranged
3882 //even spells got this attack style
3883 int Actor::GetAttackStyle()
3886 //Non NULL if the equipped slot is a projectile or a throwing weapon
3887 //TODO some weapons have both melee and ranged capability
3888 if (GetRangedWeapon(wi
) != NULL
) return WEAPON_RANGED
;
3889 return WEAPON_MELEE
;
3892 void Actor::SetTarget( Scriptable
*target
)
3894 if (target
->Type
==ST_ACTOR
) {
3895 Actor
*tar
= (Actor
*) target
;
3896 LastTarget
= tar
->GetID();
3897 tar
->LastAttacker
= GetID();
3898 //we tell the game object that this creature
3899 //must be added to the list of combatants
3900 core
->GetGame()->InAttack(tar
->LastAttacker
);
3902 SetOrientation( GetOrient( target
->Pos
, Pos
), false );
3905 //in case of LastTarget = 0
3906 void Actor::StopAttack()
3908 SetStance(IE_ANI_READY
);
3910 core
->GetGame()->OutAttack(GetID());
3911 InternalFlags
|=IF_TARGETGONE
; //this is for the trigger!
3913 core
->Autopause(AP_NOTARGET
);
3917 int Actor::Immobile() const
3919 if (GetStat(IE_CASTERHOLD
)) {
3922 if (GetStat(IE_HELD
)) {
3925 if (GetStat(IE_STATE_ID
) & STATE_STILL
) {
3931 //calculate how many attacks will be performed
3933 //only called when Game thinks we are in attack
3934 //so it is safe to do cleanup here (it will be called only once)
3935 void Actor::InitRound(ieDword gameTime
)
3937 lastInit
= gameTime
;
3938 secondround
= !secondround
;
3940 //roundTime will equal 0 if we aren't attacking something
3942 //only perform calculations at the beginning of the round!
3943 if (((gameTime
-roundTime
)%core
->Time
.round_size
!= 0) || \
3944 (roundTime
== lastInit
)) {
3949 //reset variables used in PerformAttack
3951 attacksperround
= 0;
3955 //we set roundTime to zero on any of the following returns, because this
3956 //is guaranteed to be the start of a round, and we only want roundTime
3957 //if we are attacking this round
3958 if (InternalFlags
&IF_STOPATTACK
) {
3959 core
->GetGame()->OutAttack(GetID());
3970 //if held or disabled, etc, then cannot continue attacking
3971 ieDword state
= GetStat(IE_STATE_ID
);
3972 if (state
&STATE_CANTMOVE
) {
3981 //add one for second round to get an extra attack only if we
3982 //are x/2 attacks per round
3983 attackcount
= GetStat(IE_NUMBEROFATTACKS
);
3987 //all numbers of attacks are stored at twice their value
3990 //make sure we always get at least 1apr
3991 if (attackcount
< 1) {
3995 //set our apr and starting round time
3996 attacksperround
= attackcount
;
3997 roundTime
= gameTime
;
3999 //print a little message :)
4000 printMessage("InitRound", " ", WHITE
);
4001 printf("Name: %s | Attacks: %d | Start: %d\n", ShortName
, attacksperround
, gameTime
);
4003 // this might not be the right place, but let's give it a go
4004 if (attacksperround
&& InParty
) {
4005 core
->Autopause(AP_ENDROUND
);
4009 bool Actor::GetCombatDetails(int &tohit
, bool leftorright
, WeaponInfo
& wi
, ITMExtHeader
*&header
, ITMExtHeader
*&hittingheader
, \
4010 ieDword
&Flags
, int &DamageBonus
, int &speed
, int &CriticalBonus
, int &style
)
4012 tohit
= GetStat(IE_TOHIT
);
4013 speed
= -GetStat(IE_PHYSICALSPEED
);
4014 bool dualwielding
= IsDualWielding();
4015 header
= GetWeapon(wi
, leftorright
&& dualwielding
);
4021 hittingheader
= header
;
4022 ITMExtHeader
*rangedheader
= NULL
;
4023 int THAC0Bonus
= hittingheader
->THAC0Bonus
;
4024 DamageBonus
= hittingheader
->DamageBonus
;
4025 switch(hittingheader
->AttackType
) {
4027 Flags
= WEAPON_MELEE
;
4029 case ITEM_AT_PROJECTILE
: //throwing weapon
4030 Flags
= WEAPON_RANGED
;
4033 rangedheader
= GetRangedWeapon(wi
);
4034 if (!rangedheader
) {
4035 //display out of ammo verbal constant if there is any???
4036 //DisplayStringCore(this, VB_OUTOFAMMO, DS_CONSOLE|DS_CONST );
4037 SetStance(IE_ANI_READY
);
4041 Flags
= WEAPON_RANGED
;
4042 //The bow can give some bonuses, but the core attack is made by the arrow.
4043 hittingheader
= rangedheader
;
4044 THAC0Bonus
+= rangedheader
->THAC0Bonus
;
4045 DamageBonus
+= rangedheader
->DamageBonus
;
4048 //item is unsuitable for fight
4049 SetStance(IE_ANI_READY
);
4052 //this flag is set by the bow in case of projectile launcher.
4053 if (header
->RechargeFlags
&IE_ITEM_USESTRENGTH
) Flags
|=WEAPON_USESTRENGTH
;
4055 // get our dual wielding modifier
4058 DamageBonus
+= GetStat(IE_DAMAGEBONUSLEFT
);
4060 DamageBonus
+= GetStat(IE_DAMAGEBONUSRIGHT
);
4063 leftorright
= leftorright
&& dualwielding
;
4064 if (leftorright
) Flags
|=WEAPON_LEFTHAND
;
4066 //add in proficiency bonuses
4068 if (wi
.prof
&& (wi
.prof
<= MAX_STATS
)) {
4069 stars
= GetStat(wi
.prof
)&PROFS_MASK
;
4071 //hit/damage/speed bonuses from wspecial
4072 if ((signed)stars
> wspecial_max
) {
4073 stars
= wspecial_max
;
4075 THAC0Bonus
+= wspecial
[stars
][0];
4076 DamageBonus
+= wspecial
[stars
][1];
4077 speed
+= wspecial
[stars
][2];
4078 // add non-proficiency penalty, which is missing from the table
4079 if (stars
== 0) THAC0Bonus
-= 4;
4082 if (IsDualWielding() && wsdualwield
) {
4083 //add dual wielding penalty
4084 stars
= GetStat(IE_PROFICIENCY2WEAPON
)&PROFS_MASK
;
4085 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4087 style
= 1000*stars
+ IE_PROFICIENCY2WEAPON
;
4088 THAC0Bonus
+= wsdualwield
[stars
][leftorright
?1:0];
4089 } else if (wi
.itemflags
&(IE_INV_ITEM_TWOHANDED
) && (Flags
&WEAPON_MELEE
) && wstwohanded
) {
4090 //add two handed profs bonus
4091 stars
= GetStat(IE_PROFICIENCY2HANDED
)&PROFS_MASK
;
4092 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4094 style
= 1000*stars
+ IE_PROFICIENCY2HANDED
;
4095 DamageBonus
+= wstwohanded
[stars
][0];
4096 CriticalBonus
= wstwohanded
[stars
][1];
4097 speed
+= wstwohanded
[stars
][2];
4098 } else if (Flags
&WEAPON_MELEE
) {
4100 CREItem
*weapon
= inventory
.GetUsedWeapon(true, slot
);
4101 if(wssingle
&& weapon
== NULL
) {
4102 //NULL return from GetUsedWeapon means no shield slot
4103 stars
= GetStat(IE_PROFICIENCYSINGLEWEAPON
)&PROFS_MASK
;
4104 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4106 style
= 1000*stars
+ IE_PROFICIENCYSINGLEWEAPON
;
4107 CriticalBonus
= wssingle
[stars
][1];
4108 } else if (wsswordshield
&& weapon
) {
4109 stars
= GetStat(IE_PROFICIENCYSWORDANDSHIELD
)&PROFS_MASK
;
4110 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4112 style
= 1000*stars
+ IE_PROFICIENCYSWORDANDSHIELD
;
4117 // ranged - no bonus
4120 // TODO: Elves get a racial THAC0 bonus with all swords and bows in BG2 (but not daggers)
4122 //second parameter is left or right hand flag
4123 tohit
= GetToHit(THAC0Bonus
, Flags
);
4127 int Actor::GetToHit(int bonus
, ieDword Flags
)
4131 //get our dual wielding modifier
4132 if (IsDualWielding()) {
4133 if (Flags
&WEAPON_LEFTHAND
) {
4134 tohit
+= GetStat(IE_HITBONUSLEFT
);
4136 tohit
+= GetStat(IE_HITBONUSRIGHT
);
4140 //get attack style (melee or ranged)
4141 switch(Flags
&WEAPON_STYLEMASK
) {
4143 tohit
+= GetStat(IE_MELEETOHIT
);
4146 tohit
+= GetStat(IE_FISTHIT
);
4149 tohit
+= GetStat(IE_MISSILEHITBONUS
);
4150 //add dexterity bonus
4151 tohit
+= core
->GetDexterityBonus(STAT_DEX_MISSILE
, GetStat(IE_DEX
));
4155 //add strength bonus if we need
4156 if (Flags
&WEAPON_USESTRENGTH
) {
4157 tohit
+= core
->GetStrengthBonus(0,GetStat(IE_STR
), GetStat(IE_STREXTRA
) );
4160 // if the target is using a ranged weapon while we're meleeing, we get a +4 bonus
4161 if ((Flags
&WEAPON_STYLEMASK
) != WEAPON_RANGED
) {
4162 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4163 if (target
&& target
->GetAttackStyle() == WEAPON_RANGED
) {
4168 // add +4 attack bonus vs racial enemies
4169 if (GetRangerLevel()) {
4170 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4171 if (target
&& IsRacialEnemy(target
)) {
4177 tohit
= (signed)GetStat(IE_TOHIT
)-tohit
;
4179 tohit
+= GetStat(IE_TOHIT
);
4184 static const int weapon_damagetype
[] = {DAMAGE_CRUSHING
, DAMAGE_PIERCING
,
4185 DAMAGE_CRUSHING
, DAMAGE_SLASHING
, DAMAGE_MISSILE
, DAMAGE_STUNNING
};
4187 int Actor::GetDefense(int DamageType
)
4189 //specific damage type bonus.
4193 switch (weapon_damagetype
[DamageType
]) {
4194 case DAMAGE_CRUSHING
:
4195 defense
+= GetStat(IE_ACCRUSHINGMOD
);
4197 case DAMAGE_PIERCING
:
4198 defense
+= GetStat(IE_ACPIERCINGMOD
);
4200 case DAMAGE_SLASHING
:
4201 defense
+= GetStat(IE_ACSLASHINGMOD
);
4203 case DAMAGE_MISSILE
:
4204 defense
+= GetStat(IE_ACMISSILEMOD
);
4206 //What about stunning ?
4211 //check for s/s and single weapon ac bonuses
4212 if (!IsDualWielding() && wssingle
&& wsswordshield
) {
4214 ITMExtHeader
* header
;
4215 header
= GetWeapon(wi
, false);
4216 //make sure we're wielding a single melee weapon
4217 if (header
&& (header
->AttackType
== ITEM_AT_MELEE
)) {
4220 if (inventory
.GetUsedWeapon(true, slot
) == NULL
) {
4221 //single-weapon style applies to all ac
4222 stars
= GetStat(IE_PROFICIENCYSINGLEWEAPON
)&PROFS_MASK
;
4223 if (stars
>STYLE_MAX
) stars
= STYLE_MAX
;
4224 defense
+= wssingle
[stars
][0];
4225 } else if (weapon_damagetype
[DamageType
] == DAMAGE_MISSILE
) {
4226 //sword-shield style applies only to missile ac
4227 stars
= GetStat(IE_PROFICIENCYSWORDANDSHIELD
)&PROFS_MASK
;
4228 if (stars
>STYLE_MAX
) stars
= STYLE_MAX
;
4229 defense
+= wsswordshield
[stars
][0];
4235 defense
= GetStat(IE_ARMORCLASS
)-defense
;
4237 defense
+= GetStat(IE_ARMORCLASS
);
4239 //Dexterity bonus is stored negative in 2da files.
4240 return defense
+ core
->GetDexterityBonus(STAT_DEX_AC
, GetStat(IE_DEX
) );
4243 void Actor::PerformAttack(ieDword gameTime
)
4245 // start a new round if we really don't have one yet
4247 printMessage("Actor", "Unregistered attack. We shouldn't be here?\n", RED
);
4249 InitRound(gameTime
);
4252 //only return if we don't have any attacks left this round
4253 if (attackcount
==0) return;
4255 // this check shouldn't be necessary, but it causes a divide-by-zero below,
4256 // so i would like it to be clear if it ever happens
4257 if (attacksperround
==0) {
4258 printMessage("Actor", "APR was 0 in PerformAttack!\n", RED
);
4262 //don't continue if we can't make the attack yet
4263 //we check lastattack because we will get the same gameTime a few times
4264 if ((nextattack
> gameTime
) || (gameTime
== lastattack
)) {
4265 // fuzzie added the following line as part of the UpdateActorState hack below
4266 lastattack
= gameTime
;
4270 if (InternalFlags
&IF_STOPATTACK
) {
4271 // this should be avoided by the AF_ALIVE check by all the calling actions
4272 printMessage("Actor", "Attack by dead actor!\n", LIGHT_RED
);
4277 printMessage("Actor", "Attack without valid target ID!\n", LIGHT_RED
);
4281 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4283 if (target
&& (target
->GetStat(IE_STATE_ID
)&STATE_DEAD
)) {
4288 printMessage("Actor", "Attack without valid target!\n", LIGHT_RED
);
4292 printf("Performattack for %s, target is: %s\n", ShortName
, target
->ShortName
);
4294 //which hand is used
4295 //we do apr - attacksleft so we always use the main hand first
4296 bool leftorright
= (bool) ((attacksperround
-attackcount
)&1);
4299 ITMExtHeader
*header
= NULL
;
4300 ITMExtHeader
*hittingheader
= NULL
;
4303 int DamageBonus
, CriticalBonus
;
4306 //will return false on any errors (eg, unusable weapon)
4307 if (!GetCombatDetails(tohit
, leftorright
, wi
, header
, hittingheader
, Flags
, DamageBonus
, speed
, CriticalBonus
, style
)) {
4311 //if this is the first call of the round, we need to update next attack
4312 if (nextattack
== 0) {
4313 //FIXME: figure out exactly how initiative is calculated
4314 int initiative
= core
->Roll(1, 5, GetXPLevel(true)/(-8));
4315 int spdfactor
= hittingheader
->Speed
+ speed
+ initiative
;
4316 if (spdfactor
<0) spdfactor
= 0;
4317 if (spdfactor
>10) spdfactor
= 10;
4319 //(round_size/attacks_per_round)*(initiative) is the first delta
4320 nextattack
= core
->Time
.round_size
*spdfactor
/(attacksperround
*10) + gameTime
;
4322 //we can still attack this round if we have a speed factor of 0
4323 if (nextattack
> gameTime
) {
4328 if((PersonalDistance(this, target
) > wi
.range
*10) || (GetCurrentArea()!=target
->GetCurrentArea() ) ) {
4329 // this is a temporary double-check, remove when bugfixed
4330 printMessage("Actor", "Attack action didn't bring us close enough!", LIGHT_RED
);
4334 SetStance(AttackStance
);
4336 //figure out the time for our next attack since the old time has the initiative
4337 //in it, we only have to add the basic delta
4339 nextattack
+= (core
->Time
.round_size
/attacksperround
);
4340 lastattack
= gameTime
;
4343 if (leftorright
&& IsDualWielding()) {
4344 printMessage("Attack","(Off) ", YELLOW
);
4346 printMessage("Attack","(Main) ", GREEN
);
4348 if (attacksperround
) {
4349 printf("Left: %d | ", attackcount
);
4350 printf("Next: %d ", nextattack
);
4353 int roll
= LuckyRoll(1, ATTACKROLL
, 0);
4356 printBracket("Critical Miss", RED
);
4358 displaymsg
->DisplayConstantStringName(STR_CRITICAL_MISS
, 0xffffff, this);
4359 DisplayStringCore(this, VB_CRITMISS
, DS_CONSOLE
|DS_CONST
);
4360 if (Flags
&WEAPON_RANGED
) {//no need for this with melee weapon!
4361 UseItem(wi
.slot
, (ieDword
)-2, target
, UI_MISS
);
4362 } else if (core
->HasFeature(GF_BREAKABLE_WEAPONS
)) {
4364 //TODO: this appears to be a random roll on-hit (perhaps critical failure
4365 // too); we use 1% (1d20*1d5==1)
4366 if ((header
->RechargeFlags
&IE_ITEM_BREAKABLE
) && core
->Roll(1,5,0) == 1) {
4367 inventory
.BreakItemSlot(wi
.slot
);
4374 //modify defense with damage type
4375 ieDword damagetype
= hittingheader
->DamageType
;
4379 if (hittingheader
->DiceThrown
<256) {
4380 damage
+= LuckyRoll(hittingheader
->DiceThrown
, hittingheader
->DiceSides
, 0, 1, 0);
4381 damage
+= DamageBonus
;
4382 printf("| Damage %dd%d%+d = %d ",hittingheader
->DiceThrown
, hittingheader
->DiceSides
, DamageBonus
, damage
);
4384 printf("| No Damage");
4388 if (roll
>= (ATTACKROLL
- (int) GetStat(IE_CRITICALHITBONUS
) - CriticalBonus
)) {
4390 printBracket("Critical Hit", GREEN
);
4392 displaymsg
->DisplayConstantStringName(STR_CRITICAL_HIT
, 0xffffff, this);
4393 DisplayStringCore(this, VB_CRITHIT
, DS_CONSOLE
|DS_CONST
);
4394 ModifyDamage (target
, this, damage
, resisted
, weapon_damagetype
[damagetype
], &wi
, true);
4395 UseItem(wi
.slot
, Flags
&WEAPON_RANGED
?-2:-1, target
, 0, damage
);
4402 //get target's defense against attack
4403 int defense
= target
->GetDefense(damagetype
);
4407 success
= roll
> tohit
- defense
;
4409 success
= tohit
+ roll
> defense
;
4414 if (Flags
&WEAPON_RANGED
) {//Launch the projectile anyway
4415 UseItem(wi
.slot
, (ieDword
)-2, target
, UI_MISS
);
4418 printBracket("Missed", LIGHT_RED
);
4422 printBracket("Hit", GREEN
);
4424 ModifyDamage (target
, this, damage
, resisted
, weapon_damagetype
[damagetype
], &wi
, false);
4425 UseItem(wi
.slot
, Flags
&WEAPON_RANGED
?-2:-1, target
, 0, damage
);
4429 static EffectRef fx_stoneskin_ref
={"StoneSkinModifier",NULL
,-1};
4430 static EffectRef fx_stoneskin2_ref
={"StoneSkin2Modifier",NULL
,-1};
4431 static EffectRef fx_mirrorimage_ref
={"MirrorImageModifier",NULL
,-1};
4432 static EffectRef fx_aegis_ref
={"Aegis",NULL
,-1};
4434 void Actor::ModifyDamage(Actor
*target
, Scriptable
*hitter
, int &damage
, int &resisted
, int damagetype
, WeaponInfo
*wi
, bool critical
)
4437 int mirrorimages
= target
->Modified
[IE_MIRRORIMAGES
];
4439 if (LuckyRoll(1,mirrorimages
+1,0) != 1) {
4440 target
->fxqueue
.DecreaseParam1OfEffect(fx_mirrorimage_ref
, 1);
4441 target
->Modified
[IE_MIRRORIMAGES
]--;
4447 // only check stone skins if damage type is physical or magical
4448 // DAMAGE_CRUSHING is 0, so we can't AND with it to check for its presence
4449 if (!(damagetype
& ~(DAMAGE_PIERCING
|DAMAGE_SLASHING
|DAMAGE_MISSILE
|DAMAGE_MAGIC
))) {
4450 int stoneskins
= target
->Modified
[IE_STONESKINS
];
4452 target
->fxqueue
.DecreaseParam1OfEffect(fx_stoneskin_ref
, 1);
4453 target
->fxqueue
.DecreaseParam1OfEffect(fx_aegis_ref
, 1);
4454 target
->Modified
[IE_STONESKINS
]--;
4459 stoneskins
= target
->Modified
[IE_STONESKINSGOLEM
];
4461 target
->fxqueue
.DecreaseParam1OfEffect(fx_stoneskin2_ref
, 1);
4462 target
->Modified
[IE_STONESKINSGOLEM
]--;
4469 if (BaseStats
[IE_BACKSTABDAMAGEMULTIPLIER
] > 1) {
4470 if (Modified
[IE_STATE_ID
] & (ieDword
) (STATE_INVISIBLE
) || Modified
[IE_ALWAYSBACKSTAB
]) {
4471 if ( !(core
->HasFeature(GF_PROPER_BACKSTAB
) && !IsBehind(target
)) ) {
4472 if (target
->Modified
[IE_DISABLEBACKSTAB
]) {
4473 // The backstab seems to have failed
4474 displaymsg
->DisplayConstantString (STR_BACKSTAB_FAIL
, 0xffffff);
4476 if (wi
->backstabbing
) {
4477 damage
*= Modified
[IE_BACKSTABDAMAGEMULTIPLIER
];
4478 // display a simple message instead of hardcoding multiplier names
4479 displaymsg
->DisplayConstantStringValue (STR_BACKSTAB
, 0xffffff, Modified
[IE_BACKSTABDAMAGEMULTIPLIER
]);
4481 // weapon is unsuitable for backstab
4482 displaymsg
->DisplayConstantString (STR_BACKSTAB_BAD
, 0xffffff);
4489 // add strength bonus; backstab does not affect it
4490 // TODO: should actually check WEAPON_USESTRENGTH, since a sling in bg2 has it
4491 if (GetAttackStyle() != WEAPON_RANGED
) {
4492 damage
+= core
->GetStrengthBonus(1, GetStat(IE_STR
), GetStat(IE_STREXTRA
) );
4496 if (wi
&& target
->fxqueue
.WeaponImmunity(wi
->enchantment
, wi
->itemflags
)) {
4498 resisted
= DR_IMMUNE
; // mark immunity for GetCombatDetails
4500 // check damage type immunity / resistance / susceptibility
4501 std::multimap
<ieDword
, DamageInfoStruct
>::iterator it
;
4502 it
= core
->DamageInfoMap
.find(damagetype
);
4503 if (it
== core
->DamageInfoMap
.end()) {
4504 printf("Unhandled damagetype:%d\n", damagetype
);
4505 } else if (it
->second
.resist_stat
== 0) {
4506 // damage type without a resistance stat
4508 damage
+= (signed)target
->GetStat(IE_DAMAGEBONUS
);
4509 resisted
= (int) (damage
* (signed)target
->GetStat(it
->second
.resist_stat
)/100.0);
4510 // check for bonuses for specific damage types
4511 if (core
->HasFeature(GF_SPECIFIC_DMG_BONUS
) && hitter
&& hitter
->Type
== ST_ACTOR
) {
4512 int bonus
= ((Actor
*)hitter
)->fxqueue
.SpecificDamageBonus(it
->second
.iwd_mod_type
);
4514 resisted
-= int (damage
* bonus
/ 100.0);
4515 printf("Bonus damage of %d (%+d%%), neto: %d\n", int (damage
* bonus
/ 100.0), bonus
, -resisted
);
4519 printf("Resisted %d of %d at %d%% resistance to %d\n", resisted
, damage
+resisted
, target
->GetStat(it
->second
.resist_stat
), damagetype
);
4520 if (damage
<= 0) resisted
= DR_IMMUNE
;
4524 //check casting failure
4525 if (damage
<0) damage
= 0;
4527 DisplayStringCore(this, VB_TIMMUNE
, DS_CONSOLE
|DS_CONST
);
4532 //a critical surely raises the morale?
4533 NewBase(IE_MORALE
, 1, MOD_ADDITIVE
);
4534 int head
= inventory
.GetHeadSlot();
4535 if ((head
!=-1) && target
->inventory
.HasItemInSlot("",(ieDword
) head
)) {
4536 //critical hit is averted by helmet
4537 displaymsg
->DisplayConstantStringName(STR_NO_CRITICAL
, 0xffffff, target
);
4539 damage
<<=1; //critical damage is always double?
4540 core
->timer
->SetScreenShake(16,16,8);
4546 void Actor::UpdateActorState(ieDword gameTime
) {
4547 //apply the modal effect on the beginning of each round
4548 if (((gameTime
-roundTime
)%core
->Time
.round_size
==0) && ModalState
) {
4549 if (!ModalSpell
[0]) {
4550 printMessage("Actor","Modal Spell Effect was not set!\n", YELLOW
);
4552 } else if(ModalSpell
[0]!='*') {
4553 if (ModalSpellSkillCheck()) {
4554 if (core
->ModalStates
[ModalState
].aoe_spell
) {
4555 core
->ApplySpellPoint(ModalSpell
, GetCurrentArea(), Pos
, this, 0);
4557 core
->ApplySpell(ModalSpell
, this, this, 0);
4560 displaymsg
->DisplayStringName(core
->ModalStates
[ModalState
].entering_str
, 0xffffff, this, IE_STR_SOUND
|IE_STR_SPEECH
);
4564 displaymsg
->DisplayStringName(core
->ModalStates
[ModalState
].failed_str
, 0xffffff, this, IE_STR_SOUND
|IE_STR_SPEECH
);
4566 ModalState
= MS_NONE
;
4567 // TODO: wait for a round until allowing new states?
4572 // this is a HACK, fuzzie can't work out where else to do this for now
4573 // but we shouldn't be resetting rounds/attacks just because the actor
4574 // wandered away, the action code should probably be responsible somehow
4575 // see also line above (search for comment containing UpdateActorState)!
4576 if (LastTarget
&& lastattack
&& lastattack
< (gameTime
- 1)) {
4577 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4578 if (!target
|| target
->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
4581 printMessage("Attack","(Leaving attack)", GREEN
);
4582 core
->GetGame()->OutAttack(GetID());
4590 //idx could be: 0-6, 16-22, 32-38, 48-54
4591 //the colors are stored in 7 dwords
4592 //maybe it would be simpler to store them in 28 bytes (without using stats?)
4593 void Actor::SetColor( ieDword idx
, ieDword grd
)
4595 ieByte gradient
= (ieByte
) (grd
&255);
4596 ieByte index
= (ieByte
) (idx
&15);
4597 ieByte shift
= (ieByte
) (idx
/16);
4600 //invalid value, would crash original IE
4605 // put gradient in all four bytes of value
4607 value
|= (value
<< 8);
4608 value
|= (value
<< 16);
4609 for (index
=0;index
<7;index
++) {
4610 Modified
[IE_COLORS
+index
] = value
;
4613 //invalid value, would crash original IE
4618 value
= gradient
<< shift
;
4619 value
|= Modified
[IE_COLORS
+index
] & ~(255<<shift
);
4620 Modified
[IE_COLORS
+index
] = value
;
4624 void Actor::SetColorMod( ieDword location
, RGBModifier::Type type
, int speed
,
4625 unsigned char r
, unsigned char g
, unsigned char b
,
4628 CharAnimations
* ca
= GetAnims();
4631 if (location
== 0xff) {
4632 ca
->GlobalColorMod
.type
= type
;
4633 ca
->GlobalColorMod
.speed
= speed
;
4634 ca
->GlobalColorMod
.rgb
.r
= r
;
4635 ca
->GlobalColorMod
.rgb
.g
= g
;
4636 ca
->GlobalColorMod
.rgb
.b
= b
;
4637 ca
->GlobalColorMod
.rgb
.a
= 0;
4639 ca
->GlobalColorMod
.phase
= phase
;
4641 if (ca
->GlobalColorMod
.phase
> 2*speed
)
4642 ca
->GlobalColorMod
.phase
=0;
4646 //00xx0yyy-->000xxyyy
4647 if (location
&0xffffffc8) return; //invalid location
4648 location
= (location
&7) | ((location
>>1)&0x18);
4649 ca
->ColorMods
[location
].type
= type
;
4650 ca
->ColorMods
[location
].speed
= speed
;
4651 ca
->ColorMods
[location
].rgb
.r
= r
;
4652 ca
->ColorMods
[location
].rgb
.g
= g
;
4653 ca
->ColorMods
[location
].rgb
.b
= b
;
4654 ca
->ColorMods
[location
].rgb
.a
= 0;
4656 ca
->ColorMods
[location
].phase
= phase
;
4658 if (ca
->ColorMods
[location
].phase
> 2*speed
)
4659 ca
->ColorMods
[location
].phase
= 0;
4663 void Actor::SetLeader(Actor
*actor
, int xoffset
, int yoffset
)
4665 LastFollowed
= actor
->GetID();
4666 FollowOffset
.x
= xoffset
;
4667 FollowOffset
.y
= yoffset
;
4670 //if days == 0, it means full healing
4671 void Actor::Heal(int days
)
4674 SetBase(IE_HITPOINTS
, BaseStats
[IE_HITPOINTS
]+days
*2);
4676 SetBase(IE_HITPOINTS
, BaseStats
[IE_MAXHITPOINTS
]);
4680 void Actor::AddExperience(int exp
)
4682 if (core
->HasFeature(GF_WISDOM_BONUS
)) {
4683 exp
= (exp
* (100 + core
->GetWisdomBonus(0, Modified
[IE_WIS
]))) / 100;
4685 SetBase(IE_XP
,BaseStats
[IE_XP
]+exp
);
4688 int Actor::CalculateExperience(int type
, int level
)
4690 if (type
>=xpbonustypes
) {
4693 unsigned int l
= (unsigned int) (level
- 1);
4695 if (l
>=(unsigned int) xpbonuslevels
) {
4698 return xpbonus
[type
*xpbonuslevels
+l
];
4701 bool Actor::Schedule(ieDword gametime
, bool checkhide
)
4704 if (!(InternalFlags
&IF_VISIBLE
) ) {
4709 //check for schedule
4710 ieDword bit
= 1<<((gametime
/6)%7200/300);
4711 if (appearance
& bit
) {
4717 void Actor::NewPath()
4720 Point tmp
= Destination
;
4725 Movable::WalkTo(tmp
, size
);
4728 void Actor::SetInTrap(ieDword setreset
)
4732 InternalFlags
|= IF_INTRAP
;
4734 InternalFlags
&= ~IF_INTRAP
;
4738 void Actor::SetRunFlags(ieDword flags
)
4740 InternalFlags
&= ~IF_RUNFLAGS
;
4741 InternalFlags
|= (flags
& IF_RUNFLAGS
);
4744 void Actor::WalkTo(const Point
&Des
, ieDword flags
, int MinDistance
)
4747 if (InternalFlags
&IF_REALLYDIED
) {
4752 if (Des
.x
==-2 && Des
.y
==-2) {
4753 Point
p((ieWord
) Modified
[IE_SAVEDXPOS
], (ieWord
) Modified
[IE_SAVEDYPOS
] );
4754 Movable::WalkTo(p
, MinDistance
);
4756 Movable::WalkTo(Des
, MinDistance
);
4760 //there is a similar function in Map for stationary vvcs
4761 void Actor::DrawVideocells(const Region
&screen
, vvcVector
&vvcCells
, const Color
&tint
)
4763 Map
* area
= GetCurrentArea();
4765 for (unsigned int i
= 0; i
< vvcCells
.size(); i
++) {
4766 ScriptedAnimation
* vvc
= vvcCells
[i
];
4767 /* we don't allow holes anymore
4772 // actually this is better be drawn by the vvc
4773 bool endReached
= vvc
->Draw(screen
, Pos
, tint
, area
, WantDither(), GetOrientation());
4776 vvcCells
.erase(vvcCells
.begin()+i
);
4782 void Actor::DrawActorSprite(const Region
&screen
, int cx
, int cy
, const Region
& bbox
,
4783 SpriteCover
*& newsc
, Animation
** anims
,
4784 unsigned char Face
, const Color
& tint
)
4786 CharAnimations
* ca
= GetAnims();
4787 int PartCount
= ca
->GetTotalPartCount();
4788 Video
* video
= core
->GetVideoDriver();
4789 Region vp
= video
->GetViewport();
4791 // display current frames in the right order
4792 const int* zOrder
= ca
->GetZOrder(Face
);
4793 for (int part
= 0; part
< PartCount
; ++part
) {
4795 if (zOrder
) partnum
= zOrder
[part
];
4796 Animation
* anim
= anims
[partnum
];
4797 Sprite2D
* nextFrame
= 0;
4799 nextFrame
= anim
->GetFrame(anim
->GetCurrentFrame());
4800 if (nextFrame
&& bbox
.InsideRegion( vp
) ) {
4801 if (!newsc
|| !newsc
->Covers(cx
, cy
, nextFrame
->XPos
, nextFrame
->YPos
, nextFrame
->Width
, nextFrame
->Height
)) {
4802 // the first anim contains the animarea for
4803 // the entire multi-part animation
4804 newsc
= area
->BuildSpriteCover(cx
,
4805 cy
, -anims
[0]->animArea
.x
,
4806 -anims
[0]->animArea
.y
,
4807 anims
[0]->animArea
.w
,
4808 anims
[0]->animArea
.h
, WantDither() );
4810 assert(newsc
->Covers(cx
, cy
, nextFrame
->XPos
, nextFrame
->YPos
, nextFrame
->Width
, nextFrame
->Height
));
4812 unsigned int flags
= TranslucentShadows
? BLIT_TRANSSHADOW
: 0;
4814 if (!ca
->lockPalette
) flags
|=BLIT_TINTED
;
4816 video
->BlitGameSprite( nextFrame
, cx
+ screen
.x
, cy
+ screen
.y
,
4817 flags
, tint
, newsc
, ca
->GetPartPalette(partnum
), &screen
);
4823 static const int OrientdX
[16] = { 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9, 10, 9, 7, 4 };
4824 static const int OrientdY
[16] = { 10, 9, 7, 4, 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9 };
4825 static const unsigned int MirrorImageLocation
[8] = { 4, 12, 8, 0, 6, 14, 10, 2 };
4826 static const unsigned int MirrorImageZOrder
[8] = { 2, 4, 6, 0, 1, 7, 5, 3 };
4828 bool Actor::ShouldHibernate() {
4829 //finding an excuse why we don't hybernate the actor
4830 if (Modified
[IE_ENABLEOFFSCREENAI
])
4832 if (LastTarget
) //currently attacking someone
4834 if (!lastRunTime
) // haven't had a chance to run a script
4840 if (GetNextAction())
4842 if (GetWait()) //would never stop waiting
4847 void Actor::Draw(const Region
&screen
)
4849 Map
* area
= GetCurrentArea();
4853 int explored
= Modified
[IE_DONOTJUMP
]&DNJ_UNHINDERED
;
4854 //check the deactivation condition only if needed
4855 //this fixes dead actors disappearing from fog of war (they should be permanently visible)
4856 if ((!area
->IsVisible( Pos
, explored
) || (InternalFlags
&IF_REALLYDIED
) ) && (InternalFlags
&IF_ACTIVE
) ) {
4857 //turning actor inactive if there is no action next turn
4858 if (ShouldHibernate()) {
4859 InternalFlags
|=IF_IDLE
;
4861 if (!(InternalFlags
&IF_REALLYDIED
)) {
4862 // for a while this didn't return (disable drawing) if about to hibernate;
4863 // Avenger said (aa10aaed) "we draw the actor now for the last time".
4869 area
->ClearTrap(this, InTrap
-1);
4872 // if an actor isn't visible, should we still handle animations, draw
4873 // video cells, etc? let us assume not, for now..
4874 if (!(InternalFlags
& IF_VISIBLE
)) {
4879 CharAnimations
* ca
= GetAnims();
4883 //explored or visibilitymap (bird animations are visible in fog)
4885 int NoCircle
= Modified
[IE_NOCIRCLE
];
4886 int Trans
= Modified
[IE_TRANSLUCENT
];
4892 //iwd has this flag saved in the creature
4893 if (Modified
[IE_AVATARREMOVAL
]) {
4898 int Frozen
= Immobile();
4899 int State
= Modified
[IE_STATE_ID
];
4901 if (State
&STATE_DEAD
) {
4905 if (State
&STATE_STILL
) {
4909 //adjust invisibility for enemies
4910 if (Modified
[IE_EA
]>EA_GOODCUTOFF
) {
4911 if (State
&STATE_INVISIBLE
) {
4917 //can't move this, because there is permanent blur state where
4918 //there is no effect (just state bit)
4919 if ((State
&STATE_BLUR
) && Trans
< 128) {
4922 Color tint
= area
->LightMap
->GetPixel( cx
/ 16, cy
/ 12);
4923 tint
.a
= (ieByte
) (255-Trans
);
4925 unsigned char heightmapindex
= area
->HeightMap
->GetAt( cx
/ 16, cy
/ 12);
4926 if (heightmapindex
> 15) {
4927 // there are 8bpp lightmaps (eg, bg2's AR1300) and fuzzie
4928 // cannot work out how they work, so here is an incorrect
4929 // hack (probably). please fix!
4930 heightmapindex
= 15;
4933 //don't use cy for area map access beyond this point
4934 cy
-= heightmapindex
;
4936 //draw videocells under the actor
4937 DrawVideocells(screen
, vvcShields
, tint
);
4939 Video
* video
= core
->GetVideoDriver();
4940 Region vp
= video
->GetViewport();
4942 bool drawcircle
= (NoCircle
== 0);
4943 GameControl
*gc
= core
->GetGameControl();
4944 if (gc
->GetScreenFlags()&SF_CUTSCENE
) {
4945 // ground circles are not drawn in cutscenes
4948 // the speaker should get a circle even in cutscenes
4949 if (gc
->targetID
== globalID
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
)) {
4952 if (BaseStats
[IE_STATE_ID
]&STATE_DEAD
|| InternalFlags
&IF_JUSTDIED
) {
4955 bool drawtarget
= drawcircle
;
4956 // we always show circle/target on pause
4957 if (drawcircle
&& !(gc
->GetDialogueFlags() & DF_FREEZE_SCRIPTS
)) {
4958 // check marker feedback level
4959 ieDword markerfeedback
= 4;
4960 core
->GetDictionary()->Lookup("GUI Feedback Level", markerfeedback
);
4963 drawcircle
= markerfeedback
>= 1;
4964 } else if (Selected
) {
4965 // selected creature
4966 drawcircle
= markerfeedback
>= 2;
4967 } else if (IsPC()) {
4969 drawcircle
= markerfeedback
>= 3;
4970 } else if (Modified
[IE_EA
] >= EA_EVILCUTOFF
) {
4972 drawcircle
= markerfeedback
>= 5;
4975 drawcircle
= markerfeedback
>= 6;
4977 drawtarget
= Selected
&& markerfeedback
>= 4;
4983 DrawTargetPoint(vp
);
4986 unsigned char StanceID
= GetStance();
4987 unsigned char Face
= GetNextFace();
4988 Animation
** anims
= ca
->GetAnimation( StanceID
, Face
);
4990 // update bounding box and such
4991 int PartCount
= ca
->GetTotalPartCount();
4992 Sprite2D
* nextFrame
= 0;
4993 nextFrame
= anims
[0]->GetFrame(anims
[0]->GetCurrentFrame());
4995 //make actor unselectable and unselected when it is not moving
4996 //dead, petriefied, frozen, paralysed etc.
4998 // this 0x80 stuff was broken and is more broken now, disabled
4999 //if (Selected!=0x80) {
5001 core
->GetGame()->SelectActor(this, false, SELECT_NORMAL
);
5004 //If you find a better place for it, I'll really be glad to put it there
5005 //IN BG1 and BG2, this is at the ninth frame...
5006 if(attackProjectile
&& (anims
[0]->GetCurrentFrame() == 8/*anims[0]->GetFramesCount()/2*/)) {
5007 GetCurrentArea()->AddProjectile(attackProjectile
, Pos
, LastTarget
);
5008 attackProjectile
= NULL
;
5010 if (nextFrame
&& lastFrame
!= nextFrame
) {
5012 if (PartCount
== 1) {
5013 newBBox
.x
= cx
- nextFrame
->XPos
;
5014 newBBox
.w
= nextFrame
->Width
;
5015 newBBox
.y
= cy
- nextFrame
->YPos
;
5016 newBBox
.h
= nextFrame
->Height
;
5018 // FIXME: currently using the animarea instead
5019 // of the real bounding box of this (multi-part) frame.
5020 // Shouldn't matter much, though. (wjp)
5021 newBBox
.x
= cx
+ anims
[0]->animArea
.x
;
5022 newBBox
.y
= cy
+ anims
[0]->animArea
.y
;
5023 newBBox
.w
= anims
[0]->animArea
.w
;
5024 newBBox
.h
= anims
[0]->animArea
.h
;
5026 lastFrame
= nextFrame
;
5030 // Drawing the actor:
5032 // Drawn without transparency, unless fully invisible.
5033 // Order: W, E, N, S, NW, SE, NE, SW
5034 // Uses extraCovers 3-10
5035 // * blurred copies (3 of them)
5036 // Drawn with transparency.
5037 // distance between copies depends on IE_MOVEMENTRATE
5038 // TODO: actually, the direction is the real movement direction,
5039 // not the (rounded) direction given Face
5040 // Uses extraCovers 0-2
5042 // Uses main spritecover
5044 //comments by Avenger:
5045 // currently we don't have a real direction, but the orientation field
5046 // could be used with higher granularity. When we need the face value
5047 // it could be divided so it will become a 0-15 number.
5050 SpriteCover
*sc
= 0, *newsc
= 0;
5053 int blurdx
= (OrientdX
[Face
]*(int)Modified
[IE_MOVEMENTRATE
])/20;
5054 int blurdy
= (OrientdY
[Face
]*(int)Modified
[IE_MOVEMENTRATE
])/20;
5055 Color mirrortint
= tint
;
5056 //mirror images are also half transparent when invis
5057 //if (mirrortint.a > 0) mirrortint.a = 255;
5061 // mirror images behind the actor
5062 for (i
= 0; i
< 4; ++i
) {
5063 unsigned int m
= MirrorImageZOrder
[i
];
5064 if (m
< Modified
[IE_MIRRORIMAGES
]) {
5065 Region sbbox
= BBox
;
5066 int dir
= MirrorImageLocation
[m
];
5067 int icx
= cx
+ 3*OrientdX
[dir
];
5068 int icy
= cy
+ 3*OrientdY
[dir
];
5069 Point
iPos(icx
, icy
);
5070 if (area
->GetBlocked(iPos
) & (PATH_MAP_PASSABLE
|PATH_MAP_ACTOR
)) {
5071 sbbox
.x
+= 3*OrientdX
[dir
];
5072 sbbox
.y
+= 3*OrientdY
[dir
];
5073 newsc
= sc
= extraCovers
[3+m
];
5074 DrawActorSprite(screen
, icx
, icy
, sbbox
, newsc
,
5075 anims
, Face
, mirrortint
);
5078 extraCovers
[3+m
] = newsc
;
5082 delete extraCovers
[3+m
];
5083 extraCovers
[3+m
] = NULL
;
5087 // blur sprites behind the actor
5088 if (State
& STATE_BLUR
) {
5089 if (Face
< 4 || Face
>= 12) {
5090 Region sbbox
= BBox
;
5091 sbbox
.x
-= 4*blurdx
; sbbox
.y
-= 4*blurdy
;
5092 blurx
-= 4*blurdx
; blury
-= 4*blurdy
;
5093 for (i
= 0; i
< 3; ++i
) {
5094 sbbox
.x
+= blurdx
; sbbox
.y
+= blurdy
;
5095 blurx
+= blurdx
; blury
+= blurdy
;
5096 newsc
= sc
= extraCovers
[i
];
5097 DrawActorSprite(screen
, blurx
, blury
, sbbox
, newsc
,
5101 extraCovers
[i
] = newsc
;
5108 if (!(State
&(STATE_DEAD
|STATE_FROZEN
|STATE_PETRIFIED
) ) &&
5109 (area
->GetLightLevel(Pos
)<128) &&
5110 core
->GetGame()->PartyHasInfravision()) {
5115 newsc
= sc
= GetSpriteCover();
5116 DrawActorSprite(screen
, cx
, cy
, BBox
, newsc
, anims
, Face
, tint
);
5117 if (newsc
!= sc
) SetSpriteCover(newsc
);
5119 // blur sprites in front of the actor
5120 if (State
& STATE_BLUR
) {
5121 if (Face
>= 4 && Face
< 12) {
5122 Region sbbox
= BBox
;
5123 for (i
= 0; i
< 3; ++i
) {
5124 sbbox
.x
-= blurdx
; sbbox
.y
-= blurdy
;
5125 blurx
-= blurdx
; blury
-= blurdy
;
5126 newsc
= sc
= extraCovers
[i
];
5127 DrawActorSprite(screen
, blurx
, blury
, sbbox
, newsc
,
5131 extraCovers
[i
] = newsc
;
5137 // mirror images in front of the actor
5138 for (i
= 4; i
< 8; ++i
) {
5139 unsigned int m
= MirrorImageZOrder
[i
];
5140 if (m
< Modified
[IE_MIRRORIMAGES
]) {
5141 Region sbbox
= BBox
;
5142 int dir
= MirrorImageLocation
[m
];
5143 int icx
= cx
+ 3*OrientdX
[dir
];
5144 int icy
= cy
+ 3*OrientdY
[dir
];
5145 Point
iPos(icx
, icy
);
5146 if (area
->GetBlocked(iPos
) & (PATH_MAP_PASSABLE
|PATH_MAP_ACTOR
)) {
5147 sbbox
.x
+= 3*OrientdX
[dir
];
5148 sbbox
.y
+= 3*OrientdY
[dir
];
5149 newsc
= sc
= extraCovers
[3+m
];
5150 DrawActorSprite(screen
, icx
, icy
, sbbox
, newsc
,
5151 anims
, Face
, mirrortint
);
5154 extraCovers
[3+m
] = newsc
;
5158 delete extraCovers
[3+m
];
5159 extraCovers
[3+m
] = NULL
;
5163 // advance animations one frame (in sync)
5165 anims
[0]->LastFrame();
5167 anims
[0]->NextFrame();
5169 for (int part
= 1; part
< PartCount
; ++part
) {
5171 anims
[part
]->GetSyncedNextFrame(anims
[0]);
5174 if (anims
[0]->endReached
) {
5175 if (HandleActorStance() ) {
5176 anims
[0]->endReached
= false;
5177 anims
[0]->SetPos(0);
5181 ca
->PulseRGBModifiers();
5184 //draw videocells over the actor
5185 DrawVideocells(screen
, vvcOverlays
, tint
);
5188 /* Handling automatic stance changes */
5189 bool Actor::HandleActorStance()
5191 CharAnimations
* ca
= GetAnims();
5192 int StanceID
= GetStance();
5194 if (ca
->autoSwitchOnEnd
) {
5195 int nextstance
= ca
->nextStanceID
;
5196 SetStance( nextstance
);
5197 ca
->autoSwitchOnEnd
= false;
5200 int x
= rand()%1000;
5201 if ((StanceID
==IE_ANI_AWAKE
) && !x
) {
5202 SetStance( IE_ANI_HEAD_TURN
);
5205 // added CurrentAction as part of blocking action fixes
5206 if ((StanceID
==IE_ANI_READY
) && !CurrentAction
&& !GetNextAction()) {
5207 SetStance( IE_ANI_AWAKE
);
5210 if (StanceID
== IE_ANI_ATTACK
|| StanceID
== IE_ANI_ATTACK_JAB
||
5211 StanceID
== IE_ANI_ATTACK_SLASH
|| StanceID
== IE_ANI_ATTACK_BACKSLASH
||
5212 StanceID
== IE_ANI_SHOOT
)
5214 SetStance( AttackStance
);
5220 void Actor::GetSoundFrom2DA(ieResRef Sound
, unsigned int index
)
5224 AutoTable
tab(anims
->ResRef
);
5239 index
= 36+rand()%4;
5242 strnlwrcpy(Sound
, tab
->QueryField (index
, 0), 8);
5245 void Actor::GetSoundFromINI(ieResRef Sound
, unsigned int index
)
5247 const char *resource
= "";
5249 unsigned int animid
=BaseStats
[IE_ANIMATION_ID
];
5250 if(core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
5253 snprintf(section
,10,"%d", animid
);
5257 resource
= core
->GetResDataINI()->GetKeyAsString(section
, "at1sound","");
5260 resource
= core
->GetResDataINI()->GetKeyAsString(section
, "hitsound","");
5263 resource
= core
->GetResDataINI()->GetKeyAsString(section
, "dfbsound","");
5268 int count
= CountElements(resource
,',');
5269 if (count
<=0) return;
5270 count
= core
->Roll(1,count
,-1);
5272 while(*resource
&& *resource
!=',') resource
++;
5273 if (*resource
==',') resource
++;
5275 strncpy(Sound
, resource
, 8);
5276 for(count
=0;count
<8 && Sound
[count
]!=',';count
++) {};
5280 void Actor::ResolveStringConstant(ieResRef Sound
, unsigned int index
)
5282 if (PCStats
&& PCStats
->SoundSet
[0]) {
5283 //resolving soundset (bg1/bg2 style)
5284 if (csound
[index
]) {
5285 snprintf(Sound
, sizeof(ieResRef
), "%s%c", PCStats
->SoundSet
, csound
[index
]);
5289 snprintf(Sound
, sizeof(ieResRef
), "%s%02d", PCStats
->SoundSet
, index
);
5295 if (core
->HasFeature(GF_RESDATA_INI
)) {
5296 GetSoundFromINI(Sound
, index
);
5298 GetSoundFrom2DA(Sound
, index
);
5302 void Actor::SetActionButtonRow(ActionButtonRow
&ar
)
5304 for(int i
=0;i
<MAX_QSLOTS
;i
++) {
5305 ieByte tmp
= ar
[i
+3];
5306 if (QslotTranslation
) {
5309 PCStats
->QSlots
[i
]=tmp
;
5313 //the first 3 buttons are untouched by this function
5314 void Actor::GetActionButtonRow(ActionButtonRow
&ar
)
5316 InitButtons(GetStat(IE_CLASS
), false);
5317 for(int i
=0;i
<GUIBT_COUNT
-3;i
++) {
5318 ieByte tmp
=PCStats
->QSlots
[i
];
5319 if (QslotTranslation
) {
5320 if (tmp
>=90) { //quick weapons
5322 } else if (tmp
>=80) { //quick items
5324 } else if (tmp
>=70) { //quick spells
5332 memcpy(ar
,DefaultButtons
,3*sizeof(ieByte
) );
5335 void Actor::SetPortrait(const char* ResRef
, int Which
)
5339 if (ResRef
== NULL
) {
5343 core
->SetEventFlag(EF_PORTRAIT
);
5347 memset( SmallPortrait
, 0, 8 );
5348 strncpy( SmallPortrait
, ResRef
, 8 );
5351 memset( LargePortrait
, 0, 8 );
5352 strncpy( LargePortrait
, ResRef
, 8 );
5355 for (i
= 0; i
< 8 && ResRef
[i
]; i
++) {};
5356 SmallPortrait
[i
] = 'S';
5357 LargePortrait
[i
] = 'M';
5361 void Actor::SetSoundFolder(const char *soundset
)
5363 if (core
->HasFeature(GF_SOUNDFOLDERS
)) {
5364 char filepath
[_MAX_PATH
];
5366 strnlwrcpy(PCStats
->SoundFolder
, soundset
, 32);
5367 PathJoin(filepath
,core
->GamePath
,"sounds",PCStats
->SoundFolder
,0);
5368 char file
[_MAX_PATH
];
5369 if (FileGlob(file
, filepath
, "?????01")) {
5371 } else if (FileGlob(file
, filepath
, "????01")) {
5376 strnlwrcpy(PCStats
->SoundSet
, file
, 8);
5378 strnlwrcpy(PCStats
->SoundSet
, soundset
, 8);
5379 PCStats
->SoundFolder
[0]=0;
5383 void Actor::GetSoundFolder(char *soundset
) const
5385 if (core
->HasFeature(GF_SOUNDFOLDERS
)) {
5386 strnlwrcpy(soundset
, PCStats
->SoundFolder
, 32);
5389 strnlwrcpy(soundset
, PCStats
->SoundSet
, 8);
5393 bool Actor::HasVVCCell(const ieResRef resource
)
5395 return GetVVCCell(resource
) != NULL
;
5398 ScriptedAnimation
*Actor::GetVVCCell(const ieResRef resource
)
5401 vvcVector
*vvcCells
=&vvcShields
;
5403 size_t i
=vvcCells
->size();
5405 ScriptedAnimation
*vvc
= (*vvcCells
)[i
];
5409 if ( strnicmp(vvc
->ResName
, resource
, 8) == 0) {
5413 vvcCells
=&vvcOverlays
;
5414 if (j
) { j
= false; goto retry
; }
5418 void Actor::RemoveVVCell(const ieResRef resource
, bool graceful
)
5421 vvcVector
*vvcCells
=&vvcShields
;
5423 size_t i
=vvcCells
->size();
5425 ScriptedAnimation
*vvc
= (*vvcCells
)[i
];
5429 if ( strnicmp(vvc
->ResName
, resource
, 8) == 0) {
5431 vvc
->SetPhase(P_RELEASE
);
5434 vvcCells
->erase(vvcCells
->begin()+i
);
5438 vvcCells
=&vvcOverlays
;
5439 if (j
) { j
= false; goto retry
; }
5442 //this is a faster version of hasvvccell, because it knows where to look
5443 //for the overlay, it also returns the vvc for further manipulation
5444 //use this for the seven eyes overlay
5445 ScriptedAnimation
*Actor::FindOverlay(int index
)
5447 vvcVector
*vvcCells
;
5449 if (index
>31) return NULL
;
5451 if (hc_locations
&(1<<index
)) vvcCells
=&vvcShields
;
5452 else vvcCells
=&vvcOverlays
;
5454 const char *resRef
= hc_overlays
[index
];
5456 size_t i
=vvcCells
->size();
5458 ScriptedAnimation
*vvc
= (*vvcCells
)[i
];
5462 if ( strnicmp(vvc
->ResName
, resRef
, 8) == 0) {
5469 void Actor::AddVVCell(ScriptedAnimation
* vvc
)
5471 vvcVector
*vvcCells
;
5473 //if the vvc was not created, don't try to add it
5478 vvcCells
=&vvcShields
;
5480 vvcCells
=&vvcOverlays
;
5482 size_t i
=vvcCells
->size();
5484 if ((*vvcCells
)[i
] == NULL
) {
5485 (*vvcCells
)[i
] = vvc
;
5489 vvcCells
->push_back( vvc
);
5492 //returns restored spell level
5493 int Actor::RestoreSpellLevel(ieDword maxlevel
, ieDword type
)
5498 case 0: //allow only mage
5501 case 1: //allow only cleric
5505 //allow any (including innates)
5508 for (int i
=maxlevel
;i
>0;i
--) {
5509 CREMemorizedSpell
*cms
= spellbook
.FindUnchargedSpell(typemask
, maxlevel
);
5511 spellbook
.ChargeSpell(cms
);
5518 //replenishes spells, cures fatigue
5519 void Actor::Rest(int hours
)
5523 int remaining
= hours
*10;
5524 //removes hours*10 fatigue points
5525 NewStat (IE_FATIGUE
, -remaining
, MOD_ADDITIVE
);
5526 NewStat (IE_INTOXICATION
, -remaining
, MOD_ADDITIVE
);
5527 //restore hours*10 spell levels
5528 //rememorization starts with the lower spell levels?
5529 inventory
.ChargeAllItems (remaining
);
5530 for (int level
= 1; level
<16; level
++) {
5531 if (level
<remaining
) {
5534 while (remaining
>0) {
5535 remaining
-= RestoreSpellLevel(level
,0);
5539 SetBase (IE_FATIGUE
, 0);
5540 SetBase (IE_INTOXICATION
, 0);
5541 inventory
.ChargeAllItems (0);
5542 spellbook
.ChargeAllSpells ();
5546 //returns the actual slot from the quickslot
5547 int Actor::GetQuickSlot(int slot
)
5550 if (inventory
.HasItemInSlot("",inventory
.GetMagicSlot())) {
5551 return inventory
.GetMagicSlot();
5554 return slot
+inventory
.GetWeaponSlot();
5556 return PCStats
->QuickWeaponSlots
[slot
];
5559 //marks the quickslot as equipped
5560 int Actor::SetEquippedQuickSlot(int slot
, int header
)
5563 if (header
<0) header
=0;
5564 inventory
.SetEquippedSlot(slot
, header
);
5569 if ((slot
<0) || (slot
== IW_NO_EQUIPPED
) ) {
5570 if (slot
== IW_NO_EQUIPPED
) {
5571 slot
= inventory
.GetFistSlot();
5574 for(i
=0;i
<MAX_QUICKWEAPONSLOT
;i
++) {
5575 if(slot
+inventory
.GetWeaponSlot()==PCStats
->QuickWeaponSlots
[i
]) {
5580 if (i
==MAX_QUICKWEAPONSLOT
) {
5585 assert(slot
<MAX_QUICKWEAPONSLOT
);
5587 header
= PCStats
->QuickWeaponHeaders
[slot
];
5590 PCStats
->QuickWeaponHeaders
[slot
]=header
;
5592 slot
= PCStats
->QuickWeaponSlots
[slot
]-inventory
.GetWeaponSlot();
5593 Equipped
= (ieWordSigned
) slot
;
5594 EquippedHeader
= (ieWord
) header
;
5595 if (inventory
.SetEquippedSlot(slot
, header
)) {
5598 return STR_MAGICWEAPON
;
5601 //if target is a non living scriptable, then we simply shoot for its position
5602 //the fx should get a NULL target, and handle itself by using the position
5603 //(shouldn't crash when target is NULL)
5604 bool Actor::UseItemPoint(ieDword slot
, ieDword header
, const Point
&target
, ieDword flags
)
5606 CREItem
*item
= inventory
.GetSlotItem(slot
);
5611 strnuprcpy(tmpresref
, item
->ItemResRef
, sizeof(ieResRef
)-1);
5613 Item
*itm
= gamedata
->GetItem(tmpresref
);
5614 if (!itm
) return false; //quick item slot contains invalid item resref
5615 //item is depleted for today
5616 if(itm
->UseCharge(item
->Usages
, header
, false)==CHG_DAY
) {
5620 Projectile
*pro
= itm
->GetProjectile(slot
, header
, flags
&UI_MISS
);
5621 ChargeItem(slot
, header
, item
, itm
, flags
&UI_SILENT
);
5622 gamedata
->FreeItem(itm
,tmpresref
, false);
5624 pro
->SetCaster(globalID
);
5625 GetCurrentArea()->AddProjectile(pro
, Pos
, target
);
5631 static EffectRef fx_damage_ref
={"Damage",NULL
,-1};
5633 bool Actor::UseItem(ieDword slot
, ieDword header
, Scriptable
* target
, ieDword flags
, int damage
)
5635 if (target
->Type
!=ST_ACTOR
) {
5636 return UseItemPoint(slot
, header
, target
->Pos
, flags
);
5639 Actor
*tar
= (Actor
*) target
;
5640 CREItem
*item
= inventory
.GetSlotItem(slot
);
5645 strnuprcpy(tmpresref
, item
->ItemResRef
, sizeof(ieResRef
)-1);
5647 Item
*itm
= gamedata
->GetItem(tmpresref
);
5648 if (!itm
) return false; //quick item slot contains invalid item resref
5649 //item is depleted for today
5650 if (itm
->UseCharge(item
->Usages
, header
, false)==CHG_DAY
) {
5654 Projectile
*pro
= itm
->GetProjectile(slot
, header
, flags
&UI_MISS
);
5655 ChargeItem(slot
, header
, item
, itm
, flags
&UI_SILENT
);
5656 gamedata
->FreeItem(itm
,tmpresref
, false);
5658 //ieDword is unsigned!!
5659 pro
->SetCaster(globalID
);
5660 if(((int)header
< 0) && !(flags
&UI_MISS
)) { //using a weapon
5661 ITMExtHeader
*which
= itm
->GetWeaponHeader(header
== (ieDword
)-2);
5662 Effect
* AttackEffect
= EffectQueue::CreateEffect(fx_damage_ref
, damage
, (weapon_damagetype
[which
->DamageType
])<<16, FX_DURATION_INSTANT_LIMITED
);
5663 AttackEffect
->Projectile
= which
->ProjectileAnimation
;
5664 AttackEffect
->Target
= FX_TARGET_PRESET
;
5665 pro
->GetEffects()->AddEffect(AttackEffect
, true);
5666 //AddEffect created a copy, the original needs to be scrapped
5667 delete AttackEffect
;
5668 attackProjectile
= pro
;
5669 } else //launch it now as we are not attacking
5670 GetCurrentArea()->AddProjectile(pro
, Pos
, tar
->globalID
);
5676 void Actor::ChargeItem(ieDword slot
, ieDword header
, CREItem
*item
, Item
*itm
, bool silent
)
5679 item
= inventory
.GetSlotItem(slot
);
5682 itm
= gamedata
->GetItem(item
->ItemResRef
);
5684 if (!itm
) return; //quick item slot contains invalid item resref
5687 core
->SetEventFlag( EF_ACTION
);
5691 ieByte stance
= AttackStance
;
5692 for (int i
=0;i
<animcount
;i
++) {
5693 if ( strnicmp(item
->ItemResRef
, itemanim
[i
].itemname
, 8) == 0) {
5694 stance
= itemanim
[i
].animation
;
5699 //play only one cycle of animations
5701 // this was crashing for fuzzie due to NULL anims
5703 anims
->nextStanceID
=IE_ANI_READY
;
5704 anims
->autoSwitchOnEnd
=true;
5709 switch(itm
->UseCharge(item
->Usages
, header
, true)) {
5712 case CHG_BREAK
: //both
5714 core
->PlaySound(DS_ITEM_GONE
);
5717 case CHG_NOSOUND
: //remove item
5718 inventory
.BreakItemSlot(slot
);
5720 default: //don't do anything
5725 int Actor::IsReverseToHit()
5727 return ReverseToHit
;
5730 void Actor::InitButtons(ieDword cls
, bool forced
)
5735 if ( (PCStats
->QSlots
[0]!=0xff) && !forced
) {
5739 ActionButtonRow myrow
;
5740 if ((int) cls
>= classcount
) {
5741 memcpy(&myrow
, &DefaultButtons
, sizeof(ActionButtonRow
));
5742 for (int i
=0;i
<extraslots
;i
++) {
5743 if (cls
==OtherGUIButtons
[i
].clss
) {
5744 memcpy(&myrow
, &OtherGUIButtons
[i
].buttons
, sizeof(ActionButtonRow
));
5749 memcpy(&myrow
, GUIBTDefaults
+cls
, sizeof(ActionButtonRow
));
5751 SetActionButtonRow(myrow
);
5754 void Actor::SetFeat(unsigned int feat
, int mode
)
5756 if (feat
>=MAX_FEATS
) {
5759 ieDword mask
= 1<<(feat
&31);
5760 ieDword idx
= feat
>>5;
5762 case BM_SET
: case BM_OR
:
5763 BaseStats
[IE_FEATS1
+idx
]|=mask
;
5766 BaseStats
[IE_FEATS1
+idx
]&=~mask
;
5769 BaseStats
[IE_FEATS1
+idx
]^=mask
;
5774 void Actor::SetUsedWeapon(const char* AnimationType
, ieWord
* MeleeAnimation
, int wt
)
5776 memcpy(WeaponRef
, AnimationType
, sizeof(WeaponRef
) );
5777 if (wt
!= -1) WeaponType
= wt
;
5780 anims
->SetWeaponRef(AnimationType
);
5781 anims
->SetWeaponType(WeaponType
);
5782 SetAttackMoveChances(MeleeAnimation
);
5784 //update the paperdoll weapon animation
5785 core
->SetEventFlag(EF_UPDATEANIM
);
5788 ITMExtHeader
*header
= GetWeapon(wi
);
5790 if(header
&& (header
->AttackType
== ITEM_AT_BOW
)) {
5791 ITMExtHeader
* projHeader
= GetRangedWeapon(wi
);
5792 if (projHeader
->ProjectileQualifier
== 0) return; /* no ammo yet? */
5793 AttackStance
= IE_ANI_SHOOT
;
5794 anims
->SetRangedType(projHeader
->ProjectileQualifier
-1);
5795 //bows ARE one handed, from an anim POV at least
5796 anims
->SetWeaponType(IE_ANI_WEAPON_1H
);
5799 if(header
&& (header
->AttackType
== ITEM_AT_PROJECTILE
)) {
5800 AttackStance
= IE_ANI_ATTACK_SLASH
; //That's it!!
5803 AttackStance
= IE_ANI_ATTACK
;
5806 void Actor::SetUsedShield(const char* AnimationType
, int wt
)
5808 memcpy(ShieldRef
, AnimationType
, sizeof(ShieldRef
) );
5809 if (wt
!= -1) WeaponType
= wt
;
5810 if (AnimationType
[0] == ' ' || AnimationType
[0] == 0)
5811 if (WeaponType
== IE_ANI_WEAPON_2W
)
5812 WeaponType
= IE_ANI_WEAPON_1H
;
5816 anims
->SetOffhandRef(AnimationType
);
5817 anims
->SetWeaponType(WeaponType
);
5819 //update the paperdoll weapon animation
5820 core
->SetEventFlag(EF_UPDATEANIM
);
5824 void Actor::SetUsedHelmet(const char* AnimationType
)
5826 memcpy(HelmetRef
, AnimationType
, sizeof(HelmetRef
) );
5829 anims
->SetHelmetRef(AnimationType
);
5831 //update the paperdoll weapon animation
5832 core
->SetEventFlag(EF_UPDATEANIM
);
5836 void Actor::SetupFist()
5838 int slot
= core
->QuerySlot( 0 );
5839 assert (core
->QuerySlotEffects(slot
)==SLOT_EFFECT_FIST
);
5840 int row
= GetBase(fiststat
);
5841 int col
= GetXPLevel(false);
5845 AutoTable
fist("fistweap");
5848 strnlwrcpy( DefaultFist
, fist
->QueryField( (unsigned int) -1), 8);
5849 FistRows
= fist
->GetRowCount();
5850 fistres
= new FistResType
[FistRows
];
5851 for (int i
=0;i
<FistRows
;i
++) {
5852 int maxcol
= fist
->GetColumnCount(i
)-1;
5853 for (int cols
= 0;cols
<MAX_LEVEL
;cols
++) {
5854 strnlwrcpy( fistres
[i
][cols
], fist
->QueryField( i
, cols
>maxcol
?maxcol
:cols
), 8);
5856 *(int *) fistres
[i
] = atoi(fist
->GetRowName( i
));
5860 if (col
>MAX_LEVEL
) col
=MAX_LEVEL
;
5863 const char *ItemResRef
= DefaultFist
;
5864 for (int i
= 0;i
<FistRows
;i
++) {
5865 if (*(int *) fistres
[i
] == row
) {
5866 ItemResRef
= fistres
[i
][col
];
5869 inventory
.SetSlotItemRes(ItemResRef
, slot
);
5872 static ieDword
ResolveTableValue(const char *resref
, ieDword stat
, ieDword mcol
, ieDword vcol
) {
5874 //don't close this table, it can mess with the guiscripts
5875 int table
= gamedata
->LoadTable(resref
);
5876 Holder
<TableMgr
> tm
= gamedata
->GetTable(table
);
5882 row
= tm
->FindTableValue(mcol
, stat
);
5883 if (row
==0xffffffff) {
5887 if (valid_number(tm
->QueryField(row
, vcol
), ret
)) {
5888 return (ieDword
) ret
;
5895 static ieDword
GetKitIndex (ieDword kit
, const char *resref
="kitlist")
5899 if ((kit
&BG2_KITMASK
) == KIT_BARBARIAN
) {
5900 kitindex
= kit
&0xfff;
5903 // carefully looking for kit by the usability flag
5904 // since the barbarian kit id clashes with the no-kit value
5905 if (kitindex
== 0 && kit
!= KIT_BARBARIAN
) {
5906 Holder
<TableMgr
> tm
= gamedata
->GetTable(gamedata
->LoadTable(resref
) );
5908 kitindex
= tm
->FindTableValue(6, kit
);
5915 return (ieDword
)kitindex
;
5918 int Actor::CheckUsability(Item
*item
) const
5920 ieDword itembits
[2]={item
->UsabilityBitmask
, item
->KitUsability
};
5922 for (int i
=0;i
<usecount
;i
++) {
5923 ieDword itemvalue
= itembits
[itemuse
[i
].which
];
5924 ieDword stat
= GetStat(itemuse
[i
].stat
);
5925 ieDword mcol
= itemuse
[i
].mcol
;
5926 //if we have a kit, we just we use it's index for the lookup
5927 if (itemuse
[i
].stat
==IE_KIT
) {
5928 stat
= GetKitIndex(stat
, itemuse
[i
].table
);
5931 stat
= ResolveTableValue(itemuse
[i
].table
, stat
, mcol
, itemuse
[i
].vcol
);
5932 if (stat
&itemvalue
) {
5933 //printf("failed usability: itemvalue %d, stat %d, stat value %d\n", itemvalue, itemuse[i].stat, stat);
5934 return STR_CANNOT_USE_ITEM
;
5941 static EffectRef fx_cant_use_item_ref
={"CantUseItem",NULL
,-1};
5942 static EffectRef fx_cant_use_item_type_ref
={"CantUseItemType",NULL
,-1};
5944 //this one is the same, but returns strrefs based on effects
5945 ieStrRef
Actor::Disabled(ieResRef name
, ieDword type
) const
5949 fx
= fxqueue
.HasEffectWithResource(fx_cant_use_item_ref
, name
);
5951 return fx
->Parameter1
;
5954 fx
= fxqueue
.HasEffectWithParam(fx_cant_use_item_type_ref
, type
);
5956 return fx
->Parameter1
;
5961 //checks usability only
5962 int Actor::Unusable(Item
*item
) const
5964 if (!GetStat(IE_CANUSEANYITEM
)) {
5965 int unusable
= CheckUsability(item
);
5970 // iesdp says this is always checked?
5971 if (item
->MinLevel
>GetXPLevel(true)) {
5972 return STR_CANNOT_USE_ITEM
;
5975 if (!CheckAbilities
) {
5979 if (item
->MinStrength
>GetStat(IE_STR
)) {
5980 return STR_CANNOT_USE_ITEM
;
5983 if (item
->MinStrength
==18) {
5984 if (GetStat(IE_STR
)==18) {
5985 if (item
->MinStrengthBonus
>GetStat(IE_STREXTRA
)) {
5986 return STR_CANNOT_USE_ITEM
;
5991 if (item
->MinIntelligence
>GetStat(IE_INT
)) {
5992 return STR_CANNOT_USE_ITEM
;
5994 if (item
->MinDexterity
>GetStat(IE_DEX
)) {
5995 return STR_CANNOT_USE_ITEM
;
5997 if (item
->MinWisdom
>GetStat(IE_WIS
)) {
5998 return STR_CANNOT_USE_ITEM
;
6000 if (item
->MinConstitution
>GetStat(IE_CON
)) {
6001 return STR_CANNOT_USE_ITEM
;
6003 if (item
->MinCharisma
>GetStat(IE_CHR
)) {
6004 return STR_CANNOT_USE_ITEM
;
6006 //note, weapon proficiencies shouldn't be checked here
6007 //missing proficiency causes only attack penalty
6011 //full palette will be shaded in gradient color
6012 void Actor::SetGradient(ieDword gradient
)
6014 gradient
|= (gradient
<<16);
6015 gradient
|= (gradient
<<8);
6016 for(int i
=0;i
<7;i
++) {
6017 Modified
[IE_COLORS
+i
]=gradient
;
6021 //sets one bit of the sanctuary stat (used for overlays)
6022 void Actor::SetOverlay(unsigned int overlay
)
6024 if (overlay
>=32) return;
6025 Modified
[IE_SANCTUARY
]|=1<<overlay
;
6028 //returns true if spell state is already set or illegal
6029 bool Actor::SetSpellState(unsigned int spellstate
)
6031 if (spellstate
>=192) return true;
6032 unsigned int pos
= IE_SPLSTATE_ID1
+(spellstate
>>5);
6033 unsigned int bit
= 1<<(spellstate
&31);
6034 if (Modified
[pos
]&bit
) return true;
6039 //returns true if spell state is already set
6040 bool Actor::HasSpellState(unsigned int spellstate
)
6042 if (spellstate
>=192) return false;
6043 unsigned int pos
= IE_SPLSTATE_ID1
+(spellstate
>>5);
6044 unsigned int bit
= 1<<(spellstate
&31);
6045 if (Modified
[pos
]&bit
) return true;
6049 //returns the numeric value of a feat, different from HasFeat
6050 //for multiple feats
6051 int Actor::GetFeat(unsigned int feat
) const
6053 if (feat
>=MAX_FEATS
) {
6056 if (Modified
[IE_FEATS1
+(feat
>>5)]&(1<<(feat
&31)) ) {
6057 //return the numeric stat value, instead of the boolean
6058 if (featstats
[feat
]) {
6059 return Modified
[featstats
[feat
]];
6066 //returns true if the feat exists
6067 bool Actor::HasFeat(unsigned int featindex
) const
6069 if (featindex
>=MAX_FEATS
) return false;
6070 unsigned int pos
= IE_FEATS1
+(featindex
>>5);
6071 unsigned int bit
= 1<<(featindex
&31);
6072 if (Modified
[pos
]&bit
) return true;
6076 ieDword
Actor::ImmuneToProjectile(ieDword projectile
) const
6080 idx
= projectile
/32;
6081 if (idx
>ProjectileSize
) {
6084 return projectileImmunity
[idx
]&(1<<(projectile
&31));
6087 void Actor::AddProjectileImmunity(ieDword projectile
)
6089 projectileImmunity
[projectile
/32]|=1<<(projectile
&31);
6093 void Actor::CreateDerivedStatsBG()
6095 int turnundeadlevel
= 0;
6096 int classid
= BaseStats
[IE_CLASS
];
6098 //this works only for PC classes
6099 if (classid
>=CLASS_PCCUTOFF
) return;
6101 //recalculate all level based changes
6102 pcf_level(this,0,0);
6104 //even though the original didn't allow a cleric/paladin dual or multiclass
6105 //we shouldn't restrict the possibility by using "else if" here
6106 if (isclass
[ISCLERIC
]&(1<<classid
)) {
6107 turnundeadlevel
+= GetClericLevel()+1-turnlevels
[classid
];
6108 if (turnundeadlevel
<0) turnundeadlevel
=0;
6110 if (isclass
[ISPALADIN
]&(1<<classid
)) {
6111 turnundeadlevel
+= GetPaladinLevel()+1-turnlevels
[classid
];
6112 if (turnundeadlevel
<0) turnundeadlevel
=0;
6115 // barbarian immunity to backstab was hardcoded
6116 if (GetBarbarianLevel()) {
6117 BaseStats
[IE_DISABLEBACKSTAB
] = 1;
6120 ieDword backstabdamagemultiplier
=GetThiefLevel();
6121 if (backstabdamagemultiplier
) {
6122 // HACK: swashbucklers can't backstab
6123 if ((BaseStats
[IE_KIT
]&0xfff) == 12) {
6124 backstabdamagemultiplier
= 1;
6126 AutoTable
tm("backstab");
6127 //fallback to a general algorithm (bg2 backstab.2da version) if we can't find backstab.2da
6128 //TODO: AP_SPCL332 (increase backstab by one) seems to not be effecting this at all
6129 //for assassins perhaps the effect is being called prior to this, and this overwrites it;
6130 //stalkers work correctly, which is even more odd, considering as they use the same
6131 //effect and backstabmultiplier would be 0 for them
6133 ieDword cols
= tm
->GetColumnCount();
6134 if (backstabdamagemultiplier
>= cols
) backstabdamagemultiplier
= cols
;
6135 backstabdamagemultiplier
= atoi(tm
->QueryField(0, backstabdamagemultiplier
));
6137 backstabdamagemultiplier
= (backstabdamagemultiplier
+7)/4;
6140 if (backstabdamagemultiplier
>7) backstabdamagemultiplier
=7;
6144 // monk's level dictated ac and ac vs missiles bonus
6145 // attacks per round bonus will be handled elsewhere, since it only applies to fist apr
6146 if (isclass
[ISMONK
]&(1<<classid
)) {
6147 unsigned int level
= GetMonkLevel()-1;
6148 if (level
< monkbon_cols
) {
6149 BaseStats
[IE_ARMORCLASS
] = DEFAULTAC
- monkbon
[1][level
];
6150 BaseStats
[IE_ACMISSILEMOD
] = - monkbon
[2][level
];
6154 BaseStats
[IE_TURNUNDEADLEVEL
]=turnundeadlevel
;
6155 BaseStats
[IE_BACKSTABDAMAGEMULTIPLIER
]=backstabdamagemultiplier
;
6156 BaseStats
[IE_LAYONHANDSAMOUNT
]=GetPaladinLevel()*2;
6160 void Actor::CreateDerivedStatsIWD2()
6163 int turnundeadlevel
= 0;
6165 ieDword backstabdamagemultiplier
=GetThiefLevel();
6166 if (backstabdamagemultiplier
) {
6167 AutoTable
tm("backstab");
6169 ieDword cols
= tm
->GetColumnCount();
6170 if (backstabdamagemultiplier
>= cols
) backstabdamagemultiplier
= cols
;
6171 backstabdamagemultiplier
= atoi(tm
->QueryField(0, backstabdamagemultiplier
));
6173 backstabdamagemultiplier
= (BaseStats
[IE_LEVELTHIEF
]+1)/2;
6176 if (backstabdamagemultiplier
>7) backstabdamagemultiplier
=7;
6179 int layonhandsamount
= (int) BaseStats
[IE_LEVELPALADIN
];
6180 if (layonhandsamount
) {
6181 layonhandsamount
*= BaseStats
[IE_CHR
]/2-5;
6182 if (layonhandsamount
<1) layonhandsamount
= 1;
6185 for (i
=0;i
<11;i
++) {
6188 if (turnlevels
[i
+1]) {
6189 tmp
= BaseStats
[IE_LEVELBARBARIAN
+i
]+1-turnlevels
[i
+1];
6191 if (tmp
>turnundeadlevel
) turnundeadlevel
=tmp
;
6194 BaseStats
[IE_TURNUNDEADLEVEL
]=turnundeadlevel
;
6195 BaseStats
[IE_BACKSTABDAMAGEMULTIPLIER
]=backstabdamagemultiplier
;
6196 BaseStats
[IE_LAYONHANDSAMOUNT
]=(ieDword
) layonhandsamount
;
6199 //set up stuff here, like attack number, turn undead level
6200 //and similar derived stats that change with level
6201 void Actor::CreateDerivedStats()
6203 //we have to calculate multiclass for further code
6204 AutoTable
tm("classes");
6206 // currently we need only the MULTI value
6209 strcpy(tmpmulti
, tm
->QueryField(tm
->FindTableValue(5, BaseStats
[IE_CLASS
]), 4));
6210 if (!valid_number(tmpmulti
, tmp
))
6213 multiclass
= (ieDword
)tmp
;
6216 if (core
->HasFeature(GF_3ED_RULES
)) {
6217 CreateDerivedStatsIWD2();
6219 CreateDerivedStatsBG();
6222 /* Checks if the actor is multiclassed (the MULTI column is positive) */
6223 bool Actor::IsMultiClassed() const
6225 return (multiclass
> 0);
6228 /* Checks if the actor is dualclassed */
6229 bool Actor::IsDualClassed() const
6231 return (Modified
[IE_MC_FLAGS
] & MC_WAS_ANY
) > 0;
6234 Actor
*Actor::CopySelf() const
6236 Actor
*newActor
= new Actor();
6238 newActor
->SetName(GetName(0),0);
6239 newActor
->SetName(GetName(1),1);
6240 memcpy(newActor
->BaseStats
, BaseStats
, sizeof(BaseStats
) );
6241 // illusions aren't worth any xp
6242 newActor
->BaseStats
[IE_XPVALUE
] = 0;
6244 //IF_INITIALIZED shouldn't be set here, yet
6245 newActor
->SetMCFlag(MC_EXPORTABLE
, BM_NAND
);
6247 //the creature importer does this too
6248 memcpy(newActor
->Modified
,newActor
->BaseStats
, sizeof(Modified
) );
6250 //these need to be called too to have a valid inventory
6251 newActor
->inventory
.SetSlotCount(inventory
.GetSlotCount());
6252 newActor
->CreateDerivedStats();
6254 //copy the running effects
6255 EffectQueue
*newFXQueue
= fxqueue
.CopySelf();
6257 area
->AddActor(newActor
);
6258 newActor
->SetPosition( Pos
, CC_CHECK_IMPASSABLE
, 0 );
6259 newActor
->SetOrientation(GetOrientation(),0);
6260 newActor
->SetStance( IE_ANI_READY
);
6263 newActor
->RefreshEffects(newFXQueue
);
6267 ieDword
Actor::GetClassLevel(const ieDword id
) const
6272 //return iwd2 value if appropriate
6274 return BaseStats
[levelslotsiwd2
[id
]];
6276 //houston, we gots a problem!
6277 if (!levelslots
|| !dualswap
)
6280 //only works with PC's
6281 ieDword classid
= BaseStats
[IE_CLASS
]-1;
6282 if (classid
>=(ieDword
)classcount
|| !levelslots
[classid
])
6285 //handle barbarians specially, since they're kits and not in levelslots
6286 if (id
== ISBARBARIAN
&& levelslots
[classid
][ISFIGHTER
] && GetKitIndex(BaseStats
[IE_KIT
]) == 31) {
6287 return BaseStats
[IE_LEVEL
];
6290 //get the levelid (IE_LEVEL,*2,*3)
6291 ieDword levelid
= levelslots
[classid
][id
];
6296 if (IsDualClassed()) {
6297 //if the old class is inactive, and it is the class
6298 //being searched for, return 0
6299 if (IsDualInactive() && ((Modified
[IE_MC_FLAGS
]&MC_WAS_ANY
)==(ieDword
)mcwasflags
[id
]))
6302 return BaseStats
[levelid
];
6305 bool Actor::IsDualInactive() const
6307 if (!IsDualClassed()) return 0;
6309 //we assume the old class is in IE_LEVEL2, unless swapped
6310 ieDword oldlevel
= IsDualSwap() ? BaseStats
[IE_LEVEL
] : BaseStats
[IE_LEVEL2
];
6312 //since GetXPLevel returns the average of the 2 levels, oldclasslevel will
6313 //only be less than GetXPLevel when the new class surpasses it
6314 return oldlevel
>=GetXPLevel(false);
6317 bool Actor::IsDualSwap() const
6319 //the dualswap[class-1] holds the info
6320 if (!IsDualClassed()) return false;
6321 ieDword tmpclass
= BaseStats
[IE_CLASS
]-1;
6322 if (tmpclass
>=(ieDword
)classcount
) return false;
6323 return (ieDword
)dualswap
[tmpclass
]==(Modified
[IE_MC_FLAGS
]&MC_WAS_ANY
);
6326 ieDword
Actor::GetWarriorLevel() const
6328 if (!IsWarrior()) return 0;
6330 ieDword warriorlevels
[4] = {
6331 GetBarbarianLevel(),
6337 ieDword highest
= 0;
6338 for (int i
=0; i
<4; i
++) {
6339 if (warriorlevels
[i
] > highest
) {
6340 highest
= warriorlevels
[i
];
6347 bool Actor::BlocksSearchMap() const
6349 return Modified
[IE_DONOTJUMP
] < 2;
6352 //return true if the actor doesn't want to use an entrance
6353 bool Actor::CannotPassEntrance() const
6355 if (InternalFlags
&IF_USEEXIT
) {
6361 void Actor::UseExit(int flag
) {
6363 InternalFlags
|=IF_USEEXIT
;
6365 InternalFlags
&=~IF_USEEXIT
;
6369 // luck increases the minimum roll per dice, but only up to the number of dice sides;
6370 // luck does not affect critical hit chances:
6371 // if critical is set, it will return 1/sides on a critical, otherwise it can never
6372 // return a critical miss when luck is positive and can return a false critical hit
6373 int Actor::LuckyRoll(int dice
, int size
, int add
, bool critical
, bool only_damage
, Actor
* opponent
) const
6375 assert(this != opponent
);
6379 stat
= IE_DAMAGELUCK
;
6384 int luck
= (signed) GetStat(stat
);
6385 if (opponent
) luck
-= (signed) opponent
->GetStat(stat
);
6386 if (dice
< 1 || size
< 1) {
6392 if (abs(luck
) > size
) {
6393 bonus
= luck
/abs(luck
) * size
;
6397 int roll
= core
->Roll(1, dice
*size
, 0);
6398 if (critical
&& (roll
== 1 || roll
== size
)) {
6401 return add
+ dice
* (size
+ bonus
) / 2;
6405 int roll
, result
= 0, misses
= 0, hits
= 0;
6406 for (int i
= 0; i
< dice
; i
++) {
6407 roll
= core
->Roll(1, size
, 0);
6410 } else if (roll
== size
) {
6416 } else if (roll
< 1) {
6422 // ensure we can still return a critical failure/success
6423 if (critical
&& dice
== misses
) return 1;
6424 if (critical
&& dice
== hits
) return size
;
6426 return result
+ add
;
6429 static EffectRef fx_remove_invisible_state_ref
={"ForceVisible",NULL
,-1};
6431 // removes the (normal) invisibility state
6432 void Actor::CureInvisibility()
6434 if (Modified
[IE_STATE_ID
] & (ieDword
) (STATE_INVISIBLE
)) {
6435 //SetBaseBit(IE_STATE_ID, STATE_INVISIBLE, false);
6436 //fxqueue.RemoveAllEffectsWithParam(fx_set_invisible_state_ref, 0);
6438 //this is much closer to what the original engine does here
6441 newfx
= EffectQueue::CreateEffect(fx_remove_invisible_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
6442 core
->ApplyEffect(newfx
, this, this);
6446 //not sure, but better than nothing
6447 if (! (Modified
[IE_STATE_ID
]& STATE_INVISIBLE
)) {
6448 InternalFlags
|=IF_BECAMEVISIBLE
;
6453 static EffectRef fx_remove_sanctuary_ref
={"Cure:Sanctuary",NULL
,-1};
6455 // removes the sanctuary effect
6456 void Actor::CureSanctuary()
6460 newfx
= EffectQueue::CreateEffect(fx_remove_sanctuary_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
6461 core
->ApplyEffect(newfx
, this, this);
6466 void Actor::ResetState()
6473 // doesn't check the range, but only that the azimuth and the target
6474 // orientation match with a +/-2 allowed difference
6475 bool Actor::IsBehind(Actor
* target
)
6477 unsigned char tar_orient
= target
->GetOrientation();
6478 // computed, since we don't care where we face
6479 unsigned char my_orient
= GetOrient(target
->Pos
, Pos
);
6482 for (int i
=-2; i
<= 2; i
++) {
6484 if (diff
>= MAX_ORIENT
) diff
-= MAX_ORIENT
;
6485 if (diff
<= -1) diff
+= MAX_ORIENT
;
6486 if (diff
== (signed)tar_orient
) return true;
6491 // checks all the actor's stats to see if the target is her racial enemy
6492 bool Actor::IsRacialEnemy(Actor
* target
)
6494 if (Modified
[IE_HATEDRACE
] == target
->Modified
[IE_RACE
]) {
6496 } else if (core
->HasFeature(GF_3ED_RULES
)) {
6497 // iwd2 supports multiple racial enemies gained through level progression
6498 for (unsigned int i
=0; i
<7; i
++) {
6499 if (Modified
[IE_HATEDRACE2
+i
] == target
->Modified
[IE_RACE
]) {
6507 bool Actor::ModalSpellSkillCheck() {
6508 switch(ModalState
) {
6510 case MS_DETECTTRAPS
:
6521 static EffectRef fx_disable_button_ref
={ "DisableButton", NULL
, -1 };
6523 inline void HideFailed(Actor
* actor
)
6526 newfx
= EffectQueue::CreateEffect(fx_disable_button_ref
, 0, ACT_STEALTH
, FX_DURATION_INSTANT_LIMITED
);
6527 newfx
->Duration
= core
->Time
.round_sec
; // 90 ticks, 1 round
6528 core
->ApplyEffect(newfx
, actor
, actor
);
6532 bool Actor::TryToHide() {
6533 ieDword roll
= LuckyRoll(1, 100, 0);
6539 // check for disabled dualclassed thieves (not sure if we need it)
6541 if (Modified
[IE_DISABLEDBUTTON
] & (1<<ACT_STEALTH
)) {
6546 // check if the pc is in combat (seen / heard)
6547 Game
*game
= core
->GetGame();
6548 if (game
->PCInCombat(this)) {
6554 if (core
->HasFeature(GF_HAS_HIDE_IN_SHADOWS
)) {
6555 skill
= (GetStat(IE_HIDEINSHADOWS
) + GetStat(IE_STEALTH
))/2;
6557 skill
= GetStat(IE_STEALTH
);
6560 // check how bright our spot is
6561 ieDword lightness
= game
->GetCurrentArea()->GetLightLevel(Pos
);
6562 // seems to be the color overlay at midnight; lightness of a point with rgb (200, 100, 100)
6563 // TODO: but our NightTint computes to a higher value, which one is bad?
6564 ieDword light_diff
= int((lightness
- ref_lightness
) * 100 / (100 - ref_lightness
)) / 2;
6565 ieDword chance
= (100 - light_diff
) * skill
/100;
6567 if (roll
> chance
) {
6574 // only works with masks; use direct comparison for specific alignment checks
6575 bool Actor::MatchesAlignmentMask(ieDword mask
)
6577 ieDword stat
= Modified
[IE_ALIGNMENT
];
6581 return stat
== AL_LAWFUL_EVIL
|| stat
== AL_NEUTRAL_EVIL
|| stat
== AL_CHAOTIC_EVIL
;
6583 return stat
== AL_NEUTRAL_GOOD
|| stat
== AL_TRUE_NEUTRAL
|| stat
== AL_NEUTRAL_EVIL
;
6585 return stat
== AL_LAWFUL_GOOD
|| stat
== AL_NEUTRAL_GOOD
|| stat
== AL_CHAOTIC_GOOD
;
6587 return stat
== AL_CHAOTIC_GOOD
|| stat
== AL_CHAOTIC_NEUTRAL
|| stat
== AL_CHAOTIC_EVIL
;
6589 return stat
== AL_NEUTRAL_GOOD
|| stat
== AL_TRUE_NEUTRAL
|| stat
== AL_NEUTRAL_EVIL
;
6591 return stat
== AL_LAWFUL_GOOD
|| stat
== AL_LAWFUL_NEUTRAL
|| stat
== AL_LAWFUL_EVIL
;
6593 printf("Bad mask parameter (%d) used with Actor::MatchesAlignmentMask!\n", mask
);
6600 bool Actor::InvalidSpellTarget()
6602 if (GetStat(IE_STATE_ID
) & (STATE_DEAD
)) return true;
6603 if (HasSpellState(SS_SANCTUARY
)) return true;
6607 bool Actor::InvalidSpellTarget(int spellnum
, Actor
*caster
, int range
)
6611 //TODO: spell specific state checks
6612 if (!range
) return false;
6614 ResolveSpellName(spellres
, spellnum
);
6615 Spell
*spl
= gamedata
->GetSpell(spellres
);
6616 int srange
= spl
->GetCastingDistance(caster
);
6618 return srange
<range
;
6621 bool Actor::PCInDark() const
6623 if (!this) return false;
6624 unsigned int level
= area
->GetLightLevel(Pos
);
6625 if (level
<ref_lightness
) {