Sort include order.
[gemrb.git] / gemrb / core / Actor.cpp
blob976df7a8648e08d22c8f0bc3c84ad3fd014bd91c
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 "Actor.h"
27 #include "overlays.h"
28 #include "strrefs.h"
29 #include "win32def.h"
31 #include "Audio.h" //pst (react to death sounds)
32 #include "GSUtils.h" //needed for DisplayStringCore
33 #include "Game.h"
34 #include "GameControl.h" //checking for dialog
35 #include "GameData.h"
36 #include "GameScript.h"
37 #include "Interface.h"
38 #include "Item.h"
39 #include "Projectile.h"
40 #include "ProjectileServer.h"
41 #include "ScriptEngine.h"
42 #include "Spell.h"
43 #include "TableMgr.h"
44 #include "Video.h"
45 #include "damages.h"
47 #include <cassert>
49 static const Color green = {
50 0x00, 0xff, 0x00, 0xff
52 static const Color red = {
53 0xff, 0x00, 0x00, 0xff
55 static const Color yellow = {
56 0xff, 0xff, 0x00, 0xff
58 static const Color cyan = {
59 0x00, 0xff, 0xff, 0xff
61 static const Color magenta = {
62 0xff, 0x00, 0xff, 0xff
65 static int sharexp = SX_DIVIDE;
66 static int classcount = -1;
67 static char **clericspelltables = NULL;
68 static char **druidspelltables = NULL;
69 static char **wizardspelltables = NULL;
70 static int *turnlevels = NULL;
71 static int *booktypes = NULL;
72 static int *xpbonus = NULL;
73 static int xpbonustypes = -1;
74 static int xpbonuslevels = -1;
75 static int **levelslots = NULL;
76 static int *dualswap = NULL;
77 static int *maxhpconbon = NULL;
78 //static ieVariable IWDDeathVarFormat = "KILL_%s_CNT";
79 //static ieVariable DeathVarFormat = "SPRITE_IS_DEAD%s";
80 static ieVariable CounterNames[4]={"GOOD","LAW","LADY","MURDER"};
82 static int FistRows = -1;
83 int *wmlevels[20];
84 typedef ieResRef FistResType[MAX_LEVEL+1];
86 static FistResType *fistres = NULL;
87 static ieResRef DefaultFist = {"FIST"};
89 //item usability array
90 struct ItemUseType {
91 ieResRef table; //which table contains the stat usability flags
92 ieByte stat; //which actor stat we talk about
93 ieByte mcol; //which column should be matched against the stat
94 ieByte vcol; //which column has the bit value for it
95 ieByte which; //which item dword should be used (1 = kit)
98 static ItemUseType *itemuse = NULL;
99 static int usecount = -1;
101 //item animation override array
102 struct ItemAnimType {
103 ieResRef itemname;
104 ieByte animation;
107 static ItemAnimType *itemanim = NULL;
108 static int animcount = -1;
110 static int fiststat = IE_CLASS;
112 //conversion for 3rd ed
113 static int isclass[11]={0,0,0,0,0,0,0,0,0,0,0};
115 static const int mcwasflags[11] = {
116 MC_WAS_FIGHTER, MC_WAS_MAGE, MC_WAS_THIEF, 0, 0, MC_WAS_CLERIC,
117 MC_WAS_DRUID, 0, 0, MC_WAS_RANGER, 0};
118 static const char *isclassnames[11] = {
119 "FIGHTER", "MAGE", "THIEF", "BARBARIAN", "BARD", "CLERIC",
120 "DRUID", "MONK", "PALADIN", "RANGER", "SORCERER" };
121 static const int levelslotsiwd2[11]={IE_LEVELFIGHTER,IE_LEVELMAGE,IE_LEVELTHIEF,
122 IE_LEVELBARBARIAN,IE_LEVELBARD,IE_LEVELCLERIC,IE_LEVELDRUID,IE_LEVELMONK,
123 IE_LEVELPALADIN,IE_LEVELRANGER,IE_LEVELSORCEROR};
125 //stat values are 0-255, so a byte is enough
126 static ieByte featstats[MAX_FEATS]={0
129 //holds the wspecial table for weapon prof bonuses
130 #define WSPECIAL_COLS 3
131 static int wspecial_max = 0;
132 static int wspattack_rows = 0;
133 static int wspattack_cols = 0;
134 static int **wspecial = NULL;
135 static int **wspattack = NULL;
137 //holds the weapon style bonuses
138 #define STYLE_MAX 3
139 static int **wsdualwield = NULL;
140 static int **wstwohanded = NULL;
141 static int **wsswordshield = NULL;
142 static int **wssingle = NULL;
144 //unhardcoded monk bonuses
145 static int **monkbon = NULL;
146 static int monkbon_cols = 0;
147 static int monkbon_rows = 0;
149 // reputation modifiers
150 static int **reputationmod = NULL;
151 #define CLASS_PCCUTOFF 32
152 #define CLASS_INNOCENT 155
153 #define CLASS_FLAMINGFIST 156
155 static ActionButtonRow *GUIBTDefaults = NULL; //qslots row count
156 ActionButtonRow DefaultButtons = {ACT_TALK, ACT_WEAPON1, ACT_WEAPON2,
157 ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE,
158 ACT_NONE, ACT_INNATE};
159 static int QslotTranslation = false;
160 static int DeathOnZeroStat = true;
161 static ieDword TranslucentShadows = 0;
162 static int ProjectileSize = 0; //the size of the projectile immunity bitfield (dwords)
164 static const char iwd2gemrb[32] = {
165 0,0,20,2,22,25,0,14,
166 15,23,13,0,1,24,8,21,
167 0,0,0,0,0,0,0,0,
168 0,0,0,0,0,0,0,0
170 static const char gemrb2iwd[32] = {
171 11,12,3,71,72,73,0,0, //0
172 14,80,83,82,81,10,7,8, //8
173 0,0,0,0,2,15,4,9, //16
174 13,5,0,0,0,0,0,0 //24
177 //letters for char sound resolution bg1/bg2
178 static char csound[VCONST_COUNT];
180 static void InitActorTables();
182 #define DAMAGE_LEVELS 19
183 #define ATTACKROLL 20
184 #define SAVEROLL 20
185 #define DEFAULTAC 10
187 static ieResRef d_main[DAMAGE_LEVELS] = {
188 //slot 0 is not used in the original engine
189 "BLOODCR","BLOODS","BLOODM","BLOODL", //blood
190 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //fire
191 "SPSHKIMP","SPSHKIMP","SPSHKIMP", //spark
192 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //ice
193 "SHACID","SHACID","SHACID", //acid
194 "SPDUSTY2","SPDUSTY2","SPDUSTY2" //disintegrate
196 static ieResRef d_splash[DAMAGE_LEVELS] = {
197 "","","","",
198 "SPBURN","SPBURN","SPBURN", //flames
199 "SPSPARKS","SPSPARKS","SPSPARKS", //sparks
200 "","","",
201 "","","",
202 "","",""
205 #define BLOOD_GRADIENT 19
206 #define FIRE_GRADIENT 19
207 #define ICE_GRADIENT 71
208 #define STONE_GRADIENT 93
210 static int d_gradient[DAMAGE_LEVELS] = {
211 BLOOD_GRADIENT,BLOOD_GRADIENT,BLOOD_GRADIENT,BLOOD_GRADIENT,
212 FIRE_GRADIENT,FIRE_GRADIENT,FIRE_GRADIENT,
213 -1,-1,-1,
214 ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,
215 -1,-1,-1,
216 -1,-1,-1
219 static ieResRef hc_overlays[OVERLAY_COUNT]={"SANCTRY","SPENTACI","SPMAGGLO","SPSHIELD",
220 "GREASED","WEBENTD","MINORGLB","","","","","","","","","","","","","","",
221 "","","","SPTURNI2","SPTURNI","","","","","",""};
222 static ieDword hc_locations=0x2ba80030;
224 static int *mxsplwis = NULL;
225 static int spllevels;
227 //for every game except IWD2 we need to reverse TOHIT
228 static int ReverseToHit=true;
229 static int CheckAbilities=false;
231 //internal flags for calculating to hit
232 #define WEAPON_FIST 0
233 #define WEAPON_MELEE 1
234 #define WEAPON_RANGED 2
235 #define WEAPON_STYLEMASK 15
236 #define WEAPON_LEFTHAND 16
237 #define WEAPON_USESTRENGTH 32
239 /* counts the on bits in a number */
240 ieDword bitcount (ieDword n)
242 ieDword count=0;
243 while (n) {
244 count += n & 0x1u;
245 n >>= 1;
247 return count;
250 void ReleaseMemoryActor()
252 if (mxsplwis) {
253 //calloc'd x*y integer matrix
254 free (mxsplwis);
255 mxsplwis = NULL;
258 if (fistres) {
259 delete [] fistres;
260 fistres = NULL;
263 if (itemuse) {
264 delete [] itemuse;
265 itemuse = NULL;
268 if (itemanim) {
269 delete [] itemanim;
270 itemanim = NULL;
272 FistRows = -1;
275 Actor::Actor()
276 : Movable( ST_ACTOR )
278 int i;
280 for (i = 0; i < MAX_STATS; i++) {
281 BaseStats[i] = 0;
282 Modified[i] = 0;
285 SmallPortrait[0] = 0;
286 LargePortrait[0] = 0;
288 anims = NULL;
289 ShieldRef[0]=0;
290 HelmetRef[0]=0;
291 WeaponRef[0]=0;
292 for (i = 0; i < EXTRA_ACTORCOVERS; ++i)
293 extraCovers[i] = NULL;
295 LongName = NULL;
296 ShortName = NULL;
297 LongStrRef = (ieStrRef) -1;
298 ShortStrRef = (ieStrRef) -1;
300 LastProtected = 0;
301 LastFollowed = 0;
302 LastCommander = 0;
303 LastHelp = 0;
304 LastSeen = 0;
305 LastMarked = 0;
306 LastHeard = 0;
307 PCStats = NULL;
308 LastCommand = 0; //used by order
309 LastShout = 0; //used by heard
310 LastDamage = 0;
311 LastDamageType = 0;
312 HotKey = 0;
313 attackcount = 0;
314 attacksperround = 0;
315 nextattack = 0;
316 InTrap = 0;
317 PathTries = 0;
318 TargetDoor = NULL;
319 attackProjectile = NULL;
320 lastInit = 0;
321 roundTime = 0;
322 lastattack = 0;
324 inventory.SetInventoryType(INVENTORY_CREATURE);
325 Equipped = 0;
326 EquippedHeader = 0;
328 fxqueue.SetOwner( this );
329 inventory.SetOwner( this );
330 if (classcount<0) {
331 //This block is executed only once, when the first actor is loaded
332 InitActorTables();
334 TranslucentShadows = 0;
335 core->GetDictionary()->Lookup("Translucent Shadows", TranslucentShadows);
336 //get the needed size to store projectile immunity bitflags in Dwords
337 ProjectileSize = (core->GetProjectileServer()->GetHighestProjectileNumber()+31)/32;
338 //allowing 1024 bits (1024 projectiles ought to be enough for everybody)
339 //the rest of the projectiles would still work, but couldn't be resisted
340 if (ProjectileSize>32) {
341 ProjectileSize=32;
344 projectileImmunity = (ieDword *) calloc(ProjectileSize,sizeof(ieDword));
345 AppearanceFlags = 0;
346 SetDeathVar = IncKillCount = 0;
347 InParty = 0;
348 TalkCount = 0;
349 InteractCount = 0; //numtimesinteracted depends on this
350 appearance = 0xffffff; //might be important for created creatures
351 RemovalTime = ~0;
352 version = 0;
353 //these are used only in iwd2 so we have to default them
354 for(i=0;i<7;i++) {
355 BaseStats[IE_HATEDRACE2+i]=0xff;
357 //this one is saved only for PC's
358 ModalState = 0;
359 //set it to a neutral value
360 ModalSpell[0] = '*';
361 //this one is saved, but not loaded?
362 localID = globalID = 0;
363 //this one is not saved
364 GotLUFeedback = false;
367 Actor::~Actor(void)
369 unsigned int i;
371 delete anims;
373 core->FreeString( LongName );
374 core->FreeString( ShortName );
376 delete PCStats;
378 for (i = 0; i < vvcOverlays.size(); i++) {
379 if (vvcOverlays[i]) {
380 delete vvcOverlays[i];
381 vvcOverlays[i] = NULL;
384 for (i = 0; i < vvcShields.size(); i++) {
385 if (vvcShields[i]) {
386 delete vvcShields[i];
387 vvcShields[i] = NULL;
390 for (i = 0; i < EXTRA_ACTORCOVERS; i++)
391 delete extraCovers[i];
393 delete attackProjectile;
395 free(projectileImmunity);
398 void Actor::SetFistStat(ieDword stat)
400 fiststat = stat;
403 void Actor::SetDefaultActions(int qslot, ieByte slot1, ieByte slot2, ieByte slot3)
405 QslotTranslation=qslot;
406 DefaultButtons[0]=slot1;
407 DefaultButtons[1]=slot2;
408 DefaultButtons[2]=slot3;
411 void Actor::SetName(const char* ptr, unsigned char type)
413 size_t len = strlen( ptr ) + 1;
414 //32 is the maximum possible length of the actor name in the original games
415 if (len>32) len=33;
416 if (type!=2) {
417 LongName = ( char * ) realloc( LongName, len );
418 memcpy( LongName, ptr, len );
419 core->StripLine( LongName, len );
421 if (type!=1) {
422 ShortName = ( char * ) realloc( ShortName, len );
423 memcpy( ShortName, ptr, len );
424 core->StripLine( ShortName, len );
428 void Actor::SetName(int strref, unsigned char type)
430 if (type!=2) {
431 if (LongName) free(LongName);
432 LongName = core->GetString( strref, IE_STR_REMOVE_NEWLINE );
434 if (type!=1) {
435 if (ShortName) free(ShortName);
436 ShortName = core->GetString( strref, IE_STR_REMOVE_NEWLINE );
440 void Actor::SetAnimationID(unsigned int AnimID)
442 //if the palette is locked, then it will be transferred to the new animation
443 Palette *recover = NULL;
445 if (anims) {
446 if (anims->lockPalette) {
447 recover = anims->palette[PAL_MAIN];
449 // Take ownership so the palette won't be deleted
450 if (recover) {
451 recover->IncRef();
453 delete( anims );
455 //hacking PST no palette
456 if (core->HasFeature(GF_ONE_BYTE_ANIMID) ) {
457 if ((AnimID&0xf000)==0xe000) {
458 if (BaseStats[IE_COLORCOUNT]) {
459 printMessage("Actor"," ",YELLOW);
460 printf("Animation ID %x is supposed to be real colored (no recoloring), patched creature\n", AnimID);
462 BaseStats[IE_COLORCOUNT]=0;
465 anims = new CharAnimations( AnimID&0xffff, BaseStats[IE_ARMOR_TYPE]);
466 if(anims->ResRef[0] == 0) {
467 delete anims;
468 anims = NULL;
469 printMessage("Actor", " ",LIGHT_RED);
470 printf("Missing animation for %s\n",LongName);
471 return;
473 anims->SetOffhandRef(ShieldRef);
474 anims->SetHelmetRef(HelmetRef);
475 anims->SetWeaponRef(WeaponRef);
477 //if we have a recovery palette, then set it back
478 assert(anims->palette[PAL_MAIN] == 0);
479 anims->palette[PAL_MAIN] = recover;
480 if (recover) {
481 anims->lockPalette = true;
483 //bird animations are not hindered by searchmap
484 //only animtype==7 (bird) uses this feature
485 //this is a hardcoded hack, but works for all engine type
486 if (anims->GetAnimType()!=IE_ANI_BIRD) {
487 BaseStats[IE_DONOTJUMP]=0;
488 } else {
489 BaseStats[IE_DONOTJUMP]=DNJ_BIRD;
491 SetCircleSize();
492 anims->SetColors(BaseStats+IE_COLORS);
495 CharAnimations* Actor::GetAnims()
497 return anims;
500 /** Returns a Stat value (Base Value + Mod) */
501 ieDword Actor::GetStat(unsigned int StatIndex) const
503 if (StatIndex >= MAX_STATS) {
504 return 0xdadadada;
506 return Modified[StatIndex];
509 void Actor::SetCircleSize()
511 const Color *color;
512 int color_index;
514 if (!anims)
515 return;
517 if (UnselectableTimer) {
518 color = &magenta;
519 color_index = 4;
520 } else if (Modified[IE_STATE_ID] & STATE_PANIC) {
521 color = &yellow;
522 color_index = 5;
523 } else {
524 switch (Modified[IE_EA]) {
525 case EA_PC:
526 case EA_FAMILIAR:
527 case EA_ALLY:
528 case EA_CONTROLLED:
529 case EA_CHARMED:
530 case EA_EVILBUTGREEN:
531 case EA_GOODCUTOFF:
532 color = &green;
533 color_index = 0;
534 break;
536 case EA_ENEMY:
537 case EA_GOODBUTRED:
538 case EA_EVILCUTOFF:
539 color = &red;
540 color_index = 1;
541 break;
542 default:
543 color = &cyan;
544 color_index = 2;
545 break;
549 int csize = anims->GetCircleSize() - 1;
550 if (csize >= MAX_CIRCLE_SIZE)
551 csize = MAX_CIRCLE_SIZE - 1;
553 SetCircle( anims->GetCircleSize(), *color, core->GroundCircles[csize][color_index], core->GroundCircles[csize][(color_index == 0) ? 3 : color_index] );
556 void ApplyClab(Actor *actor, const char *clab, int level)
558 AutoTable table(clab);
559 if (table) {
560 int row = table->GetRowCount();
561 for(int i=0;i<level;i++) {
562 for (int j=0;j<row;j++) {
563 const char *res = table->QueryField(j,i);
564 if (!memcmp(res,"AP_",3)) {
565 core->ApplySpell(res+2, actor, actor, 0);
567 else if (!memcmp(res,"GA_",3)) {
568 actor->LearnSpell(res+2, 0);
570 else if (!memcmp(res,"FA_",3)) {//iwd2 only
571 int x=atoi(res+3);
572 core->DisplayStringName(x,0xffffff,actor,0);
574 else if (!memcmp(res,"FS_",3)) {//iwd2? (song?)
575 int x=atoi(res+3);
576 core->DisplayStringName(x,0xffffff,actor,0);
578 else if (!memcmp(res,"RA_",3)) {//iwd2
579 int x=atoi(res+3);
580 core->DisplayStringName(x,0xffffff,actor,0);
587 #define BG2_KITMASK 0xffffc000
588 #define KIT_BARBARIAN 0x4000
589 #define KIT_BASECLASS 0x40000000
591 //applies a kit on the character (only bg2)
592 bool Actor::ApplyKit(ieDword Value)
594 //get current unmodified level (i guess)
595 int level = GetXPLevel(false);
596 AutoTable table("kitlist");
597 if (table) {
598 ieDword row;
599 //find row by unusability
600 row = table->GetRowCount();
601 while (row) {
602 row--;
603 ieDword Unusability = (ieDword) strtol(table->QueryField(row, 6),NULL,0);
604 if (Value == Unusability) {
605 goto found_row;
608 //if it wasn't found, try the bg2 kit format
609 if ((Value&BG2_KITMASK)==KIT_BARBARIAN) {
610 row = (Value<<16);
612 //cannot find kit
613 if (table->GetRowCount()>=row) {
614 return false;
616 found_row:
617 ieDword cls = (ieDword) atoi(table->QueryField(row, 7));
618 if (cls!=BaseStats[IE_CLASS]) {
619 //cannot apply kit, because the class doesn't fit
620 return false;
622 const char *clab = table->QueryField(row, 4);
623 ApplyClab(this, clab, level);
625 return true;
628 //call this when morale or moralebreak changed
629 //cannot use old or new value, because it is called two ways
630 void pcf_morale (Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
632 if ((actor->Modified[IE_MORALE]<=actor->Modified[IE_MORALEBREAK]) && (actor->Modified[IE_MORALEBREAK] != 0) ) {
633 actor->Panic();
635 //for new colour
636 actor->SetCircleSize();
639 void pcf_ea (Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
641 if (actor->InParty) core->GetGame()->SelectActor(actor, false, SELECT_NORMAL);
642 actor->SetCircleSize();
645 //this is a good place to recalculate level up stuff
646 void pcf_level (Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
648 ieDword sum =
649 actor->GetFighterLevel()+
650 actor->GetMageLevel()+
651 actor->GetThiefLevel()+
652 actor->GetBarbarianLevel()+
653 actor->GetBardLevel()+
654 actor->GetClericLevel()+
655 actor->GetDruidLevel()+
656 actor->GetMonkLevel()+
657 actor->GetPaladinLevel()+
658 actor->GetRangerLevel()+
659 actor->GetSorcererLevel();
660 actor->SetBase(IE_CLASSLEVELSUM,sum);
661 actor->SetupFist();
662 actor->GotLUFeedback = false;
665 void pcf_class (Actor *actor, ieDword /*oldValue*/, ieDword newValue)
667 actor->InitButtons(newValue, false);
669 int sorcerer=0;
670 if (newValue<(ieDword) classcount) {
671 switch(booktypes[newValue]) {
672 case 2: sorcerer = 1<<IE_SPELL_TYPE_WIZARD; break;
673 case 3: sorcerer = 1<<IE_SPELL_TYPE_PRIEST; break;
674 default: break;
677 actor->spellbook.SetBookType(sorcerer);
680 void pcf_animid(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
682 actor->SetAnimationID(newValue);
685 static const ieDword fullwhite[7]={ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT};
687 static const ieDword fullstone[7]={STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT};
689 void pcf_state(Actor *actor, ieDword /*oldValue*/, ieDword State)
691 if (State & STATE_PETRIFIED) {
692 actor->SetLockedPalette(fullstone);
693 return;
695 if (State & STATE_FROZEN) {
696 actor->SetLockedPalette(fullwhite);
697 return;
699 if (actor->InParty) core->SetEventFlag(EF_PORTRAIT);
700 actor->UnlockPalette();
703 //changes based on extended state bits, right now it is only the seven eyes
704 //animation (used in how/iwd2)
705 void pcf_extstate(Actor *actor, ieDword oldValue, ieDword State)
707 if ((oldValue^State)&EXTSTATE_SEVEN_EYES) {
708 ieDword mask = EXTSTATE_EYE_MIND;
709 int eyeCount = 7;
710 for (int i=0;i<7;i++)
712 if (State&mask) eyeCount--;
713 mask<<=1;
715 ScriptedAnimation *sca = actor->FindOverlay(OV_SEVENEYES);
716 if (sca) {
717 sca->SetOrientation(eyeCount);
719 sca = actor->FindOverlay(OV_SEVENEYES2);
720 if (sca) {
721 sca->SetOrientation(eyeCount);
726 void pcf_hitpoint(Actor *actor, ieDword /*oldValue*/, ieDword hp)
728 if ((signed) actor->BaseStats[IE_HITPOINTS]>(signed) actor->Modified[IE_MAXHITPOINTS]) {
729 actor->BaseStats[IE_HITPOINTS]=actor->Modified[IE_MAXHITPOINTS];
732 int hptmp = (signed) actor->Modified[IE_MAXHITPOINTS];
733 if ((signed) hp>hptmp) {
734 hp=hptmp;
737 hptmp = (signed) actor->Modified[IE_MINHITPOINTS];
738 if (hptmp && (signed) hp<hptmp) {
739 hp=hptmp;
741 if ((signed) hp<=0) {
742 actor->Die(NULL);
744 actor->BaseStats[IE_HITPOINTS]=hp;
745 actor->Modified[IE_HITPOINTS]=hp;
746 if (actor->InParty) core->SetEventFlag(EF_PORTRAIT);
749 void pcf_maxhitpoint(Actor *actor, ieDword /*oldValue*/, ieDword hp)
751 if ((signed) hp<(signed) actor->BaseStats[IE_HITPOINTS]) {
752 actor->BaseStats[IE_HITPOINTS]=hp;
753 //passing 0 because it is ignored anyway
754 pcf_hitpoint(actor, 0, hp);
758 void pcf_minhitpoint(Actor *actor, ieDword /*oldValue*/, ieDword hp)
760 if ((signed) hp>(signed) actor->BaseStats[IE_HITPOINTS]) {
761 actor->BaseStats[IE_HITPOINTS]=hp;
762 //passing 0 because it is ignored anyway
763 pcf_hitpoint(actor, 0, hp);
767 void pcf_stat(Actor *actor, ieDword newValue, ieDword stat)
769 if ((signed) newValue<=0) {
770 if (DeathOnZeroStat) {
771 actor->Die(NULL);
772 } else {
773 actor->Modified[stat]=1;
778 void pcf_stat_str(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
780 pcf_stat(actor, newValue, IE_STR);
783 void pcf_stat_int(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
785 pcf_stat(actor, newValue, IE_INT);
788 void pcf_stat_wis(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
790 pcf_stat(actor, newValue, IE_WIS);
793 void pcf_stat_dex(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
795 pcf_stat(actor, newValue, IE_DEX);
798 void pcf_stat_con(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
800 pcf_stat(actor, newValue, IE_CON);
801 pcf_hitpoint(actor, 0, actor->BaseStats[IE_HITPOINTS]);
804 void pcf_stat_cha(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
806 pcf_stat(actor, newValue, IE_CHR);
809 void pcf_xp(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
811 // check if we reached a new level
812 unsigned int pc = actor->InParty;
813 if (pc && !actor->GotLUFeedback) {
814 char varname[16];
815 sprintf(varname, "CheckLevelUp%d", pc);
816 core->GetGUIScriptEngine()->RunFunction("CheckLevelUp", true, pc);
817 ieDword NeedsLevelUp = 0;
818 core->GetDictionary()->Lookup(varname, NeedsLevelUp);
819 if (NeedsLevelUp == 1) {
820 core->DisplayConstantStringName(STR_LEVELUP, 0xffffff, actor);
821 actor->GotLUFeedback = true;
826 void pcf_gold(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
828 //this function will make a party member automatically donate their
829 //gold to the party pool, not the same as in the original engine
830 if (actor->InParty) {
831 Game *game = core->GetGame();
832 game->AddGold ( actor->BaseStats[IE_GOLD] );
833 actor->BaseStats[IE_GOLD]=0;
837 static void handle_overlay(Actor *actor, ieDword idx)
839 if (actor->FindOverlay(idx))
840 return;
841 ieDword flag = hc_locations&(1<<idx);
842 ScriptedAnimation *sca = gamedata->GetScriptedAnimation(hc_overlays[idx], false);
843 if (sca) {
844 if (flag) {
845 sca->ZPos=-1;
847 actor->AddVVCell(sca);
851 //de/activates the entangle overlay
852 void pcf_entangle(Actor *actor, ieDword oldValue, ieDword newValue)
854 if (newValue&1) {
855 handle_overlay(actor, OV_ENTANGLE);
857 if (oldValue&1) {
858 actor->RemoveVVCell(hc_overlays[OV_ENTANGLE], true);
862 //de/activates the sanctuary and other overlays
863 //unlike IE, gemrb uses this stat for other overlay fields
864 //see the complete list in overlay.2da
865 //it loosely follows the internal representation of overlays in IWD2
866 void pcf_sanctuary(Actor *actor, ieDword oldValue, ieDword newValue)
868 ieDword changed = newValue^oldValue;
869 ieDword mask = 1;
870 for (int i=0;i<32;i++) {
871 if (changed&mask) {
872 if (newValue&mask) {
873 handle_overlay(actor, i);
874 } else {
875 actor->RemoveVVCell(hc_overlays[i], true);
878 mask<<=1;
882 //de/activates the prot from missiles overlay
883 void pcf_shieldglobe(Actor *actor, ieDword oldValue, ieDword newValue)
885 if (newValue&1) {
886 handle_overlay(actor, OV_SHIELDGLOBE);
887 return;
889 if (oldValue&1) {
890 actor->RemoveVVCell(hc_overlays[OV_SHIELDGLOBE], true);
894 //de/activates the globe of invul. overlay
895 void pcf_minorglobe(Actor *actor, ieDword oldValue, ieDword newValue)
897 if (newValue&1) {
898 handle_overlay(actor, OV_MINORGLOBE);
899 return;
901 if (oldValue&1) {
902 actor->RemoveVVCell(hc_overlays[OV_MINORGLOBE], true);
906 //de/activates the grease background
907 void pcf_grease(Actor *actor, ieDword oldValue, ieDword newValue)
909 if (newValue&1) {
910 handle_overlay(actor, OV_GREASE);
911 return;
913 if (oldValue&1) {
914 actor->RemoveVVCell(hc_overlays[OV_GREASE], true);
918 //de/activates the web overlay
919 //the web effect also immobilizes the actor!
920 void pcf_web(Actor *actor, ieDword oldValue, ieDword newValue)
922 if (newValue&1) {
923 handle_overlay(actor, OV_WEB);
924 return;
926 if (oldValue&1) {
927 actor->RemoveVVCell(hc_overlays[OV_WEB], true);
931 //de/activates the spell bounce background
932 void pcf_bounce(Actor *actor, ieDword oldValue, ieDword newValue)
934 if (newValue&1) {
935 handle_overlay(actor, OV_BOUNCE);
936 return;
938 if (oldValue&1) {
939 //it seems we have to remove it abruptly
940 actor->RemoveVVCell(hc_overlays[OV_BOUNCE], false);
944 //no separate values (changes are permanent)
945 void pcf_fatigue(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
947 actor->BaseStats[IE_FATIGUE]=newValue;
950 //no separate values (changes are permanent)
951 void pcf_intoxication(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
953 actor->BaseStats[IE_INTOXICATION]=newValue;
956 void pcf_color(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
958 CharAnimations *anims = actor->GetAnims();
959 if (anims) {
960 anims->SetColors(actor->Modified+IE_COLORS);
964 void pcf_armorlevel(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
966 CharAnimations *anims = actor->GetAnims();
967 if (anims) {
968 anims->SetArmourLevel(newValue);
972 static int maximum_values[MAX_STATS]={
973 32767,32767,20,100,100,100,100,25,10,25,25,25,25,25,100,100,//0f
974 100,100,100,100,100,100,100,100,100,100,255,255,255,255,100,100,//1f
975 200,200,MAX_LEVEL,255,25,100,25,25,25,25,25,999999999,999999999,999999999,25,25,//2f
976 200,255,200,100,100,200,200,25,5,100,1,1,100,1,1,0,//3f
977 511,1,1,1,MAX_LEVEL,MAX_LEVEL,1,9999,25,100,100,255,1,20,20,25,//4f
978 25,1,1,255,25,25,255,255,25,255,255,255,255,255,255,255,//5f
979 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//6f
980 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//7f
981 255,255,255,255,255,255,255,100,100,100,255,5,5,255,1,1,//8f
982 1,25,25,30,1,1,1,25,0,100,100,1,255,255,255,255,//9f
983 255,255,255,255,255,255,20,255,255,1,20,255,999999999,999999999,1,1,//af
984 999999999,999999999,0,0,20,0,0,0,0,0,0,0,0,0,0,0,//bf
985 0,0,0,0,0,0,0,25,25,255,255,255,255,65535,0,0,//cf - 207
986 0,0,0,0,0,0,0,0,MAX_LEVEL,255,65535,3,255,255,255,255,//df - 223
987 255,255,255,255,255,255,255,255,255,255,255,255,65535,65535,15,0,//ef - 239
988 MAX_LEVEL,MAX_LEVEL,MAX_LEVEL,MAX_LEVEL, MAX_LEVEL,MAX_LEVEL,MAX_LEVEL,MAX_LEVEL, //0xf7 - 247
989 MAX_LEVEL,MAX_LEVEL,0,0,0,0,0,0//ff
992 typedef void (*PostChangeFunctionType)(Actor *actor, ieDword oldValue, ieDword newValue);
993 static PostChangeFunctionType post_change_functions[MAX_STATS]={
994 pcf_hitpoint, pcf_maxhitpoint, NULL, NULL, NULL, NULL, NULL, NULL,
995 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //0f
996 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
997 NULL,NULL,NULL,NULL, NULL, NULL, pcf_fatigue, pcf_intoxication, //1f
998 NULL,NULL,pcf_level,NULL, pcf_stat_str, NULL, pcf_stat_int, pcf_stat_wis,
999 pcf_stat_dex,pcf_stat_con,pcf_stat_cha,NULL, pcf_xp, pcf_gold, pcf_morale, NULL, //2f
1000 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1001 NULL,NULL,NULL,NULL, NULL, NULL, pcf_entangle, pcf_sanctuary, //3f
1002 pcf_minorglobe, pcf_shieldglobe, pcf_grease, pcf_web, pcf_level, pcf_level, NULL, NULL,
1003 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //4f
1004 NULL,NULL,NULL,pcf_minhitpoint, NULL, NULL, NULL, NULL,
1005 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //5f
1006 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1007 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //6f
1008 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1009 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //7f
1010 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1011 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //8f
1012 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1013 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //9f
1014 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1015 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //af
1016 NULL,NULL,NULL,NULL, pcf_morale, pcf_bounce, NULL, NULL,
1017 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //bf
1018 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1019 NULL,NULL,NULL,NULL, NULL, pcf_animid,pcf_state, pcf_extstate, //cf
1020 pcf_color,pcf_color,pcf_color,pcf_color, pcf_color, pcf_color, pcf_color, NULL,
1021 NULL,NULL,NULL,pcf_armorlevel, NULL, NULL, NULL, NULL, //df
1022 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1023 pcf_class,NULL,pcf_ea,NULL, NULL, NULL, NULL, NULL, //ef
1024 pcf_level,pcf_level,pcf_level,pcf_level, pcf_level, pcf_level, pcf_level, pcf_level,
1025 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL //ff
1028 /** call this from ~Interface() */
1029 void Actor::ReleaseMemory()
1031 int i;
1033 if (classcount>=0) {
1034 if (clericspelltables) {
1035 for (i=0;i<classcount;i++) {
1036 if (clericspelltables[i]) {
1037 free (clericspelltables[i]);
1040 free(clericspelltables);
1041 clericspelltables=NULL;
1043 if (druidspelltables) {
1044 for (i=0;i<classcount;i++) {
1045 if (druidspelltables[i]) {
1046 free (druidspelltables[i]);
1049 free(druidspelltables);
1050 druidspelltables=NULL;
1052 if (wizardspelltables) {
1053 for (i=0;i<classcount;i++) {
1054 if (wizardspelltables[i]) {
1055 free(wizardspelltables[i]);
1058 free(wizardspelltables);
1059 wizardspelltables=NULL;
1061 if (turnlevels) {
1062 free(turnlevels);
1063 turnlevels=NULL;
1066 if (booktypes) {
1067 free(booktypes);
1068 booktypes=NULL;
1071 if (xpbonus) {
1072 free(xpbonus);
1073 xpbonus=NULL;
1074 xpbonuslevels = -1;
1075 xpbonustypes = -1;
1077 if (levelslots) {
1078 for (i=0; i<classcount; i++) {
1079 if (levelslots[i]) {
1080 free(levelslots[i]);
1083 free(levelslots);
1084 levelslots=NULL;
1086 if (dualswap) {
1087 free(dualswap);
1088 dualswap=NULL;
1090 if (maxhpconbon) {
1091 free(maxhpconbon);
1092 maxhpconbon=NULL;
1094 if (wspecial) {
1095 for (i=0; i<=wspecial_max; i++) {
1096 if (wspecial[i]) {
1097 free(wspecial[i]);
1100 free(wspecial);
1101 wspecial=NULL;
1103 if (wspattack) {
1104 for (i=0; i<wspattack_rows; i++) {
1105 if (wspattack[i]) {
1106 free(wspattack[i]);
1109 free(wspattack);
1110 wspattack=NULL;
1112 if (wsdualwield) {
1113 for (i=0; i<=STYLE_MAX; i++) {
1114 if (wsdualwield[i]) {
1115 free(wsdualwield[i]);
1118 free(wsdualwield);
1119 wsdualwield=NULL;
1121 if (wstwohanded) {
1122 for (i=0; i<=STYLE_MAX; i++) {
1123 if (wstwohanded[i]) {
1124 free(wstwohanded[i]);
1127 free(wstwohanded);
1128 wstwohanded=NULL;
1130 if (wsswordshield) {
1131 for (i=0; i<=STYLE_MAX; i++) {
1132 if (wsswordshield[i]) {
1133 free(wsswordshield[i]);
1136 free(wsswordshield);
1137 wsswordshield=NULL;
1139 if (wssingle) {
1140 for (i=0; i<=STYLE_MAX; i++) {
1141 if (wssingle[i]) {
1142 free(wssingle[i]);
1145 free(wssingle);
1146 wssingle=NULL;
1148 if (monkbon) {
1149 for (i=0; i<monkbon_rows; i++) {
1150 if (monkbon[i]) {
1151 free(monkbon[i]);
1154 free(monkbon);
1155 monkbon=NULL;
1157 for(i=0;i<20;i++) {
1158 free(wmlevels[i]);
1159 wmlevels[i]=NULL;
1161 if (reputationmod) {
1162 for (i=0; i<20; i++) {
1163 if (reputationmod[i]) {
1164 free(reputationmod[i]);
1167 free(reputationmod);
1168 reputationmod=NULL;
1171 if (GUIBTDefaults) {
1172 free (GUIBTDefaults);
1173 GUIBTDefaults=NULL;
1175 classcount = -1;
1178 #define COL_HATERACE 0 //ranger type racial enemy
1179 #define COL_CLERIC_SPELL 1 //cleric spells
1180 #define COL_MAGE_SPELL 2 //mage spells
1181 #define COL_STARTXP 3 //starting xp
1182 #define COL_BARD_SKILL 4 //bard skills
1183 #define COL_THIEF_SKILL 5 //thief skills
1185 #define COL_MAIN 0
1186 #define COL_SPARKS 1
1187 #define COL_GRADIENT 2
1189 /* returns the ISCLASS for the class based on name */
1190 int IsClassFromName (const char* name)
1192 //TODO: is there a better way of doing this?
1193 for (int i=0; i<ISCLASSES; i++) {
1194 if (strcmp(name, isclassnames[i]) == 0)
1195 return i;
1197 return -1;
1200 static void InitActorTables()
1202 int i, j;
1204 //if (core->HasFeature(GF_IWD_DEATHVARFORMAT)) {
1205 // memcpy(DeathVarFormat, IWDDeathVarFormat, sizeof(ieVariable));
1208 if (core->HasFeature(GF_CHALLENGERATING)) {
1209 sharexp=SX_DIVIDE|SX_CR;
1210 } else {
1211 sharexp=SX_DIVIDE;
1213 ReverseToHit = core->HasFeature(GF_REVERSE_TOHIT);
1214 CheckAbilities = core->HasFeature(GF_CHECK_ABILITIES);
1215 DeathOnZeroStat = core->HasFeature(GF_DEATH_ON_ZERO_STAT);
1217 //this table lists various level based xp bonuses
1218 AutoTable tm("xpbonus");
1219 if (tm) {
1220 xpbonustypes = tm->GetRowCount();
1221 if (xpbonustypes == 0) {
1222 xpbonuslevels = 0;
1223 } else {
1224 xpbonuslevels = tm->GetColumnCount(0);
1225 xpbonus = (int *) calloc(xpbonuslevels*xpbonustypes, sizeof(int));
1226 for (i = 0; i<xpbonustypes; i++) {
1227 for(j = 0; j<xpbonuslevels; j++) {
1228 xpbonus[i*xpbonuslevels+j] = atoi(tm->QueryField(i,j));
1232 } else {
1233 xpbonustypes = 0;
1234 xpbonuslevels = 0;
1236 //this table lists skill groups assigned to classes
1237 //it is theoretically possible to create hybrid classes
1238 tm.load("clskills");
1239 if (tm) {
1240 classcount = tm->GetRowCount();
1241 memset (isclass,0,sizeof(isclass));
1242 clericspelltables = (char **) calloc(classcount, sizeof(char*));
1243 druidspelltables = (char **) calloc(classcount, sizeof(char*));
1244 wizardspelltables = (char **) calloc(classcount, sizeof(char*));
1245 turnlevels = (int *) calloc(classcount, sizeof(int));
1246 booktypes = (int *) calloc(classcount, sizeof(int));
1248 ieDword bitmask = 1;
1250 for(i = 0; i<classcount; i++) {
1251 const char *field;
1252 int turnlevel = atoi(tm->QueryField( i, 7));
1253 turnlevels[i]=turnlevel;
1255 field = tm->QueryField( i, 0 );
1256 if (field[0]!='*') {
1257 isclass[ISDRUID] |= bitmask;
1258 druidspelltables[i]=strdup(field);
1260 field = tm->QueryField( i, 1 );
1261 if (field[0]!='*') {
1262 isclass[ISCLERIC] |= bitmask;
1263 clericspelltables[i]=strdup(field);
1266 field = tm->QueryField( i, 2 );
1267 if (field[0]!='*') {
1268 isclass[ISMAGE] |= bitmask;
1269 wizardspelltables[i]=strdup(field);
1272 // field 3 holds the starting xp
1274 field = tm->QueryField( i, 4 );
1275 if (field[0]!='*') {
1276 isclass[ISBARD] |= bitmask;
1279 field = tm->QueryField( i, 5 );
1280 if (field[0]!='*') {
1281 isclass[ISTHIEF] |= bitmask;
1284 field = tm->QueryField( i, 6 );
1285 if (field[0]!='*') {
1286 isclass[ISPALADIN] |= bitmask;
1289 // field 7 holds the turn undead level
1291 field = tm->QueryField( i, 8 );
1292 booktypes[i]=atoi(field);
1293 //if booktype == 3 then it is a 'divine sorceror' class
1294 //we shouldn't hardcode iwd2 classes this heavily
1295 if (booktypes[i]==2) {
1296 isclass[ISSORCERER] |= bitmask;
1299 field = tm->QueryField( i, 9 );
1300 if (field[0]!='*') {
1301 isclass[ISRANGER] |= bitmask;
1304 field = tm->QueryField( i, 10 );
1305 if (!strnicmp(field, "CLABMO", 6)) {
1306 isclass[ISMONK] |= bitmask;
1308 bitmask <<=1;
1310 } else {
1311 classcount = 0; //well
1314 i = core->GetMaximumAbility();
1315 maximum_values[IE_STR]=i;
1316 maximum_values[IE_INT]=i;
1317 maximum_values[IE_DEX]=i;
1318 maximum_values[IE_CON]=i;
1319 maximum_values[IE_CHR]=i;
1320 maximum_values[IE_WIS]=i;
1321 if (ReverseToHit) {
1322 //all games except iwd2
1323 maximum_values[IE_ARMORCLASS]=20;
1324 } else {
1325 //iwd2
1326 maximum_values[IE_ARMORCLASS]=199;
1329 //initializing the vvc resource references
1330 tm.load("damage");
1331 if (tm) {
1332 for (i=0;i<DAMAGE_LEVELS;i++) {
1333 const char *tmp = tm->QueryField( i, COL_MAIN );
1334 strnlwrcpy(d_main[i], tmp, 8);
1335 if (d_main[i][0]=='*') {
1336 d_main[i][0]=0;
1338 tmp = tm->QueryField( i, COL_SPARKS );
1339 strnlwrcpy(d_splash[i], tmp, 8);
1340 if (d_splash[i][0]=='*') {
1341 d_splash[i][0]=0;
1343 tmp = tm->QueryField( i, COL_GRADIENT );
1344 d_gradient[i]=atoi(tmp);
1348 tm.load("overlay");
1349 if (tm) {
1350 ieDword mask = 1;
1351 for (i=0;i<OVERLAY_COUNT;i++) {
1352 const char *tmp = tm->QueryField( i, 0 );
1353 strnlwrcpy(hc_overlays[i], tmp, 8);
1354 if (atoi(tm->QueryField( i, 1))) {
1355 hc_locations|=mask;
1357 mask<<=1;
1361 //csound for bg1/bg2
1362 memset(csound,0,sizeof(csound));
1363 if (!core->HasFeature(GF_SOUNDFOLDERS)) {
1364 tm.load("csound");
1365 if (tm) {
1366 for(i=0;i<VCONST_COUNT;i++) {
1367 const char *tmp = tm->QueryField( i, 0 );
1368 if (tmp[0]!='*') {
1369 csound[i]=tmp[0];
1375 tm.load("qslots");
1376 GUIBTDefaults = (ActionButtonRow *) calloc( classcount,sizeof(ActionButtonRow) );
1378 for (i = 0; i < classcount; i++) {
1379 memcpy(GUIBTDefaults+i, &DefaultButtons, sizeof(ActionButtonRow));
1380 if (tm) {
1381 for (int j=0;j<MAX_QSLOTS;j++) {
1382 GUIBTDefaults[i][j+3]=(ieByte) atoi( tm->QueryField(i,j) );
1387 tm.load("itemuse");
1388 if (tm) {
1389 usecount = tm->GetRowCount();
1390 itemuse = new ItemUseType[usecount];
1391 for (i = 0; i < usecount; i++) {
1392 itemuse[i].stat = (ieByte) core->TranslateStat( tm->QueryField(i,0) );
1393 strnlwrcpy(itemuse[i].table, tm->QueryField(i,1),8 );
1394 itemuse[i].mcol = (ieByte) atoi( tm->QueryField(i,2) );
1395 itemuse[i].vcol = (ieByte) atoi( tm->QueryField(i,3) );
1396 itemuse[i].which = (ieByte) atoi( tm->QueryField(i,4) );
1397 //limiting it to 0 or 1 to avoid crashes
1398 if (itemuse[i].which!=1) {
1399 itemuse[i].which=0;
1404 tm.load("itemanim");
1405 if (tm) {
1406 animcount = tm->GetRowCount();
1407 itemanim = new ItemAnimType[animcount];
1408 for (i = 0; i < animcount; i++) {
1409 strnlwrcpy(itemanim[i].itemname, tm->QueryField(i,0),8 );
1410 itemanim[i].animation = (ieByte) atoi( tm->QueryField(i,1) );
1414 tm.load("mxsplwis");
1415 if (tm) {
1416 spllevels = tm->GetColumnCount(0);
1417 int max = core->GetMaximumAbility();
1418 mxsplwis = (int *) calloc(max*spllevels, sizeof(int));
1419 for (i = 0; i < spllevels; i++) {
1420 for(int j = 0; j < max; j++) {
1421 int k = atoi(tm->GetRowName(j))-1;
1422 if (k>=0 && k<max) {
1423 mxsplwis[k*spllevels+i]=atoi(tm->QueryField(j,i));
1429 tm.load("featreq");
1430 if (tm) {
1431 unsigned int tmp;
1433 for(i=0;i<MAX_FEATS;i++) {
1434 //we need the MULTIPLE column only
1435 //it stores the FEAT_* stat index, and could be taken multiple
1436 //times
1437 tmp = core->TranslateStat(tm->QueryField(i,0));
1438 if (tmp>=MAX_STATS) {
1439 printMessage("Actor","Invalid stat value in featreq.2da",YELLOW);
1441 featstats[i] = (ieByte) tmp;
1445 //default all hp con bonuses to 9; this should be updated below
1446 //TODO: check iwd2
1447 maxhpconbon = (int *) calloc(classcount, sizeof(int));
1448 for (i = 0; i < classcount; i++) {
1449 maxhpconbon[i] = 9;
1451 tm.load("classes");
1452 if (tm && !core->HasFeature(GF_LEVELSLOT_PER_CLASS)) {
1453 AutoTable hptm;
1454 //iwd2 just uses levelslotsiwd2 instead
1455 printf("Examining classes.2da\n");
1457 //when searching the levelslots, you must search for
1458 //levelslots[BaseStats[IE_CLASS]-1] as there is no class id of 0
1459 levelslots = (int **) calloc(classcount, sizeof(int*));
1460 dualswap = (int *) calloc(classcount, sizeof(int));
1461 ieDword tmpindex;
1462 for (i=0; i<classcount; i++) {
1463 //make sure we have a valid classid, then decrement
1464 //it to get the correct array index
1465 tmpindex = atoi(tm->QueryField(i, 5));
1466 if (!tmpindex)
1467 continue;
1468 tmpindex--;
1470 printf("\tID: %d ", tmpindex);
1471 //only create the array if it isn't yet made
1472 //i.e. barbarians would overwrite fighters in bg2
1473 if (levelslots[tmpindex]) {
1474 printf ("Already Found!\n");
1475 continue;
1478 const char* classname = tm->GetRowName(i);
1479 printf("Name: %s ", classname);
1480 int classis = 0;
1481 //default all levelslots to 0
1482 levelslots[tmpindex] = (int *) calloc(ISCLASSES, sizeof(int));
1484 //single classes only worry about IE_LEVEL
1485 ieDword tmpclass = atoi(tm->QueryField(i, 4));
1486 if (!tmpclass) {
1487 classis = IsClassFromName(classname);
1488 if (classis>=0) {
1489 printf("Classis: %d ", classis);
1490 levelslots[tmpindex][classis] = IE_LEVEL;
1491 //get the max hp con bonus
1492 hptm.load(tm->QueryField(i, 6));
1493 if (hptm) {
1494 int tmphp = 0;
1495 int rollscolumn = hptm->GetColumnIndex("ROLLS");
1496 while (atoi(hptm->QueryField(tmphp, rollscolumn)))
1497 tmphp++;
1498 printf("TmpHP: %d ", tmphp);
1499 if (tmphp) maxhpconbon[tmpindex] = tmphp;
1502 continue;
1505 //we have to account for dual-swap in the multiclass field
1506 ieDword numfound = 1;
1507 ieDword tmpbits = bitcount (tmpclass);
1509 //we need all the classnames of the multi to compare with the order we load them in
1510 //because the original game set the levels based on name order, not bit order
1511 char **classnames = (char **) calloc(tmpbits, sizeof(char *));
1512 classnames[0] = (char*)strtok(strdup((char*)classname), "_");
1513 while (numfound<tmpbits && (classnames[numfound] = strdup(strtok(NULL, "_")))) {
1514 numfound++;
1516 numfound = 0;
1517 bool foundwarrior = false;
1518 for (int j=0; j<classcount; j++) {
1519 //no sense continuing if we've found all to be found
1520 if (numfound==tmpbits)
1521 break;
1522 if ((1<<j)&tmpclass) {
1523 //save the IE_LEVEL information
1524 const char* currentname = tm->GetRowName((ieDword)(tm->FindTableValue(5, j+1)));
1525 classis = IsClassFromName(currentname);
1526 if (classis>=0) {
1527 //search for the current class in the split of the names to get it's
1528 //correct order
1529 for (ieDword k=0; k<tmpbits; k++) {
1530 if (strcmp(classnames[k], currentname) == 0) {
1531 int tmplevel = 0;
1532 if (k==0) tmplevel = IE_LEVEL;
1533 else if (k==1) tmplevel = IE_LEVEL2;
1534 else tmplevel = IE_LEVEL3;
1535 levelslots[tmpindex][classis] = tmplevel;
1538 printf("Classis: %d ", classis);
1540 //warrior take presedence
1541 if (!foundwarrior) {
1542 foundwarrior = (classis==ISFIGHTER||classis==ISRANGER||classis==ISPALADIN||
1543 classis==ISBARBARIAN);
1544 hptm.load(tm->QueryField(currentname, "HP"));
1545 if (hptm) {
1546 int tmphp = 0;
1547 int rollscolumn = hptm->GetColumnIndex("ROLLS");
1548 while (atoi(hptm->QueryField(tmphp, rollscolumn)))
1549 tmphp++;
1550 //make sure we at least set the first class
1551 if ((tmphp>maxhpconbon[tmpindex])||foundwarrior||numfound==0)
1552 maxhpconbon[tmpindex]=tmphp;
1557 //save the MC_WAS_ID of the first class in the dual-class
1558 if (numfound==0 && tmpbits==2) {
1559 if (strcmp(classnames[0], currentname) == 0) {
1560 dualswap[tmpindex] = strtol(tm->QueryField(classname, "MC_WAS_ID"), NULL, 0);
1562 } else if (numfound==1 && tmpbits==2 && !dualswap[tmpindex]) {
1563 dualswap[tmpindex] = strtol(tm->QueryField(classname, "MC_WAS_ID"), NULL, 0);
1565 numfound++;
1568 if (classnames) {
1569 for (ieDword j=0; j<tmpbits; j++) {
1570 if (classnames[j]) {
1571 free(classnames[j]);
1574 free(classnames);
1575 classnames = NULL;
1577 printf("HPCON: %d ", maxhpconbon[tmpindex]);
1578 printf("DS: %d\n", dualswap[tmpindex]);
1580 /*this could be enabled to ensure all levelslots are filled with at least 0's;
1581 *however, the access code should ensure this never happens
1582 for (i=0; i<classcount; i++) {
1583 if (!levelslots[i]) {
1584 levelslots[i] = (int *) calloc(ISCLASSES, sizeof(int *));
1588 printf("Finished examining classes.2da\n");
1590 //pre-cache hit/damage/speed bonuses for weapons
1591 tm.load("wspecial");
1592 if (tm) {
1593 //load in the identifiers
1594 wspecial_max = tm->GetRowCount()-1;
1595 int cols = tm->GetColumnCount();
1596 wspecial = (int **) calloc(wspecial_max+1, sizeof(int *));
1598 for (i=0; i<=wspecial_max; i++) {
1599 wspecial[i] = (int *) calloc(WSPECIAL_COLS, sizeof(int));
1600 for (int j=0; j<cols; j++) {
1601 wspecial[i][j] = atoi(tm->QueryField(i, j));
1606 //pre-cache attack per round bonuses
1607 tm.load("wspatck");
1608 if (tm) {
1609 wspattack_rows = tm->GetRowCount();
1610 wspattack_cols = tm->GetColumnCount();
1611 wspattack = (int **) calloc(wspattack_rows, sizeof(int *));
1613 int tmp = 0;
1614 for (i=0; i<wspattack_rows; i++) {
1615 wspattack[i] = (int *) calloc(wspattack_cols, sizeof(int));
1616 for (int j=0; j<wspattack_cols; j++) {
1617 tmp = atoi(tm->QueryField(i, j));
1618 //negative values relate to x/2, so we adjust them
1619 //positive values relate to x, so we must times by 2
1620 if (tmp<0) tmp = -2*tmp-1;
1621 else tmp *= 2;
1622 wspattack[i][j] = tmp;
1627 //dual-wielding table
1628 tm.load("wstwowpn");
1629 if (tm) {
1630 wsdualwield = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1631 int cols = tm->GetColumnCount();
1632 for (i=0; i<=STYLE_MAX; i++) {
1633 wsdualwield[i] = (int *) calloc(cols, sizeof(int));
1634 for (int j=0; j<cols; j++) {
1635 wsdualwield[i][j] = atoi(tm->QueryField(i, j));
1640 //two-handed table
1641 tm.load("wstwohnd");
1642 if (tm) {
1643 wstwohanded = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1644 int cols = tm->GetColumnCount();
1645 for (i=0; i<=STYLE_MAX; i++) {
1646 wstwohanded[i] = (int *) calloc(cols, sizeof(int));
1647 for (int j=0; j<cols; j++) {
1648 wstwohanded[i][j] = atoi(tm->QueryField(i, j));
1653 //two-handed table
1654 tm.load("wsshield");
1655 if (tm) {
1656 wsswordshield = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1657 int cols = tm->GetColumnCount();
1658 for (i=0; i<=STYLE_MAX; i++) {
1659 wsswordshield[i] = (int *) calloc(cols, sizeof(int));
1660 for (int j=0; j<cols; j++) {
1661 wsswordshield[i][j] = atoi(tm->QueryField(i, j));
1666 //two-handed table
1667 tm.load("wssingle");
1668 if (tm) {
1669 wssingle = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1670 int cols = tm->GetColumnCount();
1671 for (i=0; i<=STYLE_MAX; i++) {
1672 wssingle[i] = (int *) calloc(cols, sizeof(int));
1673 for (int j=0; j<cols; j++) {
1674 wssingle[i][j] = atoi(tm->QueryField(i, j));
1679 //unhardcoded monk bonus table
1680 tm.load("monkbon");
1681 if (tm) {
1682 monkbon_rows = tm->GetRowCount();
1683 monkbon_cols = tm->GetColumnCount();
1684 monkbon = (int **) calloc(monkbon_rows, sizeof(int *));
1685 for (i=0; i<monkbon_rows; i++) {
1686 monkbon[i] = (int *) calloc(monkbon_cols, sizeof(int));
1687 for (int j=0; j<monkbon_cols; j++) {
1688 monkbon[i][j] = atoi(tm->QueryField(i, j));
1693 //wild magic level modifiers
1694 for(i=0;i<20;i++) {
1695 wmlevels[i]=(int *) calloc(MAX_LEVEL,sizeof(int) );
1697 tm.load("lvlmodwm");
1698 if (tm) {
1699 int maxrow = tm->GetRowCount();
1700 for (i=0;i<20;i++) {
1701 for(j=0;j<MAX_LEVEL;j++) {
1702 int row = maxrow;
1703 if (j<row) row=j;
1704 wmlevels[i][j]=strtol(tm->QueryField(row,i), NULL, 0);
1709 // reputation modifiers
1710 tm.load("reputati");
1711 if (tm) {
1712 reputationmod = (int **) calloc(21, sizeof(int *));
1713 int cols = tm->GetColumnCount();
1714 for (i=0; i<20; i++) {
1715 reputationmod[i] = (int *) calloc(cols, sizeof(int));
1716 for (int j=0; j<cols; j++) {
1717 reputationmod[i][j] = atoi(tm->QueryField(i, j));
1723 void Actor::SetLockedPalette(const ieDword *gradients)
1725 if (!anims) return; //cannot apply it (yet)
1726 anims->LockPalette(gradients);
1729 void Actor::UnlockPalette()
1731 if (!anims) return;
1732 anims->lockPalette=false;
1733 anims->SetColors(Modified+IE_COLORS);
1736 void Actor::AddAnimation(const ieResRef resource, int gradient, int height, int flags)
1738 ScriptedAnimation *sca = gamedata->GetScriptedAnimation(resource, false);
1739 if (!sca)
1740 return;
1741 sca->ZPos=height;
1742 if (flags&AA_PLAYONCE) {
1743 sca->PlayOnce();
1745 if (flags&&AA_BLEND) {
1746 //pst anims need this?
1747 sca->SetBlend();
1749 if (gradient!=-1) {
1750 sca->SetPalette(gradient, 4);
1752 AddVVCell(sca);
1755 void Actor::PlayDamageAnimation(int type, bool hit)
1757 int i;
1759 switch(type) {
1760 case 0: case 1: case 2: case 3: //blood
1761 i = (int) GetStat(IE_ANIMATION_ID)>>16;
1762 if (!i) i = d_gradient[type];
1763 if(hit) {
1764 AddAnimation(d_main[type], i, 0, AA_PLAYONCE);
1766 break;
1767 case 4: case 5: case 6: //fire
1768 if(hit) {
1769 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1771 for(i=DL_FIRE;i<=type;i++) {
1772 AddAnimation(d_splash[i], d_gradient[i], 0, AA_PLAYONCE);
1774 break;
1775 case 7: case 8: case 9: //electricity
1776 if (hit) {
1777 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1779 for(i=DL_ELECTRICITY;i<=type;i++) {
1780 AddAnimation(d_splash[i], d_gradient[i], 0, AA_PLAYONCE);
1782 break;
1783 case 10: case 11: case 12://cold
1784 if (hit) {
1785 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1787 break;
1788 case 13: case 14: case 15://acid
1789 if (hit) {
1790 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1792 break;
1793 case 16: case 17: case 18://disintegrate
1794 if (hit) {
1795 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1797 break;
1801 bool Actor::SetStat(unsigned int StatIndex, ieDword Value, int pcf)
1803 if (StatIndex >= MAX_STATS) {
1804 return false;
1806 if ( (signed) Value<-100) {
1807 Value = (ieDword) -100;
1809 else {
1810 if ( maximum_values[StatIndex]>0) {
1811 if ( (signed) Value>maximum_values[StatIndex]) {
1812 Value = (ieDword) maximum_values[StatIndex];
1817 unsigned int previous = Modified[StatIndex];
1818 if (Modified[StatIndex]!=Value) {
1819 Modified[StatIndex] = Value;
1820 if (pcf) {
1821 PostChangeFunctionType f = post_change_functions[StatIndex];
1822 if (f) (*f)(this, previous, Value);
1825 return true;
1828 int Actor::GetMod(unsigned int StatIndex)
1830 if (StatIndex >= MAX_STATS) {
1831 return 0xdadadada;
1833 return (signed) Modified[StatIndex] - (signed) BaseStats[StatIndex];
1835 /** Returns a Stat Base Value */
1836 ieDword Actor::GetBase(unsigned int StatIndex)
1838 if (StatIndex >= MAX_STATS) {
1839 return 0xffff;
1841 return BaseStats[StatIndex];
1844 /** Sets a Stat Base Value */
1845 /** If required, modify the modified value and run the pcf function */
1846 bool Actor::SetBase(unsigned int StatIndex, ieDword Value)
1848 if (StatIndex >= MAX_STATS) {
1849 return false;
1851 ieDword diff = Modified[StatIndex]-BaseStats[StatIndex];
1853 //maximize the base stat
1854 if ( maximum_values[StatIndex]) {
1855 if ( (signed) Value>maximum_values[StatIndex]) {
1856 Value = (ieDword) maximum_values[StatIndex];
1860 BaseStats[StatIndex] = Value;
1862 //if already initialized, then the modified stats
1863 //might need to run the post change function (stat change can kill actor)
1864 SetStat (StatIndex, Value+diff, InternalFlags&IF_INITIALIZED);
1865 return true;
1868 bool Actor::SetBaseNoPCF(unsigned int StatIndex, ieDword Value)
1870 if (StatIndex >= MAX_STATS) {
1871 return false;
1873 ieDword diff = Modified[StatIndex]-BaseStats[StatIndex];
1875 //maximize the base stat
1876 if ( maximum_values[StatIndex]) {
1877 if ( (signed) Value>maximum_values[StatIndex]) {
1878 Value = (ieDword) maximum_values[StatIndex];
1882 BaseStats[StatIndex] = Value;
1884 //if already initialized, then the modified stats
1885 //might need to run the post change function (stat change can kill actor)
1886 SetStat (StatIndex, Value+diff, 0);
1887 return true;
1890 bool Actor::SetBaseBit(unsigned int StatIndex, ieDword Value, bool setreset)
1892 if (StatIndex >= MAX_STATS) {
1893 return false;
1895 if (setreset) {
1896 BaseStats[StatIndex] |= Value;
1897 } else {
1898 BaseStats[StatIndex] &= ~Value;
1900 //if already initialized, then the modified stats
1901 //need to run the post change function (stat change can kill actor)
1902 if (setreset) {
1903 SetStat (StatIndex, Modified[StatIndex]|Value, InternalFlags&IF_INITIALIZED);
1904 } else {
1905 SetStat (StatIndex, Modified[StatIndex]&~Value, InternalFlags&IF_INITIALIZED);
1907 return true;
1910 const unsigned char *Actor::GetStateString()
1912 if (!PCStats) {
1913 return NULL;
1915 ieByte *tmp = PCStats->PortraitIconString;
1916 ieWord *Icons = PCStats->PortraitIcons;
1917 int j=0;
1918 for (int i=0;i<MAX_PORTRAIT_ICONS;i++) {
1919 if (!(Icons[i]&0xff00)) {
1920 tmp[j++]=(ieByte) ((Icons[i]&0xff)+66);
1923 tmp[j]=0;
1924 return tmp;
1927 void Actor::AddPortraitIcon(ieByte icon)
1929 if (!PCStats) {
1930 return;
1932 ieWord *Icons = PCStats->PortraitIcons;
1934 for(int i=0;i<MAX_PORTRAIT_ICONS;i++) {
1935 if (Icons[i]==0xffff) {
1936 Icons[i]=icon;
1937 return;
1939 if (icon == (Icons[i]&0xff)) {
1940 return;
1945 void Actor::DisablePortraitIcon(ieByte icon)
1947 if (!PCStats) {
1948 return;
1950 ieWord *Icons = PCStats->PortraitIcons;
1951 int i;
1953 for(i=0;i<MAX_PORTRAIT_ICONS;i++) {
1954 if (icon == (Icons[i]&0xff)) {
1955 Icons[i]=0xff00|icon;
1956 return;
1961 /** call this after load, to apply effects */
1962 void Actor::RefreshEffects(EffectQueue *fx)
1964 ieDword previous[MAX_STATS];
1966 //put all special cleanup calls here
1967 CharAnimations* anims = GetAnims();
1968 if (anims) {
1969 anims->GlobalColorMod.type = RGBModifier::NONE;
1970 anims->GlobalColorMod.speed = 0;
1971 unsigned int location;
1972 for (location = 0; location < 32; ++location) {
1973 anims->ColorMods[location].type = RGBModifier::NONE;
1974 anims->ColorMods[location].speed = 0;
1977 spellbook.ClearBonus();
1978 memset(applyWhenHittingMelee,0,sizeof(ieResRef));
1979 memset(applyWhenHittingRanged,0,sizeof(ieResRef));
1980 memset(applyWhenNearLiving,0,sizeof(ieResRef));
1981 memset(applyWhen50Damage,0,sizeof(ieResRef));
1982 memset(applyWhen90Damage,0,sizeof(ieResRef));
1983 memset(applyWhenEnemySighted,0,sizeof(ieResRef));
1984 memset(applyWhenPoisoned,0,sizeof(ieResRef));
1985 memset(applyWhenHelpless,0,sizeof(ieResRef));
1986 memset(applyWhenAttacked,0,sizeof(ieResRef));
1987 memset(applyWhenBeingHit,0,sizeof(ieResRef));
1988 memset(projectileImmunity,0,ProjectileSize*sizeof(ieDword));
1990 //initialize base stats
1991 bool first = !(InternalFlags&IF_INITIALIZED);
1993 if (first) {
1994 InternalFlags|=IF_INITIALIZED;
1995 memcpy( previous, BaseStats, MAX_STATS * sizeof( ieDword ) );
1996 } else {
1997 memcpy( previous, Modified, MAX_STATS * sizeof( ieDword ) );
1999 memcpy( Modified, BaseStats, MAX_STATS * sizeof( ieDword ) );
2000 if (PCStats) memset( PCStats->PortraitIcons, -1, sizeof(PCStats->PortraitIcons) );
2002 if (fx) {
2003 fx->SetOwner(this);
2004 fx->AddAllEffects(this, Pos);
2005 delete fx;
2006 //copy back the original stats, because the effects
2007 //will be reapplied in ApplyAllEffects again
2008 memcpy( Modified, BaseStats, MAX_STATS * sizeof( ieDword ) );
2009 //also clear the spell bonuses just given, they will be
2010 //recalculated below again
2011 spellbook.ClearBonus();
2014 fxqueue.ApplyAllEffects( this );
2016 // IE_CLASS is >classcount for non-PCs/NPCs
2017 if (BaseStats[IE_CLASS] <= (ieDword)classcount)
2018 RefreshPCStats();
2020 for (unsigned int i=0;i<MAX_STATS;i++) {
2021 if (first || Modified[i]!=previous[i]) {
2022 PostChangeFunctionType f = post_change_functions[i];
2023 if (f) {
2024 (*f)(this, previous[i], Modified[i]);
2028 //add wisdom bonus spells
2029 if (!spellbook.IsIWDSpellBook() && mxsplwis) {
2030 int level = Modified[IE_WIS];
2031 if (level--) {
2032 spellbook.BonusSpells(IE_SPELL_TYPE_PRIEST, spllevels, mxsplwis+spllevels*level);
2036 // check if any new portrait icon was removed or added
2037 if (PCStats) {
2038 if (memcmp(PCStats->PreviousPortraitIcons, PCStats->PortraitIcons, sizeof(PCStats->PreviousPortraitIcons))) {
2039 core->SetEventFlag(EF_PORTRAIT);
2040 memcpy( PCStats->PreviousPortraitIcons, PCStats->PortraitIcons, sizeof(PCStats->PreviousPortraitIcons) );
2045 // refresh stats on creatures (PC or NPC) with a valid class (not animals etc)
2046 // internal use only, and this is maybe a stupid name :)
2047 void Actor::RefreshPCStats() {
2048 //calculate hp bonus
2049 int bonus;
2050 int bonlevel = GetXPLevel(true);
2051 int oldlevel, oldbonus;
2052 oldlevel = oldbonus = 0;
2053 ieDword bonindex = BaseStats[IE_CLASS]-1;
2055 //we must limit the levels to the max allowable
2056 if (bonlevel>maxhpconbon[bonindex])
2057 bonlevel = maxhpconbon[bonindex];
2059 if (IsDualInactive()) {
2060 //we apply the inactive hp bonus if it's better than the new hp bonus, so that we
2061 //never lose hp, only gain, on leveling
2062 oldlevel = IsDualSwap() ? BaseStats[IE_LEVEL] : BaseStats[IE_LEVEL2];
2063 bonlevel = IsDualSwap() ? BaseStats[IE_LEVEL2] : BaseStats[IE_LEVEL];
2064 oldlevel = (oldlevel > maxhpconbon[bonindex]) ? maxhpconbon[bonindex] : oldlevel;
2065 if (Modified[IE_MC_FLAGS] & (MC_WAS_FIGHTER|MC_WAS_RANGER)) {
2066 oldbonus = core->GetConstitutionBonus(STAT_CON_HP_WARRIOR, Modified[IE_CON]);
2067 } else {
2068 oldbonus = core->GetConstitutionBonus(STAT_CON_HP_NORMAL, Modified[IE_CON]);
2072 // warrior (fighter, barbarian, ranger, or paladin) or not
2073 // GetClassLevel now takes into consideration inactive dual-classes
2074 if (IsWarrior()) {
2075 bonus = core->GetConstitutionBonus(STAT_CON_HP_WARRIOR,Modified[IE_CON]);
2076 } else {
2078 bonus = core->GetConstitutionBonus(STAT_CON_HP_NORMAL,Modified[IE_CON]);
2080 bonus *= bonlevel;
2081 oldbonus *= oldlevel;
2082 bonus = (oldbonus > bonus) ? oldbonus : bonus;
2084 //morale recovery every xth AI cycle
2085 int mrec = GetStat(IE_MORALERECOVERYTIME);
2086 if (mrec) {
2087 if (!(core->GetGame()->GameTime%mrec)) {
2088 NewBase(IE_MORALE,1,MOD_ADDITIVE);
2092 if (bonus<0 && (Modified[IE_MAXHITPOINTS]+bonus)<=0) {
2093 bonus=1-Modified[IE_MAXHITPOINTS];
2096 //get the wspattack bonuses for proficiencies
2097 WeaponInfo wi;
2098 ITMExtHeader *header = GetWeapon(wi, false);
2099 ieDword stars;
2100 int dualwielding = IsDualWielding();
2101 if (header && (wi.prof <= MAX_STATS)) {
2102 stars = GetStat(wi.prof)&PROFS_MASK;
2103 if (stars >= (unsigned)wspattack_rows) {
2104 stars = wspattack_rows-1;
2107 int tmplevel = GetWarriorLevel();
2108 if (tmplevel >= wspattack_cols) {
2109 tmplevel = wspattack_cols-1;
2110 } else if (tmplevel < 0) {
2111 tmplevel = 0;
2114 //HACK: attacks per round bonus for monks should only apply to fists
2115 if (isclass[ISMONK]&(1<<BaseStats[IE_CLASS])) {
2116 unsigned int level = GetMonkLevel()-1;
2117 SetBase(IE_NUMBEROFATTACKS, 2 + monkbon[0][level]);
2118 } else {
2119 //wspattack appears to only effect warriors
2120 int defaultattacks = 2 + 2*dualwielding;
2121 if (tmplevel) {
2122 SetBase(IE_NUMBEROFATTACKS, defaultattacks+wspattack[stars][tmplevel]);
2123 } else {
2124 SetBase(IE_NUMBEROFATTACKS, defaultattacks);
2129 //we still apply the maximum bonus to dead characters, but don't apply
2130 //to current HP, or we'd have dead characters showing as having hp
2131 //Modified[IE_MAXHITPOINTS]+=bonus;
2132 //if(BaseStats[IE_STATE_ID]&STATE_DEAD)
2133 // bonus = 0;
2134 // BaseStats[IE_HITPOINTS]+=bonus;
2136 // apply the intelligence and wisdom bonus to lore
2137 Modified[IE_LORE] += core->GetLoreBonus(0, Modified[IE_INT]) + core->GetLoreBonus(0, Modified[IE_WIS]);
2140 void Actor::RollSaves()
2142 if (InternalFlags&IF_USEDSAVE) {
2143 SavingThrow[0]=(ieByte) core->Roll(1, SAVEROLL, 0);
2144 SavingThrow[1]=(ieByte) core->Roll(1, SAVEROLL, 0);
2145 SavingThrow[2]=(ieByte) core->Roll(1, SAVEROLL, 0);
2146 SavingThrow[3]=(ieByte) core->Roll(1, SAVEROLL, 0);
2147 SavingThrow[4]=(ieByte) core->Roll(1, SAVEROLL, 0);
2148 InternalFlags&=~IF_USEDSAVE;
2152 //saving throws:
2153 //type bits in file order in stats
2154 //0 spells 1 4
2155 //1 breath 2 3
2156 //2 death 4 0
2157 //3 wands 8 1
2158 //4 polymorph 16 2
2160 //iwd2 (luckily they use the same bits as it would be with bg2):
2161 //0 not used
2162 //1 not used
2163 //2 fortitude 4 0
2164 //3 reflex 8 1
2165 //4 will 16 2
2167 #define SAVECOUNT 5
2168 static int savingthrows[SAVECOUNT]={IE_SAVEVSSPELL, IE_SAVEVSBREATH, IE_SAVEVSDEATH, IE_SAVEVSWANDS, IE_SAVEVSPOLY};
2170 /** returns true if actor made the save against saving throw type */
2171 bool Actor::GetSavingThrow(ieDword type, int modifier)
2173 assert(type<SAVECOUNT);
2174 InternalFlags|=IF_USEDSAVE;
2175 int ret = SavingThrow[type];
2176 if (ret == 1) return false;
2177 if (ret == SAVEROLL) return true;
2178 ret += modifier + GetStat(IE_LUCK);
2179 return ret > (int) GetStat(savingthrows[type]);
2182 /** implements a generic opcode function, modify modified stats
2183 returns the change
2185 int Actor::NewStat(unsigned int StatIndex, ieDword ModifierValue, ieDword ModifierType)
2187 int oldmod = Modified[StatIndex];
2189 switch (ModifierType) {
2190 case MOD_ADDITIVE:
2191 //flat point modifier
2192 SetStat(StatIndex, Modified[StatIndex]+ModifierValue, 0);
2193 break;
2194 case MOD_ABSOLUTE:
2195 //straight stat change
2196 SetStat(StatIndex, ModifierValue, 0);
2197 break;
2198 case MOD_PERCENT:
2199 //percentile
2200 SetStat(StatIndex, BaseStats[StatIndex] * ModifierValue / 100, 0);
2201 break;
2203 return Modified[StatIndex] - oldmod;
2206 int Actor::NewBase(unsigned int StatIndex, ieDword ModifierValue, ieDword ModifierType)
2208 int oldmod = BaseStats[StatIndex];
2210 switch (ModifierType) {
2211 case MOD_ADDITIVE:
2212 //flat point modifier
2213 SetBase(StatIndex, BaseStats[StatIndex]+ModifierValue);
2214 break;
2215 case MOD_ABSOLUTE:
2216 //straight stat change
2217 SetBase(StatIndex, ModifierValue);
2218 break;
2219 case MOD_PERCENT:
2220 //percentile
2221 SetBase(StatIndex, BaseStats[StatIndex] * ModifierValue / 100);
2222 break;
2224 return BaseStats[StatIndex] - oldmod;
2227 inline int CountElements(const char *s, char separator)
2229 int ret = 1;
2230 while(*s) {
2231 if (*s==separator) ret++;
2232 s++;
2234 return ret;
2237 void Actor::Interact(int type)
2239 int start;
2240 int count;
2242 switch(type) {
2243 case I_INSULT: start=VB_INSULT; count=3; break;
2244 case I_COMPLIMENT: start=VB_COMPLIMENT; count=3; break;
2245 case I_SPECIAL: start=VB_SPECIAL; count=3; break;
2246 default:
2247 return;
2249 VerbalConstant(start, count);
2252 void Actor::VerbalConstant(int start, int count)
2254 count=rand()%count;
2255 while(count>=0 && (!StrRefs[start+count] || (StrRefs[start+count]==(ieStrRef) -1)) ) count--;
2256 if(count>=0) {
2257 DisplayStringCore(this, start+count, DS_CONSOLE|DS_CONST );
2261 void Actor::Response(int type)
2263 int start;
2264 int count;
2266 switch(type) {
2267 case I_INSULT: start=VB_RESP_INS; count=3; break;
2268 case I_COMPLIMENT: start=VB_RESP_COMP; count=3; break;
2269 default:
2270 return;
2273 count=rand()%count;
2274 while(count && StrRefs[start+count]!=0xffff) count--;
2275 if(count>=0) {
2276 DisplayStringCore(this, start+count, DS_CONSOLE|DS_CONST );
2280 void Actor::ReactToDeath(const char * deadname)
2282 AutoTable tm("death");
2283 if (!tm) return;
2284 // lookup value based on died's scriptingname and ours
2285 // if value is 0 - use reactdeath
2286 // if value is 1 - use reactspecial
2287 // if value is string - use playsound instead (pst)
2288 const char *value = tm->QueryField (scriptName, deadname);
2289 switch (value[0]) {
2290 case '0':
2291 DisplayStringCore(this, VB_REACT, DS_CONSOLE|DS_CONST );
2292 break;
2293 case '1':
2294 DisplayStringCore(this, VB_REACT_S, DS_CONSOLE|DS_CONST );
2295 break;
2296 default:
2298 int count = CountElements(value,',');
2299 if (count<=0) break;
2300 count = core->Roll(1,count,-1);
2301 ieResRef resref;
2302 while(count--) {
2303 while(*value && *value!=',') value++;
2304 if (*value==',') value++;
2306 strncpy(resref, value, 8);
2307 for(count=0;count<8 && resref[count]!=',';count++) {};
2308 resref[count]=0;
2310 ieDword len = core->GetAudioDrv()->Play( resref );
2311 ieDword counter = ( AI_UPDATE_TIME * len ) / 1000;
2312 if (counter != 0)
2313 SetWait( counter );
2314 break;
2319 //call this only from gui selects
2320 void Actor::SelectActor()
2322 DisplayStringCore(this, VB_SELECT, DS_CONSOLE|DS_CONST );
2325 void Actor::Panic()
2327 if (GetStat(IE_STATE_ID)&STATE_PANIC) {
2328 //already in panic
2329 return;
2331 if (InParty) core->GetGame()->SelectActor(this, false, SELECT_NORMAL);
2332 SetBaseBit(IE_STATE_ID, STATE_PANIC, true);
2333 DisplayStringCore(this, VB_PANIC, DS_CONSOLE|DS_CONST );
2336 void Actor::SetMCFlag(ieDword arg, int op)
2338 ieDword tmp = BaseStats[IE_MC_FLAGS];
2339 switch (op) {
2340 case BM_SET: tmp = arg; break;
2341 case BM_OR: tmp |= arg; break;
2342 case BM_NAND: tmp &= ~arg; break;
2343 case BM_XOR: tmp ^= arg; break;
2344 case BM_AND: tmp &= arg; break;
2346 SetBase(IE_MC_FLAGS, tmp);
2349 void Actor::DialogInterrupt()
2351 //if dialoginterrupt was set, no verbal constant
2352 if ( Modified[IE_MC_FLAGS]&MC_NO_TALK)
2353 return;
2355 /* this part is unsure */
2356 if (Modified[IE_EA]>=EA_EVILCUTOFF) {
2357 DisplayStringCore(this, VB_HOSTILE, DS_CONSOLE|DS_CONST );
2358 } else {
2359 DisplayStringCore(this, VB_DIALOG, DS_CONSOLE|DS_CONST );
2363 static EffectRef fx_cure_sleep_ref={"Cure:Sleep",NULL,-1};
2365 void Actor::GetHit()
2367 SetStance( IE_ANI_DAMAGE );
2368 DisplayStringCore(this, VB_DAMAGE, DS_CONSOLE|DS_CONST );
2369 if (Modified[IE_STATE_ID]&STATE_SLEEP) {
2370 if (Modified[IE_EXTSTATE_ID]&EXTSTATE_NO_WAKEUP) {
2371 return;
2373 Effect *fx = EffectQueue::CreateEffect(fx_cure_sleep_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
2374 fxqueue.AddEffect(fx);
2378 bool Actor::HandleCastingStance(const ieResRef SpellResRef, bool deplete)
2380 if (deplete) {
2381 if (! spellbook.HaveSpell( SpellResRef, HS_DEPLETE )) {
2382 SetStance(IE_ANI_READY);
2383 return true;
2386 SetStance(IE_ANI_CAST);
2387 return false;
2390 static EffectRef fx_sleep_ref={"State:Helpless", NULL, -1};
2392 //returns actual damage
2393 int Actor::Damage(int damage, int damagetype, Scriptable *hitter, int modtype)
2395 //add lastdamagetype up ? maybe
2396 LastDamageType|=damagetype;
2397 if(hitter && hitter->Type==ST_ACTOR) {
2398 LastHitter=((Actor *) hitter)->GetID();
2399 } else {
2400 //Maybe it should be something impossible like 0xffff, and use 'Someone'
2401 LastHitter=GetID();
2404 switch(modtype)
2406 case MOD_ADDITIVE:
2407 //damage = -NewBase(IE_HITPOINTS, (ieDword) -damage, MOD_ADDITIVE);
2408 break;
2409 case MOD_ABSOLUTE:
2410 //damage = -NewBase(IE_HITPOINTS, (ieDword) damage, MOD_ABSOLUTE);
2411 damage = GetBase(IE_HITPOINTS) - damage;
2412 break;
2413 case MOD_PERCENT:
2414 //damage = -NewBase(IE_HITPOINTS, (ieDword) damage, MOD_PERCENT);
2415 damage = GetStat(IE_MAXHITPOINTS) * 100 / damage;
2416 break;
2417 default:
2418 //this shouldn't happen
2419 printMessage("Actor","Invalid damagetype!\n",RED);
2420 return 0;
2423 int resisted = 0;
2424 ModifyDamage (this, hitter, damage, resisted, damagetype, NULL, false);
2425 if (damage) GetHit();
2427 DisplayCombatFeedback(damage, resisted, damagetype, hitter);
2429 if (BaseStats[IE_HITPOINTS] <= (ieDword) damage) {
2430 // common fists do normal damage, but cause sleeping for a round instead of death
2431 if ((damagetype & DAMAGE_STUNNING) && Modified[IE_MINHITPOINTS] <= 0) {
2432 NewBase(IE_HITPOINTS, 1, MOD_ABSOLUTE);
2433 Effect *fx = EffectQueue::CreateEffect(fx_sleep_ref, 0, 0, FX_DURATION_INSTANT_LIMITED);
2434 fx->Duration = ROUND_SECONDS; // 1 round
2435 core->ApplyEffect(fx, this, this);
2436 delete fx;
2437 } else {
2438 NewBase(IE_HITPOINTS, (ieDword) -damage, MOD_ADDITIVE);
2440 } else {
2441 NewBase(IE_HITPOINTS, (ieDword) -damage, MOD_ADDITIVE);
2443 // also apply reputation damage if we hurt (but not killed) an innocent
2444 int reputation = core->GetGame()->Reputation / 10;
2445 if (Modified[IE_CLASS] == CLASS_INNOCENT) {
2446 core->GetGame()->SetReputation(reputation*10 + reputationmod[reputation-1][1]);
2450 LastDamage=damage;
2451 InternalFlags|=IF_ACTIVE;
2452 int chp = (signed) BaseStats[IE_HITPOINTS];
2453 int damagelevel = 3;
2454 if (damage<5) {
2455 damagelevel = 1;
2456 } else if (damage<10) {
2457 damagelevel = 2;
2458 } else {
2459 NewBase(IE_MORALE, (ieDword) -1, MOD_ADDITIVE);
2460 if (chp<-10) {
2461 damagelevel = 0; //chunky death
2463 else {
2464 damagelevel = 3;
2468 if (damagetype & (DAMAGE_FIRE|DAMAGE_MAGICFIRE) ) {
2469 PlayDamageAnimation(DL_FIRE+damagelevel);
2470 } else if (damagetype & (DAMAGE_COLD|DAMAGE_MAGICCOLD) ) {
2471 PlayDamageAnimation(DL_COLD+damagelevel);
2472 } else if (damagetype & (DAMAGE_ELECTRICITY) ) {
2473 PlayDamageAnimation(DL_ELECTRICITY+damagelevel);
2474 } else if (damagetype & (DAMAGE_ACID) ) {
2475 PlayDamageAnimation(DL_ACID+damagelevel);
2476 } else if (damagetype & (DAMAGE_MAGIC) ) {
2477 PlayDamageAnimation(DL_DISINTEGRATE+damagelevel);
2478 } else {
2479 PlayDamageAnimation(damagelevel);
2482 if (InParty) {
2483 if (chp<(signed) Modified[IE_MAXHITPOINTS]/10) {
2484 core->Autopause(AP_WOUNDED);
2486 if (damage>0) {
2487 core->Autopause(AP_HIT);
2488 core->SetEventFlag(EF_PORTRAIT);
2491 return damage;
2494 //TODO: handle pst
2495 void Actor::DisplayCombatFeedback (unsigned int damage, int resisted, int damagetype, Scriptable *hitter)
2497 bool detailed = false;
2498 const char *type_name = "unknown";
2499 if (core->HasStringReference(STR_DMG_SLASHING)) { // how and iwd2
2500 std::multimap<ieDword, DamageInfoStruct>::iterator it;
2501 it = core->DamageInfoMap.find(damagetype);
2502 if (it != core->DamageInfoMap.end()) {
2503 type_name = core->GetString(it->second.strref, 0);
2505 detailed = true;
2508 if (damage > 0 && resisted != DR_IMMUNE) {
2509 printMessage("Actor", " ", GREEN);
2510 printf("%d damage taken.\n", damage);
2512 if (detailed) {
2513 // 3 choices depending on resistance and boni
2514 // iwd2 also has two Tortoise Shell (spell) absorption strings
2515 core->GetTokenDictionary()->SetAtCopy( "TYPE", type_name);
2516 core->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage);
2517 if (hitter && hitter->Type == ST_ACTOR) {
2518 core->GetTokenDictionary()->SetAtCopy( "DAMAGER", hitter->GetName(1) );
2519 } else {
2520 core->GetTokenDictionary()->SetAtCopy( "DAMAGER", "trap" );
2522 if (resisted < 0) {
2523 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage bonus)
2524 core->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted));
2525 core->DisplayConstantStringName(STR_DAMAGE3, 0xffffff, this);
2526 } else if (resisted > 0) {
2527 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage resisted)
2528 core->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted));
2529 core->DisplayConstantStringName(STR_DAMAGE2, 0xffffff, this);
2530 } else {
2531 //Takes <AMOUNT> <TYPE> damage from <DAMAGER>
2532 core->DisplayConstantStringName(STR_DAMAGE1, 0xffffff, this);
2534 } else if (stricmp( core->GameType, "pst" ) == 0) {
2535 if(0) printf("TODO: pst floating text\n");
2536 } else if (core->HasStringReference(STR_DAMAGE1) || !hitter || hitter->Type != ST_ACTOR) {
2537 // bg1 and iwd
2538 // or traps: "Damage Taken (damage)", but there's no token
2539 char tmp[32];
2540 snprintf(tmp, sizeof(tmp), "Damage Taken (%d)", damage);
2541 core->DisplayStringName(tmp, 0xffffff, this);
2542 } else { //bg2
2543 //<DAMAGER> did <AMOUNT> damage to <DAMAGEE>
2544 core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2545 // wipe the DAMAGER token, so we can color it
2546 core->GetTokenDictionary()->SetAtCopy( "DAMAGER", "" );
2547 core->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage);
2548 core->DisplayConstantStringName(STR_DAMAGE1, 0xffffff, hitter);
2550 } else {
2551 if (resisted == DR_IMMUNE) {
2552 printMessage("Actor", " ", GREEN);
2553 printf("is immune to damage type: %s.\n", type_name);
2554 if (hitter && hitter->Type == ST_ACTOR) {
2555 if (detailed) {
2556 //<DAMAGEE> was immune to my <TYPE> damage
2557 core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2558 core->GetTokenDictionary()->SetAtCopy( "TYPE", type_name );
2559 core->DisplayConstantStringName(STR_DAMAGE_IMMUNITY, 0xffffff, hitter);
2560 } else if (core->HasStringReference(STR_DAMAGE_IMMUNITY) && core->HasStringReference(STR_DAMAGE1)) {
2561 // bg2
2562 //<DAMAGEE> was immune to my damage.
2563 core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2564 core->DisplayConstantStringName(STR_DAMAGE_IMMUNITY, 0xffffff, hitter);
2565 } // else: other games don't display anything
2567 } else {
2568 // mirror image or stoneskin: no message
2572 //PST hit sounds
2573 DataFileMgr *resdata = core->GetResDataINI();
2574 if (resdata) {
2575 PlayHitSound(resdata, damagetype, false);
2579 //Play PST specific hit sounds (HIT_0<dtype><armor>)
2580 void Actor::PlayHitSound(DataFileMgr *resdata, int damagetype, bool suffix)
2582 int type;
2584 switch(damagetype) {
2585 case DAMAGE_SLASHING: type = 1; break; //slashing
2586 case DAMAGE_PIERCING: type = 2; break; //piercing
2587 case DAMAGE_CRUSHING: type = 3; break; //crushing
2588 case DAMAGE_MISSILE: type = 4; break; //missile
2589 default: return; //other
2592 ieResRef Sound;
2593 char section[12];
2594 unsigned int animid=BaseStats[IE_ANIMATION_ID];
2595 if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
2596 animid&=0xff;
2599 snprintf(section,10,"%d", animid);
2601 int armor = resdata->GetKeyAsInt(section, "armor",0);
2602 if (armor<0 || armor>35) return;
2604 snprintf(Sound,8,"HIT_0%d%c%c",type, armor+'A', suffix?'1':0);
2606 core->GetAudioDrv()->Play( Sound,Pos.x,Pos.y,0 );
2609 //Just to quickly inspect debug maximum values
2610 #if 0
2611 void Actor::DumpMaxValues()
2613 int symbol = core->LoadSymbol( "stats" );
2614 SymbolMgr *sym = core->GetSymbol( symbol );
2616 for(int i=0;i<MAX_STATS;i++) {
2617 printf("%d (%s) %d\n", i, sym->GetValue(i), maximum_values[i]);
2620 #endif
2622 void Actor::DebugDump()
2624 unsigned int i;
2626 printf( "Debugdump of Actor %s (%s, %s):\n", LongName, ShortName, GetName(-1) );
2627 printf ("Scripts:");
2628 for (i = 0; i < MAX_SCRIPTS; i++) {
2629 const char* poi = "<none>";
2630 if (Scripts[i]) {
2631 poi = Scripts[i]->GetName();
2633 printf( " %.8s", poi );
2635 printf( "\nArea: %.8s ", Area );
2636 printf( "Dialog: %.8s\n", Dialog );
2637 printf( "Global ID: %d Local ID: %d\n", globalID, localID);
2638 printf( "Script name:%.32s\n", scriptName );
2639 printf( "TalkCount: %d ", TalkCount );
2640 printf( "PartySlot: %d\n", InParty );
2641 printf( "Allegiance: %d current allegiance:%d\n", BaseStats[IE_EA], Modified[IE_EA] );
2642 printf( "Class: %d current class:%d\n", BaseStats[IE_CLASS], Modified[IE_CLASS] );
2643 printf( "Race: %d current race:%d\n", BaseStats[IE_RACE], Modified[IE_RACE] );
2644 printf( "Gender: %d current gender:%d\n", BaseStats[IE_SEX], Modified[IE_SEX] );
2645 printf( "Specifics: %d current specifics:%d\n", BaseStats[IE_SPECIFIC], Modified[IE_SPECIFIC] );
2646 printf( "Alignment: %x current alignment:%x\n", BaseStats[IE_ALIGNMENT], Modified[IE_ALIGNMENT] );
2647 printf( "Morale: %d current morale:%d\n", BaseStats[IE_MORALE], Modified[IE_MORALE] );
2648 printf( "Moralebreak:%d Morale recovery:%d\n", Modified[IE_MORALEBREAK], Modified[IE_MORALERECOVERYTIME] );
2649 printf( "Visualrange:%d (Explorer: %d)\n", Modified[IE_VISUALRANGE], Modified[IE_EXPLORE] );
2650 printf( "current HP:%d\n", BaseStats[IE_HITPOINTS] );
2651 printf( "Mod[IE_ANIMATION_ID]: 0x%04X\n", Modified[IE_ANIMATION_ID] );
2652 printf( "Colors: ");
2653 if (core->HasFeature(GF_ONE_BYTE_ANIMID) ) {
2654 for(i=0;i<Modified[IE_COLORCOUNT];i++) {
2655 printf(" %d", Modified[IE_COLORS+i]);
2658 else {
2659 for(i=0;i<7;i++) {
2660 printf(" %d", Modified[IE_COLORS+i]);
2663 printf( "\nWaitCounter: %d\n", (int) GetWait());
2664 printf( "LastTarget: %d %s\n", LastTarget, GetActorNameByID(LastTarget));
2665 printf( "LastTalked: %d %s\n", LastTalkedTo, GetActorNameByID(LastTalkedTo));
2666 inventory.dump();
2667 spellbook.dump();
2668 fxqueue.dump();
2669 #if 0
2670 DumpMaxValues();
2671 #endif
2674 const char* Actor::GetActorNameByID(ieDword ID) const
2676 Actor *actor = GetCurrentArea()->GetActorByGlobalID(ID);
2677 if (!actor) {
2678 return "<NULL>";
2680 return actor->GetScriptName();
2683 void Actor::SetMap(Map *map, ieWord LID, ieWord GID)
2685 //Did we have an area?
2686 bool effinit=!GetCurrentArea();
2687 Scriptable::SetMap(map); //now we have an area
2688 localID = LID;
2689 globalID = GID;
2691 //These functions are called once when the actor is first put in
2692 //the area. It already has all the items (including fist) at this
2693 //time and it is safe to call effects.
2694 //This hack is to delay the equipping effects until the actor has
2695 //an area (and the game object is also existing)
2696 if (effinit) {
2697 int SlotCount = inventory.GetSlotCount();
2698 for (int Slot = 0; Slot<SlotCount;Slot++) {
2699 int slottype = core->QuerySlotEffects( Slot );
2700 switch (slottype) {
2701 case SLOT_EFFECT_NONE:
2702 case SLOT_EFFECT_MELEE:
2703 break;
2704 default:
2705 inventory.EquipItem( Slot );
2706 break;
2709 //We need to convert this to signed 16 bits, because
2710 //it is actually a 16 bit number.
2711 //It is signed to have the correct math
2712 //when adding it to the base slot (SLOT_WEAPON) in
2713 //case of quivers. (weird IE magic)
2714 //The other word is the equipped header.
2715 //find a quiver for the bow, etc
2716 if (Equipped!=IW_NO_EQUIPPED) {
2717 inventory.EquipItem( Equipped+inventory.GetWeaponSlot());
2718 SetEquippedQuickSlot( inventory.GetEquipped(), EquippedHeader );
2723 void Actor::SetPosition(const Point &position, int jump, int radius)
2725 PathTries = 0;
2726 ClearPath();
2727 Point p;
2728 p.x = position.x/16;
2729 p.y = position.y/12;
2730 if (jump && !(Modified[IE_DONOTJUMP] & DNJ_FIT) && size ) {
2731 GetCurrentArea()->AdjustPosition( p, radius );
2733 p.x = p.x * 16 + 8;
2734 p.y = p.y * 12 + 6;
2735 MoveTo( p );
2738 /* this is returning the level of the character for xp calculations
2739 and the average level for dual/multiclass (rounded up),
2740 also with iwd2's 3rd ed rules, this is why it is a separate function */
2741 ieDword Actor::GetXPLevel(int modified) const
2743 const ieDword *stats;
2745 if (modified) {
2746 stats = Modified;
2748 else {
2749 stats = BaseStats;
2752 float classcount = 0;
2753 float average = 0;
2754 if (core->HasFeature(GF_LEVELSLOT_PER_CLASS)) {
2755 // iwd2
2756 for (int i=0; i < 11; i++) {
2757 if (stats[levelslotsiwd2[i]] > 0) classcount++;
2759 average = stats[IE_CLASSLEVELSUM] / classcount + 0.5;
2761 else {
2762 int levels[3]={stats[IE_LEVEL], stats[IE_LEVEL2], stats[IE_LEVEL3]};
2763 average = levels[0];
2764 classcount = 1;
2765 if (IsDualClassed()) {
2766 // dualclassed
2767 if (levels[1] > 0) {
2768 classcount++;
2769 average += levels[1];
2772 else if (IsMultiClassed()) {
2773 //classcount is the number of on bits in the MULTI field
2774 classcount = bitcount (multiclass);
2775 for (int i=1; i<classcount; i++)
2776 average += levels[i];
2777 } //else single classed
2778 average = average / classcount + 0.5;
2780 return ieDword(average);
2783 int Actor::GetWildMod(int level) const
2785 if(GetStat(IE_KIT)&0x8000) {
2786 if (level>=MAX_LEVEL) level=MAX_LEVEL;
2787 if(level<1) level=1;
2788 return wmlevels[core->Roll(1,20,-1)][level-1];
2790 return 0;
2793 int Actor::CastingLevelBonus(int level, int type) const
2795 int bonus = 0;
2796 switch(type)
2798 case IE_SPL_PRIEST:
2799 bonus = GetStat(IE_CASTINGLEVELBONUSCLERIC);
2800 break;
2801 case IE_SPL_WIZARD:
2802 bonus = GetWildMod(level) + GetStat(IE_CASTINGLEVELBONUSMAGE);
2805 if (!bonus) {
2806 return 0;
2809 core->GetTokenDictionary()->SetAtCopy("LEVELDIF", bonus);
2811 if (bonus > 0) {
2812 core->DisplayConstantStringName(STR_CASTER_LVL_INC, 0xffffff, this);
2813 } else {
2814 core->DisplayConstantStringName(STR_CASTER_LVL_DEC, 0xffffff, this);
2817 return bonus;
2820 /** maybe this would be more useful if we calculate with the strength too
2822 int Actor::GetEncumbrance()
2824 return inventory.GetWeight();
2827 EffectRef control_undead_ref = { "ControlUndead2", NULL, -1};
2829 //receive turning
2830 void Actor::Turn(Scriptable *cleric, ieDword turnlevel)
2832 //this is safely hardcoded i guess
2833 if (Modified[IE_GENERAL]!=GEN_UNDEAD) {
2834 return;
2837 if (!turnlevel) {
2838 return;
2841 //determine if we see the cleric (distance)
2842 if (!CanSee(cleric, this, true, GA_NO_DEAD)) {
2843 return;
2846 //determine alignment (if equals, then no turning)
2848 //determine panic or destruction/control
2849 //we get the modified level
2850 if (turnlevel>GetXPLevel(true)) {
2851 if (cleric->Type == ST_ACTOR && ((Actor*)cleric)->MatchesAlignmentMask(AL_EVIL)) {
2852 Effect *fx = fxqueue.CreateEffect(control_undead_ref, GEN_UNDEAD, 3, FX_DURATION_INSTANT_LIMITED);
2853 fx->Duration = ROUND_SECONDS;
2854 fx->Target = FX_TARGET_PRESET;
2855 core->ApplyEffect(fx, this, cleric);
2856 delete fx;
2857 } else {
2858 Die(cleric);
2860 } else {
2861 Panic();
2865 void Actor::Resurrect()
2867 if (!(Modified[IE_STATE_ID ] & STATE_DEAD)) {
2868 return;
2870 InternalFlags&=IF_FROMGAME; //keep these flags (what about IF_INITIALIZED)
2871 InternalFlags|=IF_ACTIVE|IF_VISIBLE; //set these flags
2872 SetBase(IE_STATE_ID, 0);
2873 SetBase(IE_MORALE, 10);
2874 SetBase(IE_HITPOINTS, BaseStats[IE_MAXHITPOINTS]);
2875 ClearActions();
2876 ClearPath();
2877 SetStance(IE_ANI_EMERGE);
2878 //readjust death variable on resurrection
2879 if (core->HasFeature(GF_HAS_KAPUTZ) && (AppearanceFlags&APP_DEATHVAR)) {
2880 ieVariable DeathVar;
2882 snprintf(DeathVar,sizeof(ieVariable),"%s_DEAD",scriptName);
2883 Game *game=core->GetGame();
2884 ieDword value=0;
2886 game->kaputz->Lookup(DeathVar, value);
2887 if (value) {
2888 game->kaputz->SetAt(DeathVar, value-1);
2891 //clear effects?
2894 void Actor::Die(Scriptable *killer)
2896 int i,j;
2898 if (InternalFlags&IF_REALLYDIED) {
2899 return; //can die only once
2902 int minhp = (signed) Modified[IE_MINHITPOINTS];
2903 if (minhp > 0) { //can't die
2904 SetBase(IE_HITPOINTS, minhp);
2905 return;
2907 //Can't simply set Selected to false, game has its own little list
2908 Game *game = core->GetGame();
2909 game->SelectActor(this, false, SELECT_NORMAL);
2910 game->OutAttack(GetID());
2912 ClearActions();
2913 ClearPath();
2914 SetModal( MS_NONE );
2915 core->DisplayConstantStringName(STR_DEATH, 0xffffff, this);
2916 DisplayStringCore(this, VB_DIE, DS_CONSOLE|DS_CONST );
2918 // clearing the search map here means it's not blocked during death animations
2919 // this is perhaps not ideal, but matches other searchmap code which uses
2920 // GA_NO_DEAD to exclude IF_JUSTDIED actors as well as dead ones
2921 if (area)
2922 area->ClearSearchMapFor(this);
2924 //JUSTDIED will be removed when the Die() trigger executed
2925 //otherwise it is the same as REALLYDIED
2926 InternalFlags|=IF_REALLYDIED|IF_JUSTDIED;
2927 SetStance( IE_ANI_DIE );
2929 if (InParty) {
2930 game->PartyMemberDied(this);
2931 core->Autopause(AP_DEAD);
2932 } else {
2933 Actor *act=NULL;
2934 if (!killer) {
2935 killer = area->GetActorByGlobalID(LastHitter);
2938 if (killer) {
2939 if (killer->Type==ST_ACTOR) {
2940 act = (Actor *) killer;
2944 if (act) {
2945 if (act->InParty) {
2946 //adjust kill statistics here
2947 PCStatsStruct *stat = act->PCStats;
2948 if (stat) {
2949 stat->NotifyKill(Modified[IE_XPVALUE], ShortStrRef);
2951 InternalFlags|=IF_GIVEXP;
2954 // friendly party summons' kills also grant xp
2955 if (act->Modified[IE_SEX] == SEX_SUMMON && act->Modified[IE_EA] == EA_CONTROLLED) {
2956 InternalFlags|=IF_GIVEXP;
2961 // XP seems to be handed at out at the moment of death
2962 if (InternalFlags&IF_GIVEXP) {
2963 //give experience to party
2964 game->ShareXP(Modified[IE_XPVALUE], sharexp );
2966 if (!InParty) {
2967 // adjust reputation if the corpse was:
2968 // an innocent, a member of the Flaming Fist or something evil
2969 int reputation = game->Reputation / 10;
2970 int repmod = 0;
2971 if (Modified[IE_CLASS] == CLASS_INNOCENT) {
2972 repmod = reputationmod[reputation-1][0];
2973 } else if (Modified[IE_CLASS] == CLASS_FLAMINGFIST) {
2974 repmod = reputationmod[reputation-1][3];
2976 if (MatchesAlignmentMask(AL_EVIL)) {
2977 repmod += reputationmod[reputation-1][7];
2979 if (repmod) {
2980 game->SetReputation(reputation*10 + repmod);
2985 ieDword value = 0;
2986 ieVariable varname;
2988 // death variables are updated at the moment of death
2989 if (KillVar[0]) {
2990 if (core->HasFeature(GF_HAS_KAPUTZ) ) {
2991 game->kaputz->Lookup(KillVar, value);
2992 game->kaputz->SetAt(KillVar, value+1);
2993 } else {
2994 // iwd/iwd2 path *sets* this var, so i changed it, not sure about pst above
2995 game->locals->SetAt(KillVar, 1);
2998 if (IncKillVar[0]) {
2999 value = 0;
3000 game->locals->Lookup(IncKillVar, value);
3001 game->locals->SetAt(IncKillVar, value + 1);
3004 if (scriptName[0]) {
3005 value = 0;
3006 if (core->HasFeature(GF_HAS_KAPUTZ) ) {
3007 if (AppearanceFlags&APP_DEATHVAR) {
3008 snprintf(varname, 32, "%s_DEAD", scriptName);
3009 game->kaputz->Lookup(varname, value);
3010 game->kaputz->SetAt(varname, value+1);
3012 if (AppearanceFlags&APP_DEATHTYPE) {
3013 snprintf(varname, 32, "KILL_%s", KillVar);
3014 game->kaputz->Lookup(varname, value);
3015 game->kaputz->SetAt(varname, value+1);
3017 } else {
3018 snprintf(varname, 32, core->GetDeathVarFormat(), scriptName);
3019 game->locals->Lookup(varname, value);
3020 game->locals->SetAt(varname, value+1);
3023 if (SetDeathVar) {
3024 value = 0;
3025 snprintf(varname, 32, "%s_DEAD", scriptName);
3026 game->locals->Lookup(varname, value);
3027 game->locals->SetAt(varname, 1);
3028 if (value) {
3029 snprintf(varname, 32, "%s_KILL_CNT", scriptName);
3030 value = 1;
3031 game->locals->Lookup(varname, value);
3032 game->locals->SetAt(varname, value + 1);
3037 if (IncKillCount) {
3038 // racial dead count
3039 value = 0;
3040 int racetable = core->LoadSymbol("race");
3041 if (racetable != -1) {
3042 Holder<SymbolMgr> race = core->GetSymbol(racetable);
3043 const char *raceName = race->GetValue(Modified[IE_RACE]);
3044 if (raceName) {
3045 // todo: should probably not set this for humans in iwd?
3046 snprintf(varname, 32, "KILL_%s_CNT", raceName);
3047 game->locals->Lookup(varname, value);
3048 game->locals->SetAt(varname, value+1);
3053 //death counters for PST
3054 j=APP_GOOD;
3055 for(i=0;i<4;i++) {
3056 if (AppearanceFlags&j) {
3057 ieDword value = 0;
3058 game->locals->Lookup(CounterNames[i], value);
3059 game->locals->SetAt(CounterNames[i], value+DeathCounters[i]);
3061 j+=j;
3064 // EXTRACOUNT is updated at the moment of death
3065 if (Modified[IE_SEX] == SEX_EXTRA || (Modified[IE_SEX] >= SEX_EXTRA2 && Modified[IE_SEX] <= SEX_MAXEXTRA)) {
3066 // if gender is set to one of the EXTRA values, then at death, we have to decrease
3067 // the relevant EXTRACOUNT area variable. scripts use this to check how many actors
3068 // of this extra id are still alive (for example, see the ToB challenge scripts)
3069 ieVariable varname;
3070 if (Modified[IE_SEX] == SEX_EXTRA) {
3071 snprintf(varname, 32, "EXTRACOUNT");
3072 } else {
3073 snprintf(varname, 32, "EXTRACOUNT%d", 2 + (Modified[IE_SEX] - SEX_EXTRA2));
3076 Map *area = GetCurrentArea();
3077 if (area) {
3078 ieDword value = 0;
3079 area->locals->Lookup(varname, value);
3080 // i am guessing that we shouldn't decrease below 0
3081 if (value > 0) {
3082 area->locals->SetAt(varname, value-1);
3087 //a plot critical creature has died (iwd2)
3088 if (BaseStats[IE_MC_FLAGS]&MC_PLOT_CRITICAL) {
3089 core->GetGUIScriptEngine()->RunFunction("DeathWindowPlot", false);
3091 //ensure that the scripts of the actor will run as soon as possible
3092 ImmediateEvent();
3095 void Actor::SetPersistent(int partyslot)
3097 InParty = (ieByte) partyslot;
3098 InternalFlags|=IF_FROMGAME;
3099 //if an actor is coming from a game, it should have these too
3100 CreateStats();
3103 void Actor::DestroySelf()
3105 InternalFlags|=IF_CLEANUP;
3106 // clear search map so that a new actor can immediately go there
3107 // (via ChangeAnimationCore)
3108 if (area)
3109 area->ClearSearchMapFor(this);
3112 bool Actor::CheckOnDeath()
3114 if (InternalFlags&IF_CLEANUP) {
3115 return true;
3117 if (InternalFlags&IF_JUSTDIED) {
3118 if (lastRunTime == 0 || CurrentAction || GetNextAction()) {
3119 return false; //actor is currently dying, let him die first
3122 if (!(InternalFlags&IF_REALLYDIED) ) {
3123 return false;
3125 //don't mess with the already deceased
3126 if (BaseStats[IE_STATE_ID]&STATE_DEAD) {
3127 return false;
3129 // don't destroy actors currently in a dialog
3130 GameControl *gc = core->GetGameControl();
3131 if (gc && (globalID == gc->targetID || globalID == gc->speakerID)) {
3132 return false;
3135 //we need to check animID here, if it has not played the death
3136 //sequence yet, then we could return now
3137 ClearActions();
3138 //missed the opportunity of Died()
3139 InternalFlags&=~IF_JUSTDIED;
3141 // items seem to be dropped at the moment of death
3142 // .. but this can't go in Die() because that is called
3143 // from effects and dropping items might change effects!
3144 DropItem("",0);
3146 //remove all effects that are not 'permanent after death' here
3147 //permanent after death type is 9
3148 SetBaseBit(IE_STATE_ID, STATE_DEAD, true);
3150 // party actors are never removed
3151 if (InParty) return false;
3153 if (Modified[IE_MC_FLAGS]&MC_REMOVE_CORPSE) return true;
3154 if (Modified[IE_MC_FLAGS]&MC_KEEP_CORPSE) return false;
3155 //if chunked death, then return true
3156 if (LastDamageType&DAMAGE_CHUNKING) {
3157 //play chunky animation
3158 //chunks are projectiles
3159 return true;
3161 return false;
3164 /* this will create a heap at location, and transfer the item(s) */
3165 void Actor::DropItem(const ieResRef resref, unsigned int flags)
3167 if (inventory.DropItemAtLocation( resref, flags, area, Pos )) {
3168 ReinitQuickSlots();
3172 void Actor::DropItem(int slot , unsigned int flags)
3174 if (inventory.DropItemAtLocation( slot, flags, area, Pos )) {
3175 ReinitQuickSlots();
3179 /** returns quick item data */
3180 /** if header==-1 which is a 'use quickitem' action */
3181 /** if header is set, then which is the absolute slot index, */
3182 /** and header is the header index */
3183 void Actor::GetItemSlotInfo(ItemExtHeader *item, int which, int header)
3185 ieWord idx;
3186 ieWord headerindex;
3188 memset(item, 0, sizeof(ItemExtHeader) );
3189 if (header<0) {
3190 if (!PCStats) return; //not a player character
3191 PCStats->GetSlotAndIndex(which,idx,headerindex);
3192 if (headerindex==0xffff) return; //headerindex is invalid
3193 } else {
3194 idx=(ieWord) which;
3195 headerindex=(ieWord) header;
3197 const CREItem *slot = inventory.GetSlotItem(idx);
3198 if (!slot) return; //quick item slot is empty
3199 Item *itm = gamedata->GetItem(slot->ItemResRef);
3200 if (!itm) return; //quick item slot contains invalid item resref
3201 ITMExtHeader *ext_header = itm->GetExtHeader(headerindex);
3202 //item has no extended header, or header index is wrong
3203 if (!ext_header) return;
3204 memcpy(item->itemname, slot->ItemResRef, sizeof(ieResRef) );
3205 item->slot = idx;
3206 item->headerindex = headerindex;
3207 memcpy(&(item->AttackType), &(ext_header->AttackType),
3208 ((char *) &(item->itemname)) -((char *) &(item->AttackType)) );
3209 if (headerindex>=CHARGE_COUNTERS) {
3210 item->Charges=0;
3211 } else {
3212 item->Charges=slot->Usages[headerindex];
3214 gamedata->FreeItem(itm,slot->ItemResRef, false);
3217 void Actor::ReinitQuickSlots()
3219 if (!PCStats) {
3220 return;
3223 // Note: (wjp, 20061226)
3224 // This function needs some rethinking.
3225 // It tries to satisfy two things at the moment:
3226 // Fill quickslots when they are empty and an item is placed in the
3227 // inventory slot corresponding to the quickslot
3228 // Reset quickslots when an item is removed
3229 // Currently, it resets all slots when items are removed,
3230 // but it only refills the ACT_QSLOTn slots, not the ACT_WEAPONx slots.
3232 // Refilling a weapon slot is possible, but essentially duplicates a lot
3233 // of code from Inventory::EquipItem() which performs the same steps for
3234 // the Inventory::Equipped slot.
3235 // Hopefully, weapons/arrows are never added to inventory slots without
3236 // EquipItem being called.
3238 int i=sizeof(PCStats->QSlots);
3239 while (i--) {
3240 int slot;
3241 int which;
3242 if (i<0) which = ACT_WEAPON4+i+1;
3243 else which = PCStats->QSlots[i];
3244 switch (which) {
3245 case ACT_WEAPON1:
3246 case ACT_WEAPON2:
3247 case ACT_WEAPON3:
3248 case ACT_WEAPON4:
3249 CheckWeaponQuickSlot(which);
3250 slot = 0;
3251 break;
3252 //WARNING:this cannot be condensed, because the symbols don't come in order!!!
3253 case ACT_QSLOT1: slot = inventory.GetQuickSlot(); break;
3254 case ACT_QSLOT2: slot = inventory.GetQuickSlot()+1; break;
3255 case ACT_QSLOT3: slot = inventory.GetQuickSlot()+2; break;
3256 case ACT_QSLOT4: slot = inventory.GetQuickSlot()+3; break;
3257 case ACT_QSLOT5: slot = inventory.GetQuickSlot()+4; break;
3258 default:
3259 slot = 0;
3261 if (!slot) continue;
3262 //if magic items are equipped the equipping info doesn't change
3263 //(afaik)
3265 // Note: we're now in the QSLOTn case
3266 // If slot is empty, reset quickslot to 0xffff/0xffff
3268 if (!inventory.HasItemInSlot("", slot)) {
3269 SetupQuickSlot(which, 0xffff, 0xffff);
3270 } else {
3271 ieWord idx;
3272 ieWord headerindex;
3273 PCStats->GetSlotAndIndex(which,idx,headerindex);
3274 if (idx != slot || headerindex == 0xffff) {
3275 // If slot just became filled, set it to filled
3276 SetupQuickSlot(which,slot,0);
3281 //these are always present
3282 CheckWeaponQuickSlot(0);
3283 CheckWeaponQuickSlot(1);
3284 //disabling quick weapon slots for certain classes
3285 for(i=0;i<2;i++) {
3286 int which = ACT_WEAPON3+i;
3287 // Assuming that ACT_WEAPON3 and 4 are always in the first two spots
3288 if (PCStats->QSlots[i]!=which) {
3289 SetupQuickSlot(which, 0xffff, 0xffff);
3294 void Actor::CheckWeaponQuickSlot(unsigned int which)
3296 if (!PCStats) return;
3298 bool empty = false;
3299 // If current quickweaponslot doesn't contain an item, reset it to fist
3300 int slot = PCStats->QuickWeaponSlots[which];
3301 int header = PCStats->QuickWeaponHeaders[which];
3302 if (!inventory.HasItemInSlot("", slot) || header == 0xffff) {
3303 //a quiver just went dry, falling back to fist
3304 empty = true;
3305 } else {
3306 // If current quickweaponslot contains ammo, and bow not found, reset
3308 if (core->QuerySlotEffects(slot) == SLOT_EFFECT_MISSILE) {
3309 const CREItem *slotitm = inventory.GetSlotItem(slot);
3310 assert(slotitm);
3311 Item *itm = gamedata->GetItem(slotitm->ItemResRef);
3312 assert(itm);
3313 ITMExtHeader *ext_header = itm->GetExtHeader(header);
3314 if (ext_header) {
3315 int type = ext_header->ProjectileQualifier;
3316 int weaponslot = inventory.FindTypedRangedWeapon(type);
3317 if (weaponslot == inventory.GetFistSlot()) {
3318 empty = true;
3320 } else {
3321 empty = true;
3323 gamedata->FreeItem(itm,slotitm->ItemResRef, false);
3327 if (empty)
3328 SetupQuickSlot(ACT_WEAPON1+which, inventory.GetFistSlot(), 0);
3332 void Actor::SetupQuickSlot(unsigned int which, int slot, int headerindex)
3334 if (!PCStats) return;
3335 PCStats->InitQuickSlot(which, slot, headerindex);
3336 //something changed about the quick items
3337 core->SetEventFlag(EF_ACTION);
3340 bool Actor::ValidTarget(int ga_flags) const
3342 if (Immobile()) return false;
3343 //scripts can still see this type of actor
3345 if (ga_flags&GA_NO_HIDDEN) {
3346 if (Modified[IE_AVATARREMOVAL]) return false;
3347 if (Modified[IE_EA]>EA_GOODCUTOFF && Modified[IE_STATE_ID]&STATE_INVISIBLE) return false;
3350 if (ga_flags&GA_NO_ALLY) {
3351 if(InParty) return false;
3352 if(Modified[IE_EA]<=EA_GOODCUTOFF) return false;
3355 if (ga_flags&GA_NO_ENEMY) {
3356 if(!InParty && (Modified[IE_EA]>=EA_EVILCUTOFF) ) return false;
3359 if (ga_flags&GA_NO_NEUTRAL) {
3360 if((Modified[IE_EA]>EA_GOODCUTOFF) && (Modified[IE_EA]<EA_EVILCUTOFF) ) return false;
3363 switch(ga_flags&GA_ACTION) {
3364 case GA_PICK:
3365 if (Modified[IE_STATE_ID] & STATE_CANTSTEAL) return false;
3366 break;
3367 case GA_TALK:
3368 //can't talk to dead
3369 if (Modified[IE_STATE_ID] & STATE_CANTLISTEN) return false;
3370 //can't talk to hostile
3371 if (Modified[IE_EA]>=EA_EVILCUTOFF) return false;
3372 break;
3374 if (ga_flags&GA_NO_DEAD) {
3375 if (InternalFlags&IF_JUSTDIED) return false;
3376 if (Modified[IE_STATE_ID] & STATE_DEAD) return false;
3378 if (ga_flags&GA_SELECT) {
3379 if (UnselectableTimer) return false;
3381 return true;
3384 //returns true if it won't be destroyed with an area
3385 //in this case it shouldn't be saved with the area either
3386 //it will be saved in the savegame
3387 bool Actor::Persistent() const
3389 if (InParty) return true;
3390 if (InternalFlags&IF_FROMGAME) return true;
3391 return false;
3394 //this is a reimplementation of cheatkey a/s of bg2
3395 //cycling through animation/stance
3396 // a - get next animation, s - get next stance
3398 void Actor::GetNextAnimation()
3400 int RowNum = anims->AvatarsRowNum - 1;
3401 if (RowNum<0)
3402 RowNum = CharAnimations::GetAvatarsCount() - 1;
3403 int NewAnimID = CharAnimations::GetAvatarStruct(RowNum)->AnimID;
3404 printf ("AnimID: %04X\n", NewAnimID);
3405 SetBase( IE_ANIMATION_ID, NewAnimID);
3406 //SetAnimationID ( NewAnimID );
3409 void Actor::GetPrevAnimation()
3411 int RowNum = anims->AvatarsRowNum + 1;
3412 if (RowNum>=CharAnimations::GetAvatarsCount() )
3413 RowNum = 0;
3414 int NewAnimID = CharAnimations::GetAvatarStruct(RowNum)->AnimID;
3415 printf ("AnimID: %04X\n", NewAnimID);
3416 SetBase( IE_ANIMATION_ID, NewAnimID);
3417 //SetAnimationID ( NewAnimID );
3420 //slot is the projectile slot
3421 //This will return the projectile item.
3422 ITMExtHeader *Actor::GetRangedWeapon(WeaponInfo &wi) const
3424 //EquippedSlot is the projectile. To get the weapon, use inventory.GetUsedWeapon()
3425 wi.slot = inventory.GetEquippedSlot();
3426 const CREItem *wield = inventory.GetSlotItem(wi.slot);
3427 if (!wield) {
3428 return NULL;
3430 Item *item = gamedata->GetItem(wield->ItemResRef);
3431 if (!item) {
3432 return NULL;
3434 //The magic of the bow and the arrow add up?
3435 wi.enchantment += item->Enchantment;
3436 wi.itemflags = wield->Flags;
3437 //wi.range is not set, the projectile has no effect on range?
3439 ITMExtHeader *which = item->GetWeaponHeader(true);
3440 gamedata->FreeItem(item, wield->ItemResRef, false);
3441 return which;
3444 int Actor::IsDualWielding() const
3446 int slot;
3447 //if the shield slot is a weapon, we're dual wielding
3448 const CREItem *wield = inventory.GetUsedWeapon(true, slot);
3449 if (!wield) {
3450 return 0;
3453 Item *itm = gamedata->GetItem( wield->ItemResRef );
3454 if (!itm) {
3455 return 0;
3458 //if the item is usable in weapon slot, then it is weapon
3459 int weapon = core->CanUseItemType( SLOT_WEAPON, itm );
3460 gamedata->FreeItem( itm, wield->ItemResRef, false );
3461 //is just weapon>0 ok?
3462 return (weapon>0)?1:0;
3465 //returns weapon header currently used (bow in case of bow+arrow)
3466 //if range is nonzero, then the returned header is valid
3467 ITMExtHeader *Actor::GetWeapon(WeaponInfo &wi, bool leftorright)
3469 //only use the shield slot if we are dual wielding
3470 leftorright = leftorright && IsDualWielding();
3472 const CREItem *wield = inventory.GetUsedWeapon(leftorright, wi.slot);
3473 if (!wield) {
3474 return 0;
3476 Item *item = gamedata->GetItem(wield->ItemResRef);
3477 if (!item) {
3478 return 0;
3481 wi.enchantment = item->Enchantment;
3482 wi.itemflags = wield->Flags;
3483 wi.prof = item->WeaProf;
3485 //select first weapon header
3486 ITMExtHeader *which;
3487 if (GetAttackStyle() == WEAPON_RANGED) {
3488 which = item->GetWeaponHeader(true);
3489 wi.backstabbing = false;
3490 } else {
3491 which = item->GetWeaponHeader(false);
3492 // any melee weapon usable by a single class thief is game (UAI does not affect this)
3493 wi.backstabbing = !(item->UsabilityBitmask & 0x400000);
3496 //make sure we use 'false' in this freeitem
3497 //so 'which' won't point into invalid memory
3498 gamedata->FreeItem(item, wield->ItemResRef, false);
3499 if (!which) {
3500 return 0;
3502 if (which->Location!=ITEM_LOC_WEAPON) {
3503 return 0;
3505 wi.range = which->Range+1;
3506 return which;
3507 //return which->Range+1;
3510 void Actor::GetNextStance()
3512 static int Stance = IE_ANI_AWAKE;
3514 if (--Stance < 0) Stance = MAX_ANIMS-1;
3515 printf ("StanceID: %d\n", Stance);
3516 SetStance( Stance );
3519 int Actor::LearnSpell(const ieResRef spellname, ieDword flags)
3521 if (spellbook.HaveSpell(spellname, 0) ) {
3522 return LSR_KNOWN;
3524 Spell *spell = gamedata->GetSpell(spellname);
3525 if (!spell) {
3526 return LSR_INVALID; //not existent spell
3529 if (flags & LS_STATS) {
3530 // chance to learn roll
3531 if (LuckyRoll(1,100,0) > core->GetIntelligenceBonus(0, GetStat(IE_INT))) {
3532 return LSR_FAILED;
3536 int explev = spellbook.LearnSpell(spell, flags&LS_MEMO);
3537 int tmp = spell->SpellName;
3538 if (flags&LS_LEARN) {
3539 core->GetTokenDictionary()->SetAt("SPECIALABILITYNAME", core->GetString(tmp));
3540 switch (spell->SpellType) {
3541 case IE_SPL_INNATE:
3542 tmp = STR_GOTABILITY;
3543 break;
3544 case IE_SPL_SONG:
3545 tmp = STR_GOTSONG;
3546 break;
3547 default:
3548 tmp = STR_GOTSPELL;
3549 break;
3551 } else tmp = 0;
3552 gamedata->FreeSpell(spell, spellname, false);
3553 if (!explev) {
3554 return LSR_INVALID;
3556 if (tmp) {
3557 core->DisplayConstantStringName(tmp, 0xbcefbc, this);
3559 if (flags&LS_ADDXP) {
3560 AddExperience(XP_LEARNSPELL, explev);
3562 return LSR_OK;
3565 const char *Actor::GetDialog(int flags) const
3567 if (!flags) {
3568 return Dialog;
3570 if (Modified[IE_EA]>=EA_EVILCUTOFF) {
3571 return NULL;
3574 if ( (InternalFlags & IF_NOINT) && CurrentAction) {
3575 if (flags>1) {
3576 core->DisplayConstantString(STR_TARGETBUSY,0xff0000);
3578 return NULL;
3580 return Dialog;
3583 void Actor::CreateStats()
3585 if (!PCStats) {
3586 PCStats = new PCStatsStruct();
3590 const char* Actor::GetScript(int ScriptIndex) const
3592 return Scripts[ScriptIndex]->GetName();
3595 void Actor::SetModal(ieDword newstate, bool force)
3597 switch(newstate) {
3598 case MS_NONE:
3599 break;
3600 case MS_BATTLESONG:
3601 break;
3602 case MS_DETECTTRAPS:
3603 break;
3604 case MS_STEALTH:
3605 break;
3606 case MS_TURNUNDEAD:
3607 break;
3608 default:
3609 return;
3612 if (InParty) {
3613 // display the turning-off message
3614 if (ModalState != MS_NONE) {
3615 core->DisplayStringName(core->ModalStates[ModalState].leaving_str, 0xffffff, this, IE_STR_SOUND|IE_STR_SPEECH);
3618 // when called with the same state twice, toggle to MS_NONE
3619 if (!force && ModalState == newstate) {
3620 ModalState = MS_NONE;
3621 } else {
3622 ModalState = newstate;
3625 //update the action bar
3626 core->SetEventFlag(EF_ACTION);
3627 } else {
3628 ModalState = newstate;
3632 void Actor::SetModalSpell(ieDword state, const char *spell)
3634 if (spell) {
3635 strnlwrcpy(ModalSpell, spell, 8);
3636 } else {
3637 if (state >= core->ModalStates.size()) {
3638 ModalSpell[0] = 0;
3639 } else {
3640 strnlwrcpy(ModalSpell, core->ModalStates[state].spell, 8);
3645 //this is just a stub function for now, attackstyle could be melee/ranged
3646 //even spells got this attack style
3647 int Actor::GetAttackStyle()
3649 WeaponInfo wi;
3650 //Non NULL if the equipped slot is a projectile or a throwing weapon
3651 //TODO some weapons have both melee and ranged capability
3652 if (GetRangedWeapon(wi) != NULL) return WEAPON_RANGED;
3653 return WEAPON_MELEE;
3656 void Actor::SetTarget( Scriptable *target)
3658 if (target->Type==ST_ACTOR) {
3659 Actor *tar = (Actor *) target;
3660 LastTarget = tar->GetID();
3661 tar->LastAttacker = GetID();
3662 //we tell the game object that this creature
3663 //must be added to the list of combatants
3664 core->GetGame()->InAttack(tar->LastAttacker);
3666 SetOrientation( GetOrient( target->Pos, Pos ), false );
3669 //in case of LastTarget = 0
3670 void Actor::StopAttack()
3672 SetStance(IE_ANI_READY);
3673 core->GetGame()->OutAttack(GetID());
3674 InternalFlags|=IF_TARGETGONE; //this is for the trigger!
3675 if (InParty) {
3676 core->Autopause(AP_NOTARGET);
3680 int Actor::Immobile() const
3682 if (GetStat(IE_CASTERHOLD)) {
3683 return 1;
3685 if (GetStat(IE_HELD)) {
3686 return 1;
3688 return 0;
3691 //calculate how many attacks will be performed
3692 //in the next round
3693 //only called when Game thinks we are in attack
3694 //so it is safe to do cleanup here (it will be called only once)
3695 void Actor::InitRound(ieDword gameTime, bool secondround)
3697 lastInit = gameTime;
3699 //roundTime will equal 0 if we aren't attacking something
3700 if (roundTime) {
3701 //only perform calculations at the beginning of the round!
3702 if (((gameTime-roundTime)%ROUND_SIZE != 0) || \
3703 (roundTime == lastInit)) {
3704 return;
3708 //reset variables used in PerformAttack
3709 attackcount = 0;
3710 attacksperround = 0;
3711 nextattack = 0;
3712 lastattack = 0;
3714 //we set roundTime to zero on any of the following returns, because this
3715 //is guaranteed to be the start of a round, and we only want roundTime
3716 //if we are attacking this round
3717 if (InternalFlags&IF_STOPATTACK) {
3718 core->GetGame()->OutAttack(GetID());
3719 roundTime = 0;
3720 return;
3723 if (!LastTarget) {
3724 StopAttack();
3725 roundTime = 0;
3726 return;
3729 //if held or disabled, etc, then cannot continue attacking
3730 ieDword state = GetStat(IE_STATE_ID);
3731 if (state&STATE_CANTMOVE) {
3732 roundTime = 0;
3733 return;
3735 if (Immobile()) {
3736 roundTime = 0;
3737 return;
3740 //add one for second round to get an extra attack only if we
3741 //are x/2 attacks per round
3742 attackcount = GetStat(IE_NUMBEROFATTACKS);
3743 if (secondround) {
3744 attackcount++;
3746 //all numbers of attacks are stored at twice their value
3747 attackcount >>= 1;
3749 //make sure we always get at least 1apr
3750 if (attackcount < 1) attackcount = 1;
3752 //set our apr and starting round time
3753 attacksperround = attackcount;
3754 roundTime = gameTime;
3756 //print a little message :)
3757 printMessage("InitRound", " ", WHITE);
3758 printf("Name: %s | Attacks: %d | Start: %d\n", ShortName, attacksperround, gameTime);
3760 // this might not be the right place, but let's give it a go
3761 if (attacksperround && InParty) {
3762 core->Autopause(AP_ENDROUND);
3766 bool Actor::GetCombatDetails(int &tohit, bool leftorright, WeaponInfo& wi, ITMExtHeader *&header, ITMExtHeader *&hittingheader, \
3767 ieDword &Flags, int &DamageBonus, int &speed, int &CriticalBonus, int &style)
3769 tohit = GetStat(IE_TOHIT);
3770 speed = -GetStat(IE_PHYSICALSPEED);
3771 bool dualwielding = IsDualWielding();
3772 header = GetWeapon(wi, leftorright && dualwielding);
3773 if (!header) {
3774 return false;
3776 style = 0;
3777 CriticalBonus = 0;
3778 hittingheader = header;
3779 ITMExtHeader *rangedheader = NULL;
3780 int THAC0Bonus = hittingheader->THAC0Bonus;
3781 DamageBonus = hittingheader->DamageBonus;
3782 switch(hittingheader->AttackType) {
3783 case ITEM_AT_MELEE:
3784 Flags = WEAPON_MELEE;
3785 break;
3786 case ITEM_AT_PROJECTILE: //throwing weapon
3787 Flags = WEAPON_RANGED;
3788 break;
3789 case ITEM_AT_BOW:
3790 rangedheader = GetRangedWeapon(wi);
3791 if (!rangedheader) {
3792 //display out of ammo verbal constant if there is any???
3793 //DisplayStringCore(this, VB_OUTOFAMMO, DS_CONSOLE|DS_CONST );
3794 SetStance(IE_ANI_READY);
3795 //set some trigger?
3796 return false;
3798 Flags = WEAPON_RANGED;
3799 //The bow can give some bonuses, but the core attack is made by the arrow.
3800 hittingheader = rangedheader;
3801 THAC0Bonus += rangedheader->THAC0Bonus;
3802 DamageBonus += rangedheader->DamageBonus;
3803 break;
3804 default:
3805 //item is unsuitable for fight
3806 SetStance(IE_ANI_READY);
3807 return false;
3808 }//melee or ranged
3809 //this flag is set by the bow in case of projectile launcher.
3810 if (header->RechargeFlags&IE_ITEM_USESTRENGTH) Flags|=WEAPON_USESTRENGTH;
3812 // get our dual wielding modifier
3813 if (dualwielding) {
3814 if (leftorright) {
3815 DamageBonus += GetStat(IE_DAMAGEBONUSLEFT);
3816 } else {
3817 DamageBonus += GetStat(IE_DAMAGEBONUSRIGHT);
3820 leftorright = leftorright && dualwielding;
3821 if (leftorright) Flags|=WEAPON_LEFTHAND;
3823 //add in proficiency bonuses
3824 ieDword stars;
3825 if (wi.prof && (wi.prof <= MAX_STATS)) {
3826 stars = GetStat(wi.prof)&PROFS_MASK;
3828 //hit/damage/speed bonuses from wspecial
3829 if ((signed)stars > wspecial_max) {
3830 stars = wspecial_max;
3832 THAC0Bonus += wspecial[stars][0];
3833 DamageBonus += wspecial[stars][1];
3834 speed += wspecial[stars][2];
3835 // add non-proficiency penalty, which is missing from the table
3836 if (stars == 0) THAC0Bonus -= 4;
3839 if (IsDualWielding() && wsdualwield) {
3840 //add dual wielding penalty
3841 stars = GetStat(IE_PROFICIENCY2WEAPON)&PROFS_MASK;
3842 if (stars > STYLE_MAX) stars = STYLE_MAX;
3844 style = 1000*stars + IE_PROFICIENCY2WEAPON;
3845 THAC0Bonus += wsdualwield[stars][leftorright?1:0];
3846 } else if (wi.itemflags&(IE_INV_ITEM_TWOHANDED) && (Flags&WEAPON_MELEE) && wstwohanded) {
3847 //add two handed profs bonus
3848 stars = GetStat(IE_PROFICIENCY2HANDED)&PROFS_MASK;
3849 if (stars > STYLE_MAX) stars = STYLE_MAX;
3851 style = 1000*stars + IE_PROFICIENCY2HANDED;
3852 DamageBonus += wstwohanded[stars][0];
3853 CriticalBonus = wstwohanded[stars][1];
3854 speed += wstwohanded[stars][2];
3855 } else if (Flags&WEAPON_MELEE) {
3856 int slot;
3857 CREItem *weapon = inventory.GetUsedWeapon(true, slot);
3858 if(wssingle && weapon == NULL) {
3859 //NULL return from GetUsedWeapon means no shield slot
3860 stars = GetStat(IE_PROFICIENCYSINGLEWEAPON)&PROFS_MASK;
3861 if (stars > STYLE_MAX) stars = STYLE_MAX;
3863 style = 1000*stars + IE_PROFICIENCYSINGLEWEAPON;
3864 CriticalBonus = wssingle[stars][1];
3865 } else if (wsswordshield && weapon) {
3866 stars = GetStat(IE_PROFICIENCYSWORDANDSHIELD)&PROFS_MASK;
3867 if (stars > STYLE_MAX) stars = STYLE_MAX;
3869 style = 1000*stars + IE_PROFICIENCYSWORDANDSHIELD;
3870 } else {
3871 // no bonus
3873 } else {
3874 // ranged - no bonus
3877 // TODO: Elves get a racial THAC0 bonus with all swords and bows in BG2 (but not daggers)
3879 //second parameter is left or right hand flag
3880 tohit = GetToHit(THAC0Bonus, Flags);
3881 return true;
3884 int Actor::GetToHit(int bonus, ieDword Flags)
3886 int tohit = bonus;
3888 //get our dual wielding modifier
3889 if (IsDualWielding()) {
3890 if (Flags&WEAPON_LEFTHAND) {
3891 tohit += GetStat(IE_HITBONUSLEFT);
3892 } else {
3893 tohit += GetStat(IE_HITBONUSRIGHT);
3897 //get attack style (melee or ranged)
3898 switch(Flags&WEAPON_STYLEMASK) {
3899 case WEAPON_MELEE:
3900 tohit += GetStat(IE_MELEETOHIT);
3901 break;
3902 case WEAPON_FIST:
3903 tohit += GetStat(IE_FISTHIT);
3904 break;
3905 case WEAPON_RANGED:
3906 tohit += GetStat(IE_MISSILEHITBONUS);
3907 //add dexterity bonus
3908 tohit += core->GetDexterityBonus(STAT_DEX_MISSILE, GetStat(IE_DEX));
3909 break;
3912 //add strength bonus if we need
3913 if (Flags&WEAPON_USESTRENGTH) {
3914 tohit += core->GetStrengthBonus(0,GetStat(IE_STR), GetStat(IE_STREXTRA) );
3917 // if the target is using a ranged weapon while we're meleeing, we get a +4 bonus
3918 if ((Flags&WEAPON_STYLEMASK) != WEAPON_RANGED) {
3919 Actor *target = area->GetActorByGlobalID(LastTarget);
3920 if (target && target->GetAttackStyle() == WEAPON_RANGED) {
3921 tohit += 4;
3925 // add +4 attack bonus vs racial enemies
3926 if (GetRangerLevel()) {
3927 Actor *target = area->GetActorByGlobalID(LastTarget);
3928 if (target && IsRacialEnemy(target)) {
3929 tohit += 4;
3933 if (ReverseToHit) {
3934 tohit = (signed)GetStat(IE_TOHIT)-tohit;
3935 } else {
3936 tohit += GetStat(IE_TOHIT);
3938 return tohit;
3941 static const int weapon_damagetype[] = {DAMAGE_CRUSHING, DAMAGE_PIERCING,
3942 DAMAGE_CRUSHING, DAMAGE_SLASHING, DAMAGE_MISSILE, DAMAGE_STUNNING};
3944 int Actor::GetDefense(int DamageType)
3946 //specific damage type bonus.
3947 int defense = 0;
3948 if(DamageType > 5)
3949 DamageType = 0;
3950 switch (weapon_damagetype[DamageType]) {
3951 case DAMAGE_CRUSHING:
3952 defense += GetStat(IE_ACCRUSHINGMOD);
3953 break;
3954 case DAMAGE_PIERCING:
3955 defense += GetStat(IE_ACPIERCINGMOD);
3956 break;
3957 case DAMAGE_SLASHING:
3958 defense += GetStat(IE_ACSLASHINGMOD);
3959 break;
3960 case DAMAGE_MISSILE:
3961 defense += GetStat(IE_ACMISSILEMOD);
3962 break;
3963 //What about stunning ?
3964 default :
3965 break;
3968 //check for s/s and single weapon ac bonuses
3969 if (!IsDualWielding() && wssingle && wsswordshield) {
3970 WeaponInfo wi;
3971 ITMExtHeader* header;
3972 header = GetWeapon(wi, false);
3973 //make sure we're wielding a single melee weapon
3974 if (header && (header->AttackType == ITEM_AT_MELEE)) {
3975 int slot;
3976 ieDword stars;
3977 if (inventory.GetUsedWeapon(true, slot) == NULL) {
3978 //single-weapon style applies to all ac
3979 stars = GetStat(IE_PROFICIENCYSINGLEWEAPON)&PROFS_MASK;
3980 if (stars>STYLE_MAX) stars = STYLE_MAX;
3981 defense += wssingle[stars][0];
3982 } else if (weapon_damagetype[DamageType] == DAMAGE_MISSILE) {
3983 //sword-shield style applies only to missile ac
3984 stars = GetStat(IE_PROFICIENCYSWORDANDSHIELD)&PROFS_MASK;
3985 if (stars>STYLE_MAX) stars = STYLE_MAX;
3986 defense += wsswordshield[stars][0];
3991 if (ReverseToHit) {
3992 defense = GetStat(IE_ARMORCLASS)-defense;
3993 } else {
3994 defense += GetStat(IE_ARMORCLASS);
3996 //Dexterity bonus is stored negative in 2da files.
3997 return defense + core->GetDexterityBonus(STAT_DEX_AC, GetStat(IE_DEX) );
4000 void Actor::PerformAttack(ieDword gameTime)
4002 // start a new round if we really don't have one yet
4003 if (!roundTime) {
4004 InitRound(gameTime, false);
4007 //only return if we don't have any attacks left this round
4008 if (attackcount==0) return;
4010 // this check shouldn't be necessary, but it causes a divide-by-zero below,
4011 // so i would like it to be clear if it ever happens
4012 if (attacksperround==0) {
4013 printMessage("Actor", "APR was 0 in PerformAttack!\n", RED);
4014 return;
4017 //don't continue if we can't make the attack yet
4018 //we check lastattack because we will get the same gameTime a few times
4019 if ((nextattack > gameTime) || (gameTime == lastattack)) {
4020 // fuzzie added the following line as part of the UpdateActorState hack below
4021 lastattack = gameTime;
4022 return;
4025 if (InternalFlags&IF_STOPATTACK) {
4026 // this should be avoided by the AF_ALIVE check by all the calling actions
4027 printMessage("Actor", "Attack by dead actor!\n", LIGHT_RED);
4028 return;
4031 if (!LastTarget) {
4032 printMessage("Actor", "Attack without valid target ID!\n", LIGHT_RED);
4033 return;
4035 //get target
4036 Actor *target = area->GetActorByGlobalID(LastTarget);
4038 if (target && (target->GetStat(IE_STATE_ID)&STATE_DEAD)) {
4039 target = NULL;
4042 if (!target) {
4043 printMessage("Actor", "Attack without valid target!\n", LIGHT_RED);
4044 return;
4047 printf("Performattack for %s, target is: %s\n", ShortName, target->ShortName);
4049 //which hand is used
4050 //we do apr - attacksleft so we always use the main hand first
4051 bool leftorright = (bool) ((attacksperround-attackcount)&1);
4053 WeaponInfo wi;
4054 ITMExtHeader *header = NULL;
4055 ITMExtHeader *hittingheader = NULL;
4056 int tohit;
4057 ieDword Flags;
4058 int DamageBonus, CriticalBonus;
4059 int speed, style;
4061 //will return false on any errors (eg, unusable weapon)
4062 if (!GetCombatDetails(tohit, leftorright, wi, header, hittingheader, Flags, DamageBonus, speed, CriticalBonus, style)) {
4063 return;
4066 //if this is the first call of the round, we need to update next attack
4067 if (nextattack == 0) {
4068 //FIXME: figure out exactly how initiative is calculated
4069 int initiative = core->Roll(1, 5, GetXPLevel(true)/(-8));
4070 int spdfactor = hittingheader->Speed + speed + initiative;
4071 if (spdfactor<0) spdfactor = 0;
4072 if (spdfactor>10) spdfactor = 10;
4074 //(round_size/attacks_per_round)*(initiative) is the first delta
4075 nextattack = ROUND_SIZE*spdfactor/(attacksperround*10) + gameTime;
4077 //we can still attack this round if we have a speed factor of 0
4078 if (nextattack > gameTime) {
4079 return;
4083 if((PersonalDistance(this, target) > wi.range*10) || (GetCurrentArea()!=target->GetCurrentArea() ) ) {
4084 // this is a temporary double-check, remove when bugfixed
4085 printMessage("Actor", "Attack action didn't bring us close enough!", LIGHT_RED);
4086 return;
4089 SetStance(AttackStance);
4091 //figure out the time for our next attack since the old time has the initiative
4092 //in it, we only have to add the basic delta
4093 attackcount--;
4094 nextattack += (ROUND_SIZE/attacksperround);
4095 lastattack = gameTime;
4097 //debug messages
4098 if (leftorright && IsDualWielding()) {
4099 printMessage("Attack","(Off) ", YELLOW);
4100 } else {
4101 printMessage("Attack","(Main) ", GREEN);
4103 if (attacksperround) {
4104 printf("Left: %d | ", attackcount);
4105 printf("Next: %d ", nextattack);
4108 int roll = LuckyRoll(1, ATTACKROLL, 0);
4109 if (roll==1) {
4110 //critical failure
4111 printBracket("Critical Miss", RED);
4112 printf("\n");
4113 core->DisplayConstantStringName(STR_CRITICAL_MISS, 0xffffff, this);
4114 DisplayStringCore(this, VB_CRITMISS, DS_CONSOLE|DS_CONST );
4115 if (Flags&WEAPON_RANGED) {//no need for this with melee weapon!
4116 UseItem(wi.slot, (ieDword)-2, target, UI_MISS);
4117 } else if (core->HasFeature(GF_BREAKABLE_WEAPONS)) {
4118 //break sword
4119 //TODO: this appears to be a random roll on-hit (perhaps critical failure
4120 // too); we use 1% (1d20*1d5==1)
4121 if ((header->RechargeFlags&IE_ITEM_BREAKABLE) && core->Roll(1,5,0) == 1) {
4122 inventory.BreakItemSlot(wi.slot);
4125 ResetState();
4126 return;
4128 //damage type is?
4129 //modify defense with damage type
4130 ieDword damagetype = hittingheader->DamageType;
4131 int damage = 0;
4132 int resisted = 0;
4134 if (hittingheader->DiceThrown<256) {
4135 damage += LuckyRoll(hittingheader->DiceThrown, hittingheader->DiceSides, 0, 1, 0);
4136 damage += DamageBonus;
4137 printf("| Damage %dd%d%+d = %d ",hittingheader->DiceThrown, hittingheader->DiceSides, DamageBonus, damage);
4138 } else {
4139 printf("| No Damage");
4140 damage = 0;
4143 if (roll >= (ATTACKROLL - (int) GetStat(IE_CRITICALHITBONUS) - CriticalBonus)) {
4144 //critical success
4145 printBracket("Critical Hit", GREEN);
4146 printf("\n");
4147 core->DisplayConstantStringName(STR_CRITICAL_HIT, 0xffffff, this);
4148 DisplayStringCore(this, VB_CRITHIT, DS_CONSOLE|DS_CONST );
4149 ModifyDamage (target, this, damage, resisted, weapon_damagetype[damagetype], &wi, true);
4150 UseItem(wi.slot, Flags&WEAPON_RANGED?-2:-1, target, 0, damage);
4151 ResetState();
4153 return;
4157 //get target's defense against attack
4158 int defense = target->GetDefense(damagetype);
4160 bool success;
4161 if(ReverseToHit) {
4162 success = roll > tohit - defense;
4163 } else {
4164 success = tohit + roll > defense;
4167 if (!success) {
4168 //hit failed
4169 if (Flags&WEAPON_RANGED) {//Launch the projectile anyway
4170 UseItem(wi.slot, (ieDword)-2, target, UI_MISS);
4172 ResetState();
4173 printBracket("Missed", LIGHT_RED);
4174 printf("\n");
4175 return;
4177 printBracket("Hit", GREEN);
4178 printf("\n");
4179 ModifyDamage (target, this, damage, resisted, weapon_damagetype[damagetype], &wi, false);
4180 UseItem(wi.slot, Flags&WEAPON_RANGED?-2:-1, target, 0, damage);
4181 ResetState();
4184 static EffectRef fx_stoneskin_ref={"StoneSkinModifier",NULL,-1};
4185 static EffectRef fx_stoneskin2_ref={"StoneSkin2Modifier",NULL,-1};
4186 static EffectRef fx_mirrorimage_ref={"MirrorImageModifier",NULL,-1};
4187 static EffectRef fx_aegis_ref={"Aegis",NULL,-1};
4189 void Actor::ModifyDamage(Actor *target, Scriptable *hitter, int &damage, int &resisted, int damagetype, WeaponInfo *wi, bool critical)
4192 int mirrorimages = target->Modified[IE_MIRRORIMAGES];
4193 if (mirrorimages) {
4194 if (LuckyRoll(1,mirrorimages+1,0) != 1) {
4195 target->fxqueue.DecreaseParam1OfEffect(fx_mirrorimage_ref, 1);
4196 target->Modified[IE_MIRRORIMAGES]--;
4197 damage = 0;
4198 return;
4202 // only check stone skins if damage type is physical or magical
4203 // DAMAGE_CRUSHING is 0, so we can't AND with it to check for its presence
4204 if (!(damagetype & ~(DAMAGE_PIERCING|DAMAGE_SLASHING|DAMAGE_MISSILE|DAMAGE_MAGIC))) {
4205 int stoneskins = target->Modified[IE_STONESKINS];
4206 if (stoneskins) {
4207 target->fxqueue.DecreaseParam1OfEffect(fx_stoneskin_ref, 1);
4208 target->fxqueue.DecreaseParam1OfEffect(fx_aegis_ref, 1);
4209 target->Modified[IE_STONESKINS]--;
4210 damage = 0;
4211 return;
4214 stoneskins = target->Modified[IE_STONESKINSGOLEM];
4215 if (stoneskins) {
4216 target->fxqueue.DecreaseParam1OfEffect(fx_stoneskin2_ref, 1);
4217 target->Modified[IE_STONESKINSGOLEM]--;
4218 damage = 0;
4219 return;
4223 if (wi) {
4224 if (BaseStats[IE_BACKSTABDAMAGEMULTIPLIER] > 1) {
4225 if (Modified[IE_STATE_ID] & (ieDword) (STATE_INVISIBLE) || Modified[IE_ALWAYSBACKSTAB]) {
4226 if ( !(core->HasFeature(GF_PROPER_BACKSTAB) && !IsBehind(target)) ) {
4227 if (target->Modified[IE_DISABLEBACKSTAB]) {
4228 // The backstab seems to have failed
4229 core->DisplayConstantString (STR_BACKSTAB_FAIL, 0xffffff);
4230 } else {
4231 if (wi->backstabbing) {
4232 damage *= Modified[IE_BACKSTABDAMAGEMULTIPLIER];
4233 // display a simple message instead of hardcoding multiplier names
4234 core->DisplayConstantStringValue (STR_BACKSTAB, 0xffffff, Modified[IE_BACKSTABDAMAGEMULTIPLIER]);
4235 } else {
4236 // weapon is unsuitable for backstab
4237 core->DisplayConstantString (STR_BACKSTAB_BAD, 0xffffff);
4244 // add strength bonus; backstab does not affect it
4245 // TODO: should actually check WEAPON_USESTRENGTH, since a sling in bg2 has it
4246 if (GetAttackStyle() != WEAPON_RANGED) {
4247 damage += core->GetStrengthBonus(1, GetStat(IE_STR), GetStat(IE_STREXTRA) );
4251 if (wi && target->fxqueue.WeaponImmunity(wi->enchantment, wi->itemflags)) {
4252 damage = 0;
4253 resisted = DR_IMMUNE; // mark immunity for GetCombatDetails
4254 } else {
4255 // check damage type immunity / resistance / susceptibility
4256 std::multimap<ieDword, DamageInfoStruct>::iterator it;
4257 it = core->DamageInfoMap.find(damagetype);
4258 if (it == core->DamageInfoMap.end()) {
4259 printf("Unhandled damagetype:%d\n", damagetype);
4260 } else if (it->second.resist_stat == 0) {
4261 // damage type without a resistance stat
4262 } else {
4263 damage += (signed)target->GetStat(IE_DAMAGEBONUS);
4264 resisted = (int) (damage * (signed)target->GetStat(it->second.resist_stat)/100.0);
4265 // check for bonuses for specific damage types
4266 if (core->HasFeature(GF_SPECIFIC_DMG_BONUS) && hitter && hitter->Type == ST_ACTOR) {
4267 int bonus = ((Actor *)hitter)->fxqueue.SpecificDamageBonus(it->second.iwd_mod_type);
4268 if (bonus) {
4269 resisted -= int (damage * bonus / 100.0);
4270 printf("Bonus damage of %d (%+d%%), neto: %d\n", int (damage * bonus / 100.0), bonus, -resisted);
4273 damage -= resisted;
4274 printf("Resisted %d of %d at %d%% resistance to %d\n", resisted, damage+resisted, target->GetStat(it->second.resist_stat), damagetype);
4275 if (damage <= 0) resisted = DR_IMMUNE;
4279 //check casting failure
4280 if (damage<0) damage = 0;
4281 if (!damage) {
4282 DisplayStringCore(this, VB_TIMMUNE, DS_CONSOLE|DS_CONST );
4283 return;
4286 if (critical) {
4287 //a critical surely raises the morale?
4288 NewBase(IE_MORALE, 1, MOD_ADDITIVE);
4289 int head = inventory.GetHeadSlot();
4290 if ((head!=-1) && target->inventory.HasItemInSlot("",(ieDword) head)) {
4291 //critical hit is averted by helmet
4292 core->DisplayConstantStringName(STR_NO_CRITICAL, 0xffffff, target);
4293 } else {
4294 damage <<=1; //critical damage is always double?
4295 core->timer->SetScreenShake(16,16,8);
4298 return;
4301 void Actor::UpdateActorState(ieDword gameTime) {
4302 //apply the modal effect on the beginning of each round
4303 if (((gameTime-roundTime)%ROUND_SIZE==0) && ModalState) {
4304 if (!ModalSpell[0]) {
4305 printMessage("Actor","Modal Spell Effect was not set!\n", YELLOW);
4306 ModalSpell[0]='*';
4307 } else if(ModalSpell[0]!='*') {
4308 if (ModalSpellSkillCheck()) {
4309 if (core->ModalStates[ModalState].aoe_spell) {
4310 core->ApplySpellPoint(ModalSpell, GetCurrentArea(), Pos, this, 0);
4311 } else {
4312 core->ApplySpell(ModalSpell, this, this, 0);
4314 if (InParty) {
4315 core->DisplayStringName(core->ModalStates[ModalState].entering_str, 0xffffff, this, IE_STR_SOUND|IE_STR_SPEECH);
4317 } else {
4318 if (InParty) {
4319 core->DisplayStringName(core->ModalStates[ModalState].failed_str, 0xffffff, this, IE_STR_SOUND|IE_STR_SPEECH);
4321 ModalState = MS_NONE;
4322 // TODO: wait for a round until allowing new states?
4327 // this is a HACK, fuzzie can't work out where else to do this for now
4328 // but we shouldn't be resetting rounds/attacks just because the actor
4329 // wandered away, the action code should probably be responsible somehow
4330 // see also line above (search for comment containing UpdateActorState)!
4331 if (LastTarget && lastattack && lastattack < (gameTime - 1)) {
4332 Actor *target = area->GetActorByGlobalID(LastTarget);
4333 if (!target || target->GetStat(IE_STATE_ID)&STATE_DEAD) {
4334 StopAttack();
4335 } else {
4336 printMessage("Attack","(Leaving attack)", GREEN);
4337 core->GetGame()->OutAttack(GetID());
4340 roundTime = 0;
4341 lastattack = 0;
4345 //idx could be: 0-6, 16-22, 32-38, 48-54
4346 //the colors are stored in 7 dwords
4347 //maybe it would be simpler to store them in 28 bytes (without using stats?)
4348 void Actor::SetColor( ieDword idx, ieDword grd)
4350 ieByte gradient = (ieByte) (grd&255);
4351 ieByte index = (ieByte) (idx&15);
4352 ieByte shift = (ieByte) (idx/16);
4353 ieDword value;
4355 //invalid value, would crash original IE
4356 if (index>6) {
4357 return;
4359 if (shift == 15) {
4360 // put gradient in all four bytes of value
4361 value = gradient;
4362 value |= (value << 8);
4363 value |= (value << 16);
4364 for (index=0;index<7;index++) {
4365 Modified[IE_COLORS+index] = value;
4367 } else {
4368 //invalid value, would crash original IE
4369 if (shift>3) {
4370 return;
4372 shift *= 8;
4373 value = gradient << shift;
4374 value |= Modified[IE_COLORS+index] & ~(255<<shift);
4375 Modified[IE_COLORS+index] = value;
4379 void Actor::SetColorMod( ieDword location, RGBModifier::Type type, int speed,
4380 unsigned char r, unsigned char g, unsigned char b,
4381 int phase)
4383 CharAnimations* ca = GetAnims();
4384 if (!ca) return;
4386 if (location == 0xff) {
4387 ca->GlobalColorMod.type = type;
4388 ca->GlobalColorMod.speed = speed;
4389 ca->GlobalColorMod.rgb.r = r;
4390 ca->GlobalColorMod.rgb.g = g;
4391 ca->GlobalColorMod.rgb.b = b;
4392 ca->GlobalColorMod.rgb.a = 0;
4393 if (phase >= 0)
4394 ca->GlobalColorMod.phase = phase;
4395 else {
4396 if (ca->GlobalColorMod.phase > 2*speed)
4397 ca->GlobalColorMod.phase=0;
4399 return;
4401 //00xx0yyy-->000xxyyy
4402 if (location&0xffffffc8) return; //invalid location
4403 location = (location &7) | ((location>>1)&0x18);
4404 ca->ColorMods[location].type = type;
4405 ca->ColorMods[location].speed = speed;
4406 ca->ColorMods[location].rgb.r = r;
4407 ca->ColorMods[location].rgb.g = g;
4408 ca->ColorMods[location].rgb.b = b;
4409 ca->ColorMods[location].rgb.a = 0;
4410 if (phase >= 0)
4411 ca->ColorMods[location].phase = phase;
4412 else {
4413 if (ca->ColorMods[location].phase > 2*speed)
4414 ca->ColorMods[location].phase = 0;
4418 void Actor::SetLeader(Actor *actor, int xoffset, int yoffset)
4420 LastFollowed = actor->GetID();
4421 FollowOffset.x = xoffset;
4422 FollowOffset.y = yoffset;
4425 //if days == 0, it means full healing
4426 void Actor::Heal(int days)
4428 if (days) {
4429 SetBase(IE_HITPOINTS, BaseStats[IE_HITPOINTS]+days*2);
4430 } else {
4431 SetBase(IE_HITPOINTS, BaseStats[IE_MAXHITPOINTS]);
4435 void Actor::AddExperience(int exp)
4437 SetBase(IE_XP,BaseStats[IE_XP]+exp);
4440 void Actor::AddExperience(int type, int level)
4442 if (type>=xpbonustypes) {
4443 return;
4445 unsigned int l = (unsigned int) (level - 1);
4447 if (l>=(unsigned int) xpbonuslevels) {
4448 l=xpbonuslevels-1;
4450 int xp = xpbonus[type*xpbonuslevels+l];
4451 core->DisplayConstantStringValue(STR_GOTXP, 0xbcefbc, (ieDword) xp);
4452 AddExperience(xp);
4455 bool Actor::Schedule(ieDword gametime, bool checkhide)
4457 if (checkhide) {
4458 if (!(InternalFlags&IF_VISIBLE) ) {
4459 return false;
4463 //check for schedule
4464 ieDword bit = 1<<((gametime/ROUND_SIZE)%7200/300);
4465 if (appearance & bit) {
4466 return true;
4468 return false;
4471 void Actor::NewPath()
4473 PathTries++;
4474 Point tmp = Destination;
4475 ClearPath();
4476 if (PathTries>10) {
4477 return;
4479 Movable::WalkTo(tmp, size );
4482 void Actor::SetInTrap(ieDword setreset)
4484 InTrap = setreset;
4485 if (setreset) {
4486 InternalFlags |= IF_INTRAP;
4487 } else {
4488 InternalFlags &= ~IF_INTRAP;
4492 void Actor::SetRunFlags(ieDword flags)
4494 InternalFlags &= ~IF_RUNFLAGS;
4495 InternalFlags |= (flags & IF_RUNFLAGS);
4498 void Actor::WalkTo(const Point &Des, ieDword flags, int MinDistance)
4500 PathTries = 0;
4501 if (InternalFlags&IF_REALLYDIED) {
4502 return;
4504 SetRunFlags(flags);
4505 // is this true???
4506 if (Des.x==-2 && Des.y==-2) {
4507 Point p((ieWord) Modified[IE_SAVEDXPOS], (ieWord) Modified[IE_SAVEDYPOS] );
4508 Movable::WalkTo(p, MinDistance);
4509 } else {
4510 Movable::WalkTo(Des, MinDistance);
4514 //there is a similar function in Map for stationary vvcs
4515 void Actor::DrawVideocells(const Region &screen, vvcVector &vvcCells, const Color &tint)
4517 Map* area = GetCurrentArea();
4519 for (unsigned int i = 0; i < vvcCells.size(); i++) {
4520 ScriptedAnimation* vvc = vvcCells[i];
4521 /* we don't allow holes anymore
4522 if (!vvc)
4523 continue;
4526 // actually this is better be drawn by the vvc
4527 bool endReached = vvc->Draw(screen, Pos, tint, area, WantDither(), GetOrientation());
4528 if (endReached) {
4529 delete vvc;
4530 vvcCells.erase(vvcCells.begin()+i);
4531 continue;
4536 void Actor::DrawActorSprite(const Region &screen, int cx, int cy, const Region& bbox,
4537 SpriteCover*& newsc, Animation** anims,
4538 unsigned char Face, const Color& tint)
4540 CharAnimations* ca = GetAnims();
4541 int PartCount = ca->GetTotalPartCount();
4542 Video* video = core->GetVideoDriver();
4543 Region vp = video->GetViewport();
4545 // display current frames in the right order
4546 const int* zOrder = ca->GetZOrder(Face);
4547 for (int part = 0; part < PartCount; ++part) {
4548 int partnum = part;
4549 if (zOrder) partnum = zOrder[part];
4550 Animation* anim = anims[partnum];
4551 Sprite2D* nextFrame = 0;
4552 if (anim)
4553 nextFrame = anim->GetFrame(anim->GetCurrentFrame());
4554 if (nextFrame && bbox.InsideRegion( vp ) ) {
4555 if (!newsc || !newsc->Covers(cx, cy, nextFrame->XPos, nextFrame->YPos, nextFrame->Width, nextFrame->Height)) {
4556 // the first anim contains the animarea for
4557 // the entire multi-part animation
4558 newsc = area->BuildSpriteCover(cx,
4559 cy, -anims[0]->animArea.x,
4560 -anims[0]->animArea.y,
4561 anims[0]->animArea.w,
4562 anims[0]->animArea.h, WantDither() );
4564 assert(newsc->Covers(cx, cy, nextFrame->XPos, nextFrame->YPos, nextFrame->Width, nextFrame->Height));
4566 unsigned int flags = TranslucentShadows ? BLIT_TRANSSHADOW : 0;
4567 if (!ca->lockPalette) flags|=BLIT_TINTED;
4569 video->BlitGameSprite( nextFrame, cx + screen.x, cy + screen.y,
4570 flags, tint, newsc, ca->GetPartPalette(partnum), &screen);
4576 static const int OrientdX[16] = { 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9, 10, 9, 7, 4 };
4577 static const int OrientdY[16] = { 10, 9, 7, 4, 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9 };
4578 static const unsigned int MirrorImageLocation[8] = { 4, 12, 8, 0, 6, 14, 10, 2 };
4579 static const unsigned int MirrorImageZOrder[8] = { 2, 4, 6, 0, 1, 7, 5, 3 };
4581 bool Actor::ShouldHibernate() {
4582 //finding an excuse why we don't hybernate the actor
4583 if (Modified[IE_ENABLEOFFSCREENAI])
4584 return false;
4585 if (LastTarget) //currently attacking someone
4586 return false;
4587 if (!lastRunTime) // haven't had a chance to run a script
4588 return false;
4589 if (CurrentAction)
4590 return false;
4591 if (GetNextStep())
4592 return false;
4593 if (GetNextAction())
4594 return false;
4595 if (GetWait()) //would never stop waiting
4596 return false;
4597 return true;
4600 void Actor::Draw(const Region &screen)
4602 Map* area = GetCurrentArea();
4604 int cx = Pos.x;
4605 int cy = Pos.y;
4606 int explored = Modified[IE_DONOTJUMP]&DNJ_UNHINDERED;
4607 //check the deactivation condition only if needed
4608 //this fixes dead actors disappearing from fog of war (they should be permanently visible)
4609 if ((!area->IsVisible( Pos, explored) || (InternalFlags&IF_REALLYDIED) ) && (InternalFlags&IF_ACTIVE) ) {
4610 //turning actor inactive if there is no action next turn
4611 if (ShouldHibernate()) {
4612 InternalFlags|=IF_IDLE;
4614 if (!(InternalFlags&IF_REALLYDIED)) {
4615 // for a while this didn't return (disable drawing) if about to hibernate;
4616 // Avenger said (aa10aaed) "we draw the actor now for the last time".
4617 return;
4621 if (InTrap) {
4622 area->ClearTrap(this, InTrap-1);
4625 // if an actor isn't visible, should we still handle animations, draw
4626 // video cells, etc? let us assume not, for now..
4627 if (!(InternalFlags & IF_VISIBLE)) {
4628 return;
4631 //visual feedback
4632 CharAnimations* ca = GetAnims();
4633 if (!ca)
4634 return;
4636 //explored or visibilitymap (bird animations are visible in fog)
4637 //0 means opaque
4638 int NoCircle = Modified[IE_NOCIRCLE];
4639 int Trans = Modified[IE_TRANSLUCENT];
4641 if (Trans>255) {
4642 Trans=255;
4645 //iwd has this flag saved in the creature
4646 if (Modified[IE_AVATARREMOVAL]) {
4647 Trans = 255;
4648 NoCircle = 1;
4651 int Frozen = Immobile();
4652 int State = Modified[IE_STATE_ID];
4654 if (State&STATE_DEAD) {
4655 NoCircle = 1;
4658 if (State&STATE_STILL) {
4659 Frozen = 1;
4662 //adjust invisibility for enemies
4663 if (Modified[IE_EA]>EA_GOODCUTOFF) {
4664 if (State&STATE_INVISIBLE) {
4665 Trans = 255;
4669 //can't move this, because there is permanent blur state where
4670 //there is no effect (just state bit)
4671 if ((State&STATE_BLUR) && Trans < 128) {
4672 Trans = 128;
4674 Color tint = area->LightMap->GetPixel( cx / 16, cy / 12);
4675 tint.a = (ieByte) (255-Trans);
4677 unsigned char heightmapindex = area->HeightMap->GetAt( cx / 16, cy / 12);
4678 if (heightmapindex > 15) {
4679 // there are 8bpp lightmaps (eg, bg2's AR1300) and fuzzie
4680 // cannot work out how they work, so here is an incorrect
4681 // hack (probably). please fix!
4682 heightmapindex = 15;
4685 //don't use cy for area map access beyond this point
4686 cy -= heightmapindex;
4688 //draw videocells under the actor
4689 DrawVideocells(screen, vvcShields, tint);
4691 Video* video = core->GetVideoDriver();
4692 Region vp = video->GetViewport();
4694 bool drawcircle = (NoCircle == 0);
4695 if ((core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE)) {
4696 // ground circles are not drawn in cutscenes
4697 // they SHOULD be drawn for at least white speaker circles
4698 // (eg, via VerbalConstant), please fix :)
4699 drawcircle = false;
4701 if (BaseStats[IE_STATE_ID]&STATE_DEAD || InternalFlags&IF_JUSTDIED) {
4702 drawcircle = false;
4704 bool drawtarget = drawcircle;
4705 // we always show circle/target on pause
4706 if (drawcircle && !(core->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS)) {
4707 // check marker feedback level
4708 ieDword markerfeedback = 4;
4709 core->GetDictionary()->Lookup("GUI Feedback Level", markerfeedback);
4710 if (Over) {
4711 // picked creature
4712 drawcircle = markerfeedback >= 1;
4713 } else if (Selected) {
4714 // selected creature
4715 drawcircle = markerfeedback >= 2;
4716 } else if (IsPC()) {
4717 // selectable
4718 drawcircle = markerfeedback >= 3;
4719 } else if (Modified[IE_EA] >= EA_EVILCUTOFF) {
4720 // hostile
4721 drawcircle = markerfeedback >= 5;
4722 } else {
4723 // all
4724 drawcircle = markerfeedback >= 6;
4726 drawtarget = Selected && markerfeedback >= 4;
4728 if (drawcircle) {
4729 DrawCircle(vp);
4731 if (drawtarget) {
4732 DrawTargetPoint(vp);
4735 unsigned char StanceID = GetStance();
4736 unsigned char Face = GetNextFace();
4737 Animation** anims = ca->GetAnimation( StanceID, Face );
4738 if (anims) {
4739 // update bounding box and such
4740 int PartCount = ca->GetTotalPartCount();
4741 Sprite2D* nextFrame = 0;
4742 nextFrame = anims[0]->GetFrame(anims[0]->GetCurrentFrame());
4744 //make actor unselectable and unselected when it is not moving
4745 //dead, petriefied, frozen, paralysed etc.
4746 if (Frozen) {
4747 if (Selected!=0x80) {
4748 Selected = 0x80;
4749 core->GetGame()->SelectActor(this, false, SELECT_NORMAL);
4752 //If you find a better place for it, I'll really be glad to put it there
4753 //IN BG1 and BG2, this is at the ninth frame...
4754 if(attackProjectile && (anims[0]->GetCurrentFrame() == 8/*anims[0]->GetFramesCount()/2*/)) {
4755 GetCurrentArea()->AddProjectile(attackProjectile, Pos, LastTarget);
4756 attackProjectile = NULL;
4758 if (nextFrame && lastFrame != nextFrame) {
4759 Region newBBox;
4760 if (PartCount == 1) {
4761 newBBox.x = cx - nextFrame->XPos;
4762 newBBox.w = nextFrame->Width;
4763 newBBox.y = cy - nextFrame->YPos;
4764 newBBox.h = nextFrame->Height;
4765 } else {
4766 // FIXME: currently using the animarea instead
4767 // of the real bounding box of this (multi-part) frame.
4768 // Shouldn't matter much, though. (wjp)
4769 newBBox.x = cx + anims[0]->animArea.x;
4770 newBBox.y = cy + anims[0]->animArea.y;
4771 newBBox.w = anims[0]->animArea.w;
4772 newBBox.h = anims[0]->animArea.h;
4774 lastFrame = nextFrame;
4775 SetBBox( newBBox );
4778 // Drawing the actor:
4779 // * mirror images:
4780 // Drawn without transparency, unless fully invisible.
4781 // Order: W, E, N, S, NW, SE, NE, SW
4782 // Uses extraCovers 3-10
4783 // * blurred copies (3 of them)
4784 // Drawn with transparency.
4785 // distance between copies depends on IE_MOVEMENTRATE
4786 // TODO: actually, the direction is the real movement direction,
4787 // not the (rounded) direction given Face
4788 // Uses extraCovers 0-2
4789 // * actor itself
4790 // Uses main spritecover
4792 //comments by Avenger:
4793 // currently we don't have a real direction, but the orientation field
4794 // could be used with higher granularity. When we need the face value
4795 // it could be divided so it will become a 0-15 number.
4798 SpriteCover *sc = 0, *newsc = 0;
4799 int blurx = cx;
4800 int blury = cy;
4801 int blurdx = (OrientdX[Face]*(int)Modified[IE_MOVEMENTRATE])/20;
4802 int blurdy = (OrientdY[Face]*(int)Modified[IE_MOVEMENTRATE])/20;
4803 Color mirrortint = tint;
4804 //mirror images are also half transparent when invis
4805 //if (mirrortint.a > 0) mirrortint.a = 255;
4807 int i;
4809 // mirror images behind the actor
4810 for (i = 0; i < 4; ++i) {
4811 unsigned int m = MirrorImageZOrder[i];
4812 if (m < Modified[IE_MIRRORIMAGES]) {
4813 Region sbbox = BBox;
4814 int dir = MirrorImageLocation[m];
4815 int icx = cx + 3*OrientdX[dir];
4816 int icy = cy + 3*OrientdY[dir];
4817 Point iPos(icx, icy);
4818 if (area->GetBlocked(iPos) & (PATH_MAP_PASSABLE|PATH_MAP_ACTOR)) {
4819 sbbox.x += 3*OrientdX[dir];
4820 sbbox.y += 3*OrientdY[dir];
4821 newsc = sc = extraCovers[3+m];
4822 DrawActorSprite(screen, icx, icy, sbbox, newsc,
4823 anims, Face, mirrortint);
4824 if (newsc != sc) {
4825 delete sc;
4826 extraCovers[3+m] = newsc;
4829 } else {
4830 delete extraCovers[3+m];
4831 extraCovers[3+m] = NULL;
4835 // blur sprites behind the actor
4836 if (State & STATE_BLUR) {
4837 if (Face < 4 || Face >= 12) {
4838 Region sbbox = BBox;
4839 sbbox.x -= 4*blurdx; sbbox.y -= 4*blurdy;
4840 blurx -= 4*blurdx; blury -= 4*blurdy;
4841 for (i = 0; i < 3; ++i) {
4842 sbbox.x += blurdx; sbbox.y += blurdy;
4843 blurx += blurdx; blury += blurdy;
4844 newsc = sc = extraCovers[i];
4845 DrawActorSprite(screen, blurx, blury, sbbox, newsc,
4846 anims, Face, tint);
4847 if (newsc != sc) {
4848 delete sc;
4849 extraCovers[i] = newsc;
4855 // actor itself
4856 newsc = sc = GetSpriteCover();
4857 DrawActorSprite(screen, cx, cy, BBox, newsc, anims, Face, tint);
4858 if (newsc != sc) SetSpriteCover(newsc);
4860 // blur sprites in front of the actor
4861 if (State & STATE_BLUR) {
4862 if (Face >= 4 && Face < 12) {
4863 Region sbbox = BBox;
4864 for (i = 0; i < 3; ++i) {
4865 sbbox.x -= blurdx; sbbox.y -= blurdy;
4866 blurx -= blurdx; blury -= blurdy;
4867 newsc = sc = extraCovers[i];
4868 DrawActorSprite(screen, blurx, blury, sbbox, newsc,
4869 anims, Face, tint);
4870 if (newsc != sc) {
4871 delete sc;
4872 extraCovers[i] = newsc;
4878 // mirror images in front of the actor
4879 for (i = 4; i < 8; ++i) {
4880 unsigned int m = MirrorImageZOrder[i];
4881 if (m < Modified[IE_MIRRORIMAGES]) {
4882 Region sbbox = BBox;
4883 int dir = MirrorImageLocation[m];
4884 int icx = cx + 3*OrientdX[dir];
4885 int icy = cy + 3*OrientdY[dir];
4886 Point iPos(icx, icy);
4887 if (area->GetBlocked(iPos) & (PATH_MAP_PASSABLE|PATH_MAP_ACTOR)) {
4888 sbbox.x += 3*OrientdX[dir];
4889 sbbox.y += 3*OrientdY[dir];
4890 newsc = sc = extraCovers[3+m];
4891 DrawActorSprite(screen, icx, icy, sbbox, newsc,
4892 anims, Face, mirrortint);
4893 if (newsc != sc) {
4894 delete sc;
4895 extraCovers[3+m] = newsc;
4898 } else {
4899 delete extraCovers[3+m];
4900 extraCovers[3+m] = NULL;
4904 // advance animations one frame (in sync)
4905 if (Frozen)
4906 anims[0]->LastFrame();
4907 else
4908 anims[0]->NextFrame();
4910 for (int part = 1; part < PartCount; ++part) {
4911 if (anims[part])
4912 anims[part]->GetSyncedNextFrame(anims[0]);
4915 if (anims[0]->endReached) {
4916 if (HandleActorStance() ) {
4917 anims[0]->endReached = false;
4918 anims[0]->SetPos(0);
4922 ca->PulseRGBModifiers();
4925 //draw videocells over the actor
4926 DrawVideocells(screen, vvcOverlays, tint);
4929 /* Handling automatic stance changes */
4930 bool Actor::HandleActorStance()
4932 CharAnimations* ca = GetAnims();
4933 int StanceID = GetStance();
4935 if (ca->autoSwitchOnEnd) {
4936 int nextstance = ca->nextStanceID;
4938 if (nextstance == IE_ANI_READY) {
4939 if (!core->GetGame()->CombatCounter) {
4940 nextstance = IE_ANI_AWAKE;
4944 SetStance( nextstance );
4945 ca->autoSwitchOnEnd = false;
4946 return true;
4948 int x = rand()%1000;
4949 if ((StanceID==IE_ANI_AWAKE) && !x ) {
4950 SetStance( IE_ANI_HEAD_TURN );
4951 return true;
4953 // added CurrentAction as part of blocking action fixes
4954 if ((StanceID==IE_ANI_READY) && !CurrentAction && !GetNextAction()) {
4955 SetStance( IE_ANI_AWAKE );
4956 return true;
4958 if (StanceID == IE_ANI_ATTACK || StanceID == IE_ANI_ATTACK_JAB ||
4959 StanceID == IE_ANI_ATTACK_SLASH || StanceID == IE_ANI_ATTACK_BACKSLASH ||
4960 StanceID == IE_ANI_SHOOT)
4962 SetStance( AttackStance );
4963 return true;
4965 return false;
4968 void Actor::GetSoundFrom2DA(ieResRef Sound, unsigned int index)
4970 if (!anims) return;
4972 AutoTable tab(anims->ResRef);
4973 if (!tab)
4974 return;
4976 switch (index) {
4977 case VB_ATTACK:
4978 index = 0;
4979 break;
4980 case VB_DAMAGE:
4981 index = 8;
4982 break;
4983 case VB_DIE:
4984 index = 10;
4985 break;
4986 case VB_SELECT:
4987 index = 36+rand()%4;
4988 break;
4990 strnlwrcpy(Sound, tab->QueryField (index, 0), 8);
4993 void Actor::GetSoundFromINI(ieResRef Sound, unsigned int index)
4995 const char *resource = "";
4996 char section[12];
4997 unsigned int animid=BaseStats[IE_ANIMATION_ID];
4998 if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
4999 animid&=0xff;
5001 snprintf(section,10,"%d", animid);
5003 switch(index) {
5004 case VB_ATTACK:
5005 resource = core->GetResDataINI()->GetKeyAsString(section, "at1sound","");
5006 break;
5007 case VB_DAMAGE:
5008 resource = core->GetResDataINI()->GetKeyAsString(section, "hitsound","");
5009 break;
5010 case VB_DIE:
5011 resource = core->GetResDataINI()->GetKeyAsString(section, "dfbsound","");
5012 break;
5013 case VB_SELECT:
5014 break;
5016 int count = CountElements(resource,',');
5017 if (count<=0) return;
5018 count = core->Roll(1,count,-1);
5019 while(count--) {
5020 while(*resource && *resource!=',') resource++;
5021 if (*resource==',') resource++;
5023 strncpy(Sound, resource, 8);
5024 for(count=0;count<8 && Sound[count]!=',';count++) {};
5025 Sound[count]=0;
5028 void Actor::ResolveStringConstant(ieResRef Sound, unsigned int index)
5030 //resolving soundset (bg1/bg2 style)
5031 if (PCStats && PCStats->SoundSet[0]&& csound[index]) {
5032 snprintf(Sound, sizeof(ieResRef), "%s%c", PCStats->SoundSet, csound[index]);
5033 return;
5036 Sound[0]=0;
5038 if (core->HasFeature(GF_RESDATA_INI)) {
5039 GetSoundFromINI(Sound, index);
5040 } else {
5041 GetSoundFrom2DA(Sound, index);
5045 void Actor::SetActionButtonRow(ActionButtonRow &ar)
5047 for(int i=0;i<MAX_QSLOTS;i++) {
5048 ieByte tmp = ar[i+3];
5049 if (QslotTranslation) {
5050 tmp=gemrb2iwd[tmp];
5052 PCStats->QSlots[i]=tmp;
5056 //the first 3 buttons are untouched by this function
5057 void Actor::GetActionButtonRow(ActionButtonRow &ar)
5059 InitButtons(GetStat(IE_CLASS), false);
5060 for(int i=0;i<GUIBT_COUNT-3;i++) {
5061 ieByte tmp=PCStats->QSlots[i];
5062 if (QslotTranslation) {
5063 if (tmp>=90) { //quick weapons
5064 tmp=16+tmp%10;
5065 } else if (tmp>=80) { //quick items
5066 tmp=9+tmp%10;
5067 } else if (tmp>=70) { //quick spells
5068 tmp=3+tmp%10;
5069 } else {
5070 tmp=iwd2gemrb[tmp];
5073 ar[i+3]=tmp;
5075 memcpy(ar,DefaultButtons,3*sizeof(ieByte) );
5078 void Actor::SetPortrait(const char* ResRef, int Which)
5080 int i;
5082 if (ResRef == NULL) {
5083 return;
5085 if (InParty) {
5086 core->SetEventFlag(EF_PORTRAIT);
5089 if(Which!=1) {
5090 memset( SmallPortrait, 0, 8 );
5091 strncpy( SmallPortrait, ResRef, 8 );
5093 if(Which!=2) {
5094 memset( LargePortrait, 0, 8 );
5095 strncpy( LargePortrait, ResRef, 8 );
5097 if(!Which) {
5098 for (i = 0; i < 8 && ResRef[i]; i++) {};
5099 SmallPortrait[i] = 'S';
5100 LargePortrait[i] = 'M';
5104 void Actor::SetSoundFolder(const char *soundset)
5106 if (core->HasFeature(GF_SOUNDFOLDERS)) {
5107 char filepath[_MAX_PATH];
5109 strnlwrcpy(PCStats->SoundFolder, soundset, 32);
5110 PathJoin(filepath,core->GamePath,"sounds",PCStats->SoundFolder,0);
5111 char file[_MAX_PATH];
5112 if (FileGlob(file, filepath, "?????01")) {
5113 file[5] = '\0';
5114 } else if (FileGlob(file, filepath, "????01")) {
5115 file[4] = '\0';
5116 } else {
5117 return;
5119 strnlwrcpy(PCStats->SoundSet, file, 8);
5120 } else {
5121 strnlwrcpy(PCStats->SoundSet, soundset, 8);
5122 PCStats->SoundFolder[0]=0;
5126 bool Actor::HasVVCCell(const ieResRef resource)
5128 int j = true;
5129 vvcVector *vvcCells=&vvcShields;
5130 retry:
5131 size_t i=vvcCells->size();
5132 while (i--) {
5133 ScriptedAnimation *vvc = (*vvcCells)[i];
5134 if (vvc == NULL) {
5135 continue;
5137 if ( strnicmp(vvc->ResName, resource, 8) == 0) {
5138 return true;
5141 vvcCells=&vvcOverlays;
5142 if (j) { j = false; goto retry; }
5143 return false;
5146 void Actor::RemoveVVCell(const ieResRef resource, bool graceful)
5148 bool j = true;
5149 vvcVector *vvcCells=&vvcShields;
5150 retry:
5151 size_t i=vvcCells->size();
5152 while (i--) {
5153 ScriptedAnimation *vvc = (*vvcCells)[i];
5154 if (vvc == NULL) {
5155 continue;
5157 if ( strnicmp(vvc->ResName, resource, 8) == 0) {
5158 if (graceful) {
5159 vvc->SetPhase(P_RELEASE);
5160 } else {
5161 delete vvc;
5162 vvcCells->erase(vvcCells->begin()+i);
5166 vvcCells=&vvcOverlays;
5167 if (j) { j = false; goto retry; }
5170 //this is a faster version of hasvvccell, because it knows where to look
5171 //for the overlay, it also returns the vvc for further manipulation
5172 //use this for the seven eyes overlay
5173 ScriptedAnimation *Actor::FindOverlay(int index)
5175 vvcVector *vvcCells;
5177 if (index>31) return NULL;
5179 if (hc_locations&(1<<index)) vvcCells=&vvcShields;
5180 else vvcCells=&vvcOverlays;
5182 const char *resRef = hc_overlays[index];
5184 size_t i=vvcCells->size();
5185 while (i--) {
5186 ScriptedAnimation *vvc = (*vvcCells)[i];
5187 if (vvc == NULL) {
5188 continue;
5190 if ( strnicmp(vvc->ResName, resRef, 8) == 0) {
5191 return vvc;
5194 return NULL;
5197 void Actor::AddVVCell(ScriptedAnimation* vvc)
5199 vvcVector *vvcCells;
5201 //if the vvc was not created, don't try to add it
5202 if (!vvc) {
5203 return;
5205 if (vvc->ZPos<0) {
5206 vvcCells=&vvcShields;
5207 } else {
5208 vvcCells=&vvcOverlays;
5210 size_t i=vvcCells->size();
5211 while (i--) {
5212 if ((*vvcCells)[i] == NULL) {
5213 (*vvcCells)[i] = vvc;
5214 return;
5217 vvcCells->push_back( vvc );
5220 //returns restored spell level
5221 int Actor::RestoreSpellLevel(ieDword maxlevel, ieDword type)
5223 int typemask;
5225 switch (type) {
5226 case 0: //allow only mage
5227 typemask = ~2;
5228 break;
5229 case 1: //allow only cleric
5230 typemask = ~1;
5231 break;
5232 default:
5233 //allow any (including innates)
5234 typemask = ~0;
5236 for (int i=maxlevel;i>0;i--) {
5237 CREMemorizedSpell *cms = spellbook.FindUnchargedSpell(typemask, maxlevel);
5238 if (cms) {
5239 spellbook.ChargeSpell(cms);
5240 return i;
5243 return 0;
5246 //replenishes spells, cures fatigue
5247 void Actor::Rest(int hours)
5249 if (hours) {
5250 //do remove effects
5251 int remaining = hours*10;
5252 //removes hours*10 fatigue points
5253 NewStat (IE_FATIGUE, -remaining, MOD_ADDITIVE);
5254 NewStat (IE_INTOXICATION, -remaining, MOD_ADDITIVE);
5255 //restore hours*10 spell levels
5256 //rememorization starts with the lower spell levels?
5257 inventory.ChargeAllItems (remaining);
5258 for (int level = 1; level<16; level++) {
5259 if (level<remaining) {
5260 break;
5262 while (remaining>0) {
5263 remaining -= RestoreSpellLevel(level,0);
5266 } else {
5267 SetBase (IE_FATIGUE, 0);
5268 SetBase (IE_INTOXICATION, 0);
5269 inventory.ChargeAllItems (0);
5270 spellbook.ChargeAllSpells ();
5274 //returns the actual slot from the quickslot
5275 int Actor::GetQuickSlot(int slot)
5277 assert(slot<8);
5278 if (inventory.HasItemInSlot("",inventory.GetMagicSlot())) {
5279 return inventory.GetMagicSlot();
5281 if (!PCStats) {
5282 return slot+inventory.GetWeaponSlot();
5284 return PCStats->QuickWeaponSlots[slot];
5287 //marks the quickslot as equipped
5288 int Actor::SetEquippedQuickSlot(int slot, int header)
5290 if (!PCStats) {
5291 if (header<0) header=0;
5292 inventory.SetEquippedSlot(slot, header);
5293 return 0;
5297 if ((slot<0) || (slot == IW_NO_EQUIPPED) ) {
5298 if (slot == IW_NO_EQUIPPED) {
5299 slot = inventory.GetFistSlot();
5301 int i;
5302 for(i=0;i<MAX_QUICKWEAPONSLOT;i++) {
5303 if(slot+inventory.GetWeaponSlot()==PCStats->QuickWeaponSlots[i]) {
5304 slot = i;
5305 break;
5308 if (i==MAX_QUICKWEAPONSLOT) {
5309 return 0;
5313 assert(slot<MAX_QUICKWEAPONSLOT);
5314 if (header==-1) {
5315 header = PCStats->QuickWeaponHeaders[slot];
5317 else {
5318 PCStats->QuickWeaponHeaders[slot]=header;
5320 slot = PCStats->QuickWeaponSlots[slot]-inventory.GetWeaponSlot();
5321 Equipped = (ieWordSigned) slot;
5322 EquippedHeader = (ieWord) header;
5323 if (inventory.SetEquippedSlot(slot, header)) {
5324 return 0;
5326 return STR_MAGICWEAPON;
5329 //if target is a non living scriptable, then we simply shoot for its position
5330 //the fx should get a NULL target, and handle itself by using the position
5331 //(shouldn't crash when target is NULL)
5332 bool Actor::UseItemPoint(ieDword slot, ieDword header, const Point &target, ieDword flags)
5334 CREItem *item = inventory.GetSlotItem(slot);
5335 if (!item)
5336 return false;
5338 ieResRef tmpresref;
5339 strnuprcpy(tmpresref, item->ItemResRef, sizeof(ieResRef)-1);
5341 Item *itm = gamedata->GetItem(tmpresref);
5342 if (!itm) return false; //quick item slot contains invalid item resref
5343 //item is depleted for today
5344 if(itm->UseCharge(item->Usages, header, false)==CHG_DAY) {
5345 return false;
5348 Projectile *pro = itm->GetProjectile(slot, header, flags&UI_MISS);
5349 ChargeItem(slot, header, item, itm, flags&UI_SILENT);
5350 gamedata->FreeItem(itm,tmpresref, false);
5351 if (pro) {
5352 pro->SetCaster(globalID);
5353 GetCurrentArea()->AddProjectile(pro, Pos, target);
5354 return true;
5356 return false;
5359 static EffectRef fx_damage_ref={"Damage",NULL,-1};
5361 bool Actor::UseItem(ieDword slot, ieDword header, Scriptable* target, ieDword flags, int damage)
5363 if (target->Type!=ST_ACTOR) {
5364 return UseItemPoint(slot, header, target->Pos, flags);
5367 Actor *tar = (Actor *) target;
5368 CREItem *item = inventory.GetSlotItem(slot);
5369 if (!item)
5370 return false;
5372 ieResRef tmpresref;
5373 strnuprcpy(tmpresref, item->ItemResRef, sizeof(ieResRef)-1);
5375 Item *itm = gamedata->GetItem(tmpresref);
5376 if (!itm) return false; //quick item slot contains invalid item resref
5377 //item is depleted for today
5378 if (itm->UseCharge(item->Usages, header, false)==CHG_DAY) {
5379 return false;
5382 Projectile *pro = itm->GetProjectile(slot, header, flags&UI_MISS);
5383 ChargeItem(slot, header, item, itm, flags&UI_SILENT);
5384 gamedata->FreeItem(itm,tmpresref, false);
5385 if (pro) {
5386 //ieDword is unsigned!!
5387 pro->SetCaster(globalID);
5388 if(((int)header < 0) && !(flags&UI_MISS)) { //using a weapon
5389 ITMExtHeader *which = itm->GetWeaponHeader(header == (ieDword)-2);
5390 Effect* AttackEffect = EffectQueue::CreateEffect(fx_damage_ref, damage, (weapon_damagetype[which->DamageType])<<16, FX_DURATION_INSTANT_LIMITED);
5391 AttackEffect->Projectile = which->ProjectileAnimation;
5392 AttackEffect->Target = FX_TARGET_PRESET;
5393 pro->GetEffects()->AddEffect(AttackEffect, true);
5394 //AddEffect created a copy, the original needs to be scrapped
5395 delete AttackEffect;
5396 attackProjectile = pro;
5397 } else //launch it now as we are not attacking
5398 GetCurrentArea()->AddProjectile(pro, Pos, tar->globalID);
5399 return true;
5401 return false;
5404 void Actor::ChargeItem(ieDword slot, ieDword header, CREItem *item, Item *itm, bool silent)
5406 if (!itm) {
5407 item = inventory.GetSlotItem(slot);
5408 if (!item)
5409 return;
5410 itm = gamedata->GetItem(item->ItemResRef);
5412 if (!itm) return; //quick item slot contains invalid item resref
5414 if (InParty) {
5415 core->SetEventFlag( EF_ACTION );
5418 if (!silent) {
5419 ieByte stance = AttackStance;
5420 for (int i=0;i<animcount;i++) {
5421 if ( strnicmp(item->ItemResRef, itemanim[i].itemname, 8) == 0) {
5422 stance = itemanim[i].animation;
5425 if (stance!=0xff) {
5426 SetStance(stance);
5427 //play only one cycle of animations
5429 // this was crashing for fuzzie due to NULL anims
5430 if (anims) {
5431 anims->nextStanceID=IE_ANI_READY;
5432 anims->autoSwitchOnEnd=true;
5437 switch(itm->UseCharge(item->Usages, header, true)) {
5438 case CHG_DAY:
5439 break;
5440 case CHG_BREAK: //both
5441 if (!silent) {
5442 core->PlaySound(DS_ITEM_GONE);
5444 //fall through
5445 case CHG_NOSOUND: //remove item
5446 inventory.BreakItemSlot(slot);
5447 break;
5448 default: //don't do anything
5449 break;
5453 int Actor::IsReverseToHit()
5455 return ReverseToHit;
5458 void Actor::InitButtons(ieDword cls, bool forced)
5460 if (!PCStats) {
5461 return;
5463 if ( (PCStats->QSlots[0]!=0xff) && !forced) {
5464 return;
5467 ActionButtonRow myrow;
5468 if ((int) cls >= classcount) {
5469 memcpy(&myrow, &DefaultButtons, sizeof(ActionButtonRow));
5470 } else {
5471 memcpy(&myrow, GUIBTDefaults+cls, sizeof(ActionButtonRow));
5473 SetActionButtonRow(myrow);
5476 void Actor::SetFeat(unsigned int feat, int mode)
5478 if (feat>=MAX_FEATS) {
5479 return;
5481 ieDword mask = 1<<(feat&31);
5482 ieDword idx = feat>>5;
5483 switch (mode) {
5484 case BM_SET: case BM_OR:
5485 BaseStats[IE_FEATS1+idx]|=mask;
5486 break;
5487 case BM_NAND:
5488 BaseStats[IE_FEATS1+idx]&=~mask;
5489 break;
5490 case BM_XOR:
5491 BaseStats[IE_FEATS1+idx]^=mask;
5492 break;
5496 void Actor::SetUsedWeapon(const char* AnimationType, ieWord* MeleeAnimation, int wt)
5498 memcpy(WeaponRef, AnimationType, sizeof(WeaponRef) );
5499 if (wt != -1) WeaponType = wt;
5500 if (!anims)
5501 return;
5502 anims->SetWeaponRef(AnimationType);
5503 anims->SetWeaponType(WeaponType);
5504 SetAttackMoveChances(MeleeAnimation);
5505 if (InParty) {
5506 //update the paperdoll weapon animation
5507 core->SetEventFlag(EF_UPDATEANIM);
5509 WeaponInfo wi;
5510 ITMExtHeader *header = GetWeapon(wi);
5512 if(header && (header->AttackType == ITEM_AT_BOW)) {
5513 ITMExtHeader* projHeader = GetRangedWeapon(wi);
5514 if (projHeader->ProjectileQualifier == 0) return; /* no ammo yet? */
5515 AttackStance = IE_ANI_SHOOT;
5516 anims->SetRangedType(projHeader->ProjectileQualifier-1);
5517 //bows ARE one handed, from an anim POV at least
5518 anims->SetWeaponType(IE_ANI_WEAPON_1H);
5519 return;
5521 if(header && (header->AttackType == ITEM_AT_PROJECTILE)) {
5522 AttackStance = IE_ANI_ATTACK_SLASH; //That's it!!
5523 return;
5525 AttackStance = IE_ANI_ATTACK;
5528 void Actor::SetUsedShield(const char* AnimationType, int wt)
5530 memcpy(ShieldRef, AnimationType, sizeof(ShieldRef) );
5531 if (wt != -1) WeaponType = wt;
5532 if (AnimationType[0] == ' ' || AnimationType[0] == 0)
5533 if (WeaponType == IE_ANI_WEAPON_2W)
5534 WeaponType = IE_ANI_WEAPON_1H;
5536 if (!anims)
5537 return;
5538 anims->SetOffhandRef(AnimationType);
5539 anims->SetWeaponType(WeaponType);
5540 if (InParty) {
5541 //update the paperdoll weapon animation
5542 core->SetEventFlag(EF_UPDATEANIM);
5546 void Actor::SetUsedHelmet(const char* AnimationType)
5548 memcpy(HelmetRef, AnimationType, sizeof(HelmetRef) );
5549 if (!anims)
5550 return;
5551 anims->SetHelmetRef(AnimationType);
5552 if (InParty) {
5553 //update the paperdoll weapon animation
5554 core->SetEventFlag(EF_UPDATEANIM);
5558 void Actor::SetupFist()
5560 int slot = core->QuerySlot( 0 );
5561 assert (core->QuerySlotEffects(slot)==SLOT_EFFECT_FIST);
5562 int row = GetBase(fiststat);
5563 int col = GetXPLevel(false);
5565 if (FistRows<0) {
5566 FistRows=0;
5567 AutoTable fist("fistweap");
5568 if (fist) {
5569 //default value
5570 strnlwrcpy( DefaultFist, fist->QueryField( (unsigned int) -1), 8);
5571 FistRows = fist->GetRowCount();
5572 fistres = new FistResType[FistRows];
5573 for (int i=0;i<FistRows;i++) {
5574 int maxcol = fist->GetColumnCount(i)-1;
5575 for (int cols = 0;cols<MAX_LEVEL;cols++) {
5576 strnlwrcpy( fistres[i][cols], fist->QueryField( i, cols>maxcol?maxcol:cols ), 8);
5578 *(int *) fistres[i] = atoi(fist->GetRowName( i));
5582 if (col>MAX_LEVEL) col=MAX_LEVEL;
5583 if (col<1) col=1;
5585 const char *ItemResRef = DefaultFist;
5586 for (int i = 0;i<FistRows;i++) {
5587 if (*(int *) fistres[i] == row) {
5588 ItemResRef = fistres[i][col];
5591 inventory.SetSlotItemRes(ItemResRef, slot);
5594 static ieDword ResolveTableValue(const char *resref, ieDword stat, ieDword mcol, ieDword vcol) {
5595 long ret = 0;
5596 //don't close this table, it can mess with the guiscripts
5597 int table = gamedata->LoadTable(resref);
5598 Holder<TableMgr> tm = gamedata->GetTable(table);
5599 if (tm) {
5600 unsigned int row;
5601 if (mcol == 0xff) {
5602 row = stat;
5603 } else {
5604 row = tm->FindTableValue(mcol, stat);
5605 if (row==0xffffffff) {
5606 return 0;
5609 if (valid_number(tm->QueryField(row, vcol), ret)) {
5610 return (ieDword) ret;
5614 return 0;
5617 static ieDword GetKitIndex (ieDword kit, const char *resref="kitlist")
5619 int kitindex = 0;
5621 if ((kit&BG2_KITMASK) == KIT_BARBARIAN) {
5622 kitindex = kit&0xfff;
5625 // carefully looking for kit by the usability flag
5626 // since the barbarian kit id clashes with the no-kit value
5627 if (kitindex == 0 && kit != KIT_BARBARIAN) {
5628 Holder<TableMgr> tm = gamedata->GetTable(gamedata->LoadTable(resref) );
5629 if (tm) {
5630 kitindex = tm->FindTableValue(6, kit);
5631 if (kitindex < 0) {
5632 kitindex = 0;
5637 return (ieDword)kitindex;
5640 int Actor::CheckUsability(Item *item) const
5642 ieDword itembits[2]={item->UsabilityBitmask, item->KitUsability};
5644 for (int i=0;i<usecount;i++) {
5645 ieDword itemvalue = itembits[itemuse[i].which];
5646 ieDword stat = GetStat(itemuse[i].stat);
5647 ieDword mcol = itemuse[i].mcol;
5648 //if we have a kit, we just we use it's index for the lookup
5649 if (itemuse[i].stat==IE_KIT) {
5650 stat = GetKitIndex(stat, itemuse[i].table);
5651 mcol = 0xff;
5653 stat = ResolveTableValue(itemuse[i].table, stat, mcol, itemuse[i].vcol);
5654 if (stat&itemvalue) {
5655 //printf("failed usability: itemvalue %d, stat %d, stat value %d\n", itemvalue, itemuse[i].stat, stat);
5656 return STR_CANNOT_USE_ITEM;
5660 return 0;
5663 //checks usability only
5664 int Actor::Unusable(Item *item) const
5666 if (!GetStat(IE_CANUSEANYITEM)) {
5667 int unusable = CheckUsability(item);
5668 if (unusable) {
5669 return unusable;
5673 // iesdp says this is always checked?
5674 if (item->MinLevel>GetXPLevel(true)) {
5675 return STR_CANNOT_USE_ITEM;
5678 if (!CheckAbilities) {
5679 return 0;
5682 if (item->MinStrength>GetStat(IE_STR)) {
5683 return STR_CANNOT_USE_ITEM;
5686 if (item->MinStrength==18) {
5687 if (GetStat(IE_STR)==18) {
5688 if (item->MinStrengthBonus>GetStat(IE_STREXTRA)) {
5689 return STR_CANNOT_USE_ITEM;
5694 if (item->MinIntelligence>GetStat(IE_INT)) {
5695 return STR_CANNOT_USE_ITEM;
5697 if (item->MinDexterity>GetStat(IE_DEX)) {
5698 return STR_CANNOT_USE_ITEM;
5700 if (item->MinWisdom>GetStat(IE_WIS)) {
5701 return STR_CANNOT_USE_ITEM;
5703 if (item->MinConstitution>GetStat(IE_CON)) {
5704 return STR_CANNOT_USE_ITEM;
5706 if (item->MinCharisma>GetStat(IE_CHR)) {
5707 return STR_CANNOT_USE_ITEM;
5709 //note, weapon proficiencies shouldn't be checked here
5710 //missing proficiency causes only attack penalty
5711 return 0;
5714 //full palette will be shaded in gradient color
5715 void Actor::SetGradient(ieDword gradient)
5717 gradient |= (gradient <<16);
5718 gradient |= (gradient <<8);
5719 for(int i=0;i<7;i++) {
5720 Modified[IE_COLORS+i]=gradient;
5724 //sets one bit of the sanctuary stat (used for overlays)
5725 void Actor::SetOverlay(unsigned int overlay)
5727 if (overlay>=32) return;
5728 Modified[IE_SANCTUARY]|=1<<overlay;
5731 //returns true if spell state is already set or illegal
5732 bool Actor::SetSpellState(unsigned int spellstate)
5734 if (spellstate>=192) return true;
5735 unsigned int pos = IE_SPLSTATE_ID1+(spellstate>>5);
5736 unsigned int bit = 1<<(spellstate&31);
5737 if (Modified[pos]&bit) return true;
5738 Modified[pos]|=bit;
5739 return false;
5742 //returns true if spell state is already set
5743 bool Actor::HasSpellState(unsigned int spellstate)
5745 if (spellstate>=192) return false;
5746 unsigned int pos = IE_SPLSTATE_ID1+(spellstate>>5);
5747 unsigned int bit = 1<<(spellstate&31);
5748 if (Modified[pos]&bit) return true;
5749 return false;
5752 //returns the numeric value of a feat, different from HasFeat
5753 //for multiple feats
5754 int Actor::GetFeat(unsigned int feat) const
5756 if (feat>=MAX_FEATS) {
5757 return -1;
5759 if (Modified[IE_FEATS1+(feat>>5)]&(1<<(feat&31)) ) {
5760 //return the numeric stat value, instead of the boolean
5761 if (featstats[feat]) {
5762 return Modified[featstats[feat]];
5764 return 1;
5766 return 0;
5769 //returns true if the feat exists
5770 bool Actor::HasFeat(unsigned int featindex) const
5772 if (featindex>=MAX_FEATS) return false;
5773 unsigned int pos = IE_FEATS1+(featindex>>5);
5774 unsigned int bit = 1<<(featindex&31);
5775 if (Modified[pos]&bit) return true;
5776 return false;
5779 ieDword Actor::ImmuneToProjectile(ieDword projectile) const
5781 int idx;
5783 idx = projectile/32;
5784 if (idx>ProjectileSize) {
5785 return 0;
5787 return projectileImmunity[idx]&(1<<(projectile&31));
5790 void Actor::AddProjectileImmunity(ieDword projectile)
5792 projectileImmunity[projectile/32]|=1<<(projectile&31);
5795 //2nd edition rules
5796 void Actor::CreateDerivedStatsBG()
5798 int turnundeadlevel = 0;
5799 int classid = BaseStats[IE_CLASS];
5801 //this works only for PC classes
5802 if (classid>=CLASS_PCCUTOFF) return;
5804 //recalculate all level based changes
5805 pcf_level(this,0,0);
5807 //even though the original didn't allow a cleric/paladin dual or multiclass
5808 //we shouldn't restrict the possibility by using "else if" here
5809 if (isclass[ISCLERIC]&(1<<classid)) {
5810 turnundeadlevel += GetClericLevel()+1-turnlevels[classid];
5811 if (turnundeadlevel<0) turnundeadlevel=0;
5813 if (isclass[ISPALADIN]&(1<<classid)) {
5814 turnundeadlevel += GetPaladinLevel()+1-turnlevels[classid];
5815 if (turnundeadlevel<0) turnundeadlevel=0;
5818 // barbarian immunity to backstab was hardcoded
5819 if (GetBarbarianLevel()) {
5820 BaseStats[IE_DISABLEBACKSTAB] = 1;
5823 ieDword backstabdamagemultiplier=GetThiefLevel();
5824 if (backstabdamagemultiplier) {
5825 // HACK: swashbucklers can't backstab
5826 if ((BaseStats[IE_KIT]&0xfff) == 12) {
5827 backstabdamagemultiplier = 1;
5828 } else {
5829 AutoTable tm("backstab");
5830 //fallback to a general algorithm (bg2 backstab.2da version) if we can't find backstab.2da
5831 //TODO: AP_SPCL332 (increase backstab by one) seems to not be effecting this at all
5832 //for assassins perhaps the effect is being called prior to this, and this overwrites it;
5833 //stalkers work correctly, which is even more odd, considering as they use the same
5834 //effect and backstabmultiplier would be 0 for them
5835 if (tm) {
5836 ieDword cols = tm->GetColumnCount();
5837 if (backstabdamagemultiplier >= cols) backstabdamagemultiplier = cols;
5838 backstabdamagemultiplier = atoi(tm->QueryField(0, backstabdamagemultiplier));
5839 } else {
5840 backstabdamagemultiplier = (backstabdamagemultiplier+7)/4;
5842 printf("\n");
5843 if (backstabdamagemultiplier>7) backstabdamagemultiplier=7;
5847 // monk's level dictated ac and ac vs missiles bonus
5848 // attacks per round bonus will be handled elsewhere, since it only applies to fist apr
5849 if (isclass[ISMONK]&(1<<classid)) {
5850 unsigned int level = GetMonkLevel()-1;
5851 BaseStats[IE_ARMORCLASS] = DEFAULTAC - monkbon[1][level];
5852 BaseStats[IE_ACMISSILEMOD] = - monkbon[2][level];
5855 BaseStats[IE_TURNUNDEADLEVEL]=turnundeadlevel;
5856 BaseStats[IE_BACKSTABDAMAGEMULTIPLIER]=backstabdamagemultiplier;
5857 BaseStats[IE_LAYONHANDSAMOUNT]=GetPaladinLevel()*2;
5860 //3rd edition rules
5861 void Actor::CreateDerivedStatsIWD2()
5863 int i;
5864 int turnundeadlevel = 0;
5866 ieDword backstabdamagemultiplier=GetThiefLevel();
5867 if (backstabdamagemultiplier) {
5868 AutoTable tm("backstab");
5869 if (tm) {
5870 ieDword cols = tm->GetColumnCount();
5871 if (backstabdamagemultiplier >= cols) backstabdamagemultiplier = cols;
5872 backstabdamagemultiplier = atoi(tm->QueryField(0, backstabdamagemultiplier));
5873 } else {
5874 backstabdamagemultiplier = (BaseStats[IE_LEVELTHIEF]+1)/2;
5876 printf("\n");
5877 if (backstabdamagemultiplier>7) backstabdamagemultiplier=7;
5880 int layonhandsamount = (int) BaseStats[IE_LEVELPALADIN];
5881 if (layonhandsamount) {
5882 layonhandsamount *= BaseStats[IE_CHR]/2-5;
5883 if (layonhandsamount<1) layonhandsamount = 1;
5886 for (i=0;i<11;i++) {
5887 int tmp;
5889 if (turnlevels[i+1]) {
5890 tmp = BaseStats[IE_LEVELBARBARIAN+i]+1-turnlevels[i+1];
5891 if (tmp<0) tmp=0;
5892 if (tmp>turnundeadlevel) turnundeadlevel=tmp;
5895 BaseStats[IE_TURNUNDEADLEVEL]=turnundeadlevel;
5896 BaseStats[IE_BACKSTABDAMAGEMULTIPLIER]=backstabdamagemultiplier;
5897 BaseStats[IE_LAYONHANDSAMOUNT]=(ieDword) layonhandsamount;
5900 //set up stuff here, like attack number, turn undead level
5901 //and similar derived stats that change with level
5902 void Actor::CreateDerivedStats()
5904 //we have to calculate multiclass for further code
5905 AutoTable tm("classes");
5906 if (tm) {
5907 // currently we need only the MULTI value
5908 char tmpmulti[8];
5909 long tmp;
5910 strcpy(tmpmulti, tm->QueryField(tm->FindTableValue(5, BaseStats[IE_CLASS]), 4));
5911 if (!valid_number(tmpmulti, tmp))
5912 multiclass = 0;
5913 else
5914 multiclass = (ieDword)tmp;
5917 if (core->HasFeature(GF_3ED_RULES)) {
5918 CreateDerivedStatsIWD2();
5919 } else {
5920 CreateDerivedStatsBG();
5923 /* Checks if the actor is multiclassed (the MULTI column is positive) */
5924 bool Actor::IsMultiClassed() const
5926 return (multiclass > 0);
5929 /* Checks if the actor is dualclassed */
5930 bool Actor::IsDualClassed() const
5932 return (Modified[IE_MC_FLAGS] & MC_WAS_ANY) > 0;
5935 Actor *Actor::CopySelf() const
5937 Actor *newActor = new Actor();
5939 newActor->SetName(GetName(0),0);
5940 newActor->SetName(GetName(1),1);
5941 memcpy(newActor->BaseStats, BaseStats, sizeof(BaseStats) );
5942 // illusions aren't worth any xp
5943 newActor->BaseStats[IE_XPVALUE] = 0;
5945 //IF_INITIALIZED shouldn't be set here, yet
5946 newActor->SetMCFlag(MC_EXPORTABLE, BM_NAND);
5948 //the creature importer does this too
5949 memcpy(newActor->Modified,newActor->BaseStats, sizeof(Modified) );
5951 //these need to be called too to have a valid inventory
5952 newActor->inventory.SetSlotCount(inventory.GetSlotCount());
5953 newActor->CreateDerivedStats();
5955 //copy the running effects
5956 EffectQueue *newFXQueue = fxqueue.CopySelf();
5958 area->AddActor(newActor);
5959 newActor->SetPosition( Pos, CC_CHECK_IMPASSABLE, 0 );
5960 newActor->SetOrientation(GetOrientation(),0);
5961 newActor->SetStance( IE_ANI_READY );
5963 //and apply them
5964 newActor->RefreshEffects(newFXQueue);
5965 return newActor;
5968 ieDword Actor::GetClassLevel(const ieDword id) const
5970 if (id>=ISCLASSES)
5971 return 0;
5973 //return iwd2 value if appropriate
5974 if (version==22)
5975 return BaseStats[levelslotsiwd2[id]];
5977 //houston, we gots a problem!
5978 if (!levelslots || !dualswap)
5979 return 0;
5981 //only works with PC's
5982 ieDword classid = BaseStats[IE_CLASS]-1;
5983 if (classid>=(ieDword)classcount || !levelslots[classid])
5984 return 0;
5986 //handle barbarians specially, since they're kits and not in levelslots
5987 if (id == ISBARBARIAN && levelslots[classid][ISFIGHTER] && GetKitIndex(BaseStats[IE_KIT]) == 31) {
5988 return BaseStats[IE_LEVEL];
5991 //get the levelid (IE_LEVEL,*2,*3)
5992 ieDword levelid = levelslots[classid][id];
5993 if (!levelid)
5994 return 0;
5996 //do dual-swap
5997 if (IsDualClassed()) {
5998 //if the old class is inactive, and it is the class
5999 //being searched for, return 0
6000 if (IsDualInactive() && ((Modified[IE_MC_FLAGS]&MC_WAS_ANY)==(ieDword)mcwasflags[id]))
6001 return 0;
6003 return BaseStats[levelid];
6006 bool Actor::IsDualInactive() const
6008 if (!IsDualClassed()) return 0;
6010 //we assume the old class is in IE_LEVEL2, unless swapped
6011 ieDword oldlevel = IsDualSwap() ? BaseStats[IE_LEVEL] : BaseStats[IE_LEVEL2];
6013 //since GetXPLevel returns the average of the 2 levels, oldclasslevel will
6014 //only be less than GetXPLevel when the new class surpasses it
6015 return oldlevel>=GetXPLevel(false);
6018 bool Actor::IsDualSwap() const
6020 //the dualswap[class-1] holds the info
6021 if (!IsDualClassed()) return false;
6022 ieDword tmpclass = BaseStats[IE_CLASS]-1;
6023 if (tmpclass>=(ieDword)classcount) return false;
6024 return (ieDword)dualswap[tmpclass]==(Modified[IE_MC_FLAGS]&MC_WAS_ANY);
6027 ieDword Actor::GetWarriorLevel() const
6029 if (!IsWarrior()) return 0;
6031 ieDword warriorlevels[4] = {
6032 GetBarbarianLevel(),
6033 GetFighterLevel(),
6034 GetPaladinLevel(),
6035 GetRangerLevel()
6038 ieDword highest = 0;
6039 for (int i=0; i<4; i++) {
6040 if (warriorlevels[i] > highest) {
6041 highest = warriorlevels[i];
6045 return highest;
6048 bool Actor::BlocksSearchMap() const
6050 return Modified[IE_DONOTJUMP] < 2;
6053 //return true if the actor doesn't want to use an entrance
6054 bool Actor::CannotPassEntrance() const
6056 if (InternalFlags&IF_USEEXIT) {
6057 return false;
6059 return true;
6062 void Actor::UseExit(int flag) {
6063 if (flag) {
6064 InternalFlags|=IF_USEEXIT;
6065 } else {
6066 InternalFlags&=~IF_USEEXIT;
6070 // luck increases the minimum roll per dice, but only up to the number of dice sides;
6071 // luck does not affect critical hit chances:
6072 // if critical is set, it will return 1/sides on a critical, otherwise it can never
6073 // return a critical miss when luck is positive and can return a false critical hit
6074 int Actor::LuckyRoll(int dice, int size, int add, bool critical, bool only_damage, Actor* opponent) const
6076 assert(this != opponent);
6078 ieDword stat;
6079 if (only_damage) {
6080 stat = IE_DAMAGELUCK;
6081 } else {
6082 stat = IE_LUCK;
6085 int luck = (signed) GetStat(stat);
6086 if (opponent) luck -= (signed) opponent->GetStat(stat);
6087 if (dice < 1 || size < 1) {
6088 return add + luck;
6091 if (dice > 100) {
6092 int bonus;
6093 if (abs(luck) > size) {
6094 bonus = luck/abs(luck) * size;
6095 } else {
6096 bonus = luck;
6098 int roll = core->Roll(1, dice*size, 0);
6099 if (critical && (roll == 1 || roll == size)) {
6100 return roll;
6101 } else {
6102 return add + dice * (size + bonus) / 2;
6106 int roll, result = 0, misses = 0, hits = 0;
6107 for (int i = 0; i < dice; i++) {
6108 roll = core->Roll(1, size, 0);
6109 if (roll == 1) {
6110 misses++;
6111 } else if (roll == size) {
6112 hits++;
6114 roll += luck;
6115 if (roll > size) {
6116 roll = size;
6117 } else if (roll < 1) {
6118 roll = 1;
6120 result += roll;
6123 // ensure we can still return a critical failure/success
6124 if (critical && dice == misses) return 1;
6125 if (critical && dice == hits) return size;
6127 return result + add;
6130 static EffectRef fx_remove_invisible_state_ref={"ForceVisible",NULL,-1};
6132 // removes the (normal) invisibility state
6133 void Actor::CureInvisibility()
6135 if (Modified[IE_STATE_ID] & (ieDword) (STATE_INVISIBLE)) {
6136 //SetBaseBit(IE_STATE_ID, STATE_INVISIBLE, false);
6137 //fxqueue.RemoveAllEffectsWithParam(fx_set_invisible_state_ref, 0);
6139 //this is much closer to what the original engine does here
6140 Effect *newfx;
6142 newfx = EffectQueue::CreateEffect(fx_remove_invisible_state_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
6143 core->ApplyEffect(newfx, this, this);
6145 delete newfx;
6147 //not sure, but better than nothing
6148 if (! (Modified[IE_STATE_ID]& STATE_INVISIBLE)) {
6149 InternalFlags|=IF_BECAMEVISIBLE;
6154 static EffectRef fx_remove_sanctuary_ref={"Cure:Sanctuary",NULL,-1};
6156 // removes the sanctuary effect
6157 void Actor::CureSanctuary()
6159 Effect *newfx;
6161 newfx = EffectQueue::CreateEffect(fx_remove_sanctuary_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
6162 core->ApplyEffect(newfx, this, this);
6164 delete newfx;
6167 void Actor::ResetState()
6169 CureInvisibility();
6170 CureSanctuary();
6171 SetModal(MS_NONE);
6174 // doesn't check the range, but only that the azimuth and the target
6175 // orientation match with a +/-2 allowed difference
6176 bool Actor::IsBehind(Actor* target)
6178 unsigned char tar_orient = target->GetOrientation();
6179 // computed, since we don't care where we face
6180 unsigned char my_orient = GetOrient(target->Pos, Pos);
6182 signed char diff;
6183 for (int i=-2; i <= 2; i++) {
6184 diff = my_orient+i;
6185 if (diff >= MAX_ORIENT) diff -= MAX_ORIENT;
6186 if (diff <= -1) diff += MAX_ORIENT;
6187 if (diff == (signed)tar_orient) return true;
6189 return false;
6192 // checks all the actor's stats to see if the target is her racial enemy
6193 bool Actor::IsRacialEnemy(Actor* target)
6195 if (Modified[IE_HATEDRACE] == target->Modified[IE_RACE]) {
6196 return true;
6197 } else if (core->HasFeature(GF_3ED_RULES)) {
6198 // iwd2 supports multiple racial enemies gained through level progression
6199 for (unsigned int i=0; i<7; i++) {
6200 if (Modified[IE_HATEDRACE2+i] == target->Modified[IE_RACE]) {
6201 return true;
6205 return false;
6208 bool Actor::ModalSpellSkillCheck() {
6209 switch(ModalState) {
6210 case MS_BATTLESONG:
6211 case MS_DETECTTRAPS:
6212 case MS_TURNUNDEAD:
6213 return true;
6214 case MS_STEALTH:
6215 return TryToHide();
6216 case MS_NONE:
6217 default:
6218 return false;
6222 static EffectRef fx_disable_button_ref={ "DisableButton", NULL, -1 };
6224 inline void HideFailed(Actor* actor)
6226 Effect *newfx;
6227 newfx = EffectQueue::CreateEffect(fx_disable_button_ref, 0, ACT_STEALTH, FX_DURATION_INSTANT_LIMITED);
6228 newfx->Duration = ROUND_SECONDS; // 90 ticks, 1 round
6229 core->ApplyEffect(newfx, actor, actor);
6230 delete newfx;
6233 bool Actor::TryToHide() {
6234 ieDword roll = LuckyRoll(1, 100, 0);
6235 if (roll == 1) {
6236 HideFailed(this);
6237 return false;
6240 // check for disabled dualclassed thieves (not sure if we need it)
6242 if (Modified[IE_DISABLEDBUTTON] & (1<<ACT_STEALTH)) {
6243 HideFailed(this);
6244 return false;
6247 // check if the pc is in combat (seen / heard)
6248 Game *game = core->GetGame();
6249 if (game->PCInCombat(this)) {
6250 HideFailed(this);
6251 return false;
6254 ieDword skill;
6255 if (core->HasFeature(GF_HAS_HIDE_IN_SHADOWS)) {
6256 skill = (GetStat(IE_HIDEINSHADOWS) + GetStat(IE_STEALTH))/2;
6257 } else {
6258 skill = GetStat(IE_STEALTH);
6261 // check how bright our spot is
6262 ieDword lightness = game->GetCurrentArea()->GetLightLevel(Pos);
6263 // seems to be the color overlay at midnight; lightness of a point with rgb (200, 100, 100)
6264 // TODO: but our NightTint computes to a higher value, which one is bad?
6265 ieDword ref_lightness = 43;
6266 ieDword light_diff = int((lightness - ref_lightness) * 100 / (100 - ref_lightness)) / 2;
6267 ieDword chance = (100 - light_diff) * skill/100;
6269 if (roll > chance) {
6270 HideFailed(this);
6271 return false;
6273 return true;
6276 // only works with masks; use direct comparison for specific alignment checks
6277 bool Actor::MatchesAlignmentMask(ieDword mask)
6279 ieDword stat = Modified[IE_ALIGNMENT];
6281 switch (mask) {
6282 case AL_EVIL:
6283 return stat == AL_LAWFUL_EVIL || stat == AL_NEUTRAL_EVIL || stat == AL_CHAOTIC_EVIL;
6284 case AL_GE_NEUTRAL:
6285 return stat == AL_NEUTRAL_GOOD || stat == AL_TRUE_NEUTRAL || stat == AL_NEUTRAL_EVIL;
6286 case AL_GOOD:
6287 return stat == AL_LAWFUL_GOOD || stat == AL_NEUTRAL_GOOD || stat == AL_CHAOTIC_GOOD;
6288 case AL_CHAOTIC:
6289 return stat == AL_CHAOTIC_GOOD || stat == AL_CHAOTIC_NEUTRAL || stat == AL_CHAOTIC_EVIL;
6290 case AL_LC_NEUTRAL:
6291 return stat == AL_NEUTRAL_GOOD || stat == AL_TRUE_NEUTRAL || stat == AL_NEUTRAL_EVIL;
6292 case AL_LAWFUL:
6293 return stat == AL_LAWFUL_GOOD || stat == AL_LAWFUL_NEUTRAL || stat == AL_LAWFUL_EVIL;
6294 default:
6295 printf("Bad mask parameter (%d) used with Actor::MatchesAlignmentMask!\n", mask);
6296 assert(false);
6297 return false;