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