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)
33 #include "DialogHandler.h" // checking for dialog
35 #include "DisplayMessage.h"
37 #include "Interface.h"
39 #include "PolymorphCache.h" // stupid polymorph cache hack
40 #include "Projectile.h"
41 #include "ProjectileServer.h"
42 #include "ScriptEngine.h"
47 #include "GameScript/GSUtils.h" //needed for DisplayStringCore
48 #include "GameScript/GameScript.h"
49 #include "GUI/GameControl.h"
54 ieDword ref_lightness
= 43;
56 static const Color white
= {
57 0xff, 0xff, 0xff, 0xff
59 static const Color green
= {
60 0x00, 0xff, 0x00, 0xff
62 static const Color red
= {
63 0xff, 0x00, 0x00, 0xff
65 static const Color yellow
= {
66 0xff, 0xff, 0x00, 0xff
68 static const Color cyan
= {
69 0x00, 0xff, 0xff, 0xff
71 static const Color magenta
= {
72 0xff, 0x00, 0xff, 0xff
75 static int sharexp
= SX_DIVIDE
;
76 static int classcount
= -1;
77 static int extraslots
= -1;
78 static char **clericspelltables
= NULL
;
79 static char **druidspelltables
= NULL
;
80 static char **wizardspelltables
= NULL
;
81 static char **classabilities
= NULL
;
82 static int *turnlevels
= NULL
;
83 static int *booktypes
= NULL
;
84 static int *xpbonus
= NULL
;
85 static int xpbonustypes
= -1;
86 static int xpbonuslevels
= -1;
87 static int **levelslots
= NULL
;
88 static int *dualswap
= NULL
;
89 static int *maxhpconbon
= NULL
;
90 static ieVariable CounterNames
[4]={"GOOD","LAW","LADY","MURDER"};
92 static int FistRows
= -1;
94 typedef ieResRef FistResType
[MAX_LEVEL
+1];
96 static FistResType
*fistres
= NULL
;
97 static ieResRef DefaultFist
= {"FIST"};
99 static int VCMap
[VCONST_COUNT
];
101 //item usability array
103 ieResRef table
; //which table contains the stat usability flags
104 ieByte stat
; //which actor stat we talk about
105 ieByte mcol
; //which column should be matched against the stat
106 ieByte vcol
; //which column has the bit value for it
107 ieByte which
; //which item dword should be used (1 = kit)
110 static ItemUseType
*itemuse
= NULL
;
111 static int usecount
= -1;
113 //item animation override array
114 struct ItemAnimType
{
119 static ItemAnimType
*itemanim
= NULL
;
120 static int animcount
= -1;
122 static int fiststat
= IE_CLASS
;
124 //conversion for 3rd ed
125 static int isclass
[11]={0,0,0,0,0,0,0,0,0,0,0};
127 static const int mcwasflags
[11] = {
128 MC_WAS_FIGHTER
, MC_WAS_MAGE
, MC_WAS_THIEF
, 0, 0, MC_WAS_CLERIC
,
129 MC_WAS_DRUID
, 0, 0, MC_WAS_RANGER
, 0};
130 static const char *isclassnames
[11] = {
131 "FIGHTER", "MAGE", "THIEF", "BARBARIAN", "BARD", "CLERIC",
132 "DRUID", "MONK", "PALADIN", "RANGER", "SORCERER" };
134 //fighter is the default level here
135 //fixme, make this externalized
136 static const int levelslotsbg
[21]={ISFIGHTER
, ISMAGE
, ISFIGHTER
, ISCLERIC
, ISTHIEF
,
137 ISBARD
, ISPALADIN
, 0, 0, 0, 0, ISDRUID
, ISRANGER
, 0,0,0,0,0,0,ISSORCERER
, ISMONK
};
138 static const int levelslotsiwd2
[11]={IE_LEVELFIGHTER
,IE_LEVELMAGE
,IE_LEVELTHIEF
,
139 IE_LEVELBARBARIAN
,IE_LEVELBARD
,IE_LEVELCLERIC
,IE_LEVELDRUID
,IE_LEVELMONK
,
140 IE_LEVELPALADIN
,IE_LEVELRANGER
,IE_LEVELSORCEROR
};
141 static const unsigned int classesiwd2
[ISCLASSES
]={5, 11, 9, 1, 2, 3, 4, 6, 7, 8, 10};
143 //stat values are 0-255, so a byte is enough
144 static ieByte featstats
[MAX_FEATS
]={0
147 //holds the wspecial table for weapon prof bonuses
148 #define WSPECIAL_COLS 3
149 static int wspecial_max
= 0;
150 static int wspattack_rows
= 0;
151 static int wspattack_cols
= 0;
152 static int **wspecial
= NULL
;
153 static int **wspattack
= NULL
;
155 //holds the weapon style bonuses
157 static int **wsdualwield
= NULL
;
158 static int **wstwohanded
= NULL
;
159 static int **wsswordshield
= NULL
;
160 static int **wssingle
= NULL
;
162 //unhardcoded monk bonuses
163 static int **monkbon
= NULL
;
164 static unsigned int monkbon_cols
= 0;
165 static unsigned int monkbon_rows
= 0;
167 // reputation modifiers
168 #define CLASS_PCCUTOFF 32
169 #define CLASS_INNOCENT 155
170 #define CLASS_FLAMINGFIST 156
172 static ActionButtonRow
*GUIBTDefaults
= NULL
; //qslots row count
173 static ActionButtonRow2
*OtherGUIButtons
= NULL
;
174 ActionButtonRow DefaultButtons
= {ACT_TALK
, ACT_WEAPON1
, ACT_WEAPON2
,
175 ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
, ACT_NONE
,
176 ACT_NONE
, ACT_INNATE
};
177 static int QslotTranslation
= false;
178 static int DeathOnZeroStat
= true;
179 static ieDword TranslucentShadows
= 0;
180 static int ProjectileSize
= 0; //the size of the projectile immunity bitfield (dwords)
182 static const char iwd2gemrb
[32] = {
184 15,23,13,0,1,24,8,21,
188 static const char gemrb2iwd
[32] = {
189 11,12,3,71,72,73,0,0, //0
190 14,80,83,82,81,10,7,8, //8
191 0,0,0,0,2,15,4,9, //16
192 13,5,0,0,0,0,0,0 //24
195 //letters for char sound resolution bg1/bg2
196 static char csound
[VCONST_COUNT
];
198 static void InitActorTables();
200 #define DAMAGE_LEVELS 19
201 #define ATTACKROLL 20
206 #define TURN_PANIC_LVL_MOD 3
207 #define TURN_DEATH_LVL_MOD 7
209 static ieResRef d_main
[DAMAGE_LEVELS
] = {
210 //slot 0 is not used in the original engine
211 "BLOODCR","BLOODS","BLOODM","BLOODL", //blood
212 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //fire
213 "SPSHKIMP","SPSHKIMP","SPSHKIMP", //spark
214 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //ice
215 "SHACID","SHACID","SHACID", //acid
216 "SPDUSTY2","SPDUSTY2","SPDUSTY2" //disintegrate
218 static ieResRef d_splash
[DAMAGE_LEVELS
] = {
220 "SPBURN","SPBURN","SPBURN", //flames
221 "SPSPARKS","SPSPARKS","SPSPARKS", //sparks
227 #define BLOOD_GRADIENT 19
228 #define FIRE_GRADIENT 19
229 #define ICE_GRADIENT 71
230 #define STONE_GRADIENT 93
232 static int d_gradient
[DAMAGE_LEVELS
] = {
233 BLOOD_GRADIENT
,BLOOD_GRADIENT
,BLOOD_GRADIENT
,BLOOD_GRADIENT
,
234 FIRE_GRADIENT
,FIRE_GRADIENT
,FIRE_GRADIENT
,
236 ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,
241 static ieResRef hc_overlays
[OVERLAY_COUNT
]={"SANCTRY","SPENTACI","SPMAGGLO","SPSHIELD",
242 "GREASED","WEBENTD","MINORGLB","","","","","","","","","","","","","","",
243 "","","","SPTURNI2","SPTURNI","","","","","",""};
244 static ieDword hc_locations
=0x2ba80030;
246 static int *mxsplwis
= NULL
;
247 static int spllevels
;
249 //for every game except IWD2 we need to reverse TOHIT
250 static int ReverseToHit
=true;
251 static int CheckAbilities
=false;
253 //internal flags for calculating to hit
254 #define WEAPON_FIST 0
255 #define WEAPON_MELEE 1
256 #define WEAPON_RANGED 2
257 #define WEAPON_STYLEMASK 15
258 #define WEAPON_LEFTHAND 16
259 #define WEAPON_USESTRENGTH 32
261 /* counts the on bits in a number */
262 ieDword
bitcount (ieDword n
)
272 void ReleaseMemoryActor()
275 //calloc'd x*y integer matrix
298 : Movable( ST_ACTOR
)
302 for (i
= 0; i
< MAX_STATS
; i
++) {
307 SmallPortrait
[0] = 0;
308 LargePortrait
[0] = 0;
314 for (i
= 0; i
< EXTRA_ACTORCOVERS
; ++i
)
315 extraCovers
[i
] = NULL
;
319 LongStrRef
= (ieStrRef
) -1;
320 ShortStrRef
= (ieStrRef
) -1;
331 LastCommand
= 0; //used by order
332 LastShout
= 0; //used by heard
344 attackProjectile
= NULL
;
349 inventory
.SetInventoryType(INVENTORY_CREATURE
);
353 fxqueue
.SetOwner( this );
354 inventory
.SetOwner( this );
356 //This block is executed only once, when the first actor is loaded
359 TranslucentShadows
= 0;
360 core
->GetDictionary()->Lookup("Translucent Shadows", TranslucentShadows
);
361 //get the needed size to store projectile immunity bitflags in Dwords
362 ProjectileSize
= (core
->GetProjectileServer()->GetHighestProjectileNumber()+31)/32;
363 //allowing 1024 bits (1024 projectiles ought to be enough for everybody)
364 //the rest of the projectiles would still work, but couldn't be resisted
365 if (ProjectileSize
>32) {
369 projectileImmunity
= (ieDword
*) calloc(ProjectileSize
,sizeof(ieDword
));
371 SetDeathVar
= IncKillCount
= UnknownField
= 0;
372 memset( DeathCounters
, 0, sizeof(DeathCounters
) );
375 InteractCount
= 0; //numtimesinteracted depends on this
376 appearance
= 0xffffff; //might be important for created creatures
379 //these are used only in iwd2 so we have to default them
381 BaseStats
[IE_HATEDRACE2
+i
]=0xff;
383 //this one is saved only for PC's
385 //set it to a neutral value
387 //this one is saved, but not loaded?
388 localID
= globalID
= 0;
389 //this one is not saved
390 GotLUFeedback
= false;
393 polymorphCache
= NULL
;
402 core
->FreeString( LongName
);
403 core
->FreeString( ShortName
);
407 for (i
= 0; i
< vvcOverlays
.size(); i
++) {
408 if (vvcOverlays
[i
]) {
409 delete vvcOverlays
[i
];
410 vvcOverlays
[i
] = NULL
;
413 for (i
= 0; i
< vvcShields
.size(); i
++) {
415 delete vvcShields
[i
];
416 vvcShields
[i
] = NULL
;
419 for (i
= 0; i
< EXTRA_ACTORCOVERS
; i
++)
420 delete extraCovers
[i
];
422 delete attackProjectile
;
423 delete polymorphCache
;
425 free(projectileImmunity
);
428 void Actor::SetFistStat(ieDword stat
)
433 void Actor::SetDefaultActions(int qslot
, ieByte slot1
, ieByte slot2
, ieByte slot3
)
435 QslotTranslation
=qslot
;
436 DefaultButtons
[0]=slot1
;
437 DefaultButtons
[1]=slot2
;
438 DefaultButtons
[2]=slot3
;
441 void Actor::SetName(const char* ptr
, unsigned char type
)
443 size_t len
= strlen( ptr
) + 1;
444 //32 is the maximum possible length of the actor name in the original games
447 LongName
= ( char * ) realloc( LongName
, len
);
448 memcpy( LongName
, ptr
, len
);
449 core
->StripLine( LongName
, len
);
452 ShortName
= ( char * ) realloc( ShortName
, len
);
453 memcpy( ShortName
, ptr
, len
);
454 core
->StripLine( ShortName
, len
);
458 void Actor::SetName(int strref
, unsigned char type
)
461 if (LongName
) free(LongName
);
462 LongName
= core
->GetString( strref
, IE_STR_REMOVE_NEWLINE
);
465 if (ShortName
) free(ShortName
);
466 ShortName
= core
->GetString( strref
, IE_STR_REMOVE_NEWLINE
);
470 void Actor::SetAnimationID(unsigned int AnimID
)
472 //if the palette is locked, then it will be transferred to the new animation
473 Palette
*recover
= NULL
;
476 if (anims
->lockPalette
) {
477 recover
= anims
->palette
[PAL_MAIN
];
479 // Take ownership so the palette won't be deleted
485 //hacking PST no palette
486 if (core
->HasFeature(GF_ONE_BYTE_ANIMID
) ) {
487 if ((AnimID
&0xf000)==0xe000) {
488 if (BaseStats
[IE_COLORCOUNT
]) {
489 printMessage("Actor"," ",YELLOW
);
490 printf("Animation ID %x is supposed to be real colored (no recoloring), patched creature\n", AnimID
);
492 BaseStats
[IE_COLORCOUNT
]=0;
495 anims
= new CharAnimations( AnimID
&0xffff, BaseStats
[IE_ARMOR_TYPE
]);
496 if(anims
->ResRef
[0] == 0) {
499 printMessage("Actor", " ",LIGHT_RED
);
500 printf("Missing animation for %s\n",LongName
);
503 anims
->SetOffhandRef(ShieldRef
);
504 anims
->SetHelmetRef(HelmetRef
);
505 anims
->SetWeaponRef(WeaponRef
);
507 //if we have a recovery palette, then set it back
508 assert(anims
->palette
[PAL_MAIN
] == 0);
509 anims
->palette
[PAL_MAIN
] = recover
;
511 anims
->lockPalette
= true;
513 //bird animations are not hindered by searchmap
514 //only animtype==7 (bird) uses this feature
515 //this is a hardcoded hack, but works for all engine type
516 if (anims
->GetAnimType()!=IE_ANI_BIRD
) {
517 BaseStats
[IE_DONOTJUMP
]=0;
519 BaseStats
[IE_DONOTJUMP
]=DNJ_BIRD
;
522 anims
->SetColors(BaseStats
+IE_COLORS
);
524 //Speed is determined by the number of frames in each cycle of its animation
525 // (beware! GetAnimation has side effects!)
526 // TODO: we should have a more efficient way to look this up
527 Animation
** anim
= anims
->GetAnimation(IE_ANI_WALK
, 0);
528 if (anim
&& anim
[0]) {
529 SetBase(IE_MOVEMENTRATE
, anim
[0]->GetFrameCount()) ;
531 printMessage("Actor", "Unable to determine movement rate for animation ", YELLOW
);
532 printf("%04x!\n", AnimID
);
537 CharAnimations
* Actor::GetAnims()
542 /** Returns a Stat value (Base Value + Mod) */
543 ieDword
Actor::GetStat(unsigned int StatIndex
) const
545 if (StatIndex
>= MAX_STATS
) {
548 return Modified
[StatIndex
];
551 void Actor::SetCircleSize()
559 GameControl
*gc
= core
->GetGameControl();
560 if (UnselectableTimer
) {
563 } else if (Modified
[IE_STATE_ID
] & STATE_PANIC
) {
566 } else if (gc
&& gc
->dialoghandler
->targetID
== globalID
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
)) {
568 color_index
= 3; //?? made up
570 switch (Modified
[IE_EA
]) {
576 case EA_EVILBUTGREEN
:
595 int csize
= anims
->GetCircleSize() - 1;
596 if (csize
>= MAX_CIRCLE_SIZE
)
597 csize
= MAX_CIRCLE_SIZE
- 1;
599 SetCircle( anims
->GetCircleSize(), *color
, core
->GroundCircles
[csize
][color_index
], core
->GroundCircles
[csize
][(color_index
== 0) ? 3 : color_index
] );
602 static void ApplyClab_internal(Actor
*actor
, const char *clab
, int level
, bool remove
)
604 AutoTable
table(clab
);
606 int row
= table
->GetRowCount();
607 for(int i
=0;i
<level
;i
++) {
608 for (int j
=0;j
<row
;j
++) {
609 const char *res
= table
->QueryField(j
,i
);
610 if (res
[0]=='*') continue;
612 if (!memcmp(res
,"AP_",3)) {
614 actor
->fxqueue
.RemoveAllEffects(res
+3);
616 core
->ApplySpell(res
+3, actor
, actor
, 0);
619 else if (!memcmp(res
,"GA_",3)) {
621 actor
->spellbook
.RemoveSpell(res
+3);
623 actor
->LearnSpell(res
+3, LS_MEMO
);
626 else if (!memcmp(res
,"FA_",3)) {//iwd2 only
630 ResolveSpellName(resref
, x
);
631 actor
->LearnSpell(resref
, LS_MEMO
);
633 else if (!memcmp(res
,"FS_",3)) {//iwd2 only
634 //don't memorize these
637 ResolveSpellName(resref
, x
);
638 actor
->LearnSpell(resref
, 0);
640 else if (!memcmp(res
,"RA_",3)) {//iwd2 only
643 actor
->spellbook
.RemoveSpell(x
);
650 #define BG2_KITMASK 0xffffc000
651 #define KIT_BASECLASS 0x4000
653 static ieDword
GetKitIndex (ieDword kit
, const char *resref
="kitlist")
657 if ((kit
&BG2_KITMASK
) == KIT_BASECLASS
) {
658 kitindex
= kit
&0xfff;
661 // carefully looking for kit by the usability flag
662 // since the barbarian kit id clashes with the no-kit value
663 if (kitindex
== 0 && kit
!= KIT_BASECLASS
) {
664 Holder
<TableMgr
> tm
= gamedata
->GetTable(gamedata
->LoadTable(resref
) );
666 kitindex
= tm
->FindTableValue(6, kit
);
673 return (ieDword
)kitindex
;
676 //applies a kit on the character (only bg2)
677 bool Actor::ApplyKit(bool remove
)
679 ieDword kit
= GetStat(IE_KIT
);
680 ieDword kitclass
= 0;
681 ieDword row
= GetKitIndex(kit
);
682 const char *clab
= NULL
;
687 Holder
<TableMgr
> tm
= gamedata
->GetTable(gamedata
->LoadTable("kitlist"));
689 kitclass
= (ieDword
) atoi(tm
->QueryField(row
, 7));
690 clab
= tm
->QueryField(row
, 4);
697 for(unsigned int i
=1;(i
<32) && (msk
<=multiclass
);i
++) {
698 if (multiclass
& msk
) {
699 max
= GetClassLevel(levelslotsbg
[i
]);
700 // don't apply/remove the old kit clab if the kit is disabled
701 if (i
==kitclass
&& !IsDualClassed()) {
702 ApplyClab(clab
, max
, remove
);
704 ApplyClab(classabilities
[i
], max
, remove
);
712 ieDword cls
= GetStat(IE_CLASS
);
713 max
= GetClassLevel(levelslotsbg
[cls
]);
715 ApplyClab(clab
, max
, remove
);
718 ApplyClab(classabilities
[cls
], max
, remove
);
723 void Actor::ApplyClab(const char *clab
, ieDword max
, bool remove
)
729 ApplyClab_internal(this, clab
, max
, true);
731 ApplyClab_internal(this, clab
, max
, true);
732 ApplyClab_internal(this, clab
, max
, false);
738 //call this when morale or moralebreak changed
739 //cannot use old or new value, because it is called two ways
740 void pcf_morale (Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
742 if ((actor
->Modified
[IE_MORALE
]<=actor
->Modified
[IE_MORALEBREAK
]) && (actor
->Modified
[IE_MORALEBREAK
] != 0) ) {
746 actor
->SetCircleSize();
749 void pcf_ea (Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
751 if (actor
->Selected
&& (newValue
>EA_GOODCUTOFF
) ) {
752 core
->GetGame()->SelectActor(actor
, false, SELECT_NORMAL
);
754 actor
->SetCircleSize();
757 //this is a good place to recalculate level up stuff
758 void pcf_level (Actor
*actor
, ieDword oldValue
, ieDword newValue
)
761 actor
->GetFighterLevel()+
762 actor
->GetMageLevel()+
763 actor
->GetThiefLevel()+
764 actor
->GetBarbarianLevel()+
765 actor
->GetBardLevel()+
766 actor
->GetClericLevel()+
767 actor
->GetDruidLevel()+
768 actor
->GetMonkLevel()+
769 actor
->GetPaladinLevel()+
770 actor
->GetRangerLevel()+
771 actor
->GetSorcererLevel();
772 actor
->SetBase(IE_CLASSLEVELSUM
,sum
);
774 if (newValue
!=oldValue
) {
775 actor
->ApplyKit(false);
777 actor
->GotLUFeedback
= false;
780 void pcf_class (Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
782 actor
->InitButtons(newValue
, true);
785 if (newValue
<(ieDword
) classcount
) {
786 switch(booktypes
[newValue
]) {
787 case 2: sorcerer
= 1<<IE_SPELL_TYPE_WIZARD
; break;
788 case 3: sorcerer
= 1<<IE_SPELL_TYPE_PRIEST
; break;
792 actor
->spellbook
.SetBookType(sorcerer
);
795 void pcf_animid(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
797 actor
->SetAnimationID(newValue
);
800 static const ieDword fullwhite
[7]={ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
,ICE_GRADIENT
};
802 static const ieDword fullstone
[7]={STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
,STONE_GRADIENT
};
804 void pcf_state(Actor
*actor
, ieDword
/*oldValue*/, ieDword State
)
806 if (actor
->InParty
) core
->SetEventFlag(EF_PORTRAIT
);
807 if (State
& STATE_PETRIFIED
) {
808 actor
->SetLockedPalette(fullstone
);
811 if (State
& STATE_FROZEN
) {
812 actor
->SetLockedPalette(fullwhite
);
815 //it is not enough to check the new state
816 core
->GetGame()->Infravision();
817 actor
->UnlockPalette();
820 //changes based on extended state bits, right now it is only the seven eyes
821 //animation (used in how/iwd2)
822 void pcf_extstate(Actor
*actor
, ieDword oldValue
, ieDword State
)
824 if ((oldValue
^State
)&EXTSTATE_SEVEN_EYES
) {
825 ieDword mask
= EXTSTATE_EYE_MIND
;
827 for (int i
=0;i
<7;i
++)
829 if (State
&mask
) eyeCount
--;
832 ScriptedAnimation
*sca
= actor
->FindOverlay(OV_SEVENEYES
);
834 sca
->SetOrientation(eyeCount
);
836 sca
= actor
->FindOverlay(OV_SEVENEYES2
);
838 sca
->SetOrientation(eyeCount
);
843 void pcf_hitpoint(Actor
*actor
, ieDword
/*oldValue*/, ieDword hp
)
845 if ((signed) actor
->BaseStats
[IE_HITPOINTS
]>(signed) actor
->Modified
[IE_MAXHITPOINTS
]) {
846 actor
->BaseStats
[IE_HITPOINTS
]=actor
->Modified
[IE_MAXHITPOINTS
];
849 int hptmp
= (signed) actor
->Modified
[IE_MAXHITPOINTS
];
850 if ((signed) hp
>hptmp
) {
854 hptmp
= (signed) actor
->Modified
[IE_MINHITPOINTS
];
855 if (hptmp
&& (signed) hp
<hptmp
) {
858 if ((signed) hp
<=0) {
861 actor
->BaseStats
[IE_HITPOINTS
]=hp
;
862 actor
->Modified
[IE_HITPOINTS
]=hp
;
863 if (actor
->InParty
) core
->SetEventFlag(EF_PORTRAIT
);
866 void pcf_maxhitpoint(Actor
*actor
, ieDword
/*oldValue*/, ieDword hp
)
868 if ((signed) hp
<(signed) actor
->BaseStats
[IE_HITPOINTS
]) {
869 actor
->BaseStats
[IE_HITPOINTS
]=hp
;
870 //passing 0 because it is ignored anyway
871 pcf_hitpoint(actor
, 0, hp
);
875 void pcf_minhitpoint(Actor
*actor
, ieDword
/*oldValue*/, ieDword hp
)
877 if ((signed) hp
>(signed) actor
->BaseStats
[IE_HITPOINTS
]) {
878 actor
->BaseStats
[IE_HITPOINTS
]=hp
;
879 //passing 0 because it is ignored anyway
880 pcf_hitpoint(actor
, 0, hp
);
884 void pcf_stat(Actor
*actor
, ieDword newValue
, ieDword stat
)
886 if ((signed) newValue
<=0) {
887 if (DeathOnZeroStat
) {
890 actor
->Modified
[stat
]=1;
895 void pcf_stat_str(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
897 pcf_stat(actor
, newValue
, IE_STR
);
900 void pcf_stat_int(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
902 pcf_stat(actor
, newValue
, IE_INT
);
905 void pcf_stat_wis(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
907 pcf_stat(actor
, newValue
, IE_WIS
);
910 void pcf_stat_dex(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
912 pcf_stat(actor
, newValue
, IE_DEX
);
915 void pcf_stat_con(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
917 pcf_stat(actor
, newValue
, IE_CON
);
918 pcf_hitpoint(actor
, 0, actor
->BaseStats
[IE_HITPOINTS
]);
921 void pcf_stat_cha(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
923 pcf_stat(actor
, newValue
, IE_CHR
);
926 void pcf_xp(Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
928 // check if we reached a new level
929 unsigned int pc
= actor
->InParty
;
930 if (pc
&& !actor
->GotLUFeedback
) {
932 sprintf(varname
, "CheckLevelUp%d", pc
);
933 core
->GetGUIScriptEngine()->RunFunction("GUICommonWindows", "CheckLevelUp", true, pc
);
934 ieDword NeedsLevelUp
= 0;
935 core
->GetDictionary()->Lookup(varname
, NeedsLevelUp
);
936 if (NeedsLevelUp
== 1) {
937 displaymsg
->DisplayConstantStringName(STR_LEVELUP
, 0xffffff, actor
);
938 actor
->GotLUFeedback
= true;
943 void pcf_gold(Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
945 //this function will make a party member automatically donate their
946 //gold to the party pool, not the same as in the original engine
947 if (actor
->InParty
) {
948 Game
*game
= core
->GetGame();
949 game
->AddGold ( actor
->BaseStats
[IE_GOLD
] );
950 actor
->BaseStats
[IE_GOLD
]=0;
954 static void handle_overlay(Actor
*actor
, ieDword idx
)
956 if (actor
->FindOverlay(idx
))
958 ieDword flag
= hc_locations
&(1<<idx
);
959 ScriptedAnimation
*sca
= gamedata
->GetScriptedAnimation(hc_overlays
[idx
], false);
964 actor
->AddVVCell(sca
);
968 //de/activates the entangle overlay
969 void pcf_entangle(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
972 handle_overlay(actor
, OV_ENTANGLE
);
975 actor
->RemoveVVCell(hc_overlays
[OV_ENTANGLE
], true);
979 //de/activates the sanctuary and other overlays
980 //unlike IE, gemrb uses this stat for other overlay fields
981 //see the complete list in overlay.2da
982 //it loosely follows the internal representation of overlays in IWD2
983 void pcf_sanctuary(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
985 ieDword changed
= newValue
^oldValue
;
987 for (int i
=0;i
<32;i
++) {
990 handle_overlay(actor
, i
);
992 actor
->RemoveVVCell(hc_overlays
[i
], true);
999 //de/activates the prot from missiles overlay
1000 void pcf_shieldglobe(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1003 handle_overlay(actor
, OV_SHIELDGLOBE
);
1007 actor
->RemoveVVCell(hc_overlays
[OV_SHIELDGLOBE
], true);
1011 //de/activates the globe of invul. overlay
1012 void pcf_minorglobe(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1015 handle_overlay(actor
, OV_MINORGLOBE
);
1019 actor
->RemoveVVCell(hc_overlays
[OV_MINORGLOBE
], true);
1023 //de/activates the grease background
1024 void pcf_grease(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1027 handle_overlay(actor
, OV_GREASE
);
1031 actor
->RemoveVVCell(hc_overlays
[OV_GREASE
], true);
1035 //de/activates the web overlay
1036 //the web effect also immobilizes the actor!
1037 void pcf_web(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1040 handle_overlay(actor
, OV_WEB
);
1044 actor
->RemoveVVCell(hc_overlays
[OV_WEB
], true);
1048 //de/activates the spell bounce background
1049 void pcf_bounce(Actor
*actor
, ieDword oldValue
, ieDword newValue
)
1052 handle_overlay(actor
, OV_BOUNCE
);
1056 //it seems we have to remove it abruptly
1057 actor
->RemoveVVCell(hc_overlays
[OV_BOUNCE
], false);
1061 //no separate values (changes are permanent)
1062 void pcf_fatigue(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
1064 actor
->BaseStats
[IE_FATIGUE
]=newValue
;
1067 //no separate values (changes are permanent)
1068 void pcf_intoxication(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
1070 actor
->BaseStats
[IE_INTOXICATION
]=newValue
;
1073 void pcf_color(Actor
*actor
, ieDword
/*oldValue*/, ieDword
/*newValue*/)
1075 CharAnimations
*anims
= actor
->GetAnims();
1077 anims
->SetColors(actor
->Modified
+IE_COLORS
);
1081 void pcf_armorlevel(Actor
*actor
, ieDword
/*oldValue*/, ieDword newValue
)
1083 CharAnimations
*anims
= actor
->GetAnims();
1085 anims
->SetArmourLevel(newValue
);
1089 static int maximum_values
[MAX_STATS
]={
1090 32767,32767,20,100,100,100,100,25,10,25,25,25,25,25,100,100,//0f
1091 100,100,100,100,100,100,100,100,100,100,255,255,255,255,100,100,//1f
1092 200,200,MAX_LEVEL
,255,25,100,25,25,25,25,25,999999999,999999999,999999999,25,25,//2f
1093 200,255,200,100,100,200,200,25,5,100,1,1,100,1,1,0,//3f
1094 511,1,1,1,MAX_LEVEL
,MAX_LEVEL
,1,9999,25,100,100,255,1,20,20,25,//4f
1095 25,1,1,255,25,25,255,255,25,255,255,255,255,255,255,255,//5f
1096 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//6f
1097 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//7f
1098 255,255,255,255,255,255,255,100,100,100,255,5,5,255,1,1,//8f
1099 1,25,25,30,1,1,1,25,0,100,100,1,255,255,255,255,//9f
1100 255,255,255,255,255,255,20,255,255,1,20,255,999999999,999999999,1,1,//af
1101 999999999,999999999,0,0,20,0,0,0,0,0,0,0,0,0,0,0,//bf
1102 0,0,0,0,0,0,0,25,25,255,255,255,255,65535,0,0,//cf - 207
1103 0,0,0,0,0,0,0,0,MAX_LEVEL
,255,65535,3,255,255,255,255,//df - 223
1104 255,255,255,255,255,255,255,255,255,255,255,255,65535,65535,15,0,//ef - 239
1105 MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
, MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
,MAX_LEVEL
, //0xf7 - 247
1106 MAX_LEVEL
,MAX_LEVEL
,0,0,0,0,0,0//ff
1109 typedef void (*PostChangeFunctionType
)(Actor
*actor
, ieDword oldValue
, ieDword newValue
);
1110 static PostChangeFunctionType post_change_functions
[MAX_STATS
]={
1111 pcf_hitpoint
, pcf_maxhitpoint
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
1112 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //0f
1113 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1114 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, pcf_fatigue
, pcf_intoxication
, //1f
1115 NULL
,NULL
,pcf_level
,NULL
, pcf_stat_str
, NULL
, pcf_stat_int
, pcf_stat_wis
,
1116 pcf_stat_dex
,pcf_stat_con
,pcf_stat_cha
,NULL
, pcf_xp
, pcf_gold
, pcf_morale
, NULL
, //2f
1117 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1118 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, pcf_entangle
, pcf_sanctuary
, //3f
1119 pcf_minorglobe
, pcf_shieldglobe
, pcf_grease
, pcf_web
, pcf_level
, pcf_level
, NULL
, NULL
,
1120 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //4f
1121 NULL
,NULL
,NULL
,pcf_minhitpoint
, NULL
, NULL
, NULL
, NULL
,
1122 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //5f
1123 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1124 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //6f
1125 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1126 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //7f
1127 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1128 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //8f
1129 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1130 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //9f
1131 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1132 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //af
1133 NULL
,NULL
,NULL
,NULL
, pcf_morale
, pcf_bounce
, NULL
, NULL
,
1134 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
, //bf
1135 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1136 NULL
,NULL
,NULL
,NULL
, NULL
, pcf_animid
,pcf_state
, pcf_extstate
, //cf
1137 pcf_color
,pcf_color
,pcf_color
,pcf_color
, pcf_color
, pcf_color
, pcf_color
, NULL
,
1138 NULL
,NULL
,NULL
,pcf_armorlevel
, NULL
, NULL
, NULL
, NULL
, //df
1139 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
,
1140 pcf_class
,NULL
,pcf_ea
,NULL
, NULL
, NULL
, NULL
, NULL
, //ef
1141 pcf_level
,pcf_level
,pcf_level
,pcf_level
, pcf_level
, pcf_level
, pcf_level
, pcf_level
,
1142 NULL
,NULL
,NULL
,NULL
, NULL
, NULL
, NULL
, NULL
//ff
1145 /** call this from ~Interface() */
1146 void Actor::ReleaseMemory()
1150 if (classcount
>=0) {
1151 if (clericspelltables
) {
1152 for (i
=0;i
<classcount
;i
++) {
1153 if (clericspelltables
[i
]) {
1154 free (clericspelltables
[i
]);
1157 free(clericspelltables
);
1158 clericspelltables
=NULL
;
1160 if (druidspelltables
) {
1161 for (i
=0;i
<classcount
;i
++) {
1162 if (druidspelltables
[i
]) {
1163 free (druidspelltables
[i
]);
1166 free(druidspelltables
);
1167 druidspelltables
=NULL
;
1169 if (wizardspelltables
) {
1170 for (i
=0;i
<classcount
;i
++) {
1171 if (wizardspelltables
[i
]) {
1172 free(wizardspelltables
[i
]);
1175 free(wizardspelltables
);
1176 wizardspelltables
=NULL
;
1178 if (classabilities
) {
1179 for (i
=0;i
<classcount
;i
++) {
1180 if (classabilities
[i
]) {
1181 free (classabilities
[i
]);
1184 free(classabilities
);
1185 classabilities
=NULL
;
1204 for (i
=0; i
<classcount
; i
++) {
1205 if (levelslots
[i
]) {
1206 free(levelslots
[i
]);
1221 for (i
=0; i
<=wspecial_max
; i
++) {
1230 for (i
=0; i
<wspattack_rows
; i
++) {
1239 for (i
=0; i
<=STYLE_MAX
; i
++) {
1240 if (wsdualwield
[i
]) {
1241 free(wsdualwield
[i
]);
1248 for (i
=0; i
<=STYLE_MAX
; i
++) {
1249 if (wstwohanded
[i
]) {
1250 free(wstwohanded
[i
]);
1256 if (wsswordshield
) {
1257 for (i
=0; i
<=STYLE_MAX
; i
++) {
1258 if (wsswordshield
[i
]) {
1259 free(wsswordshield
[i
]);
1262 free(wsswordshield
);
1266 for (i
=0; i
<=STYLE_MAX
; i
++) {
1275 for (unsigned i
=0; i
<monkbon_rows
; i
++) {
1288 if (GUIBTDefaults
) {
1289 free (GUIBTDefaults
);
1292 if (OtherGUIButtons
) {
1293 free (OtherGUIButtons
);
1298 #define COL_HATERACE 0 //ranger type racial enemy
1299 #define COL_CLERIC_SPELL 1 //cleric spells
1300 #define COL_MAGE_SPELL 2 //mage spells
1301 #define COL_STARTXP 3 //starting xp
1302 #define COL_BARD_SKILL 4 //bard skills
1303 #define COL_THIEF_SKILL 5 //thief skills
1306 #define COL_SPARKS 1
1307 #define COL_GRADIENT 2
1309 /* returns the ISCLASS for the class based on name */
1310 int IsClassFromName (const char* name
)
1312 //TODO: is there a better way of doing this?
1313 for (int i
=0; i
<ISCLASSES
; i
++) {
1314 if (strcmp(name
, isclassnames
[i
]) == 0)
1320 static void InitActorTables()
1324 //if (core->HasFeature(GF_IWD_DEATHVARFORMAT)) {
1325 // memcpy(DeathVarFormat, IWDDeathVarFormat, sizeof(ieVariable));
1328 if (core
->HasFeature(GF_CHALLENGERATING
)) {
1329 sharexp
=SX_DIVIDE
|SX_CR
;
1333 ReverseToHit
= core
->HasFeature(GF_REVERSE_TOHIT
);
1334 CheckAbilities
= core
->HasFeature(GF_CHECK_ABILITIES
);
1335 DeathOnZeroStat
= core
->HasFeature(GF_DEATH_ON_ZERO_STAT
);
1337 //this table lists various level based xp bonuses
1338 AutoTable
tm("xpbonus");
1340 xpbonustypes
= tm
->GetRowCount();
1341 if (xpbonustypes
== 0) {
1344 xpbonuslevels
= tm
->GetColumnCount(0);
1345 xpbonus
= (int *) calloc(xpbonuslevels
*xpbonustypes
, sizeof(int));
1346 for (i
= 0; i
<xpbonustypes
; i
++) {
1347 for(j
= 0; j
<xpbonuslevels
; j
++) {
1348 xpbonus
[i
*xpbonuslevels
+j
] = atoi(tm
->QueryField(i
,j
));
1356 //this table lists skill groups assigned to classes
1357 //it is theoretically possible to create hybrid classes
1358 tm
.load("clskills");
1360 classcount
= tm
->GetRowCount();
1361 memset (isclass
,0,sizeof(isclass
));
1362 clericspelltables
= (char **) calloc(classcount
, sizeof(char*));
1363 druidspelltables
= (char **) calloc(classcount
, sizeof(char*));
1364 wizardspelltables
= (char **) calloc(classcount
, sizeof(char*));
1365 turnlevels
= (int *) calloc(classcount
, sizeof(int));
1366 booktypes
= (int *) calloc(classcount
, sizeof(int));
1367 classabilities
= (char **) calloc(classcount
, sizeof(char*));
1369 ieDword bitmask
= 1;
1371 for(i
= 0; i
<classcount
; i
++) {
1373 int turnlevel
= atoi(tm
->QueryField( i
, 7));
1374 turnlevels
[i
]=turnlevel
;
1376 field
= tm
->QueryField( i
, 0 );
1377 if (field
[0]!='*') {
1378 isclass
[ISDRUID
] |= bitmask
;
1379 druidspelltables
[i
]=strdup(field
);
1381 field
= tm
->QueryField( i
, 1 );
1382 if (field
[0]!='*') {
1383 isclass
[ISCLERIC
] |= bitmask
;
1384 clericspelltables
[i
]=strdup(field
);
1387 field
= tm
->QueryField( i
, 2 );
1388 if (field
[0]!='*') {
1389 isclass
[ISMAGE
] |= bitmask
;
1390 wizardspelltables
[i
]=strdup(field
);
1393 // field 3 holds the starting xp
1395 field
= tm
->QueryField( i
, 4 );
1396 if (field
[0]!='*') {
1397 isclass
[ISBARD
] |= bitmask
;
1400 field
= tm
->QueryField( i
, 5 );
1401 if (field
[0]!='*') {
1402 isclass
[ISTHIEF
] |= bitmask
;
1405 field
= tm
->QueryField( i
, 6 );
1406 if (field
[0]!='*') {
1407 isclass
[ISPALADIN
] |= bitmask
;
1410 // field 7 holds the turn undead level
1412 field
= tm
->QueryField( i
, 8 );
1413 booktypes
[i
]=atoi(field
);
1414 //if booktype == 3 then it is a 'divine sorceror' class
1415 //we shouldn't hardcode iwd2 classes this heavily
1416 if (booktypes
[i
]==2) {
1417 isclass
[ISSORCERER
] |= bitmask
;
1420 field
= tm
->QueryField( i
, 9 );
1421 if (field
[0]!='*') {
1422 isclass
[ISRANGER
] |= bitmask
;
1425 field
= tm
->QueryField( i
, 10 );
1426 if (!strnicmp(field
, "CLABMO", 6)) {
1427 isclass
[ISMONK
] |= bitmask
;
1429 classabilities
[i
]=strdup(field
);
1433 classcount
= 0; //well
1436 i
= core
->GetMaximumAbility();
1437 maximum_values
[IE_STR
]=i
;
1438 maximum_values
[IE_INT
]=i
;
1439 maximum_values
[IE_DEX
]=i
;
1440 maximum_values
[IE_CON
]=i
;
1441 maximum_values
[IE_CHR
]=i
;
1442 maximum_values
[IE_WIS
]=i
;
1444 //all games except iwd2
1445 maximum_values
[IE_ARMORCLASS
]=20;
1448 maximum_values
[IE_ARMORCLASS
]=199;
1451 //initializing the vvc resource references
1454 for (i
=0;i
<DAMAGE_LEVELS
;i
++) {
1455 const char *tmp
= tm
->QueryField( i
, COL_MAIN
);
1456 strnlwrcpy(d_main
[i
], tmp
, 8);
1457 if (d_main
[i
][0]=='*') {
1460 tmp
= tm
->QueryField( i
, COL_SPARKS
);
1461 strnlwrcpy(d_splash
[i
], tmp
, 8);
1462 if (d_splash
[i
][0]=='*') {
1465 tmp
= tm
->QueryField( i
, COL_GRADIENT
);
1466 d_gradient
[i
]=atoi(tmp
);
1473 for (i
=0;i
<OVERLAY_COUNT
;i
++) {
1474 const char *tmp
= tm
->QueryField( i
, 0 );
1475 strnlwrcpy(hc_overlays
[i
], tmp
, 8);
1476 if (atoi(tm
->QueryField( i
, 1))) {
1483 //csound for bg1/bg2
1484 memset(csound
,0,sizeof(csound
));
1485 if (!core
->HasFeature(GF_SOUNDFOLDERS
)) {
1488 for(i
=0;i
<VCONST_COUNT
;i
++) {
1489 const char *tmp
= tm
->QueryField( i
, 0 );
1492 //I have no idea what this ! mean
1493 case '!': csound
[i
]=tmp
[1]; break;
1494 default: csound
[i
]=tmp
[0]; break;
1501 GUIBTDefaults
= (ActionButtonRow
*) calloc( classcount
,sizeof(ActionButtonRow
) );
1503 for (i
= 0; i
< classcount
; i
++) {
1504 memcpy(GUIBTDefaults
+i
, &DefaultButtons
, sizeof(ActionButtonRow
));
1506 for (int j
=0;j
<MAX_QSLOTS
;j
++) {
1507 GUIBTDefaults
[i
][j
+3]=(ieByte
) atoi( tm
->QueryField(i
,j
) );
1514 extraslots
= tm
->GetRowCount();
1515 OtherGUIButtons
= (ActionButtonRow2
*) calloc( extraslots
, sizeof (ActionButtonRow2
) );
1517 for (i
=0; i
<usecount
; i
++) {
1518 OtherGUIButtons
[i
].clss
= (ieByte
) atoi( tm
->QueryField(i
,0) );
1519 memcpy(OtherGUIButtons
[i
].buttons
, &DefaultButtons
, sizeof(ActionButtonRow
));
1520 for (int j
=0;j
<MAX_QSLOTS
;j
++) {
1521 OtherGUIButtons
[i
].buttons
[j
+3]=(ieByte
) atoi( tm
->QueryField(i
,j
+1) );
1528 usecount
= tm
->GetRowCount();
1529 itemuse
= new ItemUseType
[usecount
];
1530 for (i
= 0; i
< usecount
; i
++) {
1531 itemuse
[i
].stat
= (ieByte
) core
->TranslateStat( tm
->QueryField(i
,0) );
1532 strnlwrcpy(itemuse
[i
].table
, tm
->QueryField(i
,1),8 );
1533 itemuse
[i
].mcol
= (ieByte
) atoi( tm
->QueryField(i
,2) );
1534 itemuse
[i
].vcol
= (ieByte
) atoi( tm
->QueryField(i
,3) );
1535 itemuse
[i
].which
= (ieByte
) atoi( tm
->QueryField(i
,4) );
1536 //limiting it to 0 or 1 to avoid crashes
1537 if (itemuse
[i
].which
!=1) {
1543 tm
.load("itemanim");
1545 animcount
= tm
->GetRowCount();
1546 itemanim
= new ItemAnimType
[animcount
];
1547 for (i
= 0; i
< animcount
; i
++) {
1548 strnlwrcpy(itemanim
[i
].itemname
, tm
->QueryField(i
,0),8 );
1549 itemanim
[i
].animation
= (ieByte
) atoi( tm
->QueryField(i
,1) );
1553 tm
.load("mxsplwis");
1555 spllevels
= tm
->GetColumnCount(0);
1556 int max
= core
->GetMaximumAbility();
1557 mxsplwis
= (int *) calloc(max
*spllevels
, sizeof(int));
1558 for (i
= 0; i
< spllevels
; i
++) {
1559 for(int j
= 0; j
< max
; j
++) {
1560 int k
= atoi(tm
->GetRowName(j
))-1;
1561 if (k
>=0 && k
<max
) {
1562 mxsplwis
[k
*spllevels
+i
]=atoi(tm
->QueryField(j
,i
));
1572 for(i
=0;i
<MAX_FEATS
;i
++) {
1573 //we need the MULTIPLE column only
1574 //it stores the FEAT_* stat index, and could be taken multiple
1576 tmp
= core
->TranslateStat(tm
->QueryField(i
,0));
1577 if (tmp
>=MAX_STATS
) {
1578 printMessage("Actor","Invalid stat value in featreq.2da",YELLOW
);
1580 featstats
[i
] = (ieByte
) tmp
;
1584 //default all hp con bonuses to 9; this should be updated below
1586 maxhpconbon
= (int *) calloc(classcount
, sizeof(int));
1587 for (i
= 0; i
< classcount
; i
++) {
1591 if (tm
&& !core
->HasFeature(GF_LEVELSLOT_PER_CLASS
)) {
1593 //iwd2 just uses levelslotsiwd2 instead
1594 printf("Examining classes.2da\n");
1596 //when searching the levelslots, you must search for
1597 //levelslots[BaseStats[IE_CLASS]-1] as there is no class id of 0
1598 levelslots
= (int **) calloc(classcount
, sizeof(int*));
1599 dualswap
= (int *) calloc(classcount
, sizeof(int));
1601 for (i
=0; i
<classcount
; i
++) {
1602 //make sure we have a valid classid, then decrement
1603 //it to get the correct array index
1604 tmpindex
= atoi(tm
->QueryField(i
, 5));
1609 printf("\tID: %d ", tmpindex
);
1610 //only create the array if it isn't yet made
1611 //i.e. barbarians would overwrite fighters in bg2
1612 if (levelslots
[tmpindex
]) {
1613 printf ("Already Found!\n");
1617 const char* classname
= tm
->GetRowName(i
);
1618 printf("Name: %s ", classname
);
1620 //default all levelslots to 0
1621 levelslots
[tmpindex
] = (int *) calloc(ISCLASSES
, sizeof(int));
1623 //single classes only worry about IE_LEVEL
1624 ieDword tmpclass
= atoi(tm
->QueryField(i
, 4));
1626 classis
= IsClassFromName(classname
);
1628 printf("Classis: %d ", classis
);
1629 levelslots
[tmpindex
][classis
] = IE_LEVEL
;
1630 //get the max hp con bonus
1631 hptm
.load(tm
->QueryField(i
, 6));
1634 int rollscolumn
= hptm
->GetColumnIndex("ROLLS");
1635 while (atoi(hptm
->QueryField(tmphp
, rollscolumn
)))
1637 printf("TmpHP: %d ", tmphp
);
1638 if (tmphp
) maxhpconbon
[tmpindex
] = tmphp
;
1644 //we have to account for dual-swap in the multiclass field
1645 ieDword numfound
= 1;
1646 ieDword tmpbits
= bitcount (tmpclass
);
1648 //we need all the classnames of the multi to compare with the order we load them in
1649 //because the original game set the levels based on name order, not bit order
1650 char **classnames
= (char **) calloc(tmpbits
, sizeof(char *));
1651 classnames
[0] = (char*)strtok(strdup((char*)classname
), "_");
1652 while (numfound
<tmpbits
&& (classnames
[numfound
] = strdup(strtok(NULL
, "_")))) {
1656 bool foundwarrior
= false;
1657 for (int j
=0; j
<classcount
; j
++) {
1658 //no sense continuing if we've found all to be found
1659 if (numfound
==tmpbits
)
1661 if ((1<<j
)&tmpclass
) {
1662 //save the IE_LEVEL information
1663 const char* currentname
= tm
->GetRowName((ieDword
)(tm
->FindTableValue(5, j
+1)));
1664 classis
= IsClassFromName(currentname
);
1666 //search for the current class in the split of the names to get it's
1668 for (ieDword k
=0; k
<tmpbits
; k
++) {
1669 if (strcmp(classnames
[k
], currentname
) == 0) {
1671 if (k
==0) tmplevel
= IE_LEVEL
;
1672 else if (k
==1) tmplevel
= IE_LEVEL2
;
1673 else tmplevel
= IE_LEVEL3
;
1674 levelslots
[tmpindex
][classis
] = tmplevel
;
1677 printf("Classis: %d ", classis
);
1679 //warrior take presedence
1680 if (!foundwarrior
) {
1681 foundwarrior
= (classis
==ISFIGHTER
||classis
==ISRANGER
||classis
==ISPALADIN
||
1682 classis
==ISBARBARIAN
);
1683 hptm
.load(tm
->QueryField(currentname
, "HP"));
1686 int rollscolumn
= hptm
->GetColumnIndex("ROLLS");
1687 while (atoi(hptm
->QueryField(tmphp
, rollscolumn
)))
1689 //make sure we at least set the first class
1690 if ((tmphp
>maxhpconbon
[tmpindex
])||foundwarrior
||numfound
==0)
1691 maxhpconbon
[tmpindex
]=tmphp
;
1696 //save the MC_WAS_ID of the first class in the dual-class
1697 if (numfound
==0 && tmpbits
==2) {
1698 if (strcmp(classnames
[0], currentname
) == 0) {
1699 dualswap
[tmpindex
] = strtol(tm
->QueryField(classname
, "MC_WAS_ID"), NULL
, 0);
1701 } else if (numfound
==1 && tmpbits
==2 && !dualswap
[tmpindex
]) {
1702 dualswap
[tmpindex
] = strtol(tm
->QueryField(classname
, "MC_WAS_ID"), NULL
, 0);
1708 for (ieDword j
=0; j
<tmpbits
; j
++) {
1709 if (classnames
[j
]) {
1710 free(classnames
[j
]);
1716 printf("HPCON: %d ", maxhpconbon
[tmpindex
]);
1717 printf("DS: %d\n", dualswap
[tmpindex
]);
1719 /*this could be enabled to ensure all levelslots are filled with at least 0's;
1720 *however, the access code should ensure this never happens
1721 for (i=0; i<classcount; i++) {
1722 if (!levelslots[i]) {
1723 levelslots[i] = (int *) calloc(ISCLASSES, sizeof(int *));
1727 printf("Finished examining classes.2da\n");
1729 //pre-cache hit/damage/speed bonuses for weapons
1730 tm
.load("wspecial");
1732 //load in the identifiers
1733 wspecial_max
= tm
->GetRowCount()-1;
1734 int cols
= tm
->GetColumnCount();
1735 wspecial
= (int **) calloc(wspecial_max
+1, sizeof(int *));
1737 for (i
=0; i
<=wspecial_max
; i
++) {
1738 wspecial
[i
] = (int *) calloc(WSPECIAL_COLS
, sizeof(int));
1739 for (int j
=0; j
<cols
; j
++) {
1740 wspecial
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1745 //pre-cache attack per round bonuses
1748 wspattack_rows
= tm
->GetRowCount();
1749 wspattack_cols
= tm
->GetColumnCount();
1750 wspattack
= (int **) calloc(wspattack_rows
, sizeof(int *));
1753 for (i
=0; i
<wspattack_rows
; i
++) {
1754 wspattack
[i
] = (int *) calloc(wspattack_cols
, sizeof(int));
1755 for (int j
=0; j
<wspattack_cols
; j
++) {
1756 tmp
= atoi(tm
->QueryField(i
, j
));
1757 //negative values relate to x/2, so we adjust them
1758 //positive values relate to x, so we must times by 2
1759 if (tmp
<0) tmp
= -2*tmp
-1;
1761 wspattack
[i
][j
] = tmp
;
1766 //dual-wielding table
1767 tm
.load("wstwowpn");
1769 wsdualwield
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1770 int cols
= tm
->GetColumnCount();
1771 for (i
=0; i
<=STYLE_MAX
; i
++) {
1772 wsdualwield
[i
] = (int *) calloc(cols
, sizeof(int));
1773 for (int j
=0; j
<cols
; j
++) {
1774 wsdualwield
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1780 tm
.load("wstwohnd");
1782 wstwohanded
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1783 int cols
= tm
->GetColumnCount();
1784 for (i
=0; i
<=STYLE_MAX
; i
++) {
1785 wstwohanded
[i
] = (int *) calloc(cols
, sizeof(int));
1786 for (int j
=0; j
<cols
; j
++) {
1787 wstwohanded
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1793 tm
.load("wsshield");
1795 wsswordshield
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1796 int cols
= tm
->GetColumnCount();
1797 for (i
=0; i
<=STYLE_MAX
; i
++) {
1798 wsswordshield
[i
] = (int *) calloc(cols
, sizeof(int));
1799 for (int j
=0; j
<cols
; j
++) {
1800 wsswordshield
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1806 tm
.load("wssingle");
1808 wssingle
= (int **) calloc(STYLE_MAX
+1, sizeof(int *));
1809 int cols
= tm
->GetColumnCount();
1810 for (i
=0; i
<=STYLE_MAX
; i
++) {
1811 wssingle
[i
] = (int *) calloc(cols
, sizeof(int));
1812 for (int j
=0; j
<cols
; j
++) {
1813 wssingle
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1818 //unhardcoded monk bonus table
1821 monkbon_rows
= tm
->GetRowCount();
1822 monkbon_cols
= tm
->GetColumnCount();
1823 monkbon
= (int **) calloc(monkbon_rows
, sizeof(int *));
1824 for (unsigned i
=0; i
<monkbon_rows
; i
++) {
1825 monkbon
[i
] = (int *) calloc(monkbon_cols
, sizeof(int));
1826 for (unsigned j
=0; j
<monkbon_cols
; j
++) {
1827 monkbon
[i
][j
] = atoi(tm
->QueryField(i
, j
));
1832 //wild magic level modifiers
1834 wmlevels
[i
]=(int *) calloc(MAX_LEVEL
,sizeof(int) );
1836 tm
.load("lvlmodwm");
1838 int maxrow
= tm
->GetRowCount();
1839 for (i
=0;i
<20;i
++) {
1840 for(j
=0;j
<MAX_LEVEL
;j
++) {
1843 wmlevels
[i
][j
]=strtol(tm
->QueryField(row
,i
), NULL
, 0);
1848 // verbal constant remapping, if omitted, it is an 1-1 mapping
1849 // TODO: allow disabled VC slots
1850 for (i
=0;i
<VCONST_COUNT
;i
++) {
1855 int rows
= tm
->GetRowCount();
1857 for (i
=0;i
<rows
;i
++) {
1858 int row
= atoi(tm
->QueryField(i
,0));
1859 if (row
<0 || row
>=VCONST_COUNT
) continue;
1860 int value
= atoi(tm
->QueryField(i
,1));
1861 if (value
<0 || value
>=VCONST_COUNT
) continue;
1867 void Actor::SetLockedPalette(const ieDword
*gradients
)
1869 if (!anims
) return; //cannot apply it (yet)
1870 anims
->LockPalette(gradients
);
1873 void Actor::UnlockPalette()
1876 anims
->lockPalette
=false;
1877 anims
->SetColors(Modified
+IE_COLORS
);
1880 void Actor::AddAnimation(const ieResRef resource
, int gradient
, int height
, int flags
)
1882 ScriptedAnimation
*sca
= gamedata
->GetScriptedAnimation(resource
, false);
1886 if (flags
&AA_PLAYONCE
) {
1889 if (flags
&&AA_BLEND
) {
1890 //pst anims need this?
1894 sca
->SetPalette(gradient
, 4);
1899 void Actor::PlayDamageAnimation(int type
, bool hit
)
1903 printf("Damage animation type: %d\n", type
);
1906 case 0: case 1: case 2: case 3: //blood
1907 i
= (int) GetStat(IE_ANIMATION_ID
)>>16;
1908 if (!i
) i
= d_gradient
[type
];
1910 AddAnimation(d_main
[type
], i
, 0, AA_PLAYONCE
);
1913 case 4: case 5: case 6: //fire
1915 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1917 for(i
=DL_FIRE
;i
<=type
;i
++) {
1918 AddAnimation(d_splash
[i
], d_gradient
[i
], 0, AA_PLAYONCE
);
1921 case 7: case 8: case 9: //electricity
1923 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1925 for(i
=DL_ELECTRICITY
;i
<=type
;i
++) {
1926 AddAnimation(d_splash
[i
], d_gradient
[i
], 0, AA_PLAYONCE
);
1929 case 10: case 11: case 12://cold
1931 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1934 case 13: case 14: case 15://acid
1936 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1939 case 16: case 17: case 18://disintegrate
1941 AddAnimation(d_main
[type
], d_gradient
[type
], 0, AA_PLAYONCE
);
1947 bool Actor::SetStat(unsigned int StatIndex
, ieDword Value
, int pcf
)
1949 if (StatIndex
>= MAX_STATS
) {
1952 if ( (signed) Value
<-100) {
1953 Value
= (ieDword
) -100;
1956 if ( maximum_values
[StatIndex
]>0) {
1957 if ( (signed) Value
>maximum_values
[StatIndex
]) {
1958 Value
= (ieDword
) maximum_values
[StatIndex
];
1963 unsigned int previous
= Modified
[StatIndex
];
1964 if (Modified
[StatIndex
]!=Value
) {
1965 Modified
[StatIndex
] = Value
;
1967 PostChangeFunctionType f
= post_change_functions
[StatIndex
];
1968 if (f
) (*f
)(this, previous
, Value
);
1974 int Actor::GetMod(unsigned int StatIndex
)
1976 if (StatIndex
>= MAX_STATS
) {
1979 return (signed) Modified
[StatIndex
] - (signed) BaseStats
[StatIndex
];
1981 /** Returns a Stat Base Value */
1982 ieDword
Actor::GetBase(unsigned int StatIndex
)
1984 if (StatIndex
>= MAX_STATS
) {
1987 return BaseStats
[StatIndex
];
1990 /** Sets a Stat Base Value */
1991 /** If required, modify the modified value and run the pcf function */
1992 bool Actor::SetBase(unsigned int StatIndex
, ieDword Value
)
1994 if (StatIndex
>= MAX_STATS
) {
1997 ieDword diff
= Modified
[StatIndex
]-BaseStats
[StatIndex
];
1999 //maximize the base stat
2000 if ( maximum_values
[StatIndex
]) {
2001 if ( (signed) Value
>maximum_values
[StatIndex
]) {
2002 Value
= (ieDword
) maximum_values
[StatIndex
];
2006 BaseStats
[StatIndex
] = Value
;
2008 //if already initialized, then the modified stats
2009 //might need to run the post change function (stat change can kill actor)
2010 SetStat (StatIndex
, Value
+diff
, InternalFlags
&IF_INITIALIZED
);
2014 bool Actor::SetBaseNoPCF(unsigned int StatIndex
, ieDword Value
)
2016 if (StatIndex
>= MAX_STATS
) {
2019 ieDword diff
= Modified
[StatIndex
]-BaseStats
[StatIndex
];
2021 //maximize the base stat
2022 if ( maximum_values
[StatIndex
]) {
2023 if ( (signed) Value
>maximum_values
[StatIndex
]) {
2024 Value
= (ieDword
) maximum_values
[StatIndex
];
2028 BaseStats
[StatIndex
] = Value
;
2030 //if already initialized, then the modified stats
2031 //might need to run the post change function (stat change can kill actor)
2032 SetStat (StatIndex
, Value
+diff
, 0);
2036 bool Actor::SetBaseBit(unsigned int StatIndex
, ieDword Value
, bool setreset
)
2038 if (StatIndex
>= MAX_STATS
) {
2042 BaseStats
[StatIndex
] |= Value
;
2044 BaseStats
[StatIndex
] &= ~Value
;
2046 //if already initialized, then the modified stats
2047 //need to run the post change function (stat change can kill actor)
2049 SetStat (StatIndex
, Modified
[StatIndex
]|Value
, InternalFlags
&IF_INITIALIZED
);
2051 SetStat (StatIndex
, Modified
[StatIndex
]&~Value
, InternalFlags
&IF_INITIALIZED
);
2056 const unsigned char *Actor::GetStateString()
2061 ieByte
*tmp
= PCStats
->PortraitIconString
;
2062 ieWord
*Icons
= PCStats
->PortraitIcons
;
2064 for (int i
=0;i
<MAX_PORTRAIT_ICONS
;i
++) {
2065 if (!(Icons
[i
]&0xff00)) {
2066 tmp
[j
++]=(ieByte
) ((Icons
[i
]&0xff)+66);
2073 void Actor::AddPortraitIcon(ieByte icon
)
2078 ieWord
*Icons
= PCStats
->PortraitIcons
;
2080 for(int i
=0;i
<MAX_PORTRAIT_ICONS
;i
++) {
2081 if (Icons
[i
]==0xffff) {
2085 if (icon
== (Icons
[i
]&0xff)) {
2091 void Actor::DisablePortraitIcon(ieByte icon
)
2096 ieWord
*Icons
= PCStats
->PortraitIcons
;
2099 for(i
=0;i
<MAX_PORTRAIT_ICONS
;i
++) {
2100 if (icon
== (Icons
[i
]&0xff)) {
2101 Icons
[i
]=0xff00|icon
;
2107 /** call this after load, to apply effects */
2108 void Actor::RefreshEffects(EffectQueue
*fx
)
2110 ieDword previous
[MAX_STATS
];
2112 //put all special cleanup calls here
2113 CharAnimations
* anims
= GetAnims();
2115 anims
->GlobalColorMod
.type
= RGBModifier::NONE
;
2116 anims
->GlobalColorMod
.speed
= 0;
2117 unsigned int location
;
2118 for (location
= 0; location
< 32; ++location
) {
2119 anims
->ColorMods
[location
].type
= RGBModifier::NONE
;
2120 anims
->ColorMods
[location
].speed
= 0;
2123 spellbook
.ClearBonus();
2124 memset(applyWhenHittingMelee
,0,sizeof(ieResRef
));
2125 memset(applyWhenHittingRanged
,0,sizeof(ieResRef
));
2126 memset(applyWhenNearLiving
,0,sizeof(ieResRef
));
2127 memset(applyWhen50Damage
,0,sizeof(ieResRef
));
2128 memset(applyWhen90Damage
,0,sizeof(ieResRef
));
2129 memset(applyWhenEnemySighted
,0,sizeof(ieResRef
));
2130 memset(applyWhenPoisoned
,0,sizeof(ieResRef
));
2131 memset(applyWhenHelpless
,0,sizeof(ieResRef
));
2132 memset(applyWhenAttacked
,0,sizeof(ieResRef
));
2133 memset(applyWhenBeingHit
,0,sizeof(ieResRef
));
2134 memset(projectileImmunity
,0,ProjectileSize
*sizeof(ieDword
));
2136 //initialize base stats
2137 bool first
= !(InternalFlags
&IF_INITIALIZED
);
2140 InternalFlags
|=IF_INITIALIZED
;
2141 memcpy( previous
, BaseStats
, MAX_STATS
* sizeof( ieDword
) );
2143 memcpy( previous
, Modified
, MAX_STATS
* sizeof( ieDword
) );
2145 memcpy( Modified
, BaseStats
, MAX_STATS
* sizeof( ieDword
) );
2146 if (PCStats
) memset( PCStats
->PortraitIcons
, -1, sizeof(PCStats
->PortraitIcons
) );
2150 fx
->AddAllEffects(this, Pos
);
2152 //copy back the original stats, because the effects
2153 //will be reapplied in ApplyAllEffects again
2154 memcpy( Modified
, BaseStats
, MAX_STATS
* sizeof( ieDword
) );
2155 //also clear the spell bonuses just given, they will be
2156 //recalculated below again
2157 spellbook
.ClearBonus();
2162 // some VVCs are controlled by stats (and so by PCFs), the rest have 'effect_owned' set
2163 for (i
= 0; i
< vvcOverlays
.size(); i
++) {
2164 if (vvcOverlays
[i
] && vvcOverlays
[i
]->effect_owned
) vvcOverlays
[i
]->active
= false;
2166 for (i
= 0; i
< vvcShields
.size(); i
++) {
2167 if (vvcShields
[i
] && vvcShields
[i
]->effect_owned
) vvcShields
[i
]->active
= false;
2170 fxqueue
.ApplyAllEffects( this );
2172 // IE_CLASS is >classcount for non-PCs/NPCs
2173 if (BaseStats
[IE_CLASS
] <= (ieDword
)classcount
)
2176 for (i
=0;i
<MAX_STATS
;i
++) {
2177 if (first
|| Modified
[i
]!=previous
[i
]) {
2178 PostChangeFunctionType f
= post_change_functions
[i
];
2180 (*f
)(this, previous
[i
], Modified
[i
]);
2184 //add wisdom bonus spells
2185 if (!spellbook
.IsIWDSpellBook() && mxsplwis
) {
2186 int level
= Modified
[IE_WIS
];
2188 spellbook
.BonusSpells(IE_SPELL_TYPE_PRIEST
, spllevels
, mxsplwis
+spllevels
*level
);
2192 // check if any new portrait icon was removed or added
2194 if (memcmp(PCStats
->PreviousPortraitIcons
, PCStats
->PortraitIcons
, sizeof(PCStats
->PreviousPortraitIcons
))) {
2195 core
->SetEventFlag(EF_PORTRAIT
);
2196 memcpy( PCStats
->PreviousPortraitIcons
, PCStats
->PortraitIcons
, sizeof(PCStats
->PreviousPortraitIcons
) );
2201 // refresh stats on creatures (PC or NPC) with a valid class (not animals etc)
2202 // internal use only, and this is maybe a stupid name :)
2203 void Actor::RefreshPCStats() {
2204 //calculate hp bonus
2206 int bonlevel
= GetXPLevel(true);
2207 int oldlevel
, oldbonus
;
2208 oldlevel
= oldbonus
= 0;
2209 ieDword bonindex
= BaseStats
[IE_CLASS
]-1;
2211 //we must limit the levels to the max allowable
2212 if (bonlevel
>maxhpconbon
[bonindex
])
2213 bonlevel
= maxhpconbon
[bonindex
];
2215 if (IsDualInactive()) {
2216 //we apply the inactive hp bonus if it's better than the new hp bonus, so that we
2217 //never lose hp, only gain, on leveling
2218 oldlevel
= IsDualSwap() ? BaseStats
[IE_LEVEL
] : BaseStats
[IE_LEVEL2
];
2219 bonlevel
= IsDualSwap() ? BaseStats
[IE_LEVEL2
] : BaseStats
[IE_LEVEL
];
2220 oldlevel
= (oldlevel
> maxhpconbon
[bonindex
]) ? maxhpconbon
[bonindex
] : oldlevel
;
2221 if (Modified
[IE_MC_FLAGS
] & (MC_WAS_FIGHTER
|MC_WAS_RANGER
)) {
2222 oldbonus
= core
->GetConstitutionBonus(STAT_CON_HP_WARRIOR
, Modified
[IE_CON
]);
2224 oldbonus
= core
->GetConstitutionBonus(STAT_CON_HP_NORMAL
, Modified
[IE_CON
]);
2228 // warrior (fighter, barbarian, ranger, or paladin) or not
2229 // GetClassLevel now takes into consideration inactive dual-classes
2231 bonus
= core
->GetConstitutionBonus(STAT_CON_HP_WARRIOR
,Modified
[IE_CON
]);
2234 bonus
= core
->GetConstitutionBonus(STAT_CON_HP_NORMAL
,Modified
[IE_CON
]);
2237 oldbonus
*= oldlevel
;
2238 bonus
= (oldbonus
> bonus
) ? oldbonus
: bonus
;
2240 //morale recovery every xth AI cycle
2241 int mrec
= GetStat(IE_MORALERECOVERYTIME
);
2243 if (!(core
->GetGame()->GameTime
%mrec
)) {
2244 NewBase(IE_MORALE
,1,MOD_ADDITIVE
);
2248 if (bonus
<0 && (Modified
[IE_MAXHITPOINTS
]+bonus
)<=0) {
2249 bonus
=1-Modified
[IE_MAXHITPOINTS
];
2252 //get the wspattack bonuses for proficiencies
2254 ITMExtHeader
*header
= GetWeapon(wi
, false);
2256 int dualwielding
= IsDualWielding();
2257 if (header
&& (wi
.prof
<= MAX_STATS
)) {
2258 stars
= GetStat(wi
.prof
)&PROFS_MASK
;
2259 if (stars
>= (unsigned)wspattack_rows
) {
2260 stars
= wspattack_rows
-1;
2263 int tmplevel
= GetWarriorLevel();
2264 if (tmplevel
>= wspattack_cols
) {
2265 tmplevel
= wspattack_cols
-1;
2266 } else if (tmplevel
< 0) {
2270 //HACK: attacks per round bonus for monks should only apply to fists
2271 if (isclass
[ISMONK
]&(1<<BaseStats
[IE_CLASS
])) {
2272 unsigned int level
= GetMonkLevel()-1;
2273 if (level
< monkbon_cols
) {
2274 SetBase(IE_NUMBEROFATTACKS
, 2 + monkbon
[0][level
]);
2277 //wspattack appears to only effect warriors
2278 int defaultattacks
= 2 + 2*dualwielding
;
2280 SetBase(IE_NUMBEROFATTACKS
, defaultattacks
+wspattack
[stars
][tmplevel
]);
2282 SetBase(IE_NUMBEROFATTACKS
, defaultattacks
);
2287 //we still apply the maximum bonus to dead characters, but don't apply
2288 //to current HP, or we'd have dead characters showing as having hp
2289 //Modified[IE_MAXHITPOINTS]+=bonus;
2290 //if(BaseStats[IE_STATE_ID]&STATE_DEAD)
2292 // BaseStats[IE_HITPOINTS]+=bonus;
2294 // apply the intelligence and wisdom bonus to lore
2295 Modified
[IE_LORE
] += core
->GetLoreBonus(0, Modified
[IE_INT
]) + core
->GetLoreBonus(0, Modified
[IE_WIS
]);
2298 void Actor::RollSaves()
2300 if (InternalFlags
&IF_USEDSAVE
) {
2301 SavingThrow
[0]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2302 SavingThrow
[1]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2303 SavingThrow
[2]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2304 SavingThrow
[3]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2305 SavingThrow
[4]=(ieByte
) core
->Roll(1, SAVEROLL
, 0);
2306 InternalFlags
&=~IF_USEDSAVE
;
2311 //type bits in file order in stats
2318 //iwd2 (luckily they use the same bits as it would be with bg2):
2326 static int savingthrows
[SAVECOUNT
]={IE_SAVEVSSPELL
, IE_SAVEVSBREATH
, IE_SAVEVSDEATH
, IE_SAVEVSWANDS
, IE_SAVEVSPOLY
};
2328 /** returns true if actor made the save against saving throw type */
2329 bool Actor::GetSavingThrow(ieDword type
, int modifier
)
2331 assert(type
<SAVECOUNT
);
2332 InternalFlags
|=IF_USEDSAVE
;
2333 int ret
= SavingThrow
[type
];
2334 if (ret
== 1) return false;
2335 if (ret
== SAVEROLL
) return true;
2336 ret
+= modifier
+ GetStat(IE_LUCK
);
2337 return ret
> (int) GetStat(savingthrows
[type
]);
2340 /** implements a generic opcode function, modify modified stats
2343 int Actor::NewStat(unsigned int StatIndex
, ieDword ModifierValue
, ieDword ModifierType
)
2345 int oldmod
= Modified
[StatIndex
];
2347 switch (ModifierType
) {
2349 //flat point modifier
2350 SetStat(StatIndex
, Modified
[StatIndex
]+ModifierValue
, 0);
2353 //straight stat change
2354 SetStat(StatIndex
, ModifierValue
, 0);
2358 SetStat(StatIndex
, BaseStats
[StatIndex
] * ModifierValue
/ 100, 0);
2361 return Modified
[StatIndex
] - oldmod
;
2364 int Actor::NewBase(unsigned int StatIndex
, ieDword ModifierValue
, ieDword ModifierType
)
2366 int oldmod
= BaseStats
[StatIndex
];
2368 switch (ModifierType
) {
2370 //flat point modifier
2371 SetBase(StatIndex
, BaseStats
[StatIndex
]+ModifierValue
);
2374 //straight stat change
2375 SetBase(StatIndex
, ModifierValue
);
2379 SetBase(StatIndex
, BaseStats
[StatIndex
] * ModifierValue
/ 100);
2382 return BaseStats
[StatIndex
] - oldmod
;
2385 inline int CountElements(const char *s
, char separator
)
2389 if (*s
==separator
) ret
++;
2395 void Actor::Interact(int type
)
2401 case I_INSULT
: start
=VB_INSULT
; count
=3; break;
2402 case I_COMPLIMENT
: start
=VB_COMPLIMENT
; count
=3; break;
2403 case I_SPECIAL
: start
=VB_SPECIAL
; count
=3; break;
2407 VerbalConstant(start
, count
);
2410 ieStrRef
Actor::GetVerbalConstant(int index
) const
2412 if (index
<0 || index
>=VCONST_COUNT
) {
2413 return (ieStrRef
) -1;
2416 int idx
= VCMap
[index
];
2418 if (idx
<0 || idx
>=VCONST_COUNT
) {
2419 return (ieStrRef
) -1;
2421 return StrRefs
[idx
];
2424 void Actor::VerbalConstant(int start
, int count
)
2430 while(count
>=0 && (!(vc
= GetVerbalConstant(start
+count
)) || (vc
==(ieStrRef
) -1)) ) {
2434 DisplayStringCore(this, start
+count
, DS_CONSOLE
|DS_CONST
);
2438 void Actor::Response(int type
)
2445 case I_INSULT
: start
=VB_RESP_INS
; count
=3; break;
2446 case I_COMPLIMENT
: start
=VB_RESP_COMP
; count
=3; break;
2452 while(count
&& ((vc
= GetVerbalConstant(start
+count
))!=(ieStrRef
) -1)) {
2456 DisplayStringCore(this, start
+count
, DS_CONSOLE
|DS_CONST
);
2460 void Actor::ReactToDeath(const char * deadname
)
2462 AutoTable
tm("death");
2464 // lookup value based on died's scriptingname and ours
2465 // if value is 0 - use reactdeath
2466 // if value is 1 - use reactspecial
2467 // if value is string - use playsound instead (pst)
2468 const char *value
= tm
->QueryField (scriptName
, deadname
);
2471 DisplayStringCore(this, VB_REACT
, DS_CONSOLE
|DS_CONST
);
2474 DisplayStringCore(this, VB_REACT_S
, DS_CONSOLE
|DS_CONST
);
2478 int count
= CountElements(value
,',');
2479 if (count
<=0) break;
2480 count
= core
->Roll(1,count
,-1);
2483 while(*value
&& *value
!=',') value
++;
2484 if (*value
==',') value
++;
2486 strncpy(resref
, value
, 8);
2487 for(count
=0;count
<8 && resref
[count
]!=',';count
++) {};
2490 ieDword len
= core
->GetAudioDrv()->Play( resref
);
2491 ieDword counter
= ( AI_UPDATE_TIME
* len
) / 1000;
2499 //call this only from gui selects
2500 void Actor::SelectActor()
2502 DisplayStringCore(this, VB_SELECT
, DS_CONSOLE
|DS_CONST
);
2507 if (GetStat(IE_STATE_ID
)&STATE_PANIC
) {
2511 if (InParty
) core
->GetGame()->SelectActor(this, false, SELECT_NORMAL
);
2512 SetBaseBit(IE_STATE_ID
, STATE_PANIC
, true);
2513 DisplayStringCore(this, VB_PANIC
, DS_CONSOLE
|DS_CONST
);
2516 void Actor::SetMCFlag(ieDword arg
, int op
)
2518 ieDword tmp
= BaseStats
[IE_MC_FLAGS
];
2520 case BM_SET
: tmp
= arg
; break;
2521 case BM_OR
: tmp
|= arg
; break;
2522 case BM_NAND
: tmp
&= ~arg
; break;
2523 case BM_XOR
: tmp
^= arg
; break;
2524 case BM_AND
: tmp
&= arg
; break;
2526 SetBase(IE_MC_FLAGS
, tmp
);
2529 void Actor::DialogInterrupt()
2531 //if dialoginterrupt was set, no verbal constant
2532 if ( Modified
[IE_MC_FLAGS
]&MC_NO_TALK
)
2535 /* this part is unsure */
2536 if (Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
2537 DisplayStringCore(this, VB_HOSTILE
, DS_CONSOLE
|DS_CONST
);
2539 DisplayStringCore(this, VB_DIALOG
, DS_CONSOLE
|DS_CONST
);
2543 static EffectRef fx_cure_sleep_ref
={"Cure:Sleep",NULL
,-1};
2545 void Actor::GetHit()
2547 SetStance( IE_ANI_DAMAGE
);
2548 DisplayStringCore(this, VB_DAMAGE
, DS_CONSOLE
|DS_CONST
);
2549 if (Modified
[IE_STATE_ID
]&STATE_SLEEP
) {
2550 if (Modified
[IE_EXTSTATE_ID
]&EXTSTATE_NO_WAKEUP
) {
2553 Effect
*fx
= EffectQueue::CreateEffect(fx_cure_sleep_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
2554 fxqueue
.AddEffect(fx
);
2558 bool Actor::HandleCastingStance(const ieResRef SpellResRef
, bool deplete
)
2561 if (! spellbook
.HaveSpell( SpellResRef
, HS_DEPLETE
)) {
2562 SetStance(IE_ANI_READY
);
2566 SetStance(IE_ANI_CAST
);
2570 static EffectRef fx_sleep_ref
={"State:Helpless", NULL
, -1};
2572 //returns actual damage
2573 int Actor::Damage(int damage
, int damagetype
, Scriptable
*hitter
, int modtype
)
2575 //won't get any more hurt
2576 if (InternalFlags
& IF_REALLYDIED
) {
2580 //add lastdamagetype up ? maybe
2581 LastDamageType
|=damagetype
;
2582 if(hitter
&& hitter
->Type
==ST_ACTOR
) {
2583 LastHitter
=((Actor
*) hitter
)->GetID();
2585 //Maybe it should be something impossible like 0xffff, and use 'Someone'
2594 damage
= GetBase(IE_HITPOINTS
) - damage
;
2597 damage
= GetStat(IE_MAXHITPOINTS
) * 100 / damage
;
2600 //this shouldn't happen
2601 printMessage("Actor","Invalid damagetype!\n",RED
);
2606 ModifyDamage (this, hitter
, damage
, resisted
, damagetype
, NULL
, false);
2609 core
->SetEventFlag(EF_CLOSECONTAINER
);
2614 DisplayCombatFeedback(damage
, resisted
, damagetype
, hitter
);
2616 if (BaseStats
[IE_HITPOINTS
] <= (ieDword
) damage
) {
2617 // common fists do normal damage, but cause sleeping for a round instead of death
2618 if ((damagetype
& DAMAGE_STUNNING
) && Modified
[IE_MINHITPOINTS
] <= 0) {
2619 NewBase(IE_HITPOINTS
, 1, MOD_ABSOLUTE
);
2620 Effect
*fx
= EffectQueue::CreateEffect(fx_sleep_ref
, 0, 0, FX_DURATION_INSTANT_LIMITED
);
2621 fx
->Duration
= core
->Time
.round_sec
; // 1 round
2622 core
->ApplyEffect(fx
, this, this);
2625 NewBase(IE_HITPOINTS
, (ieDword
) -damage
, MOD_ADDITIVE
);
2628 NewBase(IE_HITPOINTS
, (ieDword
) -damage
, MOD_ADDITIVE
);
2630 // also apply reputation damage if we hurt (but not killed) an innocent
2631 if (Modified
[IE_CLASS
] == CLASS_INNOCENT
) {
2632 core
->GetGame()->SetReputation(core
->GetGame()->Reputation
+ core
->GetReputationMod(1));
2637 InternalFlags
|=IF_ACTIVE
;
2638 int chp
= (signed) BaseStats
[IE_HITPOINTS
];
2639 int damagelevel
= 0;
2643 NewBase(IE_MORALE
, (ieDword
) -1, MOD_ADDITIVE
);
2647 if (damagetype
& (DAMAGE_FIRE
|DAMAGE_MAGICFIRE
) ) {
2648 PlayDamageAnimation(DL_FIRE
+damagelevel
);
2649 } else if (damagetype
& (DAMAGE_COLD
|DAMAGE_MAGICCOLD
) ) {
2650 PlayDamageAnimation(DL_COLD
+damagelevel
);
2651 } else if (damagetype
& (DAMAGE_ELECTRICITY
) ) {
2652 PlayDamageAnimation(DL_ELECTRICITY
+damagelevel
);
2653 } else if (damagetype
& (DAMAGE_ACID
) ) {
2654 PlayDamageAnimation(DL_ACID
+damagelevel
);
2655 } else if (damagetype
& (DAMAGE_MAGIC
) ) {
2656 PlayDamageAnimation(DL_DISINTEGRATE
+damagelevel
);
2659 PlayDamageAnimation(DL_CRITICAL
);
2661 PlayDamageAnimation(DL_BLOOD
+damagelevel
);
2666 if (chp
<(signed) Modified
[IE_MAXHITPOINTS
]/10) {
2667 core
->Autopause(AP_WOUNDED
);
2670 core
->Autopause(AP_HIT
);
2671 core
->SetEventFlag(EF_PORTRAIT
);
2678 void Actor::DisplayCombatFeedback (unsigned int damage
, int resisted
, int damagetype
, Scriptable
*hitter
)
2680 bool detailed
= false;
2681 const char *type_name
= "unknown";
2682 if (displaymsg
->HasStringReference(STR_DMG_SLASHING
)) { // how and iwd2
2683 std::multimap
<ieDword
, DamageInfoStruct
>::iterator it
;
2684 it
= core
->DamageInfoMap
.find(damagetype
);
2685 if (it
!= core
->DamageInfoMap
.end()) {
2686 type_name
= core
->GetString(it
->second
.strref
, 0);
2691 if (damage
> 0 && resisted
!= DR_IMMUNE
) {
2692 printMessage("Actor", " ", GREEN
);
2693 printf("%d damage taken.\n", damage
);
2696 // 3 choices depending on resistance and boni
2697 // iwd2 also has two Tortoise Shell (spell) absorption strings
2698 core
->GetTokenDictionary()->SetAtCopy( "TYPE", type_name
);
2699 core
->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage
);
2700 if (hitter
&& hitter
->Type
== ST_ACTOR
) {
2701 core
->GetTokenDictionary()->SetAtCopy( "DAMAGER", hitter
->GetName(1) );
2703 core
->GetTokenDictionary()->SetAtCopy( "DAMAGER", "trap" );
2706 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage bonus)
2707 core
->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted
));
2708 displaymsg
->DisplayConstantStringName(STR_DAMAGE3
, 0xffffff, this);
2709 } else if (resisted
> 0) {
2710 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage resisted)
2711 core
->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted
));
2712 displaymsg
->DisplayConstantStringName(STR_DAMAGE2
, 0xffffff, this);
2714 //Takes <AMOUNT> <TYPE> damage from <DAMAGER>
2715 displaymsg
->DisplayConstantStringName(STR_DAMAGE1
, 0xffffff, this);
2717 } else if (stricmp( core
->GameType
, "pst" ) == 0) {
2718 if(0) printf("TODO: pst floating text\n");
2719 } else if (displaymsg
->HasStringReference(STR_DAMAGE1
) || !hitter
|| hitter
->Type
!= ST_ACTOR
) {
2721 // or traps: "Damage Taken (damage)", but there's no token
2723 snprintf(tmp
, sizeof(tmp
), "Damage Taken (%d)", damage
);
2724 displaymsg
->DisplayStringName(tmp
, 0xffffff, this);
2726 //<DAMAGER> did <AMOUNT> damage to <DAMAGEE>
2727 core
->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2728 // wipe the DAMAGER token, so we can color it
2729 core
->GetTokenDictionary()->SetAtCopy( "DAMAGER", "" );
2730 core
->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage
);
2731 displaymsg
->DisplayConstantStringName(STR_DAMAGE1
, 0xffffff, hitter
);
2734 if (resisted
== DR_IMMUNE
) {
2735 printMessage("Actor", " ", GREEN
);
2736 printf("is immune to damage type: %s.\n", type_name
);
2737 if (hitter
&& hitter
->Type
== ST_ACTOR
) {
2739 //<DAMAGEE> was immune to my <TYPE> damage
2740 core
->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2741 core
->GetTokenDictionary()->SetAtCopy( "TYPE", type_name
);
2742 displaymsg
->DisplayConstantStringName(STR_DAMAGE_IMMUNITY
, 0xffffff, hitter
);
2743 } else if (displaymsg
->HasStringReference(STR_DAMAGE_IMMUNITY
) && displaymsg
->HasStringReference(STR_DAMAGE1
)) {
2745 //<DAMAGEE> was immune to my damage.
2746 core
->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2747 displaymsg
->DisplayConstantStringName(STR_DAMAGE_IMMUNITY
, 0xffffff, hitter
);
2748 } // else: other games don't display anything
2751 // mirror image or stoneskin: no message
2756 DataFileMgr
*resdata
= core
->GetResDataINI();
2758 PlayHitSound(resdata
, damagetype
, false);
2762 //Play PST specific hit sounds (HIT_0<dtype><armor>)
2763 void Actor::PlayHitSound(DataFileMgr
*resdata
, int damagetype
, bool suffix
)
2767 switch(damagetype
) {
2768 case DAMAGE_SLASHING
: type
= 1; break; //slashing
2769 case DAMAGE_PIERCING
: type
= 2; break; //piercing
2770 case DAMAGE_CRUSHING
: type
= 3; break; //crushing
2771 case DAMAGE_MISSILE
: type
= 4; break; //missile
2772 default: return; //other
2777 unsigned int animid
=BaseStats
[IE_ANIMATION_ID
];
2778 if(core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
2782 snprintf(section
,10,"%d", animid
);
2784 int armor
= resdata
->GetKeyAsInt(section
, "armor",0);
2785 if (armor
<0 || armor
>35) return;
2787 snprintf(Sound
,8,"HIT_0%d%c%c",type
, armor
+'A', suffix
?'1':0);
2789 core
->GetAudioDrv()->Play( Sound
,Pos
.x
,Pos
.y
,0 );
2792 //Just to quickly inspect debug maximum values
2794 void Actor::DumpMaxValues()
2796 int symbol
= core
->LoadSymbol( "stats" );
2797 SymbolMgr
*sym
= core
->GetSymbol( symbol
);
2799 for(int i
=0;i
<MAX_STATS
;i
++) {
2800 printf("%d (%s) %d\n", i
, sym
->GetValue(i
), maximum_values
[i
]);
2805 void Actor::DebugDump()
2809 printf( "Debugdump of Actor %s (%s, %s):\n", LongName
, ShortName
, GetName(-1) );
2810 printf ("Scripts:");
2811 for (i
= 0; i
< MAX_SCRIPTS
; i
++) {
2812 const char* poi
= "<none>";
2814 poi
= Scripts
[i
]->GetName();
2816 printf( " %.8s", poi
);
2818 printf( "\nArea: %.8s ", Area
);
2819 printf( "Dialog: %.8s\n", Dialog
);
2820 printf( "Global ID: %d Local ID: %d\n", globalID
, localID
);
2821 printf( "Script name:%.32s\n", scriptName
);
2822 printf( "TalkCount: %d ", TalkCount
);
2823 printf( "PartySlot: %d\n", InParty
);
2824 printf( "Allegiance: %d current allegiance:%d\n", BaseStats
[IE_EA
], Modified
[IE_EA
] );
2825 printf( "Class: %d current class:%d\n", BaseStats
[IE_CLASS
], Modified
[IE_CLASS
] );
2826 printf( "Race: %d current race:%d\n", BaseStats
[IE_RACE
], Modified
[IE_RACE
] );
2827 printf( "Gender: %d current gender:%d\n", BaseStats
[IE_SEX
], Modified
[IE_SEX
] );
2828 printf( "Specifics: %d current specifics:%d\n", BaseStats
[IE_SPECIFIC
], Modified
[IE_SPECIFIC
] );
2829 printf( "Alignment: %x current alignment:%x\n", BaseStats
[IE_ALIGNMENT
], Modified
[IE_ALIGNMENT
] );
2830 printf( "Morale: %d current morale:%d\n", BaseStats
[IE_MORALE
], Modified
[IE_MORALE
] );
2831 printf( "Moralebreak:%d Morale recovery:%d\n", Modified
[IE_MORALEBREAK
], Modified
[IE_MORALERECOVERYTIME
] );
2832 printf( "Visualrange:%d (Explorer: %d)\n", Modified
[IE_VISUALRANGE
], Modified
[IE_EXPLORE
] );
2833 printf( "current HP:%d\n", BaseStats
[IE_HITPOINTS
] );
2834 printf( "Mod[IE_ANIMATION_ID]: 0x%04X\n", Modified
[IE_ANIMATION_ID
] );
2835 printf( "Colors: ");
2836 if (core
->HasFeature(GF_ONE_BYTE_ANIMID
) ) {
2837 for(i
=0;i
<Modified
[IE_COLORCOUNT
];i
++) {
2838 printf(" %d", Modified
[IE_COLORS
+i
]);
2843 printf(" %d", Modified
[IE_COLORS
+i
]);
2846 printf( "\nWaitCounter: %d\n", (int) GetWait());
2847 printf( "LastTarget: %d %s\n", LastTarget
, GetActorNameByID(LastTarget
));
2848 printf( "LastTalked: %d %s\n", LastTalkedTo
, GetActorNameByID(LastTalkedTo
));
2857 const char* Actor::GetActorNameByID(ieDword ID
) const
2859 Actor
*actor
= GetCurrentArea()->GetActorByGlobalID(ID
);
2863 return actor
->GetScriptName();
2866 void Actor::SetMap(Map
*map
, ieWord LID
, ieWord GID
)
2868 //Did we have an area?
2869 bool effinit
=!GetCurrentArea();
2870 //now we have an area
2871 Scriptable::SetMap(map
);
2872 //unless we just lost it, in that case clear up some fields and leave
2874 //the local ID is surely illegal after the map is nulled
2876 //more bits may or may not be needed
2877 InternalFlags
&=~IF_CLEANUP
;
2884 //These functions are called once when the actor is first put in
2885 //the area. It already has all the items (including fist) at this
2886 //time and it is safe to call effects.
2887 //This hack is to delay the equipping effects until the actor has
2888 //an area (and the game object is also existing)
2890 int SlotCount
= inventory
.GetSlotCount();
2891 for (int Slot
= 0; Slot
<SlotCount
;Slot
++) {
2892 int slottype
= core
->QuerySlotEffects( Slot
);
2894 case SLOT_EFFECT_NONE
:
2895 case SLOT_EFFECT_MELEE
:
2898 inventory
.EquipItem( Slot
);
2902 //We need to convert this to signed 16 bits, because
2903 //it is actually a 16 bit number.
2904 //It is signed to have the correct math
2905 //when adding it to the base slot (SLOT_WEAPON) in
2906 //case of quivers. (weird IE magic)
2907 //The other word is the equipped header.
2908 //find a quiver for the bow, etc
2909 if (Equipped
!=IW_NO_EQUIPPED
) {
2910 inventory
.EquipItem( Equipped
+inventory
.GetWeaponSlot());
2911 SetEquippedQuickSlot( inventory
.GetEquipped(), EquippedHeader
);
2916 void Actor::SetPosition(const Point
&position
, int jump
, int radius
)
2921 p
.x
= position
.x
/16;
2922 p
.y
= position
.y
/12;
2923 if (jump
&& !(Modified
[IE_DONOTJUMP
] & DNJ_FIT
) && size
) {
2924 GetCurrentArea()->AdjustPosition( p
, radius
);
2931 /* this is returning the level of the character for xp calculations
2932 and the average level for dual/multiclass (rounded up),
2933 also with iwd2's 3rd ed rules, this is why it is a separate function */
2934 ieDword
Actor::GetXPLevel(int modified
) const
2936 const ieDword
*stats
;
2945 float classcount
= 0;
2947 if (core
->HasFeature(GF_LEVELSLOT_PER_CLASS
)) {
2949 for (int i
=0; i
< 11; i
++) {
2950 if (stats
[levelslotsiwd2
[i
]] > 0) classcount
++;
2952 average
= stats
[IE_CLASSLEVELSUM
] / classcount
+ 0.5;
2955 int levels
[3]={stats
[IE_LEVEL
], stats
[IE_LEVEL2
], stats
[IE_LEVEL3
]};
2956 average
= levels
[0];
2958 if (IsDualClassed()) {
2960 if (levels
[1] > 0) {
2962 average
+= levels
[1];
2965 else if (IsMultiClassed()) {
2966 //classcount is the number of on bits in the MULTI field
2967 classcount
= bitcount (multiclass
);
2968 for (int i
=1; i
<classcount
; i
++)
2969 average
+= levels
[i
];
2970 } //else single classed
2971 average
= average
/ classcount
+ 0.5;
2973 return ieDword(average
);
2976 int Actor::GetWildMod(int level
) const
2978 if(GetStat(IE_KIT
)&0x8000) {
2979 if (level
>=MAX_LEVEL
) level
=MAX_LEVEL
;
2980 if(level
<1) level
=1;
2981 return wmlevels
[core
->Roll(1,20,-1)][level
-1];
2986 int Actor::CastingLevelBonus(int level
, int type
) const
2992 bonus
= GetStat(IE_CASTINGLEVELBONUSCLERIC
);
2995 bonus
= GetWildMod(level
) + GetStat(IE_CASTINGLEVELBONUSMAGE
);
3002 core
->GetTokenDictionary()->SetAtCopy("LEVELDIF", bonus
);
3005 displaymsg
->DisplayConstantStringName(STR_CASTER_LVL_INC
, 0xffffff, this);
3007 displaymsg
->DisplayConstantStringName(STR_CASTER_LVL_DEC
, 0xffffff, this);
3013 /** maybe this would be more useful if we calculate with the strength too
3015 int Actor::GetEncumbrance()
3017 return inventory
.GetWeight();
3020 EffectRef control_undead_ref
= { "ControlUndead2", NULL
, -1};
3023 void Actor::Turn(Scriptable
*cleric
, ieDword turnlevel
)
3025 //this is safely hardcoded i guess
3026 if (Modified
[IE_GENERAL
]!=GEN_UNDEAD
) {
3034 //determine if we see the cleric (distance)
3035 if (!CanSee(cleric
, this, true, GA_NO_DEAD
)) {
3039 //determine alignment (if equals, then no turning)
3041 LastTurner
= cleric
->GetGlobalID();
3043 //determine panic or destruction/control
3044 //we get the modified level
3045 if (turnlevel
- TURN_DEATH_LVL_MOD
>= GetXPLevel(true)) {
3046 if (cleric
->Type
== ST_ACTOR
&& ((Actor
*)cleric
)->MatchesAlignmentMask(AL_EVIL
)) {
3047 Effect
*fx
= fxqueue
.CreateEffect(control_undead_ref
, GEN_UNDEAD
, 3, FX_DURATION_INSTANT_LIMITED
);
3048 fx
->Duration
= core
->Time
.round_sec
;
3049 fx
->Target
= FX_TARGET_PRESET
;
3050 core
->ApplyEffect(fx
, this, cleric
);
3055 } else if (turnlevel
- TURN_PANIC_LVL_MOD
>= GetXPLevel(true)) {
3060 //TODO: needs a way to respawn at a point
3061 void Actor::Resurrect()
3063 if (!(Modified
[IE_STATE_ID
] & STATE_DEAD
)) {
3066 InternalFlags
&=IF_FROMGAME
; //keep these flags (what about IF_INITIALIZED)
3067 InternalFlags
|=IF_ACTIVE
|IF_VISIBLE
; //set these flags
3068 SetBase(IE_STATE_ID
, 0);
3069 SetBase(IE_MORALE
, 10);
3070 SetBase(IE_HITPOINTS
, BaseStats
[IE_MAXHITPOINTS
]);
3073 SetStance(IE_ANI_EMERGE
);
3074 //readjust death variable on resurrection
3075 if (core
->HasFeature(GF_HAS_KAPUTZ
) && (AppearanceFlags
&APP_DEATHVAR
)) {
3076 ieVariable DeathVar
;
3078 snprintf(DeathVar
,sizeof(ieVariable
),"%s_DEAD",scriptName
);
3079 Game
*game
=core
->GetGame();
3082 game
->kaputz
->Lookup(DeathVar
, value
);
3084 game
->kaputz
->SetAt(DeathVar
, value
-1);
3090 static EffectRef fx_cure_poisoned_state_ref
={"Cure:Poison",NULL
,-1};
3091 static EffectRef fx_cure_hold_state_ref
={"Cure:Hold",NULL
,-1};
3092 static EffectRef fx_cure_stun_state_ref
={"Cure:Stun",NULL
,-1};
3093 static EffectRef fx_remove_portrait_icon_ref
={"Icon:Remove",NULL
,-1};
3094 static EffectRef fx_unpause_caster_ref
={"Cure:CasterHold",NULL
,-1};
3096 void Actor::Die(Scriptable
*killer
)
3100 if (InternalFlags
&IF_REALLYDIED
) {
3101 return; //can die only once
3104 int minhp
= (signed) Modified
[IE_MINHITPOINTS
];
3105 if (minhp
> 0) { //can't die
3106 SetBase(IE_HITPOINTS
, minhp
);
3110 //Can't simply set Selected to false, game has its own little list
3111 Game
*game
= core
->GetGame();
3112 game
->SelectActor(this, false, SELECT_NORMAL
);
3113 game
->OutAttack(GetID());
3115 displaymsg
->DisplayConstantStringName(STR_DEATH
, 0xffffff, this);
3116 DisplayStringCore(this, VB_DIE
, DS_CONSOLE
|DS_CONST
);
3118 // remove poison, hold, casterhold, stun and its icon
3120 newfx
= EffectQueue::CreateEffect(fx_cure_poisoned_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
3121 core
->ApplyEffect(newfx
, this, this);
3123 newfx
= EffectQueue::CreateEffect(fx_cure_hold_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
3124 core
->ApplyEffect(newfx
, this, this);
3126 newfx
= EffectQueue::CreateEffect(fx_unpause_caster_ref
, 0, 100, FX_DURATION_INSTANT_PERMANENT
);
3127 core
->ApplyEffect(newfx
, this, this);
3129 newfx
= EffectQueue::CreateEffect(fx_cure_stun_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
3130 core
->ApplyEffect(newfx
, this, this);
3132 newfx
= EffectQueue::CreateEffect(fx_remove_portrait_icon_ref
, 0, 37, FX_DURATION_INSTANT_PERMANENT
);
3133 core
->ApplyEffect(newfx
, this, this);
3136 // clearing the search map here means it's not blocked during death animations
3137 // this is perhaps not ideal, but matches other searchmap code which uses
3138 // GA_NO_DEAD to exclude IF_JUSTDIED actors as well as dead ones
3140 area
->ClearSearchMapFor(this);
3142 //JUSTDIED will be removed when the Die() trigger executed
3143 //otherwise it is the same as REALLYDIED
3144 InternalFlags
|=IF_REALLYDIED
|IF_JUSTDIED
;
3145 SetStance( IE_ANI_DIE
);
3148 game
->PartyMemberDied(this);
3149 core
->Autopause(AP_DEAD
);
3153 killer
= area
->GetActorByGlobalID(LastHitter
);
3157 if (killer
->Type
==ST_ACTOR
) {
3158 act
= (Actor
*) killer
;
3164 //adjust kill statistics here
3165 PCStatsStruct
*stat
= act
->PCStats
;
3167 stat
->NotifyKill(Modified
[IE_XPVALUE
], ShortStrRef
);
3169 InternalFlags
|=IF_GIVEXP
;
3172 // friendly party summons' kills also grant xp
3173 if (act
->Modified
[IE_SEX
] == SEX_SUMMON
&& act
->Modified
[IE_EA
] == EA_CONTROLLED
) {
3174 InternalFlags
|=IF_GIVEXP
;
3175 } else if (act
->Modified
[IE_EA
] == EA_FAMILIAR
) {
3176 // familiar's kills also grant xp
3177 InternalFlags
|=IF_GIVEXP
;
3182 // XP seems to be handed at out at the moment of death
3183 if (InternalFlags
&IF_GIVEXP
) {
3184 //give experience to party
3185 game
->ShareXP(Modified
[IE_XPVALUE
], sharexp
);
3188 // adjust reputation if the corpse was:
3189 // an innocent, a member of the Flaming Fist or something evil
3191 if (Modified
[IE_CLASS
] == CLASS_INNOCENT
) {
3192 repmod
= core
->GetReputationMod(0);
3193 } else if (Modified
[IE_CLASS
] == CLASS_FLAMINGFIST
) {
3194 repmod
= core
->GetReputationMod(3);
3196 if (MatchesAlignmentMask(AL_EVIL
)) {
3197 repmod
+= core
->GetReputationMod(7);
3200 game
->SetReputation(game
->Reputation
+ repmod
);
3205 //moved clear actions after xp is given
3206 //it clears some triggers, including the LastHitter
3209 SetModal( MS_NONE
);
3214 // death variables are updated at the moment of death
3216 if (core
->HasFeature(GF_HAS_KAPUTZ
) ) {
3217 game
->kaputz
->Lookup(KillVar
, value
);
3218 game
->kaputz
->SetAt(KillVar
, value
+1);
3220 // iwd/iwd2 path *sets* this var, so i changed it, not sure about pst above
3221 game
->locals
->SetAt(KillVar
, 1);
3224 if (IncKillVar
[0]) {
3226 game
->locals
->Lookup(IncKillVar
, value
);
3227 game
->locals
->SetAt(IncKillVar
, value
+ 1);
3230 if (scriptName
[0]) {
3232 if (core
->HasFeature(GF_HAS_KAPUTZ
) ) {
3233 if (AppearanceFlags
&APP_DEATHVAR
) {
3234 snprintf(varname
, 32, "%s_DEAD", scriptName
);
3235 game
->kaputz
->Lookup(varname
, value
);
3236 game
->kaputz
->SetAt(varname
, value
+1);
3238 if (AppearanceFlags
&APP_DEATHTYPE
) {
3239 snprintf(varname
, 32, "KILL_%s", KillVar
);
3240 game
->kaputz
->Lookup(varname
, value
);
3241 game
->kaputz
->SetAt(varname
, value
+1);
3244 snprintf(varname
, 32, core
->GetDeathVarFormat(), scriptName
);
3245 game
->locals
->Lookup(varname
, value
);
3246 game
->locals
->SetAt(varname
, value
+1);
3251 snprintf(varname
, 32, "%s_DEAD", scriptName
);
3252 game
->locals
->Lookup(varname
, value
);
3253 game
->locals
->SetAt(varname
, 1);
3255 snprintf(varname
, 32, "%s_KILL_CNT", scriptName
);
3257 game
->locals
->Lookup(varname
, value
);
3258 game
->locals
->SetAt(varname
, value
+ 1);
3264 // racial dead count
3266 int racetable
= core
->LoadSymbol("race");
3267 if (racetable
!= -1) {
3268 Holder
<SymbolMgr
> race
= core
->GetSymbol(racetable
);
3269 const char *raceName
= race
->GetValue(Modified
[IE_RACE
]);
3271 // todo: should probably not set this for humans in iwd?
3272 snprintf(varname
, 32, "KILL_%s_CNT", raceName
);
3273 game
->locals
->Lookup(varname
, value
);
3274 game
->locals
->SetAt(varname
, value
+1);
3279 //death counters for PST
3282 if (AppearanceFlags
&j
) {
3284 game
->locals
->Lookup(CounterNames
[i
], value
);
3285 game
->locals
->SetAt(CounterNames
[i
], value
+DeathCounters
[i
]);
3290 // EXTRACOUNT is updated at the moment of death
3291 if (Modified
[IE_SEX
] == SEX_EXTRA
|| (Modified
[IE_SEX
] >= SEX_EXTRA2
&& Modified
[IE_SEX
] <= SEX_MAXEXTRA
)) {
3292 // if gender is set to one of the EXTRA values, then at death, we have to decrease
3293 // the relevant EXTRACOUNT area variable. scripts use this to check how many actors
3294 // of this extra id are still alive (for example, see the ToB challenge scripts)
3296 if (Modified
[IE_SEX
] == SEX_EXTRA
) {
3297 snprintf(varname
, 32, "EXTRACOUNT");
3299 snprintf(varname
, 32, "EXTRACOUNT%d", 2 + (Modified
[IE_SEX
] - SEX_EXTRA2
));
3302 Map
*area
= GetCurrentArea();
3305 area
->locals
->Lookup(varname
, value
);
3306 // i am guessing that we shouldn't decrease below 0
3308 area
->locals
->SetAt(varname
, value
-1);
3313 //a plot critical creature has died (iwd2)
3314 if (BaseStats
[IE_MC_FLAGS
]&MC_PLOT_CRITICAL
) {
3315 core
->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindowPlot", false);
3317 //ensure that the scripts of the actor will run as soon as possible
3321 void Actor::SetPersistent(int partyslot
)
3323 InParty
= (ieByte
) partyslot
;
3324 InternalFlags
|=IF_FROMGAME
;
3325 //if an actor is coming from a game, it should have these too
3329 void Actor::DestroySelf()
3331 InternalFlags
|=IF_CLEANUP
;
3332 // clear search map so that a new actor can immediately go there
3333 // (via ChangeAnimationCore)
3335 area
->ClearSearchMapFor(this);
3338 bool Actor::CheckOnDeath()
3340 if (InternalFlags
&IF_CLEANUP
) {
3343 if (InternalFlags
&IF_JUSTDIED
) {
3344 if (lastRunTime
== 0 || CurrentAction
|| GetNextAction()) {
3345 return false; //actor is currently dying, let him die first
3348 if (!(InternalFlags
&IF_REALLYDIED
) ) {
3351 //don't mess with the already deceased
3352 if (BaseStats
[IE_STATE_ID
]&STATE_DEAD
) {
3355 // don't destroy actors currently in a dialog
3356 GameControl
*gc
= core
->GetGameControl();
3357 if (gc
&& (globalID
== gc
->dialoghandler
->targetID
|| globalID
== gc
->dialoghandler
->speakerID
)) {
3361 //we need to check animID here, if it has not played the death
3362 //sequence yet, then we could return now
3364 //missed the opportunity of Died()
3365 InternalFlags
&=~IF_JUSTDIED
;
3367 // items seem to be dropped at the moment of death
3368 // .. but this can't go in Die() because that is called
3369 // from effects and dropping items might change effects!
3372 //remove all effects that are not 'permanent after death' here
3373 //permanent after death type is 9
3374 SetBaseBit(IE_STATE_ID
, STATE_DEAD
, true);
3376 // party actors are never removed
3377 if (Persistent()) return false;
3379 if (Modified
[IE_MC_FLAGS
]&MC_REMOVE_CORPSE
) return true;
3380 if (Modified
[IE_MC_FLAGS
]&MC_KEEP_CORPSE
) return false;
3381 //if chunked death, then return true
3382 if (LastDamageType
&DAMAGE_CHUNKING
) {
3383 //play chunky animation
3384 //chunks are projectiles
3390 /* this will create a heap at location, and transfer the item(s) */
3391 void Actor::DropItem(const ieResRef resref
, unsigned int flags
)
3393 if (inventory
.DropItemAtLocation( resref
, flags
, area
, Pos
)) {
3398 void Actor::DropItem(int slot
, unsigned int flags
)
3400 if (inventory
.DropItemAtLocation( slot
, flags
, area
, Pos
)) {
3405 /** returns quick item data */
3406 /** if header==-1 which is a 'use quickitem' action */
3407 /** if header is set, then which is the absolute slot index, */
3408 /** and header is the header index */
3409 void Actor::GetItemSlotInfo(ItemExtHeader
*item
, int which
, int header
)
3414 memset(item
, 0, sizeof(ItemExtHeader
) );
3416 if (!PCStats
) return; //not a player character
3417 PCStats
->GetSlotAndIndex(which
,idx
,headerindex
);
3418 if (headerindex
==0xffff) return; //headerindex is invalid
3421 headerindex
=(ieWord
) header
;
3423 const CREItem
*slot
= inventory
.GetSlotItem(idx
);
3424 if (!slot
) return; //quick item slot is empty
3425 Item
*itm
= gamedata
->GetItem(slot
->ItemResRef
);
3426 if (!itm
) return; //quick item slot contains invalid item resref
3427 ITMExtHeader
*ext_header
= itm
->GetExtHeader(headerindex
);
3428 //item has no extended header, or header index is wrong
3429 if (!ext_header
) return;
3430 memcpy(item
->itemname
, slot
->ItemResRef
, sizeof(ieResRef
) );
3432 item
->headerindex
= headerindex
;
3433 memcpy(&(item
->AttackType
), &(ext_header
->AttackType
),
3434 ((char *) &(item
->itemname
)) -((char *) &(item
->AttackType
)) );
3435 if (headerindex
>=CHARGE_COUNTERS
) {
3438 item
->Charges
=slot
->Usages
[headerindex
];
3440 gamedata
->FreeItem(itm
,slot
->ItemResRef
, false);
3443 void Actor::ReinitQuickSlots()
3449 // Note: (wjp, 20061226)
3450 // This function needs some rethinking.
3451 // It tries to satisfy two things at the moment:
3452 // Fill quickslots when they are empty and an item is placed in the
3453 // inventory slot corresponding to the quickslot
3454 // Reset quickslots when an item is removed
3455 // Currently, it resets all slots when items are removed,
3456 // but it only refills the ACT_QSLOTn slots, not the ACT_WEAPONx slots.
3458 // Refilling a weapon slot is possible, but essentially duplicates a lot
3459 // of code from Inventory::EquipItem() which performs the same steps for
3460 // the Inventory::Equipped slot.
3461 // Hopefully, weapons/arrows are never added to inventory slots without
3462 // EquipItem being called.
3464 int i
=sizeof(PCStats
->QSlots
);
3468 if (i
<0) which
= ACT_WEAPON4
+i
+1;
3469 else which
= PCStats
->QSlots
[i
];
3475 CheckWeaponQuickSlot(which
-ACT_WEAPON1
);
3478 //WARNING:this cannot be condensed, because the symbols don't come in order!!!
3479 case ACT_QSLOT1
: slot
= inventory
.GetQuickSlot(); break;
3480 case ACT_QSLOT2
: slot
= inventory
.GetQuickSlot()+1; break;
3481 case ACT_QSLOT3
: slot
= inventory
.GetQuickSlot()+2; break;
3482 case ACT_QSLOT4
: slot
= inventory
.GetQuickSlot()+3; break;
3483 case ACT_QSLOT5
: slot
= inventory
.GetQuickSlot()+4; break;
3487 if (!slot
) continue;
3488 //if magic items are equipped the equipping info doesn't change
3491 // Note: we're now in the QSLOTn case
3492 // If slot is empty, reset quickslot to 0xffff/0xffff
3494 if (!inventory
.HasItemInSlot("", slot
)) {
3495 SetupQuickSlot(which
, 0xffff, 0xffff);
3499 PCStats
->GetSlotAndIndex(which
,idx
,headerindex
);
3500 if (idx
!= slot
|| headerindex
== 0xffff) {
3501 // If slot just became filled, set it to filled
3502 SetupQuickSlot(which
,slot
,0);
3507 //these are always present
3508 CheckWeaponQuickSlot(0);
3509 CheckWeaponQuickSlot(1);
3510 //disabling quick weapon slots for certain classes
3512 int which
= ACT_WEAPON3
+i
;
3513 // Assuming that ACT_WEAPON3 and 4 are always in the first two spots
3514 if (PCStats
->QSlots
[i
]!=which
) {
3515 SetupQuickSlot(which
, 0xffff, 0xffff);
3520 void Actor::CheckWeaponQuickSlot(unsigned int which
)
3522 if (!PCStats
) return;
3525 // If current quickweaponslot doesn't contain an item, reset it to fist
3526 int slot
= PCStats
->QuickWeaponSlots
[which
];
3527 int header
= PCStats
->QuickWeaponHeaders
[which
];
3528 if (!inventory
.HasItemInSlot("", slot
) || header
== 0xffff) {
3529 //a quiver just went dry, falling back to fist
3532 // If current quickweaponslot contains ammo, and bow not found, reset
3534 if (core
->QuerySlotEffects(slot
) == SLOT_EFFECT_MISSILE
) {
3535 const CREItem
*slotitm
= inventory
.GetSlotItem(slot
);
3537 Item
*itm
= gamedata
->GetItem(slotitm
->ItemResRef
);
3539 ITMExtHeader
*ext_header
= itm
->GetExtHeader(header
);
3541 int type
= ext_header
->ProjectileQualifier
;
3542 int weaponslot
= inventory
.FindTypedRangedWeapon(type
);
3543 if (weaponslot
== inventory
.GetFistSlot()) {
3549 gamedata
->FreeItem(itm
,slotitm
->ItemResRef
, false);
3554 SetupQuickSlot(ACT_WEAPON1
+which
, inventory
.GetFistSlot(), 0);
3558 void Actor::SetupQuickSlot(unsigned int which
, int slot
, int headerindex
)
3560 if (!PCStats
) return;
3561 PCStats
->InitQuickSlot(which
, slot
, headerindex
);
3562 //something changed about the quick items
3563 core
->SetEventFlag(EF_ACTION
);
3566 bool Actor::ValidTarget(int ga_flags
) const
3568 //scripts can still see this type of actor
3570 if (ga_flags
&GA_NO_HIDDEN
) {
3571 if (Modified
[IE_AVATARREMOVAL
]) return false;
3572 if (Modified
[IE_EA
]>EA_GOODCUTOFF
&& Modified
[IE_STATE_ID
]&STATE_INVISIBLE
) return false;
3575 if (ga_flags
&GA_NO_ALLY
) {
3576 if(InParty
) return false;
3577 if(Modified
[IE_EA
]<=EA_GOODCUTOFF
) return false;
3580 if (ga_flags
&GA_NO_ENEMY
) {
3581 if(!InParty
&& (Modified
[IE_EA
]>=EA_EVILCUTOFF
) ) return false;
3584 if (ga_flags
&GA_NO_NEUTRAL
) {
3585 if((Modified
[IE_EA
]>EA_GOODCUTOFF
) && (Modified
[IE_EA
]<EA_EVILCUTOFF
) ) return false;
3588 switch(ga_flags
&GA_ACTION
) {
3590 if (Modified
[IE_STATE_ID
] & STATE_CANTSTEAL
) return false;
3593 //can't talk to dead
3594 if (Modified
[IE_STATE_ID
] & STATE_CANTLISTEN
) return false;
3595 //can't talk to hostile
3596 if (Modified
[IE_EA
]>=EA_EVILCUTOFF
) return false;
3599 if (ga_flags
&GA_NO_DEAD
) {
3600 if (InternalFlags
&IF_JUSTDIED
) return false;
3601 if (Modified
[IE_STATE_ID
] & STATE_DEAD
) return false;
3603 if (ga_flags
&GA_SELECT
) {
3604 if (UnselectableTimer
) return false;
3605 if (Immobile()) return false;
3610 //returns true if it won't be destroyed with an area
3611 //in this case it shouldn't be saved with the area either
3612 //it will be saved in the savegame
3613 bool Actor::Persistent() const
3615 if (InParty
) return true;
3616 if (InternalFlags
&IF_FROMGAME
) return true;
3620 //this is a reimplementation of cheatkey a/s of bg2
3621 //cycling through animation/stance
3622 // a - get next animation, s - get next stance
3624 void Actor::GetNextAnimation()
3626 int RowNum
= anims
->AvatarsRowNum
- 1;
3628 RowNum
= CharAnimations::GetAvatarsCount() - 1;
3629 int NewAnimID
= CharAnimations::GetAvatarStruct(RowNum
)->AnimID
;
3630 printf ("AnimID: %04X\n", NewAnimID
);
3631 SetBase( IE_ANIMATION_ID
, NewAnimID
);
3632 //SetAnimationID ( NewAnimID );
3635 void Actor::GetPrevAnimation()
3637 int RowNum
= anims
->AvatarsRowNum
+ 1;
3638 if (RowNum
>=CharAnimations::GetAvatarsCount() )
3640 int NewAnimID
= CharAnimations::GetAvatarStruct(RowNum
)->AnimID
;
3641 printf ("AnimID: %04X\n", NewAnimID
);
3642 SetBase( IE_ANIMATION_ID
, NewAnimID
);
3643 //SetAnimationID ( NewAnimID );
3646 //slot is the projectile slot
3647 //This will return the projectile item.
3648 ITMExtHeader
*Actor::GetRangedWeapon(WeaponInfo
&wi
) const
3650 //EquippedSlot is the projectile. To get the weapon, use inventory.GetUsedWeapon()
3651 wi
.slot
= inventory
.GetEquippedSlot();
3652 const CREItem
*wield
= inventory
.GetSlotItem(wi
.slot
);
3656 Item
*item
= gamedata
->GetItem(wield
->ItemResRef
);
3660 //The magic of the bow and the arrow add up?
3661 wi
.enchantment
+= item
->Enchantment
;
3662 wi
.itemflags
= wield
->Flags
;
3663 //wi.range is not set, the projectile has no effect on range?
3665 ITMExtHeader
*which
= item
->GetWeaponHeader(true);
3666 gamedata
->FreeItem(item
, wield
->ItemResRef
, false);
3670 int Actor::IsDualWielding() const
3673 //if the shield slot is a weapon, we're dual wielding
3674 const CREItem
*wield
= inventory
.GetUsedWeapon(true, slot
);
3679 Item
*itm
= gamedata
->GetItem( wield
->ItemResRef
);
3684 //if the item is usable in weapon slot, then it is weapon
3685 int weapon
= core
->CanUseItemType( SLOT_WEAPON
, itm
);
3686 gamedata
->FreeItem( itm
, wield
->ItemResRef
, false );
3687 //is just weapon>0 ok?
3688 return (weapon
>0)?1:0;
3691 //returns weapon header currently used (bow in case of bow+arrow)
3692 //if range is nonzero, then the returned header is valid
3693 ITMExtHeader
*Actor::GetWeapon(WeaponInfo
&wi
, bool leftorright
)
3695 //only use the shield slot if we are dual wielding
3696 leftorright
= leftorright
&& IsDualWielding();
3698 const CREItem
*wield
= inventory
.GetUsedWeapon(leftorright
, wi
.slot
);
3702 Item
*item
= gamedata
->GetItem(wield
->ItemResRef
);
3707 wi
.enchantment
= item
->Enchantment
;
3708 wi
.itemflags
= wield
->Flags
;
3709 wi
.prof
= item
->WeaProf
;
3711 //select first weapon header
3712 ITMExtHeader
*which
;
3713 if (GetAttackStyle() == WEAPON_RANGED
) {
3714 which
= item
->GetWeaponHeader(true);
3715 wi
.backstabbing
= false;
3717 which
= item
->GetWeaponHeader(false);
3718 // any melee weapon usable by a single class thief is game (UAI does not affect this)
3719 wi
.backstabbing
= !(item
->UsabilityBitmask
& 0x400000);
3722 //make sure we use 'false' in this freeitem
3723 //so 'which' won't point into invalid memory
3724 gamedata
->FreeItem(item
, wield
->ItemResRef
, false);
3728 if (which
->Location
!=ITEM_LOC_WEAPON
) {
3731 wi
.range
= which
->Range
+1;
3733 //return which->Range+1;
3736 void Actor::GetNextStance()
3738 static int Stance
= IE_ANI_AWAKE
;
3740 if (--Stance
< 0) Stance
= MAX_ANIMS
-1;
3741 printf ("StanceID: %d\n", Stance
);
3742 SetStance( Stance
);
3745 int Actor::LearnSpell(const ieResRef spellname
, ieDword flags
)
3747 //don't fail if the spell is also memorized (for innates)
3748 if (! (flags
&LS_MEMO
)) {
3749 if (spellbook
.HaveSpell(spellname
, 0) ) {
3753 Spell
*spell
= gamedata
->GetSpell(spellname
);
3755 return LSR_INVALID
; //not existent spell
3758 if (flags
& LS_STATS
) {
3759 // chance to learn roll
3760 if (LuckyRoll(1,100,0) > core
->GetIntelligenceBonus(0, GetStat(IE_INT
))) {
3765 int explev
= spellbook
.LearnSpell(spell
, flags
&LS_MEMO
);
3766 int tmp
= spell
->SpellName
;
3767 if (flags
&LS_LEARN
) {
3768 core
->GetTokenDictionary()->SetAt("SPECIALABILITYNAME", core
->GetString(tmp
));
3769 switch (spell
->SpellType
) {
3771 tmp
= STR_GOTABILITY
;
3781 gamedata
->FreeSpell(spell
, spellname
, false);
3786 displaymsg
->DisplayConstantStringName(tmp
, 0xbcefbc, this);
3788 if (flags
&LS_ADDXP
) {
3789 int xp
= CalculateExperience(XP_LEARNSPELL
, explev
);
3790 Game
*game
= core
->GetGame();
3791 game
->ShareXP(xp
, SX_DIVIDE
);
3796 const char *Actor::GetDialog(int flags
) const
3801 if (Modified
[IE_EA
]>=EA_EVILCUTOFF
) {
3805 if ( (InternalFlags
& IF_NOINT
) && CurrentAction
) {
3807 displaymsg
->DisplayConstantString(STR_TARGETBUSY
,0xff0000);
3814 void Actor::CreateStats()
3817 PCStats
= new PCStatsStruct();
3821 const char* Actor::GetScript(int ScriptIndex
) const
3823 return Scripts
[ScriptIndex
]->GetName();
3826 void Actor::SetModal(ieDword newstate
, bool force
)
3833 case MS_DETECTTRAPS
:
3844 // display the turning-off message
3845 if (ModalState
!= MS_NONE
) {
3846 displaymsg
->DisplayStringName(core
->ModalStates
[ModalState
].leaving_str
, 0xffffff, this, IE_STR_SOUND
|IE_STR_SPEECH
);
3849 // when called with the same state twice, toggle to MS_NONE
3850 if (!force
&& ModalState
== newstate
) {
3851 ModalState
= MS_NONE
;
3853 ModalState
= newstate
;
3856 //update the action bar
3857 core
->SetEventFlag(EF_ACTION
);
3859 ModalState
= newstate
;
3863 void Actor::SetModalSpell(ieDword state
, const char *spell
)
3866 strnlwrcpy(ModalSpell
, spell
, 8);
3868 if (state
>= core
->ModalStates
.size()) {
3871 strnlwrcpy(ModalSpell
, core
->ModalStates
[state
].spell
, 8);
3876 //this is just a stub function for now, attackstyle could be melee/ranged
3877 //even spells got this attack style
3878 int Actor::GetAttackStyle()
3881 //Non NULL if the equipped slot is a projectile or a throwing weapon
3882 //TODO some weapons have both melee and ranged capability
3883 if (GetRangedWeapon(wi
) != NULL
) return WEAPON_RANGED
;
3884 return WEAPON_MELEE
;
3887 void Actor::SetTarget( Scriptable
*target
)
3889 if (target
->Type
==ST_ACTOR
) {
3890 Actor
*tar
= (Actor
*) target
;
3891 LastTarget
= tar
->GetID();
3892 tar
->LastAttacker
= GetID();
3893 //we tell the game object that this creature
3894 //must be added to the list of combatants
3895 core
->GetGame()->InAttack(tar
->LastAttacker
);
3897 SetOrientation( GetOrient( target
->Pos
, Pos
), false );
3900 //in case of LastTarget = 0
3901 void Actor::StopAttack()
3903 SetStance(IE_ANI_READY
);
3905 core
->GetGame()->OutAttack(GetID());
3906 InternalFlags
|=IF_TARGETGONE
; //this is for the trigger!
3908 core
->Autopause(AP_NOTARGET
);
3912 int Actor::Immobile() const
3914 if (GetStat(IE_CASTERHOLD
)) {
3917 if (GetStat(IE_HELD
)) {
3920 if (GetStat(IE_STATE_ID
) & STATE_STILL
) {
3926 //calculate how many attacks will be performed
3928 //only called when Game thinks we are in attack
3929 //so it is safe to do cleanup here (it will be called only once)
3930 void Actor::InitRound(ieDword gameTime
)
3932 lastInit
= gameTime
;
3933 secondround
= !secondround
;
3935 //roundTime will equal 0 if we aren't attacking something
3937 //only perform calculations at the beginning of the round!
3938 if (((gameTime
-roundTime
)%core
->Time
.round_size
!= 0) || \
3939 (roundTime
== lastInit
)) {
3944 //reset variables used in PerformAttack
3946 attacksperround
= 0;
3950 //we set roundTime to zero on any of the following returns, because this
3951 //is guaranteed to be the start of a round, and we only want roundTime
3952 //if we are attacking this round
3953 if (InternalFlags
&IF_STOPATTACK
) {
3954 core
->GetGame()->OutAttack(GetID());
3965 //if held or disabled, etc, then cannot continue attacking
3966 ieDword state
= GetStat(IE_STATE_ID
);
3967 if (state
&STATE_CANTMOVE
) {
3976 //add one for second round to get an extra attack only if we
3977 //are x/2 attacks per round
3978 attackcount
= GetStat(IE_NUMBEROFATTACKS
);
3982 //all numbers of attacks are stored at twice their value
3985 //make sure we always get at least 1apr
3986 if (attackcount
< 1) {
3990 //set our apr and starting round time
3991 attacksperround
= attackcount
;
3992 roundTime
= gameTime
;
3994 //print a little message :)
3995 printMessage("InitRound", " ", WHITE
);
3996 printf("Name: %s | Attacks: %d | Start: %d\n", ShortName
, attacksperround
, gameTime
);
3998 // this might not be the right place, but let's give it a go
3999 if (attacksperround
&& InParty
) {
4000 core
->Autopause(AP_ENDROUND
);
4004 bool Actor::GetCombatDetails(int &tohit
, bool leftorright
, WeaponInfo
& wi
, ITMExtHeader
*&header
, ITMExtHeader
*&hittingheader
, \
4005 ieDword
&Flags
, int &DamageBonus
, int &speed
, int &CriticalBonus
, int &style
)
4007 tohit
= GetStat(IE_TOHIT
);
4008 speed
= -GetStat(IE_PHYSICALSPEED
);
4009 bool dualwielding
= IsDualWielding();
4010 header
= GetWeapon(wi
, leftorright
&& dualwielding
);
4016 hittingheader
= header
;
4017 ITMExtHeader
*rangedheader
= NULL
;
4018 int THAC0Bonus
= hittingheader
->THAC0Bonus
;
4019 DamageBonus
= hittingheader
->DamageBonus
;
4020 switch(hittingheader
->AttackType
) {
4022 Flags
= WEAPON_MELEE
;
4024 case ITEM_AT_PROJECTILE
: //throwing weapon
4025 Flags
= WEAPON_RANGED
;
4028 rangedheader
= GetRangedWeapon(wi
);
4029 if (!rangedheader
) {
4030 //display out of ammo verbal constant if there is any???
4031 //DisplayStringCore(this, VB_OUTOFAMMO, DS_CONSOLE|DS_CONST );
4032 SetStance(IE_ANI_READY
);
4036 Flags
= WEAPON_RANGED
;
4037 //The bow can give some bonuses, but the core attack is made by the arrow.
4038 hittingheader
= rangedheader
;
4039 THAC0Bonus
+= rangedheader
->THAC0Bonus
;
4040 DamageBonus
+= rangedheader
->DamageBonus
;
4043 //item is unsuitable for fight
4044 SetStance(IE_ANI_READY
);
4047 //this flag is set by the bow in case of projectile launcher.
4048 if (header
->RechargeFlags
&IE_ITEM_USESTRENGTH
) Flags
|=WEAPON_USESTRENGTH
;
4050 // get our dual wielding modifier
4053 DamageBonus
+= GetStat(IE_DAMAGEBONUSLEFT
);
4055 DamageBonus
+= GetStat(IE_DAMAGEBONUSRIGHT
);
4058 leftorright
= leftorright
&& dualwielding
;
4059 if (leftorright
) Flags
|=WEAPON_LEFTHAND
;
4061 //add in proficiency bonuses
4063 if (wi
.prof
&& (wi
.prof
<= MAX_STATS
)) {
4064 stars
= GetStat(wi
.prof
)&PROFS_MASK
;
4066 //hit/damage/speed bonuses from wspecial
4067 if ((signed)stars
> wspecial_max
) {
4068 stars
= wspecial_max
;
4070 THAC0Bonus
+= wspecial
[stars
][0];
4071 DamageBonus
+= wspecial
[stars
][1];
4072 speed
+= wspecial
[stars
][2];
4073 // add non-proficiency penalty, which is missing from the table
4074 if (stars
== 0) THAC0Bonus
-= 4;
4077 if (IsDualWielding() && wsdualwield
) {
4078 //add dual wielding penalty
4079 stars
= GetStat(IE_PROFICIENCY2WEAPON
)&PROFS_MASK
;
4080 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4082 style
= 1000*stars
+ IE_PROFICIENCY2WEAPON
;
4083 THAC0Bonus
+= wsdualwield
[stars
][leftorright
?1:0];
4084 } else if (wi
.itemflags
&(IE_INV_ITEM_TWOHANDED
) && (Flags
&WEAPON_MELEE
) && wstwohanded
) {
4085 //add two handed profs bonus
4086 stars
= GetStat(IE_PROFICIENCY2HANDED
)&PROFS_MASK
;
4087 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4089 style
= 1000*stars
+ IE_PROFICIENCY2HANDED
;
4090 DamageBonus
+= wstwohanded
[stars
][0];
4091 CriticalBonus
= wstwohanded
[stars
][1];
4092 speed
+= wstwohanded
[stars
][2];
4093 } else if (Flags
&WEAPON_MELEE
) {
4095 CREItem
*weapon
= inventory
.GetUsedWeapon(true, slot
);
4096 if(wssingle
&& weapon
== NULL
) {
4097 //NULL return from GetUsedWeapon means no shield slot
4098 stars
= GetStat(IE_PROFICIENCYSINGLEWEAPON
)&PROFS_MASK
;
4099 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4101 style
= 1000*stars
+ IE_PROFICIENCYSINGLEWEAPON
;
4102 CriticalBonus
= wssingle
[stars
][1];
4103 } else if (wsswordshield
&& weapon
) {
4104 stars
= GetStat(IE_PROFICIENCYSWORDANDSHIELD
)&PROFS_MASK
;
4105 if (stars
> STYLE_MAX
) stars
= STYLE_MAX
;
4107 style
= 1000*stars
+ IE_PROFICIENCYSWORDANDSHIELD
;
4112 // ranged - no bonus
4115 // TODO: Elves get a racial THAC0 bonus with all swords and bows in BG2 (but not daggers)
4117 //second parameter is left or right hand flag
4118 tohit
= GetToHit(THAC0Bonus
, Flags
);
4122 int Actor::GetToHit(int bonus
, ieDword Flags
)
4126 //get our dual wielding modifier
4127 if (IsDualWielding()) {
4128 if (Flags
&WEAPON_LEFTHAND
) {
4129 tohit
+= GetStat(IE_HITBONUSLEFT
);
4131 tohit
+= GetStat(IE_HITBONUSRIGHT
);
4135 //get attack style (melee or ranged)
4136 switch(Flags
&WEAPON_STYLEMASK
) {
4138 tohit
+= GetStat(IE_MELEETOHIT
);
4141 tohit
+= GetStat(IE_FISTHIT
);
4144 tohit
+= GetStat(IE_MISSILEHITBONUS
);
4145 //add dexterity bonus
4146 tohit
+= core
->GetDexterityBonus(STAT_DEX_MISSILE
, GetStat(IE_DEX
));
4150 //add strength bonus if we need
4151 if (Flags
&WEAPON_USESTRENGTH
) {
4152 tohit
+= core
->GetStrengthBonus(0,GetStat(IE_STR
), GetStat(IE_STREXTRA
) );
4155 // if the target is using a ranged weapon while we're meleeing, we get a +4 bonus
4156 if ((Flags
&WEAPON_STYLEMASK
) != WEAPON_RANGED
) {
4157 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4158 if (target
&& target
->GetAttackStyle() == WEAPON_RANGED
) {
4163 // add +4 attack bonus vs racial enemies
4164 if (GetRangerLevel()) {
4165 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4166 if (target
&& IsRacialEnemy(target
)) {
4172 tohit
= (signed)GetStat(IE_TOHIT
)-tohit
;
4174 tohit
+= GetStat(IE_TOHIT
);
4179 static const int weapon_damagetype
[] = {DAMAGE_CRUSHING
, DAMAGE_PIERCING
,
4180 DAMAGE_CRUSHING
, DAMAGE_SLASHING
, DAMAGE_MISSILE
, DAMAGE_STUNNING
};
4182 int Actor::GetDefense(int DamageType
)
4184 //specific damage type bonus.
4188 switch (weapon_damagetype
[DamageType
]) {
4189 case DAMAGE_CRUSHING
:
4190 defense
+= GetStat(IE_ACCRUSHINGMOD
);
4192 case DAMAGE_PIERCING
:
4193 defense
+= GetStat(IE_ACPIERCINGMOD
);
4195 case DAMAGE_SLASHING
:
4196 defense
+= GetStat(IE_ACSLASHINGMOD
);
4198 case DAMAGE_MISSILE
:
4199 defense
+= GetStat(IE_ACMISSILEMOD
);
4201 //What about stunning ?
4206 //check for s/s and single weapon ac bonuses
4207 if (!IsDualWielding() && wssingle
&& wsswordshield
) {
4209 ITMExtHeader
* header
;
4210 header
= GetWeapon(wi
, false);
4211 //make sure we're wielding a single melee weapon
4212 if (header
&& (header
->AttackType
== ITEM_AT_MELEE
)) {
4215 if (inventory
.GetUsedWeapon(true, slot
) == NULL
) {
4216 //single-weapon style applies to all ac
4217 stars
= GetStat(IE_PROFICIENCYSINGLEWEAPON
)&PROFS_MASK
;
4218 if (stars
>STYLE_MAX
) stars
= STYLE_MAX
;
4219 defense
+= wssingle
[stars
][0];
4220 } else if (weapon_damagetype
[DamageType
] == DAMAGE_MISSILE
) {
4221 //sword-shield style applies only to missile ac
4222 stars
= GetStat(IE_PROFICIENCYSWORDANDSHIELD
)&PROFS_MASK
;
4223 if (stars
>STYLE_MAX
) stars
= STYLE_MAX
;
4224 defense
+= wsswordshield
[stars
][0];
4230 defense
= GetStat(IE_ARMORCLASS
)-defense
;
4232 defense
+= GetStat(IE_ARMORCLASS
);
4234 //Dexterity bonus is stored negative in 2da files.
4235 return defense
+ core
->GetDexterityBonus(STAT_DEX_AC
, GetStat(IE_DEX
) );
4239 static EffectRef fx_ac_vs_creature_type_ref
={"ACVsCreatureType",NULL
,-1};
4241 void Actor::PerformAttack(ieDword gameTime
)
4243 // start a new round if we really don't have one yet
4245 printMessage("Actor", "Unregistered attack. We shouldn't be here?\n", RED
);
4247 InitRound(gameTime
);
4250 //only return if we don't have any attacks left this round
4251 if (attackcount
==0) return;
4253 // this check shouldn't be necessary, but it causes a divide-by-zero below,
4254 // so i would like it to be clear if it ever happens
4255 if (attacksperround
==0) {
4256 printMessage("Actor", "APR was 0 in PerformAttack!\n", RED
);
4260 //don't continue if we can't make the attack yet
4261 //we check lastattack because we will get the same gameTime a few times
4262 if ((nextattack
> gameTime
) || (gameTime
== lastattack
)) {
4263 // fuzzie added the following line as part of the UpdateActorState hack below
4264 lastattack
= gameTime
;
4268 if (InternalFlags
&IF_STOPATTACK
) {
4269 // this should be avoided by the AF_ALIVE check by all the calling actions
4270 printMessage("Actor", "Attack by dead actor!\n", LIGHT_RED
);
4275 printMessage("Actor", "Attack without valid target ID!\n", LIGHT_RED
);
4279 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4281 if (target
&& (target
->GetStat(IE_STATE_ID
)&(STATE_DEAD
|STATE_INVISIBLE
))) {
4286 printMessage("Actor", "Attack without valid target!\n", LIGHT_RED
);
4290 printf("Performattack for %s, target is: %s\n", ShortName
, target
->ShortName
);
4292 //which hand is used
4293 //we do apr - attacksleft so we always use the main hand first
4294 bool leftorright
= (bool) ((attacksperround
-attackcount
)&1);
4297 ITMExtHeader
*header
= NULL
;
4298 ITMExtHeader
*hittingheader
= NULL
;
4301 int DamageBonus
, CriticalBonus
;
4304 //will return false on any errors (eg, unusable weapon)
4305 if (!GetCombatDetails(tohit
, leftorright
, wi
, header
, hittingheader
, Flags
, DamageBonus
, speed
, CriticalBonus
, style
)) {
4309 //if this is the first call of the round, we need to update next attack
4310 if (nextattack
== 0) {
4311 //FIXME: figure out exactly how initiative is calculated
4312 int initiative
= core
->Roll(1, 5, GetXPLevel(true)/(-8));
4313 int spdfactor
= hittingheader
->Speed
+ speed
+ initiative
;
4314 if (spdfactor
<0) spdfactor
= 0;
4315 if (spdfactor
>10) spdfactor
= 10;
4317 //(round_size/attacks_per_round)*(initiative) is the first delta
4318 nextattack
= core
->Time
.round_size
*spdfactor
/(attacksperround
*10) + gameTime
;
4320 //we can still attack this round if we have a speed factor of 0
4321 if (nextattack
> gameTime
) {
4326 if((PersonalDistance(this, target
) > wi
.range
*10) || (GetCurrentArea()!=target
->GetCurrentArea() ) ) {
4327 // this is a temporary double-check, remove when bugfixed
4328 printMessage("Actor", "Attack action didn't bring us close enough!", LIGHT_RED
);
4332 SetStance(AttackStance
);
4334 //figure out the time for our next attack since the old time has the initiative
4335 //in it, we only have to add the basic delta
4337 nextattack
+= (core
->Time
.round_size
/attacksperround
);
4338 lastattack
= gameTime
;
4341 if (leftorright
&& IsDualWielding()) {
4342 printMessage("Attack","(Off) ", YELLOW
);
4344 printMessage("Attack","(Main) ", GREEN
);
4346 if (attacksperround
) {
4347 printf("Left: %d | ", attackcount
);
4348 printf("Next: %d ", nextattack
);
4351 int roll
= LuckyRoll(1, ATTACKROLL
, 0);
4354 printBracket("Critical Miss", RED
);
4356 displaymsg
->DisplayConstantStringName(STR_CRITICAL_MISS
, 0xffffff, this);
4357 DisplayStringCore(this, VB_CRITMISS
, DS_CONSOLE
|DS_CONST
);
4358 if (Flags
&WEAPON_RANGED
) {//no need for this with melee weapon!
4359 UseItem(wi
.slot
, (ieDword
)-2, target
, UI_MISS
);
4360 } else if (core
->HasFeature(GF_BREAKABLE_WEAPONS
)) {
4362 //TODO: this appears to be a random roll on-hit (perhaps critical failure
4363 // too); we use 1% (1d20*1d5==1)
4364 if ((header
->RechargeFlags
&IE_ITEM_BREAKABLE
) && core
->Roll(1,5,0) == 1) {
4365 inventory
.BreakItemSlot(wi
.slot
);
4372 //modify defense with damage type
4373 ieDword damagetype
= hittingheader
->DamageType
;
4377 if (hittingheader
->DiceThrown
<256) {
4378 damage
+= LuckyRoll(hittingheader
->DiceThrown
, hittingheader
->DiceSides
, 0, 1, 0);
4379 damage
+= DamageBonus
;
4380 printf("| Damage %dd%d%+d = %d ",hittingheader
->DiceThrown
, hittingheader
->DiceSides
, DamageBonus
, damage
);
4382 printf("| No Damage");
4386 if (roll
>= (ATTACKROLL
- (int) GetStat(IE_CRITICALHITBONUS
) - CriticalBonus
)) {
4388 printBracket("Critical Hit", GREEN
);
4390 displaymsg
->DisplayConstantStringName(STR_CRITICAL_HIT
, 0xffffff, this);
4391 DisplayStringCore(this, VB_CRITHIT
, DS_CONSOLE
|DS_CONST
);
4392 ModifyDamage (target
, this, damage
, resisted
, weapon_damagetype
[damagetype
], &wi
, true);
4393 UseItem(wi
.slot
, Flags
&WEAPON_RANGED
?-2:-1, target
, 0, damage
);
4400 //get target's defense against attack
4401 int defense
= target
->GetDefense(damagetype
);
4402 defense
-= target
->fxqueue
.BonusAgainstCreature(fx_ac_vs_creature_type_ref
,this);
4406 success
= roll
+ defense
> tohit
;
4408 success
= tohit
+ roll
> defense
;
4413 if (Flags
&WEAPON_RANGED
) {//Launch the projectile anyway
4414 UseItem(wi
.slot
, (ieDword
)-2, target
, UI_MISS
);
4417 printBracket("Missed", LIGHT_RED
);
4421 printBracket("Hit", GREEN
);
4423 ModifyDamage (target
, this, damage
, resisted
, weapon_damagetype
[damagetype
], &wi
, false);
4424 UseItem(wi
.slot
, Flags
&WEAPON_RANGED
?-2:-1, target
, 0, damage
);
4428 static EffectRef fx_stoneskin_ref
={"StoneSkinModifier",NULL
,-1};
4429 static EffectRef fx_stoneskin2_ref
={"StoneSkin2Modifier",NULL
,-1};
4430 static EffectRef fx_mirrorimage_ref
={"MirrorImageModifier",NULL
,-1};
4431 static EffectRef fx_aegis_ref
={"Aegis",NULL
,-1};
4433 void Actor::ModifyDamage(Actor
*target
, Scriptable
*hitter
, int &damage
, int &resisted
, int damagetype
, WeaponInfo
*wi
, bool critical
)
4436 int mirrorimages
= target
->Modified
[IE_MIRRORIMAGES
];
4438 if (LuckyRoll(1,mirrorimages
+1,0) != 1) {
4439 target
->fxqueue
.DecreaseParam1OfEffect(fx_mirrorimage_ref
, 1);
4440 target
->Modified
[IE_MIRRORIMAGES
]--;
4446 // only check stone skins if damage type is physical or magical
4447 // DAMAGE_CRUSHING is 0, so we can't AND with it to check for its presence
4448 if (!(damagetype
& ~(DAMAGE_PIERCING
|DAMAGE_SLASHING
|DAMAGE_MISSILE
|DAMAGE_MAGIC
))) {
4449 int stoneskins
= target
->Modified
[IE_STONESKINS
];
4451 target
->fxqueue
.DecreaseParam1OfEffect(fx_stoneskin_ref
, 1);
4452 target
->fxqueue
.DecreaseParam1OfEffect(fx_aegis_ref
, 1);
4453 target
->Modified
[IE_STONESKINS
]--;
4458 stoneskins
= target
->Modified
[IE_STONESKINSGOLEM
];
4460 target
->fxqueue
.DecreaseParam1OfEffect(fx_stoneskin2_ref
, 1);
4461 target
->Modified
[IE_STONESKINSGOLEM
]--;
4468 if (BaseStats
[IE_BACKSTABDAMAGEMULTIPLIER
] > 1) {
4469 if (Modified
[IE_STATE_ID
] & (ieDword
) (STATE_INVISIBLE
) || Modified
[IE_ALWAYSBACKSTAB
]) {
4470 if ( !(core
->HasFeature(GF_PROPER_BACKSTAB
) && !IsBehind(target
)) ) {
4471 if (target
->Modified
[IE_DISABLEBACKSTAB
]) {
4472 // The backstab seems to have failed
4473 displaymsg
->DisplayConstantString (STR_BACKSTAB_FAIL
, 0xffffff);
4475 if (wi
->backstabbing
) {
4476 damage
*= Modified
[IE_BACKSTABDAMAGEMULTIPLIER
];
4477 // display a simple message instead of hardcoding multiplier names
4478 displaymsg
->DisplayConstantStringValue (STR_BACKSTAB
, 0xffffff, Modified
[IE_BACKSTABDAMAGEMULTIPLIER
]);
4480 // weapon is unsuitable for backstab
4481 displaymsg
->DisplayConstantString (STR_BACKSTAB_BAD
, 0xffffff);
4488 // add strength bonus; backstab does not affect it
4489 // TODO: should actually check WEAPON_USESTRENGTH, since a sling in bg2 has it
4490 if (GetAttackStyle() != WEAPON_RANGED
) {
4491 damage
+= core
->GetStrengthBonus(1, GetStat(IE_STR
), GetStat(IE_STREXTRA
) );
4495 if (wi
&& target
->fxqueue
.WeaponImmunity(wi
->enchantment
, wi
->itemflags
)) {
4497 resisted
= DR_IMMUNE
; // mark immunity for GetCombatDetails
4499 // check damage type immunity / resistance / susceptibility
4500 std::multimap
<ieDword
, DamageInfoStruct
>::iterator it
;
4501 it
= core
->DamageInfoMap
.find(damagetype
);
4502 if (it
== core
->DamageInfoMap
.end()) {
4503 printf("Unhandled damagetype:%d\n", damagetype
);
4504 } else if (it
->second
.resist_stat
== 0) {
4505 // damage type without a resistance stat
4507 damage
+= (signed)target
->GetStat(IE_DAMAGEBONUS
);
4508 resisted
= (int) (damage
* (signed)target
->GetStat(it
->second
.resist_stat
)/100.0);
4509 // check for bonuses for specific damage types
4510 if (core
->HasFeature(GF_SPECIFIC_DMG_BONUS
) && hitter
&& hitter
->Type
== ST_ACTOR
) {
4511 int bonus
= ((Actor
*)hitter
)->fxqueue
.SpecificDamageBonus(it
->second
.iwd_mod_type
);
4513 resisted
-= int (damage
* bonus
/ 100.0);
4514 printf("Bonus damage of %d (%+d%%), neto: %d\n", int (damage
* bonus
/ 100.0), bonus
, -resisted
);
4518 printf("Resisted %d of %d at %d%% resistance to %d\n", resisted
, damage
+resisted
, target
->GetStat(it
->second
.resist_stat
), damagetype
);
4519 if (damage
<= 0) resisted
= DR_IMMUNE
;
4523 //check casting failure
4524 if (damage
<0) damage
= 0;
4526 DisplayStringCore(this, VB_TIMMUNE
, DS_CONSOLE
|DS_CONST
);
4531 //a critical surely raises the morale?
4532 NewBase(IE_MORALE
, 1, MOD_ADDITIVE
);
4533 int head
= inventory
.GetHeadSlot();
4534 if ((head
!=-1) && target
->inventory
.HasItemInSlot("",(ieDword
) head
)) {
4535 //critical hit is averted by helmet
4536 displaymsg
->DisplayConstantStringName(STR_NO_CRITICAL
, 0xffffff, target
);
4538 damage
<<=1; //critical damage is always double?
4539 core
->timer
->SetScreenShake(16,16,8);
4545 void Actor::UpdateActorState(ieDword gameTime
) {
4546 //apply the modal effect on the beginning of each round
4547 if (((gameTime
-roundTime
)%core
->Time
.round_size
==0) && ModalState
) {
4548 if (!ModalSpell
[0]) {
4549 printMessage("Actor","Modal Spell Effect was not set!\n", YELLOW
);
4551 } else if(ModalSpell
[0]!='*') {
4552 if (ModalSpellSkillCheck()) {
4553 if (core
->ModalStates
[ModalState
].aoe_spell
) {
4554 core
->ApplySpellPoint(ModalSpell
, GetCurrentArea(), Pos
, this, 0);
4556 core
->ApplySpell(ModalSpell
, this, this, 0);
4559 displaymsg
->DisplayStringName(core
->ModalStates
[ModalState
].entering_str
, 0xffffff, this, IE_STR_SOUND
|IE_STR_SPEECH
);
4563 displaymsg
->DisplayStringName(core
->ModalStates
[ModalState
].failed_str
, 0xffffff, this, IE_STR_SOUND
|IE_STR_SPEECH
);
4565 ModalState
= MS_NONE
;
4566 // TODO: wait for a round until allowing new states?
4571 // this is a HACK, fuzzie can't work out where else to do this for now
4572 // but we shouldn't be resetting rounds/attacks just because the actor
4573 // wandered away, the action code should probably be responsible somehow
4574 // see also line above (search for comment containing UpdateActorState)!
4575 if (LastTarget
&& lastattack
&& lastattack
< (gameTime
- 1)) {
4576 Actor
*target
= area
->GetActorByGlobalID(LastTarget
);
4577 if (!target
|| target
->GetStat(IE_STATE_ID
)&STATE_DEAD
) {
4580 printMessage("Attack","(Leaving attack)", GREEN
);
4581 core
->GetGame()->OutAttack(GetID());
4589 //idx could be: 0-6, 16-22, 32-38, 48-54
4590 //the colors are stored in 7 dwords
4591 //maybe it would be simpler to store them in 28 bytes (without using stats?)
4592 void Actor::SetColor( ieDword idx
, ieDword grd
)
4594 ieByte gradient
= (ieByte
) (grd
&255);
4595 ieByte index
= (ieByte
) (idx
&15);
4596 ieByte shift
= (ieByte
) (idx
/16);
4599 //invalid value, would crash original IE
4604 // put gradient in all four bytes of value
4606 value
|= (value
<< 8);
4607 value
|= (value
<< 16);
4608 for (index
=0;index
<7;index
++) {
4609 Modified
[IE_COLORS
+index
] = value
;
4612 //invalid value, would crash original IE
4617 value
= gradient
<< shift
;
4618 value
|= Modified
[IE_COLORS
+index
] & ~(255<<shift
);
4619 Modified
[IE_COLORS
+index
] = value
;
4623 void Actor::SetColorMod( ieDword location
, RGBModifier::Type type
, int speed
,
4624 unsigned char r
, unsigned char g
, unsigned char b
,
4627 CharAnimations
* ca
= GetAnims();
4630 if (location
== 0xff) {
4631 ca
->GlobalColorMod
.type
= type
;
4632 ca
->GlobalColorMod
.speed
= speed
;
4633 ca
->GlobalColorMod
.rgb
.r
= r
;
4634 ca
->GlobalColorMod
.rgb
.g
= g
;
4635 ca
->GlobalColorMod
.rgb
.b
= b
;
4636 ca
->GlobalColorMod
.rgb
.a
= 0;
4638 ca
->GlobalColorMod
.phase
= phase
;
4640 if (ca
->GlobalColorMod
.phase
> 2*speed
)
4641 ca
->GlobalColorMod
.phase
=0;
4645 //00xx0yyy-->000xxyyy
4646 if (location
&0xffffffc8) return; //invalid location
4647 location
= (location
&7) | ((location
>>1)&0x18);
4648 ca
->ColorMods
[location
].type
= type
;
4649 ca
->ColorMods
[location
].speed
= speed
;
4650 ca
->ColorMods
[location
].rgb
.r
= r
;
4651 ca
->ColorMods
[location
].rgb
.g
= g
;
4652 ca
->ColorMods
[location
].rgb
.b
= b
;
4653 ca
->ColorMods
[location
].rgb
.a
= 0;
4655 ca
->ColorMods
[location
].phase
= phase
;
4657 if (ca
->ColorMods
[location
].phase
> 2*speed
)
4658 ca
->ColorMods
[location
].phase
= 0;
4662 void Actor::SetLeader(Actor
*actor
, int xoffset
, int yoffset
)
4664 LastFollowed
= actor
->GetID();
4665 FollowOffset
.x
= xoffset
;
4666 FollowOffset
.y
= yoffset
;
4669 //if days == 0, it means full healing
4670 void Actor::Heal(int days
)
4673 SetBase(IE_HITPOINTS
, BaseStats
[IE_HITPOINTS
]+days
*2);
4675 SetBase(IE_HITPOINTS
, BaseStats
[IE_MAXHITPOINTS
]);
4679 void Actor::AddExperience(int exp
)
4681 if (core
->HasFeature(GF_WISDOM_BONUS
)) {
4682 exp
= (exp
* (100 + core
->GetWisdomBonus(0, Modified
[IE_WIS
]))) / 100;
4684 SetBase(IE_XP
,BaseStats
[IE_XP
]+exp
);
4687 int Actor::CalculateExperience(int type
, int level
)
4689 if (type
>=xpbonustypes
) {
4692 unsigned int l
= (unsigned int) (level
- 1);
4694 if (l
>=(unsigned int) xpbonuslevels
) {
4697 return xpbonus
[type
*xpbonuslevels
+l
];
4700 bool Actor::Schedule(ieDword gametime
, bool checkhide
)
4703 if (!(InternalFlags
&IF_VISIBLE
) ) {
4708 //check for schedule
4709 ieDword bit
= 1<<((gametime
/6)%7200/300);
4710 if (appearance
& bit
) {
4716 void Actor::NewPath()
4719 Point tmp
= Destination
;
4724 Movable::WalkTo(tmp
, size
);
4727 void Actor::SetInTrap(ieDword setreset
)
4731 InternalFlags
|= IF_INTRAP
;
4733 InternalFlags
&= ~IF_INTRAP
;
4737 void Actor::SetRunFlags(ieDword flags
)
4739 InternalFlags
&= ~IF_RUNFLAGS
;
4740 InternalFlags
|= (flags
& IF_RUNFLAGS
);
4743 void Actor::WalkTo(const Point
&Des
, ieDword flags
, int MinDistance
)
4746 if (InternalFlags
&IF_REALLYDIED
) {
4751 if (Des
.x
==-2 && Des
.y
==-2) {
4752 Point
p((ieWord
) Modified
[IE_SAVEDXPOS
], (ieWord
) Modified
[IE_SAVEDYPOS
] );
4753 Movable::WalkTo(p
, MinDistance
);
4755 Movable::WalkTo(Des
, MinDistance
);
4759 //there is a similar function in Map for stationary vvcs
4760 void Actor::DrawVideocells(const Region
&screen
, vvcVector
&vvcCells
, const Color
&tint
)
4762 Map
* area
= GetCurrentArea();
4764 for (unsigned int i
= 0; i
< vvcCells
.size(); i
++) {
4765 ScriptedAnimation
* vvc
= vvcCells
[i
];
4766 /* we don't allow holes anymore
4771 // actually this is better be drawn by the vvc
4772 bool endReached
= vvc
->Draw(screen
, Pos
, tint
, area
, WantDither(), GetOrientation());
4775 vvcCells
.erase(vvcCells
.begin()+i
);
4781 void Actor::DrawActorSprite(const Region
&screen
, int cx
, int cy
, const Region
& bbox
,
4782 SpriteCover
*& newsc
, Animation
** anims
,
4783 unsigned char Face
, const Color
& tint
)
4785 CharAnimations
* ca
= GetAnims();
4786 int PartCount
= ca
->GetTotalPartCount();
4787 Video
* video
= core
->GetVideoDriver();
4788 Region vp
= video
->GetViewport();
4790 // display current frames in the right order
4791 const int* zOrder
= ca
->GetZOrder(Face
);
4792 for (int part
= 0; part
< PartCount
; ++part
) {
4794 if (zOrder
) partnum
= zOrder
[part
];
4795 Animation
* anim
= anims
[partnum
];
4796 Sprite2D
* nextFrame
= 0;
4798 nextFrame
= anim
->GetFrame(anim
->GetCurrentFrame());
4799 if (nextFrame
&& bbox
.InsideRegion( vp
) ) {
4800 if (!newsc
|| !newsc
->Covers(cx
, cy
, nextFrame
->XPos
, nextFrame
->YPos
, nextFrame
->Width
, nextFrame
->Height
)) {
4801 // the first anim contains the animarea for
4802 // the entire multi-part animation
4803 newsc
= area
->BuildSpriteCover(cx
,
4804 cy
, -anims
[0]->animArea
.x
,
4805 -anims
[0]->animArea
.y
,
4806 anims
[0]->animArea
.w
,
4807 anims
[0]->animArea
.h
, WantDither() );
4809 assert(newsc
->Covers(cx
, cy
, nextFrame
->XPos
, nextFrame
->YPos
, nextFrame
->Width
, nextFrame
->Height
));
4811 unsigned int flags
= TranslucentShadows
? BLIT_TRANSSHADOW
: 0;
4813 if (!ca
->lockPalette
) flags
|=BLIT_TINTED
;
4815 video
->BlitGameSprite( nextFrame
, cx
+ screen
.x
, cy
+ screen
.y
,
4816 flags
, tint
, newsc
, ca
->GetPartPalette(partnum
), &screen
);
4822 static const int OrientdX
[16] = { 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9, 10, 9, 7, 4 };
4823 static const int OrientdY
[16] = { 10, 9, 7, 4, 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9 };
4824 static const unsigned int MirrorImageLocation
[8] = { 4, 12, 8, 0, 6, 14, 10, 2 };
4825 static const unsigned int MirrorImageZOrder
[8] = { 2, 4, 6, 0, 1, 7, 5, 3 };
4827 bool Actor::ShouldHibernate() {
4828 //finding an excuse why we don't hybernate the actor
4829 if (Modified
[IE_ENABLEOFFSCREENAI
])
4831 if (LastTarget
) //currently attacking someone
4833 if (!lastRunTime
) // haven't had a chance to run a script
4839 if (GetNextAction())
4841 if (GetWait()) //would never stop waiting
4846 void Actor::Draw(const Region
&screen
)
4848 Map
* area
= GetCurrentArea();
4852 int explored
= Modified
[IE_DONOTJUMP
]&DNJ_UNHINDERED
;
4853 //check the deactivation condition only if needed
4854 //this fixes dead actors disappearing from fog of war (they should be permanently visible)
4855 if ((!area
->IsVisible( Pos
, explored
) || (InternalFlags
&IF_REALLYDIED
) ) && (InternalFlags
&IF_ACTIVE
) ) {
4856 //turning actor inactive if there is no action next turn
4857 if (ShouldHibernate()) {
4858 InternalFlags
|=IF_IDLE
;
4860 if (!(InternalFlags
&IF_REALLYDIED
)) {
4861 // for a while this didn't return (disable drawing) if about to hibernate;
4862 // Avenger said (aa10aaed) "we draw the actor now for the last time".
4868 area
->ClearTrap(this, InTrap
-1);
4871 // if an actor isn't visible, should we still handle animations, draw
4872 // video cells, etc? let us assume not, for now..
4873 if (!(InternalFlags
& IF_VISIBLE
)) {
4878 CharAnimations
* ca
= GetAnims();
4882 //explored or visibilitymap (bird animations are visible in fog)
4884 int NoCircle
= Modified
[IE_NOCIRCLE
];
4885 int Trans
= Modified
[IE_TRANSLUCENT
];
4891 //iwd has this flag saved in the creature
4892 if (Modified
[IE_AVATARREMOVAL
]) {
4897 int Frozen
= Immobile();
4898 int State
= Modified
[IE_STATE_ID
];
4900 if (State
&STATE_DEAD
) {
4904 if (State
&STATE_STILL
) {
4908 //adjust invisibility for enemies
4909 if (Modified
[IE_EA
]>EA_GOODCUTOFF
) {
4910 if (State
&STATE_INVISIBLE
) {
4916 //can't move this, because there is permanent blur state where
4917 //there is no effect (just state bit)
4918 if ((State
&STATE_BLUR
) && Trans
< 128) {
4921 Color tint
= area
->LightMap
->GetPixel( cx
/ 16, cy
/ 12);
4922 tint
.a
= (ieByte
) (255-Trans
);
4924 unsigned char heightmapindex
= area
->HeightMap
->GetAt( cx
/ 16, cy
/ 12);
4925 if (heightmapindex
> 15) {
4926 // there are 8bpp lightmaps (eg, bg2's AR1300) and fuzzie
4927 // cannot work out how they work, so here is an incorrect
4928 // hack (probably). please fix!
4929 heightmapindex
= 15;
4932 //don't use cy for area map access beyond this point
4933 cy
-= heightmapindex
;
4935 //draw videocells under the actor
4936 DrawVideocells(screen
, vvcShields
, tint
);
4938 Video
* video
= core
->GetVideoDriver();
4939 Region vp
= video
->GetViewport();
4941 bool drawcircle
= (NoCircle
== 0);
4942 GameControl
*gc
= core
->GetGameControl();
4943 if (gc
->GetScreenFlags()&SF_CUTSCENE
) {
4944 // ground circles are not drawn in cutscenes
4947 // the speaker should get a circle even in cutscenes
4948 if (gc
->dialoghandler
->targetID
== globalID
&& (gc
->GetDialogueFlags()&DF_IN_DIALOG
)) {
4951 if (BaseStats
[IE_STATE_ID
]&STATE_DEAD
|| InternalFlags
&IF_JUSTDIED
) {
4954 bool drawtarget
= drawcircle
;
4955 // we always show circle/target on pause
4956 if (drawcircle
&& !(gc
->GetDialogueFlags() & DF_FREEZE_SCRIPTS
)) {
4957 // check marker feedback level
4958 ieDword markerfeedback
= 4;
4959 core
->GetDictionary()->Lookup("GUI Feedback Level", markerfeedback
);
4962 drawcircle
= markerfeedback
>= 1;
4963 } else if (Selected
) {
4964 // selected creature
4965 drawcircle
= markerfeedback
>= 2;
4966 } else if (IsPC()) {
4968 drawcircle
= markerfeedback
>= 3;
4969 } else if (Modified
[IE_EA
] >= EA_EVILCUTOFF
) {
4971 drawcircle
= markerfeedback
>= 5;
4974 drawcircle
= markerfeedback
>= 6;
4976 drawtarget
= Selected
&& markerfeedback
>= 4;
4982 DrawTargetPoint(vp
);
4985 unsigned char StanceID
= GetStance();
4986 unsigned char Face
= GetNextFace();
4987 Animation
** anims
= ca
->GetAnimation( StanceID
, Face
);
4989 // update bounding box and such
4990 int PartCount
= ca
->GetTotalPartCount();
4991 Sprite2D
* nextFrame
= 0;
4992 nextFrame
= anims
[0]->GetFrame(anims
[0]->GetCurrentFrame());
4994 //make actor unselectable and unselected when it is not moving
4995 //dead, petriefied, frozen, paralysed etc.
4997 // this 0x80 stuff was broken and is more broken now, disabled
4998 //if (Selected!=0x80) {
5000 core
->GetGame()->SelectActor(this, false, SELECT_NORMAL
);
5003 //If you find a better place for it, I'll really be glad to put it there
5004 //IN BG1 and BG2, this is at the ninth frame...
5005 if(attackProjectile
&& (anims
[0]->GetCurrentFrame() == 8/*anims[0]->GetFramesCount()/2*/)) {
5006 GetCurrentArea()->AddProjectile(attackProjectile
, Pos
, LastTarget
);
5007 attackProjectile
= NULL
;
5009 if (nextFrame
&& lastFrame
!= nextFrame
) {
5011 if (PartCount
== 1) {
5012 newBBox
.x
= cx
- nextFrame
->XPos
;
5013 newBBox
.w
= nextFrame
->Width
;
5014 newBBox
.y
= cy
- nextFrame
->YPos
;
5015 newBBox
.h
= nextFrame
->Height
;
5017 // FIXME: currently using the animarea instead
5018 // of the real bounding box of this (multi-part) frame.
5019 // Shouldn't matter much, though. (wjp)
5020 newBBox
.x
= cx
+ anims
[0]->animArea
.x
;
5021 newBBox
.y
= cy
+ anims
[0]->animArea
.y
;
5022 newBBox
.w
= anims
[0]->animArea
.w
;
5023 newBBox
.h
= anims
[0]->animArea
.h
;
5025 lastFrame
= nextFrame
;
5029 // Drawing the actor:
5031 // Drawn without transparency, unless fully invisible.
5032 // Order: W, E, N, S, NW, SE, NE, SW
5033 // Uses extraCovers 3-10
5034 // * blurred copies (3 of them)
5035 // Drawn with transparency.
5036 // distance between copies depends on IE_MOVEMENTRATE
5037 // TODO: actually, the direction is the real movement direction,
5038 // not the (rounded) direction given Face
5039 // Uses extraCovers 0-2
5041 // Uses main spritecover
5043 //comments by Avenger:
5044 // currently we don't have a real direction, but the orientation field
5045 // could be used with higher granularity. When we need the face value
5046 // it could be divided so it will become a 0-15 number.
5049 SpriteCover
*sc
= 0, *newsc
= 0;
5052 int blurdx
= (OrientdX
[Face
]*(int)Modified
[IE_MOVEMENTRATE
])/20;
5053 int blurdy
= (OrientdY
[Face
]*(int)Modified
[IE_MOVEMENTRATE
])/20;
5054 Color mirrortint
= tint
;
5055 //mirror images are also half transparent when invis
5056 //if (mirrortint.a > 0) mirrortint.a = 255;
5060 // mirror images behind the actor
5061 for (i
= 0; i
< 4; ++i
) {
5062 unsigned int m
= MirrorImageZOrder
[i
];
5063 if (m
< Modified
[IE_MIRRORIMAGES
]) {
5064 Region sbbox
= BBox
;
5065 int dir
= MirrorImageLocation
[m
];
5066 int icx
= cx
+ 3*OrientdX
[dir
];
5067 int icy
= cy
+ 3*OrientdY
[dir
];
5068 Point
iPos(icx
, icy
);
5069 if (area
->GetBlocked(iPos
) & (PATH_MAP_PASSABLE
|PATH_MAP_ACTOR
)) {
5070 sbbox
.x
+= 3*OrientdX
[dir
];
5071 sbbox
.y
+= 3*OrientdY
[dir
];
5072 newsc
= sc
= extraCovers
[3+m
];
5073 DrawActorSprite(screen
, icx
, icy
, sbbox
, newsc
,
5074 anims
, Face
, mirrortint
);
5077 extraCovers
[3+m
] = newsc
;
5081 delete extraCovers
[3+m
];
5082 extraCovers
[3+m
] = NULL
;
5086 // blur sprites behind the actor
5087 if (State
& STATE_BLUR
) {
5088 if (Face
< 4 || Face
>= 12) {
5089 Region sbbox
= BBox
;
5090 sbbox
.x
-= 4*blurdx
; sbbox
.y
-= 4*blurdy
;
5091 blurx
-= 4*blurdx
; blury
-= 4*blurdy
;
5092 for (i
= 0; i
< 3; ++i
) {
5093 sbbox
.x
+= blurdx
; sbbox
.y
+= blurdy
;
5094 blurx
+= blurdx
; blury
+= blurdy
;
5095 newsc
= sc
= extraCovers
[i
];
5096 DrawActorSprite(screen
, blurx
, blury
, sbbox
, newsc
,
5100 extraCovers
[i
] = newsc
;
5107 if (!(State
&(STATE_DEAD
|STATE_FROZEN
|STATE_PETRIFIED
) ) &&
5108 (area
->GetLightLevel(Pos
)<128) &&
5109 core
->GetGame()->PartyHasInfravision()) {
5114 newsc
= sc
= GetSpriteCover();
5115 DrawActorSprite(screen
, cx
, cy
, BBox
, newsc
, anims
, Face
, tint
);
5116 if (newsc
!= sc
) SetSpriteCover(newsc
);
5118 // blur sprites in front of the actor
5119 if (State
& STATE_BLUR
) {
5120 if (Face
>= 4 && Face
< 12) {
5121 Region sbbox
= BBox
;
5122 for (i
= 0; i
< 3; ++i
) {
5123 sbbox
.x
-= blurdx
; sbbox
.y
-= blurdy
;
5124 blurx
-= blurdx
; blury
-= blurdy
;
5125 newsc
= sc
= extraCovers
[i
];
5126 DrawActorSprite(screen
, blurx
, blury
, sbbox
, newsc
,
5130 extraCovers
[i
] = newsc
;
5136 // mirror images in front of the actor
5137 for (i
= 4; i
< 8; ++i
) {
5138 unsigned int m
= MirrorImageZOrder
[i
];
5139 if (m
< Modified
[IE_MIRRORIMAGES
]) {
5140 Region sbbox
= BBox
;
5141 int dir
= MirrorImageLocation
[m
];
5142 int icx
= cx
+ 3*OrientdX
[dir
];
5143 int icy
= cy
+ 3*OrientdY
[dir
];
5144 Point
iPos(icx
, icy
);
5145 if (area
->GetBlocked(iPos
) & (PATH_MAP_PASSABLE
|PATH_MAP_ACTOR
)) {
5146 sbbox
.x
+= 3*OrientdX
[dir
];
5147 sbbox
.y
+= 3*OrientdY
[dir
];
5148 newsc
= sc
= extraCovers
[3+m
];
5149 DrawActorSprite(screen
, icx
, icy
, sbbox
, newsc
,
5150 anims
, Face
, mirrortint
);
5153 extraCovers
[3+m
] = newsc
;
5157 delete extraCovers
[3+m
];
5158 extraCovers
[3+m
] = NULL
;
5162 // advance animations one frame (in sync)
5164 anims
[0]->LastFrame();
5166 anims
[0]->NextFrame();
5168 for (int part
= 1; part
< PartCount
; ++part
) {
5170 anims
[part
]->GetSyncedNextFrame(anims
[0]);
5173 if (anims
[0]->endReached
) {
5174 if (HandleActorStance() ) {
5175 anims
[0]->endReached
= false;
5176 anims
[0]->SetPos(0);
5180 ca
->PulseRGBModifiers();
5183 //draw videocells over the actor
5184 DrawVideocells(screen
, vvcOverlays
, tint
);
5187 /* Handling automatic stance changes */
5188 bool Actor::HandleActorStance()
5190 CharAnimations
* ca
= GetAnims();
5191 int StanceID
= GetStance();
5193 if (ca
->autoSwitchOnEnd
) {
5194 int nextstance
= ca
->nextStanceID
;
5195 SetStance( nextstance
);
5196 ca
->autoSwitchOnEnd
= false;
5199 int x
= rand()%1000;
5200 if ((StanceID
==IE_ANI_AWAKE
) && !x
) {
5201 SetStance( IE_ANI_HEAD_TURN
);
5204 // added CurrentAction as part of blocking action fixes
5205 if ((StanceID
==IE_ANI_READY
) && !CurrentAction
&& !GetNextAction()) {
5206 SetStance( IE_ANI_AWAKE
);
5209 if (StanceID
== IE_ANI_ATTACK
|| StanceID
== IE_ANI_ATTACK_JAB
||
5210 StanceID
== IE_ANI_ATTACK_SLASH
|| StanceID
== IE_ANI_ATTACK_BACKSLASH
||
5211 StanceID
== IE_ANI_SHOOT
)
5213 SetStance( AttackStance
);
5219 void Actor::GetSoundFrom2DA(ieResRef Sound
, unsigned int index
)
5223 AutoTable
tab(anims
->ResRef
);
5238 index
= 36+rand()%4;
5241 strnlwrcpy(Sound
, tab
->QueryField (index
, 0), 8);
5244 void Actor::GetSoundFromINI(ieResRef Sound
, unsigned int index
)
5246 const char *resource
= "";
5248 unsigned int animid
=BaseStats
[IE_ANIMATION_ID
];
5249 if(core
->HasFeature(GF_ONE_BYTE_ANIMID
)) {
5252 snprintf(section
,10,"%d", animid
);
5256 resource
= core
->GetResDataINI()->GetKeyAsString(section
, "at1sound","");
5259 resource
= core
->GetResDataINI()->GetKeyAsString(section
, "hitsound","");
5262 resource
= core
->GetResDataINI()->GetKeyAsString(section
, "dfbsound","");
5267 int count
= CountElements(resource
,',');
5268 if (count
<=0) return;
5269 count
= core
->Roll(1,count
,-1);
5271 while(*resource
&& *resource
!=',') resource
++;
5272 if (*resource
==',') resource
++;
5274 strncpy(Sound
, resource
, 8);
5275 for(count
=0;count
<8 && Sound
[count
]!=',';count
++) {};
5279 void Actor::ResolveStringConstant(ieResRef Sound
, unsigned int index
)
5281 if (PCStats
&& PCStats
->SoundSet
[0]) {
5282 //resolving soundset (bg1/bg2 style)
5283 if (csound
[index
]) {
5284 snprintf(Sound
, sizeof(ieResRef
), "%s%c", PCStats
->SoundSet
, csound
[index
]);
5288 snprintf(Sound
, sizeof(ieResRef
), "%s%02d", PCStats
->SoundSet
, index
);
5294 if (core
->HasFeature(GF_RESDATA_INI
)) {
5295 GetSoundFromINI(Sound
, index
);
5297 GetSoundFrom2DA(Sound
, index
);
5301 void Actor::SetActionButtonRow(ActionButtonRow
&ar
)
5303 for(int i
=0;i
<MAX_QSLOTS
;i
++) {
5304 ieByte tmp
= ar
[i
+3];
5305 if (QslotTranslation
) {
5308 PCStats
->QSlots
[i
]=tmp
;
5312 //the first 3 buttons are untouched by this function
5313 void Actor::GetActionButtonRow(ActionButtonRow
&ar
)
5315 InitButtons(GetStat(IE_CLASS
), false);
5316 for(int i
=0;i
<GUIBT_COUNT
-3;i
++) {
5317 ieByte tmp
=PCStats
->QSlots
[i
];
5318 if (QslotTranslation
) {
5319 if (tmp
>=90) { //quick weapons
5321 } else if (tmp
>=80) { //quick items
5323 } else if (tmp
>=70) { //quick spells
5331 memcpy(ar
,DefaultButtons
,3*sizeof(ieByte
) );
5334 void Actor::SetPortrait(const char* ResRef
, int Which
)
5338 if (ResRef
== NULL
) {
5342 core
->SetEventFlag(EF_PORTRAIT
);
5346 memset( SmallPortrait
, 0, 8 );
5347 strncpy( SmallPortrait
, ResRef
, 8 );
5350 memset( LargePortrait
, 0, 8 );
5351 strncpy( LargePortrait
, ResRef
, 8 );
5354 for (i
= 0; i
< 8 && ResRef
[i
]; i
++) {};
5355 SmallPortrait
[i
] = 'S';
5356 LargePortrait
[i
] = 'M';
5360 void Actor::SetSoundFolder(const char *soundset
)
5362 if (core
->HasFeature(GF_SOUNDFOLDERS
)) {
5363 char filepath
[_MAX_PATH
];
5365 strnlwrcpy(PCStats
->SoundFolder
, soundset
, 32);
5366 PathJoin(filepath
,core
->GamePath
,"sounds",PCStats
->SoundFolder
,0);
5367 char file
[_MAX_PATH
];
5368 if (FileGlob(file
, filepath
, "?????01")) {
5370 } else if (FileGlob(file
, filepath
, "????01")) {
5375 strnlwrcpy(PCStats
->SoundSet
, file
, 8);
5377 strnlwrcpy(PCStats
->SoundSet
, soundset
, 8);
5378 PCStats
->SoundFolder
[0]=0;
5382 void Actor::GetSoundFolder(char *soundset
) const
5384 if (core
->HasFeature(GF_SOUNDFOLDERS
)) {
5385 strnlwrcpy(soundset
, PCStats
->SoundFolder
, 32);
5388 strnlwrcpy(soundset
, PCStats
->SoundSet
, 8);
5392 bool Actor::HasVVCCell(const ieResRef resource
)
5394 return GetVVCCell(resource
) != NULL
;
5397 ScriptedAnimation
*Actor::GetVVCCell(const ieResRef resource
)
5400 vvcVector
*vvcCells
=&vvcShields
;
5402 size_t i
=vvcCells
->size();
5404 ScriptedAnimation
*vvc
= (*vvcCells
)[i
];
5408 if ( strnicmp(vvc
->ResName
, resource
, 8) == 0) {
5412 vvcCells
=&vvcOverlays
;
5413 if (j
) { j
= false; goto retry
; }
5417 void Actor::RemoveVVCell(const ieResRef resource
, bool graceful
)
5420 vvcVector
*vvcCells
=&vvcShields
;
5422 size_t i
=vvcCells
->size();
5424 ScriptedAnimation
*vvc
= (*vvcCells
)[i
];
5428 if ( strnicmp(vvc
->ResName
, resource
, 8) == 0) {
5430 vvc
->SetPhase(P_RELEASE
);
5433 vvcCells
->erase(vvcCells
->begin()+i
);
5437 vvcCells
=&vvcOverlays
;
5438 if (j
) { j
= false; goto retry
; }
5441 //this is a faster version of hasvvccell, because it knows where to look
5442 //for the overlay, it also returns the vvc for further manipulation
5443 //use this for the seven eyes overlay
5444 ScriptedAnimation
*Actor::FindOverlay(int index
)
5446 vvcVector
*vvcCells
;
5448 if (index
>31) return NULL
;
5450 if (hc_locations
&(1<<index
)) vvcCells
=&vvcShields
;
5451 else vvcCells
=&vvcOverlays
;
5453 const char *resRef
= hc_overlays
[index
];
5455 size_t i
=vvcCells
->size();
5457 ScriptedAnimation
*vvc
= (*vvcCells
)[i
];
5461 if ( strnicmp(vvc
->ResName
, resRef
, 8) == 0) {
5468 void Actor::AddVVCell(ScriptedAnimation
* vvc
)
5470 vvcVector
*vvcCells
;
5472 //if the vvc was not created, don't try to add it
5477 vvcCells
=&vvcShields
;
5479 vvcCells
=&vvcOverlays
;
5481 size_t i
=vvcCells
->size();
5483 if ((*vvcCells
)[i
] == NULL
) {
5484 (*vvcCells
)[i
] = vvc
;
5488 vvcCells
->push_back( vvc
);
5491 //returns restored spell level
5492 int Actor::RestoreSpellLevel(ieDword maxlevel
, ieDword type
)
5497 case 0: //allow only mage
5500 case 1: //allow only cleric
5504 //allow any (including innates)
5507 for (int i
=maxlevel
;i
>0;i
--) {
5508 CREMemorizedSpell
*cms
= spellbook
.FindUnchargedSpell(typemask
, maxlevel
);
5510 spellbook
.ChargeSpell(cms
);
5517 //replenishes spells, cures fatigue
5518 void Actor::Rest(int hours
)
5522 int remaining
= hours
*10;
5523 //removes hours*10 fatigue points
5524 NewStat (IE_FATIGUE
, -remaining
, MOD_ADDITIVE
);
5525 NewStat (IE_INTOXICATION
, -remaining
, MOD_ADDITIVE
);
5526 //restore hours*10 spell levels
5527 //rememorization starts with the lower spell levels?
5528 inventory
.ChargeAllItems (remaining
);
5529 for (int level
= 1; level
<16; level
++) {
5530 if (level
<remaining
) {
5533 while (remaining
>0) {
5534 remaining
-= RestoreSpellLevel(level
,0);
5538 SetBase (IE_FATIGUE
, 0);
5539 SetBase (IE_INTOXICATION
, 0);
5540 inventory
.ChargeAllItems (0);
5541 spellbook
.ChargeAllSpells ();
5545 //returns the actual slot from the quickslot
5546 int Actor::GetQuickSlot(int slot
)
5549 if (inventory
.HasItemInSlot("",inventory
.GetMagicSlot())) {
5550 return inventory
.GetMagicSlot();
5553 return slot
+inventory
.GetWeaponSlot();
5555 return PCStats
->QuickWeaponSlots
[slot
];
5558 //marks the quickslot as equipped
5559 int Actor::SetEquippedQuickSlot(int slot
, int header
)
5562 if (header
<0) header
=0;
5563 inventory
.SetEquippedSlot(slot
, header
);
5568 if ((slot
<0) || (slot
== IW_NO_EQUIPPED
) ) {
5569 if (slot
== IW_NO_EQUIPPED
) {
5570 slot
= inventory
.GetFistSlot();
5573 for(i
=0;i
<MAX_QUICKWEAPONSLOT
;i
++) {
5574 if(slot
+inventory
.GetWeaponSlot()==PCStats
->QuickWeaponSlots
[i
]) {
5579 if (i
==MAX_QUICKWEAPONSLOT
) {
5584 assert(slot
<MAX_QUICKWEAPONSLOT
);
5586 header
= PCStats
->QuickWeaponHeaders
[slot
];
5589 PCStats
->QuickWeaponHeaders
[slot
]=header
;
5591 slot
= PCStats
->QuickWeaponSlots
[slot
]-inventory
.GetWeaponSlot();
5592 Equipped
= (ieWordSigned
) slot
;
5593 EquippedHeader
= (ieWord
) header
;
5594 if (inventory
.SetEquippedSlot(slot
, header
)) {
5597 return STR_MAGICWEAPON
;
5600 //if target is a non living scriptable, then we simply shoot for its position
5601 //the fx should get a NULL target, and handle itself by using the position
5602 //(shouldn't crash when target is NULL)
5603 bool Actor::UseItemPoint(ieDword slot
, ieDword header
, const Point
&target
, ieDword flags
)
5605 CREItem
*item
= inventory
.GetSlotItem(slot
);
5610 strnuprcpy(tmpresref
, item
->ItemResRef
, sizeof(ieResRef
)-1);
5612 Item
*itm
= gamedata
->GetItem(tmpresref
);
5613 if (!itm
) return false; //quick item slot contains invalid item resref
5614 //item is depleted for today
5615 if(itm
->UseCharge(item
->Usages
, header
, false)==CHG_DAY
) {
5619 Projectile
*pro
= itm
->GetProjectile(slot
, header
, flags
&UI_MISS
);
5620 ChargeItem(slot
, header
, item
, itm
, flags
&UI_SILENT
);
5621 gamedata
->FreeItem(itm
,tmpresref
, false);
5623 pro
->SetCaster(globalID
);
5624 GetCurrentArea()->AddProjectile(pro
, Pos
, target
);
5630 static EffectRef fx_damage_ref
={"Damage",NULL
,-1};
5632 bool Actor::UseItem(ieDword slot
, ieDword header
, Scriptable
* target
, ieDword flags
, int damage
)
5634 if (target
->Type
!=ST_ACTOR
) {
5635 return UseItemPoint(slot
, header
, target
->Pos
, flags
);
5638 Actor
*tar
= (Actor
*) target
;
5639 CREItem
*item
= inventory
.GetSlotItem(slot
);
5644 strnuprcpy(tmpresref
, item
->ItemResRef
, sizeof(ieResRef
)-1);
5646 Item
*itm
= gamedata
->GetItem(tmpresref
);
5647 if (!itm
) return false; //quick item slot contains invalid item resref
5648 //item is depleted for today
5649 if (itm
->UseCharge(item
->Usages
, header
, false)==CHG_DAY
) {
5653 Projectile
*pro
= itm
->GetProjectile(slot
, header
, flags
&UI_MISS
);
5654 ChargeItem(slot
, header
, item
, itm
, flags
&UI_SILENT
);
5655 gamedata
->FreeItem(itm
,tmpresref
, false);
5657 //ieDword is unsigned!!
5658 pro
->SetCaster(globalID
);
5659 if(((int)header
< 0) && !(flags
&UI_MISS
)) { //using a weapon
5660 ITMExtHeader
*which
= itm
->GetWeaponHeader(header
== (ieDword
)-2);
5661 Effect
* AttackEffect
= EffectQueue::CreateEffect(fx_damage_ref
, damage
, (weapon_damagetype
[which
->DamageType
])<<16, FX_DURATION_INSTANT_LIMITED
);
5662 AttackEffect
->Projectile
= which
->ProjectileAnimation
;
5663 AttackEffect
->Target
= FX_TARGET_PRESET
;
5664 pro
->GetEffects()->AddEffect(AttackEffect
, true);
5665 //AddEffect created a copy, the original needs to be scrapped
5666 delete AttackEffect
;
5667 attackProjectile
= pro
;
5668 } else //launch it now as we are not attacking
5669 GetCurrentArea()->AddProjectile(pro
, Pos
, tar
->globalID
);
5675 void Actor::ChargeItem(ieDword slot
, ieDword header
, CREItem
*item
, Item
*itm
, bool silent
)
5678 item
= inventory
.GetSlotItem(slot
);
5681 itm
= gamedata
->GetItem(item
->ItemResRef
);
5683 if (!itm
) return; //quick item slot contains invalid item resref
5686 core
->SetEventFlag( EF_ACTION
);
5690 ieByte stance
= AttackStance
;
5691 for (int i
=0;i
<animcount
;i
++) {
5692 if ( strnicmp(item
->ItemResRef
, itemanim
[i
].itemname
, 8) == 0) {
5693 stance
= itemanim
[i
].animation
;
5698 //play only one cycle of animations
5700 // this was crashing for fuzzie due to NULL anims
5702 anims
->nextStanceID
=IE_ANI_READY
;
5703 anims
->autoSwitchOnEnd
=true;
5708 switch(itm
->UseCharge(item
->Usages
, header
, true)) {
5711 case CHG_BREAK
: //both
5713 core
->PlaySound(DS_ITEM_GONE
);
5716 case CHG_NOSOUND
: //remove item
5717 inventory
.BreakItemSlot(slot
);
5719 default: //don't do anything
5724 int Actor::IsReverseToHit()
5726 return ReverseToHit
;
5729 void Actor::InitButtons(ieDword cls
, bool forced
)
5734 if ( (PCStats
->QSlots
[0]!=0xff) && !forced
) {
5738 ActionButtonRow myrow
;
5739 if ((int) cls
>= classcount
) {
5740 memcpy(&myrow
, &DefaultButtons
, sizeof(ActionButtonRow
));
5741 for (int i
=0;i
<extraslots
;i
++) {
5742 if (cls
==OtherGUIButtons
[i
].clss
) {
5743 memcpy(&myrow
, &OtherGUIButtons
[i
].buttons
, sizeof(ActionButtonRow
));
5748 memcpy(&myrow
, GUIBTDefaults
+cls
, sizeof(ActionButtonRow
));
5750 SetActionButtonRow(myrow
);
5753 void Actor::SetFeat(unsigned int feat
, int mode
)
5755 if (feat
>=MAX_FEATS
) {
5758 ieDword mask
= 1<<(feat
&31);
5759 ieDword idx
= feat
>>5;
5761 case BM_SET
: case BM_OR
:
5762 BaseStats
[IE_FEATS1
+idx
]|=mask
;
5765 BaseStats
[IE_FEATS1
+idx
]&=~mask
;
5768 BaseStats
[IE_FEATS1
+idx
]^=mask
;
5773 void Actor::SetUsedWeapon(const char* AnimationType
, ieWord
* MeleeAnimation
, int wt
)
5775 memcpy(WeaponRef
, AnimationType
, sizeof(WeaponRef
) );
5776 if (wt
!= -1) WeaponType
= wt
;
5779 anims
->SetWeaponRef(AnimationType
);
5780 anims
->SetWeaponType(WeaponType
);
5781 SetAttackMoveChances(MeleeAnimation
);
5783 //update the paperdoll weapon animation
5784 core
->SetEventFlag(EF_UPDATEANIM
);
5787 ITMExtHeader
*header
= GetWeapon(wi
);
5789 if(header
&& (header
->AttackType
== ITEM_AT_BOW
)) {
5790 ITMExtHeader
* projHeader
= GetRangedWeapon(wi
);
5791 if (projHeader
->ProjectileQualifier
== 0) return; /* no ammo yet? */
5792 AttackStance
= IE_ANI_SHOOT
;
5793 anims
->SetRangedType(projHeader
->ProjectileQualifier
-1);
5794 //bows ARE one handed, from an anim POV at least
5795 anims
->SetWeaponType(IE_ANI_WEAPON_1H
);
5798 if(header
&& (header
->AttackType
== ITEM_AT_PROJECTILE
)) {
5799 AttackStance
= IE_ANI_ATTACK_SLASH
; //That's it!!
5802 AttackStance
= IE_ANI_ATTACK
;
5805 void Actor::SetUsedShield(const char* AnimationType
, int wt
)
5807 memcpy(ShieldRef
, AnimationType
, sizeof(ShieldRef
) );
5808 if (wt
!= -1) WeaponType
= wt
;
5809 if (AnimationType
[0] == ' ' || AnimationType
[0] == 0)
5810 if (WeaponType
== IE_ANI_WEAPON_2W
)
5811 WeaponType
= IE_ANI_WEAPON_1H
;
5815 anims
->SetOffhandRef(AnimationType
);
5816 anims
->SetWeaponType(WeaponType
);
5818 //update the paperdoll weapon animation
5819 core
->SetEventFlag(EF_UPDATEANIM
);
5823 void Actor::SetUsedHelmet(const char* AnimationType
)
5825 memcpy(HelmetRef
, AnimationType
, sizeof(HelmetRef
) );
5828 anims
->SetHelmetRef(AnimationType
);
5830 //update the paperdoll weapon animation
5831 core
->SetEventFlag(EF_UPDATEANIM
);
5835 void Actor::SetupFist()
5837 int slot
= core
->QuerySlot( 0 );
5838 assert (core
->QuerySlotEffects(slot
)==SLOT_EFFECT_FIST
);
5839 int row
= GetBase(fiststat
);
5840 int col
= GetXPLevel(false);
5844 AutoTable
fist("fistweap");
5847 strnlwrcpy( DefaultFist
, fist
->QueryField( (unsigned int) -1), 8);
5848 FistRows
= fist
->GetRowCount();
5849 fistres
= new FistResType
[FistRows
];
5850 for (int i
=0;i
<FistRows
;i
++) {
5851 int maxcol
= fist
->GetColumnCount(i
)-1;
5852 for (int cols
= 0;cols
<MAX_LEVEL
;cols
++) {
5853 strnlwrcpy( fistres
[i
][cols
], fist
->QueryField( i
, cols
>maxcol
?maxcol
:cols
), 8);
5855 *(int *) fistres
[i
] = atoi(fist
->GetRowName( i
));
5859 if (col
>MAX_LEVEL
) col
=MAX_LEVEL
;
5862 const char *ItemResRef
= DefaultFist
;
5863 for (int i
= 0;i
<FistRows
;i
++) {
5864 if (*(int *) fistres
[i
] == row
) {
5865 ItemResRef
= fistres
[i
][col
];
5868 inventory
.SetSlotItemRes(ItemResRef
, slot
);
5871 static ieDword
ResolveTableValue(const char *resref
, ieDword stat
, ieDword mcol
, ieDword vcol
) {
5873 //don't close this table, it can mess with the guiscripts
5874 int table
= gamedata
->LoadTable(resref
);
5875 Holder
<TableMgr
> tm
= gamedata
->GetTable(table
);
5881 row
= tm
->FindTableValue(mcol
, stat
);
5882 if (row
==0xffffffff) {
5886 if (valid_number(tm
->QueryField(row
, vcol
), ret
)) {
5887 return (ieDword
) ret
;
5894 int Actor::CheckUsability(Item
*item
) const
5896 ieDword itembits
[2]={item
->UsabilityBitmask
, item
->KitUsability
};
5898 for (int i
=0;i
<usecount
;i
++) {
5899 ieDword itemvalue
= itembits
[itemuse
[i
].which
];
5900 ieDword stat
= GetStat(itemuse
[i
].stat
);
5901 ieDword mcol
= itemuse
[i
].mcol
;
5902 //if we have a kit, we just we use it's index for the lookup
5903 if (itemuse
[i
].stat
==IE_KIT
) {
5904 stat
= GetKitIndex(stat
, itemuse
[i
].table
);
5907 stat
= ResolveTableValue(itemuse
[i
].table
, stat
, mcol
, itemuse
[i
].vcol
);
5908 if (stat
&itemvalue
) {
5909 //printf("failed usability: itemvalue %d, stat %d, stat value %d\n", itemvalue, itemuse[i].stat, stat);
5910 return STR_CANNOT_USE_ITEM
;
5917 static EffectRef fx_cant_use_item_ref
={"CantUseItem",NULL
,-1};
5918 static EffectRef fx_cant_use_item_type_ref
={"CantUseItemType",NULL
,-1};
5920 //this one is the same, but returns strrefs based on effects
5921 ieStrRef
Actor::Disabled(ieResRef name
, ieDword type
) const
5925 fx
= fxqueue
.HasEffectWithResource(fx_cant_use_item_ref
, name
);
5927 return fx
->Parameter1
;
5930 fx
= fxqueue
.HasEffectWithParam(fx_cant_use_item_type_ref
, type
);
5932 return fx
->Parameter1
;
5937 //checks usability only
5938 int Actor::Unusable(Item
*item
) const
5940 if (!GetStat(IE_CANUSEANYITEM
)) {
5941 int unusable
= CheckUsability(item
);
5946 // iesdp says this is always checked?
5947 if (item
->MinLevel
>GetXPLevel(true)) {
5948 return STR_CANNOT_USE_ITEM
;
5951 if (!CheckAbilities
) {
5955 if (item
->MinStrength
>GetStat(IE_STR
)) {
5956 return STR_CANNOT_USE_ITEM
;
5959 if (item
->MinStrength
==18) {
5960 if (GetStat(IE_STR
)==18) {
5961 if (item
->MinStrengthBonus
>GetStat(IE_STREXTRA
)) {
5962 return STR_CANNOT_USE_ITEM
;
5967 if (item
->MinIntelligence
>GetStat(IE_INT
)) {
5968 return STR_CANNOT_USE_ITEM
;
5970 if (item
->MinDexterity
>GetStat(IE_DEX
)) {
5971 return STR_CANNOT_USE_ITEM
;
5973 if (item
->MinWisdom
>GetStat(IE_WIS
)) {
5974 return STR_CANNOT_USE_ITEM
;
5976 if (item
->MinConstitution
>GetStat(IE_CON
)) {
5977 return STR_CANNOT_USE_ITEM
;
5979 if (item
->MinCharisma
>GetStat(IE_CHR
)) {
5980 return STR_CANNOT_USE_ITEM
;
5982 //note, weapon proficiencies shouldn't be checked here
5983 //missing proficiency causes only attack penalty
5987 //full palette will be shaded in gradient color
5988 void Actor::SetGradient(ieDword gradient
)
5990 gradient
|= (gradient
<<16);
5991 gradient
|= (gradient
<<8);
5992 for(int i
=0;i
<7;i
++) {
5993 Modified
[IE_COLORS
+i
]=gradient
;
5997 //sets one bit of the sanctuary stat (used for overlays)
5998 void Actor::SetOverlay(unsigned int overlay
)
6000 if (overlay
>=32) return;
6001 Modified
[IE_SANCTUARY
]|=1<<overlay
;
6004 //returns true if spell state is already set or illegal
6005 bool Actor::SetSpellState(unsigned int spellstate
)
6007 if (spellstate
>=192) return true;
6008 unsigned int pos
= IE_SPLSTATE_ID1
+(spellstate
>>5);
6009 unsigned int bit
= 1<<(spellstate
&31);
6010 if (Modified
[pos
]&bit
) return true;
6015 //returns true if spell state is already set
6016 bool Actor::HasSpellState(unsigned int spellstate
)
6018 if (spellstate
>=192) return false;
6019 unsigned int pos
= IE_SPLSTATE_ID1
+(spellstate
>>5);
6020 unsigned int bit
= 1<<(spellstate
&31);
6021 if (Modified
[pos
]&bit
) return true;
6025 //returns the numeric value of a feat, different from HasFeat
6026 //for multiple feats
6027 int Actor::GetFeat(unsigned int feat
) const
6029 if (feat
>=MAX_FEATS
) {
6032 if (Modified
[IE_FEATS1
+(feat
>>5)]&(1<<(feat
&31)) ) {
6033 //return the numeric stat value, instead of the boolean
6034 if (featstats
[feat
]) {
6035 return Modified
[featstats
[feat
]];
6042 //returns true if the feat exists
6043 bool Actor::HasFeat(unsigned int featindex
) const
6045 if (featindex
>=MAX_FEATS
) return false;
6046 unsigned int pos
= IE_FEATS1
+(featindex
>>5);
6047 unsigned int bit
= 1<<(featindex
&31);
6048 if (Modified
[pos
]&bit
) return true;
6052 ieDword
Actor::ImmuneToProjectile(ieDword projectile
) const
6056 idx
= projectile
/32;
6057 if (idx
>ProjectileSize
) {
6060 return projectileImmunity
[idx
]&(1<<(projectile
&31));
6063 void Actor::AddProjectileImmunity(ieDword projectile
)
6065 projectileImmunity
[projectile
/32]|=1<<(projectile
&31);
6069 void Actor::CreateDerivedStatsBG()
6071 int turnundeadlevel
= 0;
6072 int classid
= BaseStats
[IE_CLASS
];
6074 //this works only for PC classes
6075 if (classid
>=CLASS_PCCUTOFF
) return;
6077 //recalculate all level based changes
6078 pcf_level(this,0,0);
6080 //even though the original didn't allow a cleric/paladin dual or multiclass
6081 //we shouldn't restrict the possibility by using "else if" here
6082 if (isclass
[ISCLERIC
]&(1<<classid
)) {
6083 turnundeadlevel
+= GetClericLevel()+1-turnlevels
[classid
];
6084 if (turnundeadlevel
<0) turnundeadlevel
=0;
6086 if (isclass
[ISPALADIN
]&(1<<classid
)) {
6087 turnundeadlevel
+= GetPaladinLevel()+1-turnlevels
[classid
];
6088 if (turnundeadlevel
<0) turnundeadlevel
=0;
6091 // barbarian immunity to backstab was hardcoded
6092 if (GetBarbarianLevel()) {
6093 BaseStats
[IE_DISABLEBACKSTAB
] = 1;
6096 ieDword backstabdamagemultiplier
=GetThiefLevel();
6097 if (backstabdamagemultiplier
) {
6098 // HACK: swashbucklers can't backstab
6099 if ((BaseStats
[IE_KIT
]&0xfff) == 12) {
6100 backstabdamagemultiplier
= 1;
6102 AutoTable
tm("backstab");
6103 //fallback to a general algorithm (bg2 backstab.2da version) if we can't find backstab.2da
6104 //TODO: AP_SPCL332 (increase backstab by one) seems to not be effecting this at all
6105 //for assassins perhaps the effect is being called prior to this, and this overwrites it;
6106 //stalkers work correctly, which is even more odd, considering as they use the same
6107 //effect and backstabmultiplier would be 0 for them
6109 ieDword cols
= tm
->GetColumnCount();
6110 if (backstabdamagemultiplier
>= cols
) backstabdamagemultiplier
= cols
;
6111 backstabdamagemultiplier
= atoi(tm
->QueryField(0, backstabdamagemultiplier
));
6113 backstabdamagemultiplier
= (backstabdamagemultiplier
+7)/4;
6116 if (backstabdamagemultiplier
>7) backstabdamagemultiplier
=7;
6120 // monk's level dictated ac and ac vs missiles bonus
6121 // attacks per round bonus will be handled elsewhere, since it only applies to fist apr
6122 if (isclass
[ISMONK
]&(1<<classid
)) {
6123 unsigned int level
= GetMonkLevel()-1;
6124 if (level
< monkbon_cols
) {
6125 BaseStats
[IE_ARMORCLASS
] = DEFAULTAC
- monkbon
[1][level
];
6126 BaseStats
[IE_ACMISSILEMOD
] = - monkbon
[2][level
];
6130 BaseStats
[IE_TURNUNDEADLEVEL
]=turnundeadlevel
;
6131 BaseStats
[IE_BACKSTABDAMAGEMULTIPLIER
]=backstabdamagemultiplier
;
6132 BaseStats
[IE_LAYONHANDSAMOUNT
]=GetPaladinLevel()*2;
6136 void Actor::CreateDerivedStatsIWD2()
6139 int turnundeadlevel
= 0;
6141 ieDword backstabdamagemultiplier
=GetThiefLevel();
6142 if (backstabdamagemultiplier
) {
6143 AutoTable
tm("backstab");
6145 ieDword cols
= tm
->GetColumnCount();
6146 if (backstabdamagemultiplier
>= cols
) backstabdamagemultiplier
= cols
;
6147 backstabdamagemultiplier
= atoi(tm
->QueryField(0, backstabdamagemultiplier
));
6149 backstabdamagemultiplier
= (BaseStats
[IE_LEVELTHIEF
]+1)/2;
6152 if (backstabdamagemultiplier
>7) backstabdamagemultiplier
=7;
6155 int layonhandsamount
= (int) BaseStats
[IE_LEVELPALADIN
];
6156 if (layonhandsamount
) {
6157 layonhandsamount
*= BaseStats
[IE_CHR
]/2-5;
6158 if (layonhandsamount
<1) layonhandsamount
= 1;
6161 for (i
=0;i
<11;i
++) {
6164 if (turnlevels
[i
+1]) {
6165 tmp
= BaseStats
[IE_LEVELBARBARIAN
+i
]+1-turnlevels
[i
+1];
6167 if (tmp
>turnundeadlevel
) turnundeadlevel
=tmp
;
6170 BaseStats
[IE_TURNUNDEADLEVEL
]=turnundeadlevel
;
6171 BaseStats
[IE_BACKSTABDAMAGEMULTIPLIER
]=backstabdamagemultiplier
;
6172 BaseStats
[IE_LAYONHANDSAMOUNT
]=(ieDword
) layonhandsamount
;
6175 //set up stuff here, like attack number, turn undead level
6176 //and similar derived stats that change with level
6177 void Actor::CreateDerivedStats()
6179 //we have to calculate multiclass for further code
6180 AutoTable
tm("classes");
6182 // currently we need only the MULTI value
6185 strcpy(tmpmulti
, tm
->QueryField(tm
->FindTableValue(5, BaseStats
[IE_CLASS
]), 4));
6186 if (!valid_number(tmpmulti
, tmp
))
6189 multiclass
= (ieDword
)tmp
;
6192 if (core
->HasFeature(GF_3ED_RULES
)) {
6193 CreateDerivedStatsIWD2();
6195 CreateDerivedStatsBG();
6198 /* Checks if the actor is multiclassed (the MULTI column is positive) */
6199 bool Actor::IsMultiClassed() const
6201 return (multiclass
> 0);
6204 /* Checks if the actor is dualclassed */
6205 bool Actor::IsDualClassed() const
6207 return (Modified
[IE_MC_FLAGS
] & MC_WAS_ANY
) > 0;
6210 Actor
*Actor::CopySelf() const
6212 Actor
*newActor
= new Actor();
6214 newActor
->SetName(GetName(0),0);
6215 newActor
->SetName(GetName(1),1);
6216 memcpy(newActor
->BaseStats
, BaseStats
, sizeof(BaseStats
) );
6217 // illusions aren't worth any xp
6218 newActor
->BaseStats
[IE_XPVALUE
] = 0;
6220 //IF_INITIALIZED shouldn't be set here, yet
6221 newActor
->SetMCFlag(MC_EXPORTABLE
, BM_NAND
);
6223 //the creature importer does this too
6224 memcpy(newActor
->Modified
,newActor
->BaseStats
, sizeof(Modified
) );
6226 //these need to be called too to have a valid inventory
6227 newActor
->inventory
.SetSlotCount(inventory
.GetSlotCount());
6228 newActor
->CreateDerivedStats();
6230 //copy the running effects
6231 EffectQueue
*newFXQueue
= fxqueue
.CopySelf();
6233 area
->AddActor(newActor
);
6234 newActor
->SetPosition( Pos
, CC_CHECK_IMPASSABLE
, 0 );
6235 newActor
->SetOrientation(GetOrientation(),0);
6236 newActor
->SetStance( IE_ANI_READY
);
6239 newActor
->RefreshEffects(newFXQueue
);
6243 ieDword
Actor::GetClassLevel(const ieDword id
) const
6248 //return iwd2 value if appropriate
6250 return BaseStats
[levelslotsiwd2
[id
]];
6252 //houston, we gots a problem!
6253 if (!levelslots
|| !dualswap
)
6256 //only works with PC's
6257 ieDword classid
= BaseStats
[IE_CLASS
]-1;
6258 if (classid
>=(ieDword
)classcount
|| !levelslots
[classid
])
6261 //handle barbarians specially, since they're kits and not in levelslots
6262 if (id
== ISBARBARIAN
&& levelslots
[classid
][ISFIGHTER
] && GetKitIndex(BaseStats
[IE_KIT
]) == 31) {
6263 return BaseStats
[IE_LEVEL
];
6266 //get the levelid (IE_LEVEL,*2,*3)
6267 ieDword levelid
= levelslots
[classid
][id
];
6272 if (IsDualClassed()) {
6273 //if the old class is inactive, and it is the class
6274 //being searched for, return 0
6275 if (IsDualInactive() && ((Modified
[IE_MC_FLAGS
]&MC_WAS_ANY
)==(ieDword
)mcwasflags
[id
]))
6278 return BaseStats
[levelid
];
6281 bool Actor::IsDualInactive() const
6283 if (!IsDualClassed()) return 0;
6285 //we assume the old class is in IE_LEVEL2, unless swapped
6286 ieDword oldlevel
= IsDualSwap() ? BaseStats
[IE_LEVEL
] : BaseStats
[IE_LEVEL2
];
6288 //since GetXPLevel returns the average of the 2 levels, oldclasslevel will
6289 //only be less than GetXPLevel when the new class surpasses it
6290 return oldlevel
>=GetXPLevel(false);
6293 bool Actor::IsDualSwap() const
6295 //the dualswap[class-1] holds the info
6296 if (!IsDualClassed()) return false;
6297 ieDword tmpclass
= BaseStats
[IE_CLASS
]-1;
6298 if (tmpclass
>=(ieDword
)classcount
) return false;
6299 return (ieDword
)dualswap
[tmpclass
]==(Modified
[IE_MC_FLAGS
]&MC_WAS_ANY
);
6302 ieDword
Actor::GetWarriorLevel() const
6304 if (!IsWarrior()) return 0;
6306 ieDword warriorlevels
[4] = {
6307 GetBarbarianLevel(),
6313 ieDword highest
= 0;
6314 for (int i
=0; i
<4; i
++) {
6315 if (warriorlevels
[i
] > highest
) {
6316 highest
= warriorlevels
[i
];
6323 bool Actor::BlocksSearchMap() const
6325 return Modified
[IE_DONOTJUMP
] < 2;
6328 //return true if the actor doesn't want to use an entrance
6329 bool Actor::CannotPassEntrance() const
6331 if (InternalFlags
&IF_USEEXIT
) {
6337 void Actor::UseExit(int flag
) {
6339 InternalFlags
|=IF_USEEXIT
;
6341 InternalFlags
&=~IF_USEEXIT
;
6345 // luck increases the minimum roll per dice, but only up to the number of dice sides;
6346 // luck does not affect critical hit chances:
6347 // if critical is set, it will return 1/sides on a critical, otherwise it can never
6348 // return a critical miss when luck is positive and can return a false critical hit
6349 int Actor::LuckyRoll(int dice
, int size
, int add
, bool critical
, bool only_damage
, Actor
* opponent
) const
6351 assert(this != opponent
);
6355 stat
= IE_DAMAGELUCK
;
6360 int luck
= (signed) GetStat(stat
);
6361 if (opponent
) luck
-= (signed) opponent
->GetStat(stat
);
6362 if (dice
< 1 || size
< 1) {
6368 if (abs(luck
) > size
) {
6369 bonus
= luck
/abs(luck
) * size
;
6373 int roll
= core
->Roll(1, dice
*size
, 0);
6374 if (critical
&& (roll
== 1 || roll
== size
)) {
6377 return add
+ dice
* (size
+ bonus
) / 2;
6381 int roll
, result
= 0, misses
= 0, hits
= 0;
6382 for (int i
= 0; i
< dice
; i
++) {
6383 roll
= core
->Roll(1, size
, 0);
6386 } else if (roll
== size
) {
6392 } else if (roll
< 1) {
6398 // ensure we can still return a critical failure/success
6399 if (critical
&& dice
== misses
) return 1;
6400 if (critical
&& dice
== hits
) return size
;
6402 return result
+ add
;
6405 static EffectRef fx_remove_invisible_state_ref
={"ForceVisible",NULL
,-1};
6407 // removes the (normal) invisibility state
6408 void Actor::CureInvisibility()
6410 if (Modified
[IE_STATE_ID
] & (ieDword
) (STATE_INVISIBLE
)) {
6411 //SetBaseBit(IE_STATE_ID, STATE_INVISIBLE, false);
6412 //fxqueue.RemoveAllEffectsWithParam(fx_set_invisible_state_ref, 0);
6414 //this is much closer to what the original engine does here
6417 newfx
= EffectQueue::CreateEffect(fx_remove_invisible_state_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
6418 core
->ApplyEffect(newfx
, this, this);
6422 //not sure, but better than nothing
6423 if (! (Modified
[IE_STATE_ID
]& STATE_INVISIBLE
)) {
6424 InternalFlags
|=IF_BECAMEVISIBLE
;
6429 static EffectRef fx_remove_sanctuary_ref
={"Cure:Sanctuary",NULL
,-1};
6431 // removes the sanctuary effect
6432 void Actor::CureSanctuary()
6436 newfx
= EffectQueue::CreateEffect(fx_remove_sanctuary_ref
, 0, 0, FX_DURATION_INSTANT_PERMANENT
);
6437 core
->ApplyEffect(newfx
, this, this);
6442 void Actor::ResetState()
6449 // doesn't check the range, but only that the azimuth and the target
6450 // orientation match with a +/-2 allowed difference
6451 bool Actor::IsBehind(Actor
* target
)
6453 unsigned char tar_orient
= target
->GetOrientation();
6454 // computed, since we don't care where we face
6455 unsigned char my_orient
= GetOrient(target
->Pos
, Pos
);
6458 for (int i
=-2; i
<= 2; i
++) {
6460 if (diff
>= MAX_ORIENT
) diff
-= MAX_ORIENT
;
6461 if (diff
<= -1) diff
+= MAX_ORIENT
;
6462 if (diff
== (signed)tar_orient
) return true;
6467 // checks all the actor's stats to see if the target is her racial enemy
6468 bool Actor::IsRacialEnemy(Actor
* target
)
6470 if (Modified
[IE_HATEDRACE
] == target
->Modified
[IE_RACE
]) {
6472 } else if (core
->HasFeature(GF_3ED_RULES
)) {
6473 // iwd2 supports multiple racial enemies gained through level progression
6474 for (unsigned int i
=0; i
<7; i
++) {
6475 if (Modified
[IE_HATEDRACE2
+i
] == target
->Modified
[IE_RACE
]) {
6483 bool Actor::ModalSpellSkillCheck() {
6484 switch(ModalState
) {
6486 case MS_DETECTTRAPS
:
6497 static EffectRef fx_disable_button_ref
={ "DisableButton", NULL
, -1 };
6499 inline void HideFailed(Actor
* actor
)
6502 newfx
= EffectQueue::CreateEffect(fx_disable_button_ref
, 0, ACT_STEALTH
, FX_DURATION_INSTANT_LIMITED
);
6503 newfx
->Duration
= core
->Time
.round_sec
; // 90 ticks, 1 round
6504 core
->ApplyEffect(newfx
, actor
, actor
);
6508 bool Actor::TryToHide() {
6509 ieDword roll
= LuckyRoll(1, 100, 0);
6515 // check for disabled dualclassed thieves (not sure if we need it)
6517 if (Modified
[IE_DISABLEDBUTTON
] & (1<<ACT_STEALTH
)) {
6522 // check if the pc is in combat (seen / heard)
6523 Game
*game
= core
->GetGame();
6524 if (game
->PCInCombat(this)) {
6530 if (core
->HasFeature(GF_HAS_HIDE_IN_SHADOWS
)) {
6531 skill
= (GetStat(IE_HIDEINSHADOWS
) + GetStat(IE_STEALTH
))/2;
6533 skill
= GetStat(IE_STEALTH
);
6536 // check how bright our spot is
6537 ieDword lightness
= game
->GetCurrentArea()->GetLightLevel(Pos
);
6538 // seems to be the color overlay at midnight; lightness of a point with rgb (200, 100, 100)
6539 // TODO: but our NightTint computes to a higher value, which one is bad?
6540 ieDword light_diff
= int((lightness
- ref_lightness
) * 100 / (100 - ref_lightness
)) / 2;
6541 ieDword chance
= (100 - light_diff
) * skill
/100;
6543 if (roll
> chance
) {
6550 // only works with masks; use direct comparison for specific alignment checks
6551 bool Actor::MatchesAlignmentMask(ieDword mask
)
6553 ieDword stat
= Modified
[IE_ALIGNMENT
];
6557 return stat
== AL_LAWFUL_EVIL
|| stat
== AL_NEUTRAL_EVIL
|| stat
== AL_CHAOTIC_EVIL
;
6559 return stat
== AL_NEUTRAL_GOOD
|| stat
== AL_TRUE_NEUTRAL
|| stat
== AL_NEUTRAL_EVIL
;
6561 return stat
== AL_LAWFUL_GOOD
|| stat
== AL_NEUTRAL_GOOD
|| stat
== AL_CHAOTIC_GOOD
;
6563 return stat
== AL_CHAOTIC_GOOD
|| stat
== AL_CHAOTIC_NEUTRAL
|| stat
== AL_CHAOTIC_EVIL
;
6565 return stat
== AL_NEUTRAL_GOOD
|| stat
== AL_TRUE_NEUTRAL
|| stat
== AL_NEUTRAL_EVIL
;
6567 return stat
== AL_LAWFUL_GOOD
|| stat
== AL_LAWFUL_NEUTRAL
|| stat
== AL_LAWFUL_EVIL
;
6569 printf("Bad mask parameter (%d) used with Actor::MatchesAlignmentMask!\n", mask
);
6576 bool Actor::InvalidSpellTarget()
6578 if (GetStat(IE_STATE_ID
) & (STATE_DEAD
)) return true;
6579 if (HasSpellState(SS_SANCTUARY
)) return true;
6583 bool Actor::InvalidSpellTarget(int spellnum
, Actor
*caster
, int range
)
6587 //TODO: spell specific state checks
6588 if (!range
) return false;
6590 ResolveSpellName(spellres
, spellnum
);
6591 Spell
*spl
= gamedata
->GetSpell(spellres
);
6592 int srange
= spl
->GetCastingDistance(caster
);
6594 return srange
<range
;
6597 bool Actor::PCInDark() const
6599 if (!this) return false;
6600 unsigned int level
= area
->GetLightLevel(Pos
);
6601 if (level
<ref_lightness
) {
6607 int Actor::GetClassMask()
6610 for (int i
=0; i
< ISCLASSES
; i
++) {
6611 if (Modified
[levelslotsiwd2
[i
]] > 0) {
6612 classmask
|= 1<<(classesiwd2
[i
]-1);