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