GameScript: Make member variables private.
[gemrb.git] / gemrb / core / Actor.cpp
blob359b5f33253085def1ebbcc3b0b30cef32538a33
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 "win32def.h"
26 #include <cassert>
27 #include "TableMgr.h"
28 #include "Audio.h" //pst (react to death sounds)
29 #include "Actor.h"
30 #include "Interface.h"
31 #include "strrefs.h"
32 #include "overlays.h"
33 #include "Item.h"
34 #include "Spell.h"
35 #include "Projectile.h"
36 #include "Game.h"
37 #include "GameScript.h"
38 #include "GameControl.h" //checking for dialog
39 #include "ScriptEngine.h"
40 #include "GSUtils.h" //needed for DisplayStringCore
41 #include "Video.h"
42 #include "damages.h"
43 #include "ProjectileServer.h"
44 #include "GameData.h"
46 static const Color green = {
47 0x00, 0xff, 0x00, 0xff
49 static const Color red = {
50 0xff, 0x00, 0x00, 0xff
52 static const Color yellow = {
53 0xff, 0xff, 0x00, 0xff
55 static const Color cyan = {
56 0x00, 0xff, 0xff, 0xff
58 static const Color magenta = {
59 0xff, 0x00, 0xff, 0xff
62 static int sharexp = SX_DIVIDE;
63 static int classcount = -1;
64 static char **clericspelltables = NULL;
65 static char **druidspelltables = NULL;
66 static char **wizardspelltables = NULL;
67 static int *turnlevels = NULL;
68 static int *booktypes = NULL;
69 static int *xpbonus = NULL;
70 static int xpbonustypes = -1;
71 static int xpbonuslevels = -1;
72 static int **levelslots = NULL;
73 static int *dualswap = NULL;
74 static int *maxhpconbon = NULL;
75 //static ieVariable IWDDeathVarFormat = "KILL_%s_CNT";
76 //static ieVariable DeathVarFormat = "SPRITE_IS_DEAD%s";
77 static ieVariable CounterNames[4]={"GOOD","LAW","LADY","MURDER"};
79 static int FistRows = -1;
80 int *wmlevels[20];
81 typedef ieResRef FistResType[MAX_LEVEL+1];
83 static FistResType *fistres = NULL;
84 static ieResRef DefaultFist = {"FIST"};
86 //item usability array
87 struct ItemUseType {
88 ieResRef table; //which table contains the stat usability flags
89 ieByte stat; //which actor stat we talk about
90 ieByte mcol; //which column should be matched against the stat
91 ieByte vcol; //which column has the bit value for it
92 ieByte which; //which item dword should be used (1 = kit)
95 static ItemUseType *itemuse = NULL;
96 static int usecount = -1;
98 //item animation override array
99 struct ItemAnimType {
100 ieResRef itemname;
101 ieByte animation;
104 static ItemAnimType *itemanim = NULL;
105 static int animcount = -1;
107 static int fiststat = IE_CLASS;
109 //conversion for 3rd ed
110 static int isclass[11]={0,0,0,0,0,0,0,0,0,0,0};
112 static const int mcwasflags[11] = {
113 MC_WAS_FIGHTER, MC_WAS_MAGE, MC_WAS_THIEF, 0, 0, MC_WAS_CLERIC,
114 MC_WAS_DRUID, 0, 0, MC_WAS_RANGER, 0};
115 static const char *isclassnames[11] = {
116 "FIGHTER", "MAGE", "THIEF", "BARBARIAN", "BARD", "CLERIC",
117 "DRUID", "MONK", "PALADIN", "RANGER", "SORCERER" };
118 static const int levelslotsiwd2[11]={IE_LEVELFIGHTER,IE_LEVELMAGE,IE_LEVELTHIEF,
119 IE_LEVELBARBARIAN,IE_LEVELBARD,IE_LEVELCLERIC,IE_LEVELDRUID,IE_LEVELMONK,
120 IE_LEVELPALADIN,IE_LEVELRANGER,IE_LEVELSORCEROR};
122 //stat values are 0-255, so a byte is enough
123 static ieByte featstats[MAX_FEATS]={0
126 //holds the wspecial table for weapon prof bonuses
127 #define WSPECIAL_COLS 3
128 static int wspecial_max = 0;
129 static int wspattack_rows = 0;
130 static int wspattack_cols = 0;
131 static int **wspecial = NULL;
132 static int **wspattack = NULL;
134 //holds the weapon style bonuses
135 #define STYLE_MAX 3
136 static int **wsdualwield = NULL;
137 static int **wstwohanded = NULL;
138 static int **wsswordshield = NULL;
139 static int **wssingle = NULL;
141 //unhardcoded monk bonuses
142 static int **monkbon = NULL;
143 static int monkbon_cols = 0;
144 static int monkbon_rows = 0;
146 // reputation modifiers
147 static int **reputationmod = NULL;
148 #define CLASS_PCCUTOFF 32
149 #define CLASS_INNOCENT 155
150 #define CLASS_FLAMINGFIST 156
152 static ActionButtonRow *GUIBTDefaults = NULL; //qslots row count
153 ActionButtonRow DefaultButtons = {ACT_TALK, ACT_WEAPON1, ACT_WEAPON2,
154 ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE,
155 ACT_NONE, ACT_INNATE};
156 static int QslotTranslation = false;
157 static int DeathOnZeroStat = true;
158 static ieDword TranslucentShadows = 0;
159 static int ProjectileSize = 0; //the size of the projectile immunity bitfield (dwords)
161 static const char iwd2gemrb[32] = {
162 0,0,20,2,22,25,0,14,
163 15,23,13,0,1,24,8,21,
164 0,0,0,0,0,0,0,0,
165 0,0,0,0,0,0,0,0
167 static const char gemrb2iwd[32] = {
168 11,12,3,71,72,73,0,0, //0
169 14,80,83,82,81,10,7,8, //8
170 0,0,0,0,2,15,4,9, //16
171 13,5,0,0,0,0,0,0 //24
174 //letters for char sound resolution bg1/bg2
175 static char csound[VCONST_COUNT];
177 static void InitActorTables();
179 #define DAMAGE_LEVELS 19
180 #define ATTACKROLL 20
181 #define SAVEROLL 20
182 #define DEFAULTAC 10
184 static ieResRef d_main[DAMAGE_LEVELS] = {
185 //slot 0 is not used in the original engine
186 "BLOODCR","BLOODS","BLOODM","BLOODL", //blood
187 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //fire
188 "SPSHKIMP","SPSHKIMP","SPSHKIMP", //spark
189 "SPFIRIMP","SPFIRIMP","SPFIRIMP", //ice
190 "SHACID","SHACID","SHACID", //acid
191 "SPDUSTY2","SPDUSTY2","SPDUSTY2" //disintegrate
193 static ieResRef d_splash[DAMAGE_LEVELS] = {
194 "","","","",
195 "SPBURN","SPBURN","SPBURN", //flames
196 "SPSPARKS","SPSPARKS","SPSPARKS", //sparks
197 "","","",
198 "","","",
199 "","",""
202 #define BLOOD_GRADIENT 19
203 #define FIRE_GRADIENT 19
204 #define ICE_GRADIENT 71
205 #define STONE_GRADIENT 93
207 static int d_gradient[DAMAGE_LEVELS] = {
208 BLOOD_GRADIENT,BLOOD_GRADIENT,BLOOD_GRADIENT,BLOOD_GRADIENT,
209 FIRE_GRADIENT,FIRE_GRADIENT,FIRE_GRADIENT,
210 -1,-1,-1,
211 ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,
212 -1,-1,-1,
213 -1,-1,-1
216 static ieResRef hc_overlays[OVERLAY_COUNT]={"SANCTRY","SPENTACI","SPMAGGLO","SPSHIELD",
217 "GREASED","WEBENTD","MINORGLB","","","","","","","","","","","","","","",
218 "","","","SPTURNI2","SPTURNI","","","","","",""};
219 static ieDword hc_locations=0x2ba80030;
221 static int *mxsplwis = NULL;
222 static int spllevels;
224 //for every game except IWD2 we need to reverse TOHIT
225 static int ReverseToHit=true;
226 static int CheckAbilities=false;
228 //internal flags for calculating to hit
229 #define WEAPON_FIST 0
230 #define WEAPON_MELEE 1
231 #define WEAPON_RANGED 2
232 #define WEAPON_STYLEMASK 15
233 #define WEAPON_LEFTHAND 16
234 #define WEAPON_USESTRENGTH 32
236 /* counts the on bits in a number */
237 ieDword bitcount (ieDword n)
239 ieDword count=0;
240 while (n) {
241 count += n & 0x1u;
242 n >>= 1;
244 return count;
247 void ReleaseMemoryActor()
249 if (mxsplwis) {
250 //calloc'd x*y integer matrix
251 free (mxsplwis);
252 mxsplwis = NULL;
255 if (fistres) {
256 delete [] fistres;
257 fistres = NULL;
260 if (itemuse) {
261 delete [] itemuse;
262 itemuse = NULL;
265 if (itemanim) {
266 delete [] itemanim;
267 itemanim = NULL;
269 FistRows = -1;
272 Actor::Actor()
273 : Movable( ST_ACTOR )
275 int i;
277 for (i = 0; i < MAX_STATS; i++) {
278 BaseStats[i] = 0;
279 Modified[i] = 0;
282 SmallPortrait[0] = 0;
283 LargePortrait[0] = 0;
285 anims = NULL;
286 ShieldRef[0]=0;
287 HelmetRef[0]=0;
288 WeaponRef[0]=0;
289 for (i = 0; i < EXTRA_ACTORCOVERS; ++i)
290 extraCovers[i] = NULL;
292 LongName = NULL;
293 ShortName = NULL;
294 LongStrRef = (ieStrRef) -1;
295 ShortStrRef = (ieStrRef) -1;
297 LastProtected = 0;
298 LastFollowed = 0;
299 LastCommander = 0;
300 LastHelp = 0;
301 LastSeen = 0;
302 LastMarked = 0;
303 LastHeard = 0;
304 PCStats = NULL;
305 LastCommand = 0; //used by order
306 LastShout = 0; //used by heard
307 LastDamage = 0;
308 LastDamageType = 0;
309 HotKey = 0;
310 attackcount = 0;
311 attacksperround = 0;
312 nextattack = 0;
313 InTrap = 0;
314 PathTries = 0;
315 TargetDoor = NULL;
316 attackProjectile = NULL;
317 lastInit = 0;
318 roundTime = 0;
319 lastattack = 0;
321 inventory.SetInventoryType(INVENTORY_CREATURE);
322 Equipped = 0;
323 EquippedHeader = 0;
325 fxqueue.SetOwner( this );
326 inventory.SetOwner( this );
327 if (classcount<0) {
328 //This block is executed only once, when the first actor is loaded
329 InitActorTables();
331 TranslucentShadows = 0;
332 core->GetDictionary()->Lookup("Translucent Shadows", TranslucentShadows);
333 //get the needed size to store projectile immunity bitflags in Dwords
334 ProjectileSize = (core->GetProjectileServer()->GetHighestProjectileNumber()+31)/32;
335 //allowing 1024 bits (1024 projectiles ought to be enough for everybody)
336 //the rest of the projectiles would still work, but couldn't be resisted
337 if (ProjectileSize>32) {
338 ProjectileSize=32;
341 projectileImmunity = (ieDword *) calloc(ProjectileSize,sizeof(ieDword));
342 AppearanceFlags = 0;
343 SetDeathVar = IncKillCount = 0;
344 InParty = 0;
345 TalkCount = 0;
346 InteractCount = 0; //numtimesinteracted depends on this
347 appearance = 0xffffff; //might be important for created creatures
348 RemovalTime = ~0;
349 version = 0;
350 //these are used only in iwd2 so we have to default them
351 for(i=0;i<7;i++) {
352 BaseStats[IE_HATEDRACE2+i]=0xff;
354 //this one is saved only for PC's
355 ModalState = 0;
356 //set it to a neutral value
357 ModalSpell[0] = '*';
358 //this one is saved, but not loaded?
359 localID = globalID = 0;
360 //this one is not saved
361 GotLUFeedback = false;
364 Actor::~Actor(void)
366 unsigned int i;
368 delete anims;
370 core->FreeString( LongName );
371 core->FreeString( ShortName );
373 delete PCStats;
375 for (i = 0; i < vvcOverlays.size(); i++) {
376 if (vvcOverlays[i]) {
377 delete vvcOverlays[i];
378 vvcOverlays[i] = NULL;
381 for (i = 0; i < vvcShields.size(); i++) {
382 if (vvcShields[i]) {
383 delete vvcShields[i];
384 vvcShields[i] = NULL;
387 for (i = 0; i < EXTRA_ACTORCOVERS; i++)
388 delete extraCovers[i];
390 delete attackProjectile;
392 free(projectileImmunity);
395 void Actor::SetFistStat(ieDword stat)
397 fiststat = stat;
400 void Actor::SetDefaultActions(int qslot, ieByte slot1, ieByte slot2, ieByte slot3)
402 QslotTranslation=qslot;
403 DefaultButtons[0]=slot1;
404 DefaultButtons[1]=slot2;
405 DefaultButtons[2]=slot3;
408 void Actor::SetName(const char* ptr, unsigned char type)
410 size_t len = strlen( ptr ) + 1;
411 //32 is the maximum possible length of the actor name in the original games
412 if (len>32) len=33;
413 if (type!=2) {
414 LongName = ( char * ) realloc( LongName, len );
415 memcpy( LongName, ptr, len );
416 core->StripLine( LongName, len );
418 if (type!=1) {
419 ShortName = ( char * ) realloc( ShortName, len );
420 memcpy( ShortName, ptr, len );
421 core->StripLine( ShortName, len );
425 void Actor::SetName(int strref, unsigned char type)
427 if (type!=2) {
428 if (LongName) free(LongName);
429 LongName = core->GetString( strref, IE_STR_REMOVE_NEWLINE );
431 if (type!=1) {
432 if (ShortName) free(ShortName);
433 ShortName = core->GetString( strref, IE_STR_REMOVE_NEWLINE );
437 void Actor::SetAnimationID(unsigned int AnimID)
439 //if the palette is locked, then it will be transferred to the new animation
440 Palette *recover = NULL;
442 if (anims) {
443 if (anims->lockPalette) {
444 recover = anims->palette[PAL_MAIN];
446 // Take ownership so the palette won't be deleted
447 if (recover) {
448 recover->IncRef();
450 delete( anims );
452 //hacking PST no palette
453 if (core->HasFeature(GF_ONE_BYTE_ANIMID) ) {
454 if ((AnimID&0xf000)==0xe000) {
455 if (BaseStats[IE_COLORCOUNT]) {
456 printMessage("Actor"," ",YELLOW);
457 printf("Animation ID %x is supposed to be real colored (no recoloring), patched creature\n", AnimID);
459 BaseStats[IE_COLORCOUNT]=0;
462 anims = new CharAnimations( AnimID&0xffff, BaseStats[IE_ARMOR_TYPE]);
463 if(anims->ResRef[0] == 0) {
464 delete anims;
465 anims = NULL;
466 printMessage("Actor", " ",LIGHT_RED);
467 printf("Missing animation for %s\n",LongName);
468 return;
470 anims->SetOffhandRef(ShieldRef);
471 anims->SetHelmetRef(HelmetRef);
472 anims->SetWeaponRef(WeaponRef);
474 //if we have a recovery palette, then set it back
475 assert(anims->palette[PAL_MAIN] == 0);
476 anims->palette[PAL_MAIN] = recover;
477 if (recover) {
478 anims->lockPalette = true;
480 //bird animations are not hindered by searchmap
481 //only animtype==7 (bird) uses this feature
482 //this is a hardcoded hack, but works for all engine type
483 if (anims->GetAnimType()!=IE_ANI_BIRD) {
484 BaseStats[IE_DONOTJUMP]=0;
485 } else {
486 BaseStats[IE_DONOTJUMP]=DNJ_BIRD;
488 SetCircleSize();
489 anims->SetColors(BaseStats+IE_COLORS);
492 CharAnimations* Actor::GetAnims()
494 return anims;
497 /** Returns a Stat value (Base Value + Mod) */
498 ieDword Actor::GetStat(unsigned int StatIndex) const
500 if (StatIndex >= MAX_STATS) {
501 return 0xdadadada;
503 return Modified[StatIndex];
506 void Actor::SetCircleSize()
508 const Color *color;
509 int color_index;
511 if (!anims)
512 return;
514 if (UnselectableTimer) {
515 color = &magenta;
516 color_index = 4;
517 } else if (Modified[IE_STATE_ID] & STATE_PANIC) {
518 color = &yellow;
519 color_index = 5;
520 } else {
521 switch (Modified[IE_EA]) {
522 case EA_PC:
523 case EA_FAMILIAR:
524 case EA_ALLY:
525 case EA_CONTROLLED:
526 case EA_CHARMED:
527 case EA_EVILBUTGREEN:
528 case EA_GOODCUTOFF:
529 color = &green;
530 color_index = 0;
531 break;
533 case EA_ENEMY:
534 case EA_GOODBUTRED:
535 case EA_EVILCUTOFF:
536 color = &red;
537 color_index = 1;
538 break;
539 default:
540 color = &cyan;
541 color_index = 2;
542 break;
546 int csize = anims->GetCircleSize() - 1;
547 if (csize >= MAX_CIRCLE_SIZE)
548 csize = MAX_CIRCLE_SIZE - 1;
550 SetCircle( anims->GetCircleSize(), *color, core->GroundCircles[csize][color_index], core->GroundCircles[csize][(color_index == 0) ? 3 : color_index] );
553 void ApplyClab(Actor *actor, const char *clab, int level)
555 AutoTable table(clab);
556 if (table) {
557 int row = table->GetRowCount();
558 for(int i=0;i<level;i++) {
559 for (int j=0;j<row;j++) {
560 const char *res = table->QueryField(j,i);
561 if (!memcmp(res,"AP_",3)) {
562 core->ApplySpell(res+2, actor, actor, 0);
564 else if (!memcmp(res,"GA_",3)) {
565 actor->LearnSpell(res+2, 0);
567 else if (!memcmp(res,"FA_",3)) {//iwd2 only
568 int x=atoi(res+3);
569 core->DisplayStringName(x,0xffffff,actor,0);
571 else if (!memcmp(res,"FS_",3)) {//iwd2? (song?)
572 int x=atoi(res+3);
573 core->DisplayStringName(x,0xffffff,actor,0);
575 else if (!memcmp(res,"RA_",3)) {//iwd2
576 int x=atoi(res+3);
577 core->DisplayStringName(x,0xffffff,actor,0);
584 #define BG2_KITMASK 0xffffc000
585 #define KIT_BARBARIAN 0x4000
586 #define KIT_BASECLASS 0x40000000
588 //applies a kit on the character (only bg2)
589 bool Actor::ApplyKit(ieDword Value)
591 //get current unmodified level (i guess)
592 int level = GetXPLevel(false);
593 AutoTable table("kitlist");
594 if (table) {
595 ieDword row;
596 //find row by unusability
597 row = table->GetRowCount();
598 while (row) {
599 row--;
600 ieDword Unusability = (ieDword) strtol(table->QueryField(row, 6),NULL,0);
601 if (Value == Unusability) {
602 goto found_row;
605 //if it wasn't found, try the bg2 kit format
606 if ((Value&BG2_KITMASK)==KIT_BARBARIAN) {
607 row = (Value<<16);
609 //cannot find kit
610 if (table->GetRowCount()>=row) {
611 return false;
613 found_row:
614 ieDword cls = (ieDword) atoi(table->QueryField(row, 7));
615 if (cls!=BaseStats[IE_CLASS]) {
616 //cannot apply kit, because the class doesn't fit
617 return false;
619 const char *clab = table->QueryField(row, 4);
620 ApplyClab(this, clab, level);
622 return true;
625 //call this when morale or moralebreak changed
626 //cannot use old or new value, because it is called two ways
627 void pcf_morale (Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
629 if ((actor->Modified[IE_MORALE]<=actor->Modified[IE_MORALEBREAK]) && (actor->Modified[IE_MORALEBREAK] != 0) ) {
630 actor->Panic();
632 //for new colour
633 actor->SetCircleSize();
636 void pcf_ea (Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
638 if (actor->InParty) core->GetGame()->SelectActor(actor, false, SELECT_NORMAL);
639 actor->SetCircleSize();
642 //this is a good place to recalculate level up stuff
643 void pcf_level (Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
645 ieDword sum =
646 actor->GetFighterLevel()+
647 actor->GetMageLevel()+
648 actor->GetThiefLevel()+
649 actor->GetBarbarianLevel()+
650 actor->GetBardLevel()+
651 actor->GetClericLevel()+
652 actor->GetDruidLevel()+
653 actor->GetMonkLevel()+
654 actor->GetPaladinLevel()+
655 actor->GetRangerLevel()+
656 actor->GetSorcererLevel();
657 actor->SetBase(IE_CLASSLEVELSUM,sum);
658 actor->SetupFist();
659 actor->GotLUFeedback = false;
662 void pcf_class (Actor *actor, ieDword /*oldValue*/, ieDword newValue)
664 actor->InitButtons(newValue, false);
666 int sorcerer=0;
667 if (newValue<(ieDword) classcount) {
668 switch(booktypes[newValue]) {
669 case 2: sorcerer = 1<<IE_SPELL_TYPE_WIZARD; break;
670 case 3: sorcerer = 1<<IE_SPELL_TYPE_PRIEST; break;
671 default: break;
674 actor->spellbook.SetBookType(sorcerer);
677 void pcf_animid(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
679 actor->SetAnimationID(newValue);
682 static const ieDword fullwhite[7]={ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT};
684 static const ieDword fullstone[7]={STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT};
686 void pcf_state(Actor *actor, ieDword /*oldValue*/, ieDword State)
688 if (State & STATE_PETRIFIED) {
689 actor->SetLockedPalette(fullstone);
690 return;
692 if (State & STATE_FROZEN) {
693 actor->SetLockedPalette(fullwhite);
694 return;
696 if (actor->InParty) core->SetEventFlag(EF_PORTRAIT);
697 actor->UnlockPalette();
700 //changes based on extended state bits, right now it is only the seven eyes
701 //animation (used in how/iwd2)
702 void pcf_extstate(Actor *actor, ieDword oldValue, ieDword State)
704 if ((oldValue^State)&EXTSTATE_SEVEN_EYES) {
705 ieDword mask = EXTSTATE_EYE_MIND;
706 int eyeCount = 7;
707 for (int i=0;i<7;i++)
709 if (State&mask) eyeCount--;
710 mask<<=1;
712 ScriptedAnimation *sca = actor->FindOverlay(OV_SEVENEYES);
713 if (sca) {
714 sca->SetOrientation(eyeCount);
716 sca = actor->FindOverlay(OV_SEVENEYES2);
717 if (sca) {
718 sca->SetOrientation(eyeCount);
723 void pcf_hitpoint(Actor *actor, ieDword /*oldValue*/, ieDword hp)
725 if ((signed) actor->BaseStats[IE_HITPOINTS]>(signed) actor->Modified[IE_MAXHITPOINTS]) {
726 actor->BaseStats[IE_HITPOINTS]=actor->Modified[IE_MAXHITPOINTS];
729 int hptmp = (signed) actor->Modified[IE_MAXHITPOINTS];
730 if ((signed) hp>hptmp) {
731 hp=hptmp;
734 hptmp = (signed) actor->Modified[IE_MINHITPOINTS];
735 if (hptmp && (signed) hp<hptmp) {
736 hp=hptmp;
738 if ((signed) hp<=0) {
739 actor->Die(NULL);
741 actor->BaseStats[IE_HITPOINTS]=hp;
742 actor->Modified[IE_HITPOINTS]=hp;
743 if (actor->InParty) core->SetEventFlag(EF_PORTRAIT);
746 void pcf_maxhitpoint(Actor *actor, ieDword /*oldValue*/, ieDword hp)
748 if ((signed) hp<(signed) actor->BaseStats[IE_HITPOINTS]) {
749 actor->BaseStats[IE_HITPOINTS]=hp;
750 //passing 0 because it is ignored anyway
751 pcf_hitpoint(actor, 0, hp);
755 void pcf_minhitpoint(Actor *actor, ieDword /*oldValue*/, ieDword hp)
757 if ((signed) hp>(signed) actor->BaseStats[IE_HITPOINTS]) {
758 actor->BaseStats[IE_HITPOINTS]=hp;
759 //passing 0 because it is ignored anyway
760 pcf_hitpoint(actor, 0, hp);
764 void pcf_stat(Actor *actor, ieDword newValue, ieDword stat)
766 if ((signed) newValue<=0) {
767 if (DeathOnZeroStat) {
768 actor->Die(NULL);
769 } else {
770 actor->Modified[stat]=1;
775 void pcf_stat_str(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
777 pcf_stat(actor, newValue, IE_STR);
780 void pcf_stat_int(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
782 pcf_stat(actor, newValue, IE_INT);
785 void pcf_stat_wis(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
787 pcf_stat(actor, newValue, IE_WIS);
790 void pcf_stat_dex(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
792 pcf_stat(actor, newValue, IE_DEX);
795 void pcf_stat_con(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
797 pcf_stat(actor, newValue, IE_CON);
798 pcf_hitpoint(actor, 0, actor->BaseStats[IE_HITPOINTS]);
801 void pcf_stat_cha(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
803 pcf_stat(actor, newValue, IE_CHR);
806 void pcf_xp(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
808 // check if we reached a new level
809 unsigned int pc = actor->InParty;
810 if (pc && !actor->GotLUFeedback) {
811 char varname[16];
812 sprintf(varname, "CheckLevelUp%d", pc);
813 core->GetGUIScriptEngine()->RunFunction("CheckLevelUp", true, pc);
814 ieDword NeedsLevelUp = 0;
815 core->GetDictionary()->Lookup(varname, NeedsLevelUp);
816 if (NeedsLevelUp == 1) {
817 core->DisplayConstantStringName(STR_LEVELUP, 0xffffff, actor);
818 actor->GotLUFeedback = true;
823 void pcf_gold(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
825 //this function will make a party member automatically donate their
826 //gold to the party pool, not the same as in the original engine
827 if (actor->InParty) {
828 Game *game = core->GetGame();
829 game->AddGold ( actor->BaseStats[IE_GOLD] );
830 actor->BaseStats[IE_GOLD]=0;
834 static void handle_overlay(Actor *actor, ieDword idx)
836 if (actor->FindOverlay(idx))
837 return;
838 ieDword flag = hc_locations&(1<<idx);
839 ScriptedAnimation *sca = gamedata->GetScriptedAnimation(hc_overlays[idx], false);
840 if (sca) {
841 if (flag) {
842 sca->ZPos=-1;
844 actor->AddVVCell(sca);
848 //de/activates the entangle overlay
849 void pcf_entangle(Actor *actor, ieDword oldValue, ieDword newValue)
851 if (newValue&1) {
852 handle_overlay(actor, OV_ENTANGLE);
854 if (oldValue&1) {
855 actor->RemoveVVCell(hc_overlays[OV_ENTANGLE], true);
859 //de/activates the sanctuary and other overlays
860 //unlike IE, gemrb uses this stat for other overlay fields
861 //see the complete list in overlay.2da
862 //it loosely follows the internal representation of overlays in IWD2
863 void pcf_sanctuary(Actor *actor, ieDword oldValue, ieDword newValue)
865 ieDword changed = newValue^oldValue;
866 ieDword mask = 1;
867 for (int i=0;i<32;i++) {
868 if (changed&mask) {
869 if (newValue&mask) {
870 handle_overlay(actor, i);
871 } else {
872 actor->RemoveVVCell(hc_overlays[i], true);
875 mask<<=1;
879 //de/activates the prot from missiles overlay
880 void pcf_shieldglobe(Actor *actor, ieDword oldValue, ieDword newValue)
882 if (newValue&1) {
883 handle_overlay(actor, OV_SHIELDGLOBE);
884 return;
886 if (oldValue&1) {
887 actor->RemoveVVCell(hc_overlays[OV_SHIELDGLOBE], true);
891 //de/activates the globe of invul. overlay
892 void pcf_minorglobe(Actor *actor, ieDword oldValue, ieDword newValue)
894 if (newValue&1) {
895 handle_overlay(actor, OV_MINORGLOBE);
896 return;
898 if (oldValue&1) {
899 actor->RemoveVVCell(hc_overlays[OV_MINORGLOBE], true);
903 //de/activates the grease background
904 void pcf_grease(Actor *actor, ieDword oldValue, ieDword newValue)
906 if (newValue&1) {
907 handle_overlay(actor, OV_GREASE);
908 return;
910 if (oldValue&1) {
911 actor->RemoveVVCell(hc_overlays[OV_GREASE], true);
915 //de/activates the web overlay
916 //the web effect also immobilizes the actor!
917 void pcf_web(Actor *actor, ieDword oldValue, ieDword newValue)
919 if (newValue&1) {
920 handle_overlay(actor, OV_WEB);
921 return;
923 if (oldValue&1) {
924 actor->RemoveVVCell(hc_overlays[OV_WEB], true);
928 //de/activates the spell bounce background
929 void pcf_bounce(Actor *actor, ieDword oldValue, ieDword newValue)
931 if (newValue&1) {
932 handle_overlay(actor, OV_BOUNCE);
933 return;
935 if (oldValue&1) {
936 //it seems we have to remove it abruptly
937 actor->RemoveVVCell(hc_overlays[OV_BOUNCE], false);
941 //no separate values (changes are permanent)
942 void pcf_fatigue(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
944 actor->BaseStats[IE_FATIGUE]=newValue;
947 //no separate values (changes are permanent)
948 void pcf_intoxication(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
950 actor->BaseStats[IE_INTOXICATION]=newValue;
953 void pcf_color(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
955 CharAnimations *anims = actor->GetAnims();
956 if (anims) {
957 anims->SetColors(actor->Modified+IE_COLORS);
961 void pcf_armorlevel(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
963 CharAnimations *anims = actor->GetAnims();
964 if (anims) {
965 anims->SetArmourLevel(newValue);
969 static int maximum_values[MAX_STATS]={
970 32767,32767,20,100,100,100,100,25,10,25,25,25,25,25,100,100,//0f
971 100,100,100,100,100,100,100,100,100,100,255,255,255,255,100,100,//1f
972 200,200,MAX_LEVEL,255,25,100,25,25,25,25,25,999999999,999999999,999999999,25,25,//2f
973 200,255,200,100,100,200,200,25,5,100,1,1,100,1,1,0,//3f
974 511,1,1,1,MAX_LEVEL,MAX_LEVEL,1,9999,25,100,100,255,1,20,20,25,//4f
975 25,1,1,255,25,25,255,255,25,255,255,255,255,255,255,255,//5f
976 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//6f
977 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//7f
978 255,255,255,255,255,255,255,100,100,100,255,5,5,255,1,1,//8f
979 1,25,25,30,1,1,1,25,0,100,100,1,255,255,255,255,//9f
980 255,255,255,255,255,255,20,255,255,1,20,255,999999999,999999999,1,1,//af
981 999999999,999999999,0,0,20,0,0,0,0,0,0,0,0,0,0,0,//bf
982 0,0,0,0,0,0,0,25,25,255,255,255,255,65535,0,0,//cf - 207
983 0,0,0,0,0,0,0,0,MAX_LEVEL,255,65535,3,255,255,255,255,//df - 223
984 255,255,255,255,255,255,255,255,255,255,255,255,65535,65535,15,0,//ef - 239
985 MAX_LEVEL,MAX_LEVEL,MAX_LEVEL,MAX_LEVEL, MAX_LEVEL,MAX_LEVEL,MAX_LEVEL,MAX_LEVEL, //0xf7 - 247
986 MAX_LEVEL,MAX_LEVEL,0,0,0,0,0,0//ff
989 typedef void (*PostChangeFunctionType)(Actor *actor, ieDword oldValue, ieDword newValue);
990 static PostChangeFunctionType post_change_functions[MAX_STATS]={
991 pcf_hitpoint, pcf_maxhitpoint, NULL, NULL, NULL, NULL, NULL, NULL,
992 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //0f
993 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
994 NULL,NULL,NULL,NULL, NULL, NULL, pcf_fatigue, pcf_intoxication, //1f
995 NULL,NULL,pcf_level,NULL, pcf_stat_str, NULL, pcf_stat_int, pcf_stat_wis,
996 pcf_stat_dex,pcf_stat_con,pcf_stat_cha,NULL, pcf_xp, pcf_gold, pcf_morale, NULL, //2f
997 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
998 NULL,NULL,NULL,NULL, NULL, NULL, pcf_entangle, pcf_sanctuary, //3f
999 pcf_minorglobe, pcf_shieldglobe, pcf_grease, pcf_web, pcf_level, pcf_level, NULL, NULL,
1000 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //4f
1001 NULL,NULL,NULL,pcf_minhitpoint, NULL, NULL, NULL, NULL,
1002 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //5f
1003 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1004 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //6f
1005 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1006 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //7f
1007 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1008 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //8f
1009 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1010 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //9f
1011 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1012 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //af
1013 NULL,NULL,NULL,NULL, pcf_morale, pcf_bounce, NULL, NULL,
1014 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //bf
1015 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1016 NULL,NULL,NULL,NULL, NULL, pcf_animid,pcf_state, pcf_extstate, //cf
1017 pcf_color,pcf_color,pcf_color,pcf_color, pcf_color, pcf_color, pcf_color, NULL,
1018 NULL,NULL,NULL,pcf_armorlevel, NULL, NULL, NULL, NULL, //df
1019 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1020 pcf_class,NULL,pcf_ea,NULL, NULL, NULL, NULL, NULL, //ef
1021 pcf_level,pcf_level,pcf_level,pcf_level, pcf_level, pcf_level, pcf_level, pcf_level,
1022 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL //ff
1025 /** call this from ~Interface() */
1026 void Actor::ReleaseMemory()
1028 int i;
1030 if (classcount>=0) {
1031 if (clericspelltables) {
1032 for (i=0;i<classcount;i++) {
1033 if (clericspelltables[i]) {
1034 free (clericspelltables[i]);
1037 free(clericspelltables);
1038 clericspelltables=NULL;
1040 if (druidspelltables) {
1041 for (i=0;i<classcount;i++) {
1042 if (druidspelltables[i]) {
1043 free (druidspelltables[i]);
1046 free(druidspelltables);
1047 druidspelltables=NULL;
1049 if (wizardspelltables) {
1050 for (i=0;i<classcount;i++) {
1051 if (wizardspelltables[i]) {
1052 free(wizardspelltables[i]);
1055 free(wizardspelltables);
1056 wizardspelltables=NULL;
1058 if (turnlevels) {
1059 free(turnlevels);
1060 turnlevels=NULL;
1063 if (booktypes) {
1064 free(booktypes);
1065 booktypes=NULL;
1068 if (xpbonus) {
1069 free(xpbonus);
1070 xpbonus=NULL;
1071 xpbonuslevels = -1;
1072 xpbonustypes = -1;
1074 if (levelslots) {
1075 for (i=0; i<classcount; i++) {
1076 if (levelslots[i]) {
1077 free(levelslots[i]);
1080 free(levelslots);
1081 levelslots=NULL;
1083 if (dualswap) {
1084 free(dualswap);
1085 dualswap=NULL;
1087 if (maxhpconbon) {
1088 free(maxhpconbon);
1089 maxhpconbon=NULL;
1091 if (wspecial) {
1092 for (i=0; i<=wspecial_max; i++) {
1093 if (wspecial[i]) {
1094 free(wspecial[i]);
1097 free(wspecial);
1098 wspecial=NULL;
1100 if (wspattack) {
1101 for (i=0; i<wspattack_rows; i++) {
1102 if (wspattack[i]) {
1103 free(wspattack[i]);
1106 free(wspattack);
1107 wspattack=NULL;
1109 if (wsdualwield) {
1110 for (i=0; i<=STYLE_MAX; i++) {
1111 if (wsdualwield[i]) {
1112 free(wsdualwield[i]);
1115 free(wsdualwield);
1116 wsdualwield=NULL;
1118 if (wstwohanded) {
1119 for (i=0; i<=STYLE_MAX; i++) {
1120 if (wstwohanded[i]) {
1121 free(wstwohanded[i]);
1124 free(wstwohanded);
1125 wstwohanded=NULL;
1127 if (wsswordshield) {
1128 for (i=0; i<=STYLE_MAX; i++) {
1129 if (wsswordshield[i]) {
1130 free(wsswordshield[i]);
1133 free(wsswordshield);
1134 wsswordshield=NULL;
1136 if (wssingle) {
1137 for (i=0; i<=STYLE_MAX; i++) {
1138 if (wssingle[i]) {
1139 free(wssingle[i]);
1142 free(wssingle);
1143 wssingle=NULL;
1145 if (monkbon) {
1146 for (i=0; i<monkbon_rows; i++) {
1147 if (monkbon[i]) {
1148 free(monkbon[i]);
1151 free(monkbon);
1152 monkbon=NULL;
1154 for(i=0;i<20;i++) {
1155 free(wmlevels[i]);
1156 wmlevels[i]=NULL;
1158 if (reputationmod) {
1159 for (i=0; i<20; i++) {
1160 if (reputationmod[i]) {
1161 free(reputationmod[i]);
1164 free(reputationmod);
1165 reputationmod=NULL;
1168 if (GUIBTDefaults) {
1169 free (GUIBTDefaults);
1170 GUIBTDefaults=NULL;
1172 classcount = -1;
1175 #define COL_HATERACE 0 //ranger type racial enemy
1176 #define COL_CLERIC_SPELL 1 //cleric spells
1177 #define COL_MAGE_SPELL 2 //mage spells
1178 #define COL_STARTXP 3 //starting xp
1179 #define COL_BARD_SKILL 4 //bard skills
1180 #define COL_THIEF_SKILL 5 //thief skills
1182 #define COL_MAIN 0
1183 #define COL_SPARKS 1
1184 #define COL_GRADIENT 2
1186 /* returns the ISCLASS for the class based on name */
1187 int IsClassFromName (const char* name)
1189 //TODO: is there a better way of doing this?
1190 for (int i=0; i<ISCLASSES; i++) {
1191 if (strcmp(name, isclassnames[i]) == 0)
1192 return i;
1194 return -1;
1197 static void InitActorTables()
1199 int i, j;
1201 //if (core->HasFeature(GF_IWD_DEATHVARFORMAT)) {
1202 // memcpy(DeathVarFormat, IWDDeathVarFormat, sizeof(ieVariable));
1205 if (core->HasFeature(GF_CHALLENGERATING)) {
1206 sharexp=SX_DIVIDE|SX_CR;
1207 } else {
1208 sharexp=SX_DIVIDE;
1210 ReverseToHit = core->HasFeature(GF_REVERSE_TOHIT);
1211 CheckAbilities = core->HasFeature(GF_CHECK_ABILITIES);
1212 DeathOnZeroStat = core->HasFeature(GF_DEATH_ON_ZERO_STAT);
1214 //this table lists various level based xp bonuses
1215 AutoTable tm("xpbonus");
1216 if (tm) {
1217 xpbonustypes = tm->GetRowCount();
1218 if (xpbonustypes == 0) {
1219 xpbonuslevels = 0;
1220 } else {
1221 xpbonuslevels = tm->GetColumnCount(0);
1222 xpbonus = (int *) calloc(xpbonuslevels*xpbonustypes, sizeof(int));
1223 for (i = 0; i<xpbonustypes; i++) {
1224 for(j = 0; j<xpbonuslevels; j++) {
1225 xpbonus[i*xpbonuslevels+j] = atoi(tm->QueryField(i,j));
1229 } else {
1230 xpbonustypes = 0;
1231 xpbonuslevels = 0;
1233 //this table lists skill groups assigned to classes
1234 //it is theoretically possible to create hybrid classes
1235 tm.load("clskills");
1236 if (tm) {
1237 classcount = tm->GetRowCount();
1238 memset (isclass,0,sizeof(isclass));
1239 clericspelltables = (char **) calloc(classcount, sizeof(char*));
1240 druidspelltables = (char **) calloc(classcount, sizeof(char*));
1241 wizardspelltables = (char **) calloc(classcount, sizeof(char*));
1242 turnlevels = (int *) calloc(classcount, sizeof(int));
1243 booktypes = (int *) calloc(classcount, sizeof(int));
1245 ieDword bitmask = 1;
1247 for(i = 0; i<classcount; i++) {
1248 const char *field;
1249 int turnlevel = atoi(tm->QueryField( i, 7));
1250 turnlevels[i]=turnlevel;
1252 field = tm->QueryField( i, 0 );
1253 if (field[0]!='*') {
1254 isclass[ISDRUID] |= bitmask;
1255 druidspelltables[i]=strdup(field);
1257 field = tm->QueryField( i, 1 );
1258 if (field[0]!='*') {
1259 isclass[ISCLERIC] |= bitmask;
1260 clericspelltables[i]=strdup(field);
1263 field = tm->QueryField( i, 2 );
1264 if (field[0]!='*') {
1265 isclass[ISMAGE] |= bitmask;
1266 wizardspelltables[i]=strdup(field);
1269 // field 3 holds the starting xp
1271 field = tm->QueryField( i, 4 );
1272 if (field[0]!='*') {
1273 isclass[ISBARD] |= bitmask;
1276 field = tm->QueryField( i, 5 );
1277 if (field[0]!='*') {
1278 isclass[ISTHIEF] |= bitmask;
1281 field = tm->QueryField( i, 6 );
1282 if (field[0]!='*') {
1283 isclass[ISPALADIN] |= bitmask;
1286 // field 7 holds the turn undead level
1288 field = tm->QueryField( i, 8 );
1289 booktypes[i]=atoi(field);
1290 //if booktype == 3 then it is a 'divine sorceror' class
1291 //we shouldn't hardcode iwd2 classes this heavily
1292 if (booktypes[i]==2) {
1293 isclass[ISSORCERER] |= bitmask;
1296 field = tm->QueryField( i, 9 );
1297 if (field[0]!='*') {
1298 isclass[ISRANGER] |= bitmask;
1301 field = tm->QueryField( i, 10 );
1302 if (!strnicmp(field, "CLABMO", 6)) {
1303 isclass[ISMONK] |= bitmask;
1305 bitmask <<=1;
1307 } else {
1308 classcount = 0; //well
1311 i = core->GetMaximumAbility();
1312 maximum_values[IE_STR]=i;
1313 maximum_values[IE_INT]=i;
1314 maximum_values[IE_DEX]=i;
1315 maximum_values[IE_CON]=i;
1316 maximum_values[IE_CHR]=i;
1317 maximum_values[IE_WIS]=i;
1318 if (ReverseToHit) {
1319 //all games except iwd2
1320 maximum_values[IE_ARMORCLASS]=20;
1321 } else {
1322 //iwd2
1323 maximum_values[IE_ARMORCLASS]=199;
1326 //initializing the vvc resource references
1327 tm.load("damage");
1328 if (tm) {
1329 for (i=0;i<DAMAGE_LEVELS;i++) {
1330 const char *tmp = tm->QueryField( i, COL_MAIN );
1331 strnlwrcpy(d_main[i], tmp, 8);
1332 if (d_main[i][0]=='*') {
1333 d_main[i][0]=0;
1335 tmp = tm->QueryField( i, COL_SPARKS );
1336 strnlwrcpy(d_splash[i], tmp, 8);
1337 if (d_splash[i][0]=='*') {
1338 d_splash[i][0]=0;
1340 tmp = tm->QueryField( i, COL_GRADIENT );
1341 d_gradient[i]=atoi(tmp);
1345 tm.load("overlay");
1346 if (tm) {
1347 ieDword mask = 1;
1348 for (i=0;i<OVERLAY_COUNT;i++) {
1349 const char *tmp = tm->QueryField( i, 0 );
1350 strnlwrcpy(hc_overlays[i], tmp, 8);
1351 if (atoi(tm->QueryField( i, 1))) {
1352 hc_locations|=mask;
1354 mask<<=1;
1358 //csound for bg1/bg2
1359 memset(csound,0,sizeof(csound));
1360 if (!core->HasFeature(GF_SOUNDFOLDERS)) {
1361 tm.load("csound");
1362 if (tm) {
1363 for(i=0;i<VCONST_COUNT;i++) {
1364 const char *tmp = tm->QueryField( i, 0 );
1365 if (tmp[0]!='*') {
1366 csound[i]=tmp[0];
1372 tm.load("qslots");
1373 GUIBTDefaults = (ActionButtonRow *) calloc( classcount,sizeof(ActionButtonRow) );
1375 for (i = 0; i < classcount; i++) {
1376 memcpy(GUIBTDefaults+i, &DefaultButtons, sizeof(ActionButtonRow));
1377 if (tm) {
1378 for (int j=0;j<MAX_QSLOTS;j++) {
1379 GUIBTDefaults[i][j+3]=(ieByte) atoi( tm->QueryField(i,j) );
1384 tm.load("itemuse");
1385 if (tm) {
1386 usecount = tm->GetRowCount();
1387 itemuse = new ItemUseType[usecount];
1388 for (i = 0; i < usecount; i++) {
1389 itemuse[i].stat = (ieByte) core->TranslateStat( tm->QueryField(i,0) );
1390 strnlwrcpy(itemuse[i].table, tm->QueryField(i,1),8 );
1391 itemuse[i].mcol = (ieByte) atoi( tm->QueryField(i,2) );
1392 itemuse[i].vcol = (ieByte) atoi( tm->QueryField(i,3) );
1393 itemuse[i].which = (ieByte) atoi( tm->QueryField(i,4) );
1394 //limiting it to 0 or 1 to avoid crashes
1395 if (itemuse[i].which!=1) {
1396 itemuse[i].which=0;
1401 tm.load("itemanim");
1402 if (tm) {
1403 animcount = tm->GetRowCount();
1404 itemanim = new ItemAnimType[animcount];
1405 for (i = 0; i < animcount; i++) {
1406 strnlwrcpy(itemanim[i].itemname, tm->QueryField(i,0),8 );
1407 itemanim[i].animation = (ieByte) atoi( tm->QueryField(i,1) );
1411 tm.load("mxsplwis");
1412 if (tm) {
1413 spllevels = tm->GetColumnCount(0);
1414 int max = core->GetMaximumAbility();
1415 mxsplwis = (int *) calloc(max*spllevels, sizeof(int));
1416 for (i = 0; i < spllevels; i++) {
1417 for(int j = 0; j < max; j++) {
1418 int k = atoi(tm->GetRowName(j))-1;
1419 if (k>=0 && k<max) {
1420 mxsplwis[k*spllevels+i]=atoi(tm->QueryField(j,i));
1426 tm.load("featreq");
1427 if (tm) {
1428 unsigned int tmp;
1430 for(i=0;i<MAX_FEATS;i++) {
1431 //we need the MULTIPLE column only
1432 //it stores the FEAT_* stat index, and could be taken multiple
1433 //times
1434 tmp = core->TranslateStat(tm->QueryField(i,0));
1435 if (tmp>=MAX_STATS) {
1436 printMessage("Actor","Invalid stat value in featreq.2da",YELLOW);
1438 featstats[i] = (ieByte) tmp;
1442 //default all hp con bonuses to 9; this should be updated below
1443 //TODO: check iwd2
1444 maxhpconbon = (int *) calloc(classcount, sizeof(int));
1445 for (i = 0; i < classcount; i++) {
1446 maxhpconbon[i] = 9;
1448 tm.load("classes");
1449 if (tm && !core->HasFeature(GF_LEVELSLOT_PER_CLASS)) {
1450 AutoTable hptm;
1451 //iwd2 just uses levelslotsiwd2 instead
1452 printf("Examining classes.2da\n");
1454 //when searching the levelslots, you must search for
1455 //levelslots[BaseStats[IE_CLASS]-1] as there is no class id of 0
1456 levelslots = (int **) calloc(classcount, sizeof(int*));
1457 dualswap = (int *) calloc(classcount, sizeof(int));
1458 ieDword tmpindex;
1459 for (i=0; i<classcount; i++) {
1460 //make sure we have a valid classid, then decrement
1461 //it to get the correct array index
1462 tmpindex = atoi(tm->QueryField(i, 5));
1463 if (!tmpindex)
1464 continue;
1465 tmpindex--;
1467 printf("\tID: %d ", tmpindex);
1468 //only create the array if it isn't yet made
1469 //i.e. barbarians would overwrite fighters in bg2
1470 if (levelslots[tmpindex]) {
1471 printf ("Already Found!\n");
1472 continue;
1475 const char* classname = tm->GetRowName(i);
1476 printf("Name: %s ", classname);
1477 int classis = 0;
1478 //default all levelslots to 0
1479 levelslots[tmpindex] = (int *) calloc(ISCLASSES, sizeof(int));
1481 //single classes only worry about IE_LEVEL
1482 ieDword tmpclass = atoi(tm->QueryField(i, 4));
1483 if (!tmpclass) {
1484 classis = IsClassFromName(classname);
1485 if (classis>=0) {
1486 printf("Classis: %d ", classis);
1487 levelslots[tmpindex][classis] = IE_LEVEL;
1488 //get the max hp con bonus
1489 hptm.load(tm->QueryField(i, 6));
1490 if (hptm) {
1491 int tmphp = 0;
1492 int rollscolumn = hptm->GetColumnIndex("ROLLS");
1493 while (atoi(hptm->QueryField(tmphp, rollscolumn)))
1494 tmphp++;
1495 printf("TmpHP: %d ", tmphp);
1496 if (tmphp) maxhpconbon[tmpindex] = tmphp;
1499 continue;
1502 //we have to account for dual-swap in the multiclass field
1503 ieDword numfound = 1;
1504 ieDword tmpbits = bitcount (tmpclass);
1506 //we need all the classnames of the multi to compare with the order we load them in
1507 //because the original game set the levels based on name order, not bit order
1508 char **classnames = (char **) calloc(tmpbits, sizeof(char *));
1509 classnames[0] = (char*)strtok(strdup((char*)classname), "_");
1510 while (numfound<tmpbits && (classnames[numfound] = strdup(strtok(NULL, "_")))) {
1511 numfound++;
1513 numfound = 0;
1514 bool foundwarrior = false;
1515 for (int j=0; j<classcount; j++) {
1516 //no sense continuing if we've found all to be found
1517 if (numfound==tmpbits)
1518 break;
1519 if ((1<<j)&tmpclass) {
1520 //save the IE_LEVEL information
1521 const char* currentname = tm->GetRowName((ieDword)(tm->FindTableValue(5, j+1)));
1522 classis = IsClassFromName(currentname);
1523 if (classis>=0) {
1524 //search for the current class in the split of the names to get it's
1525 //correct order
1526 for (ieDword k=0; k<tmpbits; k++) {
1527 if (strcmp(classnames[k], currentname) == 0) {
1528 int tmplevel = 0;
1529 if (k==0) tmplevel = IE_LEVEL;
1530 else if (k==1) tmplevel = IE_LEVEL2;
1531 else tmplevel = IE_LEVEL3;
1532 levelslots[tmpindex][classis] = tmplevel;
1535 printf("Classis: %d ", classis);
1537 //warrior take presedence
1538 if (!foundwarrior) {
1539 foundwarrior = (classis==ISFIGHTER||classis==ISRANGER||classis==ISPALADIN||
1540 classis==ISBARBARIAN);
1541 hptm.load(tm->QueryField(currentname, "HP"));
1542 if (hptm) {
1543 int tmphp = 0;
1544 int rollscolumn = hptm->GetColumnIndex("ROLLS");
1545 while (atoi(hptm->QueryField(tmphp, rollscolumn)))
1546 tmphp++;
1547 //make sure we at least set the first class
1548 if ((tmphp>maxhpconbon[tmpindex])||foundwarrior||numfound==0)
1549 maxhpconbon[tmpindex]=tmphp;
1554 //save the MC_WAS_ID of the first class in the dual-class
1555 if (numfound==0 && tmpbits==2) {
1556 if (strcmp(classnames[0], currentname) == 0) {
1557 dualswap[tmpindex] = strtol(tm->QueryField(classname, "MC_WAS_ID"), NULL, 0);
1559 } else if (numfound==1 && tmpbits==2 && !dualswap[tmpindex]) {
1560 dualswap[tmpindex] = strtol(tm->QueryField(classname, "MC_WAS_ID"), NULL, 0);
1562 numfound++;
1565 if (classnames) {
1566 for (ieDword j=0; j<tmpbits; j++) {
1567 if (classnames[j]) {
1568 free(classnames[j]);
1571 free(classnames);
1572 classnames = NULL;
1574 printf("HPCON: %d ", maxhpconbon[tmpindex]);
1575 printf("DS: %d\n", dualswap[tmpindex]);
1577 /*this could be enabled to ensure all levelslots are filled with at least 0's;
1578 *however, the access code should ensure this never happens
1579 for (i=0; i<classcount; i++) {
1580 if (!levelslots[i]) {
1581 levelslots[i] = (int *) calloc(ISCLASSES, sizeof(int *));
1585 printf("Finished examining classes.2da\n");
1587 //pre-cache hit/damage/speed bonuses for weapons
1588 tm.load("wspecial");
1589 if (tm) {
1590 //load in the identifiers
1591 wspecial_max = tm->GetRowCount()-1;
1592 int cols = tm->GetColumnCount();
1593 wspecial = (int **) calloc(wspecial_max+1, sizeof(int *));
1595 for (i=0; i<=wspecial_max; i++) {
1596 wspecial[i] = (int *) calloc(WSPECIAL_COLS, sizeof(int));
1597 for (int j=0; j<cols; j++) {
1598 wspecial[i][j] = atoi(tm->QueryField(i, j));
1603 //pre-cache attack per round bonuses
1604 tm.load("wspatck");
1605 if (tm) {
1606 wspattack_rows = tm->GetRowCount();
1607 wspattack_cols = tm->GetColumnCount();
1608 wspattack = (int **) calloc(wspattack_rows, sizeof(int *));
1610 int tmp = 0;
1611 for (i=0; i<wspattack_rows; i++) {
1612 wspattack[i] = (int *) calloc(wspattack_cols, sizeof(int));
1613 for (int j=0; j<wspattack_cols; j++) {
1614 tmp = atoi(tm->QueryField(i, j));
1615 //negative values relate to x/2, so we adjust them
1616 //positive values relate to x, so we must times by 2
1617 if (tmp<0) tmp = -2*tmp-1;
1618 else tmp *= 2;
1619 wspattack[i][j] = tmp;
1624 //dual-wielding table
1625 tm.load("wstwowpn");
1626 if (tm) {
1627 wsdualwield = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1628 int cols = tm->GetColumnCount();
1629 for (i=0; i<=STYLE_MAX; i++) {
1630 wsdualwield[i] = (int *) calloc(cols, sizeof(int));
1631 for (int j=0; j<cols; j++) {
1632 wsdualwield[i][j] = atoi(tm->QueryField(i, j));
1637 //two-handed table
1638 tm.load("wstwohnd");
1639 if (tm) {
1640 wstwohanded = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1641 int cols = tm->GetColumnCount();
1642 for (i=0; i<=STYLE_MAX; i++) {
1643 wstwohanded[i] = (int *) calloc(cols, sizeof(int));
1644 for (int j=0; j<cols; j++) {
1645 wstwohanded[i][j] = atoi(tm->QueryField(i, j));
1650 //two-handed table
1651 tm.load("wsshield");
1652 if (tm) {
1653 wsswordshield = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1654 int cols = tm->GetColumnCount();
1655 for (i=0; i<=STYLE_MAX; i++) {
1656 wsswordshield[i] = (int *) calloc(cols, sizeof(int));
1657 for (int j=0; j<cols; j++) {
1658 wsswordshield[i][j] = atoi(tm->QueryField(i, j));
1663 //two-handed table
1664 tm.load("wssingle");
1665 if (tm) {
1666 wssingle = (int **) calloc(STYLE_MAX+1, sizeof(int *));
1667 int cols = tm->GetColumnCount();
1668 for (i=0; i<=STYLE_MAX; i++) {
1669 wssingle[i] = (int *) calloc(cols, sizeof(int));
1670 for (int j=0; j<cols; j++) {
1671 wssingle[i][j] = atoi(tm->QueryField(i, j));
1676 //unhardcoded monk bonus table
1677 tm.load("monkbon");
1678 if (tm) {
1679 monkbon_rows = tm->GetRowCount();
1680 monkbon_cols = tm->GetColumnCount();
1681 monkbon = (int **) calloc(monkbon_rows, sizeof(int *));
1682 for (i=0; i<monkbon_rows; i++) {
1683 monkbon[i] = (int *) calloc(monkbon_cols, sizeof(int));
1684 for (int j=0; j<monkbon_cols; j++) {
1685 monkbon[i][j] = atoi(tm->QueryField(i, j));
1690 //wild magic level modifiers
1691 for(i=0;i<20;i++) {
1692 wmlevels[i]=(int *) calloc(MAX_LEVEL,sizeof(int) );
1694 tm.load("lvlmodwm");
1695 if (tm) {
1696 int maxrow = tm->GetRowCount();
1697 for (i=0;i<20;i++) {
1698 for(j=0;j<MAX_LEVEL;j++) {
1699 int row = maxrow;
1700 if (j<row) row=j;
1701 wmlevels[i][j]=strtol(tm->QueryField(row,i), NULL, 0);
1706 // reputation modifiers
1707 tm.load("reputati");
1708 if (tm) {
1709 reputationmod = (int **) calloc(21, sizeof(int *));
1710 int cols = tm->GetColumnCount();
1711 for (i=0; i<20; i++) {
1712 reputationmod[i] = (int *) calloc(cols, sizeof(int));
1713 for (int j=0; j<cols; j++) {
1714 reputationmod[i][j] = atoi(tm->QueryField(i, j));
1720 void Actor::SetLockedPalette(const ieDword *gradients)
1722 if (!anims) return; //cannot apply it (yet)
1723 anims->LockPalette(gradients);
1726 void Actor::UnlockPalette()
1728 if (!anims) return;
1729 anims->lockPalette=false;
1730 anims->SetColors(Modified+IE_COLORS);
1733 void Actor::AddAnimation(const ieResRef resource, int gradient, int height, int flags)
1735 ScriptedAnimation *sca = gamedata->GetScriptedAnimation(resource, false);
1736 if (!sca)
1737 return;
1738 sca->ZPos=height;
1739 if (flags&AA_PLAYONCE) {
1740 sca->PlayOnce();
1742 if (flags&&AA_BLEND) {
1743 //pst anims need this?
1744 sca->SetBlend();
1746 if (gradient!=-1) {
1747 sca->SetPalette(gradient, 4);
1749 AddVVCell(sca);
1752 void Actor::PlayDamageAnimation(int type, bool hit)
1754 int i;
1756 switch(type) {
1757 case 0: case 1: case 2: case 3: //blood
1758 i = (int) GetStat(IE_ANIMATION_ID)>>16;
1759 if (!i) i = d_gradient[type];
1760 if(hit) {
1761 AddAnimation(d_main[type], i, 0, AA_PLAYONCE);
1763 break;
1764 case 4: case 5: case 6: //fire
1765 if(hit) {
1766 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1768 for(i=DL_FIRE;i<=type;i++) {
1769 AddAnimation(d_splash[i], d_gradient[i], 0, AA_PLAYONCE);
1771 break;
1772 case 7: case 8: case 9: //electricity
1773 if (hit) {
1774 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1776 for(i=DL_ELECTRICITY;i<=type;i++) {
1777 AddAnimation(d_splash[i], d_gradient[i], 0, AA_PLAYONCE);
1779 break;
1780 case 10: case 11: case 12://cold
1781 if (hit) {
1782 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1784 break;
1785 case 13: case 14: case 15://acid
1786 if (hit) {
1787 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1789 break;
1790 case 16: case 17: case 18://disintegrate
1791 if (hit) {
1792 AddAnimation(d_main[type], d_gradient[type], 0, AA_PLAYONCE);
1794 break;
1798 bool Actor::SetStat(unsigned int StatIndex, ieDword Value, int pcf)
1800 if (StatIndex >= MAX_STATS) {
1801 return false;
1803 if ( (signed) Value<-100) {
1804 Value = (ieDword) -100;
1806 else {
1807 if ( maximum_values[StatIndex]>0) {
1808 if ( (signed) Value>maximum_values[StatIndex]) {
1809 Value = (ieDword) maximum_values[StatIndex];
1814 unsigned int previous = Modified[StatIndex];
1815 if (Modified[StatIndex]!=Value) {
1816 Modified[StatIndex] = Value;
1817 if (pcf) {
1818 PostChangeFunctionType f = post_change_functions[StatIndex];
1819 if (f) (*f)(this, previous, Value);
1822 return true;
1825 int Actor::GetMod(unsigned int StatIndex)
1827 if (StatIndex >= MAX_STATS) {
1828 return 0xdadadada;
1830 return (signed) Modified[StatIndex] - (signed) BaseStats[StatIndex];
1832 /** Returns a Stat Base Value */
1833 ieDword Actor::GetBase(unsigned int StatIndex)
1835 if (StatIndex >= MAX_STATS) {
1836 return 0xffff;
1838 return BaseStats[StatIndex];
1841 /** Sets a Stat Base Value */
1842 /** If required, modify the modified value and run the pcf function */
1843 bool Actor::SetBase(unsigned int StatIndex, ieDword Value)
1845 if (StatIndex >= MAX_STATS) {
1846 return false;
1848 ieDword diff = Modified[StatIndex]-BaseStats[StatIndex];
1850 //maximize the base stat
1851 if ( maximum_values[StatIndex]) {
1852 if ( (signed) Value>maximum_values[StatIndex]) {
1853 Value = (ieDword) maximum_values[StatIndex];
1857 BaseStats[StatIndex] = Value;
1859 //if already initialized, then the modified stats
1860 //might need to run the post change function (stat change can kill actor)
1861 SetStat (StatIndex, Value+diff, InternalFlags&IF_INITIALIZED);
1862 return true;
1865 bool Actor::SetBaseNoPCF(unsigned int StatIndex, ieDword Value)
1867 if (StatIndex >= MAX_STATS) {
1868 return false;
1870 ieDword diff = Modified[StatIndex]-BaseStats[StatIndex];
1872 //maximize the base stat
1873 if ( maximum_values[StatIndex]) {
1874 if ( (signed) Value>maximum_values[StatIndex]) {
1875 Value = (ieDword) maximum_values[StatIndex];
1879 BaseStats[StatIndex] = Value;
1881 //if already initialized, then the modified stats
1882 //might need to run the post change function (stat change can kill actor)
1883 SetStat (StatIndex, Value+diff, 0);
1884 return true;
1887 bool Actor::SetBaseBit(unsigned int StatIndex, ieDword Value, bool setreset)
1889 if (StatIndex >= MAX_STATS) {
1890 return false;
1892 if (setreset) {
1893 BaseStats[StatIndex] |= Value;
1894 } else {
1895 BaseStats[StatIndex] &= ~Value;
1897 //if already initialized, then the modified stats
1898 //need to run the post change function (stat change can kill actor)
1899 if (setreset) {
1900 SetStat (StatIndex, Modified[StatIndex]|Value, InternalFlags&IF_INITIALIZED);
1901 } else {
1902 SetStat (StatIndex, Modified[StatIndex]&~Value, InternalFlags&IF_INITIALIZED);
1904 return true;
1907 const unsigned char *Actor::GetStateString()
1909 if (!PCStats) {
1910 return NULL;
1912 ieByte *tmp = PCStats->PortraitIconString;
1913 ieWord *Icons = PCStats->PortraitIcons;
1914 int j=0;
1915 for (int i=0;i<MAX_PORTRAIT_ICONS;i++) {
1916 if (!(Icons[i]&0xff00)) {
1917 tmp[j++]=(ieByte) ((Icons[i]&0xff)+66);
1920 tmp[j]=0;
1921 return tmp;
1924 void Actor::AddPortraitIcon(ieByte icon)
1926 if (!PCStats) {
1927 return;
1929 ieWord *Icons = PCStats->PortraitIcons;
1931 for(int i=0;i<MAX_PORTRAIT_ICONS;i++) {
1932 if (Icons[i]==0xffff) {
1933 Icons[i]=icon;
1934 return;
1936 if (icon == (Icons[i]&0xff)) {
1937 return;
1942 void Actor::DisablePortraitIcon(ieByte icon)
1944 if (!PCStats) {
1945 return;
1947 ieWord *Icons = PCStats->PortraitIcons;
1948 int i;
1950 for(i=0;i<MAX_PORTRAIT_ICONS;i++) {
1951 if (icon == (Icons[i]&0xff)) {
1952 Icons[i]=0xff00|icon;
1953 return;
1958 /** call this after load, to apply effects */
1959 void Actor::RefreshEffects(EffectQueue *fx)
1961 ieDword previous[MAX_STATS];
1963 //put all special cleanup calls here
1964 CharAnimations* anims = GetAnims();
1965 if (anims) {
1966 anims->GlobalColorMod.type = RGBModifier::NONE;
1967 anims->GlobalColorMod.speed = 0;
1968 unsigned int location;
1969 for (location = 0; location < 32; ++location) {
1970 anims->ColorMods[location].type = RGBModifier::NONE;
1971 anims->ColorMods[location].speed = 0;
1974 spellbook.ClearBonus();
1975 memset(applyWhenHittingMelee,0,sizeof(ieResRef));
1976 memset(applyWhenHittingRanged,0,sizeof(ieResRef));
1977 memset(applyWhenNearLiving,0,sizeof(ieResRef));
1978 memset(applyWhen50Damage,0,sizeof(ieResRef));
1979 memset(applyWhen90Damage,0,sizeof(ieResRef));
1980 memset(applyWhenEnemySighted,0,sizeof(ieResRef));
1981 memset(applyWhenPoisoned,0,sizeof(ieResRef));
1982 memset(applyWhenHelpless,0,sizeof(ieResRef));
1983 memset(applyWhenAttacked,0,sizeof(ieResRef));
1984 memset(applyWhenBeingHit,0,sizeof(ieResRef));
1985 memset(projectileImmunity,0,ProjectileSize*sizeof(ieDword));
1987 //initialize base stats
1988 bool first = !(InternalFlags&IF_INITIALIZED);
1990 if (first) {
1991 InternalFlags|=IF_INITIALIZED;
1992 memcpy( previous, BaseStats, MAX_STATS * sizeof( ieDword ) );
1993 } else {
1994 memcpy( previous, Modified, MAX_STATS * sizeof( ieDword ) );
1996 memcpy( Modified, BaseStats, MAX_STATS * sizeof( ieDword ) );
1997 if (PCStats) memset( PCStats->PortraitIcons, -1, sizeof(PCStats->PortraitIcons) );
1999 if (fx) {
2000 fx->SetOwner(this);
2001 fx->AddAllEffects(this, Pos);
2002 delete fx;
2003 //copy back the original stats, because the effects
2004 //will be reapplied in ApplyAllEffects again
2005 memcpy( Modified, BaseStats, MAX_STATS * sizeof( ieDword ) );
2006 //also clear the spell bonuses just given, they will be
2007 //recalculated below again
2008 spellbook.ClearBonus();
2011 fxqueue.ApplyAllEffects( this );
2013 // IE_CLASS is >classcount for non-PCs/NPCs
2014 if (BaseStats[IE_CLASS] <= (ieDword)classcount)
2015 RefreshPCStats();
2017 for (unsigned int i=0;i<MAX_STATS;i++) {
2018 if (first || Modified[i]!=previous[i]) {
2019 PostChangeFunctionType f = post_change_functions[i];
2020 if (f) {
2021 (*f)(this, previous[i], Modified[i]);
2025 //add wisdom bonus spells
2026 if (!spellbook.IsIWDSpellBook() && mxsplwis) {
2027 int level = Modified[IE_WIS];
2028 if (level--) {
2029 spellbook.BonusSpells(IE_SPELL_TYPE_PRIEST, spllevels, mxsplwis+spllevels*level);
2033 // check if any new portrait icon was removed or added
2034 if (PCStats) {
2035 if (memcmp(PCStats->PreviousPortraitIcons, PCStats->PortraitIcons, sizeof(PCStats->PreviousPortraitIcons))) {
2036 core->SetEventFlag(EF_PORTRAIT);
2037 memcpy( PCStats->PreviousPortraitIcons, PCStats->PortraitIcons, sizeof(PCStats->PreviousPortraitIcons) );
2042 // refresh stats on creatures (PC or NPC) with a valid class (not animals etc)
2043 // internal use only, and this is maybe a stupid name :)
2044 void Actor::RefreshPCStats() {
2045 //calculate hp bonus
2046 int bonus;
2047 int bonlevel = GetXPLevel(true);
2048 int oldlevel, oldbonus;
2049 oldlevel = oldbonus = 0;
2050 ieDword bonindex = BaseStats[IE_CLASS]-1;
2052 //we must limit the levels to the max allowable
2053 if (bonlevel>maxhpconbon[bonindex])
2054 bonlevel = maxhpconbon[bonindex];
2056 if (IsDualInactive()) {
2057 //we apply the inactive hp bonus if it's better than the new hp bonus, so that we
2058 //never lose hp, only gain, on leveling
2059 oldlevel = IsDualSwap() ? BaseStats[IE_LEVEL] : BaseStats[IE_LEVEL2];
2060 bonlevel = IsDualSwap() ? BaseStats[IE_LEVEL2] : BaseStats[IE_LEVEL];
2061 oldlevel = (oldlevel > maxhpconbon[bonindex]) ? maxhpconbon[bonindex] : oldlevel;
2062 if (Modified[IE_MC_FLAGS] & (MC_WAS_FIGHTER|MC_WAS_RANGER)) {
2063 oldbonus = core->GetConstitutionBonus(STAT_CON_HP_WARRIOR, Modified[IE_CON]);
2064 } else {
2065 oldbonus = core->GetConstitutionBonus(STAT_CON_HP_NORMAL, Modified[IE_CON]);
2069 // warrior (fighter, barbarian, ranger, or paladin) or not
2070 // GetClassLevel now takes into consideration inactive dual-classes
2071 if (IsWarrior()) {
2072 bonus = core->GetConstitutionBonus(STAT_CON_HP_WARRIOR,Modified[IE_CON]);
2073 } else {
2075 bonus = core->GetConstitutionBonus(STAT_CON_HP_NORMAL,Modified[IE_CON]);
2077 bonus *= bonlevel;
2078 oldbonus *= oldlevel;
2079 bonus = (oldbonus > bonus) ? oldbonus : bonus;
2081 //morale recovery every xth AI cycle
2082 int mrec = GetStat(IE_MORALERECOVERYTIME);
2083 if (mrec) {
2084 if (!(core->GetGame()->GameTime%mrec)) {
2085 NewBase(IE_MORALE,1,MOD_ADDITIVE);
2089 if (bonus<0 && (Modified[IE_MAXHITPOINTS]+bonus)<=0) {
2090 bonus=1-Modified[IE_MAXHITPOINTS];
2093 //get the wspattack bonuses for proficiencies
2094 WeaponInfo wi;
2095 ITMExtHeader *header = GetWeapon(wi, false);
2096 ieDword stars;
2097 int dualwielding = IsDualWielding();
2098 if (header && (wi.prof <= MAX_STATS)) {
2099 stars = GetStat(wi.prof)&PROFS_MASK;
2100 if (stars >= (unsigned)wspattack_rows) {
2101 stars = wspattack_rows-1;
2104 int tmplevel = GetWarriorLevel();
2105 if (tmplevel >= wspattack_cols) {
2106 tmplevel = wspattack_cols-1;
2107 } else if (tmplevel < 0) {
2108 tmplevel = 0;
2111 //HACK: attacks per round bonus for monks should only apply to fists
2112 if (isclass[ISMONK]&(1<<BaseStats[IE_CLASS])) {
2113 unsigned int level = GetMonkLevel()-1;
2114 SetBase(IE_NUMBEROFATTACKS, 2 + monkbon[0][level]);
2115 } else {
2116 //wspattack appears to only effect warriors
2117 int defaultattacks = 2 + 2*dualwielding;
2118 if (tmplevel) {
2119 SetBase(IE_NUMBEROFATTACKS, defaultattacks+wspattack[stars][tmplevel]);
2120 } else {
2121 SetBase(IE_NUMBEROFATTACKS, defaultattacks);
2126 //we still apply the maximum bonus to dead characters, but don't apply
2127 //to current HP, or we'd have dead characters showing as having hp
2128 //Modified[IE_MAXHITPOINTS]+=bonus;
2129 //if(BaseStats[IE_STATE_ID]&STATE_DEAD)
2130 // bonus = 0;
2131 // BaseStats[IE_HITPOINTS]+=bonus;
2133 // apply the intelligence and wisdom bonus to lore
2134 Modified[IE_LORE] += core->GetLoreBonus(0, Modified[IE_INT]) + core->GetLoreBonus(0, Modified[IE_WIS]);
2137 void Actor::RollSaves()
2139 if (InternalFlags&IF_USEDSAVE) {
2140 SavingThrow[0]=(ieByte) core->Roll(1, SAVEROLL, 0);
2141 SavingThrow[1]=(ieByte) core->Roll(1, SAVEROLL, 0);
2142 SavingThrow[2]=(ieByte) core->Roll(1, SAVEROLL, 0);
2143 SavingThrow[3]=(ieByte) core->Roll(1, SAVEROLL, 0);
2144 SavingThrow[4]=(ieByte) core->Roll(1, SAVEROLL, 0);
2145 InternalFlags&=~IF_USEDSAVE;
2149 //saving throws:
2150 //type bits in file order in stats
2151 //0 spells 1 4
2152 //1 breath 2 3
2153 //2 death 4 0
2154 //3 wands 8 1
2155 //4 polymorph 16 2
2157 //iwd2 (luckily they use the same bits as it would be with bg2):
2158 //0 not used
2159 //1 not used
2160 //2 fortitude 4 0
2161 //3 reflex 8 1
2162 //4 will 16 2
2164 #define SAVECOUNT 5
2165 static int savingthrows[SAVECOUNT]={IE_SAVEVSSPELL, IE_SAVEVSBREATH, IE_SAVEVSDEATH, IE_SAVEVSWANDS, IE_SAVEVSPOLY};
2167 /** returns true if actor made the save against saving throw type */
2168 bool Actor::GetSavingThrow(ieDword type, int modifier)
2170 assert(type<SAVECOUNT);
2171 InternalFlags|=IF_USEDSAVE;
2172 int ret = SavingThrow[type];
2173 if (ret == 1) return false;
2174 if (ret == SAVEROLL) return true;
2175 ret += modifier + GetStat(IE_LUCK);
2176 return ret > (int) GetStat(savingthrows[type]);
2179 /** implements a generic opcode function, modify modified stats
2180 returns the change
2182 int Actor::NewStat(unsigned int StatIndex, ieDword ModifierValue, ieDword ModifierType)
2184 int oldmod = Modified[StatIndex];
2186 switch (ModifierType) {
2187 case MOD_ADDITIVE:
2188 //flat point modifier
2189 SetStat(StatIndex, Modified[StatIndex]+ModifierValue, 0);
2190 break;
2191 case MOD_ABSOLUTE:
2192 //straight stat change
2193 SetStat(StatIndex, ModifierValue, 0);
2194 break;
2195 case MOD_PERCENT:
2196 //percentile
2197 SetStat(StatIndex, BaseStats[StatIndex] * ModifierValue / 100, 0);
2198 break;
2200 return Modified[StatIndex] - oldmod;
2203 int Actor::NewBase(unsigned int StatIndex, ieDword ModifierValue, ieDword ModifierType)
2205 int oldmod = BaseStats[StatIndex];
2207 switch (ModifierType) {
2208 case MOD_ADDITIVE:
2209 //flat point modifier
2210 SetBase(StatIndex, BaseStats[StatIndex]+ModifierValue);
2211 break;
2212 case MOD_ABSOLUTE:
2213 //straight stat change
2214 SetBase(StatIndex, ModifierValue);
2215 break;
2216 case MOD_PERCENT:
2217 //percentile
2218 SetBase(StatIndex, BaseStats[StatIndex] * ModifierValue / 100);
2219 break;
2221 return BaseStats[StatIndex] - oldmod;
2224 inline int CountElements(const char *s, char separator)
2226 int ret = 1;
2227 while(*s) {
2228 if (*s==separator) ret++;
2229 s++;
2231 return ret;
2234 void Actor::Interact(int type)
2236 int start;
2237 int count;
2239 switch(type) {
2240 case I_INSULT: start=VB_INSULT; count=3; break;
2241 case I_COMPLIMENT: start=VB_COMPLIMENT; count=3; break;
2242 case I_SPECIAL: start=VB_SPECIAL; count=3; break;
2243 default:
2244 return;
2246 VerbalConstant(start, count);
2249 void Actor::VerbalConstant(int start, int count)
2251 count=rand()%count;
2252 while(count>=0 && (!StrRefs[start+count] || (StrRefs[start+count]==(ieStrRef) -1)) ) count--;
2253 if(count>=0) {
2254 DisplayStringCore(this, start+count, DS_CONSOLE|DS_CONST );
2258 void Actor::Response(int type)
2260 int start;
2261 int count;
2263 switch(type) {
2264 case I_INSULT: start=VB_RESP_INS; count=3; break;
2265 case I_COMPLIMENT: start=VB_RESP_COMP; count=3; break;
2266 default:
2267 return;
2270 count=rand()%count;
2271 while(count && StrRefs[start+count]!=0xffff) count--;
2272 if(count>=0) {
2273 DisplayStringCore(this, start+count, DS_CONSOLE|DS_CONST );
2277 void Actor::ReactToDeath(const char * deadname)
2279 AutoTable tm("death");
2280 if (!tm) return;
2281 // lookup value based on died's scriptingname and ours
2282 // if value is 0 - use reactdeath
2283 // if value is 1 - use reactspecial
2284 // if value is string - use playsound instead (pst)
2285 const char *value = tm->QueryField (scriptName, deadname);
2286 switch (value[0]) {
2287 case '0':
2288 DisplayStringCore(this, VB_REACT, DS_CONSOLE|DS_CONST );
2289 break;
2290 case '1':
2291 DisplayStringCore(this, VB_REACT_S, DS_CONSOLE|DS_CONST );
2292 break;
2293 default:
2295 int count = CountElements(value,',');
2296 if (count<=0) break;
2297 count = core->Roll(1,count,-1);
2298 ieResRef resref;
2299 while(count--) {
2300 while(*value && *value!=',') value++;
2301 if (*value==',') value++;
2303 strncpy(resref, value, 8);
2304 for(count=0;count<8 && resref[count]!=',';count++) {};
2305 resref[count]=0;
2307 ieDword len = core->GetAudioDrv()->Play( resref );
2308 ieDword counter = ( AI_UPDATE_TIME * len ) / 1000;
2309 if (counter != 0)
2310 SetWait( counter );
2311 break;
2316 //call this only from gui selects
2317 void Actor::SelectActor()
2319 DisplayStringCore(this, VB_SELECT, DS_CONSOLE|DS_CONST );
2322 void Actor::Panic()
2324 if (GetStat(IE_STATE_ID)&STATE_PANIC) {
2325 //already in panic
2326 return;
2328 if (InParty) core->GetGame()->SelectActor(this, false, SELECT_NORMAL);
2329 SetBaseBit(IE_STATE_ID, STATE_PANIC, true);
2330 DisplayStringCore(this, VB_PANIC, DS_CONSOLE|DS_CONST );
2333 void Actor::SetMCFlag(ieDword arg, int op)
2335 ieDword tmp = BaseStats[IE_MC_FLAGS];
2336 switch (op) {
2337 case BM_SET: tmp = arg; break;
2338 case BM_OR: tmp |= arg; break;
2339 case BM_NAND: tmp &= ~arg; break;
2340 case BM_XOR: tmp ^= arg; break;
2341 case BM_AND: tmp &= arg; break;
2343 SetBase(IE_MC_FLAGS, tmp);
2346 void Actor::DialogInterrupt()
2348 //if dialoginterrupt was set, no verbal constant
2349 if ( Modified[IE_MC_FLAGS]&MC_NO_TALK)
2350 return;
2352 /* this part is unsure */
2353 if (Modified[IE_EA]>=EA_EVILCUTOFF) {
2354 DisplayStringCore(this, VB_HOSTILE, DS_CONSOLE|DS_CONST );
2355 } else {
2356 DisplayStringCore(this, VB_DIALOG, DS_CONSOLE|DS_CONST );
2360 static EffectRef fx_cure_sleep_ref={"Cure:Sleep",NULL,-1};
2362 void Actor::GetHit()
2364 SetStance( IE_ANI_DAMAGE );
2365 DisplayStringCore(this, VB_DAMAGE, DS_CONSOLE|DS_CONST );
2366 if (Modified[IE_STATE_ID]&STATE_SLEEP) {
2367 if (Modified[IE_EXTSTATE_ID]&EXTSTATE_NO_WAKEUP) {
2368 return;
2370 Effect *fx = EffectQueue::CreateEffect(fx_cure_sleep_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
2371 fxqueue.AddEffect(fx);
2375 bool Actor::HandleCastingStance(const ieResRef SpellResRef, bool deplete)
2377 if (deplete) {
2378 if (! spellbook.HaveSpell( SpellResRef, HS_DEPLETE )) {
2379 SetStance(IE_ANI_READY);
2380 return true;
2383 SetStance(IE_ANI_CAST);
2384 return false;
2387 static EffectRef fx_sleep_ref={"State:Helpless", NULL, -1};
2389 //returns actual damage
2390 int Actor::Damage(int damage, int damagetype, Scriptable *hitter, int modtype)
2392 //add lastdamagetype up ? maybe
2393 LastDamageType|=damagetype;
2394 if(hitter && hitter->Type==ST_ACTOR) {
2395 LastHitter=((Actor *) hitter)->GetID();
2396 } else {
2397 //Maybe it should be something impossible like 0xffff, and use 'Someone'
2398 LastHitter=GetID();
2401 switch(modtype)
2403 case MOD_ADDITIVE:
2404 //damage = -NewBase(IE_HITPOINTS, (ieDword) -damage, MOD_ADDITIVE);
2405 break;
2406 case MOD_ABSOLUTE:
2407 //damage = -NewBase(IE_HITPOINTS, (ieDword) damage, MOD_ABSOLUTE);
2408 damage = GetBase(IE_HITPOINTS) - damage;
2409 break;
2410 case MOD_PERCENT:
2411 //damage = -NewBase(IE_HITPOINTS, (ieDword) damage, MOD_PERCENT);
2412 damage = GetStat(IE_MAXHITPOINTS) * 100 / damage;
2413 break;
2414 default:
2415 //this shouldn't happen
2416 printMessage("Actor","Invalid damagetype!\n",RED);
2417 return 0;
2420 int resisted = 0;
2421 ModifyDamage (this, (Actor *)hitter, damage, resisted, damagetype, NULL, false);
2422 if (damage) GetHit();
2424 DisplayCombatFeedback(damage, resisted, damagetype, (Actor *)hitter);
2426 if (BaseStats[IE_HITPOINTS] <= (ieDword) damage) {
2427 // common fists do normal damage, but cause sleeping for a round instead of death
2428 if ((damagetype & DAMAGE_STUNNING) && Modified[IE_MINHITPOINTS] <= 0) {
2429 NewBase(IE_HITPOINTS, 1, MOD_ABSOLUTE);
2430 Effect *fx = EffectQueue::CreateEffect(fx_sleep_ref, 0, 0, FX_DURATION_INSTANT_LIMITED);
2431 fx->Duration = 6; // 1 round
2432 core->ApplyEffect(fx, this, this);
2433 delete fx;
2434 } else {
2435 NewBase(IE_HITPOINTS, (ieDword) -damage, MOD_ADDITIVE);
2437 } else {
2438 NewBase(IE_HITPOINTS, (ieDword) -damage, MOD_ADDITIVE);
2440 // also apply reputation damage if we hurt (but not killed) an innocent
2441 int reputation = core->GetGame()->Reputation / 10;
2442 if (Modified[IE_CLASS] == CLASS_INNOCENT) {
2443 core->GetGame()->SetReputation(reputation*10 + reputationmod[reputation-1][1]);
2447 LastDamage=damage;
2448 InternalFlags|=IF_ACTIVE;
2449 int chp = (signed) BaseStats[IE_HITPOINTS];
2450 int damagelevel = 3;
2451 if (damage<5) {
2452 damagelevel = 1;
2453 } else if (damage<10) {
2454 damagelevel = 2;
2455 } else {
2456 NewBase(IE_MORALE, (ieDword) -1, MOD_ADDITIVE);
2457 if (chp<-10) {
2458 damagelevel = 0; //chunky death
2460 else {
2461 damagelevel = 3;
2465 if (damagetype & (DAMAGE_FIRE|DAMAGE_MAGICFIRE) ) {
2466 PlayDamageAnimation(DL_FIRE+damagelevel);
2467 } else if (damagetype & (DAMAGE_COLD|DAMAGE_MAGICCOLD) ) {
2468 PlayDamageAnimation(DL_COLD+damagelevel);
2469 } else if (damagetype & (DAMAGE_ELECTRICITY) ) {
2470 PlayDamageAnimation(DL_ELECTRICITY+damagelevel);
2471 } else if (damagetype & (DAMAGE_ACID) ) {
2472 PlayDamageAnimation(DL_ACID+damagelevel);
2473 } else if (damagetype & (DAMAGE_MAGIC) ) {
2474 PlayDamageAnimation(DL_DISINTEGRATE+damagelevel);
2475 } else {
2476 PlayDamageAnimation(damagelevel);
2479 if (InParty) {
2480 if (chp<(signed) Modified[IE_MAXHITPOINTS]/10) {
2481 core->Autopause(AP_WOUNDED);
2483 if (damage>0) {
2484 core->Autopause(AP_HIT);
2485 core->SetEventFlag(EF_PORTRAIT);
2488 return damage;
2491 //TODO: handle pst
2492 void Actor::DisplayCombatFeedback (unsigned int damage, int resisted, int damagetype, Actor *hitter)
2494 bool detailed = false;
2495 const char *type_name = "unknown";
2496 if (core->GetStringReference(STR_DMG_SLASHING) != (ieStrRef) -1) { // how and iwd2
2497 std::multimap<ieDword, DamageInfoStruct>::iterator it;
2498 it = core->DamageInfoMap.find(damagetype);
2499 if (it != core->DamageInfoMap.end()) {
2500 type_name = core->GetString(it->second.strref, 0);
2502 detailed = true;
2505 if (damage > 0 && resisted != DR_IMMUNE) {
2506 printMessage("Actor", " ", GREEN);
2507 printf("%d damage taken.\n", damage);
2509 if (detailed) {
2510 // 3 choices depending on resistance and boni
2511 // iwd2 also has two Tortoise Shell (spell) absorption strings
2512 core->GetTokenDictionary()->SetAtCopy( "TYPE", type_name);
2513 core->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage);
2514 core->GetTokenDictionary()->SetAtCopy( "DAMAGER", hitter ? hitter->GetName(1) : this->GetName(1) );
2515 if (resisted < 0) {
2516 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage bonus)
2517 core->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted));
2518 core->DisplayConstantStringName(STR_DAMAGE3, 0xffffff, this);
2519 } else if (resisted > 0) {
2520 //Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage resisted)
2521 core->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted));
2522 core->DisplayConstantStringName(STR_DAMAGE2, 0xffffff, this);
2523 } else {
2524 //Takes <AMOUNT> <TYPE> damage from <DAMAGER>
2525 core->DisplayConstantStringName(STR_DAMAGE1, 0xffffff, this);
2527 } else if (stricmp( core->GameType, "pst" ) == 0) {
2528 if(0) printf("TODO: pst floating text\n");
2529 } else if (core->GetStringReference(STR_DAMAGE_IMMUNITY) == (ieStrRef) -1 && core->GetStringReference(STR_DAMAGE1) != (ieStrRef) -1) {
2530 // bg1 and how
2531 // "Damage Taken"
2532 core->DisplayConstantStringName(STR_DAMAGE1, 0xffffff, this);
2533 } else { //bg2
2534 //<DAMAGER> did <AMOUNT> damage to <DAMAGEE>
2535 core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2536 // wipe the DAMAGER token, so we can color it
2537 core->GetTokenDictionary()->SetAtCopy( "DAMAGER", "" );
2538 core->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage);
2539 core->DisplayConstantStringName(STR_DAMAGE1, 0xffffff, hitter);
2541 } else {
2542 if (resisted == DR_IMMUNE) {
2543 printMessage("Actor", " ", GREEN);
2544 printf("is immune to damage type: %s.\n", type_name);
2546 if (detailed) {
2547 //<DAMAGEE> was immune to my <TYPE> damage
2548 core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2549 core->GetTokenDictionary()->SetAtCopy( "TYPE", type_name );
2550 core->DisplayConstantStringName(STR_DAMAGE_IMMUNITY, 0xffffff, hitter);
2551 } else if (core->GetStringReference(STR_DAMAGE_IMMUNITY) != (ieStrRef) -1 && core->GetStringReference(STR_DAMAGE1) != (ieStrRef) -1) {
2552 // bg2
2553 //<DAMAGEE> was immune to my damage.
2554 core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
2555 core->DisplayConstantStringName(STR_DAMAGE_IMMUNITY, 0xffffff, hitter);
2556 } // else: other games don't display anything
2557 } else {
2558 // mirror image or stoneskin: no message
2562 //PST hit sounds
2563 DataFileMgr *resdata = core->GetResDataINI();
2564 if (resdata) {
2565 PlayHitSound(resdata, damagetype, false);
2569 //Play PST specific hit sounds (HIT_0<dtype><armor>)
2570 void Actor::PlayHitSound(DataFileMgr *resdata, int damagetype, bool suffix)
2572 int type;
2574 switch(damagetype) {
2575 case DAMAGE_SLASHING: type = 1; break; //slashing
2576 case DAMAGE_PIERCING: type = 2; break; //piercing
2577 case DAMAGE_CRUSHING: type = 3; break; //crushing
2578 case DAMAGE_MISSILE: type = 4; break; //missile
2579 default: return; //other
2582 ieResRef Sound;
2583 char section[12];
2584 unsigned int animid=BaseStats[IE_ANIMATION_ID];
2585 if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
2586 animid&=0xff;
2589 snprintf(section,10,"%d", animid);
2591 int armor = resdata->GetKeyAsInt(section, "armor",0);
2592 if (armor<0 || armor>35) return;
2594 snprintf(Sound,8,"HIT_0%d%c%c",type, armor+'A', suffix?'1':0);
2596 core->GetAudioDrv()->Play( Sound,Pos.x,Pos.y,0 );
2599 //Just to quickly inspect debug maximum values
2600 #if 0
2601 void Actor::DumpMaxValues()
2603 int symbol = core->LoadSymbol( "stats" );
2604 SymbolMgr *sym = core->GetSymbol( symbol );
2606 for(int i=0;i<MAX_STATS;i++) {
2607 printf("%d (%s) %d\n", i, sym->GetValue(i), maximum_values[i]);
2610 #endif
2612 void Actor::DebugDump()
2614 unsigned int i;
2616 printf( "Debugdump of Actor %s (%s, %s):\n", LongName, ShortName, GetName(-1) );
2617 printf ("Scripts:");
2618 for (i = 0; i < MAX_SCRIPTS; i++) {
2619 const char* poi = "<none>";
2620 if (Scripts[i]) {
2621 poi = Scripts[i]->GetName();
2623 printf( " %.8s", poi );
2625 printf( "\nArea: %.8s ", Area );
2626 printf( "Dialog: %.8s\n", Dialog );
2627 printf( "Global ID: %d Local ID: %d\n", globalID, localID);
2628 printf( "Script name:%.32s\n", scriptName );
2629 printf( "TalkCount: %d ", TalkCount );
2630 printf( "PartySlot: %d\n", InParty );
2631 printf( "Allegiance: %d current allegiance:%d\n", BaseStats[IE_EA], Modified[IE_EA] );
2632 printf( "Class: %d current class:%d\n", BaseStats[IE_CLASS], Modified[IE_CLASS] );
2633 printf( "Race: %d current race:%d\n", BaseStats[IE_RACE], Modified[IE_RACE] );
2634 printf( "Gender: %d current gender:%d\n", BaseStats[IE_SEX], Modified[IE_SEX] );
2635 printf( "Specifics: %d current specifics:%d\n", BaseStats[IE_SPECIFIC], Modified[IE_SPECIFIC] );
2636 printf( "Alignment: %x current alignment:%x\n", BaseStats[IE_ALIGNMENT], Modified[IE_ALIGNMENT] );
2637 printf( "Morale: %d current morale:%d\n", BaseStats[IE_MORALE], Modified[IE_MORALE] );
2638 printf( "Moralebreak:%d Morale recovery:%d\n", Modified[IE_MORALEBREAK], Modified[IE_MORALERECOVERYTIME] );
2639 printf( "Visualrange:%d (Explorer: %d)\n", Modified[IE_VISUALRANGE], Modified[IE_EXPLORE] );
2640 printf( "current HP:%d\n", BaseStats[IE_HITPOINTS] );
2641 printf( "Mod[IE_ANIMATION_ID]: 0x%04X\n", Modified[IE_ANIMATION_ID] );
2642 printf( "Colors: ");
2643 if (core->HasFeature(GF_ONE_BYTE_ANIMID) ) {
2644 for(i=0;i<Modified[IE_COLORCOUNT];i++) {
2645 printf(" %d", Modified[IE_COLORS+i]);
2648 else {
2649 for(i=0;i<7;i++) {
2650 printf(" %d", Modified[IE_COLORS+i]);
2653 printf( "\nWaitCounter: %d\n", (int) GetWait());
2654 printf( "LastTarget: %d %s\n", LastTarget, GetActorNameByID(LastTarget));
2655 printf( "LastTalked: %d %s\n", LastTalkedTo, GetActorNameByID(LastTalkedTo));
2656 inventory.dump();
2657 spellbook.dump();
2658 fxqueue.dump();
2659 #if 0
2660 DumpMaxValues();
2661 #endif
2664 const char* Actor::GetActorNameByID(ieDword ID) const
2666 Actor *actor = GetCurrentArea()->GetActorByGlobalID(ID);
2667 if (!actor) {
2668 return "<NULL>";
2670 return actor->GetScriptName();
2673 void Actor::SetMap(Map *map, ieWord LID, ieWord GID)
2675 //Did we have an area?
2676 bool effinit=!GetCurrentArea();
2677 Scriptable::SetMap(map); //now we have an area
2678 localID = LID;
2679 globalID = GID;
2681 //These functions are called once when the actor is first put in
2682 //the area. It already has all the items (including fist) at this
2683 //time and it is safe to call effects.
2684 //This hack is to delay the equipping effects until the actor has
2685 //an area (and the game object is also existing)
2686 if (effinit) {
2687 int SlotCount = inventory.GetSlotCount();
2688 for (int Slot = 0; Slot<SlotCount;Slot++) {
2689 int slottype = core->QuerySlotEffects( Slot );
2690 switch (slottype) {
2691 case SLOT_EFFECT_NONE:
2692 case SLOT_EFFECT_MELEE:
2693 break;
2694 default:
2695 inventory.EquipItem( Slot );
2696 break;
2699 //We need to convert this to signed 16 bits, because
2700 //it is actually a 16 bit number.
2701 //It is signed to have the correct math
2702 //when adding it to the base slot (SLOT_WEAPON) in
2703 //case of quivers. (weird IE magic)
2704 //The other word is the equipped header.
2705 //find a quiver for the bow, etc
2706 if (Equipped!=IW_NO_EQUIPPED) {
2707 inventory.EquipItem( Equipped+inventory.GetWeaponSlot());
2708 SetEquippedQuickSlot( inventory.GetEquipped(), EquippedHeader );
2713 void Actor::SetPosition(const Point &position, int jump, int radius)
2715 PathTries = 0;
2716 ClearPath();
2717 Point p;
2718 p.x = position.x/16;
2719 p.y = position.y/12;
2720 if (jump && !(Modified[IE_DONOTJUMP] & DNJ_FIT) && size ) {
2721 GetCurrentArea()->AdjustPosition( p, radius );
2723 p.x = p.x * 16 + 8;
2724 p.y = p.y * 12 + 6;
2725 MoveTo( p );
2728 /* this is returning the level of the character for xp calculations
2729 and the average level for dual/multiclass (rounded up),
2730 also with iwd2's 3rd ed rules, this is why it is a separate function */
2731 ieDword Actor::GetXPLevel(int modified) const
2733 const ieDword *stats;
2735 if (modified) {
2736 stats = Modified;
2738 else {
2739 stats = BaseStats;
2742 float classcount = 0;
2743 float average = 0;
2744 if (core->HasFeature(GF_LEVELSLOT_PER_CLASS)) {
2745 // iwd2
2746 for (int i=0; i < 11; i++) {
2747 if (stats[levelslotsiwd2[i]] > 0) classcount++;
2749 average = stats[IE_CLASSLEVELSUM] / classcount + 0.5;
2751 else {
2752 int levels[3]={stats[IE_LEVEL], stats[IE_LEVEL2], stats[IE_LEVEL3]};
2753 average = levels[0];
2754 classcount = 1;
2755 if (IsDualClassed()) {
2756 // dualclassed
2757 if (levels[1] > 0) {
2758 classcount++;
2759 average += levels[1];
2762 else if (IsMultiClassed()) {
2763 //classcount is the number of on bits in the MULTI field
2764 classcount = bitcount (multiclass);
2765 for (int i=1; i<classcount; i++)
2766 average += levels[i];
2767 } //else single classed
2768 average = average / classcount + 0.5;
2770 return ieDword(average);
2773 int Actor::GetWildMod(int level) const
2775 if(GetStat(IE_KIT)&0x8000) {
2776 if (level>=MAX_LEVEL) level=MAX_LEVEL;
2777 if(level<1) level=1;
2778 return wmlevels[core->Roll(1,20,-1)][level-1];
2780 return 0;
2783 int Actor::CastingLevelBonus(int level, int type) const
2785 int bonus = 0;
2786 switch(type)
2788 case IE_SPL_PRIEST:
2789 bonus = GetStat(IE_CASTINGLEVELBONUSCLERIC);
2790 break;
2791 case IE_SPL_WIZARD:
2792 bonus = GetWildMod(level) + GetStat(IE_CASTINGLEVELBONUSMAGE);
2795 if (!bonus) {
2796 return 0;
2799 core->GetTokenDictionary()->SetAtCopy("LEVELDIF", bonus);
2801 if (bonus > 0) {
2802 core->DisplayConstantStringName(STR_CASTER_LVL_INC, 0xffffff, this);
2803 } else {
2804 core->DisplayConstantStringName(STR_CASTER_LVL_DEC, 0xffffff, this);
2807 return bonus;
2810 /** maybe this would be more useful if we calculate with the strength too
2812 int Actor::GetEncumbrance()
2814 return inventory.GetWeight();
2817 EffectRef control_undead_ref = { "ControlUndead", NULL, -1};
2819 //receive turning
2820 void Actor::Turn(Scriptable *cleric, ieDword turnlevel)
2822 //this is safely hardcoded i guess
2823 if (Modified[IE_GENERAL]!=GEN_UNDEAD) {
2824 return;
2827 if (this == cleric) {//HACK: shouldn't be needed
2828 return;
2831 //determine if we see the cleric (distance)
2832 if (!CanSee(cleric, this, true, GA_NO_DEAD)) {
2833 return;
2836 //determine alignment (if equals, then no turning)
2838 //determine panic or destruction/control
2839 //we get the modified level
2840 if (turnlevel>GetXPLevel(true)) {
2841 if (cleric->Type == ST_ACTOR && ((Actor*)cleric)->MatchesAlignmentMask(AL_EVIL)) {
2842 Effect *fx = fxqueue.CreateEffect(control_undead_ref, GEN_UNDEAD, 3, FX_DURATION_INSTANT_LIMITED);
2843 if (!fx) { // HACK: currently only works in how and iwd2
2844 printMessage("Actor","Invalid control undead effect!\n",RED);
2845 delete fx;
2846 return;
2848 fx->Duration = ROUND_SECONDS;
2849 fx->Target = FX_TARGET_PRESET;
2850 core->ApplyEffect(fx, this, cleric);
2851 delete fx;
2852 } else {
2853 Die(cleric);
2855 } else {
2856 Panic();
2860 void Actor::Resurrect()
2862 if (!(Modified[IE_STATE_ID ] & STATE_DEAD)) {
2863 return;
2865 InternalFlags&=IF_FROMGAME; //keep these flags (what about IF_INITIALIZED)
2866 InternalFlags|=IF_ACTIVE|IF_VISIBLE; //set these flags
2867 SetBase(IE_STATE_ID, 0);
2868 SetBase(IE_MORALE, 10);
2869 SetBase(IE_HITPOINTS, BaseStats[IE_MAXHITPOINTS]);
2870 ClearActions();
2871 ClearPath();
2872 SetStance(IE_ANI_EMERGE);
2873 //readjust death variable on resurrection
2874 if (core->HasFeature(GF_HAS_KAPUTZ) && (AppearanceFlags&APP_DEATHVAR)) {
2875 ieVariable DeathVar;
2877 snprintf(DeathVar,sizeof(ieVariable),"%s_DEAD",scriptName);
2878 Game *game=core->GetGame();
2879 ieDword value=0;
2881 game->kaputz->Lookup(DeathVar, value);
2882 if (value) {
2883 game->kaputz->SetAt(DeathVar, value-1);
2886 //clear effects?
2889 void Actor::Die(Scriptable *killer)
2891 int i,j;
2893 if (InternalFlags&IF_REALLYDIED) {
2894 return; //can die only once
2897 int minhp = (signed) Modified[IE_MINHITPOINTS];
2898 if (minhp > 0) { //can't die
2899 SetBase(IE_HITPOINTS, minhp);
2900 return;
2902 //Can't simply set Selected to false, game has its own little list
2903 Game *game = core->GetGame();
2904 game->SelectActor(this, false, SELECT_NORMAL);
2905 game->OutAttack(GetID());
2907 ClearActions();
2908 ClearPath();
2909 SetModal( MS_NONE );
2910 core->DisplayConstantStringName(STR_DEATH, 0xffffff, this);
2911 DisplayStringCore(this, VB_DIE, DS_CONSOLE|DS_CONST );
2913 // clearing the search map here means it's not blocked during death animations
2914 // this is perhaps not ideal, but matches other searchmap code which uses
2915 // GA_NO_DEAD to exclude IF_JUSTDIED actors as well as dead ones
2916 if (area)
2917 area->ClearSearchMapFor(this);
2919 //JUSTDIED will be removed when the Die() trigger executed
2920 //otherwise it is the same as REALLYDIED
2921 InternalFlags|=IF_REALLYDIED|IF_JUSTDIED;
2922 SetStance( IE_ANI_DIE );
2924 if (InParty) {
2925 game->PartyMemberDied(this);
2926 core->Autopause(AP_DEAD);
2927 } else {
2928 Actor *act=NULL;
2929 if (!killer) {
2930 killer = area->GetActorByGlobalID(LastHitter);
2933 if (killer) {
2934 if (killer->Type==ST_ACTOR) {
2935 act = (Actor *) killer;
2939 if (act) {
2940 if (act->InParty) {
2941 //adjust kill statistics here
2942 PCStatsStruct *stat = act->PCStats;
2943 if (stat) {
2944 stat->NotifyKill(Modified[IE_XPVALUE], ShortStrRef);
2946 InternalFlags|=IF_GIVEXP;
2949 // friendly party summons' kills also grant xp
2950 if (act->Modified[IE_SEX] == SEX_SUMMON && act->Modified[IE_EA] == EA_CONTROLLED) {
2951 InternalFlags|=IF_GIVEXP;
2956 // XP seems to be handed at out at the moment of death
2957 if (InternalFlags&IF_GIVEXP) {
2958 //give experience to party
2959 game->ShareXP(Modified[IE_XPVALUE], sharexp );
2961 if (!InParty) {
2962 // adjust reputation if the corpse was:
2963 // an innocent, a member of the Flaming Fist or something evil
2964 int reputation = game->Reputation / 10;
2965 int repmod = 0;
2966 if (Modified[IE_CLASS] == CLASS_INNOCENT) {
2967 repmod = reputationmod[reputation-1][0];
2968 } else if (Modified[IE_CLASS] == CLASS_FLAMINGFIST) {
2969 repmod = reputationmod[reputation-1][3];
2971 if (MatchesAlignmentMask(AL_EVIL)) {
2972 repmod += reputationmod[reputation-1][7];
2974 if (repmod) {
2975 game->SetReputation(reputation*10 + repmod);
2980 ieDword value = 0;
2981 ieVariable varname;
2983 // death variables are updated at the moment of death
2984 if (KillVar[0]) {
2985 if (core->HasFeature(GF_HAS_KAPUTZ) ) {
2986 game->kaputz->Lookup(KillVar, value);
2987 game->kaputz->SetAt(KillVar, value+1);
2988 } else {
2989 // iwd/iwd2 path *sets* this var, so i changed it, not sure about pst above
2990 game->locals->SetAt(KillVar, 1);
2993 if (IncKillVar[0]) {
2994 value = 0;
2995 game->locals->Lookup(IncKillVar, value);
2996 game->locals->SetAt(IncKillVar, value + 1);
2999 if (scriptName[0]) {
3000 value = 0;
3001 if (core->HasFeature(GF_HAS_KAPUTZ) ) {
3002 if (AppearanceFlags&APP_DEATHVAR) {
3003 snprintf(varname, 32, "%s_DEAD", scriptName);
3004 game->kaputz->Lookup(varname, value);
3005 game->kaputz->SetAt(varname, value+1);
3007 if (AppearanceFlags&APP_DEATHTYPE) {
3008 snprintf(varname, 32, "KILL_%s", KillVar);
3009 game->kaputz->Lookup(varname, value);
3010 game->kaputz->SetAt(varname, value+1);
3012 } else {
3013 snprintf(varname, 32, core->GetDeathVarFormat(), scriptName);
3014 game->locals->Lookup(varname, value);
3015 game->locals->SetAt(varname, value+1);
3018 if (SetDeathVar) {
3019 value = 0;
3020 snprintf(varname, 32, "%s_DEAD", scriptName);
3021 game->locals->Lookup(varname, value);
3022 game->locals->SetAt(varname, 1);
3023 if (value) {
3024 snprintf(varname, 32, "%s_KILL_CNT", scriptName);
3025 value = 1;
3026 game->locals->Lookup(varname, value);
3027 game->locals->SetAt(varname, value + 1);
3032 if (IncKillCount) {
3033 // racial dead count
3034 value = 0;
3035 int racetable = core->LoadSymbol("race");
3036 if (racetable != -1) {
3037 Holder<SymbolMgr> race = core->GetSymbol(racetable);
3038 const char *raceName = race->GetValue(Modified[IE_RACE]);
3039 if (raceName) {
3040 // todo: should probably not set this for humans in iwd?
3041 snprintf(varname, 32, "KILL_%s_CNT", raceName);
3042 game->locals->Lookup(varname, value);
3043 game->locals->SetAt(varname, value+1);
3048 //death counters for PST
3049 j=APP_GOOD;
3050 for(i=0;i<4;i++) {
3051 if (AppearanceFlags&j) {
3052 ieDword value = 0;
3053 game->locals->Lookup(CounterNames[i], value);
3054 game->locals->SetAt(CounterNames[i], value+DeathCounters[i]);
3056 j+=j;
3059 // EXTRACOUNT is updated at the moment of death
3060 if (Modified[IE_SEX] == SEX_EXTRA || (Modified[IE_SEX] >= SEX_EXTRA2 && Modified[IE_SEX] <= SEX_MAXEXTRA)) {
3061 // if gender is set to one of the EXTRA values, then at death, we have to decrease
3062 // the relevant EXTRACOUNT area variable. scripts use this to check how many actors
3063 // of this extra id are still alive (for example, see the ToB challenge scripts)
3064 ieVariable varname;
3065 if (Modified[IE_SEX] == SEX_EXTRA) {
3066 snprintf(varname, 32, "EXTRACOUNT");
3067 } else {
3068 snprintf(varname, 32, "EXTRACOUNT%d", 2 + (Modified[IE_SEX] - SEX_EXTRA2));
3071 Map *area = GetCurrentArea();
3072 if (area) {
3073 ieDword value = 0;
3074 area->locals->Lookup(varname, value);
3075 // i am guessing that we shouldn't decrease below 0
3076 if (value > 0) {
3077 area->locals->SetAt(varname, value-1);
3082 //a plot critical creature has died (iwd2)
3083 if (BaseStats[IE_MC_FLAGS]&MC_PLOT_CRITICAL) {
3084 core->GetGUIScriptEngine()->RunFunction("DeathWindowPlot", false);
3086 //ensure that the scripts of the actor will run as soon as possible
3087 ImmediateEvent();
3090 void Actor::SetPersistent(int partyslot)
3092 InParty = (ieByte) partyslot;
3093 InternalFlags|=IF_FROMGAME;
3094 //if an actor is coming from a game, it should have these too
3095 CreateStats();
3098 void Actor::DestroySelf()
3100 InternalFlags|=IF_CLEANUP;
3101 // clear search map so that a new actor can immediately go there
3102 // (via ChangeAnimationCore)
3103 if (area)
3104 area->ClearSearchMapFor(this);
3107 bool Actor::CheckOnDeath()
3109 if (InternalFlags&IF_CLEANUP) {
3110 return true;
3112 if (InternalFlags&IF_JUSTDIED) {
3113 if (lastRunTime == 0 || CurrentAction || GetNextAction()) {
3114 return false; //actor is currently dying, let him die first
3117 if (!(InternalFlags&IF_REALLYDIED) ) {
3118 return false;
3120 //don't mess with the already deceased
3121 if (BaseStats[IE_STATE_ID]&STATE_DEAD) {
3122 return false;
3124 // don't destroy actors currently in a dialog
3125 GameControl *gc = core->GetGameControl();
3126 if (gc && (globalID == gc->targetID || globalID == gc->speakerID)) {
3127 return false;
3130 //we need to check animID here, if it has not played the death
3131 //sequence yet, then we could return now
3132 ClearActions();
3133 //missed the opportunity of Died()
3134 InternalFlags&=~IF_JUSTDIED;
3136 // items seem to be dropped at the moment of death
3137 // .. but this can't go in Die() because that is called
3138 // from effects and dropping items might change effects!
3139 DropItem("",0);
3141 //remove all effects that are not 'permanent after death' here
3142 //permanent after death type is 9
3143 SetBaseBit(IE_STATE_ID, STATE_DEAD, true);
3145 // party actors are never removed
3146 if (InParty) return false;
3148 if (Modified[IE_MC_FLAGS]&MC_REMOVE_CORPSE) return true;
3149 if (Modified[IE_MC_FLAGS]&MC_KEEP_CORPSE) return false;
3150 //if chunked death, then return true
3151 if (LastDamageType&DAMAGE_CHUNKING) {
3152 //play chunky animation
3153 //chunks are projectiles
3154 return true;
3156 return false;
3159 /* this will create a heap at location, and transfer the item(s) */
3160 void Actor::DropItem(const ieResRef resref, unsigned int flags)
3162 if (inventory.DropItemAtLocation( resref, flags, area, Pos )) {
3163 ReinitQuickSlots();
3167 void Actor::DropItem(int slot , unsigned int flags)
3169 if (inventory.DropItemAtLocation( slot, flags, area, Pos )) {
3170 ReinitQuickSlots();
3174 /** returns quick item data */
3175 /** if header==-1 which is a 'use quickitem' action */
3176 /** if header is set, then which is the absolute slot index, */
3177 /** and header is the header index */
3178 void Actor::GetItemSlotInfo(ItemExtHeader *item, int which, int header)
3180 ieWord idx;
3181 ieWord headerindex;
3183 memset(item, 0, sizeof(ItemExtHeader) );
3184 if (header<0) {
3185 if (!PCStats) return; //not a player character
3186 PCStats->GetSlotAndIndex(which,idx,headerindex);
3187 if (headerindex==0xffff) return; //headerindex is invalid
3188 } else {
3189 idx=(ieWord) which;
3190 headerindex=(ieWord) header;
3192 const CREItem *slot = inventory.GetSlotItem(idx);
3193 if (!slot) return; //quick item slot is empty
3194 Item *itm = gamedata->GetItem(slot->ItemResRef);
3195 if (!itm) return; //quick item slot contains invalid item resref
3196 ITMExtHeader *ext_header = itm->GetExtHeader(headerindex);
3197 //item has no extended header, or header index is wrong
3198 if (!ext_header) return;
3199 memcpy(item->itemname, slot->ItemResRef, sizeof(ieResRef) );
3200 item->slot = idx;
3201 item->headerindex = headerindex;
3202 memcpy(&(item->AttackType), &(ext_header->AttackType),
3203 ((char *) &(item->itemname)) -((char *) &(item->AttackType)) );
3204 if (headerindex>=CHARGE_COUNTERS) {
3205 item->Charges=0;
3206 } else {
3207 item->Charges=slot->Usages[headerindex];
3209 gamedata->FreeItem(itm,slot->ItemResRef, false);
3212 void Actor::ReinitQuickSlots()
3214 if (!PCStats) {
3215 return;
3218 // Note: (wjp, 20061226)
3219 // This function needs some rethinking.
3220 // It tries to satisfy two things at the moment:
3221 // Fill quickslots when they are empty and an item is placed in the
3222 // inventory slot corresponding to the quickslot
3223 // Reset quickslots when an item is removed
3224 // Currently, it resets all slots when items are removed,
3225 // but it only refills the ACT_QSLOTn slots, not the ACT_WEAPONx slots.
3227 // Refilling a weapon slot is possible, but essentially duplicates a lot
3228 // of code from Inventory::EquipItem() which performs the same steps for
3229 // the Inventory::Equipped slot.
3230 // Hopefully, weapons/arrows are never added to inventory slots without
3231 // EquipItem being called.
3233 int i=sizeof(PCStats->QSlots);
3234 while (i--) {
3235 int slot;
3236 int which;
3237 if (i<0) which = ACT_WEAPON4+i+1;
3238 else which = PCStats->QSlots[i];
3239 switch (which) {
3240 case ACT_WEAPON1:
3241 case ACT_WEAPON2:
3242 case ACT_WEAPON3:
3243 case ACT_WEAPON4:
3244 CheckWeaponQuickSlot(which);
3245 slot = 0;
3246 break;
3247 //WARNING:this cannot be condensed, because the symbols don't come in order!!!
3248 case ACT_QSLOT1: slot = inventory.GetQuickSlot(); break;
3249 case ACT_QSLOT2: slot = inventory.GetQuickSlot()+1; break;
3250 case ACT_QSLOT3: slot = inventory.GetQuickSlot()+2; break;
3251 case ACT_QSLOT4: slot = inventory.GetQuickSlot()+3; break;
3252 case ACT_QSLOT5: slot = inventory.GetQuickSlot()+4; break;
3253 default:
3254 slot = 0;
3256 if (!slot) continue;
3257 //if magic items are equipped the equipping info doesn't change
3258 //(afaik)
3260 // Note: we're now in the QSLOTn case
3261 // If slot is empty, reset quickslot to 0xffff/0xffff
3263 if (!inventory.HasItemInSlot("", slot)) {
3264 SetupQuickSlot(which, 0xffff, 0xffff);
3265 } else {
3266 ieWord idx;
3267 ieWord headerindex;
3268 PCStats->GetSlotAndIndex(which,idx,headerindex);
3269 if (idx != slot || headerindex == 0xffff) {
3270 // If slot just became filled, set it to filled
3271 SetupQuickSlot(which,slot,0);
3276 //these are always present
3277 CheckWeaponQuickSlot(0);
3278 CheckWeaponQuickSlot(1);
3279 //disabling quick weapon slots for certain classes
3280 for(i=0;i<2;i++) {
3281 int which = ACT_WEAPON3+i;
3282 // Assuming that ACT_WEAPON3 and 4 are always in the first two spots
3283 if (PCStats->QSlots[i]!=which) {
3284 SetupQuickSlot(which, 0xffff, 0xffff);
3289 void Actor::CheckWeaponQuickSlot(unsigned int which)
3291 if (!PCStats) return;
3293 bool empty = false;
3294 // If current quickweaponslot doesn't contain an item, reset it to fist
3295 int slot = PCStats->QuickWeaponSlots[which];
3296 int header = PCStats->QuickWeaponHeaders[which];
3297 if (!inventory.HasItemInSlot("", slot) || header == 0xffff) {
3298 //a quiver just went dry, falling back to fist
3299 empty = true;
3300 } else {
3301 // If current quickweaponslot contains ammo, and bow not found, reset
3303 if (core->QuerySlotEffects(slot) == SLOT_EFFECT_MISSILE) {
3304 const CREItem *slotitm = inventory.GetSlotItem(slot);
3305 assert(slotitm);
3306 Item *itm = gamedata->GetItem(slotitm->ItemResRef);
3307 assert(itm);
3308 ITMExtHeader *ext_header = itm->GetExtHeader(header);
3309 if (ext_header) {
3310 int type = ext_header->ProjectileQualifier;
3311 int weaponslot = inventory.FindTypedRangedWeapon(type);
3312 if (weaponslot == inventory.GetFistSlot()) {
3313 empty = true;
3315 } else {
3316 empty = true;
3318 gamedata->FreeItem(itm,slotitm->ItemResRef, false);
3322 if (empty)
3323 SetupQuickSlot(ACT_WEAPON1+which, inventory.GetFistSlot(), 0);
3327 void Actor::SetupQuickSlot(unsigned int which, int slot, int headerindex)
3329 if (!PCStats) return;
3330 PCStats->InitQuickSlot(which, slot, headerindex);
3331 //something changed about the quick items
3332 core->SetEventFlag(EF_ACTION);
3335 bool Actor::ValidTarget(int ga_flags) const
3337 if (Immobile()) return false;
3338 //scripts can still see this type of actor
3340 if (ga_flags&GA_NO_HIDDEN) {
3341 if (Modified[IE_AVATARREMOVAL]) return false;
3342 if (Modified[IE_EA]>EA_GOODCUTOFF && Modified[IE_STATE_ID]&STATE_INVISIBLE) return false;
3345 if (ga_flags&GA_NO_ALLY) {
3346 if(InParty) return false;
3347 if(Modified[IE_EA]<=EA_GOODCUTOFF) return false;
3350 if (ga_flags&GA_NO_ENEMY) {
3351 if(!InParty && (Modified[IE_EA]>=EA_EVILCUTOFF) ) return false;
3354 if (ga_flags&GA_NO_NEUTRAL) {
3355 if((Modified[IE_EA]>EA_GOODCUTOFF) && (Modified[IE_EA]<EA_EVILCUTOFF) ) return false;
3358 switch(ga_flags&GA_ACTION) {
3359 case GA_PICK:
3360 if (Modified[IE_STATE_ID] & STATE_CANTSTEAL) return false;
3361 break;
3362 case GA_TALK:
3363 //can't talk to dead
3364 if (Modified[IE_STATE_ID] & STATE_CANTLISTEN) return false;
3365 //can't talk to hostile
3366 if (Modified[IE_EA]>=EA_EVILCUTOFF) return false;
3367 break;
3369 if (ga_flags&GA_NO_DEAD) {
3370 if (InternalFlags&IF_JUSTDIED) return false;
3371 if (Modified[IE_STATE_ID] & STATE_DEAD) return false;
3373 if (ga_flags&GA_SELECT) {
3374 if (UnselectableTimer) return false;
3376 return true;
3379 //returns true if it won't be destroyed with an area
3380 //in this case it shouldn't be saved with the area either
3381 //it will be saved in the savegame
3382 bool Actor::Persistent() const
3384 if (InParty) return true;
3385 if (InternalFlags&IF_FROMGAME) return true;
3386 return false;
3389 //this is a reimplementation of cheatkey a/s of bg2
3390 //cycling through animation/stance
3391 // a - get next animation, s - get next stance
3393 void Actor::GetNextAnimation()
3395 int RowNum = anims->AvatarsRowNum - 1;
3396 if (RowNum<0)
3397 RowNum = CharAnimations::GetAvatarsCount() - 1;
3398 int NewAnimID = CharAnimations::GetAvatarStruct(RowNum)->AnimID;
3399 printf ("AnimID: %04X\n", NewAnimID);
3400 SetBase( IE_ANIMATION_ID, NewAnimID);
3401 //SetAnimationID ( NewAnimID );
3404 void Actor::GetPrevAnimation()
3406 int RowNum = anims->AvatarsRowNum + 1;
3407 if (RowNum>=CharAnimations::GetAvatarsCount() )
3408 RowNum = 0;
3409 int NewAnimID = CharAnimations::GetAvatarStruct(RowNum)->AnimID;
3410 printf ("AnimID: %04X\n", NewAnimID);
3411 SetBase( IE_ANIMATION_ID, NewAnimID);
3412 //SetAnimationID ( NewAnimID );
3415 //slot is the projectile slot
3416 //This will return the projectile item.
3417 ITMExtHeader *Actor::GetRangedWeapon(WeaponInfo &wi) const
3419 //EquippedSlot is the projectile. To get the weapon, use inventory.GetUsedWeapon()
3420 wi.slot = inventory.GetEquippedSlot();
3421 const CREItem *wield = inventory.GetSlotItem(wi.slot);
3422 if (!wield) {
3423 return NULL;
3425 Item *item = gamedata->GetItem(wield->ItemResRef);
3426 if (!item) {
3427 return NULL;
3429 //The magic of the bow and the arrow add up?
3430 wi.enchantment += item->Enchantment;
3431 wi.itemflags = wield->Flags;
3432 //wi.range is not set, the projectile has no effect on range?
3434 ITMExtHeader *which = item->GetWeaponHeader(true);
3435 gamedata->FreeItem(item, wield->ItemResRef, false);
3436 return which;
3439 int Actor::IsDualWielding() const
3441 int slot;
3442 //if the shield slot is a weapon, we're dual wielding
3443 const CREItem *wield = inventory.GetUsedWeapon(true, slot);
3444 if (!wield) {
3445 return 0;
3448 Item *itm = gamedata->GetItem( wield->ItemResRef );
3449 if (!itm) {
3450 return 0;
3453 //if the item is usable in weapon slot, then it is weapon
3454 int weapon = core->CanUseItemType( SLOT_WEAPON, itm );
3455 gamedata->FreeItem( itm, wield->ItemResRef, false );
3456 //is just weapon>0 ok?
3457 return (weapon>0)?1:0;
3460 //returns weapon header currently used (bow in case of bow+arrow)
3461 //if range is nonzero, then the returned header is valid
3462 ITMExtHeader *Actor::GetWeapon(WeaponInfo &wi, bool leftorright)
3464 //only use the shield slot if we are dual wielding
3465 leftorright = leftorright && IsDualWielding();
3467 const CREItem *wield = inventory.GetUsedWeapon(leftorright, wi.slot);
3468 if (!wield) {
3469 return 0;
3471 Item *item = gamedata->GetItem(wield->ItemResRef);
3472 if (!item) {
3473 return 0;
3476 wi.enchantment = item->Enchantment;
3477 wi.itemflags = wield->Flags;
3478 wi.prof = item->WeaProf;
3480 //select first weapon header
3481 ITMExtHeader *which;
3482 if (GetAttackStyle() == WEAPON_RANGED) {
3483 which = item->GetWeaponHeader(true);
3484 wi.backstabbing = false;
3485 } else {
3486 which = item->GetWeaponHeader(false);
3487 // any melee weapon usable by a single class thief is game (UAI does not affect this)
3488 wi.backstabbing = !(item->UsabilityBitmask & 0x400000);
3491 //make sure we use 'false' in this freeitem
3492 //so 'which' won't point into invalid memory
3493 gamedata->FreeItem(item, wield->ItemResRef, false);
3494 if (!which) {
3495 return 0;
3497 if (which->Location!=ITEM_LOC_WEAPON) {
3498 return 0;
3500 wi.range = which->Range+1;
3501 return which;
3502 //return which->Range+1;
3505 void Actor::GetNextStance()
3507 static int Stance = IE_ANI_AWAKE;
3509 if (--Stance < 0) Stance = MAX_ANIMS-1;
3510 printf ("StanceID: %d\n", Stance);
3511 SetStance( Stance );
3514 int Actor::LearnSpell(const ieResRef spellname, ieDword flags)
3516 if (spellbook.HaveSpell(spellname, 0) ) {
3517 return LSR_KNOWN;
3519 Spell *spell = gamedata->GetSpell(spellname);
3520 if (!spell) {
3521 return LSR_INVALID; //not existent spell
3524 if (flags & LS_STATS) {
3525 // chance to learn roll
3526 if (LuckyRoll(1,100,0) > core->GetIntelligenceBonus(0, GetStat(IE_INT))) {
3527 return LSR_FAILED;
3531 int explev = spellbook.LearnSpell(spell, flags&LS_MEMO);
3532 int tmp = spell->SpellName;
3533 if (flags&LS_LEARN) {
3534 core->GetTokenDictionary()->SetAt("SPECIALABILITYNAME", core->GetString(tmp));
3535 switch (spell->SpellType) {
3536 case IE_SPL_INNATE:
3537 tmp = STR_GOTABILITY;
3538 break;
3539 case IE_SPL_SONG:
3540 tmp = STR_GOTSONG;
3541 break;
3542 default:
3543 tmp = STR_GOTSPELL;
3544 break;
3546 } else tmp = 0;
3547 gamedata->FreeSpell(spell, spellname, false);
3548 if (!explev) {
3549 return LSR_INVALID;
3551 if (tmp) {
3552 core->DisplayConstantStringName(tmp, 0xbcefbc, this);
3554 if (flags&LS_ADDXP) {
3555 AddExperience(XP_LEARNSPELL, explev);
3557 return LSR_OK;
3560 const char *Actor::GetDialog(int flags) const
3562 if (!flags) {
3563 return Dialog;
3565 if (Modified[IE_EA]>=EA_EVILCUTOFF) {
3566 return NULL;
3569 if ( (InternalFlags & IF_NOINT) && CurrentAction) {
3570 if (flags>1) {
3571 core->DisplayConstantString(STR_TARGETBUSY,0xff0000);
3573 return NULL;
3575 return Dialog;
3578 void Actor::CreateStats()
3580 if (!PCStats) {
3581 PCStats = new PCStatsStruct();
3585 const char* Actor::GetScript(int ScriptIndex) const
3587 return Scripts[ScriptIndex]->GetName();
3590 void Actor::SetModal(ieDword newstate, bool force)
3592 switch(newstate) {
3593 case MS_NONE:
3594 break;
3595 case MS_BATTLESONG:
3596 break;
3597 case MS_DETECTTRAPS:
3598 break;
3599 case MS_STEALTH:
3600 break;
3601 case MS_TURNUNDEAD:
3602 break;
3603 default:
3604 return;
3607 if (InParty) {
3608 // display the turning-off message
3609 if (ModalState != MS_NONE) {
3610 core->DisplayStringName(core->ModalStates[ModalState].leaving_str, 0xffffff, this, IE_STR_SOUND|IE_STR_SPEECH);
3613 // when called with the same state twice, toggle to MS_NONE
3614 if (!force && ModalState == newstate) {
3615 ModalState = MS_NONE;
3616 } else {
3617 ModalState = newstate;
3620 //update the action bar
3621 core->SetEventFlag(EF_ACTION);
3622 } else {
3623 ModalState = newstate;
3627 //this is just a stub function for now, attackstyle could be melee/ranged
3628 //even spells got this attack style
3629 int Actor::GetAttackStyle()
3631 WeaponInfo wi;
3632 //Non NULL if the equipped slot is a projectile or a throwing weapon
3633 //TODO some weapons have both melee and ranged capability
3634 if (GetRangedWeapon(wi) != NULL) return WEAPON_RANGED;
3635 return WEAPON_MELEE;
3638 void Actor::SetTarget( Scriptable *target)
3640 if (target->Type==ST_ACTOR) {
3641 Actor *tar = (Actor *) target;
3642 LastTarget = tar->GetID();
3643 tar->LastAttacker = GetID();
3644 //we tell the game object that this creature
3645 //must be added to the list of combatants
3646 core->GetGame()->InAttack(tar->LastAttacker);
3648 SetOrientation( GetOrient( target->Pos, Pos ), false );
3651 //in case of LastTarget = 0
3652 void Actor::StopAttack()
3654 SetStance(IE_ANI_READY);
3655 core->GetGame()->OutAttack(GetID());
3656 InternalFlags|=IF_TARGETGONE; //this is for the trigger!
3657 if (InParty) {
3658 core->Autopause(AP_NOTARGET);
3662 int Actor::Immobile() const
3664 if (GetStat(IE_CASTERHOLD)) {
3665 return 1;
3667 if (GetStat(IE_HELD)) {
3668 return 1;
3670 return 0;
3673 //calculate how many attacks will be performed
3674 //in the next round
3675 //only called when Game thinks we are in attack
3676 //so it is safe to do cleanup here (it will be called only once)
3677 void Actor::InitRound(ieDword gameTime, bool secondround)
3679 lastInit = gameTime;
3681 //roundTime will equal 0 if we aren't attacking something
3682 if (roundTime) {
3683 //only perform calculations at the beginning of the round!
3684 if (((gameTime-roundTime)%ROUND_SIZE != 0) || \
3685 (roundTime == lastInit)) {
3686 return;
3690 //reset variables used in PerformAttack
3691 attackcount = 0;
3692 attacksperround = 0;
3693 nextattack = 0;
3694 lastattack = 0;
3696 //we set roundTime to zero on any of the following returns, because this
3697 //is guaranteed to be the start of a round, and we only want roundTime
3698 //if we are attacking this round
3699 if (InternalFlags&IF_STOPATTACK) {
3700 core->GetGame()->OutAttack(GetID());
3701 roundTime = 0;
3702 return;
3705 if (!LastTarget) {
3706 StopAttack();
3707 roundTime = 0;
3708 return;
3711 //if held or disabled, etc, then cannot continue attacking
3712 ieDword state = GetStat(IE_STATE_ID);
3713 if (state&STATE_CANTMOVE) {
3714 roundTime = 0;
3715 return;
3717 if (Immobile()) {
3718 roundTime = 0;
3719 return;
3722 //add one for second round to get an extra attack only if we
3723 //are x/2 attacks per round
3724 attackcount = GetStat(IE_NUMBEROFATTACKS);
3725 if (secondround) {
3726 attackcount++;
3728 //all numbers of attacks are stored at twice their value
3729 attackcount >>= 1;
3731 //make sure we always get at least 1apr
3732 if (attackcount < 1) attackcount = 1;
3734 //set our apr and starting round time
3735 attacksperround = attackcount;
3736 roundTime = gameTime;
3738 //print a little message :)
3739 printMessage("InitRound", " ", WHITE);
3740 printf("Name: %s | Attacks: %d | Start: %d\n", ShortName, attacksperround, gameTime);
3742 // this might not be the right place, but let's give it a go
3743 if (attacksperround && InParty) {
3744 core->Autopause(AP_ENDROUND);
3748 bool Actor::GetCombatDetails(int &tohit, bool leftorright, WeaponInfo& wi, ITMExtHeader *&header, ITMExtHeader *&hittingheader, \
3749 ieDword &Flags, int &DamageBonus, int &speed, int &CriticalBonus, int &style)
3751 tohit = GetStat(IE_TOHIT);
3752 speed = -GetStat(IE_PHYSICALSPEED);
3753 bool dualwielding = IsDualWielding();
3754 header = GetWeapon(wi, leftorright && dualwielding);
3755 if (!header) {
3756 return false;
3758 style = 0;
3759 CriticalBonus = 0;
3760 hittingheader = header;
3761 ITMExtHeader *rangedheader = NULL;
3762 int THAC0Bonus = hittingheader->THAC0Bonus;
3763 DamageBonus = hittingheader->DamageBonus;
3764 switch(hittingheader->AttackType) {
3765 case ITEM_AT_MELEE:
3766 Flags = WEAPON_MELEE;
3767 break;
3768 case ITEM_AT_PROJECTILE: //throwing weapon
3769 Flags = WEAPON_RANGED;
3770 break;
3771 case ITEM_AT_BOW:
3772 rangedheader = GetRangedWeapon(wi);
3773 if (!rangedheader) {
3774 //display out of ammo verbal constant if there is any???
3775 //DisplayStringCore(this, VB_OUTOFAMMO, DS_CONSOLE|DS_CONST );
3776 SetStance(IE_ANI_READY);
3777 //set some trigger?
3778 return false;
3780 Flags = WEAPON_RANGED;
3781 //The bow can give some bonuses, but the core attack is made by the arrow.
3782 hittingheader = rangedheader;
3783 THAC0Bonus += rangedheader->THAC0Bonus;
3784 DamageBonus += rangedheader->DamageBonus;
3785 break;
3786 default:
3787 //item is unsuitable for fight
3788 SetStance(IE_ANI_READY);
3789 return false;
3790 }//melee or ranged
3791 //this flag is set by the bow in case of projectile launcher.
3792 if (header->RechargeFlags&IE_ITEM_USESTRENGTH) Flags|=WEAPON_USESTRENGTH;
3794 // get our dual wielding modifier
3795 if (dualwielding) {
3796 if (leftorright) {
3797 DamageBonus += GetStat(IE_DAMAGEBONUSLEFT);
3798 } else {
3799 DamageBonus += GetStat(IE_DAMAGEBONUSRIGHT);
3802 leftorright = leftorright && dualwielding;
3803 if (leftorright) Flags|=WEAPON_LEFTHAND;
3805 //add in proficiency bonuses
3806 ieDword stars;
3807 if (wi.prof && (wi.prof <= MAX_STATS)) {
3808 stars = GetStat(wi.prof)&PROFS_MASK;
3810 //hit/damage/speed bonuses from wspecial
3811 if ((signed)stars > wspecial_max) {
3812 stars = wspecial_max;
3814 THAC0Bonus += wspecial[stars][0];
3815 DamageBonus += wspecial[stars][1];
3816 speed += wspecial[stars][2];
3817 // add non-proficiency penalty, which is missing from the table
3818 if (stars == 0) THAC0Bonus -= 4;
3821 if (IsDualWielding() && wsdualwield) {
3822 //add dual wielding penalty
3823 stars = GetStat(IE_PROFICIENCY2WEAPON)&PROFS_MASK;
3824 if (stars > STYLE_MAX) stars = STYLE_MAX;
3826 style = 1000*stars + IE_PROFICIENCY2WEAPON;
3827 THAC0Bonus += wsdualwield[stars][leftorright?1:0];
3828 } else if (wi.itemflags&(IE_INV_ITEM_TWOHANDED) && (Flags&WEAPON_MELEE) && wstwohanded) {
3829 //add two handed profs bonus
3830 stars = GetStat(IE_PROFICIENCY2HANDED)&PROFS_MASK;
3831 if (stars > STYLE_MAX) stars = STYLE_MAX;
3833 style = 1000*stars + IE_PROFICIENCY2HANDED;
3834 DamageBonus += wstwohanded[stars][0];
3835 CriticalBonus = wstwohanded[stars][1];
3836 speed += wstwohanded[stars][2];
3837 } else if (Flags&WEAPON_MELEE) {
3838 int slot;
3839 CREItem *weapon = inventory.GetUsedWeapon(true, slot);
3840 if(wssingle && weapon == NULL) {
3841 //NULL return from GetUsedWeapon means no shield slot
3842 stars = GetStat(IE_PROFICIENCYSINGLEWEAPON)&PROFS_MASK;
3843 if (stars > STYLE_MAX) stars = STYLE_MAX;
3845 style = 1000*stars + IE_PROFICIENCYSINGLEWEAPON;
3846 CriticalBonus = wssingle[stars][1];
3847 } else if (wsswordshield && weapon) {
3848 stars = GetStat(IE_PROFICIENCYSWORDANDSHIELD)&PROFS_MASK;
3849 if (stars > STYLE_MAX) stars = STYLE_MAX;
3851 style = 1000*stars + IE_PROFICIENCYSWORDANDSHIELD;
3852 } else {
3853 // no bonus
3855 } else {
3856 // ranged - no bonus
3859 // TODO: Elves get a racial THAC0 bonus with all swords and bows in BG2 (but not daggers)
3861 //second parameter is left or right hand flag
3862 tohit = GetToHit(THAC0Bonus, Flags);
3863 return true;
3866 int Actor::GetToHit(int bonus, ieDword Flags)
3868 int tohit = bonus;
3870 //get our dual wielding modifier
3871 if (IsDualWielding()) {
3872 if (Flags&WEAPON_LEFTHAND) {
3873 tohit += GetStat(IE_HITBONUSLEFT);
3874 } else {
3875 tohit += GetStat(IE_HITBONUSRIGHT);
3879 //get attack style (melee or ranged)
3880 switch(Flags&WEAPON_STYLEMASK) {
3881 case WEAPON_MELEE:
3882 tohit += GetStat(IE_MELEETOHIT);
3883 break;
3884 case WEAPON_FIST:
3885 tohit += GetStat(IE_FISTHIT);
3886 break;
3887 case WEAPON_RANGED:
3888 tohit += GetStat(IE_MISSILEHITBONUS);
3889 //add dexterity bonus
3890 tohit += core->GetDexterityBonus(STAT_DEX_MISSILE, GetStat(IE_DEX));
3891 break;
3894 //add strength bonus if we need
3895 if (Flags&WEAPON_USESTRENGTH) {
3896 tohit += core->GetStrengthBonus(0,GetStat(IE_STR), GetStat(IE_STREXTRA) );
3899 // if the target is using a ranged weapon while we're meleeing, we get a +4 bonus
3900 if ((Flags&WEAPON_STYLEMASK) != WEAPON_RANGED) {
3901 Actor *target = area->GetActorByGlobalID(LastTarget);
3902 if (target && target->GetAttackStyle() == WEAPON_RANGED) {
3903 tohit += 4;
3907 // add +4 attack bonus vs racial enemies
3908 if (GetRangerLevel()) {
3909 Actor *target = area->GetActorByGlobalID(LastTarget);
3910 if (target && IsRacialEnemy(target)) {
3911 tohit += 4;
3915 if (ReverseToHit) {
3916 tohit = (signed)GetStat(IE_TOHIT)-tohit;
3917 } else {
3918 tohit += GetStat(IE_TOHIT);
3920 return tohit;
3923 static const int weapon_damagetype[] = {DAMAGE_CRUSHING, DAMAGE_PIERCING,
3924 DAMAGE_CRUSHING, DAMAGE_SLASHING, DAMAGE_MISSILE, DAMAGE_STUNNING};
3926 int Actor::GetDefense(int DamageType)
3928 //specific damage type bonus.
3929 int defense = 0;
3930 if(DamageType > 5)
3931 DamageType = 0;
3932 switch (weapon_damagetype[DamageType]) {
3933 case DAMAGE_CRUSHING:
3934 defense += GetStat(IE_ACCRUSHINGMOD);
3935 break;
3936 case DAMAGE_PIERCING:
3937 defense += GetStat(IE_ACPIERCINGMOD);
3938 break;
3939 case DAMAGE_SLASHING:
3940 defense += GetStat(IE_ACSLASHINGMOD);
3941 break;
3942 case DAMAGE_MISSILE:
3943 defense += GetStat(IE_ACMISSILEMOD);
3944 break;
3945 //What about stunning ?
3946 default :
3947 break;
3950 //check for s/s and single weapon ac bonuses
3951 if (!IsDualWielding() && wssingle && wsswordshield) {
3952 WeaponInfo wi;
3953 ITMExtHeader* header;
3954 header = GetWeapon(wi, false);
3955 //make sure we're wielding a single melee weapon
3956 if (header && (header->AttackType == ITEM_AT_MELEE)) {
3957 int slot;
3958 ieDword stars;
3959 if (inventory.GetUsedWeapon(true, slot) == NULL) {
3960 //single-weapon style applies to all ac
3961 stars = GetStat(IE_PROFICIENCYSINGLEWEAPON)&PROFS_MASK;
3962 if (stars>STYLE_MAX) stars = STYLE_MAX;
3963 defense += wssingle[stars][0];
3964 } else if (weapon_damagetype[DamageType] == DAMAGE_MISSILE) {
3965 //sword-shield style applies only to missile ac
3966 stars = GetStat(IE_PROFICIENCYSWORDANDSHIELD)&PROFS_MASK;
3967 if (stars>STYLE_MAX) stars = STYLE_MAX;
3968 defense += wsswordshield[stars][0];
3973 if (ReverseToHit) {
3974 defense = GetStat(IE_ARMORCLASS)-defense;
3975 } else {
3976 defense += GetStat(IE_ARMORCLASS);
3978 //Dexterity bonus is stored negative in 2da files.
3979 return defense + core->GetDexterityBonus(STAT_DEX_AC, GetStat(IE_DEX) );
3982 void Actor::PerformAttack(ieDword gameTime)
3984 // start a new round if we really don't have one yet
3985 if (!roundTime) {
3986 InitRound(gameTime, false);
3989 //only return if we don't have any attacks left this round
3990 if (attackcount==0) return;
3992 // this check shouldn't be necessary, but it causes a divide-by-zero below,
3993 // so i would like it to be clear if it ever happens
3994 if (attacksperround==0) {
3995 printMessage("Actor", "APR was 0 in PerformAttack!\n", RED);
3996 return;
3999 //don't continue if we can't make the attack yet
4000 //we check lastattack because we will get the same gameTime a few times
4001 if ((nextattack > gameTime) || (gameTime == lastattack)) {
4002 // fuzzie added the following line as part of the UpdateActorState hack below
4003 lastattack = gameTime;
4004 return;
4007 if (InternalFlags&IF_STOPATTACK) {
4008 // this should be avoided by the AF_ALIVE check by all the calling actions
4009 printMessage("Actor", "Attack by dead actor!\n", LIGHT_RED);
4010 return;
4013 if (!LastTarget) {
4014 printMessage("Actor", "Attack without valid target ID!\n", LIGHT_RED);
4015 return;
4017 //get target
4018 Actor *target = area->GetActorByGlobalID(LastTarget);
4020 if (target && (target->GetStat(IE_STATE_ID)&STATE_DEAD)) {
4021 target = NULL;
4024 if (!target) {
4025 printMessage("Actor", "Attack without valid target!\n", LIGHT_RED);
4026 return;
4029 printf("Performattack for %s, target is: %s\n", ShortName, target->ShortName);
4031 //which hand is used
4032 //we do apr - attacksleft so we always use the main hand first
4033 bool leftorright = (bool) ((attacksperround-attackcount)&1);
4035 WeaponInfo wi;
4036 ITMExtHeader *header = NULL;
4037 ITMExtHeader *hittingheader = NULL;
4038 int tohit;
4039 ieDword Flags;
4040 int DamageBonus, CriticalBonus;
4041 int speed, style;
4043 //will return false on any errors (eg, unusable weapon)
4044 if (!GetCombatDetails(tohit, leftorright, wi, header, hittingheader, Flags, DamageBonus, speed, CriticalBonus, style)) {
4045 return;
4048 //if this is the first call of the round, we need to update next attack
4049 if (nextattack == 0) {
4050 //FIXME: figure out exactly how initiative is calculated
4051 int initiative = core->Roll(1, 5, GetXPLevel(true)/(-8));
4052 int spdfactor = hittingheader->Speed + speed + initiative;
4053 if (spdfactor<0) spdfactor = 0;
4054 if (spdfactor>10) spdfactor = 10;
4056 //(round_size/attacks_per_round)*(initiative) is the first delta
4057 nextattack = ROUND_SIZE*spdfactor/(attacksperround*10) + gameTime;
4059 //we can still attack this round if we have a speed factor of 0
4060 if (nextattack > gameTime) {
4061 return;
4065 if((PersonalDistance(this, target) > wi.range*10) || (GetCurrentArea()!=target->GetCurrentArea() ) ) {
4066 // this is a temporary double-check, remove when bugfixed
4067 printMessage("Actor", "Attack action didn't bring us close enough!", LIGHT_RED);
4068 return;
4071 SetStance(AttackStance);
4073 //figure out the time for our next attack since the old time has the initiative
4074 //in it, we only have to add the basic delta
4075 attackcount--;
4076 nextattack += (ROUND_SIZE/attacksperround);
4077 lastattack = gameTime;
4079 //debug messages
4080 if (leftorright && IsDualWielding()) {
4081 printMessage("Attack","(Off) ", YELLOW);
4082 } else {
4083 printMessage("Attack","(Main) ", GREEN);
4085 if (attacksperround) {
4086 printf("Left: %d | ", attackcount);
4087 printf("Next: %d ", nextattack);
4090 int roll = LuckyRoll(1, ATTACKROLL, 0);
4091 if (roll==1) {
4092 //critical failure
4093 printBracket("Critical Miss", RED);
4094 printf("\n");
4095 core->DisplayConstantStringName(STR_CRITICAL_MISS, 0xffffff, this);
4096 DisplayStringCore(this, VB_CRITMISS, DS_CONSOLE|DS_CONST );
4097 if (Flags&WEAPON_RANGED) {//no need for this with melee weapon!
4098 UseItem(wi.slot, (ieDword)-2, target, UI_MISS);
4099 } else if (core->HasFeature(GF_BREAKABLE_WEAPONS)) {
4100 //break sword
4101 //TODO: this appears to be a random roll on-hit (perhaps critical failure
4102 // too); we use 1% (1d20*1d5==1)
4103 if ((header->RechargeFlags&IE_ITEM_BREAKABLE) && core->Roll(1,5,0) == 1) {
4104 inventory.BreakItemSlot(wi.slot);
4107 ResetState();
4108 return;
4110 //damage type is?
4111 //modify defense with damage type
4112 ieDword damagetype = hittingheader->DamageType;
4113 int damage = 0;
4114 int resisted = 0;
4116 if (hittingheader->DiceThrown<256) {
4117 damage += LuckyRoll(hittingheader->DiceThrown, hittingheader->DiceSides, 0, 1, 0);
4118 damage += DamageBonus;
4119 printf("| Damage %dd%d%+d = %d ",hittingheader->DiceThrown, hittingheader->DiceSides, DamageBonus, damage);
4120 } else {
4121 printf("| No Damage");
4122 damage = 0;
4125 if (roll >= (ATTACKROLL - (int) GetStat(IE_CRITICALHITBONUS) - CriticalBonus)) {
4126 //critical success
4127 printBracket("Critical Hit", GREEN);
4128 printf("\n");
4129 core->DisplayConstantStringName(STR_CRITICAL_HIT, 0xffffff, this);
4130 DisplayStringCore(this, VB_CRITHIT, DS_CONSOLE|DS_CONST );
4131 ModifyDamage (target, this, damage, resisted, weapon_damagetype[damagetype], &wi, true);
4132 UseItem(wi.slot, Flags&WEAPON_RANGED?-2:-1, target, 0, damage);
4133 ResetState();
4135 return;
4139 //get target's defense against attack
4140 int defense = target->GetDefense(damagetype);
4142 bool success;
4143 if(ReverseToHit) {
4144 success = roll > tohit - defense;
4145 } else {
4146 success = tohit + roll > defense;
4149 if (!success) {
4150 //hit failed
4151 if (Flags&WEAPON_RANGED) {//Launch the projectile anyway
4152 UseItem(wi.slot, (ieDword)-2, target, UI_MISS);
4154 ResetState();
4155 printBracket("Missed", LIGHT_RED);
4156 printf("\n");
4157 return;
4159 printBracket("Hit", GREEN);
4160 printf("\n");
4161 ModifyDamage (target, this, damage, resisted, weapon_damagetype[damagetype], &wi, false);
4162 UseItem(wi.slot, Flags&WEAPON_RANGED?-2:-1, target, 0, damage);
4163 ResetState();
4166 static EffectRef fx_stoneskin_ref={"StoneSkinModifier",NULL,-1};
4167 static EffectRef fx_stoneskin2_ref={"StoneSkin2Modifier",NULL,-1};
4168 static EffectRef fx_mirrorimage_ref={"MirrorImageModifier",NULL,-1};
4169 static EffectRef fx_aegis_ref={"Aegis",NULL,-1};
4171 void Actor::ModifyDamage(Actor *target, Actor *hitter, int &damage, int &resisted, int damagetype, WeaponInfo *wi, bool critical)
4174 int mirrorimages = target->Modified[IE_MIRRORIMAGES];
4175 if (mirrorimages) {
4176 if (LuckyRoll(1,mirrorimages+1,0) != 1) {
4177 target->fxqueue.DecreaseParam1OfEffect(fx_mirrorimage_ref, 1);
4178 target->Modified[IE_MIRRORIMAGES]--;
4179 damage = 0;
4180 return;
4184 // only check stone skins if damage type is physical or magical
4185 // DAMAGE_CRUSHING is 0, so we can't AND with it to check for its presence
4186 if (!(damagetype & ~(DAMAGE_PIERCING|DAMAGE_SLASHING|DAMAGE_MISSILE|DAMAGE_MAGIC))) {
4187 int stoneskins = target->Modified[IE_STONESKINS];
4188 if (stoneskins) {
4189 target->fxqueue.DecreaseParam1OfEffect(fx_stoneskin_ref, 1);
4190 target->fxqueue.DecreaseParam1OfEffect(fx_aegis_ref, 1);
4191 target->Modified[IE_STONESKINS]--;
4192 damage = 0;
4193 return;
4196 stoneskins = target->Modified[IE_STONESKINSGOLEM];
4197 if (stoneskins) {
4198 target->fxqueue.DecreaseParam1OfEffect(fx_stoneskin2_ref, 1);
4199 target->Modified[IE_STONESKINSGOLEM]--;
4200 damage = 0;
4201 return;
4205 if (wi) {
4206 if (BaseStats[IE_BACKSTABDAMAGEMULTIPLIER] > 1) {
4207 if (Modified[IE_STATE_ID] & (ieDword) (STATE_INVISIBLE) || Modified[IE_ALWAYSBACKSTAB]) {
4208 if ( !(core->HasFeature(GF_PROPER_BACKSTAB) && !IsBehind(target)) ) {
4209 if (target->Modified[IE_DISABLEBACKSTAB]) {
4210 // The backstab seems to have failed
4211 core->DisplayConstantString (STR_BACKSTAB_FAIL, 0xffffff);
4212 } else {
4213 if (wi->backstabbing) {
4214 damage *= Modified[IE_BACKSTABDAMAGEMULTIPLIER];
4215 // display a simple message instead of hardcoding multiplier names
4216 core->DisplayConstantStringValue (STR_BACKSTAB, 0xffffff, Modified[IE_BACKSTABDAMAGEMULTIPLIER]);
4217 } else {
4218 // weapon is unsuitable for backstab
4219 core->DisplayConstantString (STR_BACKSTAB_BAD, 0xffffff);
4226 // add strength bonus; backstab does not affect it
4227 // TODO: should actually check WEAPON_USESTRENGTH, since a sling in bg2 has it
4228 if (GetAttackStyle() != WEAPON_RANGED) {
4229 damage += core->GetStrengthBonus(1, GetStat(IE_STR), GetStat(IE_STREXTRA) );
4233 if (wi && target->fxqueue.WeaponImmunity(wi->enchantment, wi->itemflags)) {
4234 damage = 0;
4235 resisted = DR_IMMUNE; // mark immunity for GetCombatDetails
4236 } else {
4237 // check damage type immunity / resistance / susceptibility
4238 std::multimap<ieDword, DamageInfoStruct>::iterator it;
4239 it = core->DamageInfoMap.find(damagetype);
4240 if (it == core->DamageInfoMap.end()) {
4241 printf("Unhandled damagetype:%d\n", damagetype);
4242 } else if (it->second.resist_stat == 0) {
4243 // damage type without a resistance stat
4244 } else {
4245 damage += (signed)target->GetStat(IE_DAMAGEBONUS);
4246 resisted = (int) (damage * (signed)target->GetStat(it->second.resist_stat)/100.0);
4247 // check for bonuses for specific damage types
4248 if (core->HasFeature(GF_SPECIFIC_DMG_BONUS) && hitter) {
4249 int bonus = hitter->fxqueue.SpecificDamageBonus(it->second.iwd_mod_type);
4250 if (bonus) {
4251 resisted -= int (damage * bonus / 100.0);
4252 printf("Bonus damage of %d (%+d%%), neto: %d\n", int (damage * bonus / 100.0), bonus, -resisted);
4255 damage -= resisted;
4256 printf("Resisted %d of %d at %d%% resistance to %d\n", resisted, damage+resisted, target->GetStat(it->second.resist_stat), damagetype);
4257 if (damage <= 0) resisted = DR_IMMUNE;
4261 //check casting failure
4262 if (damage<0) damage = 0;
4263 if (!damage) {
4264 DisplayStringCore(this, VB_TIMMUNE, DS_CONSOLE|DS_CONST );
4265 return;
4268 if (critical) {
4269 //a critical surely raises the morale?
4270 NewBase(IE_MORALE, 1, MOD_ADDITIVE);
4271 int head = inventory.GetHeadSlot();
4272 if ((head!=-1) && target->inventory.HasItemInSlot("",(ieDword) head)) {
4273 //critical hit is averted by helmet
4274 core->DisplayConstantStringName(STR_NO_CRITICAL, 0xffffff, target);
4275 } else {
4276 damage <<=1; //critical damage is always double?
4277 core->timer->SetScreenShake(16,16,8);
4280 return;
4283 void Actor::UpdateActorState(ieDword gameTime) {
4284 //apply the modal effect on the beginning of each round
4285 if (((gameTime-roundTime)%ROUND_SIZE==0) && ModalState) {
4286 if (!ModalSpell[0]) {
4287 printMessage("Actor","Modal Spell Effect was not set!\n", YELLOW);
4288 ModalSpell[0]='*';
4289 } else if(ModalSpell[0]!='*') {
4290 if (ModalSpellSkillCheck()) {
4291 if (core->ModalStates[ModalState].aoe_spell) {
4292 core->ApplySpellPoint(ModalSpell, GetCurrentArea(), Pos, this, 0);
4293 } else {
4294 core->ApplySpell(ModalSpell, this, this, 0);
4296 if (InParty) {
4297 core->DisplayStringName(core->ModalStates[ModalState].entering_str, 0xffffff, this, IE_STR_SOUND|IE_STR_SPEECH);
4299 } else {
4300 if (InParty) {
4301 core->DisplayStringName(core->ModalStates[ModalState].failed_str, 0xffffff, this, IE_STR_SOUND|IE_STR_SPEECH);
4303 ModalState = MS_NONE;
4304 // TODO: wait for a round until allowing new states?
4309 // this is a HACK, fuzzie can't work out where else to do this for now
4310 // but we shouldn't be resetting rounds/attacks just because the actor
4311 // wandered away, the action code should probably be responsible somehow
4312 // see also line above (search for comment containing UpdateActorState)!
4313 if (LastTarget && lastattack && lastattack < (gameTime - 1)) {
4314 Actor *target = area->GetActorByGlobalID(LastTarget);
4315 if (!target || target->GetStat(IE_STATE_ID)&STATE_DEAD) {
4316 StopAttack();
4317 } else {
4318 printMessage("Attack","(Leaving attack)", GREEN);
4319 core->GetGame()->OutAttack(GetID());
4322 roundTime = 0;
4323 lastattack = 0;
4327 //idx could be: 0-6, 16-22, 32-38, 48-54
4328 //the colors are stored in 7 dwords
4329 //maybe it would be simpler to store them in 28 bytes (without using stats?)
4330 void Actor::SetColor( ieDword idx, ieDword grd)
4332 ieByte gradient = (ieByte) (grd&255);
4333 ieByte index = (ieByte) (idx&15);
4334 ieByte shift = (ieByte) (idx/16);
4335 ieDword value;
4337 //invalid value, would crash original IE
4338 if (index>6) {
4339 return;
4341 if (shift == 15) {
4342 // put gradient in all four bytes of value
4343 value = gradient;
4344 value |= (value << 8);
4345 value |= (value << 16);
4346 for (index=0;index<7;index++) {
4347 Modified[IE_COLORS+index] = value;
4349 } else {
4350 //invalid value, would crash original IE
4351 if (shift>3) {
4352 return;
4354 shift *= 8;
4355 value = gradient << shift;
4356 value |= Modified[IE_COLORS+index] & ~(255<<shift);
4357 Modified[IE_COLORS+index] = value;
4361 void Actor::SetColorMod( ieDword location, RGBModifier::Type type, int speed,
4362 unsigned char r, unsigned char g, unsigned char b,
4363 int phase)
4365 CharAnimations* ca = GetAnims();
4366 if (!ca) return;
4368 if (location == 0xff) {
4369 ca->GlobalColorMod.type = type;
4370 ca->GlobalColorMod.speed = speed;
4371 ca->GlobalColorMod.rgb.r = r;
4372 ca->GlobalColorMod.rgb.g = g;
4373 ca->GlobalColorMod.rgb.b = b;
4374 ca->GlobalColorMod.rgb.a = 0;
4375 if (phase >= 0)
4376 ca->GlobalColorMod.phase = phase;
4377 else {
4378 if (ca->GlobalColorMod.phase > 2*speed)
4379 ca->GlobalColorMod.phase=0;
4381 return;
4383 //00xx0yyy-->000xxyyy
4384 if (location&0xffffffc8) return; //invalid location
4385 location = (location &7) | ((location>>1)&0x18);
4386 ca->ColorMods[location].type = type;
4387 ca->ColorMods[location].speed = speed;
4388 ca->ColorMods[location].rgb.r = r;
4389 ca->ColorMods[location].rgb.g = g;
4390 ca->ColorMods[location].rgb.b = b;
4391 ca->ColorMods[location].rgb.a = 0;
4392 if (phase >= 0)
4393 ca->ColorMods[location].phase = phase;
4394 else {
4395 if (ca->ColorMods[location].phase > 2*speed)
4396 ca->ColorMods[location].phase = 0;
4400 void Actor::SetLeader(Actor *actor, int xoffset, int yoffset)
4402 LastFollowed = actor->GetID();
4403 FollowOffset.x = xoffset;
4404 FollowOffset.y = yoffset;
4407 //if days == 0, it means full healing
4408 void Actor::Heal(int days)
4410 if (days) {
4411 SetBase(IE_HITPOINTS, BaseStats[IE_HITPOINTS]+days*2);
4412 } else {
4413 SetBase(IE_HITPOINTS, BaseStats[IE_MAXHITPOINTS]);
4417 void Actor::AddExperience(int exp)
4419 SetBase(IE_XP,BaseStats[IE_XP]+exp);
4422 void Actor::AddExperience(int type, int level)
4424 if (type>=xpbonustypes) {
4425 return;
4427 unsigned int l = (unsigned int) (level - 1);
4429 if (l>=(unsigned int) xpbonuslevels) {
4430 l=xpbonuslevels-1;
4432 int xp = xpbonus[type*xpbonuslevels+l];
4433 core->DisplayConstantStringValue(STR_GOTXP, 0xbcefbc, (ieDword) xp);
4434 AddExperience(xp);
4437 bool Actor::Schedule(ieDword gametime, bool checkhide)
4439 if (checkhide) {
4440 if (!(InternalFlags&IF_VISIBLE) ) {
4441 return false;
4445 //check for schedule
4446 ieDword bit = 1<<((gametime/ROUND_SIZE)%7200/300);
4447 if (appearance & bit) {
4448 return true;
4450 return false;
4453 void Actor::NewPath()
4455 PathTries++;
4456 Point tmp = Destination;
4457 ClearPath();
4458 if (PathTries>10) {
4459 return;
4461 Movable::WalkTo(tmp, size );
4464 void Actor::SetInTrap(ieDword setreset)
4466 InTrap = setreset;
4467 if (setreset) {
4468 InternalFlags |= IF_INTRAP;
4469 } else {
4470 InternalFlags &= ~IF_INTRAP;
4474 void Actor::SetRunFlags(ieDword flags)
4476 InternalFlags &= ~IF_RUNFLAGS;
4477 InternalFlags |= (flags & IF_RUNFLAGS);
4480 void Actor::WalkTo(Point &Des, ieDword flags, int MinDistance)
4482 PathTries = 0;
4483 if (InternalFlags&IF_REALLYDIED) {
4484 return;
4486 SetRunFlags(flags);
4487 // is this true???
4488 if (Des.x==-2 && Des.y==-2) {
4489 Point p((ieWord) Modified[IE_SAVEDXPOS], (ieWord) Modified[IE_SAVEDYPOS] );
4490 Movable::WalkTo(p, MinDistance);
4491 } else {
4492 Movable::WalkTo(Des, MinDistance);
4496 //there is a similar function in Map for stationary vvcs
4497 void Actor::DrawVideocells(Region &screen, vvcVector &vvcCells, Color &tint)
4499 Map* area = GetCurrentArea();
4501 for (unsigned int i = 0; i < vvcCells.size(); i++) {
4502 ScriptedAnimation* vvc = vvcCells[i];
4503 /* we don't allow holes anymore
4504 if (!vvc)
4505 continue;
4508 // actually this is better be drawn by the vvc
4509 bool endReached = vvc->Draw(screen, Pos, tint, area, WantDither(), GetOrientation());
4510 if (endReached) {
4511 delete vvc;
4512 vvcCells.erase(vvcCells.begin()+i);
4513 continue;
4518 void Actor::DrawActorSprite(Region &screen, int cx, int cy, Region& bbox,
4519 SpriteCover*& newsc, Animation** anims,
4520 unsigned char Face, Color& tint)
4522 CharAnimations* ca = GetAnims();
4523 int PartCount = ca->GetTotalPartCount();
4524 Video* video = core->GetVideoDriver();
4525 Region vp = video->GetViewport();
4527 // display current frames in the right order
4528 const int* zOrder = ca->GetZOrder(Face);
4529 for (int part = 0; part < PartCount; ++part) {
4530 int partnum = part;
4531 if (zOrder) partnum = zOrder[part];
4532 Animation* anim = anims[partnum];
4533 Sprite2D* nextFrame = 0;
4534 if (anim)
4535 nextFrame = anim->GetFrame(anim->GetCurrentFrame());
4536 if (nextFrame && bbox.InsideRegion( vp ) ) {
4537 if (!newsc || !newsc->Covers(cx, cy, nextFrame->XPos, nextFrame->YPos, nextFrame->Width, nextFrame->Height)) {
4538 // the first anim contains the animarea for
4539 // the entire multi-part animation
4540 newsc = area->BuildSpriteCover(cx,
4541 cy, -anims[0]->animArea.x,
4542 -anims[0]->animArea.y,
4543 anims[0]->animArea.w,
4544 anims[0]->animArea.h, WantDither() );
4546 assert(newsc->Covers(cx, cy, nextFrame->XPos, nextFrame->YPos, nextFrame->Width, nextFrame->Height));
4548 unsigned int flags = TranslucentShadows ? BLIT_TRANSSHADOW : 0;
4549 if (!ca->lockPalette) flags|=BLIT_TINTED;
4551 video->BlitGameSprite( nextFrame, cx + screen.x, cy + screen.y,
4552 flags, tint, newsc, ca->GetPartPalette(partnum), &screen);
4558 static const int OrientdX[16] = { 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9, 10, 9, 7, 4 };
4559 static const int OrientdY[16] = { 10, 9, 7, 4, 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9 };
4560 static const unsigned int MirrorImageLocation[8] = { 4, 12, 8, 0, 6, 14, 10, 2 };
4561 static const unsigned int MirrorImageZOrder[8] = { 2, 4, 6, 0, 1, 7, 5, 3 };
4563 bool Actor::ShouldHibernate() {
4564 //finding an excuse why we don't hybernate the actor
4565 if (Modified[IE_ENABLEOFFSCREENAI])
4566 return false;
4567 if (LastTarget) //currently attacking someone
4568 return false;
4569 if (!lastRunTime) // haven't had a chance to run a script
4570 return false;
4571 if (CurrentAction)
4572 return false;
4573 if (GetNextStep())
4574 return false;
4575 if (GetNextAction())
4576 return false;
4577 if (GetWait()) //would never stop waiting
4578 return false;
4579 return true;
4582 void Actor::Draw(Region &screen)
4584 Map* area = GetCurrentArea();
4586 int cx = Pos.x;
4587 int cy = Pos.y;
4588 int explored = Modified[IE_DONOTJUMP]&DNJ_UNHINDERED;
4589 //check the deactivation condition only if needed
4590 //this fixes dead actors disappearing from fog of war (they should be permanently visible)
4591 if ((!area->IsVisible( Pos, explored) || (InternalFlags&IF_REALLYDIED) ) && (InternalFlags&IF_ACTIVE) ) {
4592 //turning actor inactive if there is no action next turn
4593 if (ShouldHibernate()) {
4594 InternalFlags|=IF_IDLE;
4596 if (!(InternalFlags&IF_REALLYDIED)) {
4597 // for a while this didn't return (disable drawing) if about to hibernate;
4598 // Avenger said (aa10aaed) "we draw the actor now for the last time".
4599 return;
4603 if (InTrap) {
4604 area->ClearTrap(this, InTrap-1);
4607 // if an actor isn't visible, should we still handle animations, draw
4608 // video cells, etc? let us assume not, for now..
4609 if (!(InternalFlags & IF_VISIBLE)) {
4610 return;
4613 //visual feedback
4614 CharAnimations* ca = GetAnims();
4615 if (!ca)
4616 return;
4618 //explored or visibilitymap (bird animations are visible in fog)
4619 //0 means opaque
4620 int NoCircle = Modified[IE_NOCIRCLE];
4621 int Trans = Modified[IE_TRANSLUCENT];
4623 if (Trans>255) {
4624 Trans=255;
4627 //iwd has this flag saved in the creature
4628 if (Modified[IE_AVATARREMOVAL]) {
4629 Trans = 255;
4630 NoCircle = 1;
4633 int Frozen = Immobile();
4634 int State = Modified[IE_STATE_ID];
4636 if (State&STATE_DEAD) {
4637 NoCircle = 1;
4640 if (State&STATE_STILL) {
4641 Frozen = 1;
4644 //adjust invisibility for enemies
4645 if (Modified[IE_EA]>EA_GOODCUTOFF) {
4646 if (State&STATE_INVISIBLE) {
4647 Trans = 255;
4651 //can't move this, because there is permanent blur state where
4652 //there is no effect (just state bit)
4653 if ((State&STATE_BLUR) && Trans < 128) {
4654 Trans = 128;
4656 Color tint = area->LightMap->GetPixel( cx / 16, cy / 12);
4657 tint.a = (ieByte) (255-Trans);
4659 unsigned char heightmapindex = area->HeightMap->GetAt( cx / 16, cy / 12);
4660 if (heightmapindex > 15) {
4661 // there are 8bpp lightmaps (eg, bg2's AR1300) and fuzzie
4662 // cannot work out how they work, so here is an incorrect
4663 // hack (probably). please fix!
4664 heightmapindex = 15;
4667 //don't use cy for area map access beyond this point
4668 cy -= heightmapindex;
4670 //draw videocells under the actor
4671 DrawVideocells(screen, vvcShields, tint);
4673 Video* video = core->GetVideoDriver();
4674 Region vp = video->GetViewport();
4676 bool drawcircle = (NoCircle == 0);
4677 if ((core->GetGameControl()->GetScreenFlags()&SF_CUTSCENE)) {
4678 // ground circles are not drawn in cutscenes
4679 // they SHOULD be drawn for at least white speaker circles
4680 // (eg, via VerbalConstant), please fix :)
4681 drawcircle = false;
4683 if (BaseStats[IE_STATE_ID]&STATE_DEAD || InternalFlags&IF_JUSTDIED) {
4684 drawcircle = false;
4686 bool drawtarget = drawcircle;
4687 // we always show circle/target on pause
4688 if (drawcircle && !(core->GetGameControl()->GetDialogueFlags() & DF_FREEZE_SCRIPTS)) {
4689 // check marker feedback level
4690 ieDword markerfeedback = 4;
4691 core->GetDictionary()->Lookup("GUI Feedback Level", markerfeedback);
4692 if (Over) {
4693 // picked creature
4694 drawcircle = markerfeedback >= 1;
4695 } else if (Selected) {
4696 // selected creature
4697 drawcircle = markerfeedback >= 2;
4698 } else if (IsPC()) {
4699 // selectable
4700 drawcircle = markerfeedback >= 3;
4701 } else if (Modified[IE_EA] >= EA_EVILCUTOFF) {
4702 // hostile
4703 drawcircle = markerfeedback >= 5;
4704 } else {
4705 // all
4706 drawcircle = markerfeedback >= 6;
4708 drawtarget = Selected && markerfeedback >= 4;
4710 if (drawcircle) {
4711 DrawCircle(vp);
4713 if (drawtarget) {
4714 DrawTargetPoint(vp);
4717 unsigned char StanceID = GetStance();
4718 unsigned char Face = GetNextFace();
4719 Animation** anims = ca->GetAnimation( StanceID, Face );
4720 if (anims) {
4721 // update bounding box and such
4722 int PartCount = ca->GetTotalPartCount();
4723 Sprite2D* nextFrame = 0;
4724 nextFrame = anims[0]->GetFrame(anims[0]->GetCurrentFrame());
4726 //make actor unselectable and unselected when it is not moving
4727 //dead, petriefied, frozen, paralysed etc.
4728 if (Frozen) {
4729 if (Selected!=0x80) {
4730 Selected = 0x80;
4731 core->GetGame()->SelectActor(this, false, SELECT_NORMAL);
4734 //If you find a better place for it, I'll really be glad to put it there
4735 //IN BG1 and BG2, this is at the ninth frame...
4736 if(attackProjectile && (anims[0]->GetCurrentFrame() == 8/*anims[0]->GetFramesCount()/2*/)) {
4737 GetCurrentArea()->AddProjectile(attackProjectile, Pos, LastTarget);
4738 attackProjectile = NULL;
4740 if (nextFrame && lastFrame != nextFrame) {
4741 Region newBBox;
4742 if (PartCount == 1) {
4743 newBBox.x = cx - nextFrame->XPos;
4744 newBBox.w = nextFrame->Width;
4745 newBBox.y = cy - nextFrame->YPos;
4746 newBBox.h = nextFrame->Height;
4747 } else {
4748 // FIXME: currently using the animarea instead
4749 // of the real bounding box of this (multi-part) frame.
4750 // Shouldn't matter much, though. (wjp)
4751 newBBox.x = cx + anims[0]->animArea.x;
4752 newBBox.y = cy + anims[0]->animArea.y;
4753 newBBox.w = anims[0]->animArea.w;
4754 newBBox.h = anims[0]->animArea.h;
4756 lastFrame = nextFrame;
4757 SetBBox( newBBox );
4760 // Drawing the actor:
4761 // * mirror images:
4762 // Drawn without transparency, unless fully invisible.
4763 // Order: W, E, N, S, NW, SE, NE, SW
4764 // Uses extraCovers 3-10
4765 // * blurred copies (3 of them)
4766 // Drawn with transparency.
4767 // distance between copies depends on IE_MOVEMENTRATE
4768 // TODO: actually, the direction is the real movement direction,
4769 // not the (rounded) direction given Face
4770 // Uses extraCovers 0-2
4771 // * actor itself
4772 // Uses main spritecover
4774 //comments by Avenger:
4775 // currently we don't have a real direction, but the orientation field
4776 // could be used with higher granularity. When we need the face value
4777 // it could be divided so it will become a 0-15 number.
4780 SpriteCover *sc = 0, *newsc = 0;
4781 int blurx = cx;
4782 int blury = cy;
4783 int blurdx = (OrientdX[Face]*(int)Modified[IE_MOVEMENTRATE])/20;
4784 int blurdy = (OrientdY[Face]*(int)Modified[IE_MOVEMENTRATE])/20;
4785 Color mirrortint = tint;
4786 //mirror images are also half transparent when invis
4787 //if (mirrortint.a > 0) mirrortint.a = 255;
4789 int i;
4791 // mirror images behind the actor
4792 for (i = 0; i < 4; ++i) {
4793 unsigned int m = MirrorImageZOrder[i];
4794 if (m < Modified[IE_MIRRORIMAGES]) {
4795 Region sbbox = BBox;
4796 int dir = MirrorImageLocation[m];
4797 int icx = cx + 3*OrientdX[dir];
4798 int icy = cy + 3*OrientdY[dir];
4799 Point iPos(icx, icy);
4800 if (area->GetBlocked(iPos) & (PATH_MAP_PASSABLE|PATH_MAP_ACTOR)) {
4801 sbbox.x += 3*OrientdX[dir];
4802 sbbox.y += 3*OrientdY[dir];
4803 newsc = sc = extraCovers[3+m];
4804 DrawActorSprite(screen, icx, icy, sbbox, newsc,
4805 anims, Face, mirrortint);
4806 if (newsc != sc) {
4807 delete sc;
4808 extraCovers[3+m] = newsc;
4811 } else {
4812 delete extraCovers[3+m];
4813 extraCovers[3+m] = NULL;
4817 // blur sprites behind the actor
4818 if (State & STATE_BLUR) {
4819 if (Face < 4 || Face >= 12) {
4820 Region sbbox = BBox;
4821 sbbox.x -= 4*blurdx; sbbox.y -= 4*blurdy;
4822 blurx -= 4*blurdx; blury -= 4*blurdy;
4823 for (i = 0; i < 3; ++i) {
4824 sbbox.x += blurdx; sbbox.y += blurdy;
4825 blurx += blurdx; blury += blurdy;
4826 newsc = sc = extraCovers[i];
4827 DrawActorSprite(screen, blurx, blury, sbbox, newsc,
4828 anims, Face, tint);
4829 if (newsc != sc) {
4830 delete sc;
4831 extraCovers[i] = newsc;
4837 // actor itself
4838 newsc = sc = GetSpriteCover();
4839 DrawActorSprite(screen, cx, cy, BBox, newsc, anims, Face, tint);
4840 if (newsc != sc) SetSpriteCover(newsc);
4842 // blur sprites in front of the actor
4843 if (State & STATE_BLUR) {
4844 if (Face >= 4 && Face < 12) {
4845 Region sbbox = BBox;
4846 for (i = 0; i < 3; ++i) {
4847 sbbox.x -= blurdx; sbbox.y -= blurdy;
4848 blurx -= blurdx; blury -= blurdy;
4849 newsc = sc = extraCovers[i];
4850 DrawActorSprite(screen, blurx, blury, sbbox, newsc,
4851 anims, Face, tint);
4852 if (newsc != sc) {
4853 delete sc;
4854 extraCovers[i] = newsc;
4860 // mirror images in front of the actor
4861 for (i = 4; i < 8; ++i) {
4862 unsigned int m = MirrorImageZOrder[i];
4863 if (m < Modified[IE_MIRRORIMAGES]) {
4864 Region sbbox = BBox;
4865 int dir = MirrorImageLocation[m];
4866 int icx = cx + 3*OrientdX[dir];
4867 int icy = cy + 3*OrientdY[dir];
4868 Point iPos(icx, icy);
4869 if (area->GetBlocked(iPos) & (PATH_MAP_PASSABLE|PATH_MAP_ACTOR)) {
4870 sbbox.x += 3*OrientdX[dir];
4871 sbbox.y += 3*OrientdY[dir];
4872 newsc = sc = extraCovers[3+m];
4873 DrawActorSprite(screen, icx, icy, sbbox, newsc,
4874 anims, Face, mirrortint);
4875 if (newsc != sc) {
4876 delete sc;
4877 extraCovers[3+m] = newsc;
4880 } else {
4881 delete extraCovers[3+m];
4882 extraCovers[3+m] = NULL;
4886 // advance animations one frame (in sync)
4887 if (Frozen)
4888 anims[0]->LastFrame();
4889 else
4890 anims[0]->NextFrame();
4892 for (int part = 1; part < PartCount; ++part) {
4893 if (anims[part])
4894 anims[part]->GetSyncedNextFrame(anims[0]);
4897 if (anims[0]->endReached) {
4898 if (HandleActorStance() ) {
4899 anims[0]->endReached = false;
4900 anims[0]->SetPos(0);
4904 ca->PulseRGBModifiers();
4907 //draw videocells over the actor
4908 DrawVideocells(screen, vvcOverlays, tint);
4911 /* Handling automatic stance changes */
4912 bool Actor::HandleActorStance()
4914 CharAnimations* ca = GetAnims();
4915 int StanceID = GetStance();
4917 if (ca->autoSwitchOnEnd) {
4918 int nextstance = ca->nextStanceID;
4920 if (nextstance == IE_ANI_READY) {
4921 if (!core->GetGame()->CombatCounter) {
4922 nextstance = IE_ANI_AWAKE;
4926 SetStance( nextstance );
4927 ca->autoSwitchOnEnd = false;
4928 return true;
4930 int x = rand()%1000;
4931 if ((StanceID==IE_ANI_AWAKE) && !x ) {
4932 SetStance( IE_ANI_HEAD_TURN );
4933 return true;
4935 // added CurrentAction as part of blocking action fixes
4936 if ((StanceID==IE_ANI_READY) && !CurrentAction && !GetNextAction()) {
4937 SetStance( IE_ANI_AWAKE );
4938 return true;
4940 if (StanceID == IE_ANI_ATTACK || StanceID == IE_ANI_ATTACK_JAB ||
4941 StanceID == IE_ANI_ATTACK_SLASH || StanceID == IE_ANI_ATTACK_BACKSLASH ||
4942 StanceID == IE_ANI_SHOOT)
4944 SetStance( AttackStance );
4945 return true;
4947 return false;
4950 void Actor::GetSoundFrom2DA(ieResRef Sound, unsigned int index)
4952 if (!anims) return;
4954 AutoTable tab(anims->ResRef);
4955 if (!tab)
4956 return;
4958 switch (index) {
4959 case VB_ATTACK:
4960 index = 0;
4961 break;
4962 case VB_DAMAGE:
4963 index = 8;
4964 break;
4965 case VB_DIE:
4966 index = 10;
4967 break;
4968 case VB_SELECT:
4969 index = 36+rand()%4;
4970 break;
4972 strnlwrcpy(Sound, tab->QueryField (index, 0), 8);
4975 void Actor::GetSoundFromINI(ieResRef Sound, unsigned int index)
4977 const char *resource = "";
4978 char section[12];
4979 unsigned int animid=BaseStats[IE_ANIMATION_ID];
4980 if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
4981 animid&=0xff;
4983 snprintf(section,10,"%d", animid);
4985 switch(index) {
4986 case VB_ATTACK:
4987 resource = core->GetResDataINI()->GetKeyAsString(section, "at1sound","");
4988 break;
4989 case VB_DAMAGE:
4990 resource = core->GetResDataINI()->GetKeyAsString(section, "hitsound","");
4991 break;
4992 case VB_DIE:
4993 resource = core->GetResDataINI()->GetKeyAsString(section, "dfbsound","");
4994 break;
4995 case VB_SELECT:
4996 break;
4998 int count = CountElements(resource,',');
4999 if (count<=0) return;
5000 count = core->Roll(1,count,-1);
5001 while(count--) {
5002 while(*resource && *resource!=',') resource++;
5003 if (*resource==',') resource++;
5005 strncpy(Sound, resource, 8);
5006 for(count=0;count<8 && Sound[count]!=',';count++) {};
5007 Sound[count]=0;
5010 void Actor::ResolveStringConstant(ieResRef Sound, unsigned int index)
5012 //resolving soundset (bg1/bg2 style)
5013 if (PCStats && PCStats->SoundSet[0]&& csound[index]) {
5014 snprintf(Sound, sizeof(ieResRef), "%s%c", PCStats->SoundSet, csound[index]);
5015 return;
5018 Sound[0]=0;
5020 if (core->HasFeature(GF_RESDATA_INI)) {
5021 GetSoundFromINI(Sound, index);
5022 } else {
5023 GetSoundFrom2DA(Sound, index);
5027 void Actor::SetActionButtonRow(ActionButtonRow &ar)
5029 for(int i=0;i<MAX_QSLOTS;i++) {
5030 ieByte tmp = ar[i+3];
5031 if (QslotTranslation) {
5032 tmp=gemrb2iwd[tmp];
5034 PCStats->QSlots[i]=tmp;
5038 //the first 3 buttons are untouched by this function
5039 void Actor::GetActionButtonRow(ActionButtonRow &ar)
5041 InitButtons(GetStat(IE_CLASS), false);
5042 for(int i=0;i<GUIBT_COUNT-3;i++) {
5043 ieByte tmp=PCStats->QSlots[i];
5044 if (QslotTranslation) {
5045 if (tmp>=90) { //quick weapons
5046 tmp=16+tmp%10;
5047 } else if (tmp>=80) { //quick items
5048 tmp=9+tmp%10;
5049 } else if (tmp>=70) { //quick spells
5050 tmp=3+tmp%10;
5051 } else {
5052 tmp=iwd2gemrb[tmp];
5055 ar[i+3]=tmp;
5057 memcpy(ar,DefaultButtons,3*sizeof(ieByte) );
5060 void Actor::SetPortrait(const char* ResRef, int Which)
5062 int i;
5064 if (ResRef == NULL) {
5065 return;
5067 if (InParty) {
5068 core->SetEventFlag(EF_PORTRAIT);
5071 if(Which!=1) {
5072 memset( SmallPortrait, 0, 8 );
5073 strncpy( SmallPortrait, ResRef, 8 );
5075 if(Which!=2) {
5076 memset( LargePortrait, 0, 8 );
5077 strncpy( LargePortrait, ResRef, 8 );
5079 if(!Which) {
5080 for (i = 0; i < 8 && ResRef[i]; i++) {};
5081 SmallPortrait[i] = 'S';
5082 LargePortrait[i] = 'M';
5086 void Actor::SetSoundFolder(const char *soundset)
5088 if (core->HasFeature(GF_SOUNDFOLDERS)) {
5089 char filepath[_MAX_PATH];
5091 strnlwrcpy(PCStats->SoundFolder, soundset, 32);
5092 PathJoin(filepath,core->GamePath,"sounds",PCStats->SoundFolder,0);
5093 char file[_MAX_PATH];
5094 if (FileGlob(file, filepath, "?????01")) {
5095 file[5] = '\0';
5096 } else if (FileGlob(file, filepath, "????01")) {
5097 file[4] = '\0';
5098 } else {
5099 return;
5101 strnlwrcpy(PCStats->SoundSet, file, 8);
5102 } else {
5103 strnlwrcpy(PCStats->SoundSet, soundset, 8);
5104 PCStats->SoundFolder[0]=0;
5108 bool Actor::HasVVCCell(const ieResRef resource)
5110 int j = true;
5111 vvcVector *vvcCells=&vvcShields;
5112 retry:
5113 size_t i=vvcCells->size();
5114 while (i--) {
5115 ScriptedAnimation *vvc = (*vvcCells)[i];
5116 if (vvc == NULL) {
5117 continue;
5119 if ( strnicmp(vvc->ResName, resource, 8) == 0) {
5120 return true;
5123 vvcCells=&vvcOverlays;
5124 if (j) { j = false; goto retry; }
5125 return false;
5128 void Actor::RemoveVVCell(const ieResRef resource, bool graceful)
5130 bool j = true;
5131 vvcVector *vvcCells=&vvcShields;
5132 retry:
5133 size_t i=vvcCells->size();
5134 while (i--) {
5135 ScriptedAnimation *vvc = (*vvcCells)[i];
5136 if (vvc == NULL) {
5137 continue;
5139 if ( strnicmp(vvc->ResName, resource, 8) == 0) {
5140 if (graceful) {
5141 vvc->SetPhase(P_RELEASE);
5142 } else {
5143 delete vvc;
5144 vvcCells->erase(vvcCells->begin()+i);
5148 vvcCells=&vvcOverlays;
5149 if (j) { j = false; goto retry; }
5152 //this is a faster version of hasvvccell, because it knows where to look
5153 //for the overlay, it also returns the vvc for further manipulation
5154 //use this for the seven eyes overlay
5155 ScriptedAnimation *Actor::FindOverlay(int index)
5157 vvcVector *vvcCells;
5159 if (index>31) return NULL;
5161 if (hc_locations&(1<<index)) vvcCells=&vvcShields;
5162 else vvcCells=&vvcOverlays;
5164 const char *resRef = hc_overlays[index];
5166 size_t i=vvcCells->size();
5167 while (i--) {
5168 ScriptedAnimation *vvc = (*vvcCells)[i];
5169 if (vvc == NULL) {
5170 continue;
5172 if ( strnicmp(vvc->ResName, resRef, 8) == 0) {
5173 return vvc;
5176 return NULL;
5179 void Actor::AddVVCell(ScriptedAnimation* vvc)
5181 vvcVector *vvcCells;
5183 //if the vvc was not created, don't try to add it
5184 if (!vvc) {
5185 return;
5187 if (vvc->ZPos<0) {
5188 vvcCells=&vvcShields;
5189 } else {
5190 vvcCells=&vvcOverlays;
5192 size_t i=vvcCells->size();
5193 while (i--) {
5194 if ((*vvcCells)[i] == NULL) {
5195 (*vvcCells)[i] = vvc;
5196 return;
5199 vvcCells->push_back( vvc );
5202 //returns restored spell level
5203 int Actor::RestoreSpellLevel(ieDword maxlevel, ieDword type)
5205 int typemask;
5207 switch (type) {
5208 case 0: //allow only mage
5209 typemask = ~2;
5210 break;
5211 case 1: //allow only cleric
5212 typemask = ~1;
5213 break;
5214 default:
5215 //allow any (including innates)
5216 typemask = ~0;
5218 for (int i=maxlevel;i>0;i--) {
5219 CREMemorizedSpell *cms = spellbook.FindUnchargedSpell(typemask, maxlevel);
5220 if (cms) {
5221 spellbook.ChargeSpell(cms);
5222 return i;
5225 return 0;
5228 //replenishes spells, cures fatigue
5229 void Actor::Rest(int hours)
5231 if (hours) {
5232 //do remove effects
5233 int remaining = hours*10;
5234 //removes hours*10 fatigue points
5235 NewStat (IE_FATIGUE, -remaining, MOD_ADDITIVE);
5236 NewStat (IE_INTOXICATION, -remaining, MOD_ADDITIVE);
5237 //restore hours*10 spell levels
5238 //rememorization starts with the lower spell levels?
5239 inventory.ChargeAllItems (remaining);
5240 for (int level = 1; level<16; level++) {
5241 if (level<remaining) {
5242 break;
5244 while (remaining>0) {
5245 remaining -= RestoreSpellLevel(level,0);
5248 } else {
5249 SetBase (IE_FATIGUE, 0);
5250 SetBase (IE_INTOXICATION, 0);
5251 inventory.ChargeAllItems (0);
5252 spellbook.ChargeAllSpells ();
5256 //returns the actual slot from the quickslot
5257 int Actor::GetQuickSlot(int slot)
5259 assert(slot<8);
5260 if (inventory.HasItemInSlot("",inventory.GetMagicSlot())) {
5261 return inventory.GetMagicSlot();
5263 if (!PCStats) {
5264 return slot+inventory.GetWeaponSlot();
5266 return PCStats->QuickWeaponSlots[slot];
5269 //marks the quickslot as equipped
5270 int Actor::SetEquippedQuickSlot(int slot, int header)
5272 if (!PCStats) {
5273 if (header<0) header=0;
5274 inventory.SetEquippedSlot(slot, header);
5275 return 0;
5279 if ((slot<0) || (slot == IW_NO_EQUIPPED) ) {
5280 if (slot == IW_NO_EQUIPPED) {
5281 slot = inventory.GetFistSlot();
5283 int i;
5284 for(i=0;i<MAX_QUICKWEAPONSLOT;i++) {
5285 if(slot+inventory.GetWeaponSlot()==PCStats->QuickWeaponSlots[i]) {
5286 slot = i;
5287 break;
5290 if (i==MAX_QUICKWEAPONSLOT) {
5291 return 0;
5295 assert(slot<MAX_QUICKWEAPONSLOT);
5296 if (header==-1) {
5297 header = PCStats->QuickWeaponHeaders[slot];
5299 else {
5300 PCStats->QuickWeaponHeaders[slot]=header;
5302 slot = PCStats->QuickWeaponSlots[slot]-inventory.GetWeaponSlot();
5303 Equipped = (ieWordSigned) slot;
5304 EquippedHeader = (ieWord) header;
5305 if (inventory.SetEquippedSlot(slot, header)) {
5306 return 0;
5308 return STR_MAGICWEAPON;
5311 //if target is a non living scriptable, then we simply shoot for its position
5312 //the fx should get a NULL target, and handle itself by using the position
5313 //(shouldn't crash when target is NULL)
5314 bool Actor::UseItemPoint(ieDword slot, ieDword header, Point &target, ieDword flags)
5316 CREItem *item = inventory.GetSlotItem(slot);
5317 if (!item)
5318 return false;
5320 ieResRef tmpresref;
5321 strnuprcpy(tmpresref, item->ItemResRef, sizeof(ieResRef)-1);
5323 Item *itm = gamedata->GetItem(tmpresref);
5324 if (!itm) return false; //quick item slot contains invalid item resref
5325 //item is depleted for today
5326 if(itm->UseCharge(item->Usages, header, false)==CHG_DAY) {
5327 return false;
5330 Projectile *pro = itm->GetProjectile(slot, header, flags&UI_MISS);
5331 ChargeItem(slot, header, item, itm, flags&UI_SILENT);
5332 gamedata->FreeItem(itm,tmpresref, false);
5333 if (pro) {
5334 pro->SetCaster(globalID);
5335 GetCurrentArea()->AddProjectile(pro, Pos, target);
5336 return true;
5338 return false;
5341 static EffectRef fx_damage_ref={"Damage",NULL,-1};
5343 bool Actor::UseItem(ieDword slot, ieDword header, Scriptable* target, ieDword flags, int damage)
5345 if (target->Type!=ST_ACTOR) {
5346 return UseItemPoint(slot, header, target->Pos, flags);
5349 Actor *tar = (Actor *) target;
5350 CREItem *item = inventory.GetSlotItem(slot);
5351 if (!item)
5352 return false;
5354 ieResRef tmpresref;
5355 strnuprcpy(tmpresref, item->ItemResRef, sizeof(ieResRef)-1);
5357 Item *itm = gamedata->GetItem(tmpresref);
5358 if (!itm) return false; //quick item slot contains invalid item resref
5359 //item is depleted for today
5360 if (itm->UseCharge(item->Usages, header, false)==CHG_DAY) {
5361 return false;
5364 Projectile *pro = itm->GetProjectile(slot, header, flags&UI_MISS);
5365 ChargeItem(slot, header, item, itm, flags&UI_SILENT);
5366 gamedata->FreeItem(itm,tmpresref, false);
5367 if (pro) {
5368 //ieDword is unsigned!!
5369 pro->SetCaster(globalID);
5370 if(((int)header < 0) && !(flags&UI_MISS)) { //using a weapon
5371 ITMExtHeader *which = itm->GetWeaponHeader(header == (ieDword)-2);
5372 Effect* AttackEffect = EffectQueue::CreateEffect(fx_damage_ref, damage, (weapon_damagetype[which->DamageType])<<16, FX_DURATION_INSTANT_LIMITED);
5373 AttackEffect->Projectile = which->ProjectileAnimation;
5374 AttackEffect->Target = FX_TARGET_PRESET;
5375 pro->GetEffects()->AddEffect(AttackEffect, true);
5376 //AddEffect created a copy, the original needs to be scrapped
5377 delete AttackEffect;
5378 attackProjectile = pro;
5379 } else //launch it now as we are not attacking
5380 GetCurrentArea()->AddProjectile(pro, Pos, tar->globalID);
5381 return true;
5383 return false;
5386 void Actor::ChargeItem(ieDword slot, ieDword header, CREItem *item, Item *itm, bool silent)
5388 if (!itm) {
5389 item = inventory.GetSlotItem(slot);
5390 if (!item)
5391 return;
5392 itm = gamedata->GetItem(item->ItemResRef);
5394 if (!itm) return; //quick item slot contains invalid item resref
5396 if (InParty) {
5397 core->SetEventFlag( EF_ACTION );
5400 if (!silent) {
5401 ieByte stance = AttackStance;
5402 for (int i=0;i<animcount;i++) {
5403 if ( strnicmp(item->ItemResRef, itemanim[i].itemname, 8) == 0) {
5404 stance = itemanim[i].animation;
5407 if (stance!=0xff) {
5408 SetStance(stance);
5409 //play only one cycle of animations
5411 // this was crashing for fuzzie due to NULL anims
5412 if (anims) {
5413 anims->nextStanceID=IE_ANI_READY;
5414 anims->autoSwitchOnEnd=true;
5419 switch(itm->UseCharge(item->Usages, header, true)) {
5420 case CHG_DAY:
5421 break;
5422 case CHG_BREAK: //both
5423 if (!silent) {
5424 core->PlaySound(DS_ITEM_GONE);
5426 //fall through
5427 case CHG_NOSOUND: //remove item
5428 inventory.BreakItemSlot(slot);
5429 break;
5430 default: //don't do anything
5431 break;
5435 int Actor::IsReverseToHit()
5437 return ReverseToHit;
5440 void Actor::InitButtons(ieDword cls, bool forced)
5442 if (!PCStats) {
5443 return;
5445 if ( (PCStats->QSlots[0]!=0xff) && !forced) {
5446 return;
5449 ActionButtonRow myrow;
5450 if ((int) cls >= classcount) {
5451 memcpy(&myrow, &DefaultButtons, sizeof(ActionButtonRow));
5452 } else {
5453 memcpy(&myrow, GUIBTDefaults+cls, sizeof(ActionButtonRow));
5455 SetActionButtonRow(myrow);
5458 void Actor::SetFeat(unsigned int feat, int mode)
5460 if (feat>=MAX_FEATS) {
5461 return;
5463 ieDword mask = 1<<(feat&31);
5464 ieDword idx = feat>>5;
5465 switch (mode) {
5466 case BM_SET: case BM_OR:
5467 BaseStats[IE_FEATS1+idx]|=mask;
5468 break;
5469 case BM_NAND:
5470 BaseStats[IE_FEATS1+idx]&=~mask;
5471 break;
5472 case BM_XOR:
5473 BaseStats[IE_FEATS1+idx]^=mask;
5474 break;
5478 void Actor::SetUsedWeapon(const char* AnimationType, ieWord* MeleeAnimation, int wt)
5480 memcpy(WeaponRef, AnimationType, sizeof(WeaponRef) );
5481 if (wt != -1) WeaponType = wt;
5482 if (!anims)
5483 return;
5484 anims->SetWeaponRef(AnimationType);
5485 anims->SetWeaponType(WeaponType);
5486 SetAttackMoveChances(MeleeAnimation);
5487 if (InParty) {
5488 //update the paperdoll weapon animation
5489 core->SetEventFlag(EF_UPDATEANIM);
5491 WeaponInfo wi;
5492 ITMExtHeader *header = GetWeapon(wi);
5494 if(header && (header->AttackType == ITEM_AT_BOW)) {
5495 ITMExtHeader* projHeader = GetRangedWeapon(wi);
5496 if (projHeader->ProjectileQualifier == 0) return; /* no ammo yet? */
5497 AttackStance = IE_ANI_SHOOT;
5498 anims->SetRangedType(projHeader->ProjectileQualifier-1);
5499 //bows ARE one handed, from an anim POV at least
5500 anims->SetWeaponType(IE_ANI_WEAPON_1H);
5501 return;
5503 if(header && (header->AttackType == ITEM_AT_PROJECTILE)) {
5504 AttackStance = IE_ANI_ATTACK_SLASH; //That's it!!
5505 return;
5507 AttackStance = IE_ANI_ATTACK;
5510 void Actor::SetUsedShield(const char* AnimationType, int wt)
5512 memcpy(ShieldRef, AnimationType, sizeof(ShieldRef) );
5513 if (wt != -1) WeaponType = wt;
5514 if (AnimationType[0] == ' ' || AnimationType[0] == 0)
5515 if (WeaponType == IE_ANI_WEAPON_2W)
5516 WeaponType = IE_ANI_WEAPON_1H;
5518 if (!anims)
5519 return;
5520 anims->SetOffhandRef(AnimationType);
5521 anims->SetWeaponType(WeaponType);
5522 if (InParty) {
5523 //update the paperdoll weapon animation
5524 core->SetEventFlag(EF_UPDATEANIM);
5528 void Actor::SetUsedHelmet(const char* AnimationType)
5530 memcpy(HelmetRef, AnimationType, sizeof(HelmetRef) );
5531 if (!anims)
5532 return;
5533 anims->SetHelmetRef(AnimationType);
5534 if (InParty) {
5535 //update the paperdoll weapon animation
5536 core->SetEventFlag(EF_UPDATEANIM);
5540 void Actor::SetupFist()
5542 int slot = core->QuerySlot( 0 );
5543 assert (core->QuerySlotEffects(slot)==SLOT_EFFECT_FIST);
5544 int row = GetBase(fiststat);
5545 int col = GetXPLevel(false);
5547 if (FistRows<0) {
5548 FistRows=0;
5549 AutoTable fist("fistweap");
5550 if (fist) {
5551 //default value
5552 strnlwrcpy( DefaultFist, fist->QueryField( (unsigned int) -1), 8);
5553 FistRows = fist->GetRowCount();
5554 fistres = new FistResType[FistRows];
5555 for (int i=0;i<FistRows;i++) {
5556 int maxcol = fist->GetColumnCount(i)-1;
5557 for (int cols = 0;cols<MAX_LEVEL;cols++) {
5558 strnlwrcpy( fistres[i][cols], fist->QueryField( i, cols>maxcol?maxcol:cols ), 8);
5560 *(int *) fistres[i] = atoi(fist->GetRowName( i));
5564 if (col>MAX_LEVEL) col=MAX_LEVEL;
5565 if (col<1) col=1;
5567 const char *ItemResRef = DefaultFist;
5568 for (int i = 0;i<FistRows;i++) {
5569 if (*(int *) fistres[i] == row) {
5570 ItemResRef = fistres[i][col];
5573 inventory.SetSlotItemRes(ItemResRef, slot);
5576 static ieDword ResolveTableValue(const char *resref, ieDword stat, ieDword mcol, ieDword vcol) {
5577 long ret = 0;
5578 //don't close this table, it can mess with the guiscripts
5579 int table = gamedata->LoadTable(resref);
5580 Holder<TableMgr> tm = gamedata->GetTable(table);
5581 if (tm) {
5582 unsigned int row;
5583 if (mcol == 0xff) {
5584 row = stat;
5585 } else {
5586 row = tm->FindTableValue(mcol, stat);
5587 if (row==0xffffffff) {
5588 return 0;
5591 if (valid_number(tm->QueryField(row, vcol), ret)) {
5592 return (ieDword) ret;
5596 return 0;
5599 static ieDword GetKitIndex (ieDword kit, const char *resref="kitlist")
5601 int kitindex = 0;
5603 if ((kit&BG2_KITMASK) == KIT_BARBARIAN) {
5604 kitindex = kit&0xfff;
5607 // carefully looking for kit by the usability flag
5608 // since the barbarian kit id clashes with the no-kit value
5609 if (kitindex == 0 && kit != KIT_BARBARIAN) {
5610 Holder<TableMgr> tm = gamedata->GetTable(gamedata->LoadTable(resref) );
5611 if (tm) {
5612 kitindex = tm->FindTableValue(6, kit);
5613 if (kitindex < 0) {
5614 kitindex = 0;
5619 return (ieDword)kitindex;
5622 int Actor::CheckUsability(Item *item) const
5624 ieDword itembits[2]={item->UsabilityBitmask, item->KitUsability};
5626 for (int i=0;i<usecount;i++) {
5627 ieDword itemvalue = itembits[itemuse[i].which];
5628 ieDword stat = GetStat(itemuse[i].stat);
5629 ieDword mcol = itemuse[i].mcol;
5630 //if we have a kit, we just we use it's index for the lookup
5631 if (itemuse[i].stat==IE_KIT) {
5632 stat = GetKitIndex(stat, itemuse[i].table);
5633 mcol = 0xff;
5635 stat = ResolveTableValue(itemuse[i].table, stat, mcol, itemuse[i].vcol);
5636 if (stat&itemvalue) {
5637 //printf("failed usability: itemvalue %d, stat %d, stat value %d\n", itemvalue, itemuse[i].stat, stat);
5638 return STR_CANNOT_USE_ITEM;
5642 return 0;
5645 //checks usability only
5646 int Actor::Unusable(Item *item) const
5648 if (!GetStat(IE_CANUSEANYITEM)) {
5649 int unusable = CheckUsability(item);
5650 if (unusable) {
5651 return unusable;
5655 // iesdp says this is always checked?
5656 if (item->MinLevel>GetXPLevel(true)) {
5657 return STR_CANNOT_USE_ITEM;
5660 if (!CheckAbilities) {
5661 return 0;
5664 if (item->MinStrength>GetStat(IE_STR)) {
5665 return STR_CANNOT_USE_ITEM;
5668 if (item->MinStrength==18) {
5669 if (GetStat(IE_STR)==18) {
5670 if (item->MinStrengthBonus>GetStat(IE_STREXTRA)) {
5671 return STR_CANNOT_USE_ITEM;
5676 if (item->MinIntelligence>GetStat(IE_INT)) {
5677 return STR_CANNOT_USE_ITEM;
5679 if (item->MinDexterity>GetStat(IE_DEX)) {
5680 return STR_CANNOT_USE_ITEM;
5682 if (item->MinWisdom>GetStat(IE_WIS)) {
5683 return STR_CANNOT_USE_ITEM;
5685 if (item->MinConstitution>GetStat(IE_CON)) {
5686 return STR_CANNOT_USE_ITEM;
5688 if (item->MinCharisma>GetStat(IE_CHR)) {
5689 return STR_CANNOT_USE_ITEM;
5691 //note, weapon proficiencies shouldn't be checked here
5692 //missing proficiency causes only attack penalty
5693 return 0;
5696 //full palette will be shaded in gradient color
5697 void Actor::SetGradient(ieDword gradient)
5699 gradient |= (gradient <<16);
5700 gradient |= (gradient <<8);
5701 for(int i=0;i<7;i++) {
5702 Modified[IE_COLORS+i]=gradient;
5706 //sets one bit of the sanctuary stat (used for overlays)
5707 void Actor::SetOverlay(unsigned int overlay)
5709 if (overlay>=32) return;
5710 Modified[IE_SANCTUARY]|=1<<overlay;
5713 //returns true if spell state is already set or illegal
5714 bool Actor::SetSpellState(unsigned int spellstate)
5716 if (spellstate>=192) return true;
5717 unsigned int pos = IE_SPLSTATE_ID1+(spellstate>>5);
5718 unsigned int bit = 1<<(spellstate&31);
5719 if (Modified[pos]&bit) return true;
5720 Modified[pos]|=bit;
5721 return false;
5724 //returns true if spell state is already set
5725 bool Actor::HasSpellState(unsigned int spellstate)
5727 if (spellstate>=192) return false;
5728 unsigned int pos = IE_SPLSTATE_ID1+(spellstate>>5);
5729 unsigned int bit = 1<<(spellstate&31);
5730 if (Modified[pos]&bit) return true;
5731 return false;
5734 //returns the numeric value of a feat, different from HasFeat
5735 //for multiple feats
5736 int Actor::GetFeat(unsigned int feat) const
5738 if (feat>=MAX_FEATS) {
5739 return -1;
5741 if (Modified[IE_FEATS1+(feat>>5)]&(1<<(feat&31)) ) {
5742 //return the numeric stat value, instead of the boolean
5743 if (featstats[feat]) {
5744 return Modified[featstats[feat]];
5746 return 1;
5748 return 0;
5751 //returns true if the feat exists
5752 bool Actor::HasFeat(unsigned int featindex) const
5754 if (featindex>=MAX_FEATS) return false;
5755 unsigned int pos = IE_FEATS1+(featindex>>5);
5756 unsigned int bit = 1<<(featindex&31);
5757 if (Modified[pos]&bit) return true;
5758 return false;
5761 ieDword Actor::ImmuneToProjectile(ieDword projectile) const
5763 int idx;
5765 idx = projectile/32;
5766 if (idx>ProjectileSize) {
5767 return 0;
5769 return projectileImmunity[idx]&(1<<(projectile&31));
5772 void Actor::AddProjectileImmunity(ieDword projectile)
5774 projectileImmunity[projectile/32]|=1<<(projectile&31);
5777 //2nd edition rules
5778 void Actor::CreateDerivedStatsBG()
5780 int turnundeadlevel = 0;
5781 int classid = BaseStats[IE_CLASS];
5783 //this works only for PC classes
5784 if (classid>=CLASS_PCCUTOFF) return;
5786 //recalculate all level based changes
5787 pcf_level(this,0,0);
5789 //even though the original didn't allow a cleric/paladin dual or multiclass
5790 //we shouldn't restrict the possibility by using "else if" here
5791 if (isclass[ISCLERIC]&(1<<classid)) {
5792 turnundeadlevel += GetClericLevel()+1-turnlevels[classid];
5793 if (turnundeadlevel<0) turnundeadlevel=0;
5795 if (isclass[ISPALADIN]&(1<<classid)) {
5796 turnundeadlevel += GetPaladinLevel()+1-turnlevels[classid];
5797 if (turnundeadlevel<0) turnundeadlevel=0;
5800 // barbarian immunity to backstab was hardcoded
5801 if (GetBarbarianLevel()) {
5802 BaseStats[IE_DISABLEBACKSTAB] = 1;
5805 ieDword backstabdamagemultiplier=GetThiefLevel();
5806 if (backstabdamagemultiplier) {
5807 // HACK: swashbucklers can't backstab
5808 if ((BaseStats[IE_KIT]&0xfff) == 12) {
5809 backstabdamagemultiplier = 1;
5810 } else {
5811 AutoTable tm("backstab");
5812 //fallback to a general algorithm (bg2 backstab.2da version) if we can't find backstab.2da
5813 //TODO: AP_SPCL332 (increase backstab by one) seems to not be effecting this at all
5814 //for assassins perhaps the effect is being called prior to this, and this overwrites it;
5815 //stalkers work correctly, which is even more odd, considering as they use the same
5816 //effect and backstabmultiplier would be 0 for them
5817 if (tm) {
5818 ieDword cols = tm->GetColumnCount();
5819 if (backstabdamagemultiplier >= cols) backstabdamagemultiplier = cols;
5820 backstabdamagemultiplier = atoi(tm->QueryField(0, backstabdamagemultiplier));
5821 } else {
5822 backstabdamagemultiplier = (backstabdamagemultiplier+7)/4;
5824 printf("\n");
5825 if (backstabdamagemultiplier>7) backstabdamagemultiplier=7;
5829 // monk's level dictated ac and ac vs missiles bonus
5830 // attacks per round bonus will be handled elsewhere, since it only applies to fist apr
5831 if (isclass[ISMONK]&(1<<classid)) {
5832 unsigned int level = GetMonkLevel()-1;
5833 BaseStats[IE_ARMORCLASS] = DEFAULTAC - monkbon[1][level];
5834 BaseStats[IE_ACMISSILEMOD] = - monkbon[2][level];
5837 BaseStats[IE_TURNUNDEADLEVEL]=turnundeadlevel;
5838 BaseStats[IE_BACKSTABDAMAGEMULTIPLIER]=backstabdamagemultiplier;
5839 BaseStats[IE_LAYONHANDSAMOUNT]=GetPaladinLevel()*2;
5842 //3rd edition rules
5843 void Actor::CreateDerivedStatsIWD2()
5845 int i;
5846 int turnundeadlevel = 0;
5848 ieDword backstabdamagemultiplier=GetThiefLevel();
5849 if (backstabdamagemultiplier) {
5850 AutoTable tm("backstab");
5851 if (tm) {
5852 ieDword cols = tm->GetColumnCount();
5853 if (backstabdamagemultiplier >= cols) backstabdamagemultiplier = cols;
5854 backstabdamagemultiplier = atoi(tm->QueryField(0, backstabdamagemultiplier));
5855 } else {
5856 backstabdamagemultiplier = (BaseStats[IE_LEVELTHIEF]+1)/2;
5858 printf("\n");
5859 if (backstabdamagemultiplier>7) backstabdamagemultiplier=7;
5862 int layonhandsamount = (int) BaseStats[IE_LEVELPALADIN];
5863 if (layonhandsamount) {
5864 layonhandsamount *= BaseStats[IE_CHR]/2-5;
5865 if (layonhandsamount<1) layonhandsamount = 1;
5868 for (i=0;i<11;i++) {
5869 int tmp;
5871 if (turnlevels[i+1]) {
5872 tmp = BaseStats[IE_LEVELBARBARIAN+i]+1-turnlevels[i+1];
5873 if (tmp<0) tmp=0;
5874 if (tmp>turnundeadlevel) turnundeadlevel=tmp;
5877 BaseStats[IE_TURNUNDEADLEVEL]=turnundeadlevel;
5878 BaseStats[IE_BACKSTABDAMAGEMULTIPLIER]=backstabdamagemultiplier;
5879 BaseStats[IE_LAYONHANDSAMOUNT]=(ieDword) layonhandsamount;
5882 //set up stuff here, like attack number, turn undead level
5883 //and similar derived stats that change with level
5884 void Actor::CreateDerivedStats()
5886 //we have to calculate multiclass for further code
5887 AutoTable tm("classes");
5888 if (tm) {
5889 // currently we need only the MULTI value
5890 char tmpmulti[8];
5891 long tmp;
5892 strcpy(tmpmulti, tm->QueryField(tm->FindTableValue(5, BaseStats[IE_CLASS]), 4));
5893 if (!valid_number(tmpmulti, tmp))
5894 multiclass = 0;
5895 else
5896 multiclass = (ieDword)tmp;
5899 if (core->HasFeature(GF_3ED_RULES)) {
5900 CreateDerivedStatsIWD2();
5901 } else {
5902 CreateDerivedStatsBG();
5905 /* Checks if the actor is multiclassed (the MULTI column is positive) */
5906 bool Actor::IsMultiClassed() const
5908 return (multiclass > 0);
5911 /* Checks if the actor is dualclassed */
5912 bool Actor::IsDualClassed() const
5914 return (Modified[IE_MC_FLAGS] & MC_WAS_ANY) > 0;
5917 Actor *Actor::CopySelf() const
5919 Actor *newActor = new Actor();
5921 newActor->SetName(GetName(0),0);
5922 newActor->SetName(GetName(1),1);
5923 memcpy(newActor->BaseStats, BaseStats, sizeof(BaseStats) );
5924 // illusions aren't worth any xp
5925 newActor->BaseStats[IE_XPVALUE] = 0;
5927 //IF_INITIALIZED shouldn't be set here, yet
5928 newActor->SetMCFlag(MC_EXPORTABLE, BM_NAND);
5930 //the creature importer does this too
5931 memcpy(newActor->Modified,newActor->BaseStats, sizeof(Modified) );
5933 //these need to be called too to have a valid inventory
5934 newActor->inventory.SetSlotCount(inventory.GetSlotCount());
5935 newActor->CreateDerivedStats();
5937 //copy the running effects
5938 EffectQueue *newFXQueue = fxqueue.CopySelf();
5940 area->AddActor(newActor);
5941 newActor->SetPosition( Pos, CC_CHECK_IMPASSABLE, 0 );
5942 newActor->SetOrientation(GetOrientation(),0);
5943 newActor->SetStance( IE_ANI_READY );
5945 //and apply them
5946 newActor->RefreshEffects(newFXQueue);
5947 return newActor;
5950 ieDword Actor::GetClassLevel(const ieDword id) const
5952 if (id>=ISCLASSES)
5953 return 0;
5955 //return iwd2 value if appropriate
5956 if (version==22)
5957 return BaseStats[levelslotsiwd2[id]];
5959 //houston, we gots a problem!
5960 if (!levelslots || !dualswap)
5961 return 0;
5963 //only works with PC's
5964 ieDword classid = BaseStats[IE_CLASS]-1;
5965 if (classid>=(ieDword)classcount || !levelslots[classid])
5966 return 0;
5968 //handle barbarians specially, since they're kits and not in levelslots
5969 if (id == ISBARBARIAN && levelslots[classid][ISFIGHTER] && GetKitIndex(BaseStats[IE_KIT]) == 31) {
5970 return BaseStats[IE_LEVEL];
5973 //get the levelid (IE_LEVEL,*2,*3)
5974 ieDword levelid = levelslots[classid][id];
5975 if (!levelid)
5976 return 0;
5978 //do dual-swap
5979 if (IsDualClassed()) {
5980 //if the old class is inactive, and it is the class
5981 //being searched for, return 0
5982 if (IsDualInactive() && ((Modified[IE_MC_FLAGS]&MC_WAS_ANY)==(ieDword)mcwasflags[id]))
5983 return 0;
5985 return BaseStats[levelid];
5988 bool Actor::IsDualInactive() const
5990 if (!IsDualClassed()) return 0;
5992 //we assume the old class is in IE_LEVEL2, unless swapped
5993 ieDword oldlevel = IsDualSwap() ? BaseStats[IE_LEVEL] : BaseStats[IE_LEVEL2];
5995 //since GetXPLevel returns the average of the 2 levels, oldclasslevel will
5996 //only be less than GetXPLevel when the new class surpasses it
5997 return oldlevel>=GetXPLevel(false);
6000 bool Actor::IsDualSwap() const
6002 //the dualswap[class-1] holds the info
6003 if (!IsDualClassed()) return false;
6004 ieDword tmpclass = BaseStats[IE_CLASS]-1;
6005 if (tmpclass>=(ieDword)classcount) return false;
6006 return (ieDword)dualswap[tmpclass]==(Modified[IE_MC_FLAGS]&MC_WAS_ANY);
6009 ieDword Actor::GetWarriorLevel() const
6011 if (!IsWarrior()) return 0;
6013 ieDword warriorlevels[4] = {
6014 GetBarbarianLevel(),
6015 GetFighterLevel(),
6016 GetPaladinLevel(),
6017 GetRangerLevel()
6020 ieDword highest = 0;
6021 for (int i=0; i<4; i++) {
6022 if (warriorlevels[i] > highest) {
6023 highest = warriorlevels[i];
6027 return highest;
6030 bool Actor::BlocksSearchMap() const
6032 return Modified[IE_DONOTJUMP] < 2;
6035 //return true if the actor doesn't want to use an entrance
6036 bool Actor::CannotPassEntrance() const
6038 if (InternalFlags&IF_USEEXIT) {
6039 return false;
6041 return true;
6044 void Actor::UseExit(int flag) {
6045 if (flag) {
6046 InternalFlags|=IF_USEEXIT;
6047 } else {
6048 InternalFlags&=~IF_USEEXIT;
6052 // luck increases the minimum roll per dice, but only up to the number of dice sides;
6053 // luck does not affect critical hit chances:
6054 // if critical is set, it will return 1/sides on a critical, otherwise it can never
6055 // return a critical miss when luck is positive and can return a false critical hit
6056 int Actor::LuckyRoll(int dice, int size, int add, bool critical, bool only_damage, Actor* opponent) const
6058 assert(this != opponent);
6060 ieDword stat;
6061 if (only_damage) {
6062 stat = IE_DAMAGELUCK;
6063 } else {
6064 stat = IE_LUCK;
6067 int luck = (signed) GetStat(stat);
6068 if (opponent) luck -= (signed) opponent->GetStat(stat);
6069 if (dice < 1 || size < 1) {
6070 return add + luck;
6073 if (dice > 100) {
6074 int bonus;
6075 if (abs(luck) > size) {
6076 bonus = luck/abs(luck) * size;
6077 } else {
6078 bonus = luck;
6080 int roll = core->Roll(1, dice*size, 0);
6081 if (critical && (roll == 1 || roll == size)) {
6082 return roll;
6083 } else {
6084 return add + dice * (size + bonus) / 2;
6088 int roll, result = 0, misses = 0, hits = 0;
6089 for (int i = 0; i < dice; i++) {
6090 roll = core->Roll(1, size, 0);
6091 if (roll == 1) {
6092 misses++;
6093 } else if (roll == size) {
6094 hits++;
6096 roll += luck;
6097 if (roll > size) {
6098 roll = size;
6099 } else if (roll < 1) {
6100 roll = 1;
6102 result += roll;
6105 // ensure we can still return a critical failure/success
6106 if (critical && dice == misses) return 1;
6107 if (critical && dice == hits) return size;
6109 return result + add;
6112 static EffectRef fx_remove_invisible_state_ref={"ForceVisible",NULL,-1};
6114 // removes the (normal) invisibility state
6115 void Actor::CureInvisibility()
6117 if (Modified[IE_STATE_ID] & (ieDword) (STATE_INVISIBLE)) {
6118 //SetBaseBit(IE_STATE_ID, STATE_INVISIBLE, false);
6119 //fxqueue.RemoveAllEffectsWithParam(fx_set_invisible_state_ref, 0);
6121 //this is much closer to what the original engine does here
6122 Effect *newfx;
6124 newfx = EffectQueue::CreateEffect(fx_remove_invisible_state_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
6125 core->ApplyEffect(newfx, this, this);
6127 delete newfx;
6129 //not sure, but better than nothing
6130 if (! (Modified[IE_STATE_ID]& STATE_INVISIBLE)) {
6131 InternalFlags|=IF_BECAMEVISIBLE;
6136 static EffectRef fx_remove_sanctuary_ref={"Cure:Sanctuary",NULL,-1};
6138 // removes the sanctuary effect
6139 void Actor::CureSanctuary()
6141 Effect *newfx;
6143 newfx = EffectQueue::CreateEffect(fx_remove_sanctuary_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
6144 core->ApplyEffect(newfx, this, this);
6146 delete newfx;
6149 void Actor::ResetState()
6151 CureInvisibility();
6152 CureSanctuary();
6153 SetModal(MS_NONE);
6156 // doesn't check the range, but only that the azimuth and the target
6157 // orientation match with a +/-2 allowed difference
6158 bool Actor::IsBehind(Actor* target)
6160 unsigned char tar_orient = target->GetOrientation();
6161 // computed, since we don't care where we face
6162 unsigned char my_orient = GetOrient(target->Pos, Pos);
6164 signed char diff;
6165 for (int i=-2; i <= 2; i++) {
6166 diff = my_orient+i;
6167 if (diff >= MAX_ORIENT) diff -= MAX_ORIENT;
6168 if (diff <= -1) diff += MAX_ORIENT;
6169 if (diff == (signed)tar_orient) return true;
6171 return false;
6174 // checks all the actor's stats to see if the target is her racial enemy
6175 bool Actor::IsRacialEnemy(Actor* target)
6177 if (Modified[IE_HATEDRACE] == target->Modified[IE_RACE]) {
6178 return true;
6179 } else if (core->HasFeature(GF_3ED_RULES)) {
6180 // iwd2 supports multiple racial enemies gained through level progression
6181 for (unsigned int i=0; i<7; i++) {
6182 if (Modified[IE_HATEDRACE2+i] == target->Modified[IE_RACE]) {
6183 return true;
6187 return false;
6190 bool Actor::ModalSpellSkillCheck() {
6191 switch(ModalState) {
6192 case MS_BATTLESONG:
6193 case MS_DETECTTRAPS:
6194 case MS_TURNUNDEAD:
6195 return true;
6196 case MS_STEALTH:
6197 return TryToHide();
6198 case MS_NONE:
6199 default:
6200 return false;
6204 static EffectRef fx_disable_button_ref={ "DisableButton", NULL, -1 };
6206 inline void HideFailed(Actor* actor)
6208 Effect *newfx;
6209 newfx = EffectQueue::CreateEffect(fx_disable_button_ref, 0, ACT_STEALTH, FX_DURATION_INSTANT_LIMITED);
6210 newfx->Duration = ROUND_SECONDS; // 90 ticks, 1 round
6211 core->ApplyEffect(newfx, actor, actor);
6212 delete newfx;
6215 bool Actor::TryToHide() {
6216 ieDword roll = LuckyRoll(1, 100, 0);
6217 if (roll == 1) {
6218 HideFailed(this);
6219 return false;
6222 // check for disabled dualclassed thieves (not sure if we need it)
6224 if (Modified[IE_DISABLEDBUTTON] & (1<<ACT_STEALTH)) {
6225 HideFailed(this);
6226 return false;
6229 // check if the pc is in combat (seen / heard)
6230 Game *game = core->GetGame();
6231 if (game->PCInCombat(this)) {
6232 HideFailed(this);
6233 return false;
6236 ieDword skill;
6237 if (core->HasFeature(GF_HAS_HIDE_IN_SHADOWS)) {
6238 skill = (GetStat(IE_HIDEINSHADOWS) + GetStat(IE_STEALTH))/2;
6239 } else {
6240 skill = GetStat(IE_STEALTH);
6243 // check how bright our spot is
6244 ieDword lightness = game->GetCurrentArea()->GetLightLevel(Pos);
6245 // seems to be the color overlay at midnight; lightness of a point with rgb (200, 100, 100)
6246 // TODO: but our NightTint computes to a higher value, which one is bad?
6247 ieDword ref_lightness = 43;
6248 ieDword light_diff = int((lightness - ref_lightness) * 100 / (100 - ref_lightness)) / 2;
6249 ieDword chance = (100 - light_diff) * skill/100;
6251 if (roll > chance) {
6252 HideFailed(this);
6253 return false;
6255 return true;
6258 // only works with masks; use direct comparison for specific alignment checks
6259 bool Actor::MatchesAlignmentMask(ieDword mask)
6261 ieDword stat = Modified[IE_ALIGNMENT];
6263 switch (mask) {
6264 case AL_EVIL:
6265 return stat == AL_LAWFUL_EVIL || stat == AL_NEUTRAL_EVIL || stat == AL_CHAOTIC_EVIL;
6266 case AL_GE_NEUTRAL:
6267 return stat == AL_NEUTRAL_GOOD || stat == AL_TRUE_NEUTRAL || stat == AL_NEUTRAL_EVIL;
6268 case AL_GOOD:
6269 return stat == AL_LAWFUL_GOOD || stat == AL_NEUTRAL_GOOD || stat == AL_CHAOTIC_GOOD;
6270 case AL_CHAOTIC:
6271 return stat == AL_CHAOTIC_GOOD || stat == AL_CHAOTIC_NEUTRAL || stat == AL_CHAOTIC_EVIL;
6272 case AL_LC_NEUTRAL:
6273 return stat == AL_NEUTRAL_GOOD || stat == AL_TRUE_NEUTRAL || stat == AL_NEUTRAL_EVIL;
6274 case AL_LAWFUL:
6275 return stat == AL_LAWFUL_GOOD || stat == AL_LAWFUL_NEUTRAL || stat == AL_LAWFUL_EVIL;
6276 default:
6277 printf("Bad mask parameter (%d) used with Actor::MatchesAlignmentMask!\n", mask);
6278 assert(false);
6279 return false;