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