1 /* GemRB - Infinity Engine Emulator
2  * Copyright (C) 2003 The GemRB Project
3  *
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.
8 
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.
13 
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.
17  *
18  *
19  */
20 
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 Scriptable.cpp
24 
25 #include "Scriptable/Actor.h"
26 
27 #include "ie_feats.h"
28 #include "overlays.h"
29 #include "strrefs.h"
30 #include "opcode_params.h"
31 #include "voodooconst.h"
32 
33 #include "Bitmap.h"
34 #include "DataFileMgr.h"
35 #include "DialogHandler.h" // checking for dialog
36 #include "Game.h"
37 #include "GlobalTimer.h"
38 #include "DisplayMessage.h"
39 #include "GameData.h"
40 #include "Image.h"
41 #include "ImageMgr.h"
42 #include "Item.h"
43 #include "PolymorphCache.h" // stupid polymorph cache hack
44 #include "Projectile.h"
45 #include "ProjectileServer.h"
46 #include "ScriptEngine.h"
47 #include "Spell.h"
48 #include "Sprite2D.h"
49 #include "TableMgr.h"
50 #include "damages.h"
51 #include "GameScript/GSUtils.h" //needed for DisplayStringCore
52 #include "GameScript/GameScript.h"
53 #include "GUI/GameControl.h"
54 #include "RNG.h"
55 #include "Scriptable/InfoPoint.h"
56 #include "ScriptedAnimation.h"
57 #include "System/FileFilters.h"
58 #include "System/StringBuffer.h"
59 #include "StringMgr.h"
60 
61 #include <cmath>
62 
63 namespace GemRB {
64 
65 //configurable?
66 ieDword ref_lightness = 43;
67 
68 static int sharexp = SX_DIVIDE|SX_COMBAT;
69 static int classcount = -1;
70 static int extraslots = -1;
71 static char **clericspelltables = NULL;
72 static char **druidspelltables = NULL;
73 static char **wizardspelltables = NULL;
74 static int *turnlevels = NULL;
75 static int *booktypes = NULL;
76 static int *xpbonus = NULL;
77 static int *xpcap = NULL;
78 static int *defaultprof = NULL;
79 static int *castingstat = NULL;
80 static int *iwd2spltypes = NULL;
81 static int xpbonustypes = -1;
82 static int xpbonuslevels = -1;
83 static int **levelslots = NULL;
84 static int *dualswap = NULL;
85 static int *multi = NULL;
86 static int *maxLevelForHpRoll = NULL;
87 static std::map<int, std::vector<int> > skillstats;
88 static std::map<int, int> stat2skill;
89 static int **afcomments = NULL;
90 static int afcount = -1;
91 static ieVariable CounterNames[4]={"GOOD","LAW","LADY","MURDER"};
92 //I keep the zero index the same as core rules (default setting)
93 static int dmgadjustments[6]={0, -50, -25, 0, 50, 100}; //default, easy, normal, core rules, hard, nightmare
94 //XP adjustments on easy setting (need research on the amount)
95 //Seems like bg1 halves xp, bg2 doesn't have any impact
96 static int xpadjustments[6]={0, 0, 0, 0, 0, 0};
97 static int luckadjustments[6]={0, 0, 0, 0, 0, 0};
98 
99 static int FistRows = -1;
100 static int *wmlevels[20];
101 typedef ieResRef FistResType[MAX_LEVEL+1];
102 
103 static FistResType *fistres = NULL;
104 static int *fistresclass = NULL;
105 static ieResRef DefaultFist = {"FIST"};
106 
107 //verbal constant specific data
108 static int VCMap[VCONST_COUNT];
109 static ieDword sel_snd_freq = 0;
110 static ieDword cmd_snd_freq = 0;
111 static ieDword crit_hit_scr_shake = 1;
112 static ieDword bored_time = 3000;
113 static ieDword footsteps = 1;
114 static ieDword war_cries = 1;
115 static ieDword GameDifficulty = DIFF_CORE;
116 static ieDword NoExtraDifficultyDmg = 0;
117 //the chance to issue one of the rare select verbal constants
118 #define RARE_SELECT_CHANCE 5
119 //these are the max number of select sounds -- the size of the pool to choose from
120 static int NUM_RARE_SELECT_SOUNDS = 2; //in bg and pst it is actually 4
121 #define NUM_SELECT_SOUNDS 6 //in bg1 this is 4 but doesn't need to be checked
122 #define NUM_MC_SELECT_SOUNDS 4 //number of main charater select sounds
123 
124 #define MAX_FEATV 4294967295U // 1<<32-1 (used for the triple-stat feat handling)
125 
126 //item usability array
127 struct ItemUseType {
128 	ieResRef table; //which table contains the stat usability flags
129 	ieByte stat;	//which actor stat we talk about
130 	ieByte mcol;	//which column should be matched against the stat
131 	ieByte vcol;	//which column has the bit value for it
132 	ieByte which;	//which item dword should be used (1 = kit)
133 };
134 
135 static ieResRef featspells[ES_COUNT];
136 static ItemUseType *itemuse = NULL;
137 static int usecount = -1;
138 static bool pstflags = false;
139 static bool nocreate = false;
140 static bool third = false;
141 static bool raresnd = false;
142 static bool iwd2class = false;
143 //used in many places, but different in engines
144 static ieDword state_invisible = STATE_INVISIBLE;
145 static AutoTable extspeed;
146 static AutoTable wspecial;
147 
148 //item animation override array
149 struct ItemAnimType {
150 	ieResRef itemname;
151 	ieByte animation;
152 };
153 
154 static ItemAnimType *itemanim = NULL;
155 static int animcount = -1;
156 
157 static int fiststat = IE_CLASS;
158 
159 //conversion for 3rd ed
160 static int isclass[ISCLASSES]={0,0,0,0,0,0,0,0,0,0,0,0,0};
161 
162 static const int mcwasflags[ISCLASSES] = {
163 	MC_WAS_FIGHTER, MC_WAS_MAGE, MC_WAS_THIEF, 0, 0, MC_WAS_CLERIC,
164 	MC_WAS_DRUID, 0, 0, MC_WAS_RANGER, 0, 0, 0};
165 static const char *isclassnames[ISCLASSES] = {
166 	"FIGHTER", "MAGE", "THIEF", "BARBARIAN", "BARD", "CLERIC",
167 	"DRUID", "MONK", "PALADIN", "RANGER", "SORCERER", "CLASS12", "CLASS13" };
168 static const int levelslotsiwd2[ISCLASSES]={IE_LEVELFIGHTER, IE_LEVELMAGE, IE_LEVELTHIEF,
169 	IE_LEVELBARBARIAN, IE_LEVELBARD, IE_LEVELCLERIC, IE_LEVELDRUID, IE_LEVELMONK,
170 	IE_LEVELPALADIN, IE_LEVELRANGER, IE_LEVELSORCERER, IE_LEVELCLASS12, IE_LEVELCLASS13};
171 
172 #define BGCLASSCNT 23
173 //fighter is the default level here
174 //fixme, make this externalized
175 //this map could probably be auto-generated BG2 class ID -> ISCLASS
176 static const int levelslotsbg[BGCLASSCNT]={ISFIGHTER, ISMAGE, ISFIGHTER, ISCLERIC, ISTHIEF,
177 	ISBARD, ISPALADIN, 0, 0, 0, 0, ISDRUID, ISRANGER, 0,0,0,0,0,0,ISSORCERER, ISMONK,
178 	ISCLASS12, ISCLASS13};
179 // map isClass -> (IWD2) class ID
180 static unsigned int classesiwd2[ISCLASSES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
181 
182 // class -> kits map
183 struct ClassKits {
184 	std::vector<int> indices;
185 	std::vector<ieDword> ids;
186 	std::vector<char*> clabs;
187 	std::vector<char*> kitNames;
188 	char *clab;
189 	char *className;
190 };
191 static std::map<int, ClassKits> class2kits;
192 
193 //this map could probably be auto-generated (isClass -> IWD2 book ID)
194 static const int booksiwd2[ISCLASSES]={-1, IE_IWD2_SPELL_WIZARD, -1, -1,
195  IE_IWD2_SPELL_BARD, IE_IWD2_SPELL_CLERIC, IE_IWD2_SPELL_DRUID, -1,
196  IE_IWD2_SPELL_PALADIN, IE_IWD2_SPELL_RANGER, IE_IWD2_SPELL_SORCERER, -1, -1};
197 
198 //stat values are 0-255, so a byte is enough
199 static ieByte featstats[MAX_FEATS]={0
200 };
201 static ieByte featmax[MAX_FEATS]={0
202 };
203 
204 //holds the weapon style bonuses
205 #define STYLE_MAX 3
206 static int **wsdualwield = NULL;
207 static int **wstwohanded = NULL;
208 static int **wsswordshield = NULL;
209 static int **wssingle = NULL;
210 
211 //unhardcoded monk bonuses
212 static int **monkbon = NULL;
213 static unsigned int monkbon_cols = 0;
214 static unsigned int monkbon_rows = 0;
215 
216 // reputation modifiers
217 #define CLASS_PCCUTOFF 32
218 #define CLASS_INNOCENT 155
219 #define CLASS_FLAMINGFIST 156
220 
221 static ActionButtonRow *GUIBTDefaults = NULL; //qslots row count
222 static ActionButtonRow2 *OtherGUIButtons = NULL;
223 ActionButtonRow DefaultButtons = {ACT_TALK, ACT_WEAPON1, ACT_WEAPON2,
224 	ACT_QSPELL1, ACT_QSPELL2, ACT_QSPELL3, ACT_CAST, ACT_USE, ACT_QSLOT1, ACT_QSLOT2,
225 	ACT_QSLOT3, ACT_INNATE};
226 static int QslotTranslation = false;
227 static int DeathOnZeroStat = true;
228 static int IWDSound = false;
229 static ieDword TranslucentShadows = 0;
230 static int ProjectileSize = 0; //the size of the projectile immunity bitfield (dwords)
231 static unsigned int SpellStatesSize = 0; //and this is for the spellStates bitfield
232 
233 static const char iwd2gemrb[32] = {
234 	0,0,20,2,22,25,0,14,
235 	15,23,13,0,1,24,8,21,
236 	0,0,0,0,0,0,0,0,
237 	0,0,0,0,0,0,0,0
238 };
239 static const char gemrb2iwd[32] = {
240 	11,12,3,71,72,73,0,0, //0
241 	14,80,83,82,81,10,7,8, //8
242 	0,0,0,0,2,15,4,9, //16
243 	13,5,0,0,0,0,0,0 //24
244 };
245 
246 //letters for char sound resolution bg1/bg2
247 static char csound[VCONST_COUNT];
248 
249 static void InitActorTables();
250 
251 #define DAMAGE_LEVELS 19
252 #define ATTACKROLL    20
253 #define SAVEROLL      20
254 #define DEFAULTAC     10
255 
256 //TODO: externalise
257 #define TURN_PANIC_LVL_MOD 3
258 #define TURN_DEATH_LVL_MOD 7
259 #define REPUTATION_FALL 60
260 
261 static ieResRef d_main[DAMAGE_LEVELS] = {
262 	//slot 0 is not used in the original engine
263 	"BLOODCR","BLOODS","BLOODM","BLOODL", //blood
264 	"SPFIRIMP","SPFIRIMP","SPFIRIMP",     //fire
265 	"SPSHKIMP","SPSHKIMP","SPSHKIMP",     //spark
266 	"SPFIRIMP","SPFIRIMP","SPFIRIMP",     //ice
267 	"SHACID","SHACID","SHACID",           //acid
268 	"SPDUSTY2","SPDUSTY2","SPDUSTY2"      //disintegrate
269 };
270 static ieResRef d_splash[DAMAGE_LEVELS] = {
271 	"","","","",
272 	"SPBURN","SPBURN","SPBURN", //flames
273 	"SPSPARKS","SPSPARKS","SPSPARKS", //sparks
274 	"","","",
275 	"","","",
276 	"","",""
277 };
278 
279 #define BLOOD_GRADIENT 19
280 #define FIRE_GRADIENT 19
281 #define ICE_GRADIENT 71
282 #define STONE_GRADIENT 93
283 
284 static int d_gradient[DAMAGE_LEVELS] = {
285 	BLOOD_GRADIENT,BLOOD_GRADIENT,BLOOD_GRADIENT,BLOOD_GRADIENT,
286 	FIRE_GRADIENT,FIRE_GRADIENT,FIRE_GRADIENT,
287 	-1,-1,-1,
288 	ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,
289 	-1,-1,-1,
290 	-1,-1,-1
291 };
292 
293 static ResRef hc_overlays[OVERLAY_COUNT]={"SANCTRY","SPENTACI","SPMAGGLO","SPSHIELD",
294 "GREASED","WEBENTD","MINORGLB","","","","","","","","","","","","","","",
295 "","","","SPTURNI2","SPTURNI","","","","","",""};
296 static ieDword hc_locations = 0;
297 static int hc_flags[OVERLAY_COUNT];
298 #define HC_INVISIBLE 1
299 
300 static int *mxsplwis = NULL;
301 static int spllevels;
302 
303 // thieving skill dexterity and race boni vectors
304 std::vector<std::vector<int> > skilldex;
305 std::vector<std::vector<int> > skillrac;
306 
307 // subset of races.2da
308 std::map<unsigned int, int> favoredMap;
309 std::map<unsigned int, const char *> raceID2Name;
310 
311 // iwd2 class to-hit and apr tables read into a single object
312 std::map<char *, std::vector<BABTable> > IWD2HitTable;
313 typedef std::map<char *, std::vector<BABTable> >::iterator IWD2HitTableIter;
314 std::map<int, char *> BABClassMap; // maps classis (not id!) to the BAB table
315 
316 std::vector<ModalStatesStruct> ModalStates;
317 std::map<int, int> numWeaponSlots;
318 
319 //for every game except IWD2 we need to reverse TOHIT
320 static int ReverseToHit=true;
321 static int CheckAbilities=false;
322 
323 // from FXOpcodes
324 #define PI_DRUNK   5
325 #define PI_FATIGUE 39
326 #define PI_PROJIMAGE  77
327 
328 static EffectRef fx_set_haste_state_ref = { "State:Hasted", -1 };
329 static EffectRef fx_set_slow_state_ref = { "State:Slowed", -1 };
330 static EffectRef fx_sleep_ref = { "State:Helpless", -1 };
331 static EffectRef fx_cleave_ref = { "Cleave", -1 };
332 static EffectRef fx_tohit_vs_creature_ref = { "ToHitVsCreature", -1 };
333 static EffectRef fx_damage_vs_creature_ref = { "DamageVsCreature", -1 };
334 static EffectRef fx_mirrorimage_ref = { "MirrorImageModifier", -1 };
335 static EffectRef fx_set_charmed_state_ref = { "State:Charmed", -1 };
336 static EffectRef fx_cure_sleep_ref = { "Cure:Sleep", -1 };
337 static EffectRef fx_damage_bonus_modifier_ref = { "DamageBonusModifier2", -1 };
338 static EffectRef fx_display_portrait_icon_ref = { "Icon:Display", -1 };
339 //bg2 and iwd1
340 static EffectRef control_creature_ref = { "ControlCreature", -1 };
341 //iwd1 and iwd2
342 static EffectRef fx_eye_sword_ref = { "EyeOfTheSword", -1 };
343 static EffectRef fx_eye_mage_ref = { "EyeOfTheMage", -1 };
344 //iwd2
345 static EffectRef control_undead_ref = { "ControlUndead2", -1 };
346 static EffectRef fx_cure_poisoned_state_ref = { "Cure:Poison", -1 };
347 static EffectRef fx_cure_hold_state_ref = { "Cure:Hold", -1 };
348 static EffectRef fx_cure_stun_state_ref = { "Cure:Stun", -1 };
349 static EffectRef fx_remove_portrait_icon_ref = { "Icon:Remove", -1 };
350 static EffectRef fx_unpause_caster_ref = { "Cure:CasterHold", -1 };
351 static EffectRef fx_ac_vs_creature_type_ref = { "ACVsCreatureType", -1 };
352 static EffectRef fx_puppetmarker_ref = { "PuppetMarker", -1 };
353 static EffectRef fx_stoneskin_ref = { "StoneSkinModifier", -1 };
354 static EffectRef fx_stoneskin2_ref = { "StoneSkin2Modifier", -1 };
355 static EffectRef fx_aegis_ref = { "Aegis", -1 };
356 static EffectRef fx_cloak_ref = { "Overlay", -1 };
357 static EffectRef fx_damage_ref = { "Damage", -1 };
358 static EffectRef fx_melee_ref = { "SetMeleeEffect", -1 };
359 static EffectRef fx_ranged_ref = { "SetRangedEffect", -1 };
360 static EffectRef fx_cant_use_item_ref = { "CantUseItem", -1 };
361 static EffectRef fx_cant_use_item_type_ref = { "CantUseItemType", -1 };
362 static EffectRef fx_remove_invisible_state_ref = { "ForceVisible", -1 };
363 static EffectRef fx_remove_sanctuary_ref = { "Cure:Sanctuary", -1 };
364 static EffectRef fx_disable_button_ref = { "DisableButton", -1 };
365 static EffectRef fx_damage_reduction_ref = { "DamageReduction", -1 };
366 static EffectRef fx_missile_damage_reduction_ref = { "MissileDamageReduction", -1 };
367 static EffectRef fx_smite_evil_ref = { "SmiteEvil", -1 };
368 
369 //used by iwd2
370 static ieResRef resref_cripstr={"cripstr"};
371 static ieResRef resref_dirty={"dirty"};
372 static ieResRef resref_arterial={"artstr"};
373 
374 static const int weapon_damagetype[] = {DAMAGE_CRUSHING, DAMAGE_PIERCING,
375 	DAMAGE_CRUSHING, DAMAGE_SLASHING, DAMAGE_MISSILE, DAMAGE_STUNNING};
376 
377 //internal flags for calculating to hit
378 #define WEAPON_FIST        0
379 #define WEAPON_MELEE       1
380 #define WEAPON_RANGED      2
381 #define WEAPON_STYLEMASK   15
382 #define WEAPON_LEFTHAND    16
383 #define WEAPON_USESTRENGTH 32
384 #define WEAPON_USESTRENGTH_DMG 64
385 #define WEAPON_USESTRENGTH_HIT 128
386 #define WEAPON_FINESSE     256
387 #define WEAPON_BYPASS      0x10000
388 #define WEAPON_KEEN        0x20000
389 
390 static int avBase, avStance;
391 struct avType {
392 	ieResRef avresref;
393 	AutoTable avtable;
394 	int stat;
395 };
396 static avType *avPrefix;
397 static int avCount = -1;
398 
ReleaseMemoryActor()399 void ReleaseMemoryActor()
400 {
401 	if (mxsplwis) {
402 		//calloc'd x*y integer matrix
403 		free (mxsplwis);
404 		mxsplwis = NULL;
405 	}
406 
407 	if (fistres) {
408 		delete [] fistres;
409 		fistres = NULL;
410 		delete [] fistresclass;
411 		fistresclass = NULL;
412 	}
413 
414 	if (itemuse) {
415 		delete [] itemuse;
416 		itemuse = NULL;
417 	}
418 
419 	if (itemanim) {
420 		delete [] itemanim;
421 		itemanim = NULL;
422 	}
423 	FistRows = -1;
424 	extspeed.release();
425 	wspecial.release();
426 }
427 
Actor()428 Actor::Actor()
429 	: Movable( ST_ACTOR )
430 {
431 	int i;
432 
433 	for (i = 0; i < MAX_STATS; i++) {
434 		BaseStats[i] = 0;
435 		Modified[i] = 0;
436 	}
437 	PrevStats = NULL;
438 	SmallPortrait[0] = 0;
439 	LargePortrait[0] = 0;
440 
441 	anims = NULL;
442 	ShieldRef[0]=0;
443 	HelmetRef[0]=0;
444 	WeaponRef[0]=0;
445 
446 	memset(ShortName, 0, sizeof(ShortName));
447 	memset(LongName, 0, sizeof(LongName));
448 	LongStrRef = ieStrRef(-1);
449 	ShortStrRef = ieStrRef(-1);
450 
451 	playedCommandSound = false;
452 
453 	PCStats = NULL;
454 	LastDamage = 0;
455 	LastDamageType = 0;
456 	LastExit = 0;
457 	attackcount = 0;
458 	secondround = 0;
459 	//AttackStance = IE_ANI_ATTACK;
460 	attacksperround = 0;
461 	nextattack = 0;
462 	nextWalk = 0;
463 	lastattack = 0;
464 	InTrap = 0;
465 	ResetPathTries();
466 	TargetDoor = 0;
467 	attackProjectile = NULL;
468 	lastInit = 0;
469 	roundTime = 0;
470 	panicMode = PANIC_NONE;
471 	nextComment = 100 + RAND(0, 350); // 7-30s delay
472 	nextBored = 0;
473 	FatigueComplaintDelay = 0;
474 
475 	inventory.SetInventoryType(INVENTORY_CREATURE);
476 
477 	fxqueue.SetOwner( this );
478 	inventory.SetOwner( this );
479 	if (classcount<0) {
480 		//This block is executed only once, when the first actor is loaded
481 		InitActorTables();
482 
483 		TranslucentShadows = 0;
484 		core->GetDictionary()->Lookup("Translucent Shadows", TranslucentShadows);
485 		//get the needed size to store projectile immunity bitflags in Dwords
486 		ProjectileSize = (core->GetProjectileServer()->GetHighestProjectileNumber()+31)/32;
487 		//allowing 1024 bits (1024 projectiles ought to be enough for everybody)
488 		//the rest of the projectiles would still work, but couldn't be resisted
489 		if (ProjectileSize>32) {
490 			ProjectileSize=32;
491 		}
492 	}
493 	multiclass = 0;
494 	projectileImmunity = (ieDword *) calloc(ProjectileSize,sizeof(ieDword));
495 	AppearanceFlags = 0;
496 	SetDeathVar = IncKillCount = UnknownField = 0;
497 	memset( DeathCounters, 0, sizeof(DeathCounters) );
498 	InParty = 0;
499 	TalkCount = 0;
500 	InteractCount = 0; //numtimesinteracted depends on this
501 	appearance = 0xffffff; //might be important for created creatures
502 	RemovalTime = ~0;
503 	Spawned = false;
504 	version = 0;
505 	//these are used only in iwd2 so we have to default them
506 	for(i=0;i<7;i++) {
507 		BaseStats[IE_HATEDRACE2+i]=0xff;
508 	}
509 	//this one is saved only for PC's
510 	Modal.State = MS_NONE;
511 	//set it to a neutral value
512 	Modal.Spell[0] = '*';
513 	Modal.LingeringSpell[0] = '*';
514 	Modal.LastApplyTime = 0;
515 	Modal.LingeringCount = 0;
516 	Modal.FirstApply = 1;
517 	BackstabResRef[0] = '*';
518 	//this one is not saved
519 	GotLUFeedback = false;
520 	RollSaves();
521 	WMLevelMod = 0;
522 	TicksLastRested = LastFatigueCheck = 0;
523 	remainingTalkSoundTime = lastTalkTimeCheckAt = 0;
524 	WeaponType = AttackStance = 0;
525 	DifficultyMargin = disarmTrap = 0;
526 	spellStates = (ieDword *) calloc(SpellStatesSize, sizeof(ieDword));
527 	weapSlotCount = 4;
528 	// delay all maxhp checks until we completely load all effects
529 	checkHP = 2;
530 	checkHPTime = 0;
531 
532 	polymorphCache = NULL;
533 	memset(&wildSurgeMods, 0, sizeof(wildSurgeMods));
534 	AC.SetOwner(this);
535 	ToHit.SetOwner(this);
536 }
537 
~Actor(void)538 Actor::~Actor(void)
539 {
540 	delete anims;
541 	delete PCStats;
542 
543 	for (ScriptedAnimation* vvc : vfxQueue) {
544 		delete vvc;
545 	}
546 
547 	delete attackProjectile;
548 	delete polymorphCache;
549 
550 	free(projectileImmunity);
551 	free(spellStates);
552 }
553 
SetFistStat(ieDword stat)554 void Actor::SetFistStat(ieDword stat)
555 {
556 	fiststat = stat;
557 }
558 
SetDefaultActions(int qslot,ieByte slot1,ieByte slot2,ieByte slot3)559 void Actor::SetDefaultActions(int qslot, ieByte slot1, ieByte slot2, ieByte slot3)
560 {
561 	QslotTranslation=qslot;
562 	DefaultButtons[0]=slot1;
563 	DefaultButtons[1]=slot2;
564 	DefaultButtons[2]=slot3;
565 }
566 
SetName(const char * ptr,unsigned char type)567 void Actor::SetName(const char* ptr, unsigned char type)
568 {
569 	char* name = NULL;
570 	if (type == 1) {
571 		name = LongName;
572 	} else {
573 		name = ShortName;
574 	}
575 	// both LongName and ShortName are the same size...
576 	strncpy(name, ptr, sizeof(LongName) - 1);
577 	char* end = name + strlen(name) - 1;
578 	while (end > name && isspace(*end)) end--;
579 	*(end+1) = '\0'; // trim whitespace from the end
580 
581 	if (type == 0) {
582 		SetName(ptr, 1);
583 	}
584 }
585 
SetName(int strref,unsigned char type)586 void Actor::SetName(int strref, unsigned char type)
587 {
588 	char* name = NULL;
589 	if (type <= 1) {
590 		name = core->GetCString( strref );
591 		LongStrRef = strref;
592 		if (type == 0)
593 			ShortStrRef = strref;
594 	} else {
595 		name = core->GetCString( strref );
596 		ShortStrRef = strref;
597 	}
598 	SetName(name, type);
599 	free(name);
600 }
601 
SetAnimationID(unsigned int AnimID)602 void Actor::SetAnimationID(unsigned int AnimID)
603 {
604 	//if the palette is locked, then it will be transferred to the new animation
605 	PaletteHolder recover = nullptr;
606 	ieResRef paletteResRef;
607 
608 	if (anims) {
609 		if (anims->lockPalette) {
610 			recover = anims->PartPalettes[PAL_MAIN];
611 		}
612 		// Take ownership so the palette won't be deleted
613 		if (recover) {
614 			CopyResRef(paletteResRef, anims->PaletteResRef[PAL_MAIN]);
615 			if (recover->named) {
616 				recover = gamedata->GetPalette(paletteResRef);
617 			}
618 		}
619 		delete( anims );
620 	}
621 	//hacking PST no palette
622 	if (core->HasFeature(GF_ONE_BYTE_ANIMID) ) {
623 		if ((AnimID&0xf000)==0xe000) {
624 			if (BaseStats[IE_COLORCOUNT]) {
625 				Log(WARNING, "Actor", "Animation ID %x is supposed to be real colored (no recoloring), patched creature", AnimID);
626 			}
627 			BaseStats[IE_COLORCOUNT]=0;
628 		}
629 	}
630 	anims = new CharAnimations( AnimID&0xffff, BaseStats[IE_ARMOR_TYPE]);
631 	if(anims->ResRef[0] == 0) {
632 		delete anims;
633 		anims = NULL;
634 		Log(ERROR, "Actor", "Missing animation for %s", LongName);
635 		return;
636 	}
637 	anims->SetOffhandRef(ShieldRef);
638 	anims->SetHelmetRef(HelmetRef);
639 	anims->SetWeaponRef(WeaponRef);
640 
641 	//if we have a recovery palette, then set it back
642 	assert(anims->PartPalettes[PAL_MAIN] == 0);
643 	anims->PartPalettes[PAL_MAIN] = recover;
644 	if (recover) {
645 		anims->lockPalette = true;
646 		CopyResRef(anims->PaletteResRef[PAL_MAIN], paletteResRef);
647 	}
648 	//bird animations are not hindered by searchmap
649 	//only animations with a space of 0 in avatars.2da files use this feature
650 	if (anims->GetCircleSize() != 0) {
651 		BaseStats[IE_DONOTJUMP]=0;
652 	} else {
653 		BaseStats[IE_DONOTJUMP]=DNJ_BIRD;
654 	}
655 	SetCircleSize();
656 	anims->SetColors(BaseStats+IE_COLORS);
657 
658 	// PST and EE 2.0+ use an ini to define animation data, including walk and run speed
659 	// the rest had it hardcoded
660 	if (!core->HasFeature(GF_RESDATA_INI)) {
661 		// handle default speed and per-animation overrides
662 		int row = -1;
663 		if (extspeed.ok()) {
664 			char animHex[10];
665 			snprintf(animHex, 10, "0x%04X", AnimID);
666 			row = extspeed->FindTableValue((unsigned int) 0, animHex);
667 			if (row != -1) {
668 				int rate = atoi(extspeed->QueryField(row, 1));
669 				SetBase(IE_MOVEMENTRATE, rate);
670 			}
671 		} else {
672 			Log(MESSAGE, "Actor", "No moverate.2da found, using animation (0x%04X) for speed fallback!", AnimID);
673 		}
674 		if (row == -1) {
675 			Animation **anim = anims->GetAnimation(IE_ANI_WALK, 0);
676 			if (anim && anim[0]) {
677 				SetBase(IE_MOVEMENTRATE, anim[0]->GetFrameCount());
678 			} else {
679 				Log(WARNING, "Actor", "Unable to determine movement rate for animation 0x%04X!", AnimID);
680 			}
681 		}
682 	}
683 
684 	// set internal speed too, since we may need it in the same tick (eg. csgolem in the bg2 intro)
685 	SetSpeed(false);
686 }
687 
GetAnims() const688 CharAnimations* Actor::GetAnims() const
689 {
690 	return anims;
691 }
692 
693 /** Returns a Stat value (Base Value + Mod) */
GetStat(unsigned int StatIndex) const694 ieDword Actor::GetStat(unsigned int StatIndex) const
695 {
696 	if (StatIndex >= MAX_STATS) {
697 		return 0xdadadada;
698 	}
699 	return Modified[StatIndex];
700 }
701 
702 /** Always return a final stat value not partially calculated ones */
GetSafeStat(unsigned int StatIndex) const703 ieDword Actor::GetSafeStat(unsigned int StatIndex) const
704 {
705 	if (StatIndex >= MAX_STATS) {
706 		return 0xdadadada;
707 	}
708 	if (PrevStats) return PrevStats[StatIndex];
709 	return Modified[StatIndex];
710 }
711 
SetCircleSize()712 void Actor::SetCircleSize()
713 {
714 	Color color;
715 	int color_index;
716 
717 	if (!anims)
718 		return;
719 
720 	const GameControl *gc = core->GetGameControl();
721 	float oscillationFactor = 1.0f;
722 
723 	if (UnselectableTimer) {
724 		color = ColorMagenta;
725 		color_index = 4;
726 	} else if (Modified[IE_STATE_ID] & STATE_PANIC) {
727 		color = ColorYellow;
728 		color_index = 5;
729 	} else if (Modified[IE_CHECKFORBERSERK]) {
730 		color = ColorYellow;
731 		color_index = 5;
732 	} else if (gc && (((gc->GetDialogueFlags()&DF_IN_DIALOG) && gc->dialoghandler->IsTarget(this)) || remainingTalkSoundTime > 0)) {
733 		color = ColorWhite;
734 		color_index = 3; //?? made up
735 
736 		if (remainingTalkSoundTime > 0) {
737 			/**
738 			 * Approximation: pulsating at about 2Hz over a notable radius growth.
739 			 * Maybe check this relation for dragons and rats, too.
740 			 */
741 			oscillationFactor = 1.1f + std::sin(remainingTalkSoundTime * (4 * M_PI) / 1000) * 0.1f;
742 		}
743 	} else {
744 		switch (Modified[IE_EA]) {
745 			case EA_PC:
746 			case EA_FAMILIAR:
747 			case EA_ALLY:
748 			case EA_CONTROLLED:
749 			case EA_CHARMED:
750 			case EA_EVILBUTGREEN:
751 			case EA_GOODCUTOFF:
752 				color = ColorGreen;
753 				color_index = 0;
754 				break;
755 			case EA_EVILCUTOFF:
756 				color = ColorYellow;
757 				color_index = 5;
758 				break;
759 			case EA_ENEMY:
760 			case EA_GOODBUTRED:
761 			case EA_CHARMEDPC:
762 				color = ColorRed;
763 				color_index = 1;
764 				break;
765 			default:
766 				color = ColorCyan;
767 				color_index = 2;
768 				break;
769 		}
770 	}
771 
772 	// circle size 0 won't display, so we can ignore it when clamping
773 	int csize = Clamp(anims->GetCircleSize(), 1, MAX_CIRCLE_SIZE) - 1;
774 
775 	SetCircle( anims->GetCircleSize(), oscillationFactor, color, core->GroundCircles[csize][color_index], core->GroundCircles[csize][(color_index == 0) ? 3 : color_index] );
776 }
777 
ApplyClab_internal(Actor * actor,const char * clab,int level,bool remove,int diff)778 static void ApplyClab_internal(Actor *actor, const char *clab, int level, bool remove, int diff)
779 {
780 	AutoTable table(clab);
781 	if (!table) return;
782 
783 	int row = table->GetRowCount();
784 	int maxLevel = level;
785 	// don't remove clabs from levels we haven't attained yet, just in case they contain non-sticky
786 	// permanent effects like the charisma degradation in the oozemaster
787 	if (remove) maxLevel -= diff;
788 	for(int i=0; i<maxLevel; i++) {
789 		for (int j=0; j<row; j++) {
790 			const char *res = table->QueryField(j,i);
791 			if (res[0]=='*') continue;
792 
793 			if (!memcmp(res,"AP_",3)) {
794 				if (remove) {
795 					actor->fxqueue.RemoveAllEffects(res+3);
796 				} else {
797 					core->ApplySpell(res+3, actor, actor, 0);
798 				}
799 			} else if (!memcmp(res,"GA_",3)) {
800 				if (remove) {
801 					actor->spellbook.RemoveSpell(res+3);
802 				} else {
803 					actor->LearnSpell(res+3, LS_MEMO);
804 				}
805 			} else if (!memcmp(res,"FA_",3)) {//iwd2 only: innate name strref
806 				//memorize these?
807 				// we now learn them just to get the feedback string out
808 				if (remove) {
809 					actor->fxqueue.RemoveAllEffects(res+3);
810 				} else {
811 					actor->LearnSpell(res+3, LS_MEMO|LS_LEARN, IE_IWD2_SPELL_INNATE);
812 					actor->spellbook.RemoveSpell(res+3);
813 					core->ApplySpell(res+3, actor, actor, 0);
814 				}
815 			} else if (!memcmp(res,"FS_",3)) {//iwd2 only: song name strref (used by unused kits)
816 				//don't memorize these?
817 				if (remove) {
818 					actor->fxqueue.RemoveAllEffects(res+3);
819 				} else {
820 					actor->LearnSpell(res+3, LS_LEARN, IE_IWD2_SPELL_SONG);
821 					actor->spellbook.RemoveSpell(res+3);
822 					core->ApplySpell(res+3, actor, actor, 0);
823 				}
824 			} else if (!memcmp(res,"RA_",3)) {//iwd2 only
825 				//remove ability
826 				int x=atoi(res+3);
827 				actor->spellbook.RemoveSpell(x);
828 			}
829 		}
830 	}
831 
832 }
833 
834 #define BG2_KITMASK  0xffffc000
835 #define KIT_BASECLASS 0x4000
836 #define KIT_SWASHBUCKLER KIT_BASECLASS+12
837 #define KIT_WILDMAGE KIT_BASECLASS+30
838 #define KIT_BARBARIAN KIT_BASECLASS+31
839 
840 // iwd2 supports multiple kits per actor, but sanely only one kit per class
GetIWD2KitIndex(ieDword kit,ieDword baseclass=0,bool strict=false)841 static int GetIWD2KitIndex (ieDword kit, ieDword baseclass=0, bool strict=false)
842 {
843 	if (!kit) return -1;
844 
845 	if (baseclass != 0) {
846 		std::vector<ieDword> kits = class2kits[baseclass].ids;
847 		std::vector<ieDword>::iterator it = kits.begin();
848 		for (int idx=0; it != kits.end(); it++, idx++) {
849 			if (kit & (*it)) return class2kits[baseclass].indices[idx];
850 		}
851 		if (strict) return -1;
852 		Log(DEBUG, "Actor", "GetIWD2KitIndex: didn't find kit %d at expected class %d, recalculating!", kit, baseclass);
853 	}
854 
855 	// no class info passed, so infer it
856 	std::map<int, ClassKits>::iterator clskit = class2kits.begin();
857 	for (int cidx=0; clskit != class2kits.end(); clskit++, cidx++) {
858 		std::vector<ieDword> kits = class2kits[cidx].ids;
859 		std::vector<ieDword>::iterator it = kits.begin();
860 		for (int kidx=0; it != kits.end(); it++, kidx++) {
861 			if (kit & (*it)) {
862 				return class2kits[cidx].indices[kidx];
863 			}
864 		}
865 	}
866 
867 	Log(ERROR, "Actor", "GetIWD2KitIndex: didn't find kit %d for any class, ignoring!", kit);
868 	return -1;
869 }
870 
GetKitIndex(ieDword kit,ieDword baseclass) const871 ieDword Actor::GetKitIndex (ieDword kit, ieDword baseclass) const
872 {
873 	int kitindex = 0;
874 
875 	if (iwd2class) {
876 		return GetIWD2KitIndex(kit, baseclass);
877 	}
878 
879 	if ((kit&BG2_KITMASK) == KIT_BASECLASS) {
880 		kitindex = kit&0xfff;
881 		if (!kitindex && !baseclass) return 0;
882 	}
883 
884 	if (kitindex == 0) {
885 		if (!baseclass) baseclass = GetActiveClass();
886 		kitindex = GetIWD2KitIndex(kit, baseclass);
887 		if (kitindex < 0) {
888 			kitindex = 0;
889 		}
890 	}
891 
892 	return (ieDword)kitindex;
893 }
894 
895 //applies a kit on the character
ApplyKit(bool remove,ieDword baseclass,int diff)896 bool Actor::ApplyKit(bool remove, ieDword baseclass, int diff)
897 {
898 	ieDword kit = GetStat(IE_KIT);
899 	ieDword kitclass = 0;
900 	int row = GetKitIndex(kit, baseclass);
901 	const char *clab = NULL;
902 	ieDword max = 0;
903 	ieDword cls = GetStat(IE_CLASS);
904 	Holder<TableMgr> tm;
905 
906 	// iwd2 has support for multikit characters, so we have more work
907 	// at the same time each baseclass has its own level stat, so the logic is cleaner
908 	// NOTE: in iwd2 there are no pure class options for classes with kits, a kit has to be choosen
909 	// even generalist mages are a kit the same way as in the older games
910 	if (iwd2class) {
911 		// callers always pass a baseclass (only exception are actions not present in iwd2: addkit and addsuperkit)
912 		assert(baseclass != 0);
913 		row = GetIWD2KitIndex(kit, baseclass, true);
914 		bool kitMatchesClass = row > 0;
915 
916 		if (!kit || !kitMatchesClass) {
917 			// pure class
918 			clab = class2kits[baseclass].clab;
919 		} else {
920 			// both kit and baseclass are fine and the kit is of this baseclass
921 			std::vector<ieDword> kits = class2kits[baseclass].ids;
922 			std::vector<ieDword>::iterator it = kits.begin();
923 			for (int idx=0; it != kits.end(); it++, idx++) {
924 				if (kit & (*it)) {
925 					clab = class2kits[baseclass].clabs[idx];
926 					break;
927 				}
928 			}
929 		}
930 		assert(clab != NULL);
931 		cls = baseclass;
932 	} else if (row) {
933 		// bg2 kit abilities
934 		// this doesn't do a kitMatchesClass like above, since it is handled when applying the clab below
935 		// NOTE: a fighter/illusionist multiclass and illusionist/fighter dualclass would be good test cases, but they don't have any clabs
936 		// NOTE: multiclass characters will get the clabs applied for all classes at once, so up to three times, since there are three level stats
937 		// we can't rely on baseclass, since it will match only for combinations of fighters, mages and thieves.
938 		// TODO: fix it — one application ensures no problems with stacking permanent effects
939 		// NOTE: it can happen in normal play that we are leveling two classes at once, as some of the xp thresholds are shared (f/m at 250,000 xp).
940 		bool found = false;
941 		std::map<int, ClassKits>::iterator clskit = class2kits.begin();
942 		for (int cidx=0; clskit != class2kits.end(); clskit++, cidx++) {
943 			std::vector<int> kits = class2kits[cidx].indices;
944 			std::vector<int>::iterator it = kits.begin();
945 			for (int kidx=0; it != kits.end(); it++, kidx++) {
946 				if (row == *it) {
947 					kitclass = cidx;
948 					clab = class2kits[cidx].clabs[kidx];
949 					found = true;
950 					clskit = --class2kits.end(); // break out of the outer loop too
951 					break;
952 				}
953 			}
954 		}
955 		if (!found) {
956 			Log(ERROR, "Actor", "ApplyKit: could not look up the requested kit (%d), skipping!", kit);
957 			return false;
958 		}
959 	}
960 
961 	// a negative level diff happens when dualclassing due to three level stats being used and switched around
962 	if (diff < 0) diff = 0;
963 
964 	//multi class
965 	if (multiclass) {
966 		ieDword msk = 1;
967 		for(unsigned int i=1;(i<(unsigned int) classcount) && (msk<=multiclass);i++) {
968 			if (multiclass & msk) {
969 				max = GetLevelInClass(i);
970 				// don't apply/remove the old kit clab if the kit is disabled
971 				if (i == kitclass && !IsKitInactive()) {
972 					// in case of dc reactivation, we already removed the clabs on activation of new class
973 					// so we shouldn't do it again as some of the effects could be permanent (oozemaster)
974 					if (IsDualClassed()) {
975 						ApplyClab(clab, max, 2, 0);
976 					} else {
977 						ApplyClab(clab, max, remove, diff);
978 					}
979 				} else {
980 					ApplyClab(class2kits[i].clab, max, remove, diff);
981 				}
982 			}
983 			msk+=msk;
984 		}
985 		return true;
986 	}
987 	//single class
988 	if (cls>=(ieDword) classcount) {
989 		return false;
990 	}
991 	max = GetLevelInClass(cls);
992 	// iwd2 has clabs for kits and classes in the same table
993 	if (kitclass==cls || iwd2class) {
994 		ApplyClab(clab, max, remove, diff);
995 	} else {
996 		ApplyClab(class2kits[cls].clab, max, remove, diff);
997 	}
998 	return true;
999 }
1000 
ApplyClab(const char * clab,ieDword max,int remove,int diff)1001 void Actor::ApplyClab(const char *clab, ieDword max, int remove, int diff)
1002 {
1003 	if (clab && clab[0]!='*') {
1004 		if (max) {
1005 			//singleclass
1006 			if (remove != 2) {
1007 				ApplyClab_internal(this, clab, max, true, diff);
1008 			}
1009 			if (remove != 1) {
1010 				ApplyClab_internal(this, clab, max, false, 0);
1011 			}
1012 		}
1013 	}
1014 }
1015 
1016 //call this when morale or moralebreak changed
1017 //cannot use old or new value, because it is called two ways
pcf_morale(Actor * actor,ieDword,ieDword)1018 static void pcf_morale (Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1019 {
1020 	if (!actor->ShouldModifyMorale()) return;
1021 
1022 	if ((actor->Modified[IE_MORALE]<=actor->Modified[IE_MORALEBREAK]) && (actor->Modified[IE_MORALEBREAK] != 0) ) {
1023 		actor->Panic(core->GetGame()->GetActorByGlobalID(actor->LastAttacker), core->Roll(1,3,0) );
1024 	} else if (actor->Modified[IE_STATE_ID]&STATE_PANIC) {
1025 		// recover from panic, since morale has risen again
1026 		// but only if we have really just recovered, so panic from other
1027 		// sources isn't affected
1028 		if ((actor->Modified[IE_MORALE]-1 == actor->Modified[IE_MORALEBREAK]) || (actor->Modified[IE_MORALEBREAK] == 0) ) {
1029 			if (!third || !(actor->Modified[IE_SPECFLAGS]&SPECF_DRIVEN)) {
1030 				actor->SetBaseBit(IE_STATE_ID, STATE_PANIC, false);
1031 			}
1032 		}
1033 	}
1034 	//for new colour
1035 	actor->SetCircleSize();
1036 }
1037 
UpdateHappiness(Actor * actor)1038 static void UpdateHappiness(Actor *actor) {
1039 	if (!actor->InParty) return;
1040 	if (!core->HasFeature(GF_HAPPINESS)) return;
1041 
1042 	int newHappiness = GetHappiness(actor, core->GetGame()->Reputation);
1043 	if (newHappiness == actor->PCStats->Happiness) return;
1044 
1045 	actor->PCStats->Happiness = newHappiness;
1046 	switch (newHappiness) {
1047 		case -80: actor->VerbalConstant(VB_UNHAPPY, 1, DS_QUEUE); break;
1048 		case -160: actor->VerbalConstant(VB_UNHAPPY_SERIOUS, 1, DS_QUEUE); break;
1049 		case -300: actor->VerbalConstant(VB_BREAKING_POINT, 1, DS_QUEUE);
1050 			if (actor != core->GetGame()->GetPC(0, false)) core->GetGame()->LeaveParty(actor);
1051 			break;
1052 		case 80: actor->VerbalConstant(VB_HAPPY, 1, DS_QUEUE); break;
1053 		default: break; // case 0
1054 	}
1055 }
1056 
1057 // make paladins and rangers fallen if the reputations drops enough
pcf_reputation(Actor * actor,ieDword oldValue,ieDword newValue)1058 static void pcf_reputation(Actor *actor, ieDword oldValue, ieDword newValue)
1059 {
1060 	if (oldValue == newValue) return;
1061 	if (actor->InParty && newValue <= REPUTATION_FALL) {
1062 		if (actor->GetRangerLevel()) {
1063 			GameScript::RemoveRangerHood(actor, NULL);
1064 		} else if (actor->GetPaladinLevel()) {
1065 			GameScript::RemovePaladinHood(actor, NULL);
1066 		}
1067 	}
1068 	UpdateHappiness(actor);
1069 }
1070 
pcf_berserk(Actor * actor,ieDword,ieDword)1071 static void pcf_berserk(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1072 {
1073 	//needs for new color
1074 	actor->SetCircleSize();
1075 }
1076 
pcf_ea(Actor * actor,ieDword,ieDword newValue)1077 static void pcf_ea (Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1078 {
1079 	if (actor->Selected && (newValue>EA_GOODCUTOFF) ) {
1080 		core->GetGame()->SelectActor(actor, false, SELECT_NORMAL);
1081 	}
1082 	actor->SetCircleSize();
1083 }
1084 
1085 //this is a good place to recalculate level up stuff
1086 // iwd2 has separate stats and requires more data for clab application
pcf_level(Actor * actor,ieDword oldValue,ieDword newValue,ieDword baseClass=0)1087 static void pcf_level (Actor *actor, ieDword oldValue, ieDword newValue, ieDword baseClass=0)
1088 {
1089 	ieDword sum =
1090 		actor->GetFighterLevel()+
1091 		actor->GetMageLevel()+
1092 		actor->GetThiefLevel()+
1093 		actor->GetBarbarianLevel()+
1094 		actor->GetBardLevel()+
1095 		actor->GetClericLevel()+
1096 		actor->GetDruidLevel()+
1097 		actor->GetMonkLevel()+
1098 		actor->GetPaladinLevel()+
1099 		actor->GetRangerLevel()+
1100 		actor->GetSorcererLevel();
1101 	actor->SetBase(IE_CLASSLEVELSUM,sum);
1102 	actor->SetupFist();
1103 	if (newValue!=oldValue) {
1104 		actor->ApplyKit(false, baseClass, newValue-oldValue);
1105 	}
1106 	actor->GotLUFeedback = false;
1107 	if (third && actor->PCStats) {
1108 		actor->PCStats->UpdateClassLevels(actor->ListLevels());
1109 	}
1110 }
1111 
pcf_level_fighter(Actor * actor,ieDword oldValue,ieDword newValue)1112 static void pcf_level_fighter (Actor *actor, ieDword oldValue, ieDword newValue)
1113 {
1114 	pcf_level(actor, oldValue, newValue, classesiwd2[ISFIGHTER]);
1115 }
1116 
1117 // on load, all pcfs are ran, so we need to take care not to scramble the sorcerer type
1118 // both values are still 0 and checking equality guards against other meddling calls
1119 // (if it turns out to be wrong, just check against Ticks == 0)
pcf_level_mage(Actor * actor,ieDword oldValue,ieDword newValue)1120 static void pcf_level_mage (Actor *actor, ieDword oldValue, ieDword newValue)
1121 {
1122 	pcf_level(actor, oldValue, newValue, classesiwd2[ISMAGE]);
1123 	if (newValue != oldValue) actor->ChangeSorcererType(classesiwd2[ISMAGE]);
1124 }
1125 
pcf_level_thief(Actor * actor,ieDword oldValue,ieDword newValue)1126 static void pcf_level_thief (Actor *actor, ieDword oldValue, ieDword newValue)
1127 {
1128 	pcf_level(actor, oldValue, newValue, classesiwd2[ISTHIEF]);
1129 }
1130 
1131 // all but iwd2 only have 3 level stats, so shortcircuit them
pcf_level_barbarian(Actor * actor,ieDword oldValue,ieDword newValue)1132 static void pcf_level_barbarian (Actor *actor, ieDword oldValue, ieDword newValue)
1133 {
1134 	if (!third) return;
1135 	pcf_level(actor, oldValue, newValue, classesiwd2[ISBARBARIAN]);
1136 }
1137 
pcf_level_bard(Actor * actor,ieDword oldValue,ieDword newValue)1138 static void pcf_level_bard (Actor *actor, ieDword oldValue, ieDword newValue)
1139 {
1140 	if (!third) return;
1141 	pcf_level(actor, oldValue, newValue, classesiwd2[ISBARD]);
1142 	if (newValue != oldValue) actor->ChangeSorcererType(classesiwd2[ISBARD]);
1143 }
1144 
pcf_level_cleric(Actor * actor,ieDword oldValue,ieDword newValue)1145 static void pcf_level_cleric (Actor *actor, ieDword oldValue, ieDword newValue)
1146 {
1147 	if (!third) return;
1148 	pcf_level(actor, oldValue, newValue, classesiwd2[ISCLERIC]);
1149 	if (newValue != oldValue) actor->ChangeSorcererType(classesiwd2[ISCLERIC]);
1150 }
1151 
pcf_level_druid(Actor * actor,ieDword oldValue,ieDword newValue)1152 static void pcf_level_druid (Actor *actor, ieDword oldValue, ieDword newValue)
1153 {
1154 	if (!third) return;
1155 	pcf_level(actor, oldValue, newValue, classesiwd2[ISDRUID]);
1156 	if (newValue != oldValue) actor->ChangeSorcererType(classesiwd2[ISDRUID]);
1157 }
1158 
pcf_level_monk(Actor * actor,ieDword oldValue,ieDword newValue)1159 static void pcf_level_monk (Actor *actor, ieDword oldValue, ieDword newValue)
1160 {
1161 	if (!third) return;
1162 	pcf_level(actor, oldValue, newValue, classesiwd2[ISMONK]);
1163 }
1164 
pcf_level_paladin(Actor * actor,ieDword oldValue,ieDword newValue)1165 static void pcf_level_paladin (Actor *actor, ieDword oldValue, ieDword newValue)
1166 {
1167 	if (!third) return;
1168 	pcf_level(actor, oldValue, newValue, classesiwd2[ISPALADIN]);
1169 	if (newValue != oldValue) actor->ChangeSorcererType(classesiwd2[ISPALADIN]);
1170 }
1171 
pcf_level_ranger(Actor * actor,ieDword oldValue,ieDword newValue)1172 static void pcf_level_ranger (Actor *actor, ieDword oldValue, ieDword newValue)
1173 {
1174 	if (!third) return;
1175 	pcf_level(actor, oldValue, newValue, classesiwd2[ISRANGER]);
1176 	if (newValue != oldValue) actor->ChangeSorcererType(classesiwd2[ISRANGER]);
1177 }
1178 
pcf_level_sorcerer(Actor * actor,ieDword oldValue,ieDword newValue)1179 static void pcf_level_sorcerer (Actor *actor, ieDword oldValue, ieDword newValue)
1180 {
1181 	if (!third) return;
1182 	pcf_level(actor, oldValue, newValue, classesiwd2[ISSORCERER]);
1183 	if (newValue != oldValue) actor->ChangeSorcererType(classesiwd2[ISSORCERER]);
1184 }
1185 
pcf_class(Actor * actor,ieDword,ieDword newValue)1186 static void pcf_class (Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1187 {
1188 	//Call forced initbuttons in old style systems, and soft initbuttons
1189 	//in case of iwd2. Maybe we need a custom quickslots flag here.
1190 	// also ensure multiclass is set early, since GetActiveClass relies on it
1191 	actor->ResetMC();
1192 	actor->InitButtons(actor->GetActiveClass(), !iwd2class);
1193 	actor->ChangeSorcererType(newValue);
1194 }
1195 
1196 // sets (actually ORs in) the new spellbook type as a sorcerer-style one if needed
ChangeSorcererType(ieDword classIdx)1197 void Actor::ChangeSorcererType (ieDword classIdx)
1198 {
1199 	int sorcerer = 0;
1200 	if (classIdx <(ieDword) classcount) {
1201 		switch(booktypes[classIdx]) {
1202 		case 2:
1203 			// arcane sorcerer-style
1204 			if (third) {
1205 				sorcerer = 1 << iwd2spltypes[classIdx];
1206 			} else {
1207 				sorcerer = 1<<IE_SPELL_TYPE_WIZARD;
1208 			}
1209 			break;
1210 		case 3:
1211 			// divine caster with sorc. style spells
1212 			if (third) {
1213 				sorcerer = 1 << iwd2spltypes[classIdx];
1214 			} else {
1215 				sorcerer = 1<<IE_SPELL_TYPE_PRIEST;
1216 			}
1217 			break;
1218 		case 5: sorcerer = 1<<IE_IWD2_SPELL_SHAPE; break;  //divine caster with sorc style shapes (iwd2 druid)
1219 		default: break;
1220 		}
1221 	}
1222 	spellbook.SetBookType(sorcerer);
1223 }
1224 
pcf_animid(Actor * actor,ieDword,ieDword newValue)1225 static void pcf_animid(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1226 {
1227 	actor->SetAnimationID(newValue);
1228 }
1229 
1230 static const ieDword fullwhite[7]={ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT,ICE_GRADIENT};
1231 
1232 static const ieDword fullstone[7]={STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT,STONE_GRADIENT};
1233 
pcf_state(Actor * actor,ieDword,ieDword State)1234 static void pcf_state(Actor *actor, ieDword /*oldValue*/, ieDword State)
1235 {
1236 	if (actor->InParty) core->SetEventFlag(EF_PORTRAIT);
1237 	if (State & STATE_PETRIFIED) {
1238 		actor->SetLockedPalette(fullstone);
1239 		return;
1240 	}
1241 	if (State & STATE_FROZEN) {
1242 		actor->SetLockedPalette(fullwhite);
1243 		return;
1244 	}
1245 	//it is not enough to check the new state
1246 	core->GetGame()->Infravision();
1247 	actor->UnlockPalette();
1248 }
1249 
1250 //changes based on extended state bits, right now it is only the seven eyes
1251 //animation (used in how/iwd2)
pcf_extstate(Actor * actor,ieDword oldValue,ieDword State)1252 static void pcf_extstate(Actor *actor, ieDword oldValue, ieDword State)
1253 {
1254 	if ((oldValue^State)&EXTSTATE_SEVEN_EYES) {
1255 		int eyeCount = 7;
1256 		for (ieDword mask = EXTSTATE_EYE_MIND; mask <= EXTSTATE_EYE_STONE; mask <<= 1) {
1257 			if (State & mask) --eyeCount;
1258 		}
1259 		ScriptedAnimation *sca = actor->FindOverlay(OV_SEVENEYES);
1260 		if (sca) {
1261 			sca->SetOrientation(eyeCount);
1262 		}
1263 		sca = actor->FindOverlay(OV_SEVENEYES2);
1264 		if (sca) {
1265 			sca->SetOrientation(eyeCount);
1266 		}
1267 	}
1268 }
1269 
pcf_hitpoint(Actor * actor,ieDword oldValue,ieDword hp)1270 static void pcf_hitpoint(Actor *actor, ieDword oldValue, ieDword hp)
1271 {
1272 	if (actor->checkHP == 2) return;
1273 
1274 	int maxhp = (signed) actor->GetSafeStat(IE_MAXHITPOINTS);
1275 	// ERWAN.CRE from Victor's Improvement Pack has a max of 0 and still survives, grrr
1276 	if (maxhp && (signed) hp > maxhp) {
1277 		hp=maxhp;
1278 	}
1279 
1280 	int minhp = (signed) actor->GetSafeStat(IE_MINHITPOINTS);
1281 	if (minhp && (signed) hp<minhp) {
1282 		hp=minhp;
1283 	}
1284 	if ((signed) hp<=0) {
1285 		actor->Die(NULL);
1286 	} else {
1287 		// in testing it popped up somewhere between 39% and 25.3% (single run) -> 1/3
1288 		if (signed(3*oldValue) > maxhp && signed(3*hp) < maxhp) {
1289 			actor->VerbalConstant(VB_HURT, 1, DS_QUEUE);
1290 		}
1291 	}
1292 	// don't fire off events if nothing changed, which can happen when called indirectly
1293 	if (oldValue != hp) {
1294 		actor->BaseStats[IE_HITPOINTS] = hp;
1295 		actor->Modified[IE_HITPOINTS] = hp;
1296 		if (actor->InParty) core->SetEventFlag(EF_PORTRAIT);
1297 	}
1298 }
1299 
pcf_maxhitpoint(Actor * actor,ieDword,ieDword)1300 static void pcf_maxhitpoint(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1301 {
1302 	if (!actor->checkHP) {
1303 		actor->checkHP = 1;
1304 		actor->checkHPTime = core->GetGame()->GameTime;
1305 	}
1306 }
1307 
pcf_minhitpoint(Actor * actor,ieDword,ieDword hp)1308 static void pcf_minhitpoint(Actor *actor, ieDword /*oldValue*/, ieDword hp)
1309 {
1310 	if ((signed) hp>(signed) actor->BaseStats[IE_HITPOINTS]) {
1311 		actor->BaseStats[IE_HITPOINTS]=hp;
1312 		//passing 0 because it is ignored anyway
1313 		pcf_hitpoint(actor, 0, hp);
1314 	}
1315 }
1316 
pcf_stat(Actor * actor,ieDword newValue,ieDword stat)1317 static void pcf_stat(Actor *actor, ieDword newValue, ieDword stat)
1318 {
1319 	if ((signed) newValue<=0) {
1320 		if (DeathOnZeroStat) {
1321 			actor->Die(NULL);
1322 		} else {
1323 			actor->Modified[stat]=1;
1324 		}
1325 	}
1326 }
1327 
pcf_stat_str(Actor * actor,ieDword,ieDword newValue)1328 static void pcf_stat_str(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1329 {
1330 	pcf_stat(actor, newValue, IE_STR);
1331 }
1332 
pcf_stat_int(Actor * actor,ieDword,ieDword newValue)1333 static void pcf_stat_int(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1334 {
1335 	pcf_stat(actor, newValue, IE_INT);
1336 }
1337 
pcf_stat_wis(Actor * actor,ieDword oldValue,ieDword newValue)1338 static void pcf_stat_wis(Actor *actor, ieDword oldValue, ieDword newValue)
1339 {
1340 	pcf_stat(actor, newValue, IE_WIS);
1341 	if (third) {
1342 		int oldBonus = actor->GetAbilityBonus(IE_WIS, oldValue);
1343 		actor->Modified[IE_SAVEWILL] += actor->GetAbilityBonus(IE_WIS) - oldBonus;
1344 	}
1345 }
1346 
pcf_stat_dex(Actor * actor,ieDword oldValue,ieDword newValue)1347 static void pcf_stat_dex(Actor *actor, ieDword oldValue, ieDword newValue)
1348 {
1349 	pcf_stat(actor, newValue, IE_DEX);
1350 	if (third) {
1351 		int oldBonus = actor->GetAbilityBonus(IE_DEX, oldValue);
1352 		actor->Modified[IE_SAVEREFLEX] += actor->GetAbilityBonus(IE_DEX) - oldBonus;
1353 	}
1354 }
1355 
pcf_stat_con(Actor * actor,ieDword oldValue,ieDword newValue)1356 static void pcf_stat_con(Actor *actor, ieDword oldValue, ieDword newValue)
1357 {
1358 	pcf_stat(actor, newValue, IE_CON);
1359 	pcf_hitpoint(actor, 0, actor->BaseStats[IE_HITPOINTS]);
1360 	if (third) {
1361 		int oldBonus = actor->GetAbilityBonus(IE_CON, oldValue);
1362 		actor->Modified[IE_SAVEFORTITUDE] += actor->GetAbilityBonus(IE_CON) - oldBonus;
1363 	}
1364 }
1365 
pcf_stat_cha(Actor * actor,ieDword,ieDword newValue)1366 static void pcf_stat_cha(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1367 {
1368 	pcf_stat(actor, newValue, IE_CHR);
1369 }
1370 
pcf_xp(Actor * actor,ieDword,ieDword)1371 static void pcf_xp(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1372 {
1373 	// check if we reached a new level
1374 	unsigned int pc = actor->InParty;
1375 	if (pc && !actor->GotLUFeedback) {
1376 		char varname[16];
1377 		snprintf(varname, sizeof(varname), "CheckLevelUp%d", pc);
1378 		core->GetGUIScriptEngine()->RunFunction("GUICommonWindows", "CheckLevelUp", true, pc);
1379 		ieDword NeedsLevelUp = 0;
1380 		core->GetDictionary()->Lookup(varname, NeedsLevelUp);
1381 		if (NeedsLevelUp == 1) {
1382 			displaymsg->DisplayConstantStringName(STR_LEVELUP, DMC_WHITE, actor);
1383 			actor->GotLUFeedback = true;
1384 		}
1385 	}
1386 }
1387 
pcf_gold(Actor * actor,ieDword,ieDword)1388 static void pcf_gold(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1389 {
1390 	//this function will make a party member automatically donate their
1391 	//gold to the party pool, not the same as in the original engine
1392 	if (actor->InParty) {
1393 		Game *game = core->GetGame();
1394 		game->AddGold ( actor->BaseStats[IE_GOLD] );
1395 		actor->BaseStats[IE_GOLD]=0;
1396 	}
1397 }
1398 
handle_overlay(Actor * actor,ieDword idx)1399 static void handle_overlay(Actor *actor, ieDword idx)
1400 {
1401 	if (idx >= OVERLAY_COUNT || actor->FindOverlay(idx)) return;
1402 
1403 	ieDword flag = hc_locations&(1<<idx);
1404 	ScriptedAnimation *sca = gamedata->GetScriptedAnimation(hc_overlays[idx], false);
1405 	if (!sca) {
1406 		return;
1407 	}
1408 	// many are stored as bams and can't be translucent by default
1409 	sca->SetBlend();
1410 
1411 	// always draw it for party members; the rest must not be invisible to have it;
1412 	// this is just a guess, maybe there are extra conditions (MC_HIDDEN? IE_AVATARREMOVAL?)
1413 	if (!actor->InParty && actor->Modified[IE_STATE_ID] & state_invisible && !(hc_flags[idx] & HC_INVISIBLE)) {
1414 		delete sca;
1415 		return;
1416 	}
1417 
1418 	if (flag) {
1419 		sca->ZOffset = -1;
1420 	}
1421 	actor->AddVVCell(sca);
1422 }
1423 
1424 //de/activates the entangle overlay
pcf_entangle(Actor * actor,ieDword oldValue,ieDword newValue)1425 static void pcf_entangle(Actor *actor, ieDword oldValue, ieDword newValue)
1426 {
1427 	if (newValue&1) {
1428 		handle_overlay(actor, OV_ENTANGLE);
1429 	}
1430 	if (oldValue&1) {
1431 		actor->RemoveVVCells(hc_overlays[OV_ENTANGLE]);
1432 	}
1433 }
1434 
1435 //de/activates the sanctuary and other overlays
1436 //unlike IE, gemrb uses this stat for other overlay fields
1437 //see the complete list in overlay.2da
1438 //it loosely follows the internal representation of overlays in IWD2
pcf_sanctuary(Actor * actor,ieDword oldValue,ieDword newValue)1439 static void pcf_sanctuary(Actor *actor, ieDword oldValue, ieDword newValue)
1440 {
1441 	ieDword changed = newValue^oldValue;
1442 	ieDword mask = 1;
1443 	if (!changed) return;
1444 	for (int i=0; i<OVERLAY_COUNT; i++) {
1445 		if (changed&mask) {
1446 			if (newValue&mask) {
1447 				handle_overlay(actor, i);
1448 			} else if (oldValue&mask) {
1449 				actor->RemoveVVCells(hc_overlays[i]);
1450 			}
1451 		}
1452 		mask<<=1;
1453 	}
1454 }
1455 
1456 //de/activates the prot from missiles overlay
pcf_shieldglobe(Actor * actor,ieDword oldValue,ieDword newValue)1457 static void pcf_shieldglobe(Actor *actor, ieDword oldValue, ieDword newValue)
1458 {
1459 	if (newValue&1) {
1460 		handle_overlay(actor, OV_SHIELDGLOBE);
1461 		return;
1462 	}
1463 	if (oldValue&1) {
1464 		actor->RemoveVVCells(hc_overlays[OV_SHIELDGLOBE]);
1465 	}
1466 }
1467 
1468 //de/activates the globe of invul. overlay
pcf_minorglobe(Actor * actor,ieDword oldValue,ieDword newValue)1469 static void pcf_minorglobe(Actor *actor, ieDword oldValue, ieDword newValue)
1470 {
1471 	if (newValue&1) {
1472 		handle_overlay(actor, OV_MINORGLOBE);
1473 		return;
1474 	}
1475 	if (oldValue&1) {
1476 		actor->RemoveVVCells(hc_overlays[OV_MINORGLOBE]);
1477 	}
1478 }
1479 
1480 //de/activates the grease background
pcf_grease(Actor * actor,ieDword oldValue,ieDword newValue)1481 static void pcf_grease(Actor *actor, ieDword oldValue, ieDword newValue)
1482 {
1483 	if (newValue&1) {
1484 		handle_overlay(actor, OV_GREASE);
1485 		return;
1486 	}
1487 	if (oldValue&1) {
1488 		actor->RemoveVVCells(hc_overlays[OV_GREASE]);
1489 	}
1490 }
1491 
1492 //de/activates the web overlay
1493 //the web effect also immobilizes the actor!
pcf_web(Actor * actor,ieDword oldValue,ieDword newValue)1494 static void pcf_web(Actor *actor, ieDword oldValue, ieDword newValue)
1495 {
1496 	if (newValue&1) {
1497 		handle_overlay(actor, OV_WEB);
1498 		return;
1499 	}
1500 	if (oldValue&1) {
1501 		actor->RemoveVVCells(hc_overlays[OV_WEB]);
1502 	}
1503 }
1504 
1505 //de/activates the spell bounce background
pcf_bounce(Actor * actor,ieDword oldValue,ieDword newValue)1506 static void pcf_bounce(Actor *actor, ieDword oldValue, ieDword newValue)
1507 {
1508 	if (newValue) {
1509 		handle_overlay(actor, OV_BOUNCE);
1510 		return;
1511 	}
1512 	if (oldValue) {
1513 		actor->RemoveVVCells(hc_overlays[OV_BOUNCE]);
1514 	}
1515 }
1516 
pcf_alignment(Actor * actor,ieDword,ieDword)1517 static void pcf_alignment(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1518 {
1519 	UpdateHappiness(actor);
1520 }
1521 
pcf_avatarremoval(Actor * actor,ieDword,ieDword newValue)1522 static void pcf_avatarremoval(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1523 {
1524 	Map *map = actor->GetCurrentArea();
1525 	if (!map) return;
1526 	map->BlockSearchMap(actor->Pos, actor->size, newValue > 0 ? PathMapFlags::UNMARKED : PathMapFlags::NPC);
1527 }
1528 
1529 //spell casting or other buttons disabled/reenabled
pcf_dbutton(Actor * actor,ieDword,ieDword)1530 static void pcf_dbutton(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1531 {
1532 	if (actor->IsSelected()) {
1533 		core->SetEventFlag( EF_ACTION );
1534 	}
1535 }
1536 
1537 //no separate values (changes are permanent)
pcf_intoxication(Actor * actor,ieDword,ieDword newValue)1538 static void pcf_intoxication(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1539 {
1540 	actor->BaseStats[IE_INTOXICATION]=newValue;
1541 }
1542 
pcf_color(Actor * actor,ieDword,ieDword)1543 static void pcf_color(Actor *actor, ieDword /*oldValue*/, ieDword /*newValue*/)
1544 {
1545 	CharAnimations *anims = actor->GetAnims();
1546 	if (anims) {
1547 		anims->SetColors(actor->Modified+IE_COLORS);
1548 	}
1549 }
1550 
pcf_armorlevel(Actor * actor,ieDword,ieDword newValue)1551 static void pcf_armorlevel(Actor *actor, ieDword /*oldValue*/, ieDword newValue)
1552 {
1553 	CharAnimations *anims = actor->GetAnims();
1554 	if (anims) {
1555 		anims->SetArmourLevel(newValue);
1556 	}
1557 }
1558 
1559 static unsigned int maximum_values[MAX_STATS]={
1560 32767,32767,20,100,100,100,100,25,10,25,25,25,25,25,200,200,//0f
1561 200,200,200,200,200,100,100,100,100,100,255,255,255,255,100,100,//1f
1562 200,200,MAX_LEVEL,255,25,100,25,25,25,25,25,999999999,999999999,999999999,25,25,//2f
1563 200,255,200,100,100,200,200,25,10,100,1,1,255,1,1,0,//3f
1564 1023,1,1,1,MAX_LEVEL,MAX_LEVEL,1,9999,25,200,200,255,1,20,20,25,//4f
1565 25,1,1,255,25,25,255,255,25,255,255,255,255,255,255,255,//5f
1566 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//6f
1567 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,//7f
1568 255,255,255,MAX_FEATV,MAX_FEATV,MAX_FEATV,255,100,100,100,999999,5,5,999999,1,1,//8f
1569 1,25,25,255,1,1,1,25,0,100,100,1,255,255,255,255,//9f
1570 255,255,255,255,255,255,20,255,255,1,20,255,999999999,999999999,1,1,//af
1571 999999999,999999999,0,0,20,0,0,0,0,0,0,0,0,0,0,0,//bf
1572 0,0,0,0,0,0,0,25,25,255,255,255,255,65535,0,0,//cf - 207
1573 0,0,0,0,0,0,0,0,MAX_LEVEL,255,65535,3,255,255,255,255,//df - 223
1574 255,255,255,255,255,255,255,255,255,255,255,255,65535,65535,15,0,//ef - 239
1575 MAX_LEVEL,MAX_LEVEL,MAX_LEVEL,MAX_LEVEL, MAX_LEVEL,MAX_LEVEL,MAX_LEVEL,MAX_LEVEL, //0xf7 - 247
1576 MAX_LEVEL,MAX_LEVEL,0,0,0,0,0,0//ff
1577 };
1578 
1579 typedef void (*PostChangeFunctionType)(Actor *actor, ieDword oldValue, ieDword newValue);
1580 static PostChangeFunctionType post_change_functions[MAX_STATS]={
1581 pcf_hitpoint, pcf_maxhitpoint, NULL, NULL, NULL, NULL, NULL, NULL,
1582 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //0f
1583 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1584 NULL,NULL,NULL,NULL, NULL, NULL, NULL, pcf_intoxication, //1f
1585 NULL,NULL,pcf_level_fighter,NULL, pcf_stat_str, NULL, pcf_stat_int, pcf_stat_wis,
1586 pcf_stat_dex,pcf_stat_con,pcf_stat_cha,NULL, pcf_xp, pcf_gold, pcf_morale, NULL, //2f
1587 pcf_reputation,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1588 NULL,NULL,NULL,NULL, NULL, NULL, pcf_entangle, pcf_sanctuary, //3f
1589 pcf_minorglobe, pcf_shieldglobe, pcf_grease, pcf_web, pcf_level_mage, pcf_level_thief, NULL, NULL,
1590 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //4f
1591 NULL,NULL,NULL,pcf_minhitpoint, NULL, NULL, NULL, NULL,
1592 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //5f
1593 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1594 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //6f
1595 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1596 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //7f
1597 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1598 NULL,NULL,NULL,NULL, NULL, NULL, pcf_berserk, NULL, //8f
1599 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1600 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //9f
1601 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1602 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //af
1603 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1604 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL, //bf
1605 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1606 NULL,pcf_avatarremoval,NULL,NULL, pcf_dbutton, pcf_animid,pcf_state, pcf_extstate, //cf
1607 pcf_color,pcf_color,pcf_color,pcf_color, pcf_color, pcf_color, pcf_color, NULL,
1608 NULL, pcf_alignment, pcf_dbutton, pcf_armorlevel, NULL, NULL, NULL, NULL, //df
1609 NULL,NULL,NULL,NULL, NULL, NULL, NULL, NULL,
1610 pcf_class,NULL,pcf_ea,NULL, NULL, NULL, NULL, NULL, //ef
1611 pcf_level_barbarian,pcf_level_bard,pcf_level_cleric,pcf_level_druid, pcf_level_monk, pcf_level_paladin, pcf_level_ranger, pcf_level_sorcerer,
1612 NULL, NULL, NULL, NULL, pcf_morale, pcf_bounce, NULL, NULL //ff
1613 };
1614 
1615 /** call this from ~Interface() */
ReleaseMemory()1616 void Actor::ReleaseMemory()
1617 {
1618 	if (classcount>=0) {
1619 		if (clericspelltables) {
1620 			for (int i = 0; i < classcount; i++) {
1621 				free (clericspelltables[i]);
1622 			}
1623 			free(clericspelltables);
1624 			clericspelltables=NULL;
1625 		}
1626 		if (druidspelltables) {
1627 			for (int i = 0; i < classcount; i++) {
1628 				free (druidspelltables[i]);
1629 			}
1630 			free(druidspelltables);
1631 			druidspelltables=NULL;
1632 		}
1633 		if (wizardspelltables) {
1634 			for (int i = 0; i < classcount; i++) {
1635 				free(wizardspelltables[i]);
1636 			}
1637 			free(wizardspelltables);
1638 			wizardspelltables=NULL;
1639 		}
1640 
1641 		if (defaultprof) {
1642 			free(defaultprof);
1643 			defaultprof=NULL;
1644 		}
1645 
1646 		if (turnlevels) {
1647 			free(turnlevels);
1648 			turnlevels=NULL;
1649 		}
1650 
1651 		if (booktypes) {
1652 			free(booktypes);
1653 			booktypes=NULL;
1654 		}
1655 
1656 		if (castingstat) {
1657 			free(castingstat);
1658 			castingstat=NULL;
1659 		}
1660 
1661 		if (iwd2spltypes) {
1662 			free(iwd2spltypes);
1663 			iwd2spltypes = NULL;
1664 		}
1665 
1666 		if (xpbonus) {
1667 			free(xpbonus);
1668 			xpbonus=NULL;
1669 			xpbonuslevels = -1;
1670 			xpbonustypes = -1;
1671 		}
1672 
1673 		if (xpcap) {
1674 			free(xpcap);
1675 			xpcap = NULL;
1676 		}
1677 
1678 		if (levelslots) {
1679 			for (int i = 0; i < classcount; i++) {
1680 				free(levelslots[i]);
1681 			}
1682 			free(levelslots);
1683 			levelslots=NULL;
1684 		}
1685 		if (dualswap) {
1686 			free(dualswap);
1687 			dualswap=NULL;
1688 		}
1689 		if (multi) {
1690 			free(multi);
1691 			multi=NULL;
1692 		}
1693 		if (maxLevelForHpRoll) {
1694 			free(maxLevelForHpRoll);
1695 			maxLevelForHpRoll=NULL;
1696 		}
1697 		skillstats.clear();
1698 
1699 		if (afcomments) {
1700 			for(int i = 0; i < afcount; i++) {
1701 				free(afcomments[i]);
1702 			}
1703 			free(afcomments);
1704 			afcomments=NULL;
1705 		}
1706 
1707 		if (wsdualwield) {
1708 			for (int i = 0; i<= STYLE_MAX; i++) {
1709 				free(wsdualwield[i]);
1710 			}
1711 			free(wsdualwield);
1712 			wsdualwield=NULL;
1713 		}
1714 		if (wstwohanded) {
1715 			for (int i = 0; i<= STYLE_MAX; i++) {
1716 				free(wstwohanded[i]);
1717 			}
1718 			free(wstwohanded);
1719 			wstwohanded=NULL;
1720 		}
1721 		if (wsswordshield) {
1722 			for (int i = 0; i<= STYLE_MAX; i++) {
1723 				free(wsswordshield[i]);
1724 			}
1725 			free(wsswordshield);
1726 			wsswordshield=NULL;
1727 		}
1728 		if (wssingle) {
1729 			for (int i = 0; i<= STYLE_MAX; i++) {
1730 				free(wssingle[i]);
1731 			}
1732 			free(wssingle);
1733 			wssingle=NULL;
1734 		}
1735 		if (monkbon) {
1736 			for (unsigned i=0; i<monkbon_rows; i++) {
1737 				free(monkbon[i]);
1738 			}
1739 			free(monkbon);
1740 			monkbon=NULL;
1741 		}
1742 		for (auto wml : wmlevels) {
1743 			free(wml);
1744 			wml = NULL;
1745 		}
1746 		skilldex.clear();
1747 		skillrac.clear();
1748 		IWD2HitTable.clear();
1749 		BABClassMap.clear();
1750 		ModalStates.clear();
1751 		for (auto clskit : class2kits) {
1752 			free(clskit.second.clab);
1753 			free(clskit.second.className);
1754 			for (auto kit : clskit.second.clabs) {
1755 				free(kit);
1756 			}
1757 			for (auto kit : clskit.second.kitNames) {
1758 				free(kit);
1759 			}
1760 		}
1761 	}
1762 	if (GUIBTDefaults) {
1763 		free (GUIBTDefaults);
1764 		GUIBTDefaults=NULL;
1765 	}
1766 	if (OtherGUIButtons) {
1767 		free (OtherGUIButtons);
1768 	}
1769 	classcount = -1;
1770 }
1771 
1772 #define COL_MAIN       0
1773 #define COL_SPARKS     1
1774 #define COL_GRADIENT   2
1775 
1776 /* returns the ISCLASS for the class based on name */
IsClassFromName(const char * name)1777 static int IsClassFromName (const char* name)
1778 {
1779 	for (int i=0; i<ISCLASSES; i++) {
1780 		if (strcmp(name, isclassnames[i]) == 0)
1781 			return i;
1782 	}
1783 	return -1;
1784 }
1785 
UpdateActorConfig()1786 GEM_EXPORT void UpdateActorConfig()
1787 {
1788 	unsigned int tmp = 0;
1789 	core->GetDictionary()->Lookup("Critical Hit Screen Shake", crit_hit_scr_shake);
1790 	core->GetDictionary()->Lookup("Selection Sounds Frequency", sel_snd_freq);
1791 	core->GetDictionary()->Lookup("Effect Text Level", tmp);
1792 	core->SetFeedbackLevel(tmp);
1793 	core->GetDictionary()->Lookup("Command Sounds Frequency", cmd_snd_freq);
1794 	// only pst has the whole gamut for these two options
1795 	if (!(tmp & FT_SELECTION)) sel_snd_freq = 0;
1796 	if (!(tmp & FT_ACTIONS)) cmd_snd_freq = 0;
1797 	core->GetDictionary()->Lookup("Bored Timeout", bored_time);
1798 	core->GetDictionary()->Lookup("Footsteps", footsteps);
1799 	core->GetDictionary()->Lookup("Attack Sounds", war_cries);
1800 
1801 	//Handle Game Difficulty and Nightmare Mode
1802 	// iwd2 had it saved in the GAM, iwd1 only relied on the ini value
1803 	GameDifficulty = 0;
1804 	core->GetDictionary()->Lookup("Nightmare Mode", GameDifficulty);
1805 	Game *game = core->GetGame();
1806 	if (GameDifficulty || (game && game->HOFMode)) {
1807 		GameDifficulty = DIFF_INSANE;
1808 		if (game) game->HOFMode = true;
1809 		// also set it for GUIOPT
1810 		core->GetDictionary()->SetAt("Difficulty Level", DIFF_INSANE - 1);
1811 	} else {
1812 		GameDifficulty = 0;
1813 		core->GetDictionary()->Lookup("Difficulty Level", GameDifficulty);
1814 		GameDifficulty++; // slider starts at 0, real levels at 1
1815 	}
1816 	GameDifficulty = Clamp((int) GameDifficulty, DIFF_EASY, DIFF_INSANE);
1817 
1818 	// iwd has a config option for leniency
1819 	core->GetDictionary()->Lookup("Suppress Extra Difficulty Damage", NoExtraDifficultyDmg);
1820 }
1821 
ReadModalStates()1822 static void ReadModalStates()
1823 {
1824 	AutoTable table("modal");
1825 	if (!table) return;
1826 
1827 	ModalStatesStruct ms;
1828 	for (unsigned short i = 0; i < table->GetRowCount(); i++) {
1829 		CopyResRef(ms.spell, table->QueryField(i, 0));
1830 		strlcpy(ms.action, table->QueryField(i, 1), 16);
1831 		ms.entering_str = atoi(table->QueryField(i, 2));
1832 		ms.leaving_str = atoi(table->QueryField(i, 3));
1833 		ms.failed_str = atoi(table->QueryField(i, 4));
1834 		ms.aoe_spell = atoi(table->QueryField(i, 5));
1835 		ms.repeat_msg = atoi(table->QueryField(i, 6));
1836 		ModalStates.push_back(ms);
1837 	}
1838 }
1839 
InitActorTables()1840 static void InitActorTables()
1841 {
1842 	int i, j;
1843 
1844 	UpdateActorConfig();
1845 	pstflags = core->HasFeature(GF_PST_STATE_FLAGS) != 0;
1846 	nocreate = core->HasFeature(GF_NO_NEW_VARIABLES) != 0;
1847 	third = core->HasFeature(GF_3ED_RULES) != 0;
1848 	raresnd = core->HasFeature(GF_RARE_ACTION_VB) != 0;
1849 	iwd2class = core->HasFeature(GF_LEVELSLOT_PER_CLASS) != 0;
1850 	// iwd2 has some different base class names
1851 	if (iwd2class) {
1852 		isclassnames[ISTHIEF] = "ROGUE";
1853 		isclassnames[ISMAGE] = "WIZARD";
1854 	}
1855 
1856 	if (pstflags) {
1857 		state_invisible=STATE_PST_INVIS;
1858 	} else {
1859 		state_invisible=STATE_INVISIBLE;
1860 	}
1861 
1862 	if (core->HasFeature(GF_CHALLENGERATING)) {
1863 		sharexp=SX_DIVIDE|SX_COMBAT|SX_CR;
1864 	} else {
1865 		sharexp=SX_DIVIDE|SX_COMBAT;
1866 	}
1867 	ReverseToHit = core->HasFeature(GF_REVERSE_TOHIT);
1868 	CheckAbilities = core->HasFeature(GF_CHECK_ABILITIES);
1869 	DeathOnZeroStat = core->HasFeature(GF_DEATH_ON_ZERO_STAT);
1870 	IWDSound = core->HasFeature(GF_SOUNDS_INI);
1871 	NUM_RARE_SELECT_SOUNDS = core->GetRareSelectSoundCount();
1872 
1873 	//this table lists various level based xp bonuses
1874 	AutoTable tm("xpbonus", true);
1875 	if (tm) {
1876 		xpbonustypes = tm->GetRowCount();
1877 		if (xpbonustypes == 0) {
1878 			xpbonuslevels = 0;
1879 		} else {
1880 			xpbonuslevels = tm->GetColumnCount(0);
1881 			xpbonus = (int *) calloc(xpbonuslevels*xpbonustypes, sizeof(int));
1882 			for (i = 0; i<xpbonustypes; i++) {
1883 				for(j = 0; j<xpbonuslevels; j++) {
1884 					xpbonus[i*xpbonuslevels+j] = atoi(tm->QueryField(i,j));
1885 				}
1886 			}
1887 		}
1888 	} else {
1889 		xpbonustypes = 0;
1890 		xpbonuslevels = 0;
1891 	}
1892 	//this table lists skill groups assigned to classes
1893 	//it is theoretically possible to create hybrid classes
1894 	tm.load("clskills");
1895 	if (tm) {
1896 		classcount = tm->GetRowCount();
1897 		memset (isclass,0,sizeof(isclass));
1898 		clericspelltables = (char **) calloc(classcount, sizeof(char*));
1899 		druidspelltables = (char **) calloc(classcount, sizeof(char*));
1900 		wizardspelltables = (char **) calloc(classcount, sizeof(char*));
1901 		turnlevels = (int *) calloc(classcount, sizeof(int));
1902 		booktypes = (int *) calloc(classcount, sizeof(int));
1903 		defaultprof = (int *) calloc(classcount, sizeof(int));
1904 		castingstat = (int *) calloc(classcount, sizeof(int));
1905 		iwd2spltypes = (int *) calloc(classcount, sizeof(int));
1906 
1907 		ieDword bitmask = 1;
1908 
1909 		for(i = 0; i<classcount; i++) {
1910 			const char *field;
1911 			const char *rowname = tm->GetRowName(i);
1912 
1913 			field = tm->QueryField(rowname, "DRUIDSPELL");
1914 			if (field[0]!='*') {
1915 				isclass[ISDRUID] |= bitmask;
1916 				druidspelltables[i]=strdup(field);
1917 			}
1918 			field = tm->QueryField(rowname, "CLERICSPELL");
1919 			if (field[0]!='*') {
1920 				// iwd2 has no DRUIDSPELL
1921 				if (third && !strnicmp(field, "MXSPLDRD", 8)) {
1922 					isclass[ISDRUID] |= bitmask;
1923 					druidspelltables[i]=strdup(field);
1924 				} else {
1925 					isclass[ISCLERIC] |= bitmask;
1926 					clericspelltables[i]=strdup(field);
1927 				}
1928 			}
1929 
1930 			field = tm->QueryField(rowname, "MAGESPELL");
1931 			if (field[0]!='*') {
1932 				isclass[ISMAGE] |= bitmask;
1933 				wizardspelltables[i]=strdup(field);
1934 			}
1935 
1936 			// field 3 holds the starting xp
1937 
1938 			field = tm->QueryField(rowname, "BARDSKILL");
1939 			if (field[0]!='*') {
1940 				isclass[ISBARD] |= bitmask;
1941 			}
1942 
1943 			field = tm->QueryField(rowname, "THIEFSKILL");
1944 			if (field[0]!='*') {
1945 				isclass[ISTHIEF] |= bitmask;
1946 			}
1947 
1948 			field = tm->QueryField(rowname, "LAYHANDS");
1949 			if (field[0]!='*') {
1950 				isclass[ISPALADIN] |= bitmask;
1951 			}
1952 
1953 			field = tm->QueryField(rowname, "TURNLEVEL");
1954 			turnlevels[i]=atoi(field);
1955 
1956 			field = tm->QueryField(rowname, "BOOKTYPE");
1957 			booktypes[i]=atoi(field);
1958 			//if booktype == 3 then it is a 'divine sorcerer' class
1959 			//we shouldn't hardcode iwd2 classes this heavily
1960 			if (booktypes[i]==2) {
1961 				isclass[ISSORCERER] |= bitmask;
1962 			}
1963 
1964 			if (third) {
1965 				field = tm->QueryField(rowname, "CASTING"); // COL_HATERACE but different name
1966 				castingstat[i] = atoi(field);
1967 
1968 				field = tm->QueryField(rowname, "SPLTYPE");
1969 				iwd2spltypes[i] = atoi(field);
1970 			}
1971 
1972 			field = tm->QueryField(rowname, "HATERACE");
1973 			if (field[0]!='*') {
1974 				isclass[ISRANGER] |= bitmask;
1975 			}
1976 
1977 			field = tm->QueryField(rowname, "ABILITIES");
1978 			if (!strnicmp(field, "CLABMO", 6)) {
1979 				isclass[ISMONK] |= bitmask;
1980 			}
1981 			// everyone but pst (none at all) and iwd2 (different table)
1982 			class2kits[i].clab = strdup(field);
1983 			class2kits[i].className = strdup(rowname);
1984 
1985 			field = tm->QueryField(rowname, "NO_PROF");
1986 			defaultprof[i]=atoi(field);
1987 
1988 			bitmask <<=1;
1989 		}
1990 	} else {
1991 		classcount = 0; //well
1992 	}
1993 
1994 	i = core->GetMaximumAbility();
1995 	maximum_values[IE_STR]=i;
1996 	maximum_values[IE_INT]=i;
1997 	maximum_values[IE_DEX]=i;
1998 	maximum_values[IE_CON]=i;
1999 	maximum_values[IE_CHR]=i;
2000 	maximum_values[IE_WIS]=i;
2001 	if (ReverseToHit) {
2002 		//all games except iwd2
2003 		maximum_values[IE_ARMORCLASS]=20;
2004 	} else {
2005 		//iwd2
2006 		maximum_values[IE_ARMORCLASS]=199;
2007 	}
2008 
2009 	//initializing the vvc resource references
2010 	tm.load("damage");
2011 	if (tm) {
2012 		for (i=0;i<DAMAGE_LEVELS;i++) {
2013 			const char *tmp = tm->QueryField( i, COL_MAIN );
2014 			strnlwrcpy(d_main[i], tmp, 8);
2015 			if (d_main[i][0]=='*') {
2016 				d_main[i][0]=0;
2017 			}
2018 			tmp = tm->QueryField( i, COL_SPARKS );
2019 			strnlwrcpy(d_splash[i], tmp, 8);
2020 			if (d_splash[i][0]=='*') {
2021 				d_splash[i][0]=0;
2022 			}
2023 			tmp = tm->QueryField( i, COL_GRADIENT );
2024 			d_gradient[i]=atoi(tmp);
2025 		}
2026 	}
2027 
2028 	tm.load("overlay");
2029 	if (tm) {
2030 		ieDword mask = 1;
2031 		for (i=0;i<OVERLAY_COUNT;i++) {
2032 			hc_overlays[i] = tm->QueryField(i, 0);
2033 			if (atoi(tm->QueryField( i, 1))) {
2034 				hc_locations|=mask;
2035 			}
2036 			const char *tmp = tm->QueryField( i, 2 );
2037 			hc_flags[i] = atoi(tmp);
2038 			mask<<=1;
2039 		}
2040 	}
2041 
2042 	//csound for bg1/bg2
2043 	memset(csound,0,sizeof(csound));
2044 	if (!core->HasFeature(GF_SOUNDFOLDERS)) {
2045 		tm.load("csound");
2046 		if (tm) {
2047 			for(i=0;i<VCONST_COUNT;i++) {
2048 				const char *tmp = tm->QueryField( i, 0 );
2049 				switch(tmp[0]) {
2050 					case '*': break;
2051 					//I have no idea what this ! mean
2052 					case '!': csound[i]=tmp[1]; break;
2053 					default: csound[i]=tmp[0]; break;
2054 				}
2055 			}
2056 		}
2057 	}
2058 
2059 	tm.load("qslots");
2060 	GUIBTDefaults = (ActionButtonRow *) calloc( classcount+1,sizeof(ActionButtonRow) );
2061 
2062 	//leave room for default row at 0
2063 	for (i = 0; i <= classcount; i++) {
2064 		memcpy(GUIBTDefaults+i, &DefaultButtons, sizeof(ActionButtonRow));
2065 		if (tm && i) {
2066 			for (int j=0;j<MAX_QSLOTS;j++) {
2067 				GUIBTDefaults[i][j+3]=(ieByte) atoi( tm->QueryField(i-1,j) );
2068 			}
2069 		}
2070 	}
2071 
2072 	tm.load("qslot2", true);
2073 	if (tm) {
2074 		extraslots = tm->GetRowCount();
2075 		OtherGUIButtons = (ActionButtonRow2 *) calloc( extraslots, sizeof (ActionButtonRow2) );
2076 
2077 		for (i=0; i<extraslots; i++) {
2078 			long tmp = 0;
2079 			valid_number( tm->QueryField(i,0), tmp );
2080 			OtherGUIButtons[i].clss = (ieByte) tmp;
2081 			memcpy(OtherGUIButtons[i].buttons, &DefaultButtons, sizeof(ActionButtonRow));
2082 			for (int j=0;j<GUIBT_COUNT;j++) {
2083 				OtherGUIButtons[i].buttons[j]=(ieByte) atoi( tm->QueryField(i,j+1) );
2084 			}
2085 		}
2086 	}
2087 
2088 	tm.load("mdfeats", true);
2089 	if (tm) {
2090 		for (i=0; i<ES_COUNT; i++) {
2091 			strnuprcpy(featspells[i], tm->QueryField(i,0), sizeof(ieResRef)-1 );
2092 		}
2093 	}
2094 
2095 	tm.load("itemuse");
2096 	if (tm) {
2097 		usecount = tm->GetRowCount();
2098 		itemuse = new ItemUseType[usecount];
2099 		for (i = 0; i < usecount; i++) {
2100 			itemuse[i].stat = (ieByte) core->TranslateStat( tm->QueryField(i,0) );
2101 			strnlwrcpy(itemuse[i].table, tm->QueryField(i,1),8 );
2102 			itemuse[i].mcol = (ieByte) atoi( tm->QueryField(i,2) );
2103 			itemuse[i].vcol = (ieByte) atoi( tm->QueryField(i,3) );
2104 			itemuse[i].which = (ieByte) atoi( tm->QueryField(i,4) );
2105 			//limiting it to 0 or 1 to avoid crashes
2106 			if (itemuse[i].which!=1) {
2107 				itemuse[i].which=0;
2108 			}
2109 		}
2110 	}
2111 
2112 	tm.load("itemanim", true);
2113 	if (tm) {
2114 		animcount = tm->GetRowCount();
2115 		itemanim = new ItemAnimType[animcount];
2116 		for (i = 0; i < animcount; i++) {
2117 			strnlwrcpy(itemanim[i].itemname, tm->QueryField(i,0),8 );
2118 			itemanim[i].animation = (ieByte) atoi( tm->QueryField(i,1) );
2119 		}
2120 	}
2121 
2122 	// iwd2 has mxsplbon instead, since all casters get a bonus with high enough stats (which are not always wisdom)
2123 	// luckily, they both use the same format
2124 	if (third) {
2125 		tm.load("mxsplbon");
2126 	} else {
2127 		tm.load("mxsplwis");
2128 	}
2129 	if (tm) {
2130 		spllevels = tm->GetColumnCount(0);
2131 		int max = core->GetMaximumAbility();
2132 		mxsplwis = (int *) calloc(max*spllevels, sizeof(int));
2133 		for (i = 0; i < spllevels; i++) {
2134 			for(int j = 0; j < max; j++) {
2135 				int k = atoi(tm->GetRowName(j))-1;
2136 				if (k>=0 && k<max) {
2137 					mxsplwis[k*spllevels+i]=atoi(tm->QueryField(j,i));
2138 				}
2139 			}
2140 		}
2141 	}
2142 
2143 	tm.load("featreq", true);
2144 	if (tm) {
2145 		unsigned int stat, max;
2146 
2147 		for(i=0;i<MAX_FEATS;i++) {
2148 			//we need the MULTIPLE and MAX_LEVEL columns
2149 			//MULTIPLE: the FEAT_* stat index
2150 			//MAX_LEVEL: how many times it could be taken
2151 			stat = core->TranslateStat(tm->QueryField(i,0));
2152 			if (stat>=MAX_STATS) {
2153 				Log(WARNING, "Actor", "Invalid stat value in featreq.2da");
2154 			}
2155 			max = atoi(tm->QueryField(i,1));
2156 			//boolean feats can only be taken once, the code requires featmax for them too
2157 			if (stat && (max<1)) max=1;
2158 			featstats[i] = (ieByte) stat;
2159 			featmax[i] = (ieByte) max;
2160 		}
2161 	}
2162 
2163 	if (classcount) maxLevelForHpRoll = (int *) calloc(classcount, sizeof(int));
2164 	xpcap = (int *) calloc(classcount, sizeof(int));
2165 	AutoTable xpcapt("xpcap");
2166 	std::map<std::string, int> className2ID;
2167 
2168 	tm.load("classes");
2169 	if (!tm) {
2170 		error("Actor", "Missing classes.2da!");
2171 	}
2172 	if (iwd2class) {
2173 		// we need to set up much less here due to a saner class/level system in 3ed
2174 		Log(MESSAGE, "Actor", "Examining IWD2-style classes.2da");
2175 		AutoTable tht;
2176 		for (i=0; i<(int)tm->GetRowCount(); i++) {
2177 			const char *classname = tm->GetRowName(i);
2178 			int classis = IsClassFromName(classname);
2179 			ieDword classID = atoi(tm->QueryField(classname, "ID"));
2180 			ieDword classcol = atoi(tm->QueryField(classname, "CLASS")); // only real classes have this column at 0
2181 			const char *clab = tm->QueryField(classname, "CLAB");
2182 			if (classcol) {
2183 				// kit ids are in hex
2184 				classID = strtoul(tm->QueryField(classname, "ID"), NULL, 16);
2185 				class2kits[classcol].indices.push_back(i);
2186 				class2kits[classcol].ids.push_back(classID);
2187 				class2kits[classcol].clabs.push_back(strdup(clab));
2188 				class2kits[classcol].kitNames.push_back(strdup(classname));
2189 				continue;
2190 			} else if (i < classcount) {
2191 				// populate classesiwd2
2192 				// we need the id of the isclass name, not the current one
2193 				ieDword cid = atoi(tm->QueryField(isclassnames[i], "ID"));
2194 				classesiwd2[i] = cid;
2195 
2196 				class2kits[classID].clab = strdup(clab);
2197 			} else {
2198 				// new class out of order
2199 				Log(FATAL, "Actor", "New classes should precede any kits in classes.2da! Aborting ...");
2200 			}
2201 
2202 			xpcap[classis] = atoi(xpcapt->QueryField(classname, "VALUE"));
2203 
2204 			// set up the tohit/apr tables
2205 			char tohit[9];
2206 			strnuprcpy(tohit, tm->QueryField(classname, "TOHIT"), 8);
2207 			BABClassMap[classis] = strdup(tohit);
2208 			// the tables repeat, but we need to only load one copy
2209 			// FIXME: the attempt at skipping doesn't work!
2210 			IWD2HitTableIter it = IWD2HitTable.find(tohit);
2211 			if (it == IWD2HitTable.end()) {
2212 				tht.load(tohit, true);
2213 				if (!tht || !tohit[0]) {
2214 					error("Actor", "TOHIT table for %s does not exist!", classname);
2215 				}
2216 				ieDword row;
2217 				BABTable bt;
2218 				std::vector<BABTable> btv;
2219 				btv.reserve(tht->GetRowCount());
2220 				for (row = 0; row < tht->GetRowCount(); row++) {
2221 					bt.level = atoi(tht->GetRowName(row));
2222 					bt.bab = atoi(tht->QueryField(row, 0));
2223 					bt.apr = atoi(tht->QueryField(row, 1));
2224 					btv.push_back(bt);
2225 				}
2226 				IWD2HitTable.insert(std::make_pair (BABClassMap[classis], btv));
2227 			}
2228 
2229 			StringBuffer buffer;
2230 			buffer.appendFormatted("\tID: %d, ", classID);
2231 			buffer.appendFormatted("Name: %s, ", classname);
2232 			buffer.appendFormatted("Classis: %d, ", classis);
2233 			buffer.appendFormatted("ToHit: %s ", tohit);
2234 			buffer.appendFormatted("XPCap: %d", xpcap[classis]);
2235 
2236 			Log(DEBUG, "Actor", buffer);
2237 		}
2238 	} else {
2239 		AutoTable hptm;
2240 		//iwd2 just uses levelslotsiwd2 instead
2241 		Log(MESSAGE, "Actor", "Examining classes.2da");
2242 
2243 		//when searching the levelslots, you must search for
2244 		//levelslots[BaseStats[IE_CLASS]-1] as there is no class id of 0
2245 		levelslots = (int **) calloc(classcount, sizeof(int*));
2246 		dualswap = (int *) calloc(classcount, sizeof(int));
2247 		multi = (int *) calloc(classcount, sizeof(int));
2248 		ieDword tmpindex;
2249 
2250 		for (i=0; i<classcount; i++) {
2251 			const char* classname = tm->GetRowName(i);
2252 			//make sure we have a valid classid, then decrement
2253 			//it to get the correct array index
2254 			tmpindex = atoi(tm->QueryField(classname, "ID"));
2255 			if (!tmpindex)
2256 				continue;
2257 			className2ID[classname] = tmpindex;
2258 			tmpindex--;
2259 
2260 			StringBuffer buffer;
2261 			buffer.appendFormatted("\tID: %d ", tmpindex);
2262 			//only create the array if it isn't yet made
2263 			//i.e. barbarians would overwrite fighters in bg2
2264 			if (levelslots[tmpindex]) {
2265 				buffer.appendFormatted("Already Found!");
2266 				Log(DEBUG, "Actor", buffer);
2267 				continue;
2268 			}
2269 
2270 			buffer.appendFormatted("Name: %s ", classname);
2271 
2272 			xpcap[tmpindex] = atoi(xpcapt->QueryField(classname, "VALUE"));
2273 			buffer.appendFormatted("XPCAP: %d ", xpcap[tmpindex]);
2274 
2275 			int classis = 0;
2276 			//default all levelslots to 0
2277 			levelslots[tmpindex] = (int *) calloc(ISCLASSES, sizeof(int));
2278 
2279 			//single classes only worry about IE_LEVEL
2280 			long tmpclass = 0;
2281 			valid_number(tm->QueryField(classname, "MULTI"), tmpclass);
2282 			multi[tmpindex] = (ieDword) tmpclass;
2283 			if (!tmpclass) {
2284 				classis = IsClassFromName(classname);
2285 				if (classis>=0) {
2286 					//store the original class ID as iwd2 compatible ISCLASS (internal class number)
2287 					classesiwd2[classis] = tmpindex+1;
2288 
2289 					buffer.appendFormatted("Classis: %d ", classis);
2290 					levelslots[tmpindex][classis] = IE_LEVEL;
2291 					//get the last level when we can roll for HP
2292 					hptm.load(tm->QueryField(classname, "HP"), true);
2293 					if (hptm) {
2294 						int tmphp = 0;
2295 						int rollscolumn = hptm->GetColumnIndex("ROLLS");
2296 						while (atoi(hptm->QueryField(tmphp, rollscolumn)))
2297 							tmphp++;
2298 						buffer.appendFormatted("HPROLLMAXLVL: %d", tmphp);
2299 						if (tmphp) maxLevelForHpRoll[tmpindex] = tmphp;
2300 					}
2301 				}
2302 				Log(DEBUG, "Actor", buffer);
2303 				continue;
2304 			}
2305 
2306 			//we have to account for dual-swap in the multiclass field
2307 			ieDword numfound = 1;
2308 			size_t tmpbits = CountBits (tmpclass);
2309 
2310 			//we need all the classnames of the multi to compare with the order we load them in
2311 			//because the original game set the levels based on name order, not bit order
2312 			char **classnames = (char **) calloc(tmpbits, sizeof(char *));
2313 			classnames[0] = strtok(strdup(classname), "_");
2314 			while (numfound<tmpbits && (classnames[numfound] = strdup(strtok(NULL, "_")))) {
2315 				numfound++;
2316 			}
2317 			numfound = 0;
2318 			bool foundwarrior = false;
2319 			for (j=0; j<classcount; j++) {
2320 				//no sense continuing if we've found all to be found
2321 				if (numfound==tmpbits)
2322 					break;
2323 				if ((1<<j)&tmpclass) {
2324 					//save the IE_LEVEL information
2325 					const char* currentname = tm->GetRowName((ieDword)(tm->FindTableValue("ID", j+1)));
2326 					classis = IsClassFromName(currentname);
2327 					if (classis>=0) {
2328 						//search for the current class in the split of the names to get it's
2329 						//correct order
2330 						for (ieDword k=0; k<tmpbits; k++) {
2331 							if (strcmp(classnames[k], currentname) == 0) {
2332 								int tmplevel = 0;
2333 								if (k==0) tmplevel = IE_LEVEL;
2334 								else if (k==1) tmplevel = IE_LEVEL2;
2335 								else tmplevel = IE_LEVEL3;
2336 								levelslots[tmpindex][classis] = tmplevel;
2337 							}
2338 						}
2339 						buffer.appendFormatted("Classis: %d ", classis);
2340 
2341 						//warrior take precedence
2342 						if (!foundwarrior) {
2343 							foundwarrior = (classis==ISFIGHTER||classis==ISRANGER||classis==ISPALADIN||
2344 								classis==ISBARBARIAN);
2345 							hptm.load(tm->QueryField(currentname, "HP"), true);
2346 							if (hptm) {
2347 								int tmphp = 0;
2348 								int rollscolumn = hptm->GetColumnIndex("ROLLS");
2349 								while (atoi(hptm->QueryField(tmphp, rollscolumn)))
2350 									tmphp++;
2351 								//make sure we at least set the first class
2352 								if ((tmphp>maxLevelForHpRoll[tmpindex])||foundwarrior||numfound==0)
2353 									maxLevelForHpRoll[tmpindex]=tmphp;
2354 							}
2355 						}
2356 					}
2357 
2358 					//save the MC_WAS_ID of the first class in the dual-class
2359 					if (numfound==0 && tmpbits==2) {
2360 						if (strcmp(classnames[0], currentname) == 0) {
2361 							dualswap[tmpindex] = strtol(tm->QueryField(currentname, "MC_WAS_ID"), NULL, 0);
2362 						}
2363 					} else if (numfound==1 && tmpbits==2 && !dualswap[tmpindex]) {
2364 						dualswap[tmpindex] = strtol(tm->QueryField(currentname, "MC_WAS_ID"), NULL, 0);
2365 					}
2366 					numfound++;
2367 				}
2368 			}
2369 
2370 			for (j=0; j<(signed)tmpbits; j++) {
2371 				if (classnames[j]) {
2372 					free(classnames[j]);
2373 				}
2374 			}
2375 			free(classnames);
2376 
2377 			buffer.appendFormatted("HPROLLMAXLVL: %d ", maxLevelForHpRoll[tmpindex]);
2378 			buffer.appendFormatted("DS: %d ", dualswap[tmpindex]);
2379 			buffer.appendFormatted("MULTI: %d", multi[tmpindex]);
2380 			Log(DEBUG, "Actor", buffer);
2381 		}
2382 		/*this could be enabled to ensure all levelslots are filled with at least 0's;
2383 		*however, the access code should ensure this never happens
2384 		for (i=0; i<classcount; i++) {
2385 			if (!levelslots[i]) {
2386 				levelslots[i] = (int *) calloc(ISCLASSES, sizeof(int *));
2387 			}
2388 		}*/
2389 	}
2390 	Log(MESSAGE, "Actor", "Finished examining classes.2da");
2391 
2392 	// set the default weapon slot count for the inventory gui — if we're not in iwd2 already
2393 	if (!iwd2class) {
2394 		tm.load("numwslot", true);
2395 		if (tm) {
2396 			int rowcount = tm->GetRowCount();
2397 			for (i = 0; i < rowcount; i++) {
2398 				const char* cls = tm->GetRowName(i);
2399 				auto it = className2ID.find(cls);
2400 				int id = 0;
2401 				if (it != className2ID.end()) id = it->second;
2402 				numWeaponSlots[id] = std::min(4, atoi(tm->QueryField(i, 0)));
2403 			}
2404 		}
2405 	}
2406 	className2ID.clear();
2407 
2408 	// slurp up kitlist.2da; only iwd2 has both class and kit info in the same table
2409 	if (!iwd2class) {
2410 		tm.load("kitlist", true);
2411 		if (!tm) {
2412 			error("Actor", "Missing kitlist.2da!");
2413 		}
2414 		for (unsigned int i=0; i < tm->GetRowCount(); i++) {
2415 			char rowName[6];
2416 			snprintf(rowName, sizeof(rowName), "%d", i);
2417 			// kit usability is in hex and is sometimes used as the kit ID,
2418 			// while other times ID is the baseclass constant or-ed with the index
2419 			ieDword kitUsability = strtoul(tm->QueryField(rowName, "UNUSABLE"), NULL, 16);
2420 			int classID = atoi(tm->QueryField(rowName, "CLASS"));
2421 			const char *clab = tm->QueryField(rowName, "ABILITIES");
2422 			const char *kitName = tm->QueryField(rowName, "ROWNAME");
2423 			class2kits[classID].indices.push_back(i);
2424 			class2kits[classID].ids.push_back(kitUsability);
2425 			class2kits[classID].clabs.push_back(strdup(clab));
2426 			class2kits[classID].kitNames.push_back(strdup(kitName));
2427 		}
2428 	}
2429 
2430 	//pre-cache hit/damage/speed bonuses for weapons
2431 	wspecial.load("wspecial", true);
2432 
2433 	//dual-wielding table
2434 	tm.load("wstwowpn", true);
2435 	if (tm) {
2436 		wsdualwield = (int **) calloc(STYLE_MAX+1, sizeof(int *));
2437 		int cols = tm->GetColumnCount();
2438 		for (i=0; i<=STYLE_MAX; i++) {
2439 			wsdualwield[i] = (int *) calloc(cols, sizeof(int));
2440 			for (int j=0; j<cols; j++) {
2441 				wsdualwield[i][j] = atoi(tm->QueryField(i, j));
2442 			}
2443 		}
2444 	}
2445 
2446 	//two-handed table
2447 	tm.load("wstwohnd", true);
2448 	if (tm) {
2449 		wstwohanded = (int **) calloc(STYLE_MAX+1, sizeof(int *));
2450 		int cols = tm->GetColumnCount();
2451 		for (i=0; i<=STYLE_MAX; i++) {
2452 			wstwohanded[i] = (int *) calloc(cols, sizeof(int));
2453 			for (int j=0; j<cols; j++) {
2454 				wstwohanded[i][j] = atoi(tm->QueryField(i, j));
2455 			}
2456 		}
2457 	}
2458 
2459 	//shield table
2460 	tm.load("wsshield", true);
2461 	if (tm) {
2462 		wsswordshield = (int **) calloc(STYLE_MAX+1, sizeof(int *));
2463 		int cols = tm->GetColumnCount();
2464 		for (i=0; i<=STYLE_MAX; i++) {
2465 			wsswordshield[i] = (int *) calloc(cols, sizeof(int));
2466 			for (int j=0; j<cols; j++) {
2467 				wsswordshield[i][j] = atoi(tm->QueryField(i, j));
2468 			}
2469 		}
2470 	}
2471 
2472 	//single-handed table
2473 	tm.load("wssingle");
2474 	if (tm) {
2475 		wssingle = (int **) calloc(STYLE_MAX+1, sizeof(int *));
2476 		int cols = tm->GetColumnCount();
2477 		for (i=0; i<=STYLE_MAX; i++) {
2478 			wssingle[i] = (int *) calloc(cols, sizeof(int));
2479 			for (int j=0; j<cols; j++) {
2480 				wssingle[i][j] = atoi(tm->QueryField(i, j));
2481 			}
2482 		}
2483 	}
2484 
2485 	//unhardcoded monk bonus table
2486 	tm.load("monkbon", true);
2487 	if (tm) {
2488 		monkbon_rows = tm->GetRowCount();
2489 		monkbon_cols = tm->GetColumnCount();
2490 		monkbon = (int **) calloc(monkbon_rows, sizeof(int *));
2491 		for (unsigned i=0; i<monkbon_rows; i++) {
2492 			monkbon[i] = (int *) calloc(monkbon_cols, sizeof(int));
2493 			for (unsigned j=0; j<monkbon_cols; j++) {
2494 				monkbon[i][j] = atoi(tm->QueryField(i, j));
2495 			}
2496 		}
2497 	}
2498 
2499 	//wild magic level modifiers
2500 	for(i=0;i<20;i++) {
2501 		wmlevels[i]=(int *) calloc(MAX_LEVEL,sizeof(int) );
2502 	}
2503 	tm.load("lvlmodwm", true);
2504 	if (tm) {
2505 		int maxrow = tm->GetRowCount();
2506 		for (i=0;i<20;i++) {
2507 			for(j=0;j<MAX_LEVEL;j++) {
2508 				int row = maxrow;
2509 				if (j<row) row=j;
2510 				wmlevels[i][j]=strtol(tm->QueryField(row,i), NULL, 0);
2511 			}
2512 		}
2513 	}
2514 
2515 	// verbal constant remapping, if omitted, it is an 1-1 mapping
2516 	// TODO: allow disabled VC slots
2517 	for (i=0;i<VCONST_COUNT;i++) {
2518 		VCMap[i]=i;
2519 	}
2520 	tm.load("vcremap");
2521 	if (tm) {
2522 		int rows = tm->GetRowCount();
2523 
2524 		for (i=0;i<rows;i++) {
2525 			int row = atoi(tm->QueryField(i,0));
2526 			if (row<0 || row>=VCONST_COUNT) continue;
2527 			int value = atoi(tm->QueryField(i,1));
2528 			if (value<0 || value>=VCONST_COUNT) continue;
2529 			VCMap[row]=value;
2530 		}
2531 	}
2532 
2533 	//initializing the skill->stats conversion table (used in iwd2)
2534 	tm.load("skillsta", true);
2535 	if (tm) {
2536 		int rowcount = tm->GetRowCount();
2537 		int colcount = tm->GetColumnCount();
2538 		for (i = 0; i < rowcount; i++) {
2539 			skillstats[i] = std::vector<int>();
2540 			int j, val;
2541 			for(j = 0; j < colcount; j++) {
2542 				// the stat and ability columns need conversion into numbers
2543 				if (j < 2) {
2544 					val = core->TranslateStat(tm->QueryField(i, j));
2545 					if (j == 0) {
2546 						stat2skill[val] = i;
2547 					}
2548 				} else {
2549 					val = atoi(tm->QueryField(i, j));
2550 				}
2551 				skillstats[i].push_back (val);
2552 			}
2553 		}
2554 	}
2555 
2556 	//initializing area flag comments
2557 	tm.load("comment");
2558 	if (tm) {
2559 		int rowcount = tm->GetRowCount();
2560 		afcount = rowcount;
2561 		if (rowcount) {
2562 			afcomments = (int **) calloc(rowcount, sizeof(int *) );
2563 			while(rowcount--) {
2564 				afcomments[rowcount]=(int *) malloc(3*sizeof(int) );
2565 				for(i=0;i<3;i++) {
2566 					afcomments[rowcount][i] = strtol(tm->QueryField(rowcount,i), NULL, 0);
2567 				}
2568 			}
2569 		}
2570 	}
2571 
2572 	// dexterity modifier for thieving skills
2573 	tm.load("skilldex");
2574 	if (tm) {
2575 		int skilldexNCols = tm->GetColumnCount();
2576 		int skilldexNRows = tm->GetRowCount();
2577 		skilldex.reserve(skilldexNRows);
2578 
2579 		for (i = 0; i < skilldexNRows; i++) {
2580 			skilldex.push_back (std::vector<int>());
2581 			skilldex[i].reserve(skilldexNCols+1);
2582 			for(j = -1; j < skilldexNCols; j++) {
2583 				if (j == -1) {
2584 					skilldex[i].push_back (atoi(tm->GetRowName(i)));
2585 				} else {
2586 					skilldex[i].push_back (atoi(tm->QueryField(i, j)));
2587 				}
2588 			}
2589 		}
2590 	}
2591 
2592 	// race modifier for thieving skills
2593 	tm.load("skillrac");
2594 	int value = 0;
2595 	int racetable = core->LoadSymbol("race");
2596 	int subracetable = core->LoadSymbol("subrace");
2597 	Holder<SymbolMgr> race = NULL;
2598 	Holder<SymbolMgr> subrace = NULL;
2599 	if (racetable != -1) {
2600 		race = core->GetSymbol(racetable);
2601 	}
2602 	if (subracetable != -1) {
2603 		subrace = core->GetSymbol(subracetable);
2604 	}
2605 	if (tm) {
2606 		int cols = tm->GetColumnCount();
2607 		int rows = tm->GetRowCount();
2608 		skillrac.reserve(rows);
2609 
2610 		for (i = 0; i < rows; i++) {
2611 			skillrac.push_back (std::vector<int>());
2612 			skillrac[i].reserve(cols+1);
2613 			for(j = -1; j < cols; j++) {
2614 				if (j == -1) {
2615 					// figure out the value from the race name
2616 					if (racetable == -1) {
2617 						value = 0;
2618 					} else {
2619 						if (subracetable == -1) {
2620 							value = race->GetValue(tm->GetRowName(i));
2621 						} else {
2622 							value = subrace->GetValue(tm->GetRowName(i));
2623 						}
2624 					}
2625 					skillrac[i].push_back (value);
2626 				} else {
2627 					skillrac[i].push_back (atoi(tm->QueryField(i, j)));
2628 				}
2629 			}
2630 		}
2631 	}
2632 
2633 	//difficulty level based modifiers
2634 	tm.load("difflvls");
2635 	if (tm) {
2636 		memset(xpadjustments, 0, sizeof(xpadjustments) );
2637 		memset(dmgadjustments, 0, sizeof(dmgadjustments) );
2638 		memset(luckadjustments, 0, sizeof(luckadjustments) );
2639 		for (i=0; i<6; i++) {
2640 			dmgadjustments[i] = atoi(tm->QueryField(0, i) );
2641 			xpadjustments[i] = atoi(tm->QueryField(1, i) );
2642 			luckadjustments[i] = atoi(tm->QueryField(2, i) );
2643 		}
2644 	}
2645 
2646 	//preload stat derived animation tables
2647 	tm.load("avprefix");
2648 	delete [] avPrefix;
2649 	avBase = 0;
2650 	avCount = -1;
2651 	if (tm) {
2652 		int count = tm->GetRowCount();
2653 		if (count> 0 && count<8) {
2654 			avCount = count-1;
2655 			avPrefix = new avType[count];
2656 			avBase = strtoul(tm->QueryField(0),NULL, 0);
2657 			const char *poi = tm->QueryField(0,1);
2658 			if (*poi!='*') {
2659 				avStance = strtoul(tm->QueryField(0,1),NULL, 0);
2660 			} else {
2661 				avStance = -1;
2662 			}
2663 			for (i=0;i<avCount;i++) {
2664 				strnuprcpy(avPrefix[i].avresref, tm->QueryField(i+1), 8);
2665 				avPrefix[i].avtable.load(avPrefix[i].avresref);
2666 				if (avPrefix[i].avtable) {
2667 					avPrefix[i].stat = core->TranslateStat(avPrefix[i].avtable->QueryField(0));
2668 				} else {
2669 					avPrefix[i].stat = -1;
2670 				}
2671 			}
2672 		}
2673 	}
2674 
2675 	// races table
2676 	tm.load("races");
2677 	if (tm && !pstflags) {
2678 		int racesNRows = tm->GetRowCount();
2679 
2680 		for (i = 0; i < racesNRows; i++) {
2681 			int raceID = strtol(tm->QueryField(i, 3), NULL, 0);
2682 			int favClass = strtol(tm->QueryField(i, 8), NULL, 0);
2683 			const char *raceName = tm->GetRowName(i);
2684 			favoredMap.insert(std::make_pair(raceID, favClass));
2685 			raceID2Name.insert(std::make_pair(raceID, raceName));
2686 		}
2687 	}
2688 
2689 	// IWD, IWD2 and BG:EE have this
2690 	int splstatetable = core->LoadSymbol("splstate");
2691 	if (splstatetable != -1) {
2692 		Holder<SymbolMgr> splstate = core->GetSymbol(splstatetable);
2693 		int numstates = splstate->GetHighestValue();
2694 		if (numstates > 0) {
2695 			//rounding up
2696 			// iwd1 has a practically empty ids though, so force a minimum
2697 			SpellStatesSize = std::max(6, (numstates >> 5) + 1);
2698 		}
2699 	} else {
2700 		SpellStatesSize = 6;
2701 	}
2702 
2703 	// movement rate adjustments
2704 	extspeed.load("moverate", true);
2705 
2706 	// modal actions/state data
2707 	ReadModalStates();
2708 }
2709 
SetLockedPalette(const ieDword * gradients)2710 void Actor::SetLockedPalette(const ieDword *gradients)
2711 {
2712 	if (!anims) return; //cannot apply it (yet)
2713 	anims->LockPalette(gradients);
2714 }
2715 
UnlockPalette()2716 void Actor::UnlockPalette()
2717 {
2718 	if (!anims) return;
2719 	anims->lockPalette=false;
2720 	anims->SetColors(Modified+IE_COLORS);
2721 }
2722 
AddAnimation(const ieResRef resource,int gradient,int height,int flags)2723 void Actor::AddAnimation(const ieResRef resource, int gradient, int height, int flags)
2724 {
2725 	ScriptedAnimation *sca = gamedata->GetScriptedAnimation(resource, false);
2726 	if (!sca)
2727 		return;
2728 	sca->ZOffset = height;
2729 	if (flags&AA_PLAYONCE) {
2730 		sca->PlayOnce();
2731 	}
2732 	if (flags&AA_BLEND) {
2733 		//pst anims need this?
2734 		sca->SetBlend();
2735 	}
2736 	if (gradient!=-1) {
2737 		sca->SetPalette(gradient, 4);
2738 	}
2739 	AddVVCell(sca);
2740 }
2741 
GetSpellFailure(bool arcana) const2742 ieDword Actor::GetSpellFailure(bool arcana) const
2743 {
2744 	ieDword base = arcana?Modified[IE_SPELLFAILUREMAGE]:Modified[IE_SPELLFAILUREPRIEST];
2745 	if (HasSpellState(SS_DOMINATION)) base += 100;
2746 	// blink's malus of 20% is handled in the effect
2747 	// IWD2 has this as 20, other games as 50
2748 	if (HasSpellState(SS_DEAF)) {
2749 		base += 20;
2750 		if (!third) base += 30;
2751 	}
2752 	if (!arcana) return base;
2753 
2754 	ieDword armor = GetTotalArmorFailure();
2755 
2756 	if (armor) {
2757 		ieDword feat = GetFeat(FEAT_ARMORED_ARCANA);
2758 		if (armor<feat) armor = 0;
2759 		else armor -= feat;
2760 	}
2761 
2762 	return base+armor*5;
2763 }
2764 
2765 //dexterity AC (the lesser the better), do another negation for 3ED rules
GetDexterityAC() const2766 int Actor::GetDexterityAC() const
2767 {
2768 	if (!third) {
2769 		return core->GetDexterityBonus(STAT_DEX_AC, GetStat(IE_DEX));
2770 	}
2771 
2772 	int dexbonus = GetAbilityBonus(IE_DEX);
2773 	if (dexbonus) {
2774 		// the maximum dexterity bonus isn't stored,
2775 		// but can reliably be calculated from 8-spell failure (except for robes, which have no limit)
2776 		ieWord armtype = inventory.GetArmorItemType();
2777 		int armor = core->GetArmorFailure(armtype);
2778 
2779 		if (armor) {
2780 			armor = 8-armor;
2781 			if (dexbonus>armor) {
2782 				dexbonus = armor;
2783 			}
2784 		}
2785 
2786 		//blindness negates the dexbonus
2787 		if ((GetStat(IE_STATE_ID)&STATE_BLIND) && !HasFeat(FEAT_BLIND_FIGHT)) {
2788 			dexbonus = 0;
2789 		}
2790 	}
2791 	return dexbonus;
2792 }
2793 
2794 //wisdom AC bonus for 3ed light monks
GetWisdomAC() const2795 int Actor::GetWisdomAC() const
2796 {
2797 	if (!third || !GetStat(IE_LEVELMONK)) {
2798 		return 0;
2799 	}
2800 
2801 	int bonus = 0;
2802 	//if the monk has any typo of armor equipped, no bonus
2803 	if (GetTotalArmorFailure() == 0) {
2804 		bonus = GetAbilityBonus(IE_WIS);
2805 	}
2806 	return bonus;
2807 }
2808 
2809 //Returns the personal critical damage type in a binary compatible form (PST)
GetCriticalType() const2810 int Actor::GetCriticalType() const
2811 {
2812 	long ret = 0;
2813 	AutoTable tm("crits", true);
2814 	if (!tm) return 0;
2815 	//the ID of this PC (first 2 rows are empty)
2816 	int row = BaseStats[IE_SPECIFIC];
2817 	//defaults to 0
2818 	valid_number(tm->QueryField(row, 1), ret);
2819 	return (int) ret;
2820 }
2821 
2822 //Plays personal critical damage animation for PST PC's melee attacks
PlayCritDamageAnimation(int type)2823 void Actor::PlayCritDamageAnimation(int type)
2824 {
2825 	AutoTable tm("crits");
2826 	if (!tm) return;
2827 	//the ID's are in column 1, selected by specifics by GetCriticalType
2828 	int row = tm->FindTableValue (1, type);
2829 	if (row>=0) {
2830 		//the animations are listed in column 0
2831 		AddAnimation(tm->QueryField(row, 0), -1, 45, AA_PLAYONCE|AA_BLEND);
2832 	}
2833 }
2834 
PlayDamageAnimation(int type,bool hit)2835 void Actor::PlayDamageAnimation(int type, bool hit)
2836 {
2837 	int i;
2838 	int flags = AA_PLAYONCE;
2839 	int height = 22;
2840 	if (pstflags) {
2841 		flags |= AA_BLEND;
2842 		height = 45; // empirical like in fx_visual_spell_hit
2843 	}
2844 
2845 	Log(COMBAT, "Actor", "Damage animation type: %d", type);
2846 
2847 	switch(type&255) {
2848 		case 0:
2849 			//PST specific personal criticals
2850 			if (type&0xff00) {
2851 				PlayCritDamageAnimation(type>>8);
2852 				break;
2853 			}
2854 			//fall through
2855 		case 1: case 2: case 3: //blood
2856 			i = anims->GetBloodColor();
2857 			if (!i) i = d_gradient[type];
2858 			if(hit) {
2859 				AddAnimation(d_main[type], i, height, flags);
2860 			}
2861 			break;
2862 		case 4: case 5: case 6: //fire
2863 			if(hit) {
2864 				AddAnimation(d_main[type], d_gradient[type], height, flags);
2865 			}
2866 			for(i=DL_FIRE;i<=type;i++) {
2867 				AddAnimation(d_splash[i], d_gradient[i], height, flags);
2868 			}
2869 			break;
2870 		case 7: case 8: case 9: //electricity
2871 			if (hit) {
2872 				AddAnimation(d_main[type], d_gradient[type], height, flags);
2873 			}
2874 			for(i=DL_ELECTRICITY;i<=type;i++) {
2875 				AddAnimation(d_splash[i], d_gradient[i], height, flags);
2876 			}
2877 			break;
2878 		case 10: case 11: case 12://cold
2879 			if (hit) {
2880 				AddAnimation(d_main[type], d_gradient[type], height, flags);
2881 			}
2882 			break;
2883 		case 13: case 14: case 15://acid
2884 			if (hit) {
2885 				AddAnimation(d_main[type], d_gradient[type], height, flags);
2886 			}
2887 			break;
2888 		case 16: case 17: case 18://disintegrate
2889 			if (hit) {
2890 				AddAnimation(d_main[type], d_gradient[type], height, flags);
2891 			}
2892 			break;
2893 	}
2894 }
2895 
ClampStat(unsigned int StatIndex,ieDword Value) const2896 ieDword Actor::ClampStat(unsigned int StatIndex, ieDword Value) const
2897 {
2898 	if (StatIndex < MAX_STATS) {
2899 		if ((signed) Value < -100) {
2900 			Value = (ieDword) -100;
2901 		} else {
2902 			if (maximum_values[StatIndex] > 0) {
2903 				if ((signed)Value > 0 && Value > maximum_values[StatIndex]) {
2904 					Value = maximum_values[StatIndex];
2905 				}
2906 			}
2907 		}
2908 	}
2909 	return Value;
2910 }
2911 
SetStat(unsigned int StatIndex,ieDword Value,int pcf)2912 bool Actor::SetStat(unsigned int StatIndex, ieDword Value, int pcf)
2913 {
2914 	if (StatIndex >= MAX_STATS) {
2915 		return false;
2916 	}
2917 	Value = ClampStat(StatIndex, Value);
2918 
2919 	unsigned int previous = GetSafeStat(StatIndex);
2920 	if (Modified[StatIndex]!=Value) {
2921 		Modified[StatIndex] = Value;
2922 	}
2923 	if (previous!=Value) {
2924 		if (pcf) {
2925 			PostChangeFunctionType f = post_change_functions[StatIndex];
2926 			if (f) {
2927 				(*f)(this, previous, Value);
2928 			}
2929 		}
2930 	}
2931 	return true;
2932 }
2933 
GetMod(unsigned int StatIndex) const2934 int Actor::GetMod(unsigned int StatIndex) const
2935 {
2936 	if (StatIndex >= MAX_STATS) {
2937 		return 0xdadadada;
2938 	}
2939 	return (signed) Modified[StatIndex] - (signed) BaseStats[StatIndex];
2940 }
2941 /** Returns a Stat Base Value */
GetBase(unsigned int StatIndex) const2942 ieDword Actor::GetBase(unsigned int StatIndex) const
2943 {
2944 	if (StatIndex >= MAX_STATS) {
2945 		return 0xffff;
2946 	}
2947 	return BaseStats[StatIndex];
2948 }
2949 
2950 /** Sets a Stat Base Value */
2951 /** If required, modify the modified value and run the pcf function */
SetBase(unsigned int StatIndex,ieDword Value)2952 bool Actor::SetBase(unsigned int StatIndex, ieDword Value)
2953 {
2954 	if (StatIndex >= MAX_STATS) {
2955 		return false;
2956 	}
2957 	ieDword diff = Modified[StatIndex]-BaseStats[StatIndex];
2958 
2959 	//maximize the base stat
2960 	Value = ClampStat(StatIndex, Value);
2961 	BaseStats[StatIndex] = Value;
2962 
2963 	//if already initialized, then the modified stats
2964 	//might need to run the post change function (stat change can kill actor)
2965 	SetStat (StatIndex, Value+diff, InternalFlags&IF_INITIALIZED);
2966 	return true;
2967 }
2968 
SetBaseNoPCF(unsigned int StatIndex,ieDword Value)2969 bool Actor::SetBaseNoPCF(unsigned int StatIndex, ieDword Value)
2970 {
2971 	if (StatIndex >= MAX_STATS) {
2972 		return false;
2973 	}
2974 	ieDword diff = Modified[StatIndex]-BaseStats[StatIndex];
2975 
2976 	//maximize the base stat
2977 	Value = ClampStat(StatIndex, Value);
2978 	BaseStats[StatIndex] = Value;
2979 
2980 	//if already initialized, then the modified stats
2981 	//might need to run the post change function (stat change can kill actor)
2982 	SetStat (StatIndex, Value+diff, 0);
2983 	return true;
2984 }
2985 
SetBaseBit(unsigned int StatIndex,ieDword Value,bool setreset)2986 bool Actor::SetBaseBit(unsigned int StatIndex, ieDword Value, bool setreset)
2987 {
2988 	if (StatIndex >= MAX_STATS) {
2989 		return false;
2990 	}
2991 	if (setreset) {
2992 		BaseStats[StatIndex] |= Value;
2993 	} else {
2994 		BaseStats[StatIndex] &= ~Value;
2995 	}
2996 	//if already initialized, then the modified stats
2997 	//need to run the post change function (stat change can kill actor)
2998 	if (setreset) {
2999 		SetStat (StatIndex, Modified[StatIndex]|Value, InternalFlags&IF_INITIALIZED);
3000 	} else {
3001 		SetStat (StatIndex, Modified[StatIndex]&~Value, InternalFlags&IF_INITIALIZED);
3002 	}
3003 	return true;
3004 }
3005 
GetStateString() const3006 const unsigned char *Actor::GetStateString() const
3007 {
3008 	if (!PCStats) {
3009 		return NULL;
3010 	}
3011 	ieByte *tmp = PCStats->PortraitIconString;
3012 	ieWord *Icons = PCStats->PortraitIcons;
3013 	int j=0;
3014 	for (int i=0;i<MAX_PORTRAIT_ICONS;i++) {
3015 		if (!(Icons[i]&0xff00)) {
3016 			tmp[j++]=(ieByte) ((Icons[i]&0xff)+66);
3017 		}
3018 	}
3019 	tmp[j]=0;
3020 	return tmp;
3021 }
3022 
AddPortraitIcon(ieByte icon)3023 void Actor::AddPortraitIcon(ieByte icon)
3024 {
3025 	if (!PCStats) {
3026 		return;
3027 	}
3028 	ieWord *Icons = PCStats->PortraitIcons;
3029 
3030 	for(int i=0;i<MAX_PORTRAIT_ICONS;i++) {
3031 		if (Icons[i]==0xffff) {
3032 			Icons[i]=icon;
3033 			return;
3034 		}
3035 		if (icon == (Icons[i]&0xff)) {
3036 			return;
3037 		}
3038 	}
3039 }
3040 
DisablePortraitIcon(ieByte icon)3041 void Actor::DisablePortraitIcon(ieByte icon)
3042 {
3043 	if (!PCStats) {
3044 		return;
3045 	}
3046 	ieWord *Icons = PCStats->PortraitIcons;
3047 	int i;
3048 
3049 	for(i=0;i<MAX_PORTRAIT_ICONS;i++) {
3050 		if (icon == (Icons[i]&0xff)) {
3051 			Icons[i]=0xff00|icon;
3052 			return;
3053 		}
3054 	}
3055 }
3056 
3057 
3058 //hack to get the proper casting sounds of copied images
GetCGGender() const3059 ieDword Actor::GetCGGender() const
3060 {
3061 	ieDword gender = Modified[IE_SEX];
3062 	if (gender == SEX_ILLUSION) {
3063 		const Actor *master = core->GetGame()->GetActorByGlobalID(Modified[IE_PUPPETMASTERID]);
3064 		if (master) {
3065 			gender = master->Modified[IE_SEX];
3066 		}
3067 	}
3068 
3069 	return gender;
3070 }
3071 
CheckPuppet(Actor * puppet,ieDword type)3072 void Actor::CheckPuppet(Actor *puppet, ieDword type)
3073 {
3074 	if (!puppet) return;
3075 	if (puppet->Modified[IE_STATE_ID]&STATE_DEAD) return;
3076 
3077 	switch(type) {
3078 		case 1:
3079 			Modified[IE_STATE_ID] |= state_invisible;
3080 			//also set the improved invisibility flag where available
3081 			if (!pstflags) {
3082 				Modified[IE_STATE_ID]|=STATE_INVIS2;
3083 			}
3084 			break;
3085 		case 2:
3086 			if (InterruptCasting) {
3087 				// dispel the projected image if there is any
3088 				puppet->DestroySelf();
3089 				return;
3090 			}
3091 			Modified[IE_HELD]=1;
3092 			AddPortraitIcon(PI_PROJIMAGE);
3093 			Modified[IE_STATE_ID]|=STATE_HELPLESS;
3094 			break;
3095 	}
3096 	Modified[IE_PUPPETTYPE] = type;
3097 	Modified[IE_PUPPETID] = puppet->GetGlobalID();
3098 }
3099 
3100 
3101 /** call this after load, to apply effects */
RefreshEffects(EffectQueue * fx)3102 void Actor::RefreshEffects(EffectQueue *fx)
3103 {
3104 	ieDword previous[MAX_STATS];
3105 
3106 	//put all special cleanup calls here
3107 	CharAnimations* anims = GetAnims();
3108 	if (anims) {
3109 		anims->CheckColorMod();
3110 	}
3111 	spellbook.ClearBonus();
3112 	memset(BardSong,0,sizeof(ieResRef));
3113 	memset(projectileImmunity,0,ProjectileSize*sizeof(ieDword));
3114 
3115 	//initialize base stats
3116 	bool first = !(InternalFlags&IF_INITIALIZED);
3117 
3118 	if (first) {
3119 		InternalFlags|=IF_INITIALIZED;
3120 		memcpy( previous, BaseStats, MAX_STATS * sizeof( ieDword ) );
3121 	} else {
3122 		memcpy( previous, Modified, MAX_STATS * sizeof( ieDword ) );
3123 	}
3124 	PrevStats = &previous[0];
3125 
3126 	memcpy( Modified, BaseStats, MAX_STATS * sizeof( ieDword ) );
3127 	if (PCStats) {
3128 		memset( PCStats->PortraitIcons, -1, sizeof(PCStats->PortraitIcons) );
3129 	}
3130 	if (SpellStatesSize) {
3131 		memset(spellStates, 0, sizeof(ieDword) * SpellStatesSize);
3132 	}
3133 	AC.ResetAll();
3134 	ToHit.ResetAll(); // effects can result in the change of any of the boni, so we need to reset all
3135 
3136 	if (fx) {
3137 		fx->SetOwner(this);
3138 		fx->AddAllEffects(this, Pos);
3139 		delete fx;
3140 		//copy back the original stats, because the effects
3141 		//will be reapplied in ApplyAllEffects again
3142 		memcpy( Modified, BaseStats, MAX_STATS * sizeof( ieDword ) );
3143 		if (SpellStatesSize) {
3144 			memset(spellStates, 0, sizeof(ieDword) * SpellStatesSize);
3145 		}
3146 		//also clear the spell bonuses just given, they will be
3147 		//recalculated below again
3148 		spellbook.ClearBonus();
3149 		//AC.ResetAll(); // TODO: check if this is needed
3150 		//ToHit.ResetAll();
3151 	}
3152 
3153 	// some VVCs are controlled by stats (and so by PCFs), the rest have 'effect_owned' set
3154 	for (ScriptedAnimation* vvc : vfxQueue) {
3155 		if (vvc->effect_owned) vvc->active = false;
3156 	}
3157 
3158 	// apply palette changes not caused by persistent effects
3159 	if (Modified[IE_STATE_ID] & STATE_PETRIFIED) {
3160 		SetLockedPalette(fullstone);
3161 	} else if (Modified[IE_STATE_ID] & STATE_FROZEN) {
3162 		SetLockedPalette(fullwhite);
3163 	}
3164 
3165 	// give the 3ed save bonus before applying the effects, since they may do extra rolls
3166 	if (third) {
3167 		Modified[IE_SAVEWILL] += GetAbilityBonus(IE_WIS);
3168 		Modified[IE_SAVEREFLEX] += GetAbilityBonus(IE_DEX);
3169 		Modified[IE_SAVEFORTITUDE] += GetAbilityBonus(IE_CON);
3170 		// paladins add their charisma modifier to all saving throws
3171 		if (GetPaladinLevel()) {
3172 			Modified[IE_SAVEWILL] += GetAbilityBonus(IE_CHR);
3173 			Modified[IE_SAVEREFLEX] += GetAbilityBonus(IE_CHR);
3174 			Modified[IE_SAVEFORTITUDE] += GetAbilityBonus(IE_CHR);
3175 		}
3176 	}
3177 
3178 	fxqueue.ApplyAllEffects( this );
3179 
3180 	if (previous[IE_PUPPETID]) {
3181 		CheckPuppet(core->GetGame()->GetActorByGlobalID(previous[IE_PUPPETID]), previous[IE_PUPPETTYPE]);
3182 	}
3183 
3184 	//move this further down if needed
3185 	PrevStats = NULL;
3186 
3187 	for (std::list<TriggerEntry>::iterator m = triggers.begin(); m != triggers.end (); m++) {
3188 		m->flags |= TEF_PROCESSED_EFFECTS;
3189 
3190 		// snap out of charm if the charmer hurt us
3191 		if (m->triggerID == trigger_attackedby) {
3192 			Actor *attacker = core->GetGame()->GetActorByGlobalID(LastAttacker);
3193 			if (attacker) {
3194 				int revertToEA = 0;
3195 				if (Modified[IE_EA] == EA_CHARMED && attacker->GetStat(IE_EA) <= EA_GOODCUTOFF) {
3196 					revertToEA = EA_ENEMY;
3197 				} else if (Modified[IE_EA] == EA_CHARMEDPC && attacker->GetStat(IE_EA) >= EA_EVILCUTOFF) {
3198 					revertToEA = EA_PC;
3199 				}
3200 				if (revertToEA) {
3201 					// remove only the plain charm effect
3202 					Effect *charmfx = fxqueue.HasEffectWithParam(fx_set_charmed_state_ref, 1);
3203 					if (!charmfx) charmfx = fxqueue.HasEffectWithParam(fx_set_charmed_state_ref, 1001);
3204 					if (charmfx) {
3205 						SetStat(IE_EA, revertToEA, 1);
3206 						fxqueue.RemoveEffect(charmfx);
3207 					}
3208 				}
3209 			}
3210 		}
3211 	}
3212 	// we need to recalc these, since the stats or equipped gear may have changed (and this is relevant in iwd2)
3213 	AC.SetWisdomBonus(GetWisdomAC());
3214 	// FIXME: but the effects may reset this too and we shouldn't touch it in that case (flatfooted!)
3215 	// flatfooted by invisible attacker: this is handled by GetDefense and ok
3216 	AC.SetDexterityBonus(GetDexterityAC());
3217 
3218 	if (HasPlayerClass()) {
3219 		RefreshPCStats();
3220 	}
3221 
3222 	//if the animation ID was not modified by any effect, it may still be modified by something else
3223 	// but not if pst is playing disguise tricks (GameScript::SetNamelessDisguise)
3224 	ieDword pst_appearance = 0;
3225 	if (pstflags) {
3226 		core->GetGame()->locals->Lookup("APPEARANCE", pst_appearance);
3227 	}
3228 	if (Modified[IE_SEX] != BaseStats[IE_SEX] && pst_appearance == 0) {
3229 		UpdateAnimationID(true);
3230 	}
3231 
3232 	//delayed HP adjustment hack (after max HP modification)
3233 	//as it's triggered by PCFs from the previous tick, it should probably run before current PCFs
3234 	if (first && checkHP == 2) {
3235 		//could not set this in the constructor
3236 		checkHPTime = core->GetGame()->GameTime;
3237 	} else if (checkHP && checkHPTime != core->GetGame()->GameTime) {
3238 		checkHP = 0;
3239 		pcf_hitpoint(this, 0, BaseStats[IE_HITPOINTS]);
3240 	}
3241 
3242 	for (int i=0; i < MAX_STATS; ++i) {
3243 		if (first || Modified[i]!=previous[i]) {
3244 			PostChangeFunctionType f = post_change_functions[i];
3245 			if (f) {
3246 				(*f)(this, previous[i], Modified[i]);
3247 			}
3248 		}
3249 	}
3250 
3251 	// manually update the overlays
3252 	// we make sure to set them without pcfs, since they would trample each other otherwise
3253 	if (Modified[IE_SANCTUARY] != BaseStats[IE_SANCTUARY]) {
3254 		pcf_sanctuary(this, BaseStats[IE_SANCTUARY], Modified[IE_SANCTUARY]);
3255 	}
3256 
3257 	//add wisdom/casting_ability bonus spells
3258 	if (mxsplwis) {
3259 		if (spellbook.IsIWDSpellBook()) {
3260 			// check each class separately for the casting stat and booktype (luckily there is no bonus for domain spells)
3261 			for (int i = 0; i < ISCLASSES; ++i) {
3262 				int level = GetClassLevel(i);
3263 				int booktype = booksiwd2[i]; // ieIWD2SpellType
3264 				if (!level || booktype == -1) {
3265 					continue;
3266 				}
3267 				level = Modified[castingstat[classesiwd2[i]]];
3268 				if (level--) {
3269 					spellbook.BonusSpells(booktype, spllevels, mxsplwis+spllevels*level);
3270 				}
3271 			}
3272 		} else {
3273 			int level = Modified[IE_WIS];
3274 			if (level--) {
3275 				spellbook.BonusSpells(IE_SPELL_TYPE_PRIEST, spllevels, mxsplwis+spllevels*level);
3276 			}
3277 		}
3278 	}
3279 
3280 	// iwd2 barbarian speed increase isn't handled like for monks (normal clab)!?
3281 	if (third && GetBarbarianLevel()) {
3282 		Modified[IE_MOVEMENTRATE] += 1;
3283 	}
3284 
3285 	// check if any new portrait icon was removed or added
3286 	if (PCStats) {
3287 		if (memcmp(PCStats->PreviousPortraitIcons, PCStats->PortraitIcons, sizeof(PCStats->PreviousPortraitIcons))) {
3288 			core->SetEventFlag(EF_PORTRAIT);
3289 			memcpy( PCStats->PreviousPortraitIcons, PCStats->PortraitIcons, sizeof(PCStats->PreviousPortraitIcons) );
3290 		}
3291 	}
3292 	if (Immobile()) {
3293 		timeStartStep = core->GetGame()->Ticks;
3294 	}
3295 }
3296 
GetProficiency(int proftype) const3297 int Actor::GetProficiency(int proftype) const
3298 {
3299 	switch(proftype) {
3300 	case -2: //hand to hand old style
3301 		return 1;
3302 	case -1: //no proficiency
3303 		return 0;
3304 	default:
3305 		//bg1 style proficiencies
3306 		if(proftype>=0 && proftype<=IE_EXTRAPROFICIENCY20-IE_PROFICIENCYBASTARDSWORD) {
3307 			return GetStat(IE_PROFICIENCYBASTARDSWORD+proftype);
3308 		}
3309 
3310 		//bg2 style proficiencies
3311 		if (proftype>=IE_PROFICIENCYBASTARDSWORD && proftype<=IE_EXTRAPROFICIENCY20) {
3312 			return GetStat(proftype);
3313 		}
3314 		return 0;
3315 	}
3316 }
3317 
3318 // recalculates the constitution bonus to hp and adds it to the stat
RefreshHP()3319 void Actor::RefreshHP() {
3320 	// calculate the hp bonus for each level
3321 	//	single-classed characters:
3322 	//		apply full constitution bonus for levels up (and including) to maxLevelForHpRoll
3323 	//	dual-classed characters:
3324 	//		while inactive, there is no consititution bonus and hp gain AT ALL
3325 	//		afterwards, the same applies as for single-classed characters again
3326 	//			consititution bonus is NOT taken from the max of classes
3327 	//	multi-classed characters:
3328 	//		apply the highest constitution bonus for levels up (and including) to maxLevelForHpRoll (already the max of the classes)
3329 	//		BUT divide it by the number of classes (ideally the last one to levelup should get all the fractions)
3330 	//	for levels after maxLevelForHpRoll there is NO constitution bonus anymore
3331 	// IN IWD2, it's a simple level*conbon calculation without any fiddling
3332 	int bonus;
3333 	// this is wrong for dual-classed (so we override it later)
3334 	// and sometimes marginally wrong for multi-classed (but we usually round the average up)
3335 	int bonlevel = GetXPLevel(true);
3336 	ieDword bonindex = BaseStats[IE_CLASS]-1;
3337 
3338 	//we must limit the levels to the max allowable
3339 	if (!third) {
3340 		if (bonlevel>maxLevelForHpRoll[bonindex]) {
3341 			bonlevel = maxLevelForHpRoll[bonindex];
3342 		}
3343 	}
3344 	if (IsDualClassed()) {
3345 		int oldbonus = 0;
3346 
3347 		// just the old consititution bonus
3348 		int oldlevel = IsDualSwap() ? BaseStats[IE_LEVEL] : BaseStats[IE_LEVEL2];
3349 		bonlevel = IsDualSwap() ? BaseStats[IE_LEVEL2] : BaseStats[IE_LEVEL];
3350 		oldlevel = (oldlevel > maxLevelForHpRoll[bonindex]) ? maxLevelForHpRoll[bonindex] : oldlevel;
3351 		// give the bonus only for the levels where there were actually rolls
3352 		// if we wanted to be really strict, the old bonindex and max roll level would need to be looked up
3353 		if (oldlevel == maxLevelForHpRoll[bonindex]) {
3354 			bonlevel = 0;
3355 		} else {
3356 			bonlevel -= oldlevel; // the actual number of "rolling" levels for the new bonus
3357 			if (bonlevel+oldlevel > maxLevelForHpRoll[bonindex]) {
3358 				bonlevel = maxLevelForHpRoll[bonindex] - oldlevel;
3359 			}
3360 		}
3361 		if (bonlevel < 0) bonlevel = 0;
3362 		if (Modified[IE_MC_FLAGS] & (MC_WAS_FIGHTER|MC_WAS_RANGER)) {
3363 			oldbonus = core->GetConstitutionBonus(STAT_CON_HP_WARRIOR, Modified[IE_CON]);
3364 		} else {
3365 			oldbonus = core->GetConstitutionBonus(STAT_CON_HP_NORMAL, Modified[IE_CON]);
3366 		}
3367 		bonus = oldbonus * oldlevel;
3368 
3369 		// but if the class is already reactivated ...
3370 		if (!IsDualInactive()) {
3371 			// add in the bonus for the levels of the new class
3372 			// since there are no warrior to warrior dual-classes, just invert the previous check to get the right conmod
3373 			if (Modified[IE_MC_FLAGS] & (MC_WAS_FIGHTER|MC_WAS_RANGER)) {
3374 				bonus += bonlevel * core->GetConstitutionBonus(STAT_CON_HP_NORMAL, Modified[IE_CON]);
3375 			} else {
3376 				bonus += GetHpAdjustment(bonlevel);
3377 			}
3378 		}
3379 	} else {
3380 		bonus = GetHpAdjustment(bonlevel);
3381 	}
3382 
3383 	if (bonus<0 && (Modified[IE_MAXHITPOINTS]+bonus)<=0) {
3384 		bonus=1-Modified[IE_MAXHITPOINTS];
3385 	}
3386 
3387 	//we still apply the maximum bonus to dead characters, but don't apply
3388 	//to current HP, or we'd have dead characters showing as having hp
3389 	Modified[IE_MAXHITPOINTS]+=bonus;
3390 	// applying the bonus to the current hitpoints is trickier, since we don't want to cause regeneration
3391 	/* the following is not reliable, since the hp may become exactly oldmax via other means too
3392 	ieDword oldmax = Modified[IE_MAXHITPOINTS];
3393 	if (!(BaseStats[IE_STATE_ID]&STATE_DEAD)) {
3394 		// for now only apply it to fully healed actors iff the bonus is positive (fixes starting hp)
3395 		if (BaseStats[IE_HITPOINTS] == oldmax && bonus > 0) {
3396 			BaseStats[IE_HITPOINTS] += bonus;
3397 		}
3398 	}*/
3399 }
3400 
3401 // refresh stats on creatures (PC or NPC) with a valid class (not animals etc)
3402 // internal use only, and this is maybe a stupid name :)
RefreshPCStats()3403 void Actor::RefreshPCStats() {
3404 	RefreshHP();
3405 
3406 	Game *game = core->GetGame();
3407 	//morale recovery every xth AI cycle ... except for pst pcs
3408 	int mrec = GetStat(IE_MORALERECOVERYTIME);
3409 	if (mrec && ShouldModifyMorale()) {
3410 		if (!(game->GameTime%mrec)) {
3411 			int morale = (signed) BaseStats[IE_MORALE];
3412 			if (morale < 10) {
3413 				NewBase(IE_MORALE, 1, MOD_ADDITIVE);
3414 			} else if (morale > 10) {
3415 				NewBase(IE_MORALE, (ieDword) -1, MOD_ADDITIVE);
3416 			}
3417 		}
3418 	}
3419 
3420 	// handle intoxication
3421 	// the cutoff is at half of max, coinciding with where the intoxmod penalties start
3422 	// TODO: intoxmod, intoxcon
3423 	if (BaseStats[IE_INTOXICATION] >= 50) {
3424 		AddPortraitIcon(PI_DRUNK);
3425 	} else {
3426 		DisablePortraitIcon(PI_DRUNK);
3427 	}
3428 
3429 	//get the wspattack bonuses for proficiencies
3430 	WeaponInfo wi;
3431 	ITMExtHeader *header = GetWeapon(wi, false);
3432 	ieDword stars;
3433 	int dualwielding = IsDualWielding();
3434 	stars = GetProficiency(wi.prof)&PROFS_MASK;
3435 
3436 	// tenser's transformation ensures the actor is at least proficient with any weapon
3437 	if (!stars && HasSpellState(SS_TENSER)) stars = 1;
3438 
3439 	if (header) {
3440 		//wspattack appears to only effect warriors
3441 		int defaultattacks = 2 + 2*dualwielding;
3442 		if (stars) {
3443 			// In bg2 the proficiency and warrior level bonus is added after effects, so also ranged weapons are affected,
3444 			// since their rate of fire (apr) is set using an effect with a flat modifier.
3445 			// SetBase will compensate only for the difference between the current two stats, not considering the default
3446 			// example: actor with a bow gets 4 due to the equipping effect, while the wspatck bonus is 0-3
3447 			// the adjustment results in a base of 2-5 (2+[0-3]) and the modified stat degrades to 4+(4-[2-5]) = 8-[2-5] = 3-6
3448 			// instead of 4+[0-3] = 4-7
3449 			// For a master ranger at level 14, the difference ends up as 2 (1 apr).
3450 			// FIXME: but this isn't universally true or improved haste couldn't double the total apr! For the above case, we're half apr off.
3451 			ieDword warriorLevel = GetWarriorLevel();
3452 			if (warriorLevel) {
3453 				int mod = Modified[IE_NUMBEROFATTACKS] - BaseStats[IE_NUMBEROFATTACKS];
3454 				int bonus = gamedata->GetWeaponStyleAPRBonus(stars, warriorLevel - 1);
3455 				BaseStats[IE_NUMBEROFATTACKS] = defaultattacks + bonus;
3456 				if (GetAttackStyle() == WEAPON_RANGED) { // FIXME: should actually check if a set-apr opcode variant was used
3457 					Modified[IE_NUMBEROFATTACKS] += bonus; // no default
3458 				} else {
3459 					Modified[IE_NUMBEROFATTACKS] = BaseStats[IE_NUMBEROFATTACKS] + mod;
3460 				}
3461 			} else {
3462 				SetBase(IE_NUMBEROFATTACKS, defaultattacks + gamedata->GetWeaponStyleAPRBonus(stars, 0));
3463 			}
3464 		} else {
3465 			// unproficient user - force defaultattacks
3466 			SetBase(IE_NUMBEROFATTACKS, defaultattacks);
3467 		}
3468 	}
3469 
3470 	// apply the intelligence and wisdom bonus to lore
3471 	Modified[IE_LORE] += core->GetLoreBonus(0, Modified[IE_INT]) + core->GetLoreBonus(0, Modified[IE_WIS]);
3472 
3473 	UpdateFatigue();
3474 
3475 	// add luck bonus from difficulty
3476 	Modified[IE_LUCK] += luckadjustments[GameDifficulty - 1];
3477 
3478 	// regenerate actors with high enough constitution
3479 	int rate = GetConHealAmount();
3480 	if (rate && !(game->GameTime % rate)) {
3481 		if (core->HasFeature(GF_AREA_OVERRIDE) && game->GetPC(0, false) == this) {
3482 			NewBase(IE_HITPOINTS, 1, MOD_ADDITIVE);
3483 			// eeeh, no token (Heal: 1)
3484 			if (Modified[IE_HITPOINTS] < Modified[IE_MAXHITPOINTS]) {
3485 				String* text = core->GetString(28895);
3486 				text->push_back(L'1');
3487 				displaymsg->DisplayString(*text, DMC_BG2XPGREEN, this);
3488 				delete text;
3489 			}
3490 		} else{
3491 			NewBase(IE_HITPOINTS, 1, MOD_ADDITIVE);
3492 		}
3493 	}
3494 
3495 	// adjust thieving skills with dex and race
3496 	// table header is in this order:
3497 	// PICK_POCKETS  OPEN_LOCKS  FIND_TRAPS  MOVE_SILENTLY  HIDE_IN_SHADOWS  DETECT_ILLUSION  SET_TRAPS
3498 	Modified[IE_PICKPOCKET] += GetSkillBonus(1);
3499 	Modified[IE_LOCKPICKING] += GetSkillBonus(2);
3500 	// these are governed by other stats in iwd2 (int) or don't exist (set traps)
3501 	if (!third) {
3502 		Modified[IE_TRAPS] += GetSkillBonus(3);
3503 		Modified[IE_DETECTILLUSIONS] += GetSkillBonus(6);
3504 		Modified[IE_SETTRAPS] += GetSkillBonus(7);
3505 	}
3506 	Modified[IE_STEALTH] += GetSkillBonus(4);
3507 	Modified[IE_HIDEINSHADOWS] += GetSkillBonus(5);
3508 
3509 	if (third) {
3510 		ieDword LayOnHandsAmount = GetPaladinLevel();
3511 		if (LayOnHandsAmount) {
3512 			int mod = GetAbilityBonus(IE_CHR, Modified[IE_CHR]);
3513 			if (mod > 1) {
3514 				LayOnHandsAmount *= mod;
3515 			}
3516 		}
3517 		BaseStats[IE_LAYONHANDSAMOUNT] = LayOnHandsAmount;
3518 		Modified[IE_LAYONHANDSAMOUNT] = LayOnHandsAmount;
3519 	}
3520 
3521 }
3522 
GetConHealAmount() const3523 int Actor::GetConHealAmount() const
3524 {
3525 	int rate = 0;
3526 	Game *game = core->GetGame();
3527 	if (!game) return rate;
3528 
3529 	if (core->HasFeature(GF_AREA_OVERRIDE) && game->GetPC(0, false) == this) {
3530 		rate = core->GetConstitutionBonus(STAT_CON_TNO_REGEN, Modified[IE_CON]);
3531 	} else {
3532 		rate = core->GetConstitutionBonus(STAT_CON_HP_REGEN, Modified[IE_CON]);
3533 		rate *= AI_UPDATE_TIME;
3534 	}
3535 	return rate;
3536 }
3537 
3538 // add fatigue every 4 hours since resting and check if the actor is penalised for it
UpdateFatigue()3539 void Actor::UpdateFatigue()
3540 {
3541 	Game *game = core->GetGame();
3542 	if (!InParty || !game->GameTime) {
3543 		return;
3544 	}
3545 
3546 	bool updated = false;
3547 	if (!TicksLastRested) {
3548 		// just loaded the game; approximate last rest
3549 		TicksLastRested = game->GameTime - (2*core->Time.hour_size) * (2*GetBase(IE_FATIGUE)+1);
3550 		updated = true;
3551 	} else if (LastFatigueCheck) {
3552 		ieDword FatigueDiff = (game->GameTime - TicksLastRested) / (4*core->Time.hour_size)
3553 		                    - (LastFatigueCheck - TicksLastRested) / (4*core->Time.hour_size);
3554 		if (FatigueDiff) {
3555 			NewBase(IE_FATIGUE, FatigueDiff, MOD_ADDITIVE);
3556 			updated = true;
3557 		}
3558 	}
3559 	LastFatigueCheck = game->GameTime;
3560 
3561 	if (!core->HasFeature(GF_AREA_OVERRIDE)) {
3562 		// pst has TNO regeneration stored there
3563 		// shouldn't we check for our own flag, though?
3564 		// FIXME: the Con bonus is applied dynamically, but this doesn't appear to conform to
3565 		// the original engine behavior?  we should probably apply the bonus on rest
3566 		int FatigueBonus = core->GetConstitutionBonus(STAT_CON_FATIGUE, Modified[IE_CON]);
3567 		if ((signed) Modified[IE_FATIGUE] >= FatigueBonus) {
3568 			Modified[IE_FATIGUE] -= FatigueBonus;
3569 		} else {
3570 			Modified[IE_FATIGUE] = 0;
3571 		}
3572 	}
3573 
3574 	int LuckMod = core->ResolveStatBonus(this, "fatigue"); // fatigmod.2da
3575 	Modified[IE_LUCK] += LuckMod;
3576 	if (LuckMod < 0) {
3577 		AddPortraitIcon(PI_FATIGUE);
3578 		if (updated) {
3579 			// stagger the complaint, so long travels don't cause a fatigue choir
3580 			FatigueComplaintDelay = core->Roll(3, core->Time.round_size, 0) * 5;
3581 		}
3582 	} else {
3583 		// the icon can be added manually; eg. by spcl321 in bg2 (berserker enrage)
3584 		if (!fxqueue.HasEffectWithParam(fx_display_portrait_icon_ref, PI_FATIGUE)) {
3585 			DisablePortraitIcon(PI_FATIGUE);
3586 		}
3587 		FatigueComplaintDelay = 0;
3588 	}
3589 
3590 	if (FatigueComplaintDelay) {
3591 		FatigueComplaintDelay--;
3592 		if (!FatigueComplaintDelay) {
3593 			VerbalConstant(VB_TIRED);
3594 		}
3595 	}
3596 }
3597 
RollSaves()3598 void Actor::RollSaves()
3599 {
3600 	if (InternalFlags&IF_USEDSAVE) {
3601 		SavingThrow[0]=(ieByte) core->Roll(1, SAVEROLL, 0);
3602 		SavingThrow[1]=(ieByte) core->Roll(1, SAVEROLL, 0);
3603 		SavingThrow[2]=(ieByte) core->Roll(1, SAVEROLL, 0);
3604 		SavingThrow[3]=(ieByte) core->Roll(1, SAVEROLL, 0);
3605 		SavingThrow[4]=(ieByte) core->Roll(1, SAVEROLL, 0);
3606 		InternalFlags&=~IF_USEDSAVE;
3607 	}
3608 }
3609 
3610 //saving throws:
3611 //type      bits in file    order in stats
3612 //0  spells            1    4
3613 //1  breath            2    3
3614 //2  death             4    0
3615 //3  wands             8    1
3616 //4  polymorph        16    2
3617 
3618 //iwd2 (luckily they use the same bits as it would be with bg2):
3619 //0 not used
3620 //1 not used
3621 //2 fortitude          4   0
3622 //3 reflex             8   1
3623 //4 will              16   2
3624 
3625 // in adnd, the stat represents the limit (DC) that the roll with all the boni has to pass
3626 // since it is a derived stat, we also store the direct effect bonus/malus in it, but make sure to do it negated
3627 // in 3ed, the stat is added to the roll and boni (not negated), then compared to some predefined value (DC)
3628 
3629 #define SAVECOUNT 5
3630 static int savingthrows[SAVECOUNT]={IE_SAVEVSSPELL, IE_SAVEVSBREATH, IE_SAVEVSDEATH, IE_SAVEVSWANDS, IE_SAVEVSPOLY};
3631 
3632 /** returns true if actor made the save against saving throw type */
GetSavingThrow(ieDword type,int modifier,const Effect * fx)3633 bool Actor::GetSavingThrow(ieDword type, int modifier, const Effect *fx)
3634 {
3635 	assert(type<SAVECOUNT);
3636 	InternalFlags|=IF_USEDSAVE;
3637 	int ret = SavingThrow[type];
3638 	// NOTE: assuming criticals apply to iwd2 too
3639 	if (ret == 1) return false;
3640 	if (ret == SAVEROLL) return true;
3641 
3642 	if (!third) {
3643 		ret += modifier + GetStat(IE_LUCK);
3644 
3645 		// potentially display feedback, but do some rate limiting, since each effect in a spell ends up here
3646 		static ieDword prevType = -1;
3647 		static int prevRoll = -1;
3648 		static Actor *prevActor = NULL;
3649 		if (core->HasFeedback(FT_COMBAT) && prevType != type && prevActor != this && prevRoll != ret) {
3650 			// "Save Vs Death" in all games except pst: "Save Vs. Death:"
3651 			String *str = core->GetString(displaymsg->GetStringReference(STR_SAVE_SPELL + type));
3652 			wchar_t tmp[20];
3653 			swprintf(tmp, sizeof(tmp)/sizeof(tmp[0]), L" %d", ret);
3654 			String msg = *str + tmp;
3655 			delete str;
3656 			displaymsg->DisplayStringName(msg, DMC_WHITE, this);
3657 		}
3658 		prevType = type;
3659 		prevActor = this;
3660 		prevRoll = ret;
3661 		return ret > (int) GetStat(savingthrows[type]);
3662 	}
3663 
3664 	int roll = ret;
3665 	// NOTE: we use GetStat, assuming the stat save bonus can never be negated like some others
3666 	int save = GetStat(savingthrows[type]);
3667 	// intentionally not adding luck, which seems to have been handled separately
3668 	// eg. 11hfamlk.itm uses an extra opcode for the saving throw bonus
3669 	ret = roll + save + modifier;
3670 	assert(fx);
3671 	int spellLevel = fx->SpellLevel;
3672 	int saveBonus = fx->SavingThrowBonus;
3673 	int saveDC = 10 + spellLevel + saveBonus;
3674 
3675 	// handle special bonuses (eg. vs poison, which doesn't have a separate stat any more)
3676 	// same hardcoded list as in the original
3677 	if (savingthrows[type] == IE_SAVEFORTITUDE && fx->Opcode == 25) {
3678 		if (BaseStats[IE_RACE] == 4 /* DWARF */) ret += 2;
3679 		if (HasFeat(FEAT_SNAKE_BLOOD)) ret += 2;
3680 		if (HasFeat(FEAT_RESIST_POISON)) ret += 4;
3681 	}
3682 
3683 	// the original had a sourceType == TRIGGER check, but we handle more than ST_TRIGGER
3684 	Scriptable *caster = area->GetScriptableByGlobalID(fx->CasterID);
3685 	if (savingthrows[type] == IE_SAVEREFLEX && caster && caster->Type != ST_ACTOR) {
3686 		// loop over all classes and add TRAPSAVE.2DA values to the bonus
3687 		for (int cls = 0; cls < ISCLASSES; cls++) {
3688 			int level = GetClassLevel(cls);
3689 			if (!level) continue;
3690 			ret += gamedata->GetTrapSaveBonus(level, classesiwd2[cls]);
3691 		}
3692 	}
3693 
3694 	if (savingthrows[type] == IE_SAVEWILL) {
3695 		// aura of courage
3696 		if (Modified[IE_EA] < EA_GOODCUTOFF && stricmp(fx->Source, "SPWI420")) {
3697 			// look if an ally paladin of at least level 2 is near
3698 			std::vector<Actor *> neighbours = area->GetAllActorsInRadius(Pos, GA_NO_LOS|GA_NO_DEAD|GA_NO_UNSCHEDULED|GA_NO_ENEMY|GA_NO_NEUTRAL|GA_NO_SELF, 10);
3699 			for (const Actor *ally : neighbours) {
3700 				if (ally->GetPaladinLevel() >= 2 && !ally->CheckSilenced()) {
3701 					ret += 4;
3702 					break;
3703 				}
3704 			}
3705 		}
3706 
3707 		if (fx->Opcode == 24 && BaseStats[IE_RACE] == 5 /* HALFLING */) ret += 2;
3708 		if (GetSubRace() == 0x20001 /* DROW */) ret += 2;
3709 
3710 		// Tyrant's dictum for clerics of Bane
3711 		if (caster && caster->Type == ST_ACTOR) {
3712 			const Actor *cleric = (Actor *) caster;
3713 			if (cleric->GetClericLevel() && BaseStats[IE_KIT] & 0x200000) saveDC += 1;
3714 			// the original limited this to domain spells, but that's pretty lame
3715 		}
3716 	}
3717 
3718 	// general bonuses
3719 	// TODO: Heart of Fury upgraded creature get +5
3720 	// FIXME: externalize these two two difflvls.2da
3721 	if (Modified[IE_EA] != EA_PC && GameDifficulty == DIFF_EASY) ret -= 4;
3722 	if (Modified[IE_EA] != EA_PC && GameDifficulty == DIFF_NORMAL) ret -= 2;
3723 	// (half)elven resistance to enchantment, gnomish to illusions and dwarven to spells
3724 	if ((BaseStats[IE_RACE] == 2 || BaseStats[IE_RACE] == 3) && fx->PrimaryType == 4) ret += 2;
3725 	if (BaseStats[IE_RACE] == 6 && fx->PrimaryType == 5) ret += 2;
3726 	if (BaseStats[IE_RACE] == 4 && fx->Resistance <= FX_CAN_RESIST_CAN_DISPEL) ret += 2;
3727 	// monk's clear mind and mage specialists
3728 	if (GetMonkLevel() >= 3 && fx->PrimaryType == 4) ret += 2;
3729 	if (GetMageLevel() && (1 << (fx->PrimaryType + 5)) & BaseStats[IE_KIT]) ret += 2;
3730 
3731 	// handle animal taming last
3732 	// must roll a Will Save of 5 + player's total skill or higher to save
3733 	if (stricmp(fx->Source, "SPIN108") && fx->Opcode == 5) {
3734 		saveDC = 5;
3735 		const Actor *caster = core->GetGame()->GetActorByGlobalID(fx->CasterID);
3736 		if (caster) {
3737 			saveDC += caster->GetSkill(IE_ANIMALS);
3738 		}
3739 	}
3740 
3741 	if (ret > saveDC) {
3742 		// ~Saving throw result: (d20 + save + bonuses) %d + %d  + %d vs. (10 + spellLevel + saveMod)  10 + %d + %d - Success!~
3743 		displaymsg->DisplayRollStringName(40974, DMC_LIGHTGREY, this, roll, save, modifier, spellLevel, saveBonus);
3744 		return true;
3745 	} else {
3746 		// ~Saving throw result: (d20 + save + bonuses) %d + %d  + %d vs. (10 + spellLevel + saveMod)  10 + %d + %d - Failed!~
3747 		displaymsg->DisplayRollStringName(40975, DMC_LIGHTGREY, this, roll, save, modifier, spellLevel, saveBonus);
3748 		return false;
3749 	}
3750 }
3751 
3752 /** implements a generic opcode function, modify modified stats
3753 returns the change
3754 */
NewStat(unsigned int StatIndex,ieDword ModifierValue,ieDword ModifierType)3755 int Actor::NewStat(unsigned int StatIndex, ieDword ModifierValue, ieDword ModifierType)
3756 {
3757 	int oldmod = Modified[StatIndex];
3758 
3759 	switch (ModifierType) {
3760 		case MOD_ADDITIVE:
3761 			//flat point modifier
3762 			SetStat(StatIndex, Modified[StatIndex]+ModifierValue, 1);
3763 			break;
3764 		case MOD_ABSOLUTE:
3765 			//straight stat change
3766 			SetStat(StatIndex, ModifierValue, 1);
3767 			break;
3768 		case MOD_PERCENT:
3769 			//percentile
3770 			SetStat(StatIndex, BaseStats[StatIndex] * ModifierValue / 100, 1);
3771 			break;
3772 		case MOD_MULTIPLICATIVE:
3773 			SetStat(StatIndex, BaseStats[StatIndex] * ModifierValue, 1);
3774 			break;
3775 		case MOD_DIVISIVE:
3776 			if (ModifierValue == 0) {
3777 				Log(ERROR, "Actor", "Invalid modifier value (0) passed to NewStat: %d (%s)!", ModifierType, LongName);
3778 				break;
3779 			}
3780 			SetStat(StatIndex, BaseStats[StatIndex] / ModifierValue, 1);
3781 			break;
3782 		case MOD_MODULUS:
3783 			if (ModifierValue == 0) {
3784 				Log(ERROR, "Actor", "Invalid modifier value (0) passed to NewStat: %d (%s)!", ModifierType, LongName);
3785 				break;
3786 			}
3787 			SetStat(StatIndex, BaseStats[StatIndex] % ModifierValue, 1);
3788 			break;
3789 		case MOD_LOGAND:
3790 			SetStat(StatIndex, BaseStats[StatIndex] && ModifierValue, 1);
3791 			break;
3792 		case MOD_LOGOR:
3793 			SetStat(StatIndex, BaseStats[StatIndex] || ModifierValue, 1);
3794 			break;
3795 		case MOD_BITAND:
3796 			SetStat(StatIndex, BaseStats[StatIndex] & ModifierValue, 1);
3797 			break;
3798 		case MOD_BITOR:
3799 			SetStat(StatIndex, BaseStats[StatIndex] | ModifierValue, 1);
3800 			break;
3801 		case MOD_INVERSE:
3802 			SetStat(StatIndex, !BaseStats[StatIndex], 1);
3803 			break;
3804 		default:
3805 			Log(ERROR, "Actor", "Invalid modifier type passed to NewStat: %d (%s)!", ModifierType, LongName);
3806 	}
3807 	return Modified[StatIndex] - oldmod;
3808 }
3809 
NewBase(unsigned int StatIndex,ieDword ModifierValue,ieDword ModifierType)3810 int Actor::NewBase(unsigned int StatIndex, ieDword ModifierValue, ieDword ModifierType)
3811 {
3812 	int oldmod = BaseStats[StatIndex];
3813 
3814 	switch (ModifierType) {
3815 		case MOD_ADDITIVE:
3816 			//flat point modifier
3817 			SetBase(StatIndex, BaseStats[StatIndex]+ModifierValue);
3818 			break;
3819 		case MOD_ABSOLUTE:
3820 			//straight stat change
3821 			SetBase(StatIndex, ModifierValue);
3822 			break;
3823 		case MOD_PERCENT:
3824 			//percentile
3825 			SetBase(StatIndex, BaseStats[StatIndex] * ModifierValue / 100);
3826 			break;
3827 		case MOD_MULTIPLICATIVE:
3828 			SetBase(StatIndex, BaseStats[StatIndex] * ModifierValue);
3829 			break;
3830 		case MOD_DIVISIVE:
3831 			if (ModifierValue == 0) {
3832 				Log(ERROR, "Actor", "Invalid modifier value (0) passed to NewBase: %d (%s)!", ModifierType, LongName);
3833 				break;
3834 			}
3835 			SetBase(StatIndex, BaseStats[StatIndex] / ModifierValue);
3836 			break;
3837 		case MOD_MODULUS:
3838 			if (ModifierValue == 0) {
3839 				Log(ERROR, "Actor", "Invalid modifier value (0) passed to NewBase: %d (%s)!", ModifierType, LongName);
3840 				break;
3841 			}
3842 			SetBase(StatIndex, BaseStats[StatIndex] % ModifierValue);
3843 			break;
3844 		case MOD_LOGAND:
3845 			SetBase(StatIndex, BaseStats[StatIndex] && ModifierValue);
3846 			break;
3847 		case MOD_LOGOR:
3848 			SetBase(StatIndex, BaseStats[StatIndex] || ModifierValue);
3849 			break;
3850 		case MOD_BITAND:
3851 			SetBase(StatIndex, BaseStats[StatIndex] & ModifierValue);
3852 			break;
3853 		case MOD_BITOR:
3854 			SetBase(StatIndex, BaseStats[StatIndex] | ModifierValue);
3855 			break;
3856 		case MOD_INVERSE:
3857 			SetBase(StatIndex, !BaseStats[StatIndex]);
3858 			break;
3859 		default:
3860 			Log(ERROR, "Actor", "Invalid modifier type passed to NewBase: %d (%s)!", ModifierType, LongName);
3861 	}
3862 	return BaseStats[StatIndex] - oldmod;
3863 }
3864 
CountElements(const char * s,char separator)3865 inline int CountElements(const char *s, char separator)
3866 {
3867 	int ret = 1;
3868 	while(*s) {
3869 		if (*s==separator) ret++;
3870 		s++;
3871 	}
3872 	return ret;
3873 }
3874 
Interact(int type) const3875 void Actor::Interact(int type) const
3876 {
3877 	int start;
3878 	int count;
3879 	bool queue = false;
3880 
3881 	switch(type&0xff) {
3882 		case I_INSULT: start=VB_INSULT; break;
3883 		case I_COMPLIMENT: start=VB_COMPLIMENT; break;
3884 		case I_SPECIAL: start=VB_SPECIAL; break;
3885 		case I_INSULT_RESP: start=VB_RESP_INS; queue=true; break;
3886 		case I_COMPL_RESP: start=VB_RESP_COMP; queue=true; break;
3887 		default:
3888 			return;
3889 	}
3890 	if (type&0xff00) {
3891 		//PST style fixed slots
3892 		start+=((type&0xff00)>>8)-1;
3893 		count = 1;
3894 	} else {
3895 		//BG1 style random slots
3896 		count = 3;
3897 	}
3898 	VerbalConstant(start, count, queue ? DS_QUEUE : 0);
3899 }
3900 
GetVerbalConstant(int index) const3901 ieStrRef Actor::GetVerbalConstant(int index) const
3902 {
3903 	if (index<0 || index>=VCONST_COUNT) {
3904 		return ieStrRef(-1);
3905 	}
3906 
3907 	int idx = VCMap[index];
3908 
3909 	if (idx<0 || idx>=VCONST_COUNT) {
3910 		return ieStrRef(-1);
3911 	}
3912 	return StrRefs[idx];
3913 }
3914 
GetVerbalConstant(int start,int count) const3915 ieStrRef Actor::GetVerbalConstant(int start, int count) const
3916 {
3917 	while (count > 0 && GetVerbalConstant(start+count-1) == (ieStrRef) -1) {
3918 		count--;
3919 	}
3920 	if (count > 0) {
3921 		return GetVerbalConstant(start+RAND(0, count-1));
3922 	}
3923 	return (ieStrRef) -1;
3924 }
3925 
VerbalConstant(int start,int count,int flags) const3926 bool Actor::VerbalConstant(int start, int count, int flags) const
3927 {
3928 	if (start!=VB_DIE) {
3929 		//can't talk when dead
3930 		if (Modified[IE_STATE_ID] & (STATE_CANTLISTEN)) return false;
3931 	}
3932 
3933 	if (count < 0) {
3934 		return false;
3935 	}
3936 
3937 	flags ^= DS_CONSOLE|DS_SPEECH|DS_CIRCLE;
3938 
3939 	//If we are main character (has SoundSet) we have to check a corresponding wav file exists
3940 	bool found = false;
3941 	if (PCStats && PCStats->SoundSet[0]) {
3942 		ieResRef soundref;
3943 		char chrsound[256];
3944 		do {
3945 			count--;
3946 			ResolveStringConstant(soundref, start+count);
3947 			GetSoundFolder(chrsound, 1, soundref);
3948 			if (gamedata->Exists(chrsound, IE_WAV_CLASS_ID, true) || gamedata->Exists(chrsound, IE_OGG_CLASS_ID, true)) {
3949 				DisplayStringCore((Scriptable *) this, start + RAND(0, count), flags|DS_CONST);
3950 				found = true;
3951 				break;
3952 			}
3953 		} while (count > 0);
3954 	} else { //If we are anyone else we have to check there is a corresponding strref
3955 		ieStrRef str = GetVerbalConstant(start, count);
3956 		if (str != ieStrRef(-1)) {
3957 			DisplayStringCore((Scriptable *) this, str, flags);
3958 			found = true;
3959 		}
3960 	}
3961 	return found;
3962 }
3963 
DisplayStringOrVerbalConstant(int str,int vcstat,int vccount) const3964 void Actor::DisplayStringOrVerbalConstant(int str, int vcstat, int vccount) const {
3965 	int strref = displaymsg->GetStringReference(str);
3966 	if (strref != -1) {
3967 		DisplayStringCore((Scriptable *) this, strref, DS_CONSOLE|DS_CIRCLE);
3968 	} else {
3969 		VerbalConstant(vcstat, vccount);
3970 	}
3971 }
3972 
HasSpecialDeathReaction(const char * deadname) const3973 bool Actor::HasSpecialDeathReaction(const char *deadname) const
3974 {
3975 	AutoTable tm("death");
3976 	if (!tm) return false;
3977 	const char *value = tm->QueryField (scriptName, deadname);
3978 	return value && value[0] != '0';
3979 }
3980 
ReactToDeath(const char * deadname)3981 void Actor::ReactToDeath(const char * deadname)
3982 {
3983 	AutoTable tm("death");
3984 	if (!tm) return;
3985 	// lookup value based on died's scriptingname and ours
3986 	// if value is 0 - use reactdeath
3987 	// if value is 1 - use reactspecial
3988 	// if value is string - use playsound instead (pst)
3989 	const char *value = tm->QueryField (scriptName, deadname);
3990 	switch (value[0]) {
3991 	case '0':
3992 		VerbalConstant(VB_REACT, 1, DS_QUEUE);
3993 		break;
3994 	case '1':
3995 		VerbalConstant(VB_REACT_S, 1, DS_QUEUE);
3996 		break;
3997 	default:
3998 		{
3999 			int count = CountElements(value,',');
4000 			if (count<=0) break;
4001 			count = core->Roll(1,count,-1);
4002 			ieResRef resref;
4003 			while(count--) {
4004 				while(*value && *value!=',') value++;
4005 				if (*value==',') value++;
4006 			}
4007 			CopyResRef(resref, value);
4008 			for(count=0;count<8 && resref[count]!=',';count++) {};
4009 			resref[count]=0;
4010 
4011 			unsigned int len = 0;
4012 			unsigned int channel = SFX_CHAN_CHAR0 + InParty - 1;
4013 			core->GetAudioDrv()->Play(resref, channel, &len);
4014 			ieDword counter = ( AI_UPDATE_TIME * len ) / 1000;
4015 			if (counter != 0)
4016 				SetWait( counter );
4017 			break;
4018 		}
4019 	}
4020 }
4021 
4022 //issue area specific comments
GetAreaComment(int areaflag) const4023 void Actor::GetAreaComment(int areaflag) const
4024 {
4025 	for(int i=0;i<afcount;i++) {
4026 		if (afcomments[i][0]&areaflag) {
4027 			int vc = afcomments[i][1];
4028 			if (afcomments[i][2]) {
4029 				if (!core->GetGame()->IsDay()) {
4030 					vc++;
4031 				}
4032 			}
4033 			VerbalConstant(vc);
4034 			return;
4035 		}
4036 	}
4037 }
4038 
CheckInteract(const char * talker,const char * target)4039 static int CheckInteract(const char *talker, const char *target)
4040 {
4041 	AutoTable interact("interact");
4042 	if (!interact)
4043 		return I_NONE;
4044 	const char *value = interact->QueryField(talker, target);
4045 	if (!value)
4046 		return I_NONE;
4047 
4048 	int tmp = 0;
4049 	int x = 0;
4050 	int ln = strlen(value);
4051 
4052 	if (ln>1) { // PST
4053 		//we round the length up, so the last * will be also chosen
4054 		x = core->Roll(1,(ln+1)/2,-1)*2;
4055 		//convert '1', '2' and '3' to 0x100,0x200,0x300 respectively, all the rest becomes 0
4056 		//it is no problem if we hit the zero terminator in case of an odd length
4057 		tmp = value[x+1]-'0';
4058 		if ((ieDword) tmp>3) tmp=0;
4059 		tmp <<= 8;
4060 	}
4061 
4062 	switch(value[x]) {
4063 		case '*':
4064 			return I_DIALOG;
4065 		case 's':
4066 			return tmp+I_SPECIAL;
4067 		case 'c':
4068 			return tmp+I_COMPLIMENT;
4069 		case 'i':
4070 			return tmp+I_INSULT;
4071 		case 'I':
4072 			return tmp+I_INSULT_RESP;
4073 		case 'C':
4074 			return tmp+I_COMPL_RESP;
4075 	}
4076 	return I_NONE;
4077 }
4078 
HandleInteractV1(const Actor * target)4079 void Actor::HandleInteractV1(const Actor *target)
4080 {
4081 	LastTalker = target->GetGlobalID();
4082 	char tmp[50];
4083 	snprintf(tmp, sizeof(tmp), "Interact(\"%s\")", target->GetScriptName());
4084 	AddAction(GenerateAction(tmp));
4085 }
4086 
HandleInteract(const Actor * target) const4087 int Actor::HandleInteract(const Actor *target) const
4088 {
4089 	int type = CheckInteract(scriptName, target->GetScriptName());
4090 
4091 	//no interaction at all
4092 	if (type==I_NONE) return -1;
4093 	//banter dialog interaction
4094 	if (type==I_DIALOG) return 0;
4095 
4096 	Interact(type);
4097 	switch(type)
4098 	{
4099 	case I_COMPLIMENT:
4100 		target->Interact(I_COMPL_RESP);
4101 		break;
4102 	case I_INSULT:
4103 		target->Interact(I_INSULT_RESP);
4104 		break;
4105 	}
4106 	return 1;
4107 }
4108 
GetPartyComment()4109 bool Actor::GetPartyComment()
4110 {
4111 	Game *game = core->GetGame();
4112 
4113 	//not an NPC
4114 	if (BaseStats[IE_MC_FLAGS] & MC_EXPORTABLE) return false;
4115 	// don't bother if we're not around
4116 	if (GetCurrentArea() != game->GetCurrentArea()) return false;
4117 	ieDword size = game->GetPartySize(true);
4118 	//don't even bother, again
4119 	if (size<2) return false;
4120 
4121 	if (core->Roll(1, 2, -1)) return false;
4122 
4123 	for (unsigned int i = core->Roll(1, size, 0), n = 0; n < size; i++, n++) {
4124 		const Actor *target = game->GetPC(i % size, true);
4125 		if (target==this) continue;
4126 		if (target->BaseStats[IE_MC_FLAGS]&MC_EXPORTABLE) continue; //not NPC
4127 		if (target->GetCurrentArea()!=GetCurrentArea()) continue;
4128 
4129 		if (core->HasFeature(GF_RANDOM_BANTER_DIALOGS)) {
4130 			if (core->Roll(1, 50, 0) == 1) { // TODO: confirm frequency
4131 				//V1 interact
4132 				HandleInteractV1(target);
4133 				return true;
4134 			}
4135 		}
4136 
4137 		//simplified interact
4138 		switch(HandleInteract(target)) {
4139 		case -1: return false;
4140 		case 1: return true;
4141 		default:
4142 			//V2 interact
4143 			LastTalker = target->GetGlobalID();
4144 			Action *action = GenerateActionDirect("Interact([-1])", target);
4145 			if (action) {
4146 				AddActionInFront(action);
4147 			} else {
4148 				Log(ERROR, "Actor", "Cannot generate banter action");
4149 			}
4150 			return true;
4151 		}
4152 	}
4153 	return false;
4154 }
4155 
4156 //call this only from gui selects
PlaySelectionSound()4157 void Actor::PlaySelectionSound()
4158 {
4159 	playedCommandSound = false;
4160 	// pst uses a slider in lieu of buttons, so the frequency value is off by 1
4161 	unsigned int frequency = sel_snd_freq + pstflags;
4162 	if (!pstflags && frequency > 2) frequency = 5;
4163 	switch (frequency) {
4164 		case 1:
4165 			return;
4166 		case 2:
4167 			if (core->Roll(1,100,0) > 20) return;
4168 			break;
4169 		// pst-only
4170 		case 3:
4171 			if (core->Roll(1, 100, 0) > 50) return;
4172 			break;
4173 		case 4:
4174 			if (core->Roll(1, 100, 0) > 80) return;
4175 			break;
4176 		default:;
4177 	}
4178 
4179 	//drop the rare selection comment 5% of the time
4180 	if (InParty && core->Roll(1,100,0) <= RARE_SELECT_CHANCE){
4181 		//rare select on main character for BG1 won't work atm
4182 		VerbalConstant(VB_SELECT_RARE, NUM_RARE_SELECT_SOUNDS, DS_CIRCLE);
4183 	} else {
4184 		//checks if we are main character to limit select sounds
4185 		if (PCStats && PCStats->SoundSet[0]) {
4186 			VerbalConstant(VB_SELECT, NUM_MC_SELECT_SOUNDS, DS_CIRCLE);
4187 		} else {
4188 			VerbalConstant(VB_SELECT, NUM_SELECT_SOUNDS, DS_CIRCLE);
4189 		}
4190 	}
4191 }
4192 
PlayWarCry(int range) const4193 bool Actor::PlayWarCry(int range) const
4194 {
4195 	if (!war_cries) return false;
4196 	return VerbalConstant(VB_ATTACK, range, DS_CIRCLE);
4197 }
4198 
4199 #define SEL_ACTION_COUNT_COMMON  3
4200 #define SEL_ACTION_COUNT_ALL     7
4201 
4202 //call this when a PC receives a command from GUI
CommandActor(Action * action,bool clearPath)4203 void Actor::CommandActor(Action* action, bool clearPath)
4204 {
4205 	Scriptable::Stop(); // stop what you were doing
4206 	if (clearPath) ClearPath(true);
4207 	AddAction(action); // now do this new thing
4208 
4209 	// pst uses a slider in lieu of buttons, so the frequency value is off by 1
4210 	switch (cmd_snd_freq + pstflags) {
4211 		case 1:
4212 			return;
4213 		case 2:
4214 			if (playedCommandSound) return;
4215 			playedCommandSound = true;
4216 			// intentional fallthrough
4217 		case 3:
4218 			//PST has 4 states and rare sounds
4219 			if (pstflags) {
4220 				if (core->Roll(1,100,0)>50) return;
4221 			}
4222 			break;
4223 		case 4:
4224 			if (pstflags) {
4225 				if (core->Roll(1, 100, 0) > 80) return;
4226 			}
4227 			break;
4228 		default:;
4229 	}
4230 
4231 	if (core->GetFirstSelectedPC(false) == this) {
4232 		// bg2 uses up the traditional space for rare select sound slots for more action (command) sounds
4233 		VerbalConstant(VB_COMMAND, raresnd ? SEL_ACTION_COUNT_ALL : SEL_ACTION_COUNT_COMMON, DS_CIRCLE);
4234 	}
4235 }
4236 
4237 //Generates an idle action (party banter, area comment, bored)
IdleActions(bool nonidle)4238 void Actor::IdleActions(bool nonidle)
4239 {
4240 	//do we have an area
4241 	Map *map = GetCurrentArea();
4242 	if (!map) return;
4243 	//and not in panic
4244 	if (panicMode!=PANIC_NONE) return;
4245 
4246 	Game *game = core->GetGame();
4247 	//there is no combat
4248 	if (game->CombatCounter) {
4249 		ResetCommentTime();
4250 		return;
4251 	}
4252 
4253 	//and they are on the current area
4254 	if (map!=game->GetCurrentArea()) return;
4255 
4256 	//don't mess with cutscenes
4257 	if (core->InCutSceneMode()) {
4258 		ResetCommentTime();
4259 		return;
4260 	}
4261 
4262 	//only party [N]PCs talk but others might play existence sounds
4263 	if (!InParty) {
4264 		PlayExistenceSounds();
4265 		return;
4266 	}
4267 
4268 	ieDword time = game->GameTime;
4269 	//did scripts disable us
4270 	if (game->BanterBlockFlag || (game->BanterBlockTime>time) ) {
4271 		return;
4272 	}
4273 
4274 	if (time/nextComment > 1) { // first run, not adjusted for game time yet
4275 		nextComment += time;
4276 	}
4277 
4278 	//drop an area comment, party oneliner or initiate party banter (with Interact)
4279 	//party comments have a priority, but they happen half of the time, at most
4280 	if (nextComment<time) {
4281 		if (nextComment && !Immobile() && !GetPartyComment()) {
4282 			GetAreaComment(map->AreaType);
4283 		}
4284 		nextComment = time+core->Roll(5,1000,bored_time/2);
4285 		return;
4286 	}
4287 
4288 	//drop the bored one liner if there was no action for some time
4289 	//if bored timeout is disabled, don't bother to set the new time
4290 	if (nonidle || (!nextBored && bored_time) || InMove() || Immobile()) {
4291 		nextBored = time + core->Roll(1, 30, bored_time);
4292 	} else {
4293 		if (bored_time && nextBored && nextBored < time) {
4294 			int x = std::max(10U, bored_time / 10);
4295 			nextBored = time+core->Roll(1,30,x);
4296 			VerbalConstant(VB_BORED);
4297 		}
4298 
4299 		// display idle animation
4300 		int x = RAND(0, 25);
4301 		if (!x && (GetStance() == IE_ANI_AWAKE)) {
4302 			SetStance(IE_ANI_HEAD_TURN);
4303 		}
4304 	}
4305 }
4306 
PlayExistenceSounds()4307 void Actor::PlayExistenceSounds()
4308 {
4309 	//only non-joinable chars can have existence sounds
4310 	if (Persistent()) return;
4311 
4312 	Game *game = core->GetGame();
4313 	ieDword time = game->GameTime;
4314 	if (time/nextComment > 1) { // first run, not adjusted for game time yet
4315 		nextComment += time;
4316 	}
4317 
4318 	if (nextComment >= time) return;
4319 
4320 	ieDword delay = Modified[IE_EXISTANCEDELAY];
4321 	if (delay == (ieDword) -1) return;
4322 
4323 	Audio *audio = core->GetAudioDrv();
4324 	int xpos, ypos;
4325 	audio->GetListenerPos(xpos, ypos);
4326 	Point listener(xpos, ypos);
4327 	if (nextComment && !Immobile() && WithinAudibleRange(this, listener)) {
4328 		//setup as an ambient
4329 		ieStrRef strref = GetVerbalConstant(VB_EXISTENCE, 5);
4330 		if (strref != (ieStrRef) -1) {
4331 			StringBlock sb = core->strings->GetStringBlock(strref);
4332 			if (sb.Sound[0]) {
4333 				unsigned int vol = 100;
4334 				core->GetDictionary()->Lookup("Volume Ambients", vol);
4335 				int stream = audio->SetupNewStream(Pos.x, Pos.y, 0, vol, true, 50); // REFERENCE_DISTANCE
4336 				if (stream != -1) {
4337 					int audioLength = audio->QueueAmbient(stream, sb.Sound);
4338 					if (audioLength > 0) {
4339 						SetAnimatedTalking(audioLength);
4340 					}
4341 					audio->ReleaseStream(stream, false);
4342 				}
4343 			}
4344 		}
4345 	}
4346 	if (delay == 0) {
4347 		delay = VOODOO_EXISTENCE_DELAY_DEFAULT;
4348 	}
4349 	nextComment = time + RAND(delay*1/4, delay*7/4);
4350 }
4351 
OverrideActions()4352 bool Actor::OverrideActions()
4353 {
4354 	//TODO:: implement forced actions that mess with scripting (panic, confusion, etc)
4355 	// domination and dire charm: force the actors to be useful (trivial ai)
4356 	Action *action;
4357 	if ((Modified[IE_STATE_ID] & STATE_CHARMED) && (BaseStats[IE_EA] <= EA_GOODCUTOFF) && Modified[IE_EA] == EA_CHARMEDPC) {
4358 		Effect *charm = fxqueue.HasEffect(fx_set_charmed_state_ref);
4359 		if (!charm) return false;
4360 
4361 		// skip regular charm
4362 		switch (charm->Parameter2) {
4363 			case 2:
4364 			case 3:
4365 			case 5:
4366 			case 1002:
4367 			case 1003:
4368 			case 1005:
4369 				action = GenerateAction("AttackReevaluate([GOODCUTOFF],10)");
4370 				if (action) {
4371 					AddActionInFront(action);
4372 					return true;
4373 				} else {
4374 					Log(ERROR, "Actor", "Cannot generate override action");
4375 				}
4376 				break;
4377 			default:
4378 				break;
4379 		}
4380 	}
4381 	return false;
4382 }
4383 
Panic(const Scriptable * attacker,int panicmode)4384 void Actor::Panic(const Scriptable *attacker, int panicmode)
4385 {
4386 	if (GetStat(IE_STATE_ID)&STATE_PANIC) {
4387 		print("Already panicked");
4388 		//already in panic
4389 		return;
4390 	}
4391 	if (InParty) core->GetGame()->SelectActor(this, false, SELECT_NORMAL);
4392 	VerbalConstant(VB_PANIC);
4393 
4394 	Action *action;
4395 	if (panicmode == PANIC_RUNAWAY && (!attacker || attacker->Type!=ST_ACTOR)) {
4396 		panicmode = PANIC_RANDOMWALK;
4397 	}
4398 
4399 	switch(panicmode) {
4400 	case PANIC_RUNAWAY:
4401 		action = GenerateActionDirect("RunAwayFromNoInterrupt([-1])", attacker);
4402 		SetBaseBit(IE_STATE_ID, STATE_PANIC, true);
4403 		break;
4404 	case PANIC_RANDOMWALK:
4405 		action = GenerateAction( "RandomWalk()" );
4406 		SetBaseBit(IE_STATE_ID, STATE_PANIC, true);
4407 		break;
4408 	case PANIC_BERSERK:
4409 		action = GenerateAction( "Berserk()" );
4410 		BaseStats[IE_CHECKFORBERSERK]=3;
4411 		//SetBaseBit(IE_STATE_ID, STATE_BERSERK, true);
4412 		break;
4413 	default:
4414 		return;
4415 	}
4416 	if (action) {
4417 		AddActionInFront(action);
4418 	} else {
4419 		Log(ERROR, "Actor", "Cannot generate panic action");
4420 	}
4421 }
4422 
SetMCFlag(ieDword arg,int op)4423 void Actor::SetMCFlag(ieDword arg, int op)
4424 {
4425 	ieDword tmp = BaseStats[IE_MC_FLAGS];
4426 	SetBits(tmp, arg, op);
4427 	SetBase(IE_MC_FLAGS, tmp);
4428 }
4429 
DialogInterrupt() const4430 void Actor::DialogInterrupt() const
4431 {
4432 	//if dialoginterrupt was set, no verbal constant
4433 	if ( Modified[IE_MC_FLAGS]&MC_NO_TALK)
4434 		return;
4435 
4436 	/* this part is unsure */
4437 	if (Modified[IE_EA]>=EA_EVILCUTOFF) {
4438 		VerbalConstant(VB_HOSTILE);
4439 	} else {
4440 		if (TalkCount) {
4441 			VerbalConstant(VB_DIALOG);
4442 		} else {
4443 			VerbalConstant(VB_INITIALMEET);
4444 		}
4445 	}
4446 }
4447 
GetHit(int damage,int spellLevel)4448 void Actor::GetHit(int damage, int spellLevel)
4449 {
4450 	if (!Immobile() && !(InternalFlags & IF_REALLYDIED)) {
4451 		SetStance( IE_ANI_DAMAGE );
4452 		VerbalConstant(VB_DAMAGE);
4453 	}
4454 
4455 	if (Modified[IE_STATE_ID]&STATE_SLEEP) {
4456 		if (Modified[IE_EXTSTATE_ID]&EXTSTATE_NO_WAKEUP || HasSpellState(SS_NOAWAKE)) {
4457 			return;
4458 		}
4459 		Effect *fx = EffectQueue::CreateEffect(fx_cure_sleep_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
4460 		fxqueue.AddEffect(fx);
4461 		delete fx;
4462 	}
4463 	if (CheckSpellDisruption(damage, spellLevel)) {
4464 		InterruptCasting = true;
4465 	}
4466 }
4467 
4468 // this has no effect in adnd
4469 // iwd2 however has two mechanisms: spell disruption and concentration checks:
4470 // - disruption is checked when a caster is damaged while casting
4471 // - concentration is checked when casting is taking place <= 5' from an enemy
CheckSpellDisruption(int damage,int spellLevel) const4472 bool Actor::CheckSpellDisruption(int damage, int spellLevel) const
4473 {
4474 	if (core->HasFeature(GF_SIMPLE_DISRUPTION)) {
4475 		return LuckyRoll(1, 20, 0) < (damage + spellLevel);
4476 	}
4477 	if (!third) {
4478 		return true;
4479 	}
4480 
4481 	if (!LastSpellTarget && LastTargetPos.isempty()) {
4482 		// not casting, nothing to do
4483 		return false;
4484 	}
4485 	int roll = core->Roll(1, 20, 0);
4486 	int concentration = GetSkill(IE_CONCENTRATION);
4487 	int bonus = 0;
4488 	// combat casting bonus only applies when injured
4489 	if (HasFeat(FEAT_COMBAT_CASTING) && Modified[IE_MAXHITPOINTS] != Modified[IE_HITPOINTS]) {
4490 		bonus += 4;
4491 	}
4492 	// ~Spell Disruption check (d20 + Concentration + Combat Casting bonus) %d + %d + %d vs. (10 + damageTaken + spellLevel)  = 10 + %d + %d.~
4493 	if (GameScript::ID_ClassMask(this, 0x6ee)) { // 0x6ee == CLASSMASK_GROUP_CASTERS
4494 		// no spam for noncasters
4495 		displaymsg->DisplayRollStringName(39842, DMC_LIGHTGREY, this, roll, concentration, bonus, damage, spellLevel);
4496 	}
4497 	int chance = (roll + concentration + bonus) > (10 + damage + spellLevel);
4498 	if (chance) {
4499 		return false;
4500 	}
4501 	return true;
4502 }
4503 
HandleCastingStance(const ieResRef SpellResRef,bool deplete,bool instant)4504 bool Actor::HandleCastingStance(const ieResRef SpellResRef, bool deplete, bool instant)
4505 {
4506 	if (deplete) {
4507 		if (! spellbook.HaveSpell( SpellResRef, HS_DEPLETE )) {
4508 			SetStance(IE_ANI_READY);
4509 			return true;
4510 		}
4511 	}
4512 	if (!instant) {
4513 		SetStance(IE_ANI_CAST);
4514 	}
4515 	return false;
4516 }
4517 
AttackIsStunning(int damagetype) const4518 bool Actor::AttackIsStunning(int damagetype) const {
4519 	//stunning damagetype
4520 	if (damagetype & DAMAGE_STUNNING) {
4521 		return true;
4522 	}
4523 
4524 	return false;
4525 }
4526 
CheckSilenced() const4527 bool Actor::CheckSilenced() const
4528 {
4529 	if (!(Modified[IE_STATE_ID] & STATE_SILENCED)) return false;
4530 	if (HasFeat(FEAT_SUBVOCAL_CASTING)) return false;
4531 	if (HasSpellState(SS_VOCALIZE)) return false;
4532 	return true;
4533 }
4534 
CheckCleave()4535 void Actor::CheckCleave()
4536 {
4537 	int cleave = GetFeat(FEAT_CLEAVE);
4538 	//feat level 1 only enables one cleave per round
4539 	if ((cleave==1) && fxqueue.HasEffect(fx_cleave_ref) ) {
4540 		cleave = 0;
4541 	}
4542 	if(cleave) {
4543 		Effect * fx = EffectQueue::CreateEffect(fx_cleave_ref, attackcount, 0, FX_DURATION_INSTANT_LIMITED);
4544 		if (fx) {
4545 			fx->Duration = core->Time.round_sec;
4546 			core->ApplyEffect(fx, this, this);
4547 			delete fx;
4548 			// ~Cleave feat adds another level %d attack.~
4549 			// uses the max tohit bonus (tested), but game always displayed "level 1"
4550 			displaymsg->DisplayRollStringName(39846, DMC_LIGHTGREY, this, ToHit.GetTotal());
4551 		}
4552 	}
4553 }
4554 
4555 
4556 //returns actual damage
Damage(int damage,int damagetype,Scriptable * hitter,int modtype,int critical,int saveflags)4557 int Actor::Damage(int damage, int damagetype, Scriptable *hitter, int modtype, int critical, int saveflags)
4558 {
4559 	//won't get any more hurt
4560 	if (InternalFlags & IF_REALLYDIED) {
4561 		return 0;
4562 	}
4563 	// hidden creatures are immune too, iwd2 Targos palisade attack relies on it (12cspn1a.bcs)
4564 	// same for seagulls and other non-jumpers
4565 	if (Modified[IE_AVATARREMOVAL] || Modified[IE_DONOTJUMP] == DNJ_BIRD) {
4566 		return 0;
4567 	}
4568 
4569 	//add lastdamagetype up ? maybe
4570 	//FIXME: what does original do?
4571 	LastDamageType |= damagetype;
4572 
4573 	Actor *act = NULL;
4574 	if (hitter && hitter->Type == ST_ACTOR) {
4575 		act = (Actor *) hitter;
4576 	}
4577 
4578 	switch (modtype) {
4579 	case MOD_ADDITIVE:
4580 		//bonus against creature should only affect additive damages or spells like harm would be deadly
4581 		if (damage && act) {
4582 			damage += act->fxqueue.BonusAgainstCreature(fx_damage_vs_creature_ref, this);
4583 		}
4584 		break;
4585 	case MOD_ABSOLUTE:
4586 		damage = GetBase(IE_HITPOINTS) - damage;
4587 		break;
4588 	case MOD_PERCENT:
4589 		damage = GetStat(IE_MAXHITPOINTS) * damage / 100;
4590 		break;
4591 	default:
4592 		//this shouldn't happen
4593 		Log(ERROR, "Actor", "Invalid damage modifier type!");
4594 		return 0;
4595 	}
4596 
4597 	if (GetStat(IE_EXTSTATE_ID) & EXTSTATE_EYE_MAGE) {
4598 		if (damagetype & (DAMAGE_FIRE|DAMAGE_COLD|DAMAGE_ACID|DAMAGE_ELECTRICITY)) {
4599 			fxqueue.RemoveAllEffects(fx_eye_mage_ref);
4600 			spellbook.RemoveSpell(SevenEyes[EYE_MAGE]);
4601 			SetBaseBit(IE_EXTSTATE_ID, EXTSTATE_EYE_MAGE, false);
4602 			damage = 0;
4603 		}
4604 	}
4605 
4606 	if (damage && !(saveflags&SF_BYPASS_MIRROR_IMAGE)) {
4607 		int mirrorimages = Modified[IE_MIRRORIMAGES];
4608 		if (mirrorimages) {
4609 			if (LuckyRoll(1, mirrorimages + 1, 0) != 1) {
4610 				fxqueue.DecreaseParam1OfEffect(fx_mirrorimage_ref, 1);
4611 				Modified[IE_MIRRORIMAGES]--;
4612 				damage = 0;
4613 			}
4614 		}
4615 	}
4616 
4617 	if (!(saveflags&SF_IGNORE_DIFFICULTY) && act) {
4618 		// adjust enemy damage according to difficulty settings:
4619 		// -50%, -25%, 0, 50%, 100%, 150%
4620 		if (act->GetStat(IE_EA) > EA_GOODCUTOFF) {
4621 			int adjustmentPercent = dmgadjustments[GameDifficulty - 1];
4622 			if (!NoExtraDifficultyDmg || adjustmentPercent < 0) {
4623 				damage += (damage * adjustmentPercent)/100;
4624 			}
4625 		}
4626 	}
4627 
4628 	int resisted = 0;
4629 	if (damage) {
4630 		ModifyDamage (hitter, damage, resisted, damagetype);
4631 	}
4632 	DisplayCombatFeedback(damage, resisted, damagetype, hitter);
4633 
4634 	if (damage > 0) {
4635 		// instant chunky death if the actor is petrified or frozen
4636 		bool allowChunking = !Modified[IE_DISABLECHUNKING] && GameDifficulty > DIFF_NORMAL;
4637 		if (Modified[IE_STATE_ID] & (STATE_FROZEN|STATE_PETRIFIED) && allowChunking) {
4638 			damage = 123456; // arbitrarily high for death; won't be displayed
4639 			LastDamageType |= DAMAGE_CHUNKING;
4640 		}
4641 		// chunky death when you're reduced below -10 hp
4642 		if ((ieDword) damage >= Modified[IE_HITPOINTS] + 10 && allowChunking) {
4643 			LastDamageType |= DAMAGE_CHUNKING;
4644 		}
4645 		// mark LastHitter for repeating damage effects (eg. to get xp from melfing trolls)
4646 		if (act && LastHitter == 0) {
4647 			LastHitter = act->GetGlobalID();
4648 		}
4649 	}
4650 
4651 	if (BaseStats[IE_HITPOINTS] <= (ieDword) damage) {
4652 		// common fists do normal damage, but cause sleeping for a round instead of death
4653 		if (Modified[IE_MINHITPOINTS] <= 0 && AttackIsStunning(damagetype)) {
4654 			// stack unconsciousness carefully to avoid replaying the stance changing
4655 			Effect *sleep = fxqueue.HasEffectWithParamPair(fx_sleep_ref, 0, 0);
4656 			if (sleep) {
4657 				sleep->Duration += core->Time.round_sec;
4658 			} else {
4659 				Effect *fx = EffectQueue::CreateEffect(fx_sleep_ref, 0, 0, FX_DURATION_INSTANT_LIMITED);
4660 				fx->Duration = core->Time.round_sec; // 1 round
4661 				core->ApplyEffect(fx, this, this);
4662 				delete fx;
4663 			}
4664 			//reduce damage to keep 1 hp
4665 			damage = Modified[IE_HITPOINTS] - 1;
4666 		}
4667 	}
4668 
4669 	// also apply reputation damage if we hurt (but not killed) an innocent
4670 	if (Modified[IE_CLASS] == CLASS_INNOCENT && !core->InCutSceneMode()) {
4671 		if (act && act->GetStat(IE_EA) <= EA_CONTROLLABLE) {
4672 			core->GetGame()->SetReputation(core->GetGame()->Reputation + core->GetReputationMod(1));
4673 		}
4674 	}
4675 
4676 	int chp = (signed) BaseStats[IE_HITPOINTS];
4677 	if (damage > 0) {
4678 		//if this kills us, check if attacker could cleave
4679 		if (act && (damage > chp)) {
4680 			act->CheckCleave();
4681 		}
4682 		GetHit(damage, 3); // FIXME: carry over the correct spellLevel
4683 		//fixme: implement applytrigger, copy int0 into LastDamage there
4684 		LastDamage = damage;
4685 		AddTrigger(TriggerEntry(trigger_tookdamage, damage)); // FIXME: lastdamager? LastHitter is not set for spell damage
4686 		AddTrigger(TriggerEntry(trigger_hitby, LastHitter, damagetype)); // FIXME: currently lastdamager, should it always be set regardless of damage?
4687 
4688 		// impact morale when hp thresholds (50 %, 25 %) are crossed for the first time
4689 		int currentRatio = 100 * chp / (signed) BaseStats[IE_MAXHITPOINTS];
4690 		int newRatio = 100 * (chp + damage) / (signed) BaseStats[IE_MAXHITPOINTS];
4691 		if (ShouldModifyMorale()) {
4692 			if (currentRatio > 50 && newRatio < 25) {
4693 				NewBase(IE_MORALE, (ieDword) -4, MOD_ADDITIVE);
4694 			} else if (currentRatio > 50 && newRatio < 50) {
4695 				NewBase(IE_MORALE, (ieDword) -2, MOD_ADDITIVE);
4696 			} else if (currentRatio > 25 && newRatio < 25) {
4697 				NewBase(IE_MORALE, (ieDword) -2, MOD_ADDITIVE);
4698 			}
4699 		}
4700 	}
4701 
4702 	// can be negative if we're healing on 100%+ resistance
4703 	// do this after GetHit, so VB_HURT isn't overriden by VB_DAMAGE, just (dis)played later
4704 	if (damage != 0) {
4705 		NewBase(IE_HITPOINTS, (ieDword) -damage, MOD_ADDITIVE);
4706 	}
4707 
4708 	InternalFlags |= IF_ACTIVE;
4709 	int damagelevel;
4710 	if (damage < 10) {
4711 		damagelevel = 0;
4712 	} else if (damage < 20) { // a guess; impacts what blood bam we play, while elemental damage types are unaffected
4713 		damagelevel = 1;
4714 	} else {
4715 		damagelevel = 2;
4716 	}
4717 
4718 	if (damagetype & (DAMAGE_FIRE|DAMAGE_MAGICFIRE)) {
4719 		PlayDamageAnimation(DL_FIRE + damagelevel);
4720 	} else if (damagetype & (DAMAGE_COLD|DAMAGE_MAGICCOLD)) {
4721 		PlayDamageAnimation(DL_COLD + damagelevel);
4722 	} else if (damagetype & DAMAGE_ELECTRICITY) {
4723 		PlayDamageAnimation(DL_ELECTRICITY + damagelevel);
4724 	} else if (damagetype & DAMAGE_ACID) {
4725 		PlayDamageAnimation(DL_ACID + damagelevel);
4726 	} else if (damagetype & (DAMAGE_MAGIC|DAMAGE_DISINTEGRATE)) {
4727 		PlayDamageAnimation(DL_DISINTEGRATE + damagelevel);
4728 	} else {
4729 		if (chp < -10) {
4730 			PlayDamageAnimation(critical<<8);
4731 		} else {
4732 			PlayDamageAnimation(DL_BLOOD + damagelevel);
4733 		}
4734 	}
4735 
4736 	if (InParty) {
4737 		if (chp < (signed) Modified[IE_MAXHITPOINTS]/10) {
4738 			core->Autopause(AP_WOUNDED, this);
4739 		}
4740 		if (damage > 0) {
4741 			core->Autopause(AP_HIT, this);
4742 			core->SetEventFlag(EF_PORTRAIT);
4743 		}
4744 	}
4745 	return damage;
4746 }
4747 
DisplayCombatFeedback(unsigned int damage,int resisted,int damagetype,Scriptable * hitter)4748 void Actor::DisplayCombatFeedback (unsigned int damage, int resisted, int damagetype, Scriptable *hitter)
4749 {
4750 	// shortcircuit for disintegration, which wouldn't hit any of the below
4751 	if (damage == 0 && resisted == 0) return;
4752 
4753 	bool detailed = false;
4754 	const char *type_name = "unknown";
4755 	if (DisplayMessage::HasStringReference(STR_DAMAGE_DETAIL1)) { // how and iwd2
4756 		std::multimap<ieDword, DamageInfoStruct>::iterator it;
4757 		it = core->DamageInfoMap.find(damagetype);
4758 		if (it != core->DamageInfoMap.end()) {
4759 			type_name = core->GetCString(it->second.strref, 0);
4760 		}
4761 		detailed = true;
4762 	}
4763 
4764 	if (damage > 0 && resisted != DR_IMMUNE) {
4765 		Log(COMBAT, "Actor", "%d %s damage taken.\n", damage, type_name);
4766 
4767 		if (!core->HasFeedback(FT_STATES)) goto hitsound;
4768 
4769 		if (detailed) {
4770 			// 3 choices depending on resistance and boni
4771 			// iwd2 also has two Tortoise Shell (spell) absorption strings
4772 			core->GetTokenDictionary()->SetAtCopy( "TYPE", type_name);
4773 			core->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage);
4774 
4775 			int strref;
4776 			if (resisted < 0) {
4777 				//Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage bonus)
4778 				core->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted));
4779 				strref = STR_DAMAGE_DETAIL3;
4780 			} else if (resisted > 0) {
4781 				//Takes <AMOUNT> <TYPE> damage from <DAMAGER> (<RESISTED> damage resisted)
4782 				core->GetTokenDictionary()->SetAtCopy( "RESISTED", abs(resisted));
4783 				strref = STR_DAMAGE_DETAIL2;
4784 			} else {
4785 				//Takes <AMOUNT> <TYPE> damage from <DAMAGER>
4786 				strref = STR_DAMAGE_DETAIL1;
4787 			}
4788 			if (hitter && hitter->Type == ST_ACTOR) {
4789 				core->GetTokenDictionary()->SetAtCopy( "DAMAGER", hitter->GetName(1) );
4790 			} else {
4791 				// variant without damager
4792 				strref -= (STR_DAMAGE_DETAIL1 - STR_DAMAGE1);
4793 			}
4794 			displaymsg->DisplayConstantStringName(strref, DMC_WHITE, this);
4795 		} else if (core->HasFeature(GF_ONSCREEN_TEXT) ) {
4796 			//TODO: handle pst properly (decay, queueing, color)
4797 			wchar_t dmg[10];
4798 			swprintf(dmg, sizeof(dmg)/sizeof(dmg[0]), L"%d", damage);
4799 			SetOverheadText(dmg, true);
4800 		} else if (!displaymsg->HasStringReference(STR_DAMAGE2) || !hitter || hitter->Type != ST_ACTOR) {
4801 			// bg1 and iwd
4802 			// or any traps or self-infliction (also for bg1)
4803 			// construct an i18n friendly "Damage Taken (damage)", since there's no token
4804 			String* msg = core->GetString(displaymsg->GetStringReference(STR_DAMAGE1), 0);
4805 			wchar_t dmg[10];
4806 			swprintf(dmg, sizeof(dmg)/sizeof(dmg[0]), L" (%d)", damage);
4807 			displaymsg->DisplayStringName(*msg + dmg, DMC_WHITE, this);
4808 			delete msg;
4809 		} else { //bg2
4810 			//<DAMAGER> did <AMOUNT> damage to <DAMAGEE>
4811 			core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
4812 			// wipe the DAMAGER token, so we can color it
4813 			core->GetTokenDictionary()->SetAtCopy( "DAMAGER", "" );
4814 			core->GetTokenDictionary()->SetAtCopy( "AMOUNT", damage);
4815 			displaymsg->DisplayConstantStringName(STR_DAMAGE2, DMC_WHITE, hitter);
4816 		}
4817 	} else {
4818 		if (resisted == DR_IMMUNE) {
4819 			Log(COMBAT, "Actor", "is immune to damage type: %s.\n", type_name);
4820 			if (hitter && hitter->Type == ST_ACTOR) {
4821 				if (detailed) {
4822 					//<DAMAGEE> was immune to my <TYPE> damage
4823 					core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
4824 					core->GetTokenDictionary()->SetAtCopy( "TYPE", type_name );
4825 					displaymsg->DisplayConstantStringName(STR_DAMAGE_IMMUNITY, DMC_WHITE, hitter);
4826 				} else if (displaymsg->HasStringReference(STR_DAMAGE_IMMUNITY) && displaymsg->HasStringReference(STR_DAMAGE1)) {
4827 					// bg2
4828 					//<DAMAGEE> was immune to my damage.
4829 					core->GetTokenDictionary()->SetAtCopy( "DAMAGEE", GetName(1) );
4830 					displaymsg->DisplayConstantStringName(STR_DAMAGE_IMMUNITY, DMC_WHITE, hitter);
4831 				} // else: other games don't display anything
4832 			}
4833 		} else {
4834 			// mirror image or stoneskin: no message
4835 		}
4836 	}
4837 
4838 	hitsound:
4839 	//Play hit sounds, for pst, resdata contains the armor level
4840 	DataFileMgr *resdata = core->GetResDataINI();
4841 	PlayHitSound(resdata, damagetype, false);
4842 }
4843 
PlayWalkSound()4844 void Actor::PlayWalkSound()
4845 {
4846 	ieDword thisTime;
4847 	ieResRef Sound;
4848 
4849 	thisTime = GetTicks();
4850 	if (thisTime<nextWalk) return;
4851 	int cnt = anims->GetWalkSoundCount();
4852 	if (!cnt) return;
4853 
4854 	cnt=core->Roll(1,cnt,-1);
4855 	strnuprcpy(Sound, anims->GetWalkSound(), sizeof(ieResRef)-1 );
4856 	area->ResolveTerrainSound(Sound, Pos);
4857 
4858 	if (Sound[0] == '*') return;
4859 
4860 	int l = strlen(Sound);
4861 	/* IWD1, HOW, IWD2 sometimes append numbers here, not letters. */
4862 	if (core->HasFeature(GF_SOUNDFOLDERS) && 0 == memcmp(Sound, "FS_", 3)) {
4863 		if (l < 8) {
4864 			Sound[l] = cnt + 0x31;
4865 			Sound[l+1] = 0;
4866 		}
4867 	} else {
4868 		if (cnt) {
4869 			if (l < 8) {
4870 				Sound[l] = cnt + 0x60; // append 'a'-'g'
4871 				Sound[l+1] = 0;
4872 			}
4873 		}
4874 	}
4875 
4876 	unsigned int len = 0;
4877 	unsigned int channel = InParty ? SFX_CHAN_WALK_CHAR : SFX_CHAN_WALK_MONSTER;
4878 	core->GetAudioDrv()->Play(Sound, channel, Pos.x, Pos.y, 0, &len);
4879 	nextWalk = thisTime + len;
4880 }
4881 
4882 // guesses from audio:               bone  chain studd leather splint none other plate
4883 static const char *armor_types[8] = { "BN", "CH", "CL", "LR", "ML", "MM", "MS", "PT" };
4884 static const char *dmg_types[5] = { "PC", "SL", "BL", "ML", "RK" };
4885 
4886 //Play hit sounds (HIT_0<dtype><armor>)
4887 //IWDs have H_<dmgtype>_<armor> (including level from 1 to max 5), eg H_ML_MM3
PlayHitSound(DataFileMgr * resdata,int damagetype,bool suffix) const4888 void Actor::PlayHitSound(DataFileMgr *resdata, int damagetype, bool suffix) const
4889 {
4890 	int type;
4891 	bool levels = true;
4892 
4893 	switch(damagetype) {
4894 		case DAMAGE_PIERCING: type = 1; break; //piercing
4895 		case DAMAGE_SLASHING: type = 2; break; //slashing
4896 		case DAMAGE_CRUSHING: type = 3; break; //crushing
4897 		case DAMAGE_MISSILE: type = 4; break;  //missile
4898 		case DAMAGE_ELECTRICITY: type = 5; levels = false; break; //electricity
4899 		case DAMAGE_COLD: type = 6; levels = false; break;     //cold
4900 		case DAMAGE_MAGIC: type = 7; levels = false; break;
4901 		case DAMAGE_STUNNING: type = -3; break;
4902 		default: return;                       //other
4903 	}
4904 
4905 	ieResRef Sound;
4906 	int armor = 0;
4907 
4908 	if (resdata) {
4909 		char section[12];
4910 		unsigned int animid=BaseStats[IE_ANIMATION_ID];
4911 		if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
4912 			animid&=0xff;
4913 		}
4914 
4915 		snprintf(section,10,"%d", animid);
4916 
4917 		if (type<0) {
4918 			type = -type;
4919 		} else {
4920 			armor = resdata->GetKeyAsInt(section, "armor",0);
4921 		}
4922 		if (armor<0 || armor>35) return;
4923 	} else {
4924 		//hack for stun (always first armortype)
4925 		if (type<0) {
4926 			type = -type;
4927 		} else {
4928 			armor = Modified[IE_ARMOR_TYPE];
4929 		}
4930 	}
4931 
4932 	if (core->HasFeature(GF_IWD2_SCRIPTNAME)) {
4933 		// TODO: RE and unhardcode, especially the "armor" mapping
4934 		// no idea what RK stands for, so use it for everything else
4935 		if (type > 5) type = 5;
4936 		armor = Modified[IE_ARMOR_TYPE];
4937 		switch (armor) {
4938 			case IE_ANI_NO_ARMOR: armor = 5; break;
4939 			case IE_ANI_LIGHT_ARMOR: armor = core->Roll(1, 2, 1); break;
4940 			case IE_ANI_MEDIUM_ARMOR: armor = 1; break;
4941 			case IE_ANI_HEAVY_ARMOR: armor = 7; break;
4942 			default: armor = 6; break;
4943 		}
4944 
4945 		snprintf(Sound, 9, "H_%s_%s%d", dmg_types[type-1], armor_types[armor], core->Roll(1, 3, 0));
4946 	} else {
4947 		if (levels) {
4948 			snprintf(Sound, 9, "HIT_0%d%c%c", type, armor+'A', suffix?'1':0);
4949 		} else {
4950 			snprintf(Sound, 9, "HIT_0%d%c", type, suffix?'1':0);
4951 		}
4952 	}
4953 	core->GetAudioDrv()->Play(Sound, SFX_CHAN_HITS, Pos.x, Pos.y);
4954 }
4955 
4956 // Play swing sounds
4957 // <wtype><n> in bgs, but it gets quickly complicated with iwds (SW_<wname>) and pst, which add a bunch of exceptions
4958 // ... so they're just fully stored in itemsnd.2da
4959 // iwds also have five sounds of hitting armor (SW_SWD01) that we ignore
4960 // pst has a lot of duplicates (1&3&6, 2&5, 8&9, 4, 7, 10) and little variation (all 6 entries for 8 are identical)
PlaySwingSound(WeaponInfo & wi) const4961 void Actor::PlaySwingSound(WeaponInfo &wi) const
4962 {
4963 	ieResRef sound;
4964 	ResRef sound2;
4965 	ieDword itemType = wi.itemtype;
4966 	int isCount = gamedata->GetSwingCount(itemType);
4967 
4968 	if (isCount == -2) {
4969 		// monsters with non-standard items, none or something else
4970 		int stance = GetStance();
4971 		if (stance == IE_ANI_ATTACK_SLASH || stance == IE_ANI_ATTACK_BACKSLASH || stance == IE_ANI_ATTACK_JAB || stance == IE_ANI_SHOOT) {
4972 			GetSoundFromFile(sound, 100+stance);
4973 			sound2 = sound;
4974 		}
4975 	} else {
4976 		// swing sounds start at column 3 (index 2)
4977 		int isChoice = core->Roll(1, isCount, -1) + 2;
4978 		if (!gamedata->GetItemSound(sound2, itemType, NULL, isChoice)) return;
4979 	}
4980 
4981 	core->GetAudioDrv()->Play(sound2, SFX_CHAN_SWINGS, Pos.x, Pos.y);
4982 }
4983 
4984 //Just to quickly inspect debug maximum values
4985 #if 0
4986 void Actor::dumpMaxValues()
4987 {
4988 	int symbol = core->LoadSymbol( "stats" );
4989 	if (symbol !=-1) {
4990 		SymbolMgr *sym = core->GetSymbol( symbol );
4991 
4992 		for(int i=0;i<MAX_STATS;i++) {
4993 			print("%d(%s) %d", i, sym->GetValue(i), maximum_values[i]);
4994 		}
4995 	}
4996 }
4997 #endif
4998 
dump() const4999 void Actor::dump() const
5000 {
5001 	StringBuffer buffer;
5002 	dump(buffer);
5003 	Log(DEBUG, "Actor", buffer);
5004 }
5005 
dump(StringBuffer & buffer) const5006 void Actor::dump(StringBuffer& buffer) const
5007 {
5008 	buffer.appendFormatted( "Debugdump of Actor %s (%s, %s):\n", LongName, ShortName, GetName(-1) );
5009 	buffer.append("Scripts:");
5010 	for (unsigned int i = 0; i < MAX_SCRIPTS; i++) {
5011 		const char* poi = "<none>";
5012 		if (Scripts[i]) {
5013 			poi = Scripts[i]->GetName();
5014 		}
5015 		buffer.appendFormatted( " %.8s", poi );
5016 	}
5017 	buffer.append("\n");
5018 	buffer.appendFormatted("Area:       %.8s ([%d.%d])\n", Area, Pos.x, Pos.y);
5019 	buffer.appendFormatted("Dialog:     %.8s    TalkCount:  %d\n", Dialog, TalkCount);
5020 	buffer.appendFormatted("Global ID:  %d   PartySlot: %d\n", GetGlobalID(), InParty);
5021 	buffer.appendFormatted("Script name:%.32s    Current action: %d    Total: %ld\n", scriptName, CurrentAction ? CurrentAction->actionID : -1, (long) actionQueue.size());
5022 	buffer.appendFormatted("Int. Flags: 0x%x    ", InternalFlags);
5023 	buffer.appendFormatted("MC Flags: 0x%x    ", Modified[IE_MC_FLAGS]);
5024 	buffer.appendFormatted("Allegiance: %d   current allegiance:%d\n", BaseStats[IE_EA], Modified[IE_EA] );
5025 	buffer.appendFormatted("Class:      %d   current class:%d    Kit: %d (base: %d)\n", BaseStats[IE_CLASS], Modified[IE_CLASS], Modified[IE_KIT], BaseStats[IE_KIT] );
5026 	buffer.appendFormatted("Race:       %d   current race:%d\n", BaseStats[IE_RACE], Modified[IE_RACE] );
5027 	buffer.appendFormatted("Gender:     %d   current gender:%d\n", BaseStats[IE_SEX], Modified[IE_SEX] );
5028 	buffer.appendFormatted("Specifics:  %d   current specifics:%d\n", BaseStats[IE_SPECIFIC], Modified[IE_SPECIFIC] );
5029 	buffer.appendFormatted("Alignment:  %x   current alignment:%x\n", BaseStats[IE_ALIGNMENT], Modified[IE_ALIGNMENT] );
5030 	buffer.appendFormatted("Morale:     %d   current morale:%d\n", BaseStats[IE_MORALE], Modified[IE_MORALE] );
5031 	buffer.appendFormatted("Moralebreak:%d   Morale recovery:%d\n", Modified[IE_MORALEBREAK], Modified[IE_MORALERECOVERYTIME] );
5032 	buffer.appendFormatted("Visualrange:%d (Explorer: %d)\n", Modified[IE_VISUALRANGE], Modified[IE_EXPLORE] );
5033 	buffer.appendFormatted("Fatigue: %d (current: %d)   Luck: %d\n", BaseStats[IE_FATIGUE], Modified[IE_FATIGUE], Modified[IE_LUCK]);
5034 	buffer.appendFormatted("Movement rate: %d (current: %d)\n\n", BaseStats[IE_MOVEMENTRATE], Modified[IE_MOVEMENTRATE]);
5035 
5036 	//this works for both level slot style
5037 	buffer.appendFormatted("Levels (average: %d):\n", GetXPLevel(true));
5038 	for (unsigned int i = 0; i < ISCLASSES; i++) {
5039 		int level = GetClassLevel(i);
5040 		if (level) {
5041 			buffer.appendFormatted("%s: %d    ", isclassnames[i], level);
5042 		}
5043 	}
5044 	buffer.appendFormatted("\n");
5045 
5046 	buffer.appendFormatted("current HP:%d\n", BaseStats[IE_HITPOINTS] );
5047 	buffer.appendFormatted("Mod[IE_ANIMATION_ID]: 0x%04X ResRef:%.8s Stance: %d\n", Modified[IE_ANIMATION_ID], anims->ResRef, GetStance() );
5048 	buffer.appendFormatted("TURNUNDEADLEVEL: %d current: %d\n", BaseStats[IE_TURNUNDEADLEVEL], Modified[IE_TURNUNDEADLEVEL]);
5049 	buffer.appendFormatted("Colors:    ");
5050 	if (core->HasFeature(GF_ONE_BYTE_ANIMID) ) {
5051 		for(unsigned int i = 0; i < Modified[IE_COLORCOUNT]; i++) {
5052 			buffer.appendFormatted("   %d", Modified[IE_COLORS+i]);
5053 		}
5054 	} else {
5055 		for(unsigned int i = 0; i < 7; i++) {
5056 			buffer.appendFormatted("   %d", Modified[IE_COLORS+i]);
5057 		}
5058 	}
5059 	buffer.append("\n");
5060 	buffer.appendFormatted("WaitCounter: %d\n", (int) GetWait());
5061 	buffer.appendFormatted("LastTarget: %d %s    ", LastTarget, GetActorNameByID(LastTarget));
5062 	buffer.appendFormatted("LastSpellTarget: %d %s\n", LastSpellTarget, GetActorNameByID(LastSpellTarget));
5063 	buffer.appendFormatted("LastTalked: %d %s\n", LastTalker, GetActorNameByID(LastTalker));
5064 	inventory.dump(buffer);
5065 	spellbook.dump(buffer);
5066 	fxqueue.dump(buffer);
5067 }
5068 
GetActorNameByID(ieDword ID) const5069 const char* Actor::GetActorNameByID(ieDword ID) const
5070 {
5071 	Actor *actor = GetCurrentArea()->GetActorByGlobalID(ID);
5072 	if (!actor) {
5073 		return "<NULL>";
5074 	}
5075 	return actor->GetScriptName();
5076 }
5077 
SetMap(Map * map)5078 void Actor::SetMap(Map *map)
5079 {
5080 	//Did we have an area?
5081 	bool effinit=!GetCurrentArea();
5082 	if (area && BlocksSearchMap()) area->ClearSearchMapFor(this);
5083 	//now we have an area
5084 	Scriptable::SetMap(map);
5085 	//unless we just lost it, in that case clear up some fields and leave
5086 	if (!map) {
5087 		//more bits may or may not be needed
5088 		InternalFlags &=~IF_CLEANUP;
5089 		return;
5090 	}
5091 	InternalFlags &= ~IF_PST_WMAPPING;
5092 
5093 	//These functions are called once when the actor is first put in
5094 	//the area. It already has all the items (including fist) at this
5095 	//time and it is safe to call effects.
5096 	//This hack is to delay the equipping effects until the actor has
5097 	//an area (and the game object is also existing)
5098 	if (effinit) {
5099 		//already initialized, no need of updating stuff
5100 		if (InternalFlags & IF_GOTAREA) return;
5101 		InternalFlags |= IF_GOTAREA;
5102 
5103 		//apply feats
5104 		ApplyFeats();
5105 		//apply persistent feat spells
5106 		ApplyExtraSettings();
5107 
5108 		int SlotCount = inventory.GetSlotCount();
5109 		for (int Slot = 0; Slot<SlotCount;Slot++) {
5110 			int slottype = core->QuerySlotEffects( Slot );
5111 			switch (slottype) {
5112 			case SLOT_EFFECT_NONE:
5113 			case SLOT_EFFECT_FIST:
5114 			case SLOT_EFFECT_MELEE:
5115 			case SLOT_EFFECT_MISSILE:
5116 				break;
5117 			default:
5118 				inventory.EquipItem( Slot );
5119 				break;
5120 			}
5121 		}
5122 		//We need to convert this to signed 16 bits, because
5123 		//it is actually a 16 bit number.
5124 		//It is signed to have the correct math
5125 		//when adding it to the base slot (SLOT_WEAPON) in
5126 		//case of quivers. (weird IE magic)
5127 		//The other word is the equipped header.
5128 		//find a quiver for the bow, etc
5129 		inventory.EquipItem(inventory.GetEquippedSlot());
5130 		SetEquippedQuickSlot(inventory.GetEquipped(), inventory.GetEquippedHeader());
5131 	}
5132 	if (BlocksSearchMap()) map->BlockSearchMap(Pos, size, IsPartyMember() ? PathMapFlags::PC : PathMapFlags::NPC);
5133 }
5134 
5135 // Position should be a navmap point
SetPosition(const Point & nmptTarget,int jump,int radiusx,int radiusy,int size)5136 void Actor::SetPosition(const Point &nmptTarget, int jump, int radiusx, int radiusy, int size)
5137 {
5138 	ResetPathTries();
5139 	ClearPath(true);
5140 	Point p, q;
5141 	p.x = nmptTarget.x / 16;
5142 	p.y = nmptTarget.y / 12;
5143 
5144 	q = p;
5145 	if (jump && !(Modified[IE_DONOTJUMP] & DNJ_FIT) && size ) {
5146 		Map *map = GetCurrentArea();
5147 		//clear searchmap so we won't block ourselves
5148 		map->ClearSearchMapFor(this);
5149 		map->AdjustPosition(p, radiusx, radiusy, size);
5150 	}
5151 	if (p==q) {
5152 		MoveTo(nmptTarget);
5153 	} else {
5154 		p.x = p.x * 16 + 8;
5155 		p.y = p.y * 12 + 6;
5156 		MoveTo( p );
5157 	}
5158 }
5159 
5160 /* this is returning the level of the character for xp calculations
5161 	and the average level for dual/multiclass (rounded up),
5162 	also with iwd2's 3rd ed rules, this is why it is a separate function */
GetXPLevel(int modified) const5163 ieDword Actor::GetXPLevel(int modified) const
5164 {
5165 	const ieDword *stats;
5166 
5167 	if (modified) {
5168 		stats = Modified;
5169 	}
5170 	else {
5171 		stats = BaseStats;
5172 	}
5173 
5174 	size_t clscount = 0;
5175 	float average = 0;
5176 	if (iwd2class) {
5177 		// iwd2
5178 		return stats[IE_CLASSLEVELSUM];
5179 	} else {
5180 		unsigned int levels[3]={stats[IE_LEVEL], stats[IE_LEVEL2], stats[IE_LEVEL3]};
5181 		average = levels[0];
5182 		clscount = 1;
5183 		if (IsDualClassed()) {
5184 			// dualclassed
5185 			if (levels[1] > 0) {
5186 				clscount++;
5187 				average += levels[1];
5188 			}
5189 		}
5190 		else if (IsMultiClassed()) {
5191 			//clscount is the number of on bits in the MULTI field
5192 			clscount = CountBits (multiclass);
5193 			assert(clscount && clscount <= 3);
5194 			for (size_t i=1; i<clscount; i++)
5195 				average += levels[i];
5196 		} //else single classed
5197 		average = average / (float) clscount + 0.5;
5198 	}
5199 	return ieDword(average);
5200 }
5201 
5202 // returns the guessed caster level by passed spell type
5203 // FIXME: add more logic for cross-type kits (like avengers)?
5204 // FIXME: iwd2 does the right thing at least for spells cast from spellbooks;
5205 //        that is, it takes the correct level, not first or average or min or max.
5206 //        We need to propagate the spellbook info all through here. :/
5207 //        NOTE: this is only problematic for multiclassed actors
GetBaseCasterLevel(int spelltype,int flags) const5208 ieDword Actor::GetBaseCasterLevel(int spelltype, int flags) const
5209 {
5210 	int level = 0;
5211 
5212 	switch(spelltype)
5213 	{
5214 	case IE_SPL_PRIEST:
5215 		level = GetClericLevel();
5216 		if (!level) level = GetDruidLevel();
5217 		if (!level) level = GetPaladinLevel();
5218 		// for cleric/rangers, we can't tell from which class a spell is, unless unique, so we ignore the distinction
5219 		if (!level) level = GetRangerLevel();
5220 		break;
5221 	case IE_SPL_WIZARD:
5222 		level = GetMageLevel();
5223 		if (!level) level = GetSorcererLevel();
5224 		if (!level) level = GetBardLevel();
5225 		break;
5226 	default:
5227 		// checking if anyone uses the psion, item and song types
5228 		if (spelltype != IE_SPL_INNATE) {
5229 			Log(WARNING, "Actor", "Unhandled SPL type %d, using average casting level!", spelltype);
5230 		}
5231 		break;
5232 	}
5233 	// if nothing was found, use the average level
5234 	if (!level && !flags) level = GetXPLevel(true);
5235 
5236 	return level;
5237 }
5238 
GetWildMod(int level)5239 int Actor::GetWildMod(int level)
5240 {
5241 	if (GetStat(IE_KIT) == KIT_WILDMAGE) {
5242 		// avoid rerolling the mod, since we get called multiple times per each cast
5243 		// TODO: also handle a reroll to 0
5244 		if (!WMLevelMod) {
5245 			if (level>=MAX_LEVEL) level=MAX_LEVEL;
5246 			if(level<1) level=1;
5247 			WMLevelMod = wmlevels[core->Roll(1,20,-1)][level-1];
5248 
5249 			core->GetTokenDictionary()->SetAtCopy("LEVELDIF", abs(WMLevelMod));
5250 			if (core->HasFeedback(FT_STATES)) {
5251 				if (WMLevelMod > 0) {
5252 					displaymsg->DisplayConstantStringName(STR_CASTER_LVL_INC, DMC_WHITE, this);
5253 				} else if (WMLevelMod < 0) {
5254 					displaymsg->DisplayConstantStringName(STR_CASTER_LVL_DEC, DMC_WHITE, this);
5255 				}
5256 			}
5257 		}
5258 		return WMLevelMod;
5259 	}
5260 	return 0;
5261 }
5262 
CastingLevelBonus(int level,int type)5263 int Actor::CastingLevelBonus(int level, int type)
5264 {
5265 	int bonus = 0;
5266 	switch(type)
5267 	{
5268 	case IE_SPL_PRIEST:
5269 		bonus = GetStat(IE_CASTINGLEVELBONUSCLERIC);
5270 		break;
5271 	case IE_SPL_WIZARD:
5272 		bonus = GetWildMod(level) + GetStat(IE_CASTINGLEVELBONUSMAGE);
5273 	}
5274 
5275 	return bonus;
5276 }
5277 
GetCasterLevel(int spelltype)5278 ieDword Actor::GetCasterLevel(int spelltype)
5279 {
5280 	int level = GetBaseCasterLevel(spelltype);
5281 	return level + CastingLevelBonus(level, spelltype);
5282 }
5283 
5284 // this works properly with disabled dualclassed actors, since it ends up in GetClassLevel
GetAnyActiveCasterLevel() const5285 ieDword Actor::GetAnyActiveCasterLevel() const
5286 {
5287 	int strict = 1;
5288 	// only player classes will have levels in the correct slots
5289 	if (!HasPlayerClass()) {
5290 		strict = 0;
5291 	}
5292 	return GetBaseCasterLevel(IE_SPL_PRIEST, strict) + GetBaseCasterLevel(IE_SPL_WIZARD, strict);
5293 }
5294 
GetEncumbranceFactor(bool feedback) const5295 int Actor::GetEncumbranceFactor(bool feedback) const
5296 {
5297 	int encumbrance = inventory.GetWeight();
5298 	int maxWeight = GetMaxEncumbrance();
5299 
5300 	if (encumbrance <= maxWeight || (BaseStats[IE_EA] > EA_GOODCUTOFF && !third)) {
5301 		return 1;
5302 	}
5303 	if (encumbrance <= maxWeight * 2) {
5304 		if (feedback && core->HasFeedback(FT_STATES)) {
5305 			displaymsg->DisplayConstantStringName(STR_HALFSPEED, DMC_WHITE, this);
5306 		}
5307 		return 2;
5308 	}
5309 	if (feedback && core->HasFeedback(FT_STATES)) {
5310 		displaymsg->DisplayConstantStringName(STR_CANTMOVE, DMC_WHITE, this);
5311 	}
5312 	return 123456789; // large enough to round to 0 when used as a divisor
5313 }
5314 
CalculateSpeed(bool feedback) const5315 int Actor::CalculateSpeed(bool feedback) const
5316 {
5317 	if (core->HasFeature(GF_RESDATA_INI)) {
5318 		return CalculateSpeedFromINI(feedback);
5319 	} else {
5320 		return CalculateSpeedFromRate(feedback);
5321 	}
5322 }
5323 
5324 // NOTE: for ini-based speed users this will only update their encumbrance, speed will be 0
CalculateSpeedFromRate(bool feedback) const5325 int Actor::CalculateSpeedFromRate(bool feedback) const
5326 {
5327 	int movementRate = GetStat(IE_MOVEMENTRATE);
5328 	int encumbranceFactor = GetEncumbranceFactor(feedback);
5329 	if (BaseStats[IE_EA] > EA_GOODCUTOFF && !third) {
5330 		// cheating bastards (drow in ar2401 for example)
5331 	} else {
5332 		movementRate /= encumbranceFactor;
5333 	}
5334 	if (movementRate) {
5335 		return 1500 / movementRate;
5336 	} else {
5337 		return 0;
5338 	}
5339 }
5340 
CalculateSpeedFromINI(bool feedback) const5341 int Actor::CalculateSpeedFromINI(bool feedback) const
5342 {
5343 	int encumbranceFactor = GetEncumbranceFactor(feedback);
5344 	ieDword animid = BaseStats[IE_ANIMATION_ID];
5345 	if (core->HasFeature(GF_ONE_BYTE_ANIMID)) {
5346 		animid = animid & 0xff;
5347 	}
5348 	assert(animid < (ieDword)CharAnimations::GetAvatarsCount());
5349 	const AvatarStruct *avatar = CharAnimations::GetAvatarStruct(animid);
5350 	int newSpeed = 0;
5351 	if (avatar->RunScale && (GetInternalFlag() & IF_RUNNING)) {
5352 		newSpeed = avatar->RunScale;
5353 	} else if (avatar->WalkScale) {
5354 		newSpeed = avatar->WalkScale;
5355 	} else {
5356 		// 3 pst animations don't have a walkscale set, but they're immobile, so the default of 0 is fine
5357 	}
5358 
5359 	// the speeds are already inverted, so we need to increase them to slow down
5360 	if (encumbranceFactor <= 2) {
5361 		newSpeed *= encumbranceFactor;
5362 	} else {
5363 		newSpeed = 0;
5364 	}
5365 	return newSpeed;
5366 }
5367 
5368 //receive turning
Turn(Scriptable * cleric,ieDword turnlevel)5369 void Actor::Turn(Scriptable *cleric, ieDword turnlevel)
5370 {
5371 	bool evilcleric = false;
5372 
5373 	if (!turnlevel) {
5374 		return;
5375 	}
5376 
5377 	//determine if we see the cleric (distance)
5378 	if (!CanSee(cleric, this, true, GA_NO_DEAD)) {
5379 		return;
5380 	}
5381 
5382 	if ((cleric->Type==ST_ACTOR) && GameScript::ID_Alignment((Actor *)cleric,AL_EVIL) ) {
5383 		evilcleric = true;
5384 	}
5385 
5386 	//a little adjustment of the level to get a slight randomness on who is turned
5387 	unsigned int level = GetXPLevel(true)-(GetGlobalID()&3);
5388 
5389 	//this is safely hardcoded i guess
5390 	if (Modified[IE_GENERAL]!=GEN_UNDEAD) {
5391 		level = GetPaladinLevel();
5392 		if (evilcleric && level) {
5393 			AddTrigger(TriggerEntry(trigger_turnedby, cleric->GetGlobalID()));
5394 			if (turnlevel >= level+TURN_DEATH_LVL_MOD) {
5395 				if (gamedata->Exists("panic", IE_SPL_CLASS_ID)) {
5396 					core->ApplySpell("panic", this, cleric, level);
5397 				} else {
5398 					print("Panic from turning!");
5399 					Panic(cleric, PANIC_RUNAWAY);
5400 				}
5401 			}
5402 		}
5403 		return;
5404 	}
5405 
5406 	//determine alignment (if equals, then no turning)
5407 
5408 	AddTrigger(TriggerEntry(trigger_turnedby, cleric->GetGlobalID()));
5409 
5410 	//determine panic or destruction/control
5411 	//we get the modified level
5412 	if (turnlevel >= level+TURN_DEATH_LVL_MOD) {
5413 		if (evilcleric) {
5414 			Effect *fx = fxqueue.CreateEffect(control_creature_ref, GEN_UNDEAD, 3, FX_DURATION_INSTANT_LIMITED);
5415 			if (!fx) {
5416 				fx = fxqueue.CreateEffect(control_undead_ref, GEN_UNDEAD, 3, FX_DURATION_INSTANT_LIMITED);
5417 			}
5418 			if (fx) {
5419 				fx->Duration = core->Time.round_sec;
5420 				fx->Target = FX_TARGET_PRESET;
5421 				core->ApplyEffect(fx, this, cleric);
5422 				delete fx;
5423 				return;
5424 			}
5425 			//fallthrough for bg1
5426 		}
5427 		Die(cleric);
5428 	} else if (turnlevel >= level+TURN_PANIC_LVL_MOD) {
5429 		print("Panic from turning!");
5430 		Panic(cleric, PANIC_RUNAWAY);
5431 	}
5432 }
5433 
Resurrect(const Point & destPoint)5434 void Actor::Resurrect(const Point &destPoint)
5435 {
5436 	if (!(Modified[IE_STATE_ID ] & STATE_DEAD)) {
5437 		return;
5438 	}
5439 	InternalFlags&=IF_FROMGAME; //keep these flags (what about IF_INITIALIZED)
5440 	InternalFlags|=IF_ACTIVE|IF_VISIBLE; //set these flags
5441 	SetBaseBit(IE_STATE_ID, STATE_DEAD, false);
5442 	SetBase(IE_STATE_ID, 0);
5443 	SetBase(IE_AVATARREMOVAL, 0);
5444 	if (!destPoint.isnull()) {
5445 		SetPosition(destPoint, CC_CHECK_IMPASSABLE, 0);
5446 	}
5447 	if (ShouldModifyMorale()) SetBase(IE_MORALE, 10);
5448 	//resurrect spell sets the hitpoints to maximum in a separate effect
5449 	//raise dead leaves it at 1 hp
5450 	SetBase(IE_HITPOINTS, 1);
5451 	Stop();
5452 	SetStance(IE_ANI_EMERGE);
5453 	Game *game = core->GetGame();
5454 	//readjust death variable on resurrection
5455 	ieVariable DeathVar;
5456 	if (core->HasFeature(GF_HAS_KAPUTZ) && (AppearanceFlags&APP_DEATHVAR)) {
5457 		const size_t len = snprintf(DeathVar, sizeof(ieVariable), "%s_DEAD", scriptName);
5458 		if (len > sizeof(ieVariable)) {
5459 			Log(ERROR, "Actor", "Scriptname %s (name: %s) is too long for generating death globals!", scriptName, LongName);
5460 		}
5461 		ieDword value=0;
5462 
5463 		game->kaputz->Lookup(DeathVar, value);
5464 		if (value>0) {
5465 			game->kaputz->SetAt(DeathVar, value-1);
5466 		}
5467 	// not bothering with checking actor->SetDeathVar, since the SetAt nocreate parameter is true
5468 	} else if (!core->HasFeature(GF_HAS_KAPUTZ)) {
5469 		size_t len = snprintf(DeathVar, 32, core->GetDeathVarFormat(), scriptName);
5470 		if (len > 32) {
5471 			Log(ERROR, "Actor", "Scriptname %s (name: %s) is too long for generating death globals (on resurrect)!", scriptName, LongName);
5472 		}
5473 		game->locals->SetAt(DeathVar, 0, true);
5474 	}
5475 
5476 	ResetCommentTime();
5477 	//clear effects?
5478 }
5479 
GetVarName(const char * table,int value)5480 static const char *GetVarName(const char *table, int value)
5481 {
5482 	int symbol = core->LoadSymbol( table );
5483 	if (symbol!=-1) {
5484 		Holder<SymbolMgr> sym = core->GetSymbol( symbol );
5485 		return sym->GetValue( value );
5486 	}
5487 	return NULL;
5488 }
5489 
5490 // [EA.FACTION.TEAM.GENERAL.RACE.CLASS.SPECIFIC.GENDER.ALIGN] has to be the same for both creatures
OfType(const Actor * a,const Actor * b)5491 static bool OfType(const Actor *a, const Actor *b)
5492 {
5493 	bool same = a->GetStat(IE_EA) == b->GetStat(IE_EA) &&
5494 		a->GetStat(IE_RACE) == b->GetStat(IE_RACE) &&
5495 		a->GetStat(IE_GENERAL) == b->GetStat(IE_GENERAL) &&
5496 		a->GetStat(IE_SPECIFIC) == b->GetStat(IE_SPECIFIC) &&
5497 		a->GetStat(IE_CLASS) == b->GetStat(IE_CLASS) &&
5498 		a->GetStat(IE_TEAM) == b->GetStat(IE_TEAM) &&
5499 		a->GetStat(IE_FACTION) == b->GetStat(IE_FACTION) &&
5500 		a->GetStat(IE_SEX) == b->GetStat(IE_SEX) &&
5501 		a->GetStat(IE_ALIGNMENT) == b->GetStat(IE_ALIGNMENT);
5502 	if (!same) return false;
5503 
5504 	if (!third) return true;
5505 
5506 	return a->GetStat(IE_SUBRACE) == b->GetStat(IE_SUBRACE);
5507 }
5508 
SendDiedTrigger() const5509 void Actor::SendDiedTrigger() const
5510 {
5511 	if (!area) return;
5512 	std::vector<Actor *> neighbours = area->GetAllActorsInRadius(Pos, GA_NO_LOS|GA_NO_DEAD|GA_NO_UNSCHEDULED, GetSafeStat(IE_VISUALRANGE));
5513 	int ea = Modified[IE_EA];
5514 
5515 	std::vector<Actor *>::iterator poi;
5516 	for (poi = neighbours.begin(); poi != neighbours.end(); poi++) {
5517 		// NOTE: currently also sending the trigger to ourselves — prevent if it causes problems
5518 		(*poi)->AddTrigger(TriggerEntry(trigger_died, GetGlobalID()));
5519 
5520 		// allies take a hit on morale and nobody cares about neutrals
5521 		if (!(*poi)->ShouldModifyMorale()) continue;
5522 		int pea = (*poi)->GetStat(IE_EA);
5523 		if (ea == EA_PC && pea == EA_PC) {
5524 			(*poi)->NewBase(IE_MORALE, (ieDword) -1, MOD_ADDITIVE);
5525 		} else if (OfType(this, *poi)) {
5526 			(*poi)->NewBase(IE_MORALE, (ieDword) -1, MOD_ADDITIVE);
5527 		// are we an enemy of poi, regardless if we're good or evil?
5528 		} else if (abs(ea - pea) > 30) {
5529 			(*poi)->NewBase(IE_MORALE, 2, MOD_ADDITIVE);
5530 		}
5531 	}
5532 }
5533 
Die(Scriptable * killer,bool grantXP)5534 void Actor::Die(Scriptable *killer, bool grantXP)
5535 {
5536 	if (InternalFlags&IF_REALLYDIED) {
5537 		return; //can die only once
5538 	}
5539 
5540 	//Can't simply set Selected to false, game has its own little list
5541 	Game *game = core->GetGame();
5542 	game->SelectActor(this, false, SELECT_NORMAL);
5543 
5544 	displaymsg->DisplayConstantStringName(STR_DEATH, DMC_WHITE, this);
5545 	VerbalConstant(VB_DIE);
5546 
5547 	// remove poison, hold, casterhold, stun and its icon
5548 	Effect *newfx;
5549 	newfx = EffectQueue::CreateEffect(fx_cure_poisoned_state_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
5550 	core->ApplyEffect(newfx, this, this);
5551 	delete newfx;
5552 	newfx = EffectQueue::CreateEffect(fx_cure_hold_state_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
5553 	core->ApplyEffect(newfx, this, this);
5554 	delete newfx;
5555 	newfx = EffectQueue::CreateEffect(fx_unpause_caster_ref, 0, 100, FX_DURATION_INSTANT_PERMANENT);
5556 	core->ApplyEffect(newfx, this, this);
5557 	delete newfx;
5558 	newfx = EffectQueue::CreateEffect(fx_cure_stun_state_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
5559 	core->ApplyEffect(newfx, this, this);
5560 	delete newfx;
5561 	newfx = EffectQueue::CreateEffect(fx_remove_portrait_icon_ref, 0, 37, FX_DURATION_INSTANT_PERMANENT);
5562 	core->ApplyEffect(newfx, this, this);
5563 	delete newfx;
5564 
5565 	// clearing the search map here means it's not blocked during death animations
5566 	// this is perhaps not ideal, but matches other searchmap code which uses
5567 	// GA_NO_DEAD to exclude IF_JUSTDIED actors as well as dead ones
5568 	if (area)
5569 		area->ClearSearchMapFor(this);
5570 
5571 	//JUSTDIED will be removed after the first script check
5572 	//otherwise it is the same as REALLYDIED
5573 	InternalFlags|=IF_REALLYDIED|IF_JUSTDIED;
5574 	//remove IDLE so the actor gets a chance to die properly
5575 	InternalFlags&=~IF_IDLE;
5576 	if (GetStance() != IE_ANI_DIE) {
5577 		SetStance(IE_ANI_DIE);
5578 	}
5579 	AddTrigger(TriggerEntry(trigger_die));
5580 	SendDiedTrigger();
5581 	if (pstflags) {
5582 		AddTrigger(TriggerEntry(trigger_namelessbitthedust));
5583 	}
5584 
5585 	Actor *act=NULL;
5586 	if (!killer) {
5587 		// TODO: is this right?
5588 		killer = area->GetActorByGlobalID(LastHitter);
5589 	}
5590 
5591 	bool killerPC = false;
5592 	if (killer && killer->Type == ST_ACTOR) {
5593 		act = (Actor *) killer;
5594 		// for unknown reasons the original only sends the trigger if the killer is ok
5595 		if (act && !(act->GetStat(IE_STATE_ID) & (STATE_DEAD|STATE_PETRIFIED|STATE_FROZEN))) {
5596 			killer->AddTrigger(TriggerEntry(trigger_killed, GetGlobalID()));
5597 			if (act->ShouldModifyMorale()) act->NewBase(IE_MORALE, 3, MOD_ADDITIVE);
5598 		}
5599 		killerPC = act->InParty > 0;
5600 	}
5601 
5602 	if (InParty) {
5603 		game->PartyMemberDied(this);
5604 		core->Autopause(AP_DEAD, this);
5605 	} else {
5606 		// sometimes we want to skip xp giving and favourite registration
5607 		if (grantXP && act) {
5608 			if (act->InParty) {
5609 				//adjust kill statistics here
5610 				PCStatsStruct *stat = act->PCStats;
5611 				if (stat) {
5612 					stat->NotifyKill(Modified[IE_XPVALUE], ShortStrRef);
5613 				}
5614 				InternalFlags|=IF_GIVEXP;
5615 			}
5616 
5617 			// friendly party summons' kills also grant xp
5618 			if (act->Modified[IE_SEX] == SEX_SUMMON && act->Modified[IE_EA] == EA_CONTROLLED) {
5619 				InternalFlags|=IF_GIVEXP;
5620 			} else if (act->Modified[IE_EA] == EA_FAMILIAR) {
5621 				// familiar's kills also grant xp
5622 				InternalFlags|=IF_GIVEXP;
5623 			}
5624 		}
5625 	}
5626 
5627 	// XP seems to be handed at out at the moment of death
5628 	if (InternalFlags&IF_GIVEXP) {
5629 		//give experience to party
5630 		game->ShareXP(Modified[IE_XPVALUE], sharexp );
5631 
5632 		if (!InParty && act && act->GetStat(IE_EA) <= EA_CONTROLLABLE && !core->InCutSceneMode()) {
5633 			// adjust reputation if the corpse was:
5634 			// an innocent, a member of the Flaming Fist or something evil
5635 			int repmod = 0;
5636 			if (Modified[IE_CLASS] == CLASS_INNOCENT) {
5637 				repmod = core->GetReputationMod(0);
5638 			} else if (Modified[IE_CLASS] == CLASS_FLAMINGFIST) {
5639 				repmod = core->GetReputationMod(3);
5640 			}
5641 			if (GameScript::ID_Alignment(this,AL_EVIL) ) {
5642 				repmod += core->GetReputationMod(7);
5643 			}
5644 			if (repmod) {
5645 				game->SetReputation(game->Reputation + repmod);
5646 			}
5647 		}
5648 	}
5649 
5650 	ReleaseCurrentAction();
5651 	ClearPath(true);
5652 	SetModal( MS_NONE );
5653 
5654 	if (InParty && killerPC) {
5655 		game->locals->SetAt("PM_KILLED", 1, nocreate);
5656 	}
5657 
5658 	// EXTRACOUNT is updated at the moment of death
5659 	if (Modified[IE_SEX] == SEX_EXTRA || (Modified[IE_SEX] >= SEX_EXTRA2 && Modified[IE_SEX] <= SEX_MAXEXTRA)) {
5660 		// if gender is set to one of the EXTRA values, then at death, we have to decrease
5661 		// the relevant EXTRACOUNT area variable. scripts use this to check how many actors
5662 		// of this extra id are still alive (for example, see the ToB challenge scripts)
5663 		ieVariable varname;
5664 		if (Modified[IE_SEX] == SEX_EXTRA) {
5665 			snprintf(varname, 32, "EXTRACOUNT");
5666 		} else {
5667 			snprintf(varname, 32, "EXTRACOUNT%d", 2 + (Modified[IE_SEX] - SEX_EXTRA2));
5668 		}
5669 
5670 		Map *area = GetCurrentArea();
5671 		if (area) {
5672 			ieDword value = 0;
5673 			area->locals->Lookup(varname, value);
5674 			// i am guessing that we shouldn't decrease below 0
5675 			if (value > 0) {
5676 				area->locals->SetAt(varname, value - 1);
5677 			}
5678 		}
5679 	}
5680 
5681 	//a plot critical creature has died (iwd2)
5682 	// BG2 uses the same field for special creatures (alternate melee damage): MC_LARGE_CREATURE
5683 	if (third && BaseStats[IE_MC_FLAGS] & MC_PLOT_CRITICAL) {
5684 		core->GetGUIScriptEngine()->RunFunction("GUIWORLD", "DeathWindowPlot", false);
5685 	}
5686 	//ensure that the scripts of the actor will run as soon as possible
5687 	ImmediateEvent();
5688 }
5689 
SetPersistent(int partyslot)5690 void Actor::SetPersistent(int partyslot)
5691 {
5692 	if (partyslot<0) {
5693 		//demote actor to be saved in area (after moving between areas)
5694 		InParty = 0;
5695 		InternalFlags&=~IF_FROMGAME;
5696 		return;
5697 	}
5698 	InParty = (ieByte) partyslot;
5699 	InternalFlags|=IF_FROMGAME;
5700 	//if an actor is coming from a game, it should have these too
5701 	CreateStats();
5702 	// ensure QSlots are set up to be what the class needs
5703 	InitButtons(GetActiveClass(), false);
5704 
5705 	if (PCStats->QuickWeaponSlots[0] != 0xffff) return;
5706 	// ReinitQuickSlots does not take care of weapon slots, so do it manually
5707 	for (int i = 0; i < 4; i++) {
5708 		SetupQuickSlot(i + ACT_WEAPON1, inventory.GetWeaponSlot(i), 0);
5709 	}
5710 	// call ReinitQuickSlots here if something needs it
5711 }
5712 
DestroySelf()5713 void Actor::DestroySelf()
5714 {
5715 	InternalFlags|=IF_CLEANUP;
5716 	RemovalTime = 0;
5717 	// clear search map so that a new actor can immediately go there
5718 	// (via ChangeAnimationCore)
5719 	if (area)
5720 		area->ClearSearchMapFor(this);
5721 }
5722 
CheckOnDeath()5723 bool Actor::CheckOnDeath()
5724 {
5725 	if (InternalFlags&IF_CLEANUP) {
5726 		return true;
5727 	}
5728 	// FIXME
5729 	if (InternalFlags&IF_JUSTDIED || CurrentAction || GetNextAction() || GetStance() == IE_ANI_DIE) {
5730 		return false; //actor is currently dying, let him die first
5731 	}
5732 	if (!(InternalFlags&IF_REALLYDIED) ) {
5733 		return false;
5734 	}
5735 	//don't mess with the already deceased
5736 	if (BaseStats[IE_STATE_ID]&STATE_DEAD) {
5737 		return false;
5738 	}
5739 	// don't destroy actors currently in a dialog
5740 	GameControl *gc = core->GetGameControl();
5741 	if (gc && gc->dialoghandler->InDialog(this)) {
5742 		return false;
5743 	}
5744 
5745 	ClearActions();
5746 	//missed the opportunity of Died()
5747 	InternalFlags&=~IF_JUSTDIED;
5748 
5749 	// items seem to be dropped at the moment of death in the original but this
5750 	// can't go in Die() because that is called from effects and dropping items
5751 	// might change effects! so we just drop everything here
5752 
5753 	// disintegration destroys normal items if difficulty level is high enough
5754 	bool disintegrated = LastDamageType & DAMAGE_DISINTEGRATE;
5755 	if (disintegrated && GameDifficulty > DIFF_CORE) {
5756 		inventory.DestroyItem("", IE_INV_ITEM_DESTRUCTIBLE, (ieDword) ~0);
5757 	}
5758 	// drop everything remaining, but ignore TNO, as he needs to keep his gear
5759 	Game *game = core->GetGame();
5760 	if (game->protagonist != PM_NO || GetScriptName() != game->GetPC(0, false)->GetScriptName()) {
5761 		DropItem("", 0);
5762 	}
5763 
5764 	//remove all effects that are not 'permanent after death' here
5765 	//permanent after death type is 9
5766 	SetBaseBit(IE_STATE_ID, STATE_DEAD, true);
5767 
5768 	// death variables are updated at the moment of death in the original
5769 	if (core->HasFeature(GF_HAS_KAPUTZ)) {
5770 		const char *format = AppearanceFlags & APP_ADDKILL ? "KILL_%s" : "%s";
5771 
5772 		//don't use the raw killVar here (except when the flags explicitly ask for it)
5773 		if (AppearanceFlags & APP_DEATHTYPE) {
5774 			IncrementDeathVariable(game->kaputz, format, KillVar);
5775 		}
5776 		if (AppearanceFlags & APP_FACTION) {
5777 			IncrementDeathVariable(game->kaputz, format, GetVarName("faction", BaseStats[IE_FACTION]));
5778 		}
5779 		if (AppearanceFlags & APP_TEAM) {
5780 			IncrementDeathVariable(game->kaputz, format, GetVarName("team", BaseStats[IE_TEAM]));
5781 		}
5782 		if (AppearanceFlags & APP_DEATHVAR) {
5783 			IncrementDeathVariable(game->kaputz, "%s_DEAD", scriptName);
5784 		}
5785 
5786 	} else {
5787 		IncrementDeathVariable(game->locals, "%s", KillVar);
5788 		IncrementDeathVariable(game->locals, core->GetDeathVarFormat(), scriptName);
5789 	}
5790 
5791 	IncrementDeathVariable(game->locals, "%s", IncKillVar);
5792 
5793 	ieDword value;
5794 	if (scriptName[0] && SetDeathVar) {
5795 		ieVariable varname;
5796 		value = 0;
5797 		size_t len = snprintf(varname, 32, "%s_DEAD", scriptName);
5798 		game->locals->Lookup(varname, value);
5799 		game->locals->SetAt(varname, 1, nocreate);
5800 		if (len > 32) {
5801 			Log(ERROR, "Actor", "Scriptname %s (name: %s) is too long for generating death globals!", scriptName, LongName);
5802 		}
5803 		if (value) {
5804 			IncrementDeathVariable(game->locals, "%s_KILL_CNT", scriptName, 1);
5805 		}
5806 	}
5807 
5808 	if (IncKillCount) {
5809 		// racial dead count
5810 		int racetable = core->LoadSymbol("race");
5811 		if (racetable != -1) {
5812 			Holder<SymbolMgr> race = core->GetSymbol(racetable);
5813 			IncrementDeathVariable(game->locals, "KILL_%s_CNT", race->GetValue(Modified[IE_RACE]));
5814 		}
5815 	}
5816 
5817 	//death counters for PST
5818 	for (int i = 0, j = APP_GOOD; i < 4; i++) {
5819 		if (AppearanceFlags & j) {
5820 			value = 0;
5821 			game->locals->Lookup(CounterNames[i], value);
5822 			game->locals->SetAt(CounterNames[i], value + DeathCounters[i], nocreate);
5823 		}
5824 		j += j;
5825 	}
5826 
5827 	if (disintegrated) return true;
5828 
5829 	// party actors are never removed
5830 	if (Persistent()) {
5831 		// hide the corpse artificially
5832 		SetBase(IE_AVATARREMOVAL, 1);
5833 		return false;
5834 	}
5835 
5836 	ieDword time = core->GetGame()->GameTime;
5837 	if (!pstflags && Modified[IE_MC_FLAGS]&MC_REMOVE_CORPSE) {
5838 		RemovalTime = time;
5839 		return true;
5840 	}
5841 	if (Modified[IE_MC_FLAGS]&MC_KEEP_CORPSE) return false;
5842 	RemovalTime = time + core->Time.day_size; // keep corpse around for a day
5843 
5844 	//if chunked death, then return true
5845 	ieDword gore = 0;
5846 	core->GetDictionary()->Lookup("Gore", gore);
5847 	if (LastDamageType & DAMAGE_CHUNKING) {
5848 		if (gore) {
5849 			// TODO: play chunky animation #128
5850 			// chunks are projectiles
5851 		}
5852 		RemovalTime = time;
5853 		return true;
5854 	}
5855 	return false;
5856 }
5857 
IncrementDeathVariable(Variables * vars,const char * format,const char * name,ieDword start) const5858 ieDword Actor::IncrementDeathVariable(Variables *vars, const char *format, const char *name, ieDword start) const {
5859 	if (name && name[0]) {
5860 		ieVariable varname;
5861 		size_t len = snprintf(varname, 32, format, name);
5862 		vars->Lookup(varname, start);
5863 		vars->SetAt(varname, start + 1, nocreate);
5864 		if (len > 32) {
5865 			Log(ERROR, "Actor", "Scriptname %s (name: %s) is too long for generating death globals!", name, LongName);
5866 		}
5867 	}
5868 	return start;
5869 }
5870 
5871 /* this will create a heap at location, and transfer the item(s) */
DropItem(const ieResRef resref,unsigned int flags)5872 void Actor::DropItem(const ieResRef resref, unsigned int flags)
5873 {
5874 	if (inventory.DropItemAtLocation( resref, flags, area, Pos )) {
5875 		ReinitQuickSlots();
5876 	}
5877 }
5878 
DropItem(int slot,unsigned int flags)5879 void Actor::DropItem(int slot , unsigned int flags)
5880 {
5881 	if (inventory.DropItemAtLocation( slot, flags, area, Pos )) {
5882 		ReinitQuickSlots();
5883 	}
5884 }
5885 
5886 /** returns quick item data */
5887 /** if header==-1 which is a 'use quickitem' action */
5888 /** if header is set, then which is the absolute slot index, */
5889 /** and header is the header index */
GetItemSlotInfo(ItemExtHeader * item,int which,int header)5890 void Actor::GetItemSlotInfo(ItemExtHeader *item, int which, int header)
5891 {
5892 	ieWord idx;
5893 	ieWord headerindex;
5894 
5895 	memset(item, 0, sizeof(ItemExtHeader) );
5896 	if (header<0) {
5897 		if (!PCStats) return; //not a player character
5898 		PCStats->GetSlotAndIndex(which,idx,headerindex);
5899 		if (headerindex==0xffff) return; //headerindex is invalid
5900 	} else {
5901 		idx=(ieWord) which;
5902 		headerindex=(ieWord) header;
5903 	}
5904 	const CREItem *slot = inventory.GetSlotItem(idx);
5905 	if (!slot) return; //quick item slot is empty
5906 	Item *itm = gamedata->GetItem(slot->ItemResRef, true);
5907 	if (!itm) {
5908 		Log(WARNING, "Actor", "Invalid quick slot item: %s!", slot->ItemResRef);
5909 		return; //quick item slot contains invalid item resref
5910 	}
5911 	ITMExtHeader *ext_header = itm->GetExtHeader(headerindex);
5912 	//item has no extended header, or header index is wrong
5913 	if (!ext_header) return;
5914 	memcpy(item->itemname, slot->ItemResRef, sizeof(ieResRef) );
5915 	item->slot = idx;
5916 	item->headerindex = headerindex;
5917 	memcpy(&(item->AttackType), &(ext_header->AttackType),
5918 		((char *) &(item->itemname)) -((char *) &(item->AttackType)) );
5919 	if (headerindex>=CHARGE_COUNTERS) {
5920 		item->Charges=0;
5921 	} else {
5922 		item->Charges=slot->Usages[headerindex];
5923 	}
5924 	gamedata->FreeItem(itm,slot->ItemResRef, false);
5925 }
5926 
ReinitQuickSlots()5927 void Actor::ReinitQuickSlots()
5928 {
5929 	if (!PCStats) {
5930 		return;
5931 	}
5932 
5933 	// Note: (wjp, 20061226)
5934 	// This function needs some rethinking.
5935 	// It tries to satisfy two things at the moment:
5936 	//   Fill quickslots when they are empty and an item is placed in the
5937 	//      inventory slot corresponding to the quickslot
5938 	//   Reset quickslots when an item is removed
5939 	// Currently, it resets all slots when items are removed,
5940 	// but it only refills the ACT_QSLOTn slots, not the ACT_WEAPONx slots.
5941 	//
5942 	// Refilling a weapon slot is possible, but essentially duplicates a lot
5943 	// of code from Inventory::EquipItem() which performs the same steps for
5944 	// the Inventory::Equipped slot.
5945 	// Hopefully, weapons/arrows are never added to inventory slots without
5946 	// EquipItem being called.
5947 
5948 	int i=sizeof(PCStats->QSlots);
5949 	while (i--) {
5950 		int slot;
5951 		int which = IWD2GemrbQslot(i);
5952 
5953 		switch (which) {
5954 			case ACT_WEAPON1:
5955 			case ACT_WEAPON2:
5956 			case ACT_WEAPON3:
5957 			case ACT_WEAPON4:
5958 				CheckWeaponQuickSlot(which-ACT_WEAPON1);
5959 				slot = 0;
5960 				break;
5961 				//WARNING:this cannot be condensed, because the symbols don't come in order!!!
5962 			case ACT_QSLOT1: slot = inventory.GetQuickSlot(); break;
5963 			case ACT_QSLOT2: slot = inventory.GetQuickSlot()+1; break;
5964 			case ACT_QSLOT3: slot = inventory.GetQuickSlot()+2; break;
5965 			case ACT_QSLOT4: slot = inventory.GetQuickSlot()+3; break;
5966 			case ACT_QSLOT5: slot = inventory.GetQuickSlot()+4; break;
5967 			case ACT_IWDQITEM: slot = inventory.GetQuickSlot(); break;
5968 			case ACT_IWDQITEM+1: slot = inventory.GetQuickSlot()+1; break;
5969 			case ACT_IWDQITEM+2: slot = inventory.GetQuickSlot()+2; break;
5970 			case ACT_IWDQITEM+3: slot = inventory.GetQuickSlot()+3; break;
5971 			case ACT_IWDQITEM+4: slot = inventory.GetQuickSlot()+4; break;
5972 			// the rest are unavailable - only three slots in the actual inventory layout, 5 in the class for pst
5973 			// case ACT_IWDQITEM+9:
5974 			default:
5975 				slot = 0;
5976 		}
5977 		if (!slot) continue;
5978 		//if magic items are equipped the equipping info doesn't change
5979 		//(afaik)
5980 
5981 		// Note: we're now in the QSLOTn case
5982 		// If slot is empty, reset quickslot to 0xffff/0xffff
5983 
5984 		if (!inventory.HasItemInSlot("", slot)) {
5985 			SetupQuickSlot(which, 0xffff, 0xffff);
5986 		} else {
5987 			ieWord idx;
5988 			ieWord headerindex;
5989 			PCStats->GetSlotAndIndex(which,idx,headerindex);
5990 			if (idx != slot || headerindex == 0xffff) {
5991 				// If slot just became filled, set it to filled
5992 				SetupQuickSlot(which,slot,0);
5993 			}
5994 		}
5995 	}
5996 
5997 	//these are always present
5998 	CheckWeaponQuickSlot(0);
5999 	CheckWeaponQuickSlot(1);
6000 	if (weapSlotCount > 2) {
6001 		for(i=2; i<weapSlotCount; i++) {
6002 			CheckWeaponQuickSlot(i);
6003 		}
6004 	} else {
6005 	//disabling quick weapon slots for certain classes
6006 		for(i=0;i<2;i++) {
6007 			int which = ACT_WEAPON3+i;
6008 			// Assuming that ACT_WEAPON3 and 4 are always in the first two spots
6009 			if (PCStats->QSlots[i+3]!=which) {
6010 				SetupQuickSlot(which, 0xffff, 0xffff);
6011 			}
6012 		}
6013 	}
6014 }
6015 
CheckWeaponQuickSlot(unsigned int which)6016 void Actor::CheckWeaponQuickSlot(unsigned int which)
6017 {
6018 	if (!PCStats) return;
6019 
6020 	bool empty = false;
6021 	// If current quickweaponslot doesn't contain an item, reset it to fist
6022 	int slot = PCStats->QuickWeaponSlots[which];
6023 	int header = PCStats->QuickWeaponHeaders[which];
6024 	if (!inventory.HasItemInSlot("", slot) || header == 0xffff) {
6025 		//a quiver just went dry, falling back to fist
6026 		empty = true;
6027 	} else {
6028 		// If current quickweaponslot contains ammo, and bow not found, reset
6029 
6030 		if (core->QuerySlotEffects(slot) == SLOT_EFFECT_MISSILE) {
6031 			const CREItem *slotitm = inventory.GetSlotItem(slot);
6032 			assert(slotitm);
6033 			Item *itm = gamedata->GetItem(slotitm->ItemResRef, true);
6034 			assert(itm);
6035 			ITMExtHeader *ext_header = itm->GetExtHeader(header);
6036 			if (ext_header) {
6037 				int type = ext_header->ProjectileQualifier;
6038 				int weaponslot = inventory.FindTypedRangedWeapon(type);
6039 				if (weaponslot == inventory.GetFistSlot()) {
6040 					empty = true;
6041 				}
6042 			} else {
6043 				empty = true;
6044 			}
6045 			gamedata->FreeItem(itm,slotitm->ItemResRef, false);
6046 		}
6047 	}
6048 
6049 	if (empty)
6050 		SetupQuickSlot(ACT_WEAPON1+which, inventory.GetFistSlot(), 0);
6051 }
6052 
6053 //if dual stuff needs to be handled on load too, improve this method with it
GetHpAdjustment(int multiplier,bool modified) const6054 int Actor::GetHpAdjustment(int multiplier, bool modified) const
6055 {
6056 	int val;
6057 
6058 	// only player classes get this bonus
6059 	if (!HasPlayerClass()) {
6060 		return 0;
6061 	}
6062 
6063 	const ieDword *stats;
6064 	if (modified) {
6065 		stats = Modified;
6066 	} else {
6067 		stats = BaseStats;
6068 	}
6069 
6070 	// GetClassLevel/IsWarrior takes into consideration inactive dual-classes, so those would fail here
6071 	if (IsWarrior()) {
6072 		val = core->GetConstitutionBonus(STAT_CON_HP_WARRIOR, stats[IE_CON]);
6073 	} else {
6074 		val = core->GetConstitutionBonus(STAT_CON_HP_NORMAL, stats[IE_CON]);
6075 	}
6076 
6077 	// ensure the change does not kill the actor
6078 	if (BaseStats[IE_HITPOINTS] + val*multiplier <= 0) {
6079 		// leave them with 1hp/level worth of hp
6080 		// note: we return the adjustment and the actual setting of hp happens later
6081 		return multiplier - BaseStats[IE_HITPOINTS];
6082 	} else {
6083 		return val * multiplier;
6084 	}
6085 }
6086 
InitStatsOnLoad()6087 void Actor::InitStatsOnLoad()
6088 {
6089 	//default is 9 in Tob, 6 in bg1
6090 	SetBase(IE_MOVEMENTRATE, VOODOO_CHAR_SPEED);
6091 
6092 	ieWord animID = ( ieWord ) BaseStats[IE_ANIMATION_ID];
6093 	//this is required so the actor has animation already
6094 	SetAnimationID( animID );
6095 
6096 	// Setting up derived stats
6097 	if (BaseStats[IE_STATE_ID] & STATE_DEAD) {
6098 		SetStance( IE_ANI_TWITCH );
6099 		Deactivate();
6100 		InternalFlags|=IF_REALLYDIED;
6101 	} else {
6102 		if (BaseStats[IE_STATE_ID] & STATE_SLEEP) {
6103 			SetStance( IE_ANI_SLEEP );
6104 		} else {
6105 			SetStance( IE_ANI_AWAKE );
6106 		}
6107 	}
6108 	CreateDerivedStats();
6109 	Modified[IE_CON]=BaseStats[IE_CON]; // used by GetHpAdjustment
6110 	ieDword hp = BaseStats[IE_HITPOINTS] + GetHpAdjustment(GetXPLevel(false));
6111 	BaseStats[IE_HITPOINTS]=hp;
6112 
6113 	SetupFist();
6114 	//initial setup of modified stats
6115 	memcpy(Modified, BaseStats, sizeof(Modified));
6116 }
6117 
6118 //most feats are simulated via spells (feat<xx>)
ApplyFeats()6119 void Actor::ApplyFeats()
6120 {
6121 	ieResRef feat;
6122 
6123 	for(int i=0;i<MAX_FEATS;i++) {
6124 		int level = GetFeat(i);
6125 		snprintf(feat, sizeof(ieResRef), "FEAT%02x", i);
6126 		if (level) {
6127 			if (gamedata->Exists(feat, IE_SPL_CLASS_ID, true)) {
6128 				core->ApplySpell(feat, this, this, level);
6129 			}
6130 		}
6131 	}
6132 	//apply scripted feats
6133 	if (InParty) {
6134 		core->GetGUIScriptEngine()->RunFunction("LUCommon","ApplyFeats", true, InParty);
6135 	} else {
6136 		core->GetGUIScriptEngine()->RunFunction("LUCommon","ApplyFeats", true, GetGlobalID());
6137 	}
6138 }
6139 
ApplyExtraSettings()6140 void Actor::ApplyExtraSettings()
6141 {
6142 	if (!PCStats) return;
6143 	for (int i=0;i<ES_COUNT;i++) {
6144 		if (featspells[i][0] && featspells[i][0] != '*') {
6145 			if (PCStats->ExtraSettings[i]) {
6146 				core->ApplySpell(featspells[i], this, this, PCStats->ExtraSettings[i]);
6147 			}
6148 		}
6149 	}
6150 }
6151 
SetupQuickSlot(unsigned int which,int slot,int headerindex)6152 void Actor::SetupQuickSlot(unsigned int which, int slot, int headerindex)
6153 {
6154 	if (!PCStats) return;
6155 	PCStats->InitQuickSlot(which, slot, headerindex);
6156 	//something changed about the quick items
6157 	core->SetEventFlag(EF_ACTION);
6158 }
6159 
ValidTarget(int ga_flags,const Scriptable * checker) const6160 bool Actor::ValidTarget(int ga_flags, const Scriptable *checker) const
6161 {
6162 	//scripts can still see this type of actor
6163 
6164 	if (ga_flags&GA_NO_SELF) {
6165 		if (checker && checker == this) return false;
6166 	}
6167 
6168 	if (ga_flags&GA_NO_UNSCHEDULED) {
6169 		if (Modified[IE_AVATARREMOVAL]) return false;
6170 
6171 		Game *game = core->GetGame();
6172 		if (game) {
6173 			if (!Schedule(game->GameTime, true)) return false;
6174 		}
6175 	}
6176 
6177 	if (ga_flags&GA_NO_HIDDEN) {
6178 		if (IsInvisibleTo(checker)) return false;
6179 	}
6180 
6181 	if (ga_flags&GA_NO_ALLY) {
6182 		if(InParty) return false;
6183 		if(Modified[IE_EA]<=EA_GOODCUTOFF) return false;
6184 	}
6185 
6186 	if (ga_flags&GA_NO_ENEMY) {
6187 		if(!InParty && (Modified[IE_EA]>=EA_EVILCUTOFF) ) return false;
6188 	}
6189 
6190 	if (ga_flags&GA_NO_NEUTRAL) {
6191 		if((Modified[IE_EA]>EA_GOODCUTOFF) && (Modified[IE_EA]<EA_EVILCUTOFF) ) return false;
6192 	}
6193 
6194 	switch(ga_flags&GA_ACTION) {
6195 	case GA_PICK:
6196 		if (Modified[IE_STATE_ID] & STATE_CANTSTEAL) return false;
6197 		break;
6198 	case GA_TALK:
6199 		//can't talk to dead
6200 		if (Modified[IE_STATE_ID] & (STATE_CANTLISTEN^STATE_SLEEP)) return false;
6201 		//can't talk to hostile
6202 		if (Modified[IE_EA]>=EA_EVILCUTOFF) return false;
6203 		// neither to bats and birds
6204 		if (anims->GetCircleSize() == 0) return false;
6205 		break;
6206 	}
6207 	if (ga_flags&GA_NO_DEAD) {
6208 		if (InternalFlags&IF_REALLYDIED) return false;
6209 		if (Modified[IE_STATE_ID] & STATE_DEAD) return false;
6210 	}
6211 	if (ga_flags&GA_SELECT) {
6212 		if (UnselectableTimer) return false;
6213 		if (Immobile()) return false;
6214 		if (Modified[IE_STATE_ID] & (STATE_MINDLESS ^ (STATE_CHARMED|STATE_BERSERK))) {
6215 			return false;
6216 		}
6217 		// charmed actors are only selectable if they were charmed by the party
6218 		if ((Modified[IE_STATE_ID] & STATE_CHARMED) && Modified[IE_EA] == EA_CHARMEDPC) return false;
6219 		if (Modified[IE_STATE_ID] & STATE_BERSERK) {
6220 			if (Modified[IE_CHECKFORBERSERK]) return false;
6221 		}
6222 	}
6223 	if (ga_flags & GA_ONLY_BUMPABLE) {
6224 		if (core->InCutSceneMode()) return false;
6225 		if (core->GetGame()->CombatCounter) return false;
6226 		if (GetStat(IE_EA) >= EA_EVILCUTOFF) return false;
6227 		// Skip sitting patrons
6228 		if (GetStat(IE_ANIMATION_ID) >= 0x4000 && GetStat(IE_ANIMATION_ID) <= 0x4112) return false;
6229 		if (IsMoving()) return false;
6230 	}
6231 	if (ga_flags & GA_CAN_BUMP) {
6232 		if (core->InCutSceneMode()) return false;
6233 		if (core->GetGame()->CombatCounter) return false;
6234 		if (!((IsPartyMember() && GetStat(IE_EA) < EA_GOODCUTOFF) || GetStat(IE_NPCBUMP))) return false;
6235 	}
6236 	return true;
6237 }
6238 
6239 //returns true if it won't be destroyed with an area
6240 //in this case it shouldn't be saved with the area either
6241 //it will be saved in the savegame
Persistent() const6242 bool Actor::Persistent() const
6243 {
6244 	if (InParty) return true;
6245 	if (InternalFlags&IF_FROMGAME) return true;
6246 	return false;
6247 }
6248 
6249 //this is a reimplementation of cheatkey a/s of bg2
6250 //cycling through animation/stance
6251 // a - get next animation, s - get next stance
6252 
GetNextAnimation()6253 void Actor::GetNextAnimation()
6254 {
6255 	int RowNum = anims->AvatarsRowNum - 1;
6256 	if (RowNum<0)
6257 		RowNum = CharAnimations::GetAvatarsCount() - 1;
6258 	int NewAnimID = CharAnimations::GetAvatarStruct(RowNum)->AnimID;
6259 	print("AnimID: %04X", NewAnimID);
6260 	SetBase( IE_ANIMATION_ID, NewAnimID);
6261 }
6262 
GetPrevAnimation()6263 void Actor::GetPrevAnimation()
6264 {
6265 	int RowNum = anims->AvatarsRowNum + 1;
6266 	if (RowNum>=CharAnimations::GetAvatarsCount() )
6267 		RowNum = 0;
6268 	int NewAnimID = CharAnimations::GetAvatarStruct(RowNum)->AnimID;
6269 	print("AnimID: %04X", NewAnimID);
6270 	SetBase( IE_ANIMATION_ID, NewAnimID);
6271 }
6272 
6273 //slot is the projectile slot
6274 //This will return the projectile item.
GetRangedWeapon(WeaponInfo & wi) const6275 ITMExtHeader *Actor::GetRangedWeapon(WeaponInfo &wi) const
6276 {
6277 //EquippedSlot is the projectile. To get the weapon, use inventory.GetUsedWeapon()
6278 	wi.slot = inventory.GetEquippedSlot();
6279 	const CREItem *wield = inventory.GetSlotItem(wi.slot);
6280 	if (!wield) {
6281 		return NULL;
6282 	}
6283 	Item *item = gamedata->GetItem(wield->ItemResRef, true);
6284 	if (!item) {
6285 		Log(WARNING, "Actor", "Missing or invalid ranged weapon item: %s!", wield->ItemResRef);
6286 		return NULL;
6287 	}
6288 	//The magic of the bow and the arrow do not add up
6289 	if (item->Enchantment > wi.enchantment) {
6290 		wi.enchantment = item->Enchantment;
6291 	}
6292 	wi.itemflags = wield->Flags;
6293 	//not resetting wi.itemtype, since we want it to remain the one of the launcher
6294 	//wi.range is not set, the projectile has no effect on range?
6295 
6296 	ITMExtHeader *which = item->GetWeaponHeader(true);
6297 	gamedata->FreeItem(item, wield->ItemResRef, false);
6298 	return which;
6299 }
6300 
IsDualWielding() const6301 int Actor::IsDualWielding() const
6302 {
6303 	int slot;
6304 	//if the shield slot is a weapon, we're dual wielding
6305 	const CREItem *wield = inventory.GetUsedWeapon(true, slot);
6306 	if (!wield || slot == inventory.GetFistSlot() || slot == inventory.GetMagicSlot()) {
6307 		return 0;
6308 	}
6309 
6310 	Item *itm = gamedata->GetItem(wield->ItemResRef, true);
6311 	if (!itm) {
6312 		Log(WARNING, "Actor", "Missing or invalid wielded weapon item: %s!", wield->ItemResRef);
6313 		return 0;
6314 	}
6315 
6316 	//if the item is usable in weapon slot, then it is weapon
6317 	int weapon = core->CanUseItemType( SLOT_WEAPON, itm );
6318 	gamedata->FreeItem( itm, wield->ItemResRef, false );
6319 	//is just weapon>0 ok?
6320 	return (weapon>0)?1:0;
6321 }
6322 
6323 //returns weapon header currently used (bow in case of bow+arrow)
6324 //if range is nonzero, then the returned header is valid
GetWeapon(WeaponInfo & wi,bool leftorright) const6325 ITMExtHeader *Actor::GetWeapon(WeaponInfo &wi, bool leftorright) const
6326 {
6327 	//only use the shield slot if we are dual wielding
6328 	leftorright = leftorright && IsDualWielding();
6329 
6330 	const CREItem *wield = inventory.GetUsedWeapon(leftorright, wi.slot);
6331 	if (!wield) {
6332 		return 0;
6333 	}
6334 	Item *item = gamedata->GetItem(wield->ItemResRef, true);
6335 	if (!item) {
6336 		Log(WARNING, "Actor", "Missing or invalid weapon item: %s!", wield->ItemResRef);
6337 		return 0;
6338 	}
6339 
6340 	wi.enchantment = item->Enchantment;
6341 	wi.itemtype = item->ItemType;
6342 	wi.itemflags = wield->Flags;
6343 	wi.prof = item->WeaProf;
6344 	wi.critmulti = core->GetCriticalMultiplier(item->ItemType);
6345 	wi.critrange = core->GetCriticalRange(item->ItemType);
6346 
6347 	//select first weapon header
6348 	// except you can never dualwield two ranged (thrown) weapons
6349 	ITMExtHeader *which;
6350 	if (!leftorright && GetAttackStyle() == WEAPON_RANGED) {
6351 		which = item->GetWeaponHeader(true);
6352 		if (which) {
6353 			wi.backstabbing = which->RechargeFlags & IE_ITEM_BACKSTAB;
6354 		} else {
6355 			wi.backstabbing = false;
6356 		}
6357 		wi.wflags |= WEAPON_RANGED;
6358 	} else {
6359 		which = item->GetWeaponHeader(false);
6360 		// any melee weapon usable by a single class thief is game (UAI does not affect this)
6361 		// but also check a bit in the recharge flags (modder extension)
6362 		if (which) {
6363 			wi.backstabbing = !(item->UsabilityBitmask & 0x400000) || (which->RechargeFlags & IE_ITEM_BACKSTAB);
6364 		} else {
6365 			wi.backstabbing = !(item->UsabilityBitmask & 0x400000);
6366 		}
6367 		if (third) {
6368 			// iwd2 doesn't set the usability mask
6369 			wi.backstabbing = true;
6370 		}
6371 	}
6372 
6373 	if (which && (which->RechargeFlags&IE_ITEM_KEEN)) {
6374 		//this is correct, the threat range is only increased by one in the original engine
6375 		wi.critrange--;
6376 	}
6377 
6378 	//make sure we use 'false' in this freeitem
6379 	//so 'which' won't point into invalid memory
6380 	gamedata->FreeItem(item, wield->ItemResRef, false);
6381 	if (!which) {
6382 		return 0;
6383 	}
6384 	if (which->Location!=ITEM_LOC_WEAPON) {
6385 		return 0;
6386 	}
6387 	wi.range = which->Range+1;
6388 	return which;
6389 }
6390 
GetNextStance()6391 void Actor::GetNextStance()
6392 {
6393 	static int Stance = IE_ANI_AWAKE;
6394 
6395 	if (--Stance < 0) Stance = MAX_ANIMS-1;
6396 	print("StanceID: %d", Stance);
6397 	SetStance( Stance );
6398 }
6399 
LearnSpell(const ieResRef spellname,ieDword flags,int bookmask,int level)6400 int Actor::LearnSpell(const ieResRef spellname, ieDword flags, int bookmask, int level)
6401 {
6402 	//don't fail if the spell is also memorized (for innates)
6403 	if (! (flags&LS_MEMO)) {
6404 		if (spellbook.HaveSpell(spellname, 0) ) {
6405 			return LSR_KNOWN;
6406 		}
6407 	}
6408 	Spell *spell = gamedata->GetSpell(spellname);
6409 	if (!spell) {
6410 		return LSR_INVALID; //not existent spell
6411 	}
6412 
6413 	//innates are always memorized when gained
6414 	if (spell->SpellType==IE_SPL_INNATE) {
6415 		flags|=LS_MEMO;
6416 	}
6417 
6418 	ieDword kit = GetStat(IE_KIT);
6419 
6420 	if ((flags & LS_STATS) && (GameDifficulty>DIFF_NORMAL) ) {
6421 		// chance to learn roll
6422 		int roll = LuckyRoll(1, 100, 0);
6423 		// adjust the roll for specialist mages
6424 		// doesn't work in bg1, since its spells don't have PrimaryType set (0 is NONE)
6425 		if (!third && GetKitIndex(kit) && spell->PrimaryType) {
6426 			if (kit == (unsigned) 1<<(spell->PrimaryType+5)) { // +5 since the kit values start at 0x40
6427 				roll += 15;
6428 			} else {
6429 				roll -= 15;
6430 			}
6431 		}
6432 
6433 		if (roll > core->GetIntelligenceBonus(0, GetStat(IE_INT))) {
6434 			return LSR_FAILED;
6435 		}
6436 	}
6437 
6438 	// only look it up if none was passed
6439 	if (bookmask == -1) {
6440 		bookmask = GetBookMask();
6441 	}
6442 	int explev = spellbook.LearnSpell(spell, flags&LS_MEMO, bookmask, kit, level);
6443 	int tmp = spell->SpellName;
6444 	if (flags&LS_LEARN) {
6445 		core->GetTokenDictionary()->SetAt("SPECIALABILITYNAME", core->GetCString(tmp));
6446 		switch (spell->SpellType) {
6447 		case IE_SPL_INNATE:
6448 			tmp = STR_GOTABILITY;
6449 			break;
6450 		case IE_SPL_SONG:
6451 			tmp = STR_GOTSONG;
6452 			break;
6453 		default:
6454 			tmp = STR_GOTSPELL;
6455 			break;
6456 		}
6457 	} else tmp = 0;
6458 	gamedata->FreeSpell(spell, spellname, false);
6459 	if (!explev) {
6460 		return LSR_INVALID;
6461 	}
6462 	if (tmp) {
6463 		displaymsg->DisplayConstantStringName(tmp, DMC_BG2XPGREEN, this);
6464 	}
6465 	if (flags&LS_ADDXP && !(flags&LS_NOXP)) {
6466 		int xp = CalculateExperience(XP_LEARNSPELL, explev);
6467 		const Game *game = core->GetGame();
6468 		game->ShareXP(xp, SX_DIVIDE);
6469 	}
6470 	return LSR_OK;
6471 }
6472 
SetDialog(const ieResRef resref)6473 void Actor::SetDialog(const ieResRef resref)
6474 {
6475 	CopyResRef(Dialog, resref);
6476 }
6477 
CopyPortrait(int which) const6478 Holder<Sprite2D> Actor::CopyPortrait(int which) const
6479 {
6480 	ResourceHolder<ImageMgr> im = GetResourceHolder<ImageMgr>(which ? SmallPortrait : LargePortrait, true);
6481 	return im ? im->GetSprite2D() : nullptr;
6482 }
6483 
GetDialog(int flags) const6484 const char *Actor::GetDialog(int flags) const
6485 {
6486 	if (!flags) {
6487 		return Dialog;
6488 	}
6489 	if (Modified[IE_EA]>=EA_EVILCUTOFF) {
6490 		return NULL;
6491 	}
6492 
6493 	if ( (InternalFlags & IF_NOINT) && CurrentAction) {
6494 		if (flags>1) {
6495 			core->GetTokenDictionary()->SetAtCopy("TARGET", ShortName);
6496 			displaymsg->DisplayConstantString(STR_TARGETBUSY, DMC_RED);
6497 		}
6498 		return NULL;
6499 	}
6500 	return Dialog;
6501 }
6502 
ListLevels() const6503 std::list<int> Actor::ListLevels() const
6504 {
6505 	std::list<int> levels (ISCLASSES, 0);
6506 	if (third) {
6507 		std::list<int>::iterator it;
6508 		int i = 0;
6509 		for (it=levels.begin(); it != levels.end(); it++) {
6510 			*it = GetClassLevel(i++);
6511 		}
6512 	}
6513 	return levels;
6514 }
6515 
CreateStats()6516 void Actor::CreateStats()
6517 {
6518 	if (!PCStats) {
6519 		PCStats = new PCStatsStruct(ListLevels());
6520 	}
6521 }
6522 
GetScript(int ScriptIndex) const6523 const char* Actor::GetScript(int ScriptIndex) const
6524 {
6525 	if (Scripts[ScriptIndex]) {
6526 		return Scripts[ScriptIndex]->GetName();
6527 	} else {
6528 		return "NONE\0\0\0\0";
6529 	}
6530 }
6531 
SetModal(ieDword newstate,bool force)6532 void Actor::SetModal(ieDword newstate, bool force)
6533 {
6534 	switch(newstate) {
6535 		case MS_NONE:
6536 			break;
6537 		case MS_BATTLESONG:
6538 			break;
6539 		case MS_DETECTTRAPS:
6540 			break;
6541 		case MS_STEALTH:
6542 			break;
6543 		case MS_TURNUNDEAD:
6544 			break;
6545 		default:
6546 			return;
6547 	}
6548 
6549 	if (Modal.State != newstate) {
6550 		Modal.FirstApply = 1;
6551 	}
6552 
6553 	if (Modal.State == MS_BATTLESONG && Modal.State != newstate && HasFeat(FEAT_LINGERING_SONG)) {
6554 		strnlwrcpy(Modal.LingeringSpell, Modal.Spell, 8);
6555 		Modal.LingeringCount = 2;
6556 	}
6557 
6558 	if (IsSelected()) {
6559 		// display the turning-off message
6560 		if (Modal.State != MS_NONE && core->HasFeedback(FT_MISC)) {
6561 			displaymsg->DisplayStringName(ModalStates[Modal.State].leaving_str, DMC_WHITE, this, IE_STR_SOUND|IE_STR_SPEECH);
6562 		}
6563 
6564 		//update the action bar
6565 		if (Modal.State != newstate || newstate != MS_NONE) {
6566 			core->SetEventFlag(EF_ACTION);
6567 		}
6568 
6569 		// when called with the same state twice, toggle to MS_NONE
6570 		if (!force && Modal.State == newstate) {
6571 			Modal.State = MS_NONE;
6572 		} else {
6573 			Modal.State = newstate;
6574 		}
6575 	} else {
6576 		Modal.State = newstate;
6577 	}
6578 }
6579 
SetModalSpell(ieDword state,const char * spell)6580 void Actor::SetModalSpell(ieDword state, const char *spell)
6581 {
6582 	if (spell) {
6583 		strnlwrcpy(Modal.Spell, spell, 8);
6584 	} else {
6585 		if (state >= ModalStates.size()) {
6586 			Modal.Spell[0] = 0;
6587 		} else {
6588 			if (state==MS_BATTLESONG) {
6589 				if (BardSong[0]) {
6590 					strnlwrcpy(Modal.Spell, BardSong, 8);
6591 					return;
6592 				}
6593 			}
6594 			strnlwrcpy(Modal.Spell, ModalStates[state].spell, 8);
6595 		}
6596 	}
6597 }
6598 
6599 //even spells got this attack style
GetAttackStyle() const6600 int Actor::GetAttackStyle() const
6601 {
6602 	WeaponInfo wi;
6603 	// Some weapons have both melee and ranged capability, eg. bg2's rifthorne (ax1h09)
6604 	// so we check the equipped header's attack type: 2-projectile and 4-launcher
6605 	// It is more complicated than it seems because the equipped header is the one of the projectile for launchers
6606 	ITMExtHeader *rangedheader = GetRangedWeapon(wi);
6607 	if (!PCStats) {
6608 		// fall back to simpler logic that works most of the time
6609 		//Non NULL if the equipped slot is a projectile or a throwing weapon
6610 		if (rangedheader) return WEAPON_RANGED;
6611 		return WEAPON_MELEE;
6612 	}
6613 
6614 	ITMExtHeader *eh;
6615 	if (inventory.MagicSlotEquipped()) {
6616 		// this should be fine, as long as we default to melee,
6617 		// since there are no "magic" weapons with switchable headers
6618 		eh = rangedheader;
6619 	} else {
6620 		int qh = PCStats->GetHeaderForSlot(inventory.GetEquippedSlot());
6621 		eh = inventory.GetEquippedExtHeader(qh);
6622 	}
6623 	if (!eh) return WEAPON_MELEE; // default to melee
6624 	if (eh->AttackType && eh->AttackType%2 == 0) return WEAPON_RANGED;
6625 	return WEAPON_MELEE;
6626 }
6627 
AttackedBy(const Actor * attacker)6628 void Actor::AttackedBy(const Actor *attacker)
6629 {
6630 	AddTrigger(TriggerEntry(trigger_attackedby, attacker->GetGlobalID()));
6631 	if (attacker->GetStat(IE_EA) != EA_PC && Modified[IE_EA] != EA_PC) {
6632 		LastAttacker = attacker->GetGlobalID();
6633 	}
6634 	if (InParty) {
6635 		core->Autopause(AP_ATTACKED, this);
6636 	}
6637 }
6638 
FaceTarget(Scriptable * target)6639 void Actor::FaceTarget( Scriptable *target)
6640 {
6641 	if (!target) return;
6642 	SetOrientation( GetOrient( target->Pos, Pos ), false );
6643 }
6644 
6645 //in case of LastTarget = 0
StopAttack()6646 void Actor::StopAttack()
6647 {
6648 	SetStance(IE_ANI_READY);
6649 	lastattack = 0;
6650 	secondround = 0;
6651 	//InternalFlags|=IF_TARGETGONE; //this is for the trigger!
6652 	if (InParty) {
6653 		core->Autopause(AP_NOTARGET, this);
6654 	}
6655 }
6656 
6657 // checks for complete immobility — to the point of inaction
Immobile() const6658 int Actor::Immobile() const
6659 {
6660 	if (GetStat(IE_CASTERHOLD)) {
6661 		return 1;
6662 	}
6663 	if (GetStat(IE_HELD)) {
6664 		return 1;
6665 	}
6666 	if (GetStat(IE_STATE_ID) & STATE_STILL) {
6667 		return 1;
6668 	}
6669 	const Game *game = core->GetGame();
6670 	if (game && game->TimeStoppedFor(this)) {
6671 		return 1;
6672 	}
6673 
6674 	return 0;
6675 }
6676 
DoStep(unsigned int walkScale,ieDword time)6677 void Actor::DoStep(unsigned int walkScale, ieDword time)
6678 {
6679 	if (Immobile()) {
6680 		return;
6681 	}
6682 	Movable::DoStep(walkScale, time);
6683 }
6684 
GetNumberOfAttacks()6685 ieDword Actor::GetNumberOfAttacks()
6686 {
6687 	int bonus = 0;
6688 
6689 	if (third) {
6690 		int base = SetBaseAPRandAB (true);
6691 		// add the offhand extra attack
6692 		// TODO: check effects too
6693 		bonus = 2 * IsDualWielding();
6694 		return base + bonus;
6695 	} else {
6696 		if (monkbon != NULL && inventory.FistsEquipped()) {
6697 			unsigned int level = GetMonkLevel();
6698 			if (level>=monkbon_cols) level=monkbon_cols-1;
6699 			if (level>0) {
6700 				bonus = monkbon[0][level-1];
6701 			}
6702 		}
6703 
6704 		return GetStat(IE_NUMBEROFATTACKS)+bonus;
6705 	}
6706 }
6707 static const int BaseAttackBonusDecrement = 5; // iwd2; number of tohit points for another attack per round
SetLevelBAB(int level,ieDword index)6708 static int SetLevelBAB(int level, ieDword index)
6709 {
6710 	if (!level) {
6711 		return 0;
6712 	}
6713 	assert(index < BABClassMap.size());
6714 
6715 	IWD2HitTableIter table = IWD2HitTable.find(BABClassMap[index]);
6716 	assert(table != IWD2HitTable.end());
6717 	return table->second[level-1].bab;
6718 }
6719 
6720 // return the base APR derived from the base attack bonus, which we have to construct here too
6721 //NOTE: this doesn't break iwd2 monsters, since they have their level stored as fighters (if not more)
SetBaseAPRandAB(bool CheckRapidShot)6722 int Actor::SetBaseAPRandAB(bool CheckRapidShot)
6723 {
6724 	int pBAB = 0;
6725 	int pBABDecrement = BaseAttackBonusDecrement;
6726 	ieDword MonkLevel = 0;
6727 	ieDword LevelSum = 0;
6728 	int i;
6729 
6730 	if (!third) {
6731 		ToHit.SetBase(BaseStats[IE_TOHIT]);
6732 		return 0;
6733 	}
6734 
6735 	for (i = 0; i<ISCLASSES; i++) {
6736 		int level = GetClassLevel(i);
6737 		if (level) {
6738 			// silly monks, always wanting to be special
6739 			if (i == ISMONK) {
6740 				MonkLevel = level;
6741 				if (MonkLevel+LevelSum == Modified[IE_CLASSLEVELSUM]) {
6742 					// only the monk left to check, so skip the rest
6743 					break;
6744 				} else {
6745 					continue;
6746 				}
6747 			}
6748 			pBAB += SetLevelBAB(level, i);
6749 			LevelSum += level;
6750 			if (LevelSum == Modified[IE_CLASSLEVELSUM]) {
6751 				// skip to apr calc, no need to check the other classes
6752 				ToHit.SetBase(pBAB);
6753 				ToHit.SetBABDecrement(pBABDecrement);
6754 				return BAB2APR(pBAB, pBABDecrement, CheckRapidShot);
6755 			}
6756 		}
6757 	}
6758 
6759 	if (MonkLevel) {
6760 		// act as a rogue unless barefisted and without armor
6761 		// multiclassed monks only use their monk levels when determining barefisted bab
6762 		// check the spell failure instead of the skill penalty, since otherwise leather armor would also be treated as none
6763 		if (!inventory.FistsEquipped() || GetTotalArmorFailure()) {
6764 			pBAB += SetLevelBAB(MonkLevel, ISTHIEF);
6765 		} else {
6766 			pBABDecrement = 3;
6767 			pBAB = SetLevelBAB(MonkLevel, ISMONK);
6768 		}
6769 		LevelSum += MonkLevel;
6770 	}
6771 
6772 	assert(LevelSum == Modified[IE_CLASSLEVELSUM]);
6773 	ToHit.SetBase(pBAB);
6774 	ToHit.SetBABDecrement(pBABDecrement);
6775 	return BAB2APR(pBAB, pBABDecrement, CheckRapidShot);
6776 }
6777 
BAB2APR(int pBAB,int pBABDecrement,int CheckRapidShot) const6778 int Actor::BAB2APR(int pBAB, int pBABDecrement, int CheckRapidShot) const
6779 {
6780 	if (CheckRapidShot && HasSpellState(SS_RAPIDSHOT)) {
6781 		WeaponInfo wi;
6782 		ITMExtHeader *HittingHeader = GetRangedWeapon(wi);
6783 		if (HittingHeader) {
6784 			ieDword AttackTypeLowBits = HittingHeader->AttackType & 0xFF; // this is done by the original; leaving in case we expand
6785 			if (AttackTypeLowBits == ITEM_AT_BOW || AttackTypeLowBits == ITEM_AT_PROJECTILE) {
6786 				// rapid shot gives another attack and since it is computed from the BAB, we just increase that ...
6787 				// but monk get their speedy handy work only for fists, so we can't use the passed pBABDecrement
6788 				pBAB += BaseAttackBonusDecrement;
6789 			}
6790 		}
6791 	}
6792 
6793 	int APR = (pBAB - 1) / pBABDecrement + 1;
6794 	//FIXME: why is it not using the other IWD2HitTable column? Less moddable this way
6795 	// the original hardcoded this, but we can do better - all the data is already in the tables
6796 	// HOWEVER, what to do with multiclass characters? -> check the monk table, since it is prone to have the highest values?
6797 	// additionally, 5 is the real max, but not without dualwielding or effects
6798 	if (APR > 4) {
6799 		APR = 4;
6800 	}
6801 	// NOTE: we currently double the value, since it is stored doubled in other games and effects rely on it
6802 	// if you want to change it, don't forget to do the same for the bonus in GetNumberOfAttacks
6803 	return APR*2;
6804 }
6805 
6806 //calculate how many attacks will be performed
6807 //in the next round
6808 //only called when Game thinks we are in attack
6809 //so it is safe to do cleanup here (it will be called only once)
InitRound(ieDword gameTime)6810 void Actor::InitRound(ieDword gameTime)
6811 {
6812 	lastInit = gameTime;
6813 	secondround = !secondround;
6814 
6815 	//reset variables used in PerformAttack
6816 	attackcount = 0;
6817 	attacksperround = 0;
6818 	nextattack = 0;
6819 	lastattack = 0;
6820 
6821 	//add one for second round to get an extra attack only if we
6822 	//are x/2 attacks per round
6823 	attackcount = GetNumberOfAttacks();
6824 	if (secondround) {
6825 		attackcount++;
6826 	}
6827 	//all numbers of attacks are stored at twice their value
6828 	attackcount >>= 1;
6829 
6830 	//make sure we always get at least 1apr
6831 	// but only if it wasn't 0 from the start, like rats in Candlekeep
6832 	if (attackcount < 1 && BaseStats[IE_NUMBEROFATTACKS] != 0) {
6833 		attackcount = 1;
6834 	}
6835 
6836 	//set our apr and starting round time
6837 	attacksperround = attackcount;
6838 	roundTime = gameTime;
6839 
6840 	//print a little message :)
6841 	Log(MESSAGE, "InitRound", "Name: %s | Attacks: %d | Start: %d",
6842 		ShortName, attacksperround, gameTime);
6843 
6844 	// this might not be the right place, but let's give it a go
6845 	if (attacksperround && InParty) {
6846 		core->Autopause(AP_ENDROUND, this);
6847 	}
6848 }
6849 
6850 // a simplified check from GetCombatDetails for use in AttackCore
WeaponIsUsable(bool leftorright,ITMExtHeader * header) const6851 bool Actor::WeaponIsUsable(bool leftorright, ITMExtHeader *header) const
6852 {
6853 	WeaponInfo wi;
6854 	if (!header) {
6855 		header = GetWeapon(wi, leftorright && IsDualWielding());
6856 		if (!header) {
6857 			return false;
6858 		}
6859 	}
6860 	ITMExtHeader *rangedheader;
6861 	switch(header->AttackType) {
6862 		case ITEM_AT_MELEE:
6863 		case ITEM_AT_PROJECTILE: //throwing weapon
6864 			break;
6865 		case ITEM_AT_BOW:
6866 			rangedheader = GetRangedWeapon(wi);
6867 			if (!rangedheader) {
6868 				return false;
6869 			}
6870 			break;
6871 		default:
6872 			//item is unsuitable for fight
6873 			return false;
6874 	}
6875 	return true;
6876 }
6877 
GetCombatDetails(int & tohit,bool leftorright,WeaponInfo & wi,ITMExtHeader * & header,ITMExtHeader * & hittingheader,int & DamageBonus,int & speed,int & CriticalBonus,int & style,const Actor * target)6878 bool Actor::GetCombatDetails(int &tohit, bool leftorright, WeaponInfo& wi, ITMExtHeader *&header, ITMExtHeader *&hittingheader, \
6879 		int &DamageBonus, int &speed, int &CriticalBonus, int &style, const Actor *target)
6880 {
6881 	SetBaseAPRandAB(true);
6882 	speed = -(int)GetStat(IE_PHYSICALSPEED);
6883 	ieDword dualwielding = IsDualWielding();
6884 	header = GetWeapon(wi, leftorright && dualwielding);
6885 	if (!header) {
6886 		return false;
6887 	}
6888 	style = 0;
6889 	CriticalBonus = 0;
6890 	hittingheader = header;
6891 	ITMExtHeader *rangedheader = NULL;
6892 	int THAC0Bonus = hittingheader->THAC0Bonus;
6893 	DamageBonus = hittingheader->DamageBonus;
6894 
6895 	switch(hittingheader->AttackType) {
6896 	case ITEM_AT_MELEE:
6897 		wi.wflags = WEAPON_MELEE;
6898 		break;
6899 	case ITEM_AT_PROJECTILE: //throwing weapon
6900 		wi.wflags = WEAPON_RANGED;
6901 		break;
6902 	case ITEM_AT_BOW:
6903 		rangedheader = GetRangedWeapon(wi);
6904 		if (!rangedheader) {
6905 			//display out of ammo verbal constant if there were any
6906 			//VerbalConstant(VB_OUTOFAMMO); // FUTURE: gemrb extension
6907 			//SetStance(IE_ANI_READY);
6908 			//set some trigger?
6909 			return false;
6910 		}
6911 		wi.wflags = WEAPON_RANGED;
6912 		wi.launcherdmgbon = DamageBonus; // save the original (launcher) bonus
6913 		//The bow can give some bonuses, but the core attack is made by the arrow.
6914 		hittingheader = rangedheader;
6915 		THAC0Bonus += rangedheader->THAC0Bonus;
6916 		DamageBonus += rangedheader->DamageBonus;
6917 		break;
6918 	default:
6919 		//item is unsuitable for fight
6920 		wi.wflags = 0;
6921 		return false;
6922 	}//melee or ranged
6923 	if (ReverseToHit) THAC0Bonus = -THAC0Bonus;
6924 	ToHit.SetWeaponBonus(THAC0Bonus);
6925 
6926 	//TODO easier copying of recharge flags into wflags
6927 	//this flag is set by the bow in case of projectile launcher.
6928 	if (header->RechargeFlags&IE_ITEM_USESTRENGTH) wi.wflags|=WEAPON_USESTRENGTH;
6929 	if (header->RechargeFlags&IE_ITEM_USESTRENGTH_DMG) wi.wflags |= WEAPON_USESTRENGTH_DMG;
6930 	if (header->RechargeFlags&IE_ITEM_USESTRENGTH_HIT) wi.wflags |= WEAPON_USESTRENGTH_HIT;
6931 	// this flag is set in dagger/shortsword by the loader
6932 	if (header->RechargeFlags&IE_ITEM_USEDEXTERITY) wi.wflags|=WEAPON_FINESSE;
6933 	//also copy these flags (they match their WEAPON_ counterparts)
6934 	wi.wflags|=header->RechargeFlags&(IE_ITEM_KEEN|IE_ITEM_BYPASS);
6935 
6936 	if (wi.wflags & WEAPON_RANGED && (hittingheader->RechargeFlags&IE_ITEM_KEEN)) {
6937 		// all the previous checks were against the launcher, but ammo can be keen too (00arow87)
6938 		wi.critrange--;
6939 	}
6940 
6941 	// get our dual wielding modifier
6942 	if (dualwielding) {
6943 		if (leftorright) {
6944 			DamageBonus += GetStat(IE_DAMAGEBONUSLEFT);
6945 		} else {
6946 			DamageBonus += GetStat(IE_DAMAGEBONUSRIGHT);
6947 		}
6948 	}
6949 	DamageBonus += GetStat(IE_DAMAGEBONUS);
6950 	leftorright = leftorright && dualwielding;
6951 	if (leftorright) wi.wflags|=WEAPON_LEFTHAND;
6952 
6953 	//add in proficiency bonuses
6954 	ieDword stars = GetProficiency(wi.prof)&PROFS_MASK;
6955 
6956 	//tenser's transformation makes the actor proficient in any weapons
6957 	// also conjured weapons are wielded without penalties
6958 	if (!stars && (HasSpellState(SS_TENSER) || inventory.MagicSlotEquipped())) {
6959 		stars = 1;
6960 	}
6961 
6962 	//hit/damage/speed bonuses from wspecial (with tohit inverted in adnd)
6963 	static ieDword wspecialMax = wspecial->GetRowCount() - 1;
6964 	if (stars > wspecialMax) {
6965 		stars = wspecialMax;
6966 	}
6967 
6968 	int prof = 0;
6969 	// iwd2 adds a -4 nonprof penalty (others below, since their table is bad and actual values differ by class)
6970 	// but everyone is proficient with fists
6971 	// cheesily limited to party only (10gob hits it - practically can't hit you otherwise)
6972 	if (InParty && !inventory.FistsEquipped()) {
6973 		prof += atoi(wspecial->QueryField(stars, 0));
6974 	}
6975 
6976 	wi.profdmgbon = atoi(wspecial->QueryField(stars, 1));
6977 	DamageBonus += wi.profdmgbon;
6978 	// only bg2 wspecial.2da has this column, but all have 0 as the default table value, so this lookup is fine
6979 	speed += atoi(wspecial->QueryField(stars, 2));
6980 	// add non-proficiency penalty, which is missing from the table in non-iwd2
6981 	// stored negative
6982 	if (stars == 0 && !third) {
6983 		ieDword clss = GetActiveClass();
6984 		//Is it a PC class?
6985 		if (clss < (ieDword) classcount) {
6986 			// but skip fists, since they don't have a proficiency
6987 			if (!inventory.FistsEquipped()) {
6988 				prof += defaultprof[clss];
6989 			}
6990 		} else {
6991 			//it is not clear what is the penalty for non player classes
6992 			prof -= 4;
6993 		}
6994 	}
6995 
6996 	if (dualwielding && wsdualwield) {
6997 		//add dual wielding penalty
6998 		stars = GetStat(IE_PROFICIENCY2WEAPON)&PROFS_MASK;
6999 		if (stars > STYLE_MAX) stars = STYLE_MAX;
7000 
7001 		style = 1000*stars + IE_PROFICIENCY2WEAPON;
7002 		prof += wsdualwield[stars][leftorright?1:0];
7003 	} else if (wi.itemflags&(IE_INV_ITEM_TWOHANDED) && (wi.wflags&WEAPON_MELEE) && wstwohanded) {
7004 		//add two handed profs bonus
7005 		stars = GetStat(IE_PROFICIENCY2HANDED)&PROFS_MASK;
7006 		if (stars > STYLE_MAX) stars = STYLE_MAX;
7007 
7008 		style = 1000*stars + IE_PROFICIENCY2HANDED;
7009 		DamageBonus += wstwohanded[stars][0];
7010 		CriticalBonus = wstwohanded[stars][1];
7011 		speed += wstwohanded[stars][2];
7012 	} else if (wi.wflags&WEAPON_MELEE) {
7013 		int slot;
7014 		CREItem *weapon = inventory.GetUsedWeapon(true, slot);
7015 		if(wssingle && weapon == NULL) {
7016 			//NULL return from GetUsedWeapon means no shield slot
7017 			stars = GetStat(IE_PROFICIENCYSINGLEWEAPON)&PROFS_MASK;
7018 			if (stars > STYLE_MAX) stars = STYLE_MAX;
7019 
7020 			style = 1000*stars + IE_PROFICIENCYSINGLEWEAPON;
7021 			CriticalBonus = wssingle[stars][1];
7022 		} else if (wsswordshield && weapon) {
7023 			stars = GetStat(IE_PROFICIENCYSWORDANDSHIELD)&PROFS_MASK;
7024 			if (stars > STYLE_MAX) stars = STYLE_MAX;
7025 
7026 			style = 1000*stars + IE_PROFICIENCYSWORDANDSHIELD;
7027 		} else {
7028 			// no bonus
7029 		}
7030 	} else {
7031 		// ranged - no bonus
7032 	}
7033 
7034 	// racial enemies suffer 4hp more in all games
7035 	int favoredEnemy = GetRacialEnemyBonus(target);
7036 	if (GetRangerLevel() && favoredEnemy) {
7037 		DamageBonus += favoredEnemy;
7038 	}
7039 
7040 	// Elves get a racial THAC0 bonus with swords and bows, halflings with slings
7041 	prof += gamedata->GetRacialTHAC0Bonus(wi.prof, GetRaceName());
7042 
7043 	if (third) {
7044 		// iwd2 gives a dualwielding bonus when using a simple weapon in the offhand
7045 		// it is limited to shortswords and daggers, which also have this flag set
7046 		// the bonus is applied to both hands
7047 		if (dualwielding) {
7048 			if (leftorright) {
7049 				if (wi.wflags&WEAPON_FINESSE) {
7050 					prof += 2;
7051 				}
7052 			} else {
7053 				// lookup the offhand
7054 				ITMExtHeader *header2;
7055 				WeaponInfo wi2;
7056 				header2 = GetWeapon(wi2, true);
7057 				if (header2->RechargeFlags&IE_ITEM_USEDEXTERITY) { // identical to the WEAPON_FINESSE check
7058 					prof += 2;
7059 				}
7060 			}
7061 		}
7062 	} else {
7063 		prof = -prof;
7064 	}
7065 	ToHit.SetProficiencyBonus(prof);
7066 
7067 	// get the remaining boni
7068 	// FIXME: merge
7069 	tohit = GetToHit(wi.wflags, target);
7070 
7071 	//pst increased critical hits
7072 	if (pstflags && (Modified[IE_STATE_ID]&STATE_CRIT_ENH)) {
7073 		CriticalBonus--;
7074 	}
7075 	return true;
7076 }
7077 
MeleePenalty() const7078 int Actor::MeleePenalty() const
7079 {
7080 	if (GetMonkLevel()) return 0;
7081 	if (inventory.FistsEquipped()) return -4;
7082 	return 0;
7083 }
7084 
7085 //FIXME: can get called on its own and ToHit could erroneusly give weapon and some prof boni in that case
GetToHit(ieDword Flags,const Actor * target)7086 int Actor::GetToHit(ieDword Flags, const Actor *target)
7087 {
7088 	int generic = 0;
7089 	int prof = 0;
7090 	int attacknum = attackcount;
7091 
7092 	//get our dual wielding modifier
7093 	if (IsDualWielding()) {
7094 		if (Flags&WEAPON_LEFTHAND) {
7095 			generic = GetStat(IE_HITBONUSLEFT);
7096 			attacknum = 1; // shouldn't be needed, but let's play safe
7097 		} else {
7098 			generic = GetStat(IE_HITBONUSRIGHT);
7099 			attacknum--; // remove 1, since it is for the other hand (otherwise we would never use the max tohit for this hand)
7100 		}
7101 		if (third) {
7102 			// rangers wearing light or no armor gain ambidexterity and
7103 			//  two-weapon-fighting feats for free
7104 			bool ambidextrous = HasFeat(FEAT_AMBIDEXTERITY);
7105 			bool twoWeaponFighting = HasFeat(FEAT_TWO_WEAPON_FIGHTING);
7106 			if (GetRangerLevel()) {
7107 				ieWord armorType = inventory.GetArmorItemType();
7108 				if (GetArmorWeightClass(armorType) <= 1) {
7109 					ambidextrous = true;
7110 					twoWeaponFighting = true;
7111 				}
7112 			}
7113 
7114 			// FIXME: externalise
7115 			// penalites and boni for both hands:
7116 			// -6 main, -10 off with no adjustments
7117 			//  0 main, +4 off with ambidexterity
7118 			// +2 main, +2 off with two weapon fighting
7119 			// +2 main, +2 off with a simple weapons in the off hand (handled in GetCombatDetails)
7120 			if (twoWeaponFighting) {
7121 				prof += 2;
7122 			}
7123 			if (Flags&WEAPON_LEFTHAND) {
7124 				prof -= 6;
7125 			} else {
7126 				prof -= 10;
7127 				if (ambidextrous) {
7128 					prof += 4;
7129 				}
7130 			}
7131 		}
7132 	}
7133 	ToHit.SetProficiencyBonus(ToHit.GetProficiencyBonus()+prof);
7134 
7135 	// set up strength/dexterity boni
7136 	GetTHAbilityBonus(Flags);
7137 
7138 	// check if there is any armor unproficiency penalty
7139 	int am = 0, sm = 0;
7140 	GetArmorSkillPenalty(1, am, sm);
7141 	ToHit.SetArmorBonus(-am);
7142 	ToHit.SetShieldBonus(-sm);
7143 
7144 	//get attack style (melee or ranged)
7145 	switch(Flags&WEAPON_STYLEMASK) {
7146 		case WEAPON_MELEE:
7147 			generic += GetStat(IE_MELEETOHIT);
7148 			break;
7149 		case WEAPON_FIST:
7150 			generic += GetStat(IE_FISTHIT);
7151 			break;
7152 		case WEAPON_RANGED:
7153 			generic += GetStat(IE_MISSILEHITBONUS);
7154 			break;
7155 	}
7156 
7157 	if (target) {
7158 		// if the target is using a ranged weapon while we're meleeing, we get a +4 bonus
7159 		if ((Flags&WEAPON_STYLEMASK) != WEAPON_RANGED) {
7160 			if (target->GetAttackStyle() == WEAPON_RANGED) {
7161 				generic += 4;
7162 			}
7163 		}
7164 
7165 		// melee vs. unarmed
7166 		generic += target->MeleePenalty() - MeleePenalty();
7167 
7168 		// add +4 attack bonus vs racial enemies
7169 		if (GetRangerLevel()) {
7170 			generic += GetRacialEnemyBonus(target);
7171 		}
7172 		generic += fxqueue.BonusAgainstCreature(fx_tohit_vs_creature_ref, target);
7173 	}
7174 
7175 	// add generic bonus
7176 	generic += GetStat(IE_HITBONUS);
7177 
7178 	// now this func is the only one to modify generic bonus, so no need to add
7179 	if (ReverseToHit) {
7180 		ToHit.SetGenericBonus(-generic);
7181 		return ToHit.GetTotal();
7182 	} else {
7183 		ToHit.SetGenericBonus(generic); // flat out cumulative
7184 		return ToHit.GetTotalForAttackNum(attacknum);
7185 	}
7186 }
7187 
GetTHAbilityBonus(ieDword Flags)7188 void Actor::GetTHAbilityBonus(ieDword Flags)
7189 {
7190 	int dexbonus = 0, strbonus = 0;
7191 	// add strength bonus (discarded for ranged weapons later)
7192 	if (Flags&WEAPON_USESTRENGTH || Flags&WEAPON_USESTRENGTH_HIT) {
7193 		if (third) {
7194 			strbonus = GetAbilityBonus(IE_STR );
7195 		} else {
7196 			strbonus = core->GetStrengthBonus(0,GetStat(IE_STR), GetStat(IE_STREXTRA) );
7197 		}
7198 	}
7199 
7200 	//get attack style (melee or ranged)
7201 	switch(Flags&WEAPON_STYLEMASK) {
7202 		case WEAPON_MELEE:
7203 			if ((Flags&WEAPON_FINESSE) && HasFeat(FEAT_WEAPON_FINESSE) ) {
7204 				if (third) {
7205 					dexbonus = GetAbilityBonus(IE_DEX );
7206 				} else {
7207 					dexbonus = core->GetDexterityBonus(STAT_DEX_MISSILE, GetStat(IE_DEX));
7208 				}
7209 				// weapon finesse is not cummulative
7210 				if (dexbonus > strbonus) {
7211 					strbonus = 0;
7212 				} else {
7213 					dexbonus = 0;
7214 				}
7215 			}
7216 			break;
7217 		case WEAPON_RANGED:
7218 			//add dexterity bonus
7219 			if (third) {
7220 				dexbonus = GetAbilityBonus(IE_DEX);
7221 			} else {
7222 				dexbonus = core->GetDexterityBonus(STAT_DEX_MISSILE, GetStat(IE_DEX));
7223 			}
7224 			// WEAPON_USESTRENGTH only affects weapon damage, WEAPON_USESTRENGTH_HIT unknown
7225 			strbonus = 0;
7226 			break;
7227 		// no ability tohit bonus for WEAPON_FIST
7228 	}
7229 
7230 	// both strength and dex bonus are stored positive only in iwd2
7231 	if (third) {
7232 		ToHit.SetAbilityBonus(dexbonus + strbonus);
7233 	} else {
7234 		ToHit.SetAbilityBonus(-(dexbonus + strbonus));
7235 	}
7236 }
7237 
GetDefense(int DamageType,ieDword wflags,const Actor * attacker) const7238 int Actor::GetDefense(int DamageType, ieDword wflags, const Actor *attacker) const
7239 {
7240 	//specific damage type bonus.
7241 	int defense = 0;
7242 	if(DamageType > 5)
7243 		DamageType = 0;
7244 	switch (weapon_damagetype[DamageType]) {
7245 	case DAMAGE_CRUSHING:
7246 		defense += GetStat(IE_ACCRUSHINGMOD);
7247 		break;
7248 	case DAMAGE_PIERCING:
7249 		defense += GetStat(IE_ACPIERCINGMOD);
7250 		break;
7251 	case DAMAGE_SLASHING:
7252 		defense += GetStat(IE_ACSLASHINGMOD);
7253 		break;
7254 	case DAMAGE_MISSILE:
7255 		defense += GetStat(IE_ACMISSILEMOD);
7256 		break;
7257 	//What about stunning ?
7258 	default :
7259 		break;
7260 	}
7261 
7262 
7263 	//check for s/s and single weapon ac bonuses
7264 	if (!IsDualWielding() && wssingle && wsswordshield) {
7265 		WeaponInfo wi;
7266 		ITMExtHeader* header;
7267 		header = GetWeapon(wi, false);
7268 		//make sure we're wielding a single melee weapon
7269 		if (header && (header->AttackType == ITEM_AT_MELEE)) {
7270 			int slot;
7271 			ieDword stars;
7272 			if (inventory.GetUsedWeapon(true, slot) == NULL) {
7273 				//single-weapon style applies to all ac
7274 				stars = GetStat(IE_PROFICIENCYSINGLEWEAPON)&PROFS_MASK;
7275 				if (stars>STYLE_MAX) stars = STYLE_MAX;
7276 				defense += wssingle[stars][0];
7277 			} else if (weapon_damagetype[DamageType] == DAMAGE_MISSILE) {
7278 				//sword-shield style applies only to missile ac
7279 				stars = GetStat(IE_PROFICIENCYSWORDANDSHIELD)&PROFS_MASK;
7280 				if (stars>STYLE_MAX) stars = STYLE_MAX;
7281 				defense += wsswordshield[stars][0];
7282 			}
7283 		}
7284 	}
7285 
7286 	if (wflags&WEAPON_BYPASS) {
7287 		if (ReverseToHit) {
7288 			// deflection is used to store the armor value in adnd
7289 			defense = AC.GetTotal() - AC.GetDeflectionBonus() + defense;
7290 		} else {
7291 			defense += AC.GetTotal() - AC.GetArmorBonus() - AC.GetShieldBonus();
7292 		}
7293 	} else {
7294 		if (ReverseToHit) {
7295 			defense = AC.GetTotal() + defense;
7296 		} else {
7297 			defense += AC.GetTotal();
7298 		}
7299 	}
7300 
7301 	// is the attacker invisible? We don't care if we know the right uncanny dodge
7302 	if (third && attacker && attacker->GetStat(state_invisible)) {
7303 		if ((GetStat(IE_UNCANNY_DODGE) & 0x100) == 0) {
7304 			// oops, we lose the dex bonus (like flatfooted)
7305 			defense -= AC.GetDexterityBonus();
7306 		}
7307 	}
7308 
7309 	if (attacker) {
7310 		defense -= fxqueue.BonusAgainstCreature(fx_ac_vs_creature_type_ref,attacker);
7311 	}
7312 	return defense;
7313 }
7314 
PerformAttack(ieDword gameTime)7315 void Actor::PerformAttack(ieDword gameTime)
7316 {
7317 	// don't let imprisoned or otherwise missing actors continue their attack
7318 	if (Modified[IE_AVATARREMOVAL]) return;
7319 
7320 	if (InParty) {
7321 		// TODO: this is temporary hack
7322 		Game *game = core->GetGame();
7323 		game->PartyAttack = true;
7324 	}
7325 
7326 	if (!roundTime || (gameTime-roundTime > core->Time.attack_round_size)) { // the original didn't use a normal round
7327 		// TODO: do we need cleverness for secondround here?
7328 		InitRound(gameTime);
7329 	}
7330 
7331 	//only return if we don't have any attacks left this round
7332 	if (attackcount==0) {
7333 		// this is also part of the UpdateActorState hack below. sorry!
7334 		lastattack = gameTime;
7335 		return;
7336 	}
7337 
7338 	// this check shouldn't be necessary, but it causes a divide-by-zero below,
7339 	// so i would like it to be clear if it ever happens
7340 	if (attacksperround==0) {
7341 		Log(ERROR, "Actor", "APR was 0 in PerformAttack!");
7342 		return;
7343 	}
7344 
7345 	//don't continue if we can't make the attack yet
7346 	//we check lastattack because we will get the same gameTime a few times
7347 	if ((nextattack > gameTime) || (gameTime == lastattack)) {
7348 		// fuzzie added the following line as part of the UpdateActorState hack below
7349 		lastattack = gameTime;
7350 		return;
7351 	}
7352 
7353 	if (IsDead()) {
7354 		// this should be avoided by the AF_ALIVE check by all the calling actions
7355 		Log(ERROR, "Actor", "Attack by dead actor!");
7356 		return;
7357 	}
7358 
7359 	if (!LastTarget) {
7360 		Log(ERROR, "Actor", "Attack without valid target ID!");
7361 		return;
7362 	}
7363 	//get target
7364 	Actor *target = area->GetActorByGlobalID(LastTarget);
7365 	if (!target) {
7366 		Log(WARNING, "Actor", "Attack without valid target!");
7367 		return;
7368 	}
7369 
7370 	// also start CombatCounter if a pc is attacked
7371 	if (!InParty && target->IsPartyMember()) {
7372 		core->GetGame()->PartyAttack = true;
7373 	}
7374 
7375 	assert(!(target->IsInvisibleTo((Scriptable *) this) || (target->GetSafeStat(IE_STATE_ID) & STATE_DEAD)));
7376 	target->AttackedBy(this);
7377 	ieDword state = GetStat(IE_STATE_ID);
7378 	if (state&STATE_BERSERK) {
7379 		BaseStats[IE_CHECKFORBERSERK]=3;
7380 	}
7381 
7382 	Log(DEBUG, "Actor", "Performattack for %s, target is: %s", ShortName, target->ShortName);
7383 
7384 	//which hand is used
7385 	//we do apr - attacksleft so we always use the main hand first
7386 	// however, in 3ed, only one attack can be made by the offhand
7387 	bool leftorright;
7388 	if (third) {
7389 		leftorright = false;
7390 		// make only the last attack with the offhand (iwd2)
7391 		if (attackcount == 1 && IsDualWielding()) {
7392 			leftorright = true;
7393 		}
7394 	} else {
7395 		leftorright = (bool) ((attacksperround-attackcount)&1);
7396 	}
7397 
7398 	WeaponInfo wi;
7399 	ITMExtHeader *header = NULL;
7400 	ITMExtHeader *hittingheader = NULL;
7401 	int tohit;
7402 	int DamageBonus, CriticalBonus;
7403 	int speed, style;
7404 
7405 	//will return false on any errors (eg, unusable weapon)
7406 	if (!GetCombatDetails(tohit, leftorright, wi, header, hittingheader, DamageBonus, speed, CriticalBonus, style, target)) {
7407 		return;
7408 	}
7409 
7410 	if (PCStats) {
7411 		// make a copy of wi.slot, since GetUsedWeapon can modify it
7412 		int wislot = wi.slot;
7413 		CREItem *slot = inventory.GetUsedWeapon(leftorright && IsDualWielding(), wislot);
7414 		//if slot was null, then GetCombatDetails returned false
7415 		PCStats->RegisterFavourite(slot->ItemResRef, FAV_WEAPON);
7416 	}
7417 
7418 	//if this is the first call of the round, we need to update next attack
7419 	if (nextattack == 0) {
7420 		// initiative calculation (lucky 1d6-1 + item speed + speed stat + constant):
7421 		// speed contains the bonus from the physical speed stat and the proficiency level
7422 		int spdfactor = hittingheader->Speed + speed;
7423 		if (spdfactor<0) spdfactor = 0;
7424 		// -3: k/2 in the original, hardcoded to 6; -1 for the difference in rolls - the original rolled 0-5
7425 		spdfactor += LuckyRoll(1, 6, -4, LR_NEGATIVE);
7426 		if (spdfactor<0) spdfactor = 0;
7427 		if (spdfactor>10) spdfactor = 10;
7428 
7429 		//(round_size/attacks_per_round)*(initiative) is the first delta
7430 		nextattack = core->Time.round_size*spdfactor/(attacksperround*10) + gameTime;
7431 
7432 		//we can still attack this round if we have a speed factor of 0
7433 		if (nextattack > gameTime) {
7434 			return;
7435 		}
7436 	}
7437 
7438 	if (!WithinPersonalRange(this, target, GetWeaponRange(wi)) || (GetCurrentArea() != target->GetCurrentArea())) {
7439 		// this is a temporary double-check, remove when bugfixed
7440 		Log(ERROR, "Actor", "Attack action didn't bring us close enough!");
7441 		return;
7442 	}
7443 
7444 	SetStance(AttackStance);
7445 	PlaySwingSound(wi);
7446 
7447 	//figure out the time for our next attack since the old time has the initiative
7448 	//in it, we only have to add the basic delta
7449 	attackcount--;
7450 	nextattack += (core->Time.round_size/attacksperround);
7451 	lastattack = gameTime;
7452 
7453 	StringBuffer buffer;
7454 	//debug messages
7455 	if (leftorright && IsDualWielding()) {
7456 		buffer.append("(Off) ");
7457 	} else {
7458 		buffer.append("(Main) ");
7459 	}
7460 	if (attacksperround) {
7461 		buffer.appendFormatted("Left: %d | ", attackcount);
7462 		buffer.appendFormatted("Next: %d ", nextattack);
7463 	}
7464 	if (fxqueue.HasEffectWithParam(fx_puppetmarker_ref, 1) || fxqueue.HasEffectWithParam(fx_puppetmarker_ref, 2)) { // illusions can't hit
7465 		ResetState();
7466 		buffer.append("[Missed (puppet)]");
7467 		Log(COMBAT, "Attack", buffer);
7468 		return;
7469 	}
7470 
7471 	// iwd2 smite evil only lasts for one attack, but has an insane duration, so remove it manually
7472 	if (HasSpellState(SS_SMITEEVIL)) {
7473 		fxqueue.RemoveAllEffects(fx_smite_evil_ref);
7474 	}
7475 
7476 	// check for concealment first (iwd2), both our enemies' and from our phasing problems
7477 	int concealment = (GetStat(IE_ETHEREALNESS)>>8) + (target->GetStat(IE_ETHEREALNESS) & 0x64);
7478 	if (concealment) {
7479 		if (LuckyRoll(1, 100, 0) < concealment) {
7480 			// can we retry?
7481 			if (!HasFeat(FEAT_BLIND_FIGHT) || LuckyRoll(1, 100, 0) < concealment) {
7482 				// Missed <TARGETNAME> due to concealment.
7483 				core->GetTokenDictionary()->SetAtCopy("TARGETNAME", target->GetName(-1));
7484 				if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantStringName(STR_CONCEALED_MISS, DMC_WHITE, this);
7485 				buffer.append("[Concealment Miss]");
7486 				Log(COMBAT, "Attack", buffer);
7487 				ResetState();
7488 				return;
7489 			}
7490 		}
7491 	}
7492 
7493 	// iwd2 rerolls to check for criticals (cf. manual page 45) - the second roll just needs to hit; on miss, it degrades to a normal hit
7494 	// CriticalBonus is negative, it is added to the minimum roll needed for a critical hit
7495 	// IE_CRITICALHITBONUS is positive, it is subtracted
7496 	int roll = LuckyRoll(1, ATTACKROLL, 0, LR_CRITICAL);
7497 	int criticalroll = roll + (int) GetStat(IE_CRITICALHITBONUS) - CriticalBonus;
7498 	if (third) {
7499 		int ThreatRangeMin = wi.critrange;
7500 		ThreatRangeMin -= ((int) GetStat(IE_CRITICALHITBONUS) - CriticalBonus); // TODO: move to GetCombatDetails
7501 		criticalroll = LuckyRoll(1, ATTACKROLL, 0, LR_CRITICAL);
7502 		if (criticalroll < ThreatRangeMin || GetStat(IE_SPECFLAGS)&SPECF_CRITIMMUNITY) {
7503 			// make it an ordinary hit
7504 			criticalroll = 1;
7505 		} else {
7506 			// make sure it will be a critical hit
7507 			criticalroll = ATTACKROLL;
7508 		}
7509 	}
7510 
7511 	//damage type is?
7512 	//modify defense with damage type
7513 	ieDword damagetype = hittingheader->DamageType;
7514 	int damage = 0;
7515 
7516 	if (hittingheader->DiceThrown<256) {
7517 		// another bizarre 2E feature that's unused, but working
7518 		if (!third && hittingheader->AltDiceSides && target->GetStat(IE_MC_FLAGS) & MC_LARGE_CREATURE) {
7519 			// make sure not to discard other damage bonuses from above
7520 			int dmgBon = DamageBonus - hittingheader->DamageBonus + hittingheader->AltDamageBonus;
7521 			damage += LuckyRoll(hittingheader->AltDiceThrown, hittingheader->AltDiceSides, dmgBon, LR_DAMAGELUCK);
7522 		} else {
7523 			damage += LuckyRoll(hittingheader->DiceThrown, hittingheader->DiceSides, DamageBonus, LR_DAMAGELUCK);
7524 		}
7525 		if (damage < 0) damage = 0; // bad luck, effects and/or profs on lowlevel chars
7526 	} else {
7527 		damage = 0;
7528 	}
7529 
7530 	bool critical = criticalroll>=ATTACKROLL;
7531 	bool success = critical;
7532 	int defense = target->GetDefense(damagetype, wi.wflags, this);
7533 	int rollMod = (ReverseToHit) ? defense : tohit;
7534 	if (!critical) {
7535 		// autohit immobile enemies (true for atleast stun, sleep, timestop)
7536 		if (target->Immobile() || (target->GetStat(IE_STATE_ID) & STATE_SLEEP)) {
7537 			success = true;
7538 		} else if (roll == 1) {
7539 			success = false;
7540 		} else {
7541 			success = (roll + rollMod) > ((ReverseToHit) ? tohit : defense);
7542 		}
7543 	}
7544 
7545 	if (target->GetStat(IE_EXTSTATE_ID) & EXTSTATE_EYE_SWORD) {
7546 		target->fxqueue.RemoveAllEffects(fx_eye_sword_ref);
7547 		target->spellbook.RemoveSpell(SevenEyes[EYE_SWORD]);
7548 		target->SetBaseBit(IE_EXTSTATE_ID, EXTSTATE_EYE_SWORD, false);
7549 		success = false;
7550 		roll = 2; // avoid chance critical misses
7551 	}
7552 
7553 	if (core->HasFeedback(FT_TOHIT)) {
7554 		// log the roll
7555 		wchar_t rollLog[100];
7556 		const wchar_t *fmt = L"%ls %d %ls %d = %d : %ls";
7557 		String *leftRight, *hitMiss;
7558 		if (leftorright && displaymsg->HasStringReference(STR_ATTACK_ROLL_L)) {
7559 			leftRight = core->GetString(displaymsg->GetStringReference(STR_ATTACK_ROLL_L));
7560 		} else {
7561 			leftRight = core->GetString(displaymsg->GetStringReference(STR_ATTACK_ROLL));
7562 		}
7563 		if (success) {
7564 			hitMiss = core->GetString(displaymsg->GetStringReference(STR_HIT));
7565 		} else {
7566 			hitMiss = core->GetString(displaymsg->GetStringReference(STR_MISS));
7567 		}
7568 		swprintf(rollLog, 100, fmt, leftRight->c_str(), roll, (rollMod >= 0) ? L"+" : L"-", abs(rollMod), roll + rollMod, hitMiss->c_str());
7569 		displaymsg->DisplayStringName(rollLog, DMC_WHITE, this);
7570 		delete leftRight;
7571 		delete hitMiss;
7572 	}
7573 
7574 	if (roll == 1) {
7575 		//critical failure
7576 		buffer.append("[Critical Miss]");
7577 		Log(COMBAT, "Attack", buffer);
7578 		if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantStringName(STR_CRITICAL_MISS, DMC_WHITE, this);
7579 		VerbalConstant(VB_CRITMISS);
7580 		if (wi.wflags & WEAPON_RANGED) {//no need for this with melee weapon!
7581 			UseItem(wi.slot, (ieDword) -2, target, UI_MISS|UI_NOAURA);
7582 		} else if (core->HasFeature(GF_BREAKABLE_WEAPONS) && InParty) {
7583 			//break sword
7584 			// a random roll on-hit (perhaps critical failure too)
7585 			//  in 0,5% (1d20*1d10==1) cases
7586 			if ((header->RechargeFlags & IE_ITEM_BREAKABLE) && core->Roll(1, 10, 0) == 1) {
7587 				inventory.BreakItemSlot(wi.slot);
7588 				inventory.EquipBestWeapon(EQUIP_MELEE);
7589 			}
7590 		}
7591 		ResetState();
7592 		return;
7593 	}
7594 
7595 	if (!success) {
7596 		//hit failed
7597 		if (wi.wflags&WEAPON_RANGED) {//Launch the projectile anyway
7598 			UseItem(wi.slot, (ieDword)-2, target, UI_MISS|UI_NOAURA);
7599 		}
7600 		ResetState();
7601 		buffer.append("[Missed]");
7602 		Log(COMBAT, "Attack", buffer);
7603 		return;
7604 	}
7605 
7606 	ModifyWeaponDamage(wi, target, damage, critical);
7607 
7608 	if (third && target->GetStat(IE_MC_FLAGS) & MC_INVULNERABLE) {
7609 		Log(DEBUG, "Actor", "Attacking invulnerable target, nulifying damage!");
7610 		damage = 0;
7611 	}
7612 
7613 	if (critical) {
7614 		//critical success
7615 		buffer.append("[Critical Hit]");
7616 		Log(COMBAT, "Attack", buffer);
7617 		if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantStringName(STR_CRITICAL_HIT, DMC_WHITE, this);
7618 		VerbalConstant(VB_CRITHIT);
7619 	} else {
7620 		//normal success
7621 		buffer.append("[Hit]");
7622 		Log(COMBAT, "Attack", buffer);
7623 	}
7624 	UseItem(wi.slot, wi.wflags&WEAPON_RANGED?-2:-1, target, (critical?UI_CRITICAL:0)|UI_NOAURA, damage);
7625 	ResetState();
7626 }
7627 
GetWeaponRange(const WeaponInfo & wi) const7628 int Actor::GetWeaponRange(const WeaponInfo &wi) const
7629 {
7630 	return std::min(wi.range, Modified[IE_VISUALRANGE]);
7631 }
7632 
WeaponDamageBonus(const WeaponInfo & wi) const7633 int Actor::WeaponDamageBonus(const WeaponInfo &wi) const
7634 {
7635 	if (wi.wflags&WEAPON_USESTRENGTH || wi.wflags&WEAPON_USESTRENGTH_DMG) {
7636 		if (third) {
7637 			int bonus = GetAbilityBonus(IE_STR);
7638 			// 150% bonus for twohanders
7639 			if (wi.itemflags&IE_INV_ITEM_TWOHANDED) bonus+=bonus/2;
7640 			// only 50% for the offhand
7641 			if (wi.wflags&WEAPON_LEFTHAND) bonus=bonus/2;
7642 			return bonus;
7643 		}
7644 		return core->GetStrengthBonus(1, GetStat(IE_STR), GetStat(IE_STREXTRA) );
7645 	}
7646 
7647 	return 0;
7648 }
7649 
7650 // filter out any damage reduction that is cancelled by high weapon enchantment
7651 // damage reduction (the effect and other uses) is implemented as normal resistance for physical damage, just with extra checks
GetDamageReduction(int resist_stat,ieDword weaponEnchantment) const7652 int Actor::GetDamageReduction(int resist_stat, ieDword weaponEnchantment) const
7653 {
7654 	// this is the total, but some of it may have to be discarded
7655 	int resisted = (signed)GetSafeStat(resist_stat);
7656 	if (!resisted) {
7657 		return 0;
7658 	}
7659 	int remaining = 0;
7660 	int total = 0;
7661 	if (resist_stat == IE_RESISTMISSILE) {
7662 		remaining = fxqueue.SumDamageReduction(fx_missile_damage_reduction_ref, weaponEnchantment, total);
7663 	} else {
7664 		// the usual 3 physical types
7665 		remaining = fxqueue.SumDamageReduction(fx_damage_reduction_ref, weaponEnchantment, total);
7666 	}
7667 
7668 	if (remaining == -1) {
7669 		// no relevant effects were found, so the whole resistance value ignores enchantment checks
7670 		return resisted;
7671 	}
7672 	if (remaining == resisted) {
7673 		Log(COMBAT, "DamageReduction", "Damage resistance (%d) is completely from damage reduction.", resisted);
7674 		return resisted;
7675 	}
7676 	if (remaining == total) {
7677 		Log(COMBAT, "DamageReduction", "No weapon enchantment breach — full damage reduction and resistance used.");
7678 		return resisted;
7679 	} else {
7680 		Log(COMBAT, "DamageReduction", "Ignoring %d of %d damage reduction due to weapon enchantment breach.", total-remaining, total);
7681 		return resisted - (total-remaining);
7682 	}
7683 }
7684 
7685 /*Always call this on the suffering actor */
ModifyDamage(Scriptable * hitter,int & damage,int & resisted,int damagetype)7686 void Actor::ModifyDamage(Scriptable *hitter, int &damage, int &resisted, int damagetype)
7687 {
7688 	Actor *attacker = NULL;
7689 
7690 	if (hitter && hitter->Type==ST_ACTOR) {
7691 		attacker = (Actor *) hitter;
7692 	}
7693 
7694 	//guardian mantle for PST
7695 	if (attacker && (Modified[IE_IMMUNITY]&IMM_GUARDIAN) ) {
7696 		//if the hitter doesn't make the spell save, the mantle works and the damage is 0
7697 		if (!attacker->GetSavingThrow(0,-4) ) {
7698 			damage = 0;
7699 			return;
7700 		}
7701 	}
7702 
7703 	// only check stone skins if damage type is physical or magical
7704 	// DAMAGE_CRUSHING is 0, so we can't AND with it to check for its presence
7705 	if (!(damagetype & ~(DAMAGE_PIERCING|DAMAGE_SLASHING|DAMAGE_MISSILE|DAMAGE_MAGIC))) {
7706 		int stoneskins = Modified[IE_STONESKINS];
7707 		if (stoneskins) {
7708 			//pst style damage soaking from cloak of warding
7709 			damage = fxqueue.DecreaseParam3OfEffect(fx_cloak_ref, damage, 0);
7710 			if (!damage) {
7711 				return;
7712 			}
7713 
7714 			fxqueue.DecreaseParam1OfEffect(fx_stoneskin_ref, 1);
7715 			fxqueue.DecreaseParam1OfEffect(fx_aegis_ref, 1);
7716 
7717 			Modified[IE_STONESKINS]--;
7718 			damage = 0;
7719 			return;
7720 		}
7721 
7722 		stoneskins = GetSafeStat(IE_STONESKINSGOLEM);
7723 		if (stoneskins) {
7724 			fxqueue.DecreaseParam1OfEffect(fx_stoneskin2_ref, 1);
7725 			Modified[IE_STONESKINSGOLEM]--;
7726 			damage = 0;
7727 			return;
7728 		}
7729 	}
7730 
7731 	if (damage>0) {
7732 		// check damage type immunity / resistance / susceptibility
7733 		std::multimap<ieDword, DamageInfoStruct>::iterator it;
7734 		it = core->DamageInfoMap.find(damagetype);
7735 		if (it == core->DamageInfoMap.end()) {
7736 			Log(ERROR, "ModifyDamage", "Unhandled damagetype:%d", damagetype);
7737 		} else if (it->second.resist_stat) {
7738 			// check for bonuses for specific damage types
7739 			if (core->HasFeature(GF_SPECIFIC_DMG_BONUS) && attacker) {
7740 				int bonus = attacker->fxqueue.BonusForParam2(fx_damage_bonus_modifier_ref, it->second.iwd_mod_type);
7741 				if (bonus) {
7742 					resisted -= int (damage * bonus / 100.0);
7743 					Log(COMBAT, "ModifyDamage", "Bonus damage of %d(%+d%%), neto: %d", int(damage * bonus / 100.0), bonus, -resisted);
7744 				}
7745 			}
7746 			// damage type with a resistance stat
7747 			if (third) {
7748 				// flat resistance, eg. 10/- or eg. 5/+2 for physical types
7749 				// for actors we need special care for damage reduction - traps (...) don't have enchanted weapons
7750 				if (attacker && it->second.reduction) {
7751 					WeaponInfo wi;
7752 					attacker->GetWeapon(wi, 0); // FIXME: use a cheaper way to share the weaponEnchantment + this might have been the left hand
7753 					ieDword weaponEnchantment = wi.enchantment;
7754 					// disregard other resistance boni when checking whether to skip reduction
7755 					resisted = GetDamageReduction(it->second.resist_stat, weaponEnchantment);
7756 				} else {
7757 					resisted += (signed)GetSafeStat(it->second.resist_stat);
7758 				}
7759 				damage -= resisted;
7760 			} else {
7761 				int resistance = (signed)GetSafeStat(it->second.resist_stat);
7762 				// avoid buggy data
7763 				if ((unsigned)abs(resistance) > maximum_values[it->second.resist_stat]) {
7764 					resistance = 0;
7765 					Log(DEBUG, "ModifyDamage", "Ignoring bad damage resistance value (%d).", resistance);
7766 				}
7767 				resisted += (int) (damage * resistance/100.0);
7768 				damage -= resisted;
7769 			}
7770 			Log(COMBAT, "ModifyDamage", "Resisted %d of %d at %d%% resistance to %d", resisted, damage+resisted, GetSafeStat(it->second.resist_stat), damagetype);
7771 			// PST and BG1 may actually heal on negative damage
7772 			if (!core->HasFeature(GF_HEAL_ON_100PLUS)) {
7773 				if (damage <= 0) {
7774 					resisted = DR_IMMUNE;
7775 					damage = 0;
7776 				}
7777 			}
7778 		}
7779 	}
7780 
7781 	// don't complain when sarevok is 100% resistant in the cutscene that grants you the slayer form
7782 	if (damage <= 0 && !core->InCutSceneMode()) {
7783 		if (attacker && attacker->InParty) {
7784 			if (core->HasFeedback(FT_COMBAT)) {
7785 				attacker->DisplayStringOrVerbalConstant(STR_WEAPONINEFFECTIVE, VB_TIMMUNE);
7786 			}
7787 			core->Autopause(AP_UNUSABLE, this);
7788 		}
7789 	}
7790 }
7791 
UpdateActorState()7792 void Actor::UpdateActorState()
7793 {
7794 	if (InTrap) {
7795 		area->ClearTrap(this, InTrap-1);
7796 	}
7797 
7798 	Game* game = core->GetGame();
7799 
7800 	//make actor unselectable and unselected when it is not moving
7801 	//dead, petrified, frozen, paralysed or unavailable to player
7802 	if (!ValidTarget(GA_SELECT|GA_NO_ENEMY|GA_NO_NEUTRAL)) {
7803 		game->SelectActor(this, false, SELECT_NORMAL);
7804 	}
7805 
7806 	if (remainingTalkSoundTime > 0) {
7807 		unsigned int currentTick = GetTicks();
7808 		unsigned int diffTime = currentTick - lastTalkTimeCheckAt;
7809 		lastTalkTimeCheckAt = currentTick;
7810 
7811 		if (diffTime >= remainingTalkSoundTime) {
7812 			remainingTalkSoundTime = 0;
7813 		} else {
7814 			remainingTalkSoundTime -= diffTime;
7815 		}
7816 		SetCircleSize();
7817 	}
7818 
7819 	// display pc hitpoints if requested
7820 	// limit the invocation count to save resources (the text is drawn repeatedly anyway)
7821 	ieDword tmp = 0;
7822 	core->GetDictionary()->Lookup("HP Over Head", tmp);
7823 	assert(game->GameTime);
7824 	assert(core->Time.round_size);
7825 	if (tmp && Persistent() && (game->GameTime % (core->Time.round_size/2) == 0)) { // smaller delta to skip fading
7826 		DisplayHeadHPRatio();
7827 	}
7828 
7829 	const auto& anim = currentStance.anim;
7830 	if (attackProjectile) {
7831 		// default so that the projectile fires if we dont have an animation for some reason
7832 		unsigned int frameCount = anim.empty() ? 9 : anim[0].first->GetFrameCount();
7833 		unsigned int currentFrame = anim.empty() ? 8 : anim[0].first->GetCurrentFrameIndex();
7834 
7835 		//IN BG1 and BG2, this is at the ninth frame... (depends on the combat bitmap, which we don't handle yet)
7836 		// however some critters don't have that long animations (eg. squirrel 0xC400)
7837 		if ((frameCount > 8 && currentFrame == 8) || (frameCount <= 8 && currentFrame == frameCount/2)) {
7838 			GetCurrentArea()->AddProjectile(attackProjectile, Pos, LastTarget, false);
7839 			attackProjectile = NULL;
7840 		}
7841 	}
7842 
7843 	if (!anim.empty()) {
7844 		Animation* first = anim[0].first;
7845 
7846 		if (first->endReached) {
7847 			// possible stance change
7848 			if (HandleActorStance()) {
7849 				// restart animation for next time it is needed
7850 				first->endReached = false;
7851 				first->SetPos(0);
7852 
7853 				Animation* firstShadow = currentStance.shadow.empty() ? nullptr : currentStance.shadow[0].first;
7854 				if (firstShadow) {
7855 					firstShadow->endReached = false;
7856 					firstShadow->SetPos(0);
7857 				}
7858 			}
7859 		} else {
7860 			//check if walk sounds need to be played
7861 			//dialog, pause game
7862 			if (!(core->GetGameControl()->GetDialogueFlags()&(DF_IN_DIALOG|DF_FREEZE_SCRIPTS) ) ) {
7863 				//footsteps option set, stance
7864 				if (footsteps && (GetStance() == IE_ANI_WALK)) {
7865 					PlayWalkSound();
7866 				}
7867 			}
7868 		}
7869 	}
7870 
7871 	UpdateModalState(game->GameTime);
7872 }
7873 
UpdateModalState(ieDword gameTime)7874 void Actor::UpdateModalState(ieDword gameTime)
7875 {
7876 	if (Modal.LastApplyTime == gameTime) {
7877 		return;
7878 	}
7879 
7880 	// use the combat round size as the original;  also skald song duration matches it
7881 	int roundFraction = (gameTime - roundTime) % GetAdjustedTime(core->Time.attack_round_size);
7882 
7883 	//actually, iwd2 has autosearch, also, this is useful for dayblindness
7884 	//apply the modal effect about every second (pst and iwds have round sizes that are not multiples of 15)
7885 	// FIXME: split dayblindness out of detect.spl and only run that each tick + simplify this check
7886 	if (InParty && core->HasFeature(GF_AUTOSEARCH_HIDDEN) && (third || ((roundFraction%AI_UPDATE_TIME) == 0))) {
7887 		core->ApplySpell("detect", this, this, 0);
7888 	}
7889 
7890 	ieDword state = Modified[IE_STATE_ID];
7891 
7892 	// each round also re-confuse the actor
7893 	if (!roundFraction) {
7894 		if (BaseStats[IE_CHECKFORBERSERK]) {
7895 			BaseStats[IE_CHECKFORBERSERK]--;
7896 		}
7897 		if (state & STATE_CONFUSED) {
7898 			const char* actionString = NULL;
7899 			int tmp = core->Roll(1,3,0);
7900 			switch (tmp) {
7901 			case 2:
7902 				actionString = "RandomWalk()";
7903 				break;
7904 			case 1:
7905 				// HACK: replace with [0] (ANYONE) once we support that (Nearest matches Sender like in the original)
7906 				if (RAND(0,1)) {
7907 					actionString = "Attack(NearestEnemyOf(Myself))";
7908 				} else {
7909 					actionString = "Attack([PC])";
7910 				}
7911 				break;
7912 			default:
7913 				actionString = "NoAction()";
7914 				break;
7915 			}
7916 			Action *action = GenerateAction( actionString );
7917 			if (action) {
7918 				ReleaseCurrentAction();
7919 				AddActionInFront(action);
7920 				print("Confusion: added %s at %d (%d)", actionString, gameTime-roundTime, roundTime);
7921 			}
7922 			return;
7923 		}
7924 
7925 		if (Modified[IE_CHECKFORBERSERK] && !LastTarget && SeeAnyOne(false, false) ) {
7926 			Action *action = GenerateAction( "Berserk()" );
7927 			if (action) {
7928 				ReleaseCurrentAction();
7929 				AddActionInFront(action);
7930 			}
7931 			return;
7932 		}
7933 	}
7934 
7935 	// this is a HACK, fuzzie can't work out where else to do this for now
7936 	// but we shouldn't be resetting rounds/attacks just because the actor
7937 	// wandered away, the action code should probably be responsible somehow
7938 	// see also line above (search for comment containing UpdateActorState)!
7939 	if (LastTarget && lastattack && lastattack < (gameTime - 1)) {
7940 		Actor *target = area->GetActorByGlobalID(LastTarget);
7941 		if (!target || target->GetStat(IE_STATE_ID)&STATE_DEAD) {
7942 			StopAttack();
7943 		} else {
7944 			Log(COMBAT, "Attack", "(Leaving attack)");
7945 		}
7946 
7947 		lastattack = 0;
7948 	}
7949 
7950 	if (Modal.State == MS_NONE && !Modal.LingeringCount) {
7951 		return;
7952 	}
7953 
7954 	//apply the modal effect on the beginning of each round
7955 	if (roundFraction == 0) {
7956 		// handle lingering modal spells like bardsong in iwd2
7957 		if (Modal.LingeringCount && Modal.LingeringSpell[0]) {
7958 			Modal.LingeringCount--;
7959 			ApplyModal(Modal.LingeringSpell);
7960 		}
7961 		if (Modal.State == MS_NONE) {
7962 			return;
7963 		}
7964 
7965 		// some states and timestop disable modal actions
7966 		// interestingly the original doesn't include STATE_DISABLED, STATE_FROZEN/STATE_PETRIFIED
7967 		if (Immobile() || (state & (STATE_CONFUSED | STATE_DEAD | STATE_HELPLESS | STATE_PANIC | STATE_BERSERK | STATE_SLEEP))) {
7968 			return;
7969 		}
7970 
7971 		//we can set this to 0
7972 		Modal.LastApplyTime = gameTime;
7973 
7974 		if (!Modal.Spell[0]) {
7975 			Log(WARNING, "Actor", "Modal Spell Effect was not set!");
7976 			Modal.Spell[0]='*';
7977 		} else if (Modal.Spell[0]!='*') {
7978 			if (ModalSpellSkillCheck()) {
7979 				ApplyModal(Modal.Spell);
7980 
7981 				// some modals notify each round, some only initially
7982 				bool feedback = ModalStates[Modal.State].repeat_msg || Modal.FirstApply;
7983 				Modal.FirstApply = 0;
7984 				if (InParty && feedback && core->HasFeedback(FT_MISC)) {
7985 					displaymsg->DisplayStringName(ModalStates[Modal.State].entering_str, DMC_WHITE, this, IE_STR_SOUND|IE_STR_SPEECH);
7986 				}
7987 			} else {
7988 				if (InParty && core->HasFeedback(FT_MISC)) {
7989 					displaymsg->DisplayStringName(ModalStates[Modal.State].failed_str, DMC_WHITE, this, IE_STR_SOUND|IE_STR_SPEECH);
7990 				}
7991 				Modal.State = MS_NONE;
7992 			}
7993 		}
7994 
7995 		// shut everyone up, so they don't whine if the actor is on a long hiding-in-shadows recon mission
7996 		core->GetGame()->ResetPartyCommentTimes();
7997 	}
7998 }
7999 
ApplyModal(ieResRef modalSpell)8000 void Actor::ApplyModal(ieResRef modalSpell)
8001 {
8002 	unsigned int aoe = ModalStates[Modal.State].aoe_spell;
8003 	if (aoe == 1) {
8004 		core->ApplySpellPoint(modalSpell, GetCurrentArea(), Pos, this, 0);
8005 	} else if (aoe == 2) {
8006 		// target actors around us manually
8007 		// used for iwd2 songs, as the spells don't use an aoe projectile
8008 		if (!area) return;
8009 		std::vector<Actor *> neighbours = area->GetAllActorsInRadius(Pos, GA_NO_LOS|GA_NO_DEAD|GA_NO_UNSCHEDULED, GetSafeStat(IE_VISUALRANGE)/2);
8010 		std::vector<Actor *>::iterator neighbour;
8011 		for (neighbour = neighbours.begin(); neighbour != neighbours.end(); ++neighbour) {
8012 			core->ApplySpell(modalSpell, *neighbour, this, 0);
8013 		}
8014 	} else {
8015 		core->ApplySpell(modalSpell, this, this, 0);
8016 	}
8017 }
8018 
8019 //idx could be: 0-6, 16-22, 32-38, 48-54
8020 //the colors are stored in 7 dwords
8021 //maybe it would be simpler to store them in 28 bytes (without using stats?)
SetColor(ieDword idx,ieDword grd)8022 void Actor::SetColor( ieDword idx, ieDword grd)
8023 {
8024 	ieByte gradient = (ieByte) (grd&255);
8025 	ieByte index = (ieByte) (idx&15);
8026 	ieByte shift = (ieByte) (idx/16);
8027 	ieDword value;
8028 
8029 	//invalid value, would crash original IE
8030 	if (index>6) {
8031 		return;
8032 	}
8033 
8034 	//Don't modify the modified stats if the colors were locked (for this ai cycle)
8035 	if (anims && anims->lockPalette) {
8036 		return;
8037 	}
8038 
8039 	if (shift == 15) {
8040 		// put gradient in all four bytes of value
8041 		value = gradient;
8042 		value |= (value << 8);
8043 		value |= (value << 16);
8044 		for (index=0;index<7;index++) {
8045 			Modified[IE_COLORS+index] = value;
8046 		}
8047 	} else {
8048 		//invalid value, would crash original IE
8049 		if (shift>3) {
8050 			return;
8051 		}
8052 		shift *= 8;
8053 		value = gradient << shift;
8054 		value |= Modified[IE_COLORS+index] & ~(255<<shift);
8055 		Modified[IE_COLORS+index] = value;
8056 	}
8057 }
8058 
SetColorMod(ieDword location,RGBModifier::Type type,int speed,const Color & color,int phase) const8059 void Actor::SetColorMod(ieDword location, RGBModifier::Type type, int speed,
8060 						const Color &color, int phase) const
8061 {
8062 	CharAnimations* ca = GetAnims();
8063 	if (!ca) return;
8064 
8065 	if (location == 0xff) {
8066 		if (phase && ca->GlobalColorMod.locked) return;
8067 		ca->GlobalColorMod.locked = !phase;
8068 		ca->GlobalColorMod.type = type;
8069 		ca->GlobalColorMod.speed = speed;
8070 		ca->GlobalColorMod.rgb = color;
8071 		if (phase >= 0)
8072 			ca->GlobalColorMod.phase = phase;
8073 		else {
8074 			if (ca->GlobalColorMod.phase > 2*speed)
8075 				ca->GlobalColorMod.phase=0;
8076 		}
8077 		return;
8078 	}
8079 	//00xx0yyy-->000xxyyy
8080 	if (location&0xffffffc8) return; //invalid location
8081 	location = (location &7) | ((location>>1)&0x18);
8082 	if (phase && ca->ColorMods[location].locked) return;
8083 	ca->ColorMods[location].type = type;
8084 	ca->ColorMods[location].speed = speed;
8085 	ca->ColorMods[location].rgb = color;
8086 	if (phase >= 0)
8087 		ca->ColorMods[location].phase = phase;
8088 	else {
8089 		if (ca->ColorMods[location].phase > 2*speed)
8090 			ca->ColorMods[location].phase = 0;
8091 	}
8092 }
8093 
SetLeader(Actor * actor,int xoffset,int yoffset)8094 void Actor::SetLeader(Actor *actor, int xoffset, int yoffset)
8095 {
8096 	LastFollowed = actor->GetGlobalID();
8097 	FollowOffset.x = xoffset;
8098 	FollowOffset.y = yoffset;
8099 }
8100 
8101 //if hp <= 0, it means full healing
Heal(int hp)8102 void Actor::Heal(int hp)
8103 {
8104 	if (hp > 0) {
8105 		SetBase(IE_HITPOINTS, BaseStats[IE_HITPOINTS] + hp);
8106 	} else {
8107 		SetBase(IE_HITPOINTS, Modified[IE_MAXHITPOINTS]);
8108 	}
8109 }
8110 
AddExperience(int exp,int combat)8111 void Actor::AddExperience(int exp, int combat)
8112 {
8113 	int bonus = core->GetWisdomBonus(0, Modified[IE_WIS]);
8114 	int adjustmentPercent = xpadjustments[GameDifficulty - 1];
8115 	// the "Suppress Extra Difficulty Damage" also switches off the XP bonus
8116 	if (combat && (!NoExtraDifficultyDmg || adjustmentPercent < 0)) {
8117 		bonus += adjustmentPercent;
8118 	}
8119 	bonus += GetFavoredPenalties();
8120 
8121 	int xpStat = IE_XP;
8122 
8123 	// decide which particular XP stat to add to (only for TNO's switchable classes)
8124 	Game* game = core->GetGame();
8125 	if (pstflags && this == game->GetPC(0, false)) { // rule only applies to the protagonist
8126 		switch (BaseStats[IE_CLASS]) {
8127 			case 4:
8128 				xpStat = IE_XP_THIEF;
8129 				break;
8130 			case 1:
8131 				xpStat = IE_XP_MAGE;
8132 				break;
8133 			case 2:
8134 			default: //just in case the character was modified
8135 				break;
8136 		}
8137 	}
8138 
8139 	exp = ((exp * (100 + bonus)) / 100) + BaseStats[xpStat];
8140 	if (xpcap != NULL) {
8141 		int classid = GetActiveClass() - 1;
8142 		if (xpcap[classid] > 0 && exp > xpcap[classid]) {
8143 			exp = xpcap[classid];
8144 		}
8145 	}
8146 	SetBase(xpStat, exp);
8147 }
8148 
is_zero(const int & value)8149 static bool is_zero(const int& value) {
8150 	return value == 0;
8151 }
8152 
8153 // for each class pair that is out of level sync for more than 1 level and
8154 // one of them isn't a favored class, incur a 20% xp penalty (cummulative)
GetFavoredPenalties() const8155 int Actor::GetFavoredPenalties() const
8156 {
8157 	if (!third) return 0;
8158 	if (!PCStats) return 0;
8159 
8160 	std::list<int> classLevels(PCStats->ClassLevels);
8161 	classLevels.remove_if(is_zero);
8162 	int classCount = classLevels.size();
8163 	if (classCount == 1) return 0;
8164 
8165 	unsigned int race = GetSubRace();
8166 	int favored = favoredMap[race];
8167 	// shortcuts for special case - "any" favored class
8168 	if (favored == -1 && classCount == 2) return 0;
8169 
8170 	int flevel = -1;
8171 	if (favored != -1) {
8172 		// get the favored class index from ID
8173 		// different for (fe)males for some races, but stored in one value
8174 		if (GetStat(IE_SEX) == 1) {
8175 			favored = favored & 15;
8176 		} else {
8177 			favored = (favored>>8) & 15;
8178 		}
8179 		flevel = GetLevelInClass(favored);
8180 	}
8181 
8182 	classLevels.sort(); // ascending
8183 	if (flevel == -1) {
8184 		// any class - just remove the highest level
8185 		classLevels.erase((classLevels.end())--);
8186 		classCount--;
8187 	} else {
8188 		// remove() kills all elements with the same value, so we have to jump through hoops
8189 		classLevels.remove(flevel);
8190 		int diff = classCount - classLevels.size();
8191 		if (diff == classCount) return 0; // all class were at the same level
8192 		for (int i=1; i < diff; i++) {
8193 			// re-add missing levels (all but one)
8194 			classLevels.push_back(flevel);
8195 		}
8196 		classCount = classLevels.size();
8197 		if (classCount == 1) return 0; // only one class besides favored
8198 	}
8199 
8200 	// finally compare adjacent levels - if they're more than 1 apart
8201 	int penalty = 0;
8202 	std::list<int>::iterator it;
8203 	for (it=++classLevels.begin(); it != classLevels.end(); it++) {
8204 		int level1 = *(--it);
8205 		int level2 = *(++it);
8206 		if (level2 - level1 > 1) penalty++;
8207 	}
8208 
8209 	return -20*penalty;
8210 }
8211 
CalculateExperience(int type,int level) const8212 int Actor::CalculateExperience(int type, int level) const
8213 {
8214 	if (type>=xpbonustypes) {
8215 		return 0;
8216 	}
8217 	unsigned int l = (unsigned int) (level - 1);
8218 
8219 	if (l>=(unsigned int) xpbonuslevels) {
8220 		l=xpbonuslevels-1;
8221 	}
8222 	return xpbonus[type*xpbonuslevels+l];
8223 }
8224 
Schedule(ieDword gametime,bool checkhide) const8225 bool Actor::Schedule(ieDword gametime, bool checkhide) const
8226 {
8227 	if (checkhide) {
8228 		if (!(InternalFlags&IF_VISIBLE) ) {
8229 			return false;
8230 		}
8231 	}
8232 
8233 	//check for schedule
8234 	return GemRB::Schedule(appearance, gametime);
8235 }
8236 
8237 
SetInTrap(ieDword setreset)8238 void Actor::SetInTrap(ieDword setreset)
8239 {
8240 	InTrap = setreset;
8241 	if (setreset) {
8242 		InternalFlags |= IF_INTRAP;
8243 	} else {
8244 		InternalFlags &= ~IF_INTRAP;
8245 	}
8246 }
8247 
SetRunFlags(ieDword flags)8248 void Actor::SetRunFlags(ieDword flags)
8249 {
8250 	InternalFlags &= ~IF_RUNFLAGS;
8251 	InternalFlags |= (flags & IF_RUNFLAGS);
8252 }
8253 
NewPath()8254 void Actor::NewPath()
8255 {
8256 	if (Destination == Pos) return;
8257 	// WalkTo's and FindPath's first argument is passed by reference
8258 	// And we don't want to modify Destination so we use a temporary
8259 	Point tmp = Destination;
8260 	if (GetPathTries() > MAX_PATH_TRIES) {
8261 		ClearPath(true);
8262 		ResetPathTries();
8263 		return;
8264 	}
8265 	WalkTo(tmp, InternalFlags, pathfindingDistance);
8266 	if (!GetPath()) {
8267 		IncrementPathTries();
8268 	}
8269 }
8270 
8271 
WalkTo(const Point & Des,ieDword flags,int MinDistance)8272 void Actor::WalkTo(const Point &Des, ieDword flags, int MinDistance)
8273 {
8274 	ResetPathTries();
8275 	if (InternalFlags & IF_REALLYDIED || walkScale == 0) {
8276 		return;
8277 	}
8278 	SetRunFlags(flags);
8279 	ResetCommentTime();
8280 	Movable::WalkTo(Des, MinDistance);
8281 }
8282 
DrawActorSprite(const Point & p,BlitFlags flags,const std::vector<AnimationPart> & animParts,const Color & tint) const8283 void Actor::DrawActorSprite(const Point& p, BlitFlags flags,
8284 							const std::vector<AnimationPart>& animParts, const Color& tint) const
8285 {
8286 	if (tint.a == 0) return;
8287 
8288 	if (!anims->lockPalette) {
8289 		flags |= BlitFlags::COLOR_MOD;
8290 	}
8291 	flags |= BlitFlags::ALPHA_MOD;
8292 
8293 	Video* video = core->GetVideoDriver();
8294 
8295 	for (const auto& part : animParts) {
8296 		Animation* anim = part.first;
8297 		PaletteHolder palette = part.second;
8298 
8299 		Holder<Sprite2D> currentFrame = anim->CurrentFrame();
8300 		if (currentFrame) {
8301 			if (TranslucentShadows) {
8302 				ieByte tmpa = palette->col[1].a;
8303 				palette->col[1].a /= 2;
8304 				video->BlitGameSpriteWithPalette(currentFrame, palette, p, flags, tint);
8305 				palette->col[1].a = tmpa;
8306 			} else {
8307 				video->BlitGameSpriteWithPalette(currentFrame, palette, p, flags, tint);
8308 			}
8309 		}
8310 	}
8311 }
8312 
8313 
8314 static const int OrientdX[16] = { 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9, 10, 9, 7, 4 };
8315 static const int OrientdY[16] = { 10, 9, 7, 4, 0, -4, -7, -9, -10, -9, -7, -4, 0, 4, 7, 9 };
8316 static const unsigned int MirrorImageLocation[8] = { 4, 12, 8, 0, 6, 14, 10, 2 };
8317 static const unsigned int MirrorImageZOrder[8] = { 2, 4, 6, 0, 1, 7, 5, 3 };
8318 
HibernateIfAble()8319 bool Actor::HibernateIfAble()
8320 {
8321 	//finding an excuse why we don't hybernate the actor
8322 	if (Modified[IE_ENABLEOFFSCREENAI])
8323 		return false;
8324 	if (LastTarget) //currently attacking someone
8325 		return false;
8326 	if (!LastTargetPos.isempty()) //currently casting at the ground
8327 		return false;
8328 	if (LastSpellTarget) //currently casting at someone
8329 		return false;
8330 	if (InternalFlags&IF_JUSTDIED) // didn't have a chance to run a script
8331 		return false;
8332 	if (CurrentAction)
8333 		return false;
8334 	if (third && Modified[IE_MC_FLAGS]&MC_IGNORE_INHIBIT_AI)
8335 		return false;
8336 	if (InMove())
8337 		return false;
8338 	if (GetNextAction())
8339 		return false;
8340 	if (GetWait()) //would never stop waiting
8341 		return false;
8342 
8343 	InternalFlags |= IF_IDLE;
8344 	return true;
8345 }
8346 
AdvanceAnimations()8347 bool Actor::AdvanceAnimations()
8348 {
8349 	if (!anims) {
8350 		return false;
8351 	}
8352 
8353 	anims->PulseRGBModifiers();
8354 
8355 	ClearCurrentStanceAnims();
8356 
8357 	unsigned char stanceID = GetStance();
8358 	unsigned char face = GetNextFace();
8359 	Animation** stanceAnim = anims->GetAnimation(stanceID, face);
8360 
8361 	if (stanceAnim == nullptr) {
8362 		return false;
8363 	}
8364 
8365 	Animation** shadows = anims->GetShadowAnimation(stanceID, face);
8366 
8367 	const auto count = anims->GetTotalPartCount();
8368 	const auto zOrder = anims->GetZOrder(face);
8369 
8370 	// display current frames in the right order
8371 	for (int part = 0; part < count; ++part) {
8372 		int partnum = part;
8373 		if (zOrder) partnum = zOrder[part];
8374 		Animation* anim = stanceAnim[partnum];
8375 		if (anim) {
8376 			currentStance.anim.emplace_back(anim, anims->GetPartPalette(partnum));
8377 		}
8378 
8379 		if (shadows) {
8380 			Animation* anim = shadows[partnum];
8381 			if (anim) {
8382 				currentStance.shadow.emplace_back(anim, anims->GetShadowPalette());
8383 			}
8384 		}
8385 	}
8386 
8387 	Animation* first = currentStance.anim[0].first;
8388 	Animation* firstShadow = currentStance.shadow.empty() ? nullptr : currentStance.shadow[0].first;
8389 
8390 	// advance first (main) animation by one frame (in sync)
8391 	if (Immobile()) {
8392 		// update animation, continue last-displayed frame
8393 		first->LastFrame();
8394 		if (firstShadow) {
8395 			firstShadow->LastFrame();
8396 		}
8397 	} else {
8398 		// update animation, maybe advance a frame (if enough time has passed)
8399 		first->NextFrame();
8400 		if (firstShadow) {
8401 			firstShadow->NextFrame();
8402 		}
8403 	}
8404 
8405 	// update all other animation parts, in sync with the first part
8406 	auto it = currentStance.anim.begin() + 1;
8407 	for (; it != currentStance.anim.end(); ++it) {
8408 		it->first->GetSyncedNextFrame(first);
8409 	}
8410 
8411 	it = currentStance.shadow.begin();
8412 	if (it != currentStance.shadow.end()) {
8413 		for (++it; it != currentStance.shadow.end(); ++it) {
8414 			it->first->GetSyncedNextFrame(firstShadow);
8415 		}
8416 	}
8417 
8418 	return true;
8419 }
8420 
IsDead() const8421 bool Actor::IsDead() const
8422 {
8423 	return InternalFlags & IF_STOPATTACK;
8424 }
8425 
ShouldDrawCircle() const8426 bool Actor::ShouldDrawCircle() const
8427 {
8428 	if (Modified[IE_NOCIRCLE]) {
8429 		return false;
8430 	}
8431 
8432 	int State = Modified[IE_STATE_ID];
8433 
8434 	if ((State&STATE_DEAD) || (InternalFlags&IF_REALLYDIED)) {
8435 		return false;
8436 	}
8437 
8438 	//adjust invisibility for enemies
8439 	if (Modified[IE_EA]>EA_GOODCUTOFF) {
8440 		if (State&state_invisible) {
8441 			return false;
8442 		}
8443 	}
8444 
8445 	const GameControl* gc = core->GetGameControl();
8446 	if (gc->GetScreenFlags()&SF_CUTSCENE) {
8447 		// ground circles are not drawn in cutscenes
8448 		// except for the speaker
8449 		if (gc->dialoghandler->IsTarget(this) == false) {
8450 			return false;
8451 		}
8452 	}
8453 
8454 	bool drawcircle = true; // we always show circle/target on pause
8455 	if (!(gc->GetDialogueFlags() & DF_FREEZE_SCRIPTS)) {
8456 		// check marker feedback level
8457 		ieDword markerfeedback = 4;
8458 		core->GetDictionary()->Lookup("GUI Feedback Level", markerfeedback);
8459 		if (Selected) {
8460 			// selected creature
8461 			drawcircle = markerfeedback >= 2;
8462 		} else if (IsPC()) {
8463 			// selectable
8464 			drawcircle = markerfeedback >= 3;
8465 		} else if (Modified[IE_EA] >= EA_EVILCUTOFF) {
8466 			// hostile
8467 			drawcircle = markerfeedback >= 4;
8468 		} else {
8469 			// all
8470 			drawcircle = markerfeedback >= 5;
8471 		}
8472 	}
8473 
8474 	return drawcircle;
8475 }
8476 
ShouldDrawReticle() const8477 bool Actor::ShouldDrawReticle() const
8478 {
8479 	if (ShouldDrawCircle()){
8480 		return (!(InternalFlags&IF_NORETICLE) && Modified[IE_EA] <= EA_CONTROLLABLE && Destination != Pos);
8481 	}
8482 	return false;
8483 }
8484 
HasBodyHeat() const8485 bool Actor::HasBodyHeat() const
8486 {
8487 	if (Modified[IE_STATE_ID]&(STATE_DEAD|STATE_FROZEN|STATE_PETRIFIED) ) return false;
8488 	if (GetAnims()->GetFlags()&AV_NO_BODY_HEAT) return false;
8489 	return true;
8490 }
8491 
GetElevation() const8492 uint8_t Actor::GetElevation() const
8493 {
8494 	uint8_t height = area ? area->HeightMap->GetAt(Pos.x / 16, Pos.y / 12) : 0;
8495 	if (height > 15) {
8496 		// there are 8bpp lightmaps (eg, bg2's AR1300) and fuzzie
8497 		// cannot work out how they work, so here is an incorrect
8498 		// hack (probably). please fix!
8499 
8500 		// FIXME: is it possible that this is signed instead of unsigned?
8501 		// or just the 4 least significant bits? sign magnitude?
8502 		height = 15;
8503 	}
8504 	return height;
8505 }
8506 
UpdateDrawingState()8507 bool Actor::UpdateDrawingState()
8508 {
8509 	for (auto it = vfxQueue.cbegin(); it != vfxQueue.cend();) {
8510 		ScriptedAnimation* vvc = *it;
8511 		if ((vvc->SequenceFlags & IE_VVC_STATIC) == 0) {
8512 			vvc->Pos = Pos;
8513 		}
8514 
8515 		bool endReached = vvc->UpdateDrawingState(GetOrientation());
8516 		if (endReached) {
8517 			vfxDict.erase(vfxDict.find(vvc->ResName)); // make sure to delete only one element
8518 			it = vfxQueue.erase(it);
8519 			delete vvc;
8520 			continue;
8521 		}
8522 
8523 		if (!vvc->active) {
8524 			vvc->SetPhase(P_RELEASE);
8525 		}
8526 
8527 		++it;
8528 	}
8529 
8530 	if (!AdvanceAnimations()) {
8531 		return false;
8532 	}
8533 
8534 	UpdateDrawingRegion();
8535 	return true;
8536 }
8537 
UpdateDrawingRegion()8538 void Actor::UpdateDrawingRegion()
8539 {
8540 	Region box(Pos, Size());
8541 
8542 	auto ExpandBoxForAnimationParts = [&box, this](const std::vector<AnimationPart>& parts) {
8543 		for (const auto& part : parts) {
8544 			Animation* anim = part.first;
8545 			Holder<Sprite2D> animframe = anim->CurrentFrame();
8546 			assert(animframe);
8547 			Region partBBox = animframe->Frame;
8548 			partBBox.x = Pos.x - partBBox.x;
8549 			partBBox.y = Pos.y - partBBox.y;
8550 			box.ExpandToRegion(partBBox);
8551 			assert(box.RectInside(partBBox));
8552 		}
8553 	};
8554 
8555 	ExpandBoxForAnimationParts(currentStance.anim);
8556 	ExpandBoxForAnimationParts(currentStance.shadow);
8557 
8558 	box.y -= GetElevation();
8559 
8560 	// BBox is the the box containing the actor and all its equipment, but nothing else
8561 	SetBBox(box);
8562 
8563 	int mirrorimages = Modified[IE_MIRRORIMAGES];
8564 	for (int i = 0; i < mirrorimages; ++i) {
8565 		int dir = MirrorImageLocation[i];
8566 
8567 		Region mirrorBox = BBox;
8568 		mirrorBox.x += 3 * OrientdX[dir];
8569 		mirrorBox.y += 3 * OrientdY[dir];
8570 
8571 		box.ExpandToRegion(mirrorBox);
8572 	}
8573 
8574 	if (Modified[IE_STATE_ID] & STATE_BLUR) {
8575 		int face = GetOrientation();
8576 		int blurx = (OrientdX[face] * (int)Modified[IE_MOVEMENTRATE])/20;
8577 		int blury = (OrientdY[face] * (int)Modified[IE_MOVEMENTRATE])/20;
8578 
8579 		Region blurBox = BBox;
8580 		blurBox.x -= blurx * 3;
8581 		blurBox.y -= blury * 3;
8582 
8583 		box.ExpandToRegion(blurBox);
8584 	}
8585 
8586 	for (const auto& vvc : vfxQueue) {
8587 		Region r = vvc->DrawingRegion();
8588 		if (vvc->SequenceFlags & IE_VVC_HEIGHT) r.y -= BBox.h;
8589 		box.ExpandToRegion(r);
8590 		assert(r.w <= box.w && r.h <= box.h);
8591 	}
8592 
8593 	// drawingRegion is the the box containing all gfx attached to the actor
8594 	drawingRegion = box;
8595 }
8596 
DrawingRegion() const8597 Region Actor::DrawingRegion() const
8598 {
8599 	return drawingRegion;
8600 }
8601 
Draw(const Region & vp,Color baseTint,Color tint,BlitFlags flags) const8602 void Actor::Draw(const Region& vp, Color baseTint, Color tint, BlitFlags flags) const
8603 {
8604 	// if an actor isn't visible, should we still draw video cells?
8605 	// let us assume not, for now..
8606 	if (!(InternalFlags & IF_VISIBLE)) {
8607 		return;
8608 	}
8609 
8610 	//iwd has this flag saved in the creature
8611 	if (Modified[IE_AVATARREMOVAL]) {
8612 		return;
8613 	}
8614 
8615 	if (!DrawingRegion().IntersectsRegion(vp)) {
8616 		return;
8617 	}
8618 
8619 	//explored or visibilitymap (bird animations are visible in fog)
8620 	//0 means opaque
8621 	uint8_t trans = std::min<int>(Modified[IE_TRANSLUCENT], 255);
8622 
8623 	int State = Modified[IE_STATE_ID];
8624 
8625 	//adjust invisibility for enemies
8626 	if (Modified[IE_EA] > EA_GOODCUTOFF && (State & state_invisible)) {
8627 		trans = 255;
8628 	} else if (State & STATE_BLUR) {
8629 		//can't move this, because there is permanent blur state where
8630 		//there is no effect (just state bit)
8631 		trans = 128;
8632 	}
8633 
8634 	tint.a = 255 - trans;
8635 
8636 	//draw videocells under the actor
8637 	auto it = vfxQueue.cbegin();
8638 	for (; it != vfxQueue.cend(); ++it) {
8639 		ScriptedAnimation* vvc = *it;
8640 		if (vvc->YOffset >= 0) {
8641 			break;
8642 		}
8643 		vvc->Draw(vp, baseTint, BBox.h, flags & (BLIT_STENCIL_MASK | BlitFlags::ALPHA_MOD));
8644 	}
8645 
8646 	if (ShouldDrawCircle()) {
8647 		DrawCircle(vp.Origin());
8648 	}
8649 
8650 	if (!currentStance.anim.empty()) {
8651 		unsigned char face = GetOrientation();
8652 		// Drawing the actor:
8653 		// * mirror images:
8654 		//     Drawn without transparency, unless fully invisible.
8655 		//     Order: W, E, N, S, NW, SE, NE, SW
8656 		// * blurred copies (3 of them)
8657 		//     Drawn with transparency.
8658 		//     distance between copies depends on IE_MOVEMENTRATE
8659 		//     TODO: actually, the direction is the real movement direction,
8660 		//	not the (rounded) direction given Face
8661 		// * actor itself
8662 		//
8663 		//comments by Avenger:
8664 		// currently we don't have a real direction, but the orientation field
8665 		// could be used with higher granularity. When we need the face value
8666 		// it could be divided so it will become a 0-15 number.
8667 		//
8668 
8669 		if (AppearanceFlags & APP_HALFTRANS) flags |= BlitFlags::HALFTRANS;
8670 
8671 		Point drawPos = Pos - vp.Origin();
8672 		drawPos.y -= GetElevation();
8673 
8674 		// mirror images behind the actor
8675 		for (int i = 0; i < 4; ++i) {
8676 			unsigned int m = MirrorImageZOrder[i];
8677 			if (m < Modified[IE_MIRRORIMAGES]) {
8678 				int dir = MirrorImageLocation[m];
8679 				int icx = drawPos.x + 3 * OrientdX[dir];
8680 				int icy = drawPos.y + 3 * OrientdY[dir];
8681 				Point iPos(icx, icy);
8682 				// FIXME: I don't know if GetBlocked() is good enough
8683 				// consider the possibility the mirror image is behind a wall (walls.second)
8684 				// GetBlocked might be false, but we still should not draw the image
8685 				// maybe the mirror image coordinates can never be beyond the width of a wall?
8686 				if ((area->GetBlockedNavmap(iPos + vp.Origin()) & (PathMapFlags::PASSABLE | PathMapFlags::ACTOR)) != PathMapFlags::IMPASSABLE) {
8687 					DrawActorSprite(iPos, flags, currentStance.anim, tint);
8688 				}
8689 			}
8690 		}
8691 
8692 		// blur sprites behind the actor
8693 		int blurdx = (OrientdX[face]*(int)Modified[IE_MOVEMENTRATE])/20;
8694 		int blurdy = (OrientdY[face]*(int)Modified[IE_MOVEMENTRATE])/20;
8695 		Point blurPos = drawPos;
8696 		if (State & STATE_BLUR) {
8697 			if (face < 4 || face >= 12) {
8698 				blurPos -= Point(4 * blurdx, 4 * blurdy);
8699 				for (int i = 0; i < 3; ++i) {
8700 					blurPos += Point(blurdx, blurdy);
8701 					// FIXME: I don't think we ought to draw blurs that are behind a wall that the actor is in front of
8702 					DrawActorSprite(blurPos, flags, currentStance.anim, tint);
8703 				}
8704 			}
8705 		}
8706 
8707 		if (!currentStance.shadow.empty()) {
8708 			Game* game = core->GetGame();
8709 			// infravision, independent of light map and global light
8710 			if (HasBodyHeat() &&
8711 				game->PartyHasInfravision() &&
8712 				!game->IsDay() &&
8713 				(area->AreaType & AT_OUTDOOR) && !(area->AreaFlags & AF_DREAM)) {
8714 				Color irTint = Color(255, 120, 120, tint.a);
8715 
8716 				/* IWD2: infravision is white, not red. */
8717 				if(core->HasFeature(GF_3ED_RULES)) {
8718 					irTint = Color(255, 255, 255, tint.a);
8719 				}
8720 
8721 				DrawActorSprite(drawPos, flags, currentStance.shadow, irTint);
8722 			} else {
8723 				DrawActorSprite(drawPos, flags, currentStance.shadow, tint);
8724 			}
8725 		}
8726 
8727 		// actor itself
8728 		DrawActorSprite(drawPos, flags, currentStance.anim, tint);
8729 
8730 		// blur sprites in front of the actor
8731 		if (State & STATE_BLUR) {
8732 			if (face >= 4 && face < 12) {
8733 				for (int i = 0; i < 3; ++i) {
8734 					blurPos -= Point(blurdx, blurdy);
8735 					DrawActorSprite(blurPos, flags, currentStance.anim, tint);
8736 				}
8737 			}
8738 		}
8739 
8740 		// mirror images in front of the actor
8741 		for (int i = 4; i < 8; ++i) {
8742 			unsigned int m = MirrorImageZOrder[i];
8743 			if (m < Modified[IE_MIRRORIMAGES]) {
8744 				int dir = MirrorImageLocation[m];
8745 				int icx = drawPos.x + 3 * OrientdX[dir];
8746 				int icy = drawPos.y + 3 * OrientdY[dir];
8747 				Point iPos(icx, icy);
8748 				// FIXME: I don't know if GetBlocked() is good enough
8749 				// consider the possibility the mirror image is in front of a wall (walls.first)
8750 				// GetBlocked might be false, but we still should not draw the image
8751 				// maybe the mirror image coordinates can never be beyond the width of a wall?
8752 				if ((area->GetBlockedNavmap(iPos + vp.Origin()) & (PathMapFlags::PASSABLE | PathMapFlags::ACTOR)) != PathMapFlags::IMPASSABLE) {
8753 					DrawActorSprite(iPos, flags, currentStance.anim, tint);
8754 				}
8755 			}
8756 		}
8757 	}
8758 
8759 	//draw videocells over the actor
8760 	for (; it != vfxQueue.cend(); ++it) {
8761 		ScriptedAnimation* vvc = *it;
8762 		vvc->Draw(vp, baseTint, BBox.h, flags & (BLIT_STENCIL_MASK | BlitFlags::ALPHA_MOD));
8763 	}
8764 }
8765 
8766 /* Handling automatic stance changes */
HandleActorStance()8767 bool Actor::HandleActorStance()
8768 {
8769 	CharAnimations* ca = GetAnims();
8770 	int StanceID = GetStance();
8771 
8772 	if (ca->autoSwitchOnEnd) {
8773 		SetStance(ca->nextStanceID);
8774 		ca->autoSwitchOnEnd = false;
8775 		return true;
8776 	}
8777 	int x = RAND(0, 25);
8778 	if ((StanceID==IE_ANI_AWAKE) && !x ) {
8779 		SetStance( IE_ANI_HEAD_TURN );
8780 		return true;
8781 	}
8782 	// added CurrentAction as part of blocking action fixes
8783 	if ((StanceID==IE_ANI_READY) && !CurrentAction && !GetNextAction()) {
8784 		SetStance( IE_ANI_AWAKE );
8785 		return true;
8786 	}
8787 	if (StanceID == IE_ANI_ATTACK || StanceID == IE_ANI_ATTACK_JAB ||
8788 		StanceID == IE_ANI_ATTACK_SLASH || StanceID == IE_ANI_ATTACK_BACKSLASH ||
8789 		StanceID == IE_ANI_SHOOT)
8790 	{
8791 		SetStance( AttackStance );
8792 		return true;
8793 	}
8794 
8795 	return false;
8796 }
8797 
GetSoundFromFile(ieResRef & Sound,unsigned int index) const8798 bool Actor::GetSoundFromFile(ieResRef &Sound, unsigned int index) const
8799 {
8800 	// only dying ignores the incapacity to vocalize
8801 	if (index != VB_DIE) {
8802 		if (Modified[IE_STATE_ID] & STATE_CANTLISTEN) return false;
8803 	}
8804 
8805 	if (core->HasFeature(GF_RESDATA_INI)) {
8806 		return GetSoundFromINI(Sound, index);
8807 	} else {
8808 		return GetSoundFrom2DA(Sound, index);
8809 	}
8810 }
8811 
GetSoundFrom2DA(ieResRef & Sound,unsigned int index) const8812 bool Actor::GetSoundFrom2DA(ieResRef &Sound, unsigned int index) const
8813 {
8814 	if (!anims) return false;
8815 
8816 	AutoTable tab(anims->ResRef);
8817 	if (!tab) return false;
8818 
8819 	switch (index) {
8820 		// TODO: research whether this VB should be split into 5x VB_BATTLE_CRY and 4x VB_ATTACK (like in NI)
8821 		// wasn't played if the weapon wasn't of type misc (so just the swing sound if any)
8822 		case VB_ATTACK:
8823 			index = 0;
8824 			break;
8825 		case VB_DAMAGE:
8826 			index = 8;
8827 			break;
8828 		case VB_DIE:
8829 			index = 10;
8830 			break;
8831 		//TODO: one day we should implement verbal constant groups
8832 		case VB_DIALOG:
8833 		case VB_SELECT:
8834 		case VB_SELECT+1:
8835 		case VB_SELECT+2:
8836 			index = 36; // Selection (yes, the row names are inconsistently capitalized)
8837 			break;
8838 		// entries without VB equivalents
8839 		case 100+IE_ANI_SHOOT:
8840 			index = 16; // SHOOT
8841 			break;
8842 		// these three supposedly never worked, at least not in bg2 (https://www.gibberlings3.net/forums/topic/19034-animation-2da-files)
8843 		case 100+IE_ANI_ATTACK_SLASH:
8844 			index = 22; // ATTACK_SLASH
8845 			break;
8846 		case 100+IE_ANI_ATTACK_BACKSLASH:
8847 			index = 24; // ATTACK_BACKSLASH
8848 			break;
8849 		case 100+IE_ANI_ATTACK_JAB:
8850 			index = 26; // ATTACK_JAB
8851 			break;
8852 		// TODO: research whether this entry is ever different from ATTACK and what's the difference in use
8853 		case 200:
8854 			index = 34; // Battle_Cry
8855 			break;
8856 		default:
8857 			Log(WARNING, "Actor", "TODO:Cannot determine 2DA rowcount for index: %d", index);
8858 			return false;
8859 	}
8860 	Log(MESSAGE, "Actor", "Getting sound 2da %.8s entry: %s",
8861 		anims->ResRef, tab->GetRowName(index) );
8862 	int col = core->Roll(1,tab->GetColumnCount(index),-1);
8863 	strnlwrcpy(Sound, tab->QueryField (index, col), 8);
8864 	return true;
8865 }
8866 
8867 //Get the monster sound from a global .ini file.
8868 //It is ResData.ini in PST and Sounds.ini in IWD/HoW
GetSoundFromINI(ieResRef & Sound,unsigned int index) const8869 bool Actor::GetSoundFromINI(ieResRef &Sound, unsigned int index) const
8870 {
8871 	const char *resource = "";
8872 	char section[12];
8873 	unsigned int animid=BaseStats[IE_ANIMATION_ID];
8874 	if(core->HasFeature(GF_ONE_BYTE_ANIMID)) {
8875 		animid&=0xff;
8876 	}
8877 	snprintf(section,10,"%d", animid);
8878 
8879 	/* TODO: pst also has these, but we currently ignore them:
8880 	 * Another form of randomization for attack animations (random pick of Attack1-3 [if defined] and this is its sound)
8881 	 *    1x at2sound for each at1sound
8882 	 *    2x at3sound
8883 	 * Others:
8884 	 *   33x cf1sound (stance (combat) fidget)
8885 	 *    2x ms1sound (misc; both hammers hitting metal; ambient sounds for idle animations? Likely doesn't fit here)
8886 	 *   19x sf1sound (stand (normal) fidget)
8887 	 *
8888 	 * TODO: iwd:
8889 	 *   att2-att4
8890 	 *   fall
8891 	 *   fidget
8892 	 */
8893 	switch(index) {
8894 		case VB_ATTACK:
8895 			resource = core->GetResDataINI()->GetKeyAsString(section, IWDSound?"att1":"at1sound","");
8896 			break;
8897 		case VB_DAMAGE:
8898 			resource = core->GetResDataINI()->GetKeyAsString(section, IWDSound?"damage":"hitsound","");
8899 			break;
8900 		case VB_DIE:
8901 			resource = core->GetResDataINI()->GetKeyAsString(section, IWDSound?"death":"dfbsound","");
8902 			break;
8903 		case VB_SELECT:
8904 			//this isn't in PST, apparently
8905 			if (IWDSound) {
8906 				resource = core->GetResDataINI()->GetKeyAsString(section, "selected","");
8907 			}
8908 			break;
8909 		// entries without VB equivalents
8910 		case 100+IE_ANI_SHOOT:
8911 		case 100+IE_ANI_ATTACK_SLASH:
8912 		case 100+IE_ANI_ATTACK_BACKSLASH:
8913 		case 100+IE_ANI_ATTACK_JAB:
8914 			// FIXME: complete guess
8915 			resource = core->GetResDataINI()->GetKeyAsString(section, IWDSound?"att2":"at2sound","");
8916 			break;
8917 		case 200: // battle cry
8918 			if (IWDSound) {
8919 				resource = core->GetResDataINI()->GetKeyAsString(section, "btlcry", "");
8920 			}
8921 			break;
8922 	}
8923 
8924 	int count = CountElements(resource, ',');
8925 	int slot = RAND(0, count - 1);
8926 	while (slot--) {
8927 		while (*resource && *resource != ',') resource++;
8928 		if (*resource == ',') resource++;
8929 	}
8930 	size_t len = strcspn(resource, ",");
8931 	assert(len < sizeof(ieResRef));
8932 	strlcpy(Sound, resource, len + 1);
8933 
8934 	return true;
8935 }
8936 
ResolveStringConstant(ieResRef & Sound,unsigned int index) const8937 void Actor::ResolveStringConstant(ieResRef& Sound, unsigned int index) const
8938 {
8939 	if (PCStats && PCStats->SoundSet[0]) {
8940 		//resolving soundset (bg1/bg2 style)
8941 		size_t len;
8942 
8943 		// handle nonstandard bg1 "default" soundsets first
8944 		if (!strnicmp(PCStats->SoundSet, "main", 4)) {
8945 			static const char *suffixes[] = { "03", "08", "09", "10", "11", "17", "18", "19", "20", "21", "22", "38", "39" };
8946 			static unsigned int VB2Suffix[] = { 9, 6, 7, 8, 20, 26, 27, 28, 32, 33, 34, 18, 19 };
8947 			bool found = false;
8948 
8949 			for (int i = 0; i < 13; i++) {
8950 				if (VB2Suffix[i] == index) {
8951 					index = i;
8952 					found = true;
8953 					break;
8954 				}
8955 			}
8956 			if (!found) {
8957 				Sound[0] = 0;
8958 				return;
8959 			}
8960 
8961 			snprintf(Sound, sizeof(ieResRef), "%.5s%.2s", PCStats->SoundSet, suffixes[index]);
8962 			return;
8963 		} else if (csound[index]) {
8964 			len = snprintf(Sound, sizeof(ieResRef), "%s%c", PCStats->SoundSet, csound[index]);
8965 			if (len > sizeof(ieResRef)) Log(ERROR, "Actor", "Actor %s has too long soundset name: %s", LongName, PCStats->SoundSet);
8966 			return;
8967 		}
8968 
8969 		//icewind style
8970 		len = snprintf(Sound, sizeof(ieResRef), "%s%02d", PCStats->SoundSet, VCMap[index]);
8971 		if (len > sizeof(ieResRef)) Log(ERROR, "Actor", "Actor %s has too long soundset name: %s", LongName, PCStats->SoundSet);
8972 		return;
8973 	}
8974 
8975 	Sound[0]=0;
8976 
8977 	if (core->HasFeature(GF_RESDATA_INI)) {
8978 		GetSoundFromINI(Sound, index);
8979 	} else {
8980 		GetSoundFrom2DA(Sound, index);
8981 	}
8982 
8983 	//Empty resrefs
8984 	if (Sound[0]=='*') Sound[0]=0;
8985 	else if(!strncmp(Sound,"nosound",8) ) Sound[0]=0;
8986 }
8987 
SetActionButtonRow(ActionButtonRow & ar)8988 void Actor::SetActionButtonRow(ActionButtonRow &ar)
8989 {
8990 	for(int i=0;i<GUIBT_COUNT;i++) {
8991 		PCStats->QSlots[i] = ar[i];
8992 	}
8993 	if (QslotTranslation) dumpQSlots();
8994 }
8995 
GetActionButtonRow(ActionButtonRow & ar)8996 void Actor::GetActionButtonRow(ActionButtonRow &ar)
8997 {
8998 	//at this point, we need the stats for the action button row
8999 	//only controlled creatures (and pcs) get it
9000 	CreateStats();
9001 	InitButtons(GetActiveClass(), false);
9002 	for(int i=0;i<GUIBT_COUNT;i++) {
9003 		ar[i] = IWD2GemrbQslot(i);
9004 	}
9005 }
9006 
Gemrb2IWD2Qslot(ieByte actslot,int slotindex) const9007 int Actor::Gemrb2IWD2Qslot(ieByte actslot, int slotindex) const
9008 {
9009 	ieByte tmp = actslot;
9010 	if (QslotTranslation && slotindex>2) {
9011 		if (tmp > ACT_IWDQSONG) { //quick songs
9012 			tmp = 110 + tmp%10;
9013 		} else if (tmp > ACT_IWDQSPEC) { //quick abilities
9014 			tmp = 90 + tmp%10;
9015 		} else if (tmp > ACT_IWDQITEM) { //quick items
9016 			tmp = 80 + tmp%10;
9017 		} else if (tmp > ACT_IWDQSPELL) { //quick spells
9018 			tmp = 70 + tmp%10;
9019 		} else if (tmp > ACT_BARD) { //spellbooks
9020 			tmp = 50 + tmp%10;
9021 		} else if (tmp >= 32) { // here be dragons
9022 			Log(ERROR, "Actor", "Bad slot index passed to SetActionButtonRow!");
9023 		} else {
9024 			tmp = gemrb2iwd[tmp];
9025 		}
9026 	}
9027 	return tmp;
9028 }
9029 
IWD2GemrbQslot(int slotindex) const9030 int Actor::IWD2GemrbQslot (int slotindex) const
9031 {
9032 	ieByte tmp = PCStats->QSlots[slotindex];
9033 	//the first three buttons are hardcoded in gemrb
9034 	//don't mess with them
9035 	if (QslotTranslation && slotindex>2) {
9036 		if (tmp>=110) { //quick songs
9037 			tmp = ACT_IWDQSONG + tmp%10;
9038 		} else if (tmp>=90) { //quick abilities
9039 			tmp = ACT_IWDQSPEC + tmp%10;
9040 		} else if (tmp>=80) { //quick items
9041 			tmp = ACT_IWDQITEM + tmp%10;
9042 		} else if (tmp>=70) { //quick spells
9043 			tmp = ACT_IWDQSPELL + tmp%10;
9044 		} else if (tmp>=50) { //spellbooks
9045 			tmp = ACT_BARD + tmp%10;
9046 		} else if (tmp>=32) { // here be dragons
9047 			Log(ERROR, "Actor", "Bad slot index passed to IWD2GemrbQslot!");
9048 		} else {
9049 			tmp = iwd2gemrb[tmp];
9050 		}
9051 	}
9052 	return tmp;
9053 }
9054 
9055 // debug function; only works on pc classes
dumpQSlots() const9056 void Actor::dumpQSlots() const
9057 {
9058 	ActionButtonRow r;
9059 	memcpy(&r, GUIBTDefaults + GetActiveClass(), sizeof(ActionButtonRow));
9060 	StringBuffer buffer, buffer2, buffer3;
9061 
9062 	buffer.append("Current  default: ");
9063 	buffer2.append("IWD2gem  default: ");
9064 	buffer3.append("gem2IWD2 default: ");
9065 	for(int i=0; i<GUIBT_COUNT; i++) {
9066 		ieByte tmp = r[i];
9067 		buffer.appendFormatted("%3d ", tmp);
9068 		buffer2.appendFormatted("%3d ", IWD2GemrbQslot(tmp));
9069 		buffer3.appendFormatted("%3d ", Gemrb2IWD2Qslot(tmp, i));
9070 	}
9071 	buffer.appendFormatted("(class: %d)", GetStat(IE_CLASS));
9072 	Log(DEBUG, "Actor", buffer);
9073 //	Log(DEBUG, "Actor", buffer2);
9074 //	Log(DEBUG, "Actor", buffer3);
9075 
9076 	buffer.clear();
9077 	buffer2.clear();
9078 	buffer3.clear();
9079 	buffer.append("Current  QSlots:  ");
9080 	buffer2.append("IWD2gem  QSlots:  ");
9081 	buffer3.append("gem2IWD2 QSlots:  ");
9082 	for(int i=0; i<GUIBT_COUNT; i++) {
9083 		ieByte tmp = PCStats->QSlots[i];
9084 		buffer.appendFormatted("%3d ", tmp);
9085 		buffer2.appendFormatted("%3d ", IWD2GemrbQslot(tmp));
9086 		buffer3.appendFormatted("%3d ", Gemrb2IWD2Qslot(tmp, i));
9087 	}
9088 	Log(DEBUG, "Actor", buffer);
9089 	Log(DEBUG, "Actor", buffer2);
9090 	Log(DEBUG, "Actor", buffer3);
9091 }
9092 
SetPortrait(const char * ResRef,int Which)9093 void Actor::SetPortrait(const char* ResRef, int Which)
9094 {
9095 	int i;
9096 
9097 	if (ResRef == NULL) {
9098 		return;
9099 	}
9100 	if (InParty) {
9101 		core->SetEventFlag(EF_PORTRAIT);
9102 	}
9103 
9104 	if(Which!=1) {
9105 		CopyResRef( SmallPortrait, ResRef );
9106 	}
9107 	if(Which!=2) {
9108 		CopyResRef( LargePortrait, ResRef );
9109 	}
9110 	if(!Which) {
9111 		for (i = 0; i < 8 && ResRef[i]; i++) {};
9112 		if (SmallPortrait[i-1] != 'S' && SmallPortrait[i-1] != 's') {
9113 			SmallPortrait[i] = 'S';
9114 		}
9115 		if (LargePortrait[i-1] != 'M' && LargePortrait[i-1] != 'm') {
9116 			LargePortrait[i] = 'M';
9117 		}
9118 	}
9119 }
9120 
SetSoundFolder(const char * soundset)9121 void Actor::SetSoundFolder(const char *soundset)
9122 {
9123 	if (core->HasFeature(GF_SOUNDFOLDERS)) {
9124 		char filepath[_MAX_PATH];
9125 
9126 		strnlwrcpy(PCStats->SoundFolder, soundset, SOUNDFOLDERSIZE-1);
9127 		PathJoin(filepath, core->GamePath, "sounds", PCStats->SoundFolder, NULL);
9128 
9129 		DirectoryIterator dirIt(filepath);
9130 		dirIt.SetFilterPredicate(new EndsWithFilter("01"));
9131 		dirIt.SetFlags(DirectoryIterator::Directories);
9132 		if (dirIt) {
9133 			do {
9134 				const char* name = dirIt.GetName();
9135 				const char* end = strchr(name, '.');
9136 				if (end != NULL) {
9137 					// need to truncate the "01" from the name
9138 					strnlwrcpy(PCStats->SoundSet, name, int(end - 2 - name));
9139 					break;
9140 				}
9141 			} while (++dirIt);
9142 		}
9143 	} else {
9144 		CopyResRef(PCStats->SoundSet, soundset);
9145 		PCStats->SoundFolder[0]=0;
9146 	}
9147 }
9148 
GetSoundFolder(char * soundset,int full,ieResRef overrideSet) const9149 void Actor::GetSoundFolder(char *soundset, int full, ieResRef overrideSet) const
9150 {
9151 	ieResRef set;
9152 	if (overrideSet) {
9153 		CopyResRef(set, overrideSet);
9154 	} else {
9155 		CopyResRef(set, PCStats->SoundSet);
9156 	}
9157 
9158 	if (core->HasFeature(GF_SOUNDFOLDERS)) {
9159 		strnlwrcpy(soundset, PCStats->SoundFolder, 32);
9160 		if (full) {
9161 			strcat(soundset,"/");
9162 			strncat(soundset, set, 9);
9163 		}
9164 	}
9165 	else {
9166 		strnlwrcpy(soundset, set, 8);
9167 	}
9168 }
9169 
HasVVCCell(const ResRef & resource) const9170 bool Actor::HasVVCCell(const ResRef &resource) const
9171 {
9172 	return GetVVCCells(resource).first != vfxDict.end();
9173 }
9174 
VVCSort(const ScriptedAnimation * lhs,const ScriptedAnimation * rhs)9175 bool VVCSort(const ScriptedAnimation* lhs, const ScriptedAnimation* rhs)
9176 {
9177 	return lhs->YOffset < rhs->YOffset;
9178 }
9179 
9180 std::pair<vvcDict::const_iterator, vvcDict::const_iterator>
GetVVCCells(const ResRef & resource) const9181 Actor::GetVVCCells(const ResRef &resource) const
9182 {
9183 	return vfxDict.equal_range(resource);
9184 }
9185 
RemoveVVCells(const ResRef & resource)9186 void Actor::RemoveVVCells(const ResRef &resource)
9187 {
9188 	auto range = vfxDict.equal_range(resource);
9189 	if (range.first != vfxDict.end()) {
9190 		for (auto it = range.first; it != range.second; ++it) {
9191 			ScriptedAnimation *vvc = it->second;
9192 			vvc->SetPhase(P_RELEASE);
9193 		}
9194 	}
9195 }
9196 
9197 //this is a faster version of hasvvccell, because it knows where to look
9198 //for the overlay, it also returns the vvc for further manipulation
9199 //use this for the seven eyes overlay
FindOverlay(int index) const9200 ScriptedAnimation *Actor::FindOverlay(int index) const
9201 {
9202 	if (index >= OVERLAY_COUNT) return NULL;
9203 
9204 	auto it = vfxDict.find(hc_overlays[index]);
9205 	return (it != vfxDict.end()) ? it->second : nullptr;
9206 }
9207 
AddVVCell(ScriptedAnimation * vvc)9208 void Actor::AddVVCell(ScriptedAnimation* vvc)
9209 {
9210 	assert(vvc);
9211 	vvc->Pos = Pos;
9212 	vfxDict.insert(std::make_pair(vvc->ResName, vvc));
9213 	vfxQueue.insert(vvc);
9214 	assert(vfxDict.size() == vfxQueue.size());
9215 }
9216 
9217 //returns restored spell level
RestoreSpellLevel(ieDword maxlevel,ieDword type)9218 int Actor::RestoreSpellLevel(ieDword maxlevel, ieDword type)
9219 {
9220 	int typemask;
9221 
9222 	switch (type) {
9223 		case 0: //allow only mage
9224 			typemask = ~2;
9225 			break;
9226 		case 1: //allow only cleric
9227 			typemask = ~1;
9228 			break;
9229 		default:
9230 			//allow any (including innates)
9231 			typemask = ~0;
9232 	}
9233 	for (int i=maxlevel;i>0;i--) {
9234 		CREMemorizedSpell *cms = spellbook.FindUnchargedSpell(typemask, maxlevel);
9235 		if (cms) {
9236 			spellbook.ChargeSpell(cms);
9237 			return i;
9238 		}
9239 	}
9240 	return 0;
9241 }
9242 //replenishes spells, cures fatigue
Rest(int hours)9243 void Actor::Rest(int hours)
9244 {
9245 	if (hours < 8) {
9246 		// partial (interrupted) rest does not affect fatigue
9247 		//do remove effects
9248 		int remaining = hours*10;
9249 		NewStat (IE_INTOXICATION, -remaining, MOD_ADDITIVE);
9250 		//restore hours*10 spell levels
9251 		//rememorization starts with the lower spell levels?
9252 		inventory.ChargeAllItems (remaining);
9253 		int level = 1;
9254 		int memorizedSpell = 0;
9255 		while (remaining > 0 && level < 16)
9256 		{
9257 			memorizedSpell = RestoreSpellLevel(level, -1);
9258 			remaining -= memorizedSpell;
9259 			if (memorizedSpell == 0)
9260 			{
9261 				level += 1;
9262 			}
9263 		}
9264 	} else {
9265 		TicksLastRested = LastFatigueCheck = core->GetGame()->GameTime;
9266 		SetBase (IE_FATIGUE, 0);
9267 		SetBase (IE_INTOXICATION, 0);
9268 		inventory.ChargeAllItems (0);
9269 		spellbook.ChargeAllSpells ();
9270 	}
9271 	ResetCommentTime();
9272 }
9273 
9274 //returns the actual slot from the quickslot
GetQuickSlot(int slot) const9275 int Actor::GetQuickSlot(int slot) const
9276 {
9277 	assert(slot<8);
9278 	if (inventory.HasItemInSlot("",inventory.GetMagicSlot())) {
9279 		return inventory.GetMagicSlot();
9280 	}
9281 	if (!PCStats) {
9282 		return slot+inventory.GetWeaponSlot();
9283 	}
9284 	return PCStats->QuickWeaponSlots[slot];
9285 }
9286 
9287 //marks the quickslot as equipped
SetEquippedQuickSlot(int slot,int header)9288 int Actor::SetEquippedQuickSlot(int slot, int header)
9289 {
9290 	if (!PCStats) {
9291 		if (header<0) header=0;
9292 		inventory.SetEquippedSlot(slot, header);
9293 		return 0;
9294 	}
9295 
9296 
9297 	if ((slot<0) || (slot == IW_NO_EQUIPPED) ) {
9298 		if (slot == IW_NO_EQUIPPED) {
9299 			slot = inventory.GetFistSlot();
9300 		}
9301 		int i;
9302 		for(i=0;i<MAX_QUICKWEAPONSLOT;i++) {
9303 			if(slot+inventory.GetWeaponSlot()==PCStats->QuickWeaponSlots[i]) {
9304 				slot = i;
9305 				break;
9306 			}
9307 		}
9308 		//if it is the fist slot and not currently used, then set it up
9309 		if (i==MAX_QUICKWEAPONSLOT) {
9310 			inventory.SetEquippedSlot(IW_NO_EQUIPPED, 0);
9311 			return 0;
9312 		}
9313 	}
9314 
9315 	assert(slot<MAX_QUICKWEAPONSLOT);
9316 	if (header==-1) {
9317 		header = PCStats->QuickWeaponHeaders[slot];
9318 	}
9319 	else {
9320 		PCStats->QuickWeaponHeaders[slot]=header;
9321 	}
9322 	slot = inventory.GetWeaponQuickSlot(PCStats->QuickWeaponSlots[slot]);
9323 	if (inventory.SetEquippedSlot(slot, header)) {
9324 		return 0;
9325 	}
9326 	return STR_MAGICWEAPON;
9327 }
9328 
9329 //if target is a non living scriptable, then we simply shoot for its position
9330 //the fx should get a NULL target, and handle itself by using the position
9331 //(shouldn't crash when target is NULL)
UseItemPoint(ieDword slot,ieDword header,const Point & target,ieDword flags)9332 bool Actor::UseItemPoint(ieDword slot, ieDword header, const Point &target, ieDword flags)
9333 {
9334 	CREItem *item = inventory.GetSlotItem(slot);
9335 	if (!item)
9336 		return false;
9337 	// HACK: disable use when stunned (remove if stunned/petrified/etc actors stop running scripts)
9338 	if (Immobile()) {
9339 		return false;
9340 	}
9341 
9342 	// only one potion/wand per round
9343 	if (!(flags&UI_NOAURA) && AuraPolluted()) {
9344 		return false;
9345 	}
9346 
9347 	ieResRef tmpresref;
9348 	strnuprcpy(tmpresref, item->ItemResRef, sizeof(ieResRef)-1);
9349 
9350 	Item *itm = gamedata->GetItem(tmpresref, true);
9351 	if (!itm) {
9352 		Log(WARNING, "Actor", "Invalid quick slot item: %s!", tmpresref);
9353 		return false; //quick item slot contains invalid item resref
9354 	}
9355 	//item is depleted for today
9356 	if(itm->UseCharge(item->Usages, header, false)==CHG_DAY) {
9357 		return false;
9358 	}
9359 
9360 	Projectile *pro = itm->GetProjectile(this, header, target, slot, flags&UI_MISS);
9361 	ChargeItem(slot, header, item, itm, flags&UI_SILENT, !(flags&UI_NOCHARGE));
9362 	gamedata->FreeItem(itm,tmpresref, false);
9363 	ResetCommentTime();
9364 	if (pro) {
9365 		pro->SetCaster(GetGlobalID(), ITEM_CASTERLEVEL);
9366 		GetCurrentArea()->AddProjectile(pro, Pos, target);
9367 		return true;
9368 	}
9369 	return false;
9370 }
9371 
ModifyWeaponDamage(WeaponInfo & wi,Actor * target,int & damage,bool & critical)9372 void Actor::ModifyWeaponDamage(WeaponInfo &wi, Actor *target, int &damage, bool &critical)
9373 {
9374 	//Calculate weapon based damage bonuses (strength bonus, dexterity bonus, backstab)
9375 	bool weaponImmunity = target->fxqueue.WeaponImmunity(wi.enchantment, wi.itemflags);
9376 	int multiplier = Modified[IE_BACKSTABDAMAGEMULTIPLIER];
9377 	int extraDamage = 0; // damage unaffected by the critical multiplier
9378 
9379 	if (third) {
9380 		// 3ed sneak attack
9381 		if (multiplier > 0) {
9382 			extraDamage = GetSneakAttackDamage(target, wi, multiplier, weaponImmunity);
9383 		}
9384 	} else if (multiplier > 1) {
9385 		// aDnD backstabbing
9386 		damage = GetBackstabDamage(target, wi, multiplier, damage);
9387 	}
9388 
9389 	damage += WeaponDamageBonus(wi);
9390 
9391 	if (weaponImmunity) {
9392 		//'my weapon has no effect'
9393 		damage = 0;
9394 		critical = false;
9395 		if (InParty) {
9396 			if (core->HasFeedback(FT_COMBAT)) DisplayStringOrVerbalConstant(STR_WEAPONINEFFECTIVE, VB_TIMMUNE);
9397 			core->Autopause(AP_UNUSABLE, this);
9398 		}
9399 		return;
9400 	}
9401 
9402 	//critical protection a la PST
9403 	if (pstflags && (target->Modified[IE_STATE_ID] & (ieDword) STATE_CRIT_PROT )) {
9404 		critical = false;
9405 	}
9406 
9407 	if (critical) {
9408 		if (target->inventory.ProvidesCriticalAversion()) {
9409 			//critical hit is averted by helmet
9410 			if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantStringName(STR_NO_CRITICAL, DMC_WHITE, target);
9411 			critical = false;
9412 		} else {
9413 			VerbalConstant(VB_CRITHIT);
9414 			//multiply the damage with the critical multiplier
9415 			damage *= wi.critmulti;
9416 
9417 			// check if critical hit needs a screenshake
9418 			if (crit_hit_scr_shake && (InParty || target->InParty) ) {
9419 				core->timer.SetScreenShake(Point(10, -10), AI_UPDATE_TIME);
9420 			}
9421 
9422 			//apply the dirty fighting spell
9423 			if (HasFeat(FEAT_DIRTY_FIGHTING) ) {
9424 				core->ApplySpell(resref_dirty, target, this, multiplier);
9425 			}
9426 		}
9427 	}
9428 	// add damage that is unaffected by criticals
9429 	damage += extraDamage;
9430 }
9431 
GetSneakAttackDamage(Actor * target,WeaponInfo & wi,int & multiplier,bool weaponImmunity)9432 int Actor::GetSneakAttackDamage(Actor *target, WeaponInfo &wi, int &multiplier, bool weaponImmunity) {
9433 	ieDword always = Modified[IE_ALWAYSBACKSTAB];
9434 	bool invisible = Modified[IE_STATE_ID] & state_invisible;
9435 	int sneakAttackDamage = 0;
9436 
9437 	// TODO: should be rate limited (web says to once per 4 rounds?)
9438 	// rogue is hidden or flanking OR the target is immobile (sleep ... stun)
9439 	// or one of the stat overrides is set (unconfirmed for iwd2!)
9440 	if (invisible || always || target->Immobile() || IsBehind(target)) {
9441 		bool dodgy = target->GetStat(IE_UNCANNY_DODGE) & 0x200;
9442 		// if true, we need to be 4+ levels higher to still manage a sneak attack
9443 		if (dodgy) {
9444 			if (GetStat(IE_CLASSLEVELSUM) >= target->GetStat(IE_CLASSLEVELSUM) + 4) {
9445 				dodgy = false;
9446 			}
9447 		}
9448 		if (target->Modified[IE_DISABLEBACKSTAB] || weaponImmunity || dodgy) {
9449 			if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantString (STR_BACKSTAB_FAIL, DMC_WHITE);
9450 			wi.backstabbing = false;
9451 		} else {
9452 			if (wi.backstabbing) {
9453 				// first check for feats that change the sneak dice
9454 				// special effects on hit for arterial strike (-1d6) and hamstring (-2d6)
9455 				// both are available at level 10+ (5d6), so it's safe to decrease multiplier without checking
9456 				if (BackstabResRef[0]!='*') {
9457 					if (stricmp(BackstabResRef, resref_arterial)) {
9458 						// ~Sneak attack for %d inflicts hamstring damage (Slowed)~
9459 						multiplier -= 2;
9460 						sneakAttackDamage = LuckyRoll(multiplier, 6, 0, 0, target);
9461 						displaymsg->DisplayRollStringName(39829, DMC_LIGHTGREY, this, sneakAttackDamage);
9462 					} else {
9463 						// ~Sneak attack for %d scores arterial strike (Inflicts bleeding wound)~
9464 						multiplier--;
9465 						sneakAttackDamage = LuckyRoll(multiplier, 6, 0, 0, target);
9466 						displaymsg->DisplayRollStringName(39828, DMC_LIGHTGREY, this, sneakAttackDamage);
9467 					}
9468 					core->ApplySpell(BackstabResRef, target, this, multiplier);
9469 					//do we need this?
9470 					BackstabResRef[0]='*';
9471 					if (HasFeat(FEAT_CRIPPLING_STRIKE) ) {
9472 						core->ApplySpell(resref_cripstr, target, this, multiplier);
9473 					}
9474 				}
9475 				if (!sneakAttackDamage) {
9476 					sneakAttackDamage = LuckyRoll(multiplier, 6, 0, 0, target);
9477 					// ~Sneak Attack for %d~
9478 					//displaymsg->DisplayRollStringName(25053, DMC_LIGHTGREY, this, extraDamage);
9479 					if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantStringValue (STR_BACKSTAB, DMC_WHITE, sneakAttackDamage);
9480 				}
9481 			} else {
9482 				// weapon is unsuitable for sneak attack
9483 				if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantString (STR_BACKSTAB_BAD, DMC_WHITE);
9484 			}
9485 		}
9486 	}
9487 	return sneakAttackDamage;
9488 }
9489 
GetBackstabDamage(Actor * target,WeaponInfo & wi,int multiplier,int damage) const9490 int Actor::GetBackstabDamage(Actor *target, WeaponInfo &wi, int multiplier, int damage) const {
9491 	ieDword always = Modified[IE_ALWAYSBACKSTAB];
9492 	bool invisible = Modified[IE_STATE_ID] & state_invisible;
9493 	int backstabDamage = damage;
9494 
9495 	//ToBEx compatibility in the ALWAYSBACKSTAB field:
9496 	//0 Normal conditions (attacker must be invisible, attacker must be in 90-degree arc behind victim)
9497 	//1 Ignore invisible requirement and positioning requirement
9498 	//2 Ignore invisible requirement only
9499 	//4 Ignore positioning requirement only
9500 	if (!invisible && !(always&0x3)) {
9501 		return backstabDamage;
9502 	}
9503 
9504 	if (!(core->HasFeature(GF_PROPER_BACKSTAB) && !IsBehind(target)) || (always&0x5)) {
9505 		if (target->Modified[IE_DISABLEBACKSTAB]) {
9506 			// The backstab seems to have failed
9507 			if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantString (STR_BACKSTAB_FAIL, DMC_WHITE);
9508 			wi.backstabbing = false;
9509 		} else {
9510 			if (wi.backstabbing) {
9511 				backstabDamage = multiplier * damage;
9512 				// display a simple message instead of hardcoding multiplier names
9513 				if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantStringValue (STR_BACKSTAB, DMC_WHITE, multiplier);
9514 			} else {
9515 				// weapon is unsuitable for backstab
9516 				if (core->HasFeedback(FT_COMBAT)) displaymsg->DisplayConstantString (STR_BACKSTAB_BAD, DMC_WHITE);
9517 			}
9518 		}
9519 	}
9520 
9521 	return backstabDamage;
9522 }
9523 
UseItem(ieDword slot,ieDword header,Scriptable * target,ieDword flags,int damage)9524 bool Actor::UseItem(ieDword slot, ieDword header, Scriptable* target, ieDword flags, int damage)
9525 {
9526 	if (target->Type!=ST_ACTOR) {
9527 		return UseItemPoint(slot, header, target->Pos, flags);
9528 	}
9529 	// HACK: disable use when stunned (remove if stunned/petrified/etc actors stop running scripts)
9530 	if (Immobile()) {
9531 		return false;
9532 	}
9533 
9534 	// only one potion per round; skip for our internal attack projectile
9535 	if (!(flags&UI_NOAURA) && AuraPolluted()) {
9536 		return false;
9537 	}
9538 
9539 	Actor *tar = (Actor *) target;
9540 	CREItem *item = inventory.GetSlotItem(slot);
9541 	if (!item)
9542 		return false;
9543 
9544 	ieResRef tmpresref;
9545 	strnuprcpy(tmpresref, item->ItemResRef, sizeof(ieResRef)-1);
9546 
9547 	Item *itm = gamedata->GetItem(tmpresref);
9548 	if (!itm) {
9549 		Log(WARNING, "Actor", "Invalid quick slot item: %s!", tmpresref);
9550 		return false; //quick item slot contains invalid item resref
9551 	}
9552 	//item is depleted for today
9553 	if (itm->UseCharge(item->Usages, header, false)==CHG_DAY) {
9554 		return false;
9555 	}
9556 
9557 	Projectile *pro = itm->GetProjectile(this, header, target->Pos, slot, flags&UI_MISS);
9558 	ChargeItem(slot, header, item, itm, flags&UI_SILENT, !(flags&UI_NOCHARGE));
9559 	gamedata->FreeItem(itm,tmpresref, false);
9560 	ResetCommentTime();
9561 	if (pro) {
9562 		pro->SetCaster(GetGlobalID(), ITEM_CASTERLEVEL);
9563 		if (flags & UI_FAKE) {
9564 			delete pro;
9565 		} else if (((int)header < 0) && !(flags&UI_MISS)) { //using a weapon
9566 			bool ranged = header == (ieDword)-2;
9567 			ITMExtHeader *which = itm->GetWeaponHeader(ranged);
9568 			Effect* AttackEffect = EffectQueue::CreateEffect(fx_damage_ref, damage, (weapon_damagetype[which->DamageType])<<16, FX_DURATION_INSTANT_LIMITED);
9569 			AttackEffect->Projectile = which->ProjectileAnimation;
9570 			AttackEffect->Target = FX_TARGET_PRESET;
9571 			AttackEffect->Parameter3 = 1;
9572 			if (pstflags) {
9573 				AttackEffect->IsVariable = GetCriticalType();
9574 			} else {
9575 				AttackEffect->IsVariable = flags&UI_CRITICAL;
9576 			}
9577 			pro->GetEffects()->AddEffect(AttackEffect, true);
9578 			if (ranged) {
9579 				fxqueue.AddWeaponEffects(pro->GetEffects(), fx_ranged_ref);
9580 			} else {
9581 				// TODO: EEs add a bit to fx_melee for only applying with monk fists: parameter2 of 4 (no other value supported)
9582 				fxqueue.AddWeaponEffects(pro->GetEffects(), fx_melee_ref);
9583 				// ignore timestop
9584 				pro->TFlags |= PTF_TIMELESS;
9585 			}
9586 			//AddEffect created a copy, the original needs to be scrapped
9587 			delete AttackEffect;
9588 			attackProjectile = pro;
9589 		} else //launch it now as we are not attacking
9590 			GetCurrentArea()->AddProjectile(pro, Pos, tar->GetGlobalID(), false);
9591 		return true;
9592 	}
9593 	return false;
9594 }
9595 
ChargeItem(ieDword slot,ieDword header,CREItem * item,Item * itm,bool silent,bool expend)9596 void Actor::ChargeItem(ieDword slot, ieDword header, CREItem *item, Item *itm, bool silent, bool expend)
9597 {
9598 	if (!itm) {
9599 		item = inventory.GetSlotItem(slot);
9600 		if (!item)
9601 			return;
9602 		itm = gamedata->GetItem(item->ItemResRef, true);
9603 	}
9604 	if (!itm) {
9605 		Log(WARNING, "Actor", "Invalid quick slot item: %s!", item->ItemResRef);
9606 		return; //quick item slot contains invalid item resref
9607 	}
9608 
9609 	if (IsSelected()) {
9610 		core->SetEventFlag( EF_ACTION );
9611 	}
9612 
9613 	if (!silent) {
9614 		ieByte stance = AttackStance;
9615 		for (int i=0;i<animcount;i++) {
9616 			if ( strnicmp(item->ItemResRef, itemanim[i].itemname, 8) == 0) {
9617 				stance = itemanim[i].animation;
9618 			}
9619 		}
9620 		if (stance!=0xff) {
9621 			SetStance(stance);
9622 			//play only one cycle of animations
9623 
9624 			// this was crashing for fuzzie due to NULL anims
9625 			if (anims) {
9626 				anims->nextStanceID=IE_ANI_READY;
9627 				anims->autoSwitchOnEnd=true;
9628 			}
9629 		}
9630 	}
9631 
9632 	switch(itm->UseCharge(item->Usages, header, expend)) {
9633 		case CHG_DAY:
9634 			break;
9635 		case CHG_BREAK: //both
9636 			if (!silent) {
9637 				core->PlaySound(DS_ITEM_GONE, SFX_CHAN_GUI);
9638 			}
9639 			//fall through
9640 		case CHG_NOSOUND: //remove item
9641 			inventory.BreakItemSlot(slot);
9642 			break;
9643 		default: //don't do anything
9644 			break;
9645 	}
9646 }
9647 
IsReverseToHit()9648 int Actor::IsReverseToHit()
9649 {
9650 	return ReverseToHit;
9651 }
9652 
InitButtons(ieDword cls,bool forced)9653 void Actor::InitButtons(ieDword cls, bool forced)
9654 {
9655 	if (!PCStats) {
9656 		return;
9657 	}
9658 	if ( (PCStats->QSlots[0]!=0xff) && !forced) {
9659 		return;
9660 	}
9661 
9662 	ActionButtonRow myrow;
9663 	if (cls >= (ieDword) classcount) {
9664 		memcpy(&myrow, &DefaultButtons, sizeof(ActionButtonRow));
9665 		for (int i=0;i<extraslots;i++) {
9666 			if (cls==OtherGUIButtons[i].clss) {
9667 				memcpy(&myrow, &OtherGUIButtons[i].buttons, sizeof(ActionButtonRow));
9668 				break;
9669 			}
9670 		}
9671 	} else {
9672 		memcpy(&myrow, GUIBTDefaults+cls, sizeof(ActionButtonRow));
9673 	}
9674 	SetActionButtonRow(myrow);
9675 }
9676 
SetFeat(unsigned int feat,int mode)9677 void Actor::SetFeat(unsigned int feat, int mode)
9678 {
9679 	if (feat>=MAX_FEATS) {
9680 		return;
9681 	}
9682 	ieDword mask = 1<<(feat&31);
9683 	ieDword idx = feat>>5;
9684 
9685 	SetBits(BaseStats[IE_FEATS1+idx], mask, mode);
9686 }
9687 
SetFeatValue(unsigned int feat,int value,bool init)9688 void Actor::SetFeatValue(unsigned int feat, int value, bool init)
9689 {
9690 	if (feat>=MAX_FEATS) {
9691 		return;
9692 	}
9693 
9694 	//handle maximum and minimum values
9695 	if (value<0) value = 0;
9696 	else if (value>featmax[feat]) value = featmax[feat];
9697 
9698 	if (value) {
9699 		SetFeat(feat, OP_OR);
9700 		if (featstats[feat]) SetBase(featstats[feat], value);
9701 	} else {
9702 		SetFeat(feat, OP_NAND);
9703 		if (featstats[feat]) SetBase(featstats[feat], 0);
9704 	}
9705 
9706 	if (init) {
9707 		 ApplyFeats();
9708 	}
9709 }
9710 
ClearCurrentStanceAnims()9711 void Actor::ClearCurrentStanceAnims()
9712 {
9713 	currentStance.anim.clear();
9714 	currentStance.shadow.clear();
9715 }
9716 
SetUsedWeapon(const char (& AnimationType)[2],ieWord * MeleeAnimation,int wt)9717 void Actor::SetUsedWeapon(const char (&AnimationType)[2], ieWord* MeleeAnimation, int wt)
9718 {
9719 	memcpy(WeaponRef, AnimationType, sizeof(WeaponRef) );
9720 	if (wt != -1) WeaponType = wt;
9721 	if (!anims)
9722 		return;
9723 
9724 	anims->SetWeaponRef(AnimationType);
9725 	anims->SetWeaponType(WeaponType);
9726 	ClearCurrentStanceAnims();
9727 	SetAttackMoveChances(MeleeAnimation);
9728 	if (InParty) {
9729 		//update the paperdoll weapon animation
9730 		core->SetEventFlag(EF_UPDATEANIM);
9731 	}
9732 	WeaponInfo wi;
9733 	ITMExtHeader *header = GetWeapon(wi);
9734 
9735 	if(header && ((header->AttackType == ITEM_AT_BOW) ||
9736 		(header->AttackType == ITEM_AT_PROJECTILE && header->ProjectileQualifier))) {
9737 		ITMExtHeader* projHeader = GetRangedWeapon(wi);
9738 		if (projHeader->ProjectileQualifier == 0) return; /* no ammo yet? */
9739 		AttackStance = IE_ANI_SHOOT;
9740 		anims->SetRangedType(projHeader->ProjectileQualifier-1);
9741 		//bows ARE one handed, from an anim POV at least
9742 		anims->SetWeaponType(IE_ANI_WEAPON_1H);
9743 		return;
9744 	}
9745 	if(header && (header->AttackType == ITEM_AT_PROJECTILE)) {
9746 		AttackStance = IE_ANI_ATTACK_SLASH; //That's it!!
9747 		return;
9748 	}
9749 	AttackStance = IE_ANI_ATTACK;
9750 }
9751 
SetUsedShield(const char (& AnimationType)[2],int wt)9752 void Actor::SetUsedShield(const char (&AnimationType)[2], int wt)
9753 {
9754 	memcpy(ShieldRef, AnimationType, sizeof(ShieldRef) );
9755 	if (wt != -1) WeaponType = wt;
9756 	if (AnimationType[0] == ' ' || AnimationType[0] == 0)
9757 		if (WeaponType == IE_ANI_WEAPON_2W)
9758 			WeaponType = IE_ANI_WEAPON_1H;
9759 
9760 	if (!anims)
9761 		return;
9762 
9763 	anims->SetOffhandRef(AnimationType);
9764 	anims->SetWeaponType(WeaponType);
9765 	ClearCurrentStanceAnims();
9766 	if (InParty) {
9767 		//update the paperdoll weapon animation
9768 		core->SetEventFlag(EF_UPDATEANIM);
9769 	}
9770 }
9771 
SetUsedHelmet(const char (& AnimationType)[2])9772 void Actor::SetUsedHelmet(const char (&AnimationType)[2])
9773 {
9774 	memcpy(HelmetRef, AnimationType, sizeof(HelmetRef) );
9775 	if (!anims)
9776 		return;
9777 
9778 	anims->SetHelmetRef(AnimationType);
9779 	ClearCurrentStanceAnims();
9780 	if (InParty) {
9781 		//update the paperdoll weapon animation
9782 		core->SetEventFlag(EF_UPDATEANIM);
9783 	}
9784 }
9785 
9786 // initializes the fist data the first time it is called
SetupFistData() const9787 void Actor::SetupFistData() const
9788 {
9789 	if (FistRows >= 0) {
9790 		return;
9791 	}
9792 
9793 	FistRows = 0;
9794 	AutoTable fist("fistweap");
9795 	if (fist) {
9796 		strnlwrcpy(DefaultFist, fist->QueryDefault(), 8);
9797 		FistRows = fist->GetRowCount();
9798 		fistres = new FistResType[FistRows];
9799 		fistresclass = new int[FistRows];
9800 		for (int i = 0; i < FistRows; i++) {
9801 			int maxcol = fist->GetColumnCount(i) - 1;
9802 			for (int cols = 0; cols < MAX_LEVEL; cols++) {
9803 				strnlwrcpy(fistres[i][cols], fist->QueryField(i, cols > maxcol ? maxcol : cols), 8);
9804 			}
9805 			fistresclass[i] = atoi(fist->GetRowName(i));
9806 		}
9807 	}
9808 
9809 }
9810 
SetupFist()9811 void Actor::SetupFist()
9812 {
9813 	int slot = core->QuerySlot( 0 );
9814 	assert (core->QuerySlotEffects(slot)==SLOT_EFFECT_FIST);
9815 	int row = GetBase(fiststat);
9816 	int col = GetXPLevel(false);
9817 
9818 	if (col>MAX_LEVEL) col=MAX_LEVEL;
9819 	if (col<1) col=1;
9820 
9821 	SetupFistData();
9822 
9823 	const char *ItemResRef = DefaultFist;
9824 	for (int i = 0;i<FistRows;i++) {
9825 		if (fistresclass[i] == row) {
9826 			ItemResRef = fistres[i][col];
9827 		}
9828 	}
9829 	CREItem *currentFist = inventory.GetSlotItem(slot);
9830 	if (!currentFist || stricmp(currentFist->ItemResRef, ItemResRef)) {
9831 		inventory.SetSlotItemRes(ItemResRef, slot);
9832 	}
9833 }
9834 
ResolveTableValue(const char * resref,ieDword stat,ieDword mcol,ieDword vcol)9835 static ieDword ResolveTableValue(const char *resref, ieDword stat, ieDword mcol, ieDword vcol) {
9836 	long ret = 0;
9837 	//don't close this table, it can mess with the guiscripts
9838 	int table = gamedata->LoadTable(resref);
9839 	if (table == -1) return 0;
9840 	Holder<TableMgr> tm = gamedata->GetTable(table);
9841 	if (tm) {
9842 		unsigned int row;
9843 		if (mcol == 0xff) {
9844 			row = stat;
9845 		} else {
9846 			row = tm->FindTableValue(mcol, stat);
9847 			if (row==0xffffffff) {
9848 				return 0;
9849 			}
9850 		}
9851 		if (valid_number(tm->QueryField(row, vcol), ret)) {
9852 			return (ieDword) ret;
9853 		}
9854 	}
9855 
9856 	return 0;
9857 }
9858 
CheckUsability(const Item * item) const9859 int Actor::CheckUsability(const Item *item) const
9860 {
9861 	ieDword itembits[2]={item->UsabilityBitmask, item->KitUsability};
9862 	int kitignore = 0;
9863 
9864 	for (int i=0;i<usecount;i++) {
9865 		ieDword itemvalue = itembits[itemuse[i].which];
9866 		ieDword stat = GetStat(itemuse[i].stat);
9867 		ieDword mcol = itemuse[i].mcol;
9868 		//if we have a kit, we just use its index for the lookup
9869 		if (itemuse[i].stat==IE_KIT) {
9870 			if (!iwd2class) {
9871 				if (IsKitInactive()) continue;
9872 
9873 				stat = GetKitIndex(stat);
9874 				mcol = 0xff;
9875 			} else {
9876 				//iwd2 doesn't need translation from kit to usability, the kit value IS usability
9877 				// But it's more complicated: check the comments below.
9878 				// Skip undesired kits
9879 				stat &= ~kitignore;
9880 				goto no_resolve;
9881 			}
9882 		}
9883 
9884 		if (!iwd2class && itemuse[i].stat == IE_CLASS) {
9885 			// account for inactive duals
9886 			stat = GetActiveClass();
9887 		}
9888 
9889 		if (iwd2class && itemuse[i].stat == IE_CLASS) {
9890 			// in iwd2 any class mixin can enable the use, but the stat only holds the first class;
9891 			// it also means we shouldn't check all kits (which we do last)!
9892 			// Eg. a paladin of Mystra/sorcerer is allowed to use wands,
9893 			// but the kit check would fail, since paladins and their kits aren't.
9894 			stat = GetClassMask();
9895 			if (stat & ~itemvalue) {
9896 				if (Modified[IE_KIT] == 0) continue;
9897 			} else {
9898 				return STR_CANNOT_USE_ITEM;
9899 			}
9900 
9901 			// classes checked out, but we're kitted ...
9902 			// ignore kits from "unusable" classes
9903 			for (int j=0; j < ISCLASSES; j++) {
9904 				if (Modified[levelslotsiwd2[j]] == 0) continue;
9905 				if ((1<<(classesiwd2[j] - 1)) & ~itemvalue) continue;
9906 
9907 				std::vector<ieDword> kits = class2kits[classesiwd2[j]].ids;
9908 				std::vector<ieDword>::iterator it = kits.begin();
9909 				for ( ; it != kits.end(); it++) {
9910 					kitignore |= *it;
9911 				}
9912 			}
9913 			continue;
9914 		}
9915 
9916 		stat = ResolveTableValue(itemuse[i].table, stat, mcol, itemuse[i].vcol);
9917 
9918 no_resolve:
9919 		if (stat&itemvalue) {
9920 			//print("failed usability: itemvalue %d, stat %d, stat value %d", itemvalue, itemuse[i].stat, stat);
9921 			return STR_CANNOT_USE_ITEM;
9922 		}
9923 	}
9924 
9925 	return 0;
9926 }
9927 
9928 //this one is the same, but returns strrefs based on effects
Disabled(const ieResRef name,ieDword type) const9929 ieStrRef Actor::Disabled(const ieResRef name, ieDword type) const
9930 {
9931 	Effect *fx;
9932 
9933 	fx = fxqueue.HasEffectWithResource(fx_cant_use_item_ref, name);
9934 	if (fx) {
9935 		return fx->Parameter1;
9936 	}
9937 
9938 	fx = fxqueue.HasEffectWithParam(fx_cant_use_item_type_ref, type);
9939 	if (fx) {
9940 		return fx->Parameter1;
9941 	}
9942 	return 0;
9943 }
9944 
9945 //checks usability only
Unusable(const Item * item) const9946 int Actor::Unusable(const Item *item) const
9947 {
9948 	if (!GetStat(IE_CANUSEANYITEM)) {
9949 		int unusable = CheckUsability(item);
9950 		if (unusable) {
9951 			return unusable;
9952 		}
9953 	}
9954 	// iesdp says this is always checked?
9955 	if (item->MinLevel>GetXPLevel(true)) {
9956 		return STR_CANNOT_USE_ITEM;
9957 	}
9958 
9959 	if (!CheckAbilities) {
9960 		return 0;
9961 	}
9962 
9963 	if (item->MinStrength>GetStat(IE_STR)) {
9964 		return STR_CANNOT_USE_ITEM;
9965 	}
9966 
9967 	if (item->MinStrength==18) {
9968 		if (GetStat(IE_STR)==18) {
9969 			if (item->MinStrengthBonus>GetStat(IE_STREXTRA)) {
9970 				return STR_CANNOT_USE_ITEM;
9971 			}
9972 		}
9973 	}
9974 
9975 	if (item->MinIntelligence>GetStat(IE_INT)) {
9976 		return STR_CANNOT_USE_ITEM;
9977 	}
9978 	if (item->MinDexterity>GetStat(IE_DEX)) {
9979 		return STR_CANNOT_USE_ITEM;
9980 	}
9981 	if (item->MinWisdom>GetStat(IE_WIS)) {
9982 		return STR_CANNOT_USE_ITEM;
9983 	}
9984 	if (item->MinConstitution>GetStat(IE_CON)) {
9985 		return STR_CANNOT_USE_ITEM;
9986 	}
9987 	if (item->MinCharisma>GetStat(IE_CHR)) {
9988 		return STR_CANNOT_USE_ITEM;
9989 	}
9990 	//note, weapon proficiencies shouldn't be checked here
9991 	//missing proficiency causes only attack penalty
9992 	return 0;
9993 }
9994 
9995 //full palette will be shaded in gradient color
SetGradient(ieDword gradient)9996 void Actor::SetGradient(ieDword gradient)
9997 {
9998 	gradient |= (gradient <<16);
9999 	gradient |= (gradient <<8);
10000 	for(int i=0;i<7;i++) {
10001 		Modified[IE_COLORS+i]=gradient;
10002 	}
10003 }
10004 
10005 //sets one bit of the sanctuary stat (used for overlays)
SetOverlay(unsigned int overlay)10006 void Actor::SetOverlay(unsigned int overlay)
10007 {
10008 	if (overlay >= OVERLAY_COUNT) return;
10009 	// need to run the pcf, so the vvcs get loaded
10010 	SetStat(IE_SANCTUARY, Modified[IE_SANCTUARY] | (1<<overlay), 0);
10011 }
10012 
10013 //returns true if spell state is already set or illegal
SetSpellState(unsigned int spellstate)10014 bool Actor::SetSpellState(unsigned int spellstate)
10015 {
10016 	if (spellstate >= SpellStatesSize << 5) return true;
10017 	unsigned int pos = spellstate >> 5;
10018 	unsigned int bit = 1 << (spellstate & 31);
10019 	if (spellStates[pos] & bit) return true;
10020 	spellStates[pos] |= bit;
10021 	return false;
10022 }
10023 
10024 //returns true if spell state is already set
HasSpellState(unsigned int spellstate) const10025 bool Actor::HasSpellState(unsigned int spellstate) const
10026 {
10027 	if (spellstate >= SpellStatesSize << 5) return false;
10028 	unsigned int pos = spellstate >> 5;
10029 	unsigned int bit = 1 << (spellstate & 31);
10030 	if (spellStates[pos] & bit) return true;
10031 	return false;
10032 }
10033 
GetMaxEncumbrance() const10034 int Actor::GetMaxEncumbrance() const
10035 {
10036 	int max = core->GetStrengthBonus(3, GetStat(IE_STR), GetStat(IE_STREXTRA));
10037 	if (HasFeat(FEAT_STRONG_BACK)) max += max/2;
10038 	return max;
10039 }
10040 
10041 //this is a very specific rule that might need an external table later
GetAbilityBonus(unsigned int ability,int value) const10042 int Actor::GetAbilityBonus(unsigned int ability, int value) const
10043 {
10044 	if (value == -1) { // invalid (default), use the current value
10045 		return GetStat(ability)/2-5;
10046 	} else {
10047 		return value/2-5;
10048 	}
10049 }
10050 
GetSkillStat(unsigned int skill) const10051 int Actor::GetSkillStat(unsigned int skill) const
10052 {
10053 	if (skill >= skillstats.size()) return -1;
10054 	return skillstats[skill][0];
10055 }
10056 
GetSkill(unsigned int skill,bool ids) const10057 int Actor::GetSkill(unsigned int skill, bool ids) const
10058 {
10059 	if (!ids) {
10060 		// called with a stat, not a skill index
10061 		skill = stat2skill[skill];
10062 	}
10063 	if (skill >= skillstats.size()) return -1;
10064 	int ret = GetStat(skillstats[skill][0]);
10065 	int base = GetBase(skillstats[skill][0]);
10066 	int modStat = skillstats[skill][1];
10067 	// only give other boni for trained skills or those that don't require it
10068 	// untrained trained skills are not usable!
10069 	// DEX is handled separately by GetSkillBonus and applied directly after effects
10070 	if (base > 0 || skillstats[skill][2]) {
10071 		if (modStat != IE_DEX) {
10072 			ret += GetAbilityBonus(modStat);
10073 		}
10074 	} else {
10075 		ret = 0;
10076 	}
10077 	if (ret<0) ret = 0;
10078 	return ret;
10079 }
10080 
10081 //returns the numeric value of a feat, different from HasFeat
10082 //for multiple feats
GetFeat(unsigned int feat) const10083 int Actor::GetFeat(unsigned int feat) const
10084 {
10085 	if (feat>=MAX_FEATS) {
10086 		return -1;
10087 	}
10088 	if (BaseStats[IE_FEATS1+(feat>>5)]&(1<<(feat&31)) ) {
10089 		//return the numeric stat value, instead of the boolean
10090 		if (featstats[feat]) {
10091 			return Modified[featstats[feat]];
10092 		}
10093 		return 1;
10094 	}
10095 	return 0;
10096 }
10097 
10098 //returns true if the feat exists
HasFeat(unsigned int featindex) const10099 bool Actor::HasFeat(unsigned int featindex) const
10100 {
10101 	if (featindex>=MAX_FEATS) return false;
10102 	unsigned int pos = IE_FEATS1+(featindex>>5);
10103 	unsigned int bit = 1<<(featindex&31);
10104 	if (BaseStats[pos]&bit) return true;
10105 	return false;
10106 }
10107 
ImmuneToProjectile(ieDword projectile) const10108 ieDword Actor::ImmuneToProjectile(ieDword projectile) const
10109 {
10110 	int idx;
10111 
10112 	idx = projectile/32;
10113 	if (idx>ProjectileSize) {
10114 		return 0;
10115 	}
10116 	return projectileImmunity[idx]&(1<<(projectile&31));
10117 }
10118 
AddProjectileImmunity(ieDword projectile)10119 void Actor::AddProjectileImmunity(ieDword projectile)
10120 {
10121 	projectileImmunity[projectile/32]|=1<<(projectile&31);
10122 }
10123 
10124 //2nd edition rules
CreateDerivedStatsBG()10125 void Actor::CreateDerivedStatsBG()
10126 {
10127 	int turnundeadlevel = 0;
10128 	int classid = BaseStats[IE_CLASS];
10129 
10130 	//this works only for PC classes
10131 	if (classid>=CLASS_PCCUTOFF) return;
10132 
10133 	//recalculate all level based changes
10134 	pcf_level(this,0,0);
10135 
10136 	// barbarian immunity to backstab was hardcoded
10137 	if (GetBarbarianLevel()) {
10138 		BaseStats[IE_DISABLEBACKSTAB] = 1;
10139 	}
10140 
10141 	for (int i=0;i<ISCLASSES;i++) {
10142 		int tmp;
10143 
10144 		if (classesiwd2[i]>=(ieDword) classcount) continue;
10145 		int tl = turnlevels[classesiwd2[i]];
10146 		if (tl) {
10147 			tmp = GetClassLevel(i)+1-tl;
10148 			//adding up turn undead levels, but this is probably moot
10149 			//anyway, you will be able to create custom priest/paladin classes
10150 			if (tmp>0) {
10151 				turnundeadlevel+=tmp;
10152 			}
10153 		}
10154 	}
10155 
10156 	ieDword backstabdamagemultiplier=GetThiefLevel();
10157 	if (backstabdamagemultiplier) {
10158 		// swashbucklers can't backstab, but backstab.2da only has THIEF in it
10159 		if (BaseStats[IE_KIT] == KIT_SWASHBUCKLER) {
10160 			backstabdamagemultiplier = 1;
10161 		} else {
10162 			AutoTable tm("backstab");
10163 			//fallback to a general algorithm (bg2 backstab.2da version) if we can't find backstab.2da
10164 			// assassin's AP_SPCL332 (increase backstab by one) is not effecting this at all,
10165 			// it's just applied later
10166 			// stalkers work by just using the effect, since they're not thieves
10167 			if (tm)	{
10168 				ieDword cols = tm->GetColumnCount();
10169 				if (backstabdamagemultiplier >= cols) backstabdamagemultiplier = cols;
10170 				backstabdamagemultiplier = atoi(tm->QueryField(0, backstabdamagemultiplier));
10171 			} else {
10172 				backstabdamagemultiplier = (backstabdamagemultiplier+7)/4;
10173 			}
10174 			if (backstabdamagemultiplier>5) backstabdamagemultiplier=5;
10175 		}
10176 	}
10177 
10178 	weapSlotCount = numWeaponSlots[GetActiveClass()];
10179 	ReinitQuickSlots();
10180 
10181 	// monk's level dictated ac and ac vs missiles bonus
10182 	// attacks per round bonus will be handled elsewhere, since it only applies to fist apr
10183 	if (isclass[ISMONK]&(1<<classid)) {
10184 		unsigned int level = GetMonkLevel()-1;
10185 		if (level < monkbon_cols) {
10186 			AC.SetNatural(DEFAULTAC - monkbon[1][level]);
10187 			BaseStats[IE_ACMISSILEMOD] = - monkbon[2][level];
10188 		}
10189 	}
10190 
10191 	BaseStats[IE_TURNUNDEADLEVEL]=turnundeadlevel;
10192 	BaseStats[IE_BACKSTABDAMAGEMULTIPLIER]=backstabdamagemultiplier;
10193 	BaseStats[IE_LAYONHANDSAMOUNT]=GetPaladinLevel()*2;
10194 }
10195 
10196 //3rd edition rules
CreateDerivedStatsIWD2()10197 void Actor::CreateDerivedStatsIWD2()
10198 {
10199 	int i;
10200 	int turnundeadlevel = 0;
10201 	int classid = BaseStats[IE_CLASS];
10202 
10203 	// this works only for PC classes
10204 	if (classid>=CLASS_PCCUTOFF) return;
10205 
10206 	// recalculate all level based changes
10207 	pcf_level(this, 0, 0, classid);
10208 
10209 	// iwd2 does have backstab.2da but it is both unused and with bad data
10210 	ieDword backstabdamagemultiplier = 0;
10211 	int level = GetThiefLevel();
10212 	if (level) {
10213 		// +1d6 for each odd level
10214 		backstabdamagemultiplier = (level + 1) / 2;
10215 	}
10216 
10217 	for (i=0;i<ISCLASSES;i++) {
10218 		int tmp;
10219 
10220 		if (classesiwd2[i]>=(ieDword) classcount) continue;
10221 		int tl = turnlevels[classesiwd2[i]];
10222 		if (tl) {
10223 			tmp = GetClassLevel(i)+1-tl;
10224 			if (tmp>0) {
10225 				//the levels add up (checked)
10226 				turnundeadlevel+=tmp;
10227 			}
10228 		}
10229 	}
10230 	BaseStats[IE_TURNUNDEADLEVEL]=turnundeadlevel;
10231 	BaseStats[IE_BACKSTABDAMAGEMULTIPLIER]=backstabdamagemultiplier;
10232 }
10233 
ResetMC()10234 void Actor::ResetMC()
10235 {
10236 	if (iwd2class) {
10237 		multiclass = 0;
10238 	} else {
10239 		ieDword cls = BaseStats[IE_CLASS]-1;
10240 		if (cls >= (ieDword) classcount) {
10241 			multiclass = 0;
10242 		} else {
10243 			multiclass = multi[cls];
10244 		}
10245 	}
10246 }
10247 
10248 //set up stuff here, like attack number, turn undead level
10249 //and similar derived stats that change with level
CreateDerivedStats()10250 void Actor::CreateDerivedStats()
10251 {
10252 	ResetMC();
10253 
10254 	if (third) {
10255 		CreateDerivedStatsIWD2();
10256 	} else {
10257 		CreateDerivedStatsBG();
10258 	}
10259 
10260 	// check for HoF upgrade
10261 	const Game *game = core->GetGame();
10262 	if (!InParty && game && game->HOFMode && !(BaseStats[IE_MC_FLAGS] & MC_HOF_UPGRADED)) {
10263 		BaseStats[IE_MC_FLAGS] |= MC_HOF_UPGRADED;
10264 
10265 		// our summons get less of an hp boost
10266 		if (BaseStats[IE_EA] <= EA_CONTROLLABLE) {
10267 			BaseStats[IE_MAXHITPOINTS] = 2 * BaseStats[IE_MAXHITPOINTS] + 20;
10268 			BaseStats[IE_HITPOINTS] = 2 * BaseStats[IE_HITPOINTS] + 20;
10269 		} else {
10270 			BaseStats[IE_MAXHITPOINTS] = 3 * BaseStats[IE_MAXHITPOINTS] + 80;
10271 			BaseStats[IE_HITPOINTS] = 3 * BaseStats[IE_HITPOINTS] + 80;
10272 		}
10273 
10274 		if (third) {
10275 			BaseStats[IE_CR] += 10;
10276 			BaseStats[IE_STR] += 10;
10277 			BaseStats[IE_DEX] += 10;
10278 			BaseStats[IE_CON] += 10;
10279 			BaseStats[IE_INT] += 10;
10280 			BaseStats[IE_WIS] += 10;
10281 			BaseStats[IE_CHR] += 10;
10282 			for (int i = 0; i < ISCLASSES; i++) {
10283 				int level = GetClassLevel(i);
10284 				if (!level) continue;
10285 				BaseStats[levelslotsiwd2[i]] += 12;
10286 			}
10287 			// NOTE: this is a guess, reports vary
10288 			// the attribute increase already contributes +5
10289 			for (int i = 0; i <= IE_SAVEWILL - IE_SAVEFORTITUDE; i++) {
10290 				BaseStats[savingthrows[i]] += 5;
10291 			}
10292 		} else {
10293 			BaseStats[IE_NUMBEROFATTACKS] += 2; // 1 more APR
10294 			ToHit.HandleFxBonus(5, true);
10295 			if (BaseStats[IE_XPVALUE]) {
10296 				BaseStats[IE_XPVALUE] = 2 * BaseStats[IE_XPVALUE] + 1000;
10297 			}
10298 			if (BaseStats[IE_GOLD]) {
10299 				BaseStats[IE_GOLD] += 75;
10300 			}
10301 			if (BaseStats[IE_LEVEL]) {
10302 				BaseStats[IE_LEVEL] += 12;
10303 			}
10304 			if (BaseStats[IE_LEVEL2]) {
10305 				BaseStats[IE_LEVEL2] += 12;
10306 			}
10307 			if (BaseStats[IE_LEVEL3]) {
10308 				BaseStats[IE_LEVEL3] += 12;
10309 			}
10310 			for (int i = 0; i < SAVECOUNT; i++) {
10311 				BaseStats[savingthrows[i]]++;
10312 			}
10313 		}
10314 	}
10315 }
10316 /* Checks if the actor is multiclassed (the MULTI column is positive) */
IsMultiClassed() const10317 bool Actor::IsMultiClassed() const
10318 {
10319 	return (multiclass > 0);
10320 }
10321 
10322 /* Checks if the actor is dualclassed */
IsDualClassed() const10323 bool Actor::IsDualClassed() const
10324 {
10325 	// exclude the non-player classes
10326 	if (!HasPlayerClass()) return false;
10327 
10328 	// make sure only one bit is set, as critters like kuo toa have garbage in the mc bits
10329 	return CountBits(Modified[IE_MC_FLAGS] & MC_WAS_ANY) == 1;
10330 }
10331 
CopySelf(bool mislead) const10332 Actor *Actor::CopySelf(bool mislead) const
10333 {
10334 	Actor *newActor = new Actor();
10335 
10336 	newActor->SetName(GetName(0),0);
10337 	newActor->SetName(GetName(1),1);
10338 	newActor->version = version;
10339 	memcpy(newActor->BaseStats, BaseStats, sizeof(BaseStats) );
10340 	// illusions aren't worth any xp and don't explore
10341 	newActor->BaseStats[IE_XPVALUE] = 0;
10342 	newActor->BaseStats[IE_EXPLORE] = 0;
10343 
10344 	//IF_INITIALIZED shouldn't be set here, yet
10345 	newActor->SetMCFlag(MC_EXPORTABLE, OP_NAND);
10346 
10347 	//the creature importer does this too
10348 	memcpy(newActor->Modified,newActor->BaseStats, sizeof(Modified) );
10349 
10350 	//copy the inventory, but only if it is not the Mislead illusion
10351 	if (mislead) {
10352 		//these need to be called too to have a valid inventory
10353 		newActor->inventory.SetSlotCount(inventory.GetSlotCount());
10354 	} else {
10355 		newActor->inventory.CopyFrom(this);
10356 		if (PCStats) {
10357 			newActor->CreateStats();
10358 			*newActor->PCStats = *PCStats;
10359 		}
10360 	}
10361 
10362 	//copy the spellbook, if any
10363 	if (!mislead) {
10364 		newActor->spellbook.CopyFrom(this);
10365 	}
10366 
10367 	newActor->CreateDerivedStats();
10368 
10369 	//copy the running effects
10370 	EffectQueue *newFXQueue = fxqueue.CopySelf();
10371 
10372 	area->AddActor(newActor, true);
10373 	newActor->SetPosition( Pos, CC_CHECK_IMPASSABLE, 0 );
10374 	newActor->SetOrientation(GetOrientation(), false);
10375 	newActor->SetStance( IE_ANI_READY );
10376 
10377 	//and apply them
10378 	newActor->RefreshEffects(newFXQueue);
10379 	return newActor;
10380 }
10381 
10382 //high level function, used by scripting
GetLevelInClass(ieDword classid) const10383 ieDword Actor::GetLevelInClass(ieDword classid) const
10384 {
10385 	if (version==22) {
10386 		//iwd2
10387 		for (int i=0;i<ISCLASSES;i++) {
10388 			if (classid==classesiwd2[i]) {
10389 				return GetClassLevel(i);
10390 			}
10391 		}
10392 		return 0;
10393 	}
10394 
10395 	if (classid >= BGCLASSCNT) {
10396 		classid=0;
10397 	}
10398 	//other, levelslotsbg starts at 0 classid
10399 	return GetClassLevel(levelslotsbg[classid]);
10400 }
10401 
10402 //lowlevel internal function, isclass is NOT the class id, but an internal index
GetClassLevel(const ieDword isclass) const10403 ieDword Actor::GetClassLevel(const ieDword isclass) const
10404 {
10405 	if (isclass>=ISCLASSES)
10406 		return 0;
10407 
10408 	//return iwd2 value if appropriate
10409 	if (version==22)
10410 		return BaseStats[levelslotsiwd2[isclass]];
10411 
10412 	//houston, we got a problem!
10413 	if (!levelslots || !dualswap)
10414 		return 0;
10415 
10416 	//only works with PC's
10417 	ieDword	classid = BaseStats[IE_CLASS]-1;
10418 	if (!HasPlayerClass() || !levelslots[classid])
10419 		return 0;
10420 
10421 	//handle barbarians specially, since they're kits and not in levelslots
10422 	if ((isclass == ISBARBARIAN) && levelslots[classid][ISFIGHTER] && (BaseStats[IE_KIT] == KIT_BARBARIAN)) {
10423 		return BaseStats[IE_LEVEL];
10424 	}
10425 
10426 	//get the levelid (IE_LEVEL,*2,*3)
10427 	ieDword levelid = levelslots[classid][isclass];
10428 	if (!levelid)
10429 		return 0;
10430 
10431 	//do dual-swap
10432 	if (IsDualClassed()) {
10433 		//if the old class is inactive, and it is the class
10434 		//being searched for, return 0
10435 		if (IsDualInactive() && ((Modified[IE_MC_FLAGS]&MC_WAS_ANY)==(ieDword)mcwasflags[isclass]))
10436 			return 0;
10437 	}
10438 	return BaseStats[levelid];
10439 }
10440 
IsDualInactive() const10441 bool Actor::IsDualInactive() const
10442 {
10443 	if (!IsDualClassed()) return 0;
10444 
10445 	//we assume the old class is in IE_LEVEL2, unless swapped
10446 	ieDword oldlevel = IsDualSwap() ? BaseStats[IE_LEVEL] : BaseStats[IE_LEVEL2];
10447 
10448 	//since GetXPLevel returns the average of the 2 levels, oldclasslevel will
10449 	//only be less than GetXPLevel when the new class surpasses it
10450 	return oldlevel>=GetXPLevel(false);
10451 }
10452 
IsDualSwap() const10453 bool Actor::IsDualSwap() const
10454 {
10455 	//the dualswap[class-1] holds the info
10456 	if (!IsDualClassed()) return false;
10457 	ieDword tmpclass = BaseStats[IE_CLASS]-1;
10458 	if (!HasPlayerClass()) return false;
10459 	return (ieDword)dualswap[tmpclass]==(Modified[IE_MC_FLAGS]&MC_WAS_ANY);
10460 }
10461 
GetWarriorLevel() const10462 ieDword Actor::GetWarriorLevel() const
10463 {
10464 	if (!IsWarrior()) return 0;
10465 
10466 	ieDword warriorlevels[4] = {
10467 		GetBarbarianLevel(),
10468 		GetFighterLevel(),
10469 		GetPaladinLevel(),
10470 		GetRangerLevel()
10471 	};
10472 
10473 	ieDword highest = 0;
10474 	for (int i=0; i<4; i++) {
10475 		if (warriorlevels[i] > highest) {
10476 			highest = warriorlevels[i];
10477 		}
10478 	}
10479 
10480 	return highest;
10481 }
10482 
BlocksSearchMap() const10483 bool Actor::BlocksSearchMap() const
10484 {
10485 	return Modified[IE_DONOTJUMP] < DNJ_UNHINDERED &&
10486 		!(InternalFlags & (IF_REALLYDIED | IF_JUSTDIED)) &&
10487 		!Modified[IE_AVATARREMOVAL];
10488 }
10489 
10490 //return true if the actor doesn't want to use an entrance
CannotPassEntrance(ieDword exitID) const10491 bool Actor::CannotPassEntrance(ieDword exitID) const
10492 {
10493 	if (LastExit!=exitID) {
10494 		return true;
10495 	}
10496 
10497 	if (InternalFlags & IF_PST_WMAPPING) {
10498 		return true;
10499 	}
10500 
10501 	if (InternalFlags&IF_USEEXIT) {
10502 		return false;
10503 	}
10504 
10505 	return true;
10506 }
10507 
UseExit(ieDword exitID)10508 void Actor::UseExit(ieDword exitID) {
10509 	if (exitID) {
10510 		InternalFlags|=IF_USEEXIT;
10511 	} else {
10512 		InternalFlags&=~IF_USEEXIT;
10513 		memcpy(LastArea, Area, 8);
10514 		memset(UsedExit, 0, sizeof(ieVariable));
10515 		if (LastExit) {
10516 			const char *ipName = NULL;
10517 			Scriptable *ip = area->GetInfoPointByGlobalID(LastExit);
10518 			if (ip) ipName = ip->GetScriptName();
10519 			if (ipName && ipName[0]) {
10520 				snprintf(UsedExit, sizeof(ieVariable), "%s", ipName);
10521 			}
10522 		}
10523 	}
10524 	LastExit = exitID;
10525 }
10526 
10527 // luck increases the minimum roll per dice, but only up to the number of dice sides;
10528 // luck does not affect critical hit chances:
10529 // if critical is set, it will return 1/sides on a critical, otherwise it can never
10530 // return a critical miss when luck is positive and can return a false critical hit
10531 // Callees with LR_CRITICAL should check if the result matches 1 or size*dice.
LuckyRoll(int dice,int size,int add,ieDword flags,Actor * opponent) const10532 int Actor::LuckyRoll(int dice, int size, int add, ieDword flags, Actor* opponent) const
10533 {
10534 	assert(this != opponent);
10535 
10536 	int luck;
10537 
10538 	luck = (signed) GetSafeStat(IE_LUCK);
10539 
10540 	//damageluck is additive with regular luck (used for maximized damage, righteous magic)
10541 	if (flags&LR_DAMAGELUCK) {
10542 		luck += (signed) GetSafeStat(IE_DAMAGELUCK);
10543 	}
10544 
10545 	//it is always the opponent's luck that decrease damage (or anything)
10546 	if (opponent) luck -= opponent->GetSafeStat(IE_LUCK);
10547 
10548 	if (flags&LR_NEGATIVE) {
10549 		luck = -luck;
10550 	}
10551 
10552 	if (dice < 1 || size < 1) {
10553 		return (add + luck > 1 ? add + luck : 1);
10554 	}
10555 
10556 	ieDword critical = flags&LR_CRITICAL;
10557 
10558 	if (dice > 100) {
10559 		int bonus;
10560 		if (abs(luck) > size) {
10561 			bonus = luck/abs(luck) * size;
10562 		} else {
10563 			bonus = luck;
10564 		}
10565 		int roll = core->Roll(1, dice*size, 0);
10566 		if (critical && (roll == 1 || roll == size)) {
10567 			return roll;
10568 		} else {
10569 			return add + dice * (size + bonus) / 2;
10570 		}
10571 	}
10572 
10573 	int roll, result = 0, misses = 0, hits = 0;
10574 	for (int i = 0; i < dice; i++) {
10575 		roll = core->Roll(1, size, 0);
10576 		if (roll == 1) {
10577 			misses++;
10578 		} else if (roll == size) {
10579 			hits++;
10580 		}
10581 		roll += luck;
10582 		if (roll > size) {
10583 			roll = size;
10584 		} else if (roll < 1) {
10585 			roll = 1;
10586 		}
10587 		result += roll;
10588 	}
10589 
10590 	// ensure we can still return a critical failure/success
10591 	if (critical && dice == misses) return 1;
10592 	if (critical && dice == hits) return size*dice;
10593 
10594 	// hack for critical mode, so overbearing luck does not cause a critical hit
10595 	// FIXME: decouple the result from the critical info
10596 	if (critical && result+add >= size*dice) {
10597 		return size*dice - 1;
10598 	} else {
10599 		return result + add;
10600 	}
10601 }
10602 
10603 // removes the (normal) invisibility state
CureInvisibility()10604 void Actor::CureInvisibility()
10605 {
10606 	if (Modified[IE_STATE_ID] & state_invisible) {
10607 		Effect *newfx;
10608 
10609 		newfx = EffectQueue::CreateEffect(fx_remove_invisible_state_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
10610 		core->ApplyEffect(newfx, this, this);
10611 
10612 		delete newfx;
10613 
10614 		//not sure, but better than nothing
10615 		if (! (Modified[IE_STATE_ID]&state_invisible)) {
10616 			AddTrigger(TriggerEntry(trigger_becamevisible));
10617 		}
10618 	}
10619 }
10620 
10621 // removes the sanctuary effect
CureSanctuary()10622 void Actor::CureSanctuary()
10623 {
10624 	// clear the overlay immediately
10625 	pcf_sanctuary(this, Modified[IE_SANCTUARY], Modified[IE_SANCTUARY] & ~(1<<OV_SANCTUARY));
10626 
10627 	Effect *newfx;
10628 	newfx = EffectQueue::CreateEffect(fx_remove_sanctuary_ref, 0, 0, FX_DURATION_INSTANT_PERMANENT);
10629 	core->ApplyEffect(newfx, this, this);
10630 	delete newfx;
10631 }
10632 
ResetState()10633 void Actor::ResetState()
10634 {
10635 	CureInvisibility();
10636 	CureSanctuary();
10637 	SetModal(MS_NONE);
10638 	ResetCommentTime();
10639 }
10640 
10641 // doesn't check the range, but only that the azimuth and the target
10642 // orientation match with a +/-2 allowed difference
IsBehind(Actor * target) const10643 bool Actor::IsBehind(Actor* target) const
10644 {
10645 	unsigned char tar_orient = target->GetOrientation();
10646 	// computed, since we don't care where we face
10647 	unsigned char my_orient = GetOrient(target->Pos, Pos);
10648 
10649 	signed char diff;
10650 	for (int i=-2; i <= 2; i++) {
10651 		diff = my_orient+i;
10652 		if (diff >= MAX_ORIENT) diff -= MAX_ORIENT;
10653 		if (diff <= -1) diff += MAX_ORIENT;
10654 		if (diff == (signed)tar_orient) return true;
10655 	}
10656 	return false;
10657 }
10658 
10659 // checks all the actor's stats to see if the target is her racial enemy
GetRacialEnemyBonus(const Actor * target) const10660 int Actor::GetRacialEnemyBonus(const Actor *target) const
10661 {
10662 	if (!target) {
10663 		return 0;
10664 	}
10665 
10666 	if (third) {
10667 		int level = GetRangerLevel();
10668 		if (Modified[IE_HATEDRACE] == target->Modified[IE_RACE]) {
10669 			return (level+4)/5;
10670 		}
10671 		// iwd2 supports multiple racial enemies gained through level progression
10672 		for (unsigned int i=0; i<7; i++) {
10673 			if (Modified[IE_HATEDRACE2+i] == target->Modified[IE_RACE]) {
10674 				return (level+4)/5-i-1;
10675 			}
10676 		}
10677 		return 0;
10678 	}
10679 	if (Modified[IE_HATEDRACE] == target->Modified[IE_RACE]) {
10680 		return 4;
10681 	}
10682 	return 0;
10683 }
10684 
ModalSpellSkillCheck()10685 bool Actor::ModalSpellSkillCheck()
10686 {
10687 	switch(Modal.State) {
10688 	case MS_BATTLESONG:
10689 		if (GetBardLevel()) {
10690 			return !(Modified[IE_STATE_ID] & STATE_SILENCED);
10691 		}
10692 		return false;
10693 	case MS_DETECTTRAPS:
10694 		if (Modified[IE_TRAPS]<=0) return false;
10695 		return true;
10696 	case MS_TURNUNDEAD:
10697 		if (Modified[IE_TURNUNDEADLEVEL]<=0) return false;
10698 			return true;
10699 	case MS_STEALTH:
10700 			return TryToHide();
10701 	case MS_NONE:
10702 		default:
10703 			return false;
10704 	}
10705 }
10706 
HideFailed(Actor * actor,int reason=-1,int skill=0,int roll=0,int targetDC=0)10707 inline void HideFailed(Actor* actor, int reason = -1, int skill = 0, int roll = 0, int targetDC = 0)
10708 {
10709 	Effect *newfx;
10710 	newfx = EffectQueue::CreateEffect(fx_disable_button_ref, 0, ACT_STEALTH, FX_DURATION_INSTANT_LIMITED);
10711 	newfx->Duration = core->Time.round_sec; // 90 ticks, 1 round
10712 	core->ApplyEffect(newfx, actor, actor);
10713 	delete newfx;
10714 
10715 	if (!third) {
10716 		return;
10717 	}
10718 
10719 	int bonus = actor->GetAbilityBonus(IE_DEX);
10720 	switch (reason) {
10721 		case 0:
10722 			// ~Failed hide in shadows check! Hide in shadows check %d vs. D20 roll %d (%d Dexterity ability modifier)~
10723 			displaymsg->DisplayRollStringName(39300, DMC_LIGHTGREY, actor, skill-bonus, roll, bonus);
10724 			break;
10725 		case 1:
10726 			// ~Failed hide in shadows because you were seen by creature! Hide in Shadows check %d vs. creature's Level+Wisdom+Race modifier  %d + %d D20 Roll.~
10727 			displaymsg->DisplayRollStringName(39298, DMC_LIGHTGREY, actor, skill, targetDC, roll);
10728 			break;
10729 		case 2:
10730 			// ~Failed hide in shadows because you were heard by creature! Hide in Shadows check %d vs. creature's Level+Wisdom+Race modifier  %d + %d D20 Roll.~
10731 			displaymsg->DisplayRollStringName(39297, DMC_LIGHTGREY, actor, skill, targetDC, roll);
10732 			break;
10733 		default:
10734 			// no message
10735 			break;
10736 	}
10737 }
10738 
10739 //checks if we are seen, or seeing anyone
SeeAnyOne(bool enemy,bool seenby) const10740 bool Actor::SeeAnyOne(bool enemy, bool seenby) const
10741 {
10742 	if (!area) return false;
10743 
10744 	int flag = (seenby ? 0 : GA_NO_HIDDEN) | GA_NO_DEAD | GA_NO_UNSCHEDULED | GA_NO_SELF;
10745 	if (enemy) {
10746 		ieDword ea = GetSafeStat(IE_EA);
10747 		if (ea>=EA_EVILCUTOFF) {
10748 			flag|=GA_NO_ENEMY|GA_NO_NEUTRAL;
10749 		} else if (ea<=EA_GOODCUTOFF) {
10750 			flag|=GA_NO_ALLY|GA_NO_NEUTRAL;
10751 		} else {
10752 			return false; //neutrals got no enemy
10753 		}
10754 	}
10755 
10756 	std::vector<Actor *> visActors = area->GetAllActorsInRadius(Pos, flag, seenby ? VOODOO_VISUAL_RANGE / 2 : GetSafeStat(IE_VISUALRANGE) / 2, this);
10757 	bool seeEnemy = false;
10758 
10759 	//we need to look harder if we look for seenby anyone
10760 	for (const Actor *toCheck : visActors) {
10761 		if (seenby) {
10762 			if (WithinRange(toCheck, Pos, toCheck->GetStat(IE_VISUALRANGE) / 2)) {
10763 				seeEnemy = true;
10764 			}
10765 		} else {
10766 			seeEnemy = true;
10767 		}
10768 	}
10769 	return seeEnemy;
10770 }
10771 
TryToHide()10772 bool Actor::TryToHide()
10773 {
10774 	if (Modified[IE_DISABLEDBUTTON] & (1<<ACT_STEALTH)) {
10775 		HideFailed(this);
10776 		return false;
10777 	}
10778 
10779 	// iwd2 is like the others only when trying to hide for the first time
10780 	bool continuation = Modified[IE_STATE_ID] & state_invisible;
10781 	if (third && continuation) {
10782 		return TryToHideIWD2();
10783 	}
10784 
10785 	ieDword roll = 0;
10786 	if (third) {
10787 		roll = LuckyRoll(1, 20, GetArmorSkillPenalty(0));
10788 	} else {
10789 		roll = LuckyRoll(1, 100, GetArmorSkillPenalty(0));
10790 		// critical failure
10791 		if (roll == 1) {
10792 			HideFailed(this);
10793 			return false;
10794 		}
10795 	}
10796 
10797 	// check for disabled dualclassed thieves (not sure if we need it)
10798 
10799 	bool seen = SeeAnyOne(true, true);
10800 
10801 	ieDword skill;
10802 	if (core->HasFeature(GF_HAS_HIDE_IN_SHADOWS)) {
10803 		skill = (GetStat(IE_HIDEINSHADOWS) + GetStat(IE_STEALTH))/2;
10804 	} else {
10805 		skill = GetStat(IE_STEALTH);
10806 	}
10807 
10808 	if (seen) {
10809 		HideFailed(this, 1);
10810 	}
10811 
10812 	if (third) {
10813 		skill *= 7; // FIXME: temporary increase for the lightness percentage calculation
10814 	}
10815 	// TODO: figure out how iwd2 uses the area lightness and crelight.2da
10816 	const Game *game = core->GetGame();
10817 	// check how bright our spot is
10818 	ieDword lightness = game->GetCurrentArea()->GetLightLevel(Pos);
10819 	// seems to be the color overlay at midnight; lightness of a point with rgb (200, 100, 100)
10820 	// TODO: but our NightTint computes to a higher value, which one is bad?
10821 	ieDword light_diff = int((lightness - ref_lightness) * 100 / (100 - ref_lightness)) / 2;
10822 	ieDword chance = (100 - light_diff) * skill/100;
10823 
10824 	if (roll > chance) {
10825 		HideFailed(this, 0, skill/7, roll);
10826 		return false;
10827 	}
10828 	if (!continuation) VerbalConstant(VB_HIDE);
10829 	if (!third) return true;
10830 
10831 	// ~Successful hide in shadows check! Hide in shadows check %d vs. D20 roll %d (%d Dexterity ability modifier)~
10832 	displaymsg->DisplayRollStringName(39299, DMC_LIGHTGREY, this, skill/7, roll, GetAbilityBonus(IE_DEX));
10833 	return true;
10834 }
10835 
10836 // skill check when trying to maintain invisibility: separate move silently and visibility check
TryToHideIWD2()10837 bool Actor::TryToHideIWD2()
10838 {
10839 	int flags = GA_NO_DEAD | GA_NO_NEUTRAL | GA_NO_SELF | GA_NO_UNSCHEDULED;
10840 	ieDword ea = GetSafeStat(IE_EA);
10841 	if (ea >= EA_EVILCUTOFF) {
10842 		flags |= GA_NO_ENEMY;
10843 	} else if (ea <= EA_GOODCUTOFF) {
10844 		flags |= GA_NO_ALLY;
10845 	}
10846 	std::vector<Actor *> neighbours = area->GetAllActorsInRadius(Pos, flags, Modified[IE_VISUALRANGE] / 2, this);
10847 	ieDword roll = LuckyRoll(1, 20, GetArmorSkillPenalty(0));
10848 	int targetDC = 0;
10849 
10850 	// visibility check, you can try hiding while enemies are nearby
10851 	ieDword skill = GetSkill(IE_HIDEINSHADOWS);
10852 	for (const Actor *toCheck : neighbours) {
10853 		if (toCheck->GetStat(IE_STATE_ID)&STATE_BLIND) {
10854 			continue;
10855 		}
10856 		// we need to do an additional visual range check from the perspective of the observer
10857 		if (!WithinRange(toCheck, Pos, toCheck->GetStat(IE_VISUALRANGE) / 2)) {
10858 			continue;
10859 		}
10860 		// IE_CLASSLEVELSUM is set for all cres in iwd2 and use here was confirmed by RE
10861 		// the third summand is a racial bonus (crehidemd.2da), but we use their search skill directly
10862 		// the original actually multiplied the roll and DC by 5, making the check practically impossible to pass
10863 		targetDC = toCheck->GetStat(IE_CLASSLEVELSUM) + toCheck->GetAbilityBonus(IE_WIS) + toCheck->GetStat(IE_SEARCH);
10864 		bool seen = skill < roll + targetDC;
10865 		if (seen) {
10866 			HideFailed(this, 1, skill, roll, targetDC);
10867 			return false;
10868 		} else {
10869 			// ~You were not seen by creature! Hide check %d vs. creature's Level+Wisdom+Race modifier  %d + %d D20 Roll.~
10870 			displaymsg->DisplayRollStringName(28379, DMC_LIGHTGREY, this, skill, targetDC, roll);
10871 		}
10872 	}
10873 
10874 	// we're stationary, so no need to check if we're making movement sounds
10875 	if (!InMove()) {
10876 		return true;
10877 	}
10878 
10879 	// separate move silently check
10880 	skill = GetSkill(IE_STEALTH);
10881 	for (const Actor *toCheck : neighbours) {
10882 		if (toCheck->HasSpellState(SS_DEAF)) {
10883 			continue;
10884 		}
10885 		// NOTE: pretending there is no hearing range, just as in the original
10886 
10887 		// the third summand is a racial bonus from the nonexisting QUIETMOD column of crehidemd.2da,
10888 		// but we're fair and use their search skill directly
10889 		// the original actually multiplied the roll and DC by 5 and inverted the comparison, making the check practically impossible to pass
10890 		targetDC = toCheck->GetStat(IE_CLASSLEVELSUM) + toCheck->GetAbilityBonus(IE_WIS) + toCheck->GetStat(IE_SEARCH);
10891 		bool heard = skill < roll + targetDC;
10892 		if (heard) {
10893 			HideFailed(this, 2, skill, roll, targetDC);
10894 			return false;
10895 		} else {
10896 			// ~You were not heard by creature! Move silently check %d vs. creature's Level+Wisdom+Race modifier  %d + %d D20 Roll.~
10897 			displaymsg->DisplayRollStringName(112, DMC_LIGHTGREY, this, skill, targetDC, roll);
10898 		}
10899 	}
10900 
10901 	// NOTE: the original checked lightness for creatures that were both deaf and blind (or if nobody is around)
10902 	// check TryToHide if it ever becomes important (crelight.2da)
10903 
10904 	return true;
10905 }
10906 
10907 //cannot target actor (used by GUI)
Untargetable(ieResRef spellRef) const10908 bool Actor::Untargetable(ieResRef spellRef) const
10909 {
10910 	if (spellRef[0]) {
10911 		Spell *spl = gamedata->GetSpell(spellRef, true);
10912 		if (spl && (spl->Flags&SF_TARGETS_INVISIBLE)) {
10913 			gamedata->FreeSpell(spl, spellRef, false);
10914 			return false;
10915 		}
10916 		gamedata->FreeSpell(spl, spellRef, false);
10917 	}
10918 	return IsInvisibleTo(NULL);
10919 }
10920 
10921 //it is futile to try to harm target (used by AI scripts)
InvalidSpellTarget() const10922 bool Actor::InvalidSpellTarget() const
10923 {
10924 	if (GetSafeStat(IE_STATE_ID) & STATE_DEAD) return true;
10925 	if (HasSpellState(SS_SANCTUARY)) return true;
10926 	return false;
10927 }
10928 
InvalidSpellTarget(int spellnum,Actor * caster,int range) const10929 bool Actor::InvalidSpellTarget(int spellnum, Actor *caster, int range) const
10930 {
10931 	ieResRef spellres;
10932 
10933 	ResolveSpellName(spellres, spellnum);
10934 
10935 	//cheap substitute of the original hardcoded feature, returns true if already affected by the exact spell
10936 	//no (spell)state checks based on every effect in the spell
10937 	//FIXME: create a more compatible version if needed
10938 	if (fxqueue.HasSource(spellres)) return true;
10939 	//return true if caster cannot cast
10940 	if (!caster->CanCast(spellres, false)) return true;
10941 
10942 	if (!range) return false;
10943 
10944 	int srange = GetSpellDistance(spellres, caster);
10945 	return srange<range;
10946 }
10947 
GetClassMask() const10948 int Actor::GetClassMask() const
10949 {
10950 	int classmask = 0;
10951 	for (int i=0; i < ISCLASSES; i++) {
10952 		if (Modified[levelslotsiwd2[i]] > 0) {
10953 			classmask |= 1<<(classesiwd2[i]-1);
10954 		}
10955 	}
10956 
10957 	return classmask;
10958 }
10959 
GetBookMask() const10960 int Actor::GetBookMask() const
10961 {
10962 	int bookmask = 0;
10963 	for (int i=0; i < ISCLASSES; i++) {
10964 		if (Modified[levelslotsiwd2[i]] > 0 && booksiwd2[i] >= 0) {
10965 			bookmask |= 1 << booksiwd2[i];
10966 		}
10967 	}
10968 
10969 	return bookmask;
10970 }
10971 
10972 // returns race for non-iwd2
GetSubRace() const10973 unsigned int Actor::GetSubRace() const
10974 {
10975 	// race
10976 	int lookup = Modified[IE_RACE];
10977 	if (third) {
10978 		// mangle with subrace if any
10979 		int subrace = Modified[IE_SUBRACE];
10980 		if (subrace) lookup = lookup<<16 | subrace;
10981 	}
10982 	return lookup;
10983 }
10984 
10985 // returns the combined dexterity and racial bonus to specified thieving skill
10986 // column indices are 1-based, since 0 holds the rowname against which we do the lookup
GetSkillBonus(unsigned int col) const10987 int Actor::GetSkillBonus(unsigned int col) const
10988 {
10989 	if (skilldex.empty()) return 0;
10990 
10991 	// race
10992 	int lookup = GetSubRace();
10993 	int bonus = 0;
10994 	std::vector<std::vector<int> >::iterator it = skillrac.begin();
10995 	// make sure we have a column, since the games have different amounts of thieving skills
10996 	if (col < it->size()) {
10997 		for ( ; it != skillrac.end(); it++) {
10998 			if ((*it)[0] == lookup) {
10999 				bonus = (*it)[col];
11000 				break;
11001 			}
11002 		}
11003 	}
11004 
11005 	// dexterity
11006 	lookup = Modified[IE_DEX];
11007 	it = skilldex.begin();
11008 	// make sure we have a column, since the games have different amounts of thieving skills
11009 	if (col < it->size()) {
11010 		for ( ; it != skilldex.end(); it++) {
11011 			if ((*it)[0] == lookup) {
11012 				bonus += (*it)[col];
11013 				break;
11014 			}
11015 		}
11016 	}
11017 	return bonus;
11018 }
11019 
IsPartyMember() const11020 bool Actor::IsPartyMember() const
11021 {
11022 	if (Modified[IE_EA]<=EA_FAMILIAR) return true;
11023 	return InParty>0;
11024 }
11025 
ResetCommentTime()11026 void Actor::ResetCommentTime()
11027 {
11028 	Game *game = core->GetGame();
11029 	if (bored_time) {
11030 		nextBored = game->GameTime + core->Roll(1, 30, bored_time);
11031 		nextComment = game->GameTime + core->Roll(5, 1000, bored_time/2);
11032 	} else {
11033 		nextBored = 0;
11034 		nextComment = game->GameTime + core->Roll(10, 500, 150);
11035 	}
11036 }
11037 
11038 // this one is just a hack, so we can keep a bunch of other functions const
GetArmorSkillPenalty(int profcheck) const11039 int Actor::GetArmorSkillPenalty(int profcheck) const
11040 {
11041 	int tmp1, tmp2;
11042 	return GetArmorSkillPenalty(profcheck, tmp1, tmp2);
11043 }
11044 
11045 // Returns the armor check penalty.
11046 // used for mapping the iwd2 armor feat to the equipped armor's weight class
11047 // magical shields and armors get a +1 bonus
GetArmorSkillPenalty(int profcheck,int & armor,int & shield) const11048 int Actor::GetArmorSkillPenalty(int profcheck, int &armor, int &shield) const
11049 {
11050 	if (!third) return 0;
11051 
11052 	ieWord armorType = inventory.GetArmorItemType();
11053 	int penalty = core->GetArmorPenalty(armorType);
11054 	int weightClass = GetArmorWeightClass(armorType);
11055 
11056 	// ignore the penalty if we are proficient
11057 	if (profcheck && GetFeat(FEAT_ARMOUR_PROFICIENCY) >= weightClass) {
11058 		penalty = 0;
11059 	}
11060 	bool magical = false;
11061 	int armorSlot = inventory.GetArmorSlot();
11062 	CREItem *armorItem = inventory.GetSlotItem(armorSlot);
11063 	if (armorItem) {
11064 		magical = armorItem->Flags&IE_INV_ITEM_MAGICAL;
11065 	}
11066 	if (magical) {
11067 		penalty -= 1;
11068 		if (penalty < 0) {
11069 			penalty = 0;
11070 		}
11071 	}
11072 	armor = penalty;
11073 
11074 	// check also the shield penalty
11075 	armorType = inventory.GetShieldItemType();
11076 	int shieldPenalty = core->GetShieldPenalty(armorType);
11077 	magical = false;
11078 	armorSlot = inventory.GetShieldSlot();
11079 	if (armorSlot != -1) { // there is a shield
11080 		armorItem = inventory.GetSlotItem(armorSlot);
11081 		if (armorItem) {
11082 			magical = armorItem->Flags&IE_INV_ITEM_MAGICAL;
11083 		}
11084 	}
11085 	if (magical) {
11086 		shieldPenalty -= 1;
11087 		if (shieldPenalty < 0) {
11088 			shieldPenalty = 0;
11089 		}
11090 	}
11091 	if (profcheck) {
11092 		if (HasFeat(FEAT_SHIELD_PROF)) {
11093 			shieldPenalty = 0;
11094 		} else {
11095 			penalty += shieldPenalty;
11096 		}
11097 	} else {
11098 		penalty += shieldPenalty;
11099 	}
11100 	shield = shieldPenalty;
11101 
11102 	return -penalty;
11103 }
11104 
11105 // the armor weight class is perfectly deduced from the penalty as following:
11106 // 0,   none: none, robes
11107 // 1-3, light: leather, studded
11108 // 4-6, medium: hide, chain, scale
11109 // 7-,  heavy: splint, plate, full plate
11110 // the values are taken from our dehardcoded itemdata.2da
GetArmorWeightClass(ieWord armorType) const11111 int Actor::GetArmorWeightClass(ieWord armorType) const
11112 {
11113 	if (!third) return 0;
11114 
11115 	int penalty = core->GetArmorPenalty(armorType);
11116 	int weightClass = 0;
11117 
11118 	if (penalty >= 1 && penalty < 4) {
11119 		weightClass = 1;
11120 	} else if (penalty >= 4 && penalty < 7) {
11121 		weightClass = 2;
11122 	} else if (penalty >= 7) {
11123 		weightClass = 3;
11124 	}
11125 	return weightClass;
11126 }
11127 
GetTotalArmorFailure() const11128 int Actor::GetTotalArmorFailure() const
11129 {
11130 	int armorfailure, shieldfailure;
11131 	GetArmorFailure(armorfailure, shieldfailure);
11132 	return armorfailure+shieldfailure;
11133 }
11134 
GetArmorFailure(int & armor,int & shield) const11135 int Actor::GetArmorFailure(int &armor, int &shield) const
11136 {
11137 	armor = shield = 0;
11138 	if (!third) return 0;
11139 
11140 	ieWord armorType = inventory.GetArmorItemType();
11141 	int penalty = core->GetArmorFailure(armorType);
11142 	armor = penalty;
11143 
11144 	// check also the shield penalty
11145 	armorType = inventory.GetShieldItemType();
11146 	int shieldPenalty = core->GetShieldPenalty(armorType);
11147 	penalty += shieldPenalty;
11148 	shield = shieldPenalty;
11149 
11150 	return -penalty;
11151 }
11152 
11153 // checks whether the actor is visible to another scriptable
IsInvisibleTo(const Scriptable * checker) const11154 bool Actor::IsInvisibleTo(const Scriptable *checker) const
11155 {
11156 	bool canSeeInvisibles = false;
11157 	if (checker && checker->Type == ST_ACTOR) {
11158 		canSeeInvisibles = ((Actor *) checker)->GetSafeStat(IE_SEEINVISIBLE);
11159 	}
11160 	bool invisible = GetSafeStat(IE_STATE_ID) & state_invisible;
11161 	if (!canSeeInvisibles && (invisible || HasSpellState(SS_SANCTUARY))) {
11162 		return true;
11163 	}
11164 
11165 	return false;
11166 }
11167 
UpdateAnimationID(bool derived)11168 int Actor::UpdateAnimationID(bool derived)
11169 {
11170 	if (avCount<0) return 1;
11171 	// the base animation id
11172 	int AnimID = avBase;
11173 	int StatID = derived?GetSafeStat(IE_ANIMATION_ID):avBase;
11174 	if (AnimID<0 || StatID<AnimID || StatID>AnimID+0x1000) return 1; //no change
11175 	if (!InParty) return 1; //too many bugs caused by buggy game data, we change only PCs
11176 
11177 	// tables for additive modifiers of the animation id (race, gender, class)
11178 	for (int i = 0; i < avCount; i++) {
11179 		const TableMgr *tm = avPrefix[i].avtable.ptr();
11180 		if (!tm) {
11181 			return -3;
11182 		}
11183 		StatID = avPrefix[i].stat;
11184 		StatID = derived?GetSafeStat(StatID):GetBase( StatID );
11185 
11186 		const char *poi = tm->QueryField( StatID );
11187 		AnimID += strtoul( poi, NULL, 0 );
11188 	}
11189 	if (BaseStats[IE_ANIMATION_ID]!=(unsigned int) AnimID) {
11190 		SetBase(IE_ANIMATION_ID, (unsigned int) AnimID);
11191 	}
11192 	if (!derived) {
11193 		SetAnimationID(AnimID);
11194 		//setting PST's starting stance to 18
11195 		if (avStance !=-1) {
11196 			SetStance( avStance );
11197 		}
11198 	}
11199 	return 0;
11200 }
11201 
MovementCommand(char * command)11202 void Actor::MovementCommand(char *command)
11203 {
11204 	UseExit(0);
11205 	Stop();
11206 	AddAction( GenerateAction( command ) );
11207 	ProcessActions();
11208 }
11209 
HasVisibleHP() const11210 bool Actor::HasVisibleHP() const
11211 {
11212 	// sucks but this is set in different places
11213 	if (!pstflags && GetStat(IE_MC_FLAGS) & MC_HIDE_HP) return false;
11214 	if (HasSpellState(SS_NOHPINFO)) return false;
11215 	if (GetStat(IE_EXTSTATE_ID) & EXTSTATE_NO_HP) return false;
11216 	return true;
11217 }
11218 
11219 // shows hp/maxhp as overhead text
DisplayHeadHPRatio()11220 void Actor::DisplayHeadHPRatio()
11221 {
11222 	if (!HasVisibleHP()) return;
11223 
11224 	wchar_t tmpstr[20];
11225 	swprintf(tmpstr, 20, L"%d/%d", Modified[IE_HITPOINTS], Modified[IE_MAXHITPOINTS]);
11226 	SetOverheadText(tmpstr);
11227 }
11228 
ReleaseCurrentAction()11229 void Actor::ReleaseCurrentAction()
11230 {
11231 	disarmTrap = -1;
11232 	Scriptable::ReleaseCurrentAction();
11233 }
11234 
11235 // concentration is annoying: besides special cases, every caster should
11236 // check if there's an enemy nearby
ConcentrationCheck() const11237 bool Actor::ConcentrationCheck() const
11238 {
11239 	if (!third) return true;
11240 
11241 	if (Modified[IE_SPECFLAGS]&SPECF_DRIVEN) return true;
11242 
11243 	// anyone in a 5' radius?
11244 	std::vector<Actor *> neighbours = area->GetAllActorsInRadius(Pos, GA_NO_DEAD|GA_NO_NEUTRAL|GA_NO_ALLY|GA_NO_SELF|GA_NO_UNSCHEDULED|GA_NO_HIDDEN, 5, this);
11245 	if (neighbours.empty()) return true;
11246 
11247 	// so there is someone out to get us and we should do the real concentration check
11248 	int roll = LuckyRoll(1, 20, 0);
11249 	// TODO: the manual replaces the con bonus with an int one (verify!)
11250 	int concentration = GetStat(IE_CONCENTRATION);
11251 	int bonus = GetAbilityBonus(IE_INT);
11252 	if (HasFeat(FEAT_COMBAT_CASTING)) {
11253 		bonus += 4;
11254 	}
11255 
11256 	Spell* spl = gamedata->GetSpell(SpellResRef, true);
11257 	if (!spl) return true;
11258 	int spellLevel = spl->SpellLevel;
11259 	gamedata->FreeSpell(spl, SpellResRef, false);
11260 
11261 	if (roll + concentration + bonus < 15 + spellLevel) {
11262 		if (InParty) {
11263 			displaymsg->DisplayRollStringName(39258, DMC_LIGHTGREY, this, roll + concentration, 15 + spellLevel, bonus);
11264 		} else {
11265 			displaymsg->DisplayRollStringName(39265, DMC_LIGHTGREY, this);
11266 		}
11267 		return false;
11268 	} else {
11269 		if (InParty) {
11270 			// ~Successful spell casting concentration check! Check roll %d vs. difficulty %d (%d bonus)~
11271 			displaymsg->DisplayRollStringName(39257, DMC_LIGHTGREY, this, roll + concentration, 15 + spellLevel, bonus);
11272 		}
11273 	}
11274 	return true;
11275 }
11276 
11277 // shorthand wrapper for throw-away effects
ApplyEffectCopy(Effect * oldfx,EffectRef & newref,Scriptable * Owner,ieDword param1,ieDword param2)11278 void Actor::ApplyEffectCopy(Effect *oldfx, EffectRef &newref, Scriptable *Owner, ieDword param1, ieDword param2)
11279 {
11280 	Effect *newfx = EffectQueue::CreateEffectCopy(oldfx, newref, param1, param2);
11281 	if (newfx) {
11282 		core->ApplyEffect(newfx, this, Owner);
11283 		delete newfx;
11284 	} else {
11285 		Log(ERROR, "Actor", "Failed to create effect copy for %s! Target: %s, Owner: %s", newref.Name, GetName(1), Owner->GetName(1));
11286 	}
11287 }
11288 
11289 // check if we were the passed class at some point
11290 // NOTE: does not ignore disabled dual classes!
WasClass(ieDword oldClassID) const11291 bool Actor::WasClass(ieDword oldClassID) const
11292 {
11293 	if (oldClassID >= BGCLASSCNT) return false;
11294 
11295 	int mcWas = BaseStats[IE_MC_FLAGS] & MC_WAS_ANY;
11296 	if (!mcWas) {
11297 		// not dualclassed
11298 		return false;
11299 	}
11300 
11301 	int OldIsClassID = levelslotsbg[oldClassID];
11302 	return mcwasflags[OldIsClassID] == mcWas;
11303 }
11304 
11305 // returns effective class, accounting for possible inactive dual
GetActiveClass() const11306 ieDword Actor::GetActiveClass() const
11307 {
11308 	if (!IsDualInactive()) {
11309 		// on load, Modified is not populated yet
11310 		if (Modified[IE_CLASS] == 0) return BaseStats[IE_CLASS];
11311 		return Modified[IE_CLASS];
11312 	}
11313 
11314 	int mcwas = Modified[IE_MC_FLAGS] & MC_WAS_ANY;
11315 	int oldclass;
11316 	for (int isclass = 0; isclass < ISCLASSES; isclass++) {
11317 		oldclass = classesiwd2[isclass];
11318 		if (mcwas == mcwasflags[isclass]) break;
11319 	}
11320 	if (!oldclass) {
11321 		error("Actor", "Actor %s has incorrect MC_WAS flags (%x)!", GetName(1), mcwas);
11322 	}
11323 
11324 	int newclassmask = multiclass & ~(1 << (oldclass - 1));
11325 	for (int newclass = 1, mask = 1; mask <= newclassmask; newclass++, mask <<= 1) {
11326 		if (newclassmask == mask) return newclass;
11327 	}
11328 
11329 	// can be hit when starting a dual class
11330 	Log(ERROR, "Actor", "Dual-classed actor %s (old class %d) has wrong multiclass bits (%d), using old class!", GetName(1), oldclass, multiclass);
11331 	return oldclass;
11332 }
11333 
11334 // like IsDualInactive(), but accounts for the possibility of the active (second) class being kitted
IsKitInactive() const11335 bool Actor::IsKitInactive() const
11336 {
11337 	if (third) return false;
11338 	if (!IsDualInactive()) return false;
11339 
11340 	ieDword baseclass = GetActiveClass();
11341 	ieDword kit = GetStat(IE_KIT);
11342 	std::vector<ieDword> kits = class2kits[baseclass].ids;
11343 	std::vector<ieDword>::iterator it = kits.begin();
11344 	for (int idx = 0; it != kits.end(); it++, idx++) {
11345 		if (kit & (*it)) return false;
11346 	}
11347 	return true;
11348 }
11349 
11350 // account for haste/slow affecting the metabolism (regeneration etc.)
11351 // handled by AdjustedTicks in the original
GetAdjustedTime(unsigned int time) const11352 unsigned int Actor::GetAdjustedTime(unsigned int time) const
11353 {
11354 	// haste mode 2 (walk speed) has no effect and we have to check the opcode indirectly
11355 	// otherwise it wouldn't work if the haste/slow effect is later in the queue
11356 	if (fxqueue.HasEffectWithParam(fx_set_haste_state_ref, 0) || fxqueue.HasEffectWithParam(fx_set_haste_state_ref, 1)) {
11357 		time /= 2;
11358 	} else if (fxqueue.HasEffect(fx_set_slow_state_ref)) {
11359 		time *= 2;
11360 	}
11361 	return time;
11362 }
11363 
GetClassID(const ieDword isclass)11364 ieDword Actor::GetClassID (const ieDword isclass) {
11365 	return classesiwd2[isclass];
11366 }
11367 
GetClassName(ieDword classID) const11368 const char *Actor::GetClassName(ieDword classID) const
11369 {
11370 	return class2kits[classID].className;
11371 }
11372 
11373 // NOTE: returns first kit name for multikit chars
GetKitName(ieDword kitID) const11374 const char *Actor::GetKitName(ieDword kitID) const
11375 {
11376 	std::map<int, ClassKits>::iterator clskit = class2kits.begin();
11377 	for (int cidx = 0; clskit != class2kits.end(); clskit++, cidx++) {
11378 		std::vector<ieDword> kits = class2kits[cidx].ids;
11379 		std::vector<ieDword>::iterator it = kits.begin();
11380 		for (int kidx = 0; it != kits.end(); it++, kidx++) {
11381 			if (kitID & (*it)) {
11382 				return class2kits[cidx].kitNames[kidx];
11383 			}
11384 		}
11385 	}
11386 	return "";
11387 }
11388 
SetAnimatedTalking(unsigned int length)11389 void Actor::SetAnimatedTalking (unsigned int length) {
11390 	remainingTalkSoundTime = std::max(remainingTalkSoundTime, length);
11391 	lastTalkTimeCheckAt = GetTicks();
11392 }
11393 
HasPlayerClass() const11394 bool Actor::HasPlayerClass() const
11395 {
11396 	// no need to check for dual/multiclassing, since for that all used classes have to be player classes
11397 	int cid = BaseStats[IE_CLASS];
11398 	// IE_CLASS is >classcount for non-PCs/NPCs
11399 	return cid > 0 && cid < classcount;
11400 }
11401 
11402 // this is part of a REd function from the original (see #124)
GetArmorCode() const11403 char Actor::GetArmorCode() const
11404 {
11405 	bool mageAnimation = (BaseStats[IE_ANIMATION_ID] & 0xF00) == 0x200;
11406 	// IE_ARMOR_TYPE + 1 is the armor code, but we also need to look up robes specifically as they have 3 types :(
11407 	CREItem *itm = inventory.GetSlotItem(inventory.GetArmorSlot());
11408 	if (!itm) return '1';
11409 	Item *item = gamedata->GetItem(itm->ItemResRef, true);
11410 	if (!item) return '1';
11411 	bool wearingRobes = item->AnimationType[1] == 'W';
11412 
11413 	if (mageAnimation ^ wearingRobes) return '1';
11414 	return item->AnimationType[0];
11415 }
11416 
GetArmorSound() const11417 const char* Actor::GetArmorSound() const
11418 {
11419 	// Character has mage animation or is a non-mage wearing mage robes
11420 	if ((BaseStats[IE_ANIMATION_ID] & 0xF00) == 0x200) return "";
11421 	char armorCode = GetArmorCode();
11422 	if (armorCode == '1') {
11423 		return "";
11424 	}
11425 
11426 	char *sound = new char[9];
11427 	int maxChar = 6;
11428 	if (armorCode == '4') maxChar = 8;
11429 	if (IWDSound) {
11430 		// all three iwds have this pattern: a_chain1-6, a_lthr1-6, a_plate1-8
11431 		const char* suffixes = "12345678";
11432 		int idx = RAND(0, maxChar-1);
11433 		if (armorCode == '2') {
11434 			strcpy(sound, "A_LTHR");
11435 			sound[6] = suffixes[idx];
11436 			sound[7] = '\0';
11437 		} else if (armorCode == '3') {
11438 			strcpy(sound, "A_CHAIN");
11439 			sound[7] = suffixes[idx];
11440 			sound[8] = '\0';
11441 		} else { // 4
11442 			strcpy(sound, "A_PLATE");
11443 			sound[7] = suffixes[idx];
11444 			sound[8] = '\0';
11445 		}
11446 	} else {
11447 		// generate a 1 letter suffix or emulate an empty string
11448 		// ARM_04G and ARM_04H exist, but couldn't be picked by the original function
11449 		const char* suffixes = "abcdefgh";
11450 		int idx = RAND(0, maxChar);
11451 		char randomASCII = '\0';
11452 		if (idx < maxChar) randomASCII = suffixes[idx];
11453 
11454 		strcpy(sound, "ARM_0");
11455 		sound[5] = armorCode;
11456 		sound[6] = randomASCII;
11457 		sound[7] = '\0';
11458 	}
11459 	return sound;
11460 }
11461 
PlayArmorSound() const11462 void Actor::PlayArmorSound() const
11463 {
11464 	// don't try immediately upon loading
11465 	if (!Ticks) return;
11466 	if (Modified[IE_STATE_ID] & STATE_SILENCED) return;
11467 	// peculiar original behaviour: always for pcs, while the rest only clank if footstep sounds are on
11468 	if (!footsteps && !InParty) return;
11469 	// pst is missing the resources
11470 	if (pstflags) return;
11471 
11472 	Game *game = core->GetGame();
11473 	if (!game) return;
11474 	if (game->CombatCounter) return;
11475 
11476 	const char *armorSound = GetArmorSound();
11477 	if (armorSound[0]) {
11478 		core->GetAudioDrv()->Play(armorSound, SFX_CHAN_ARMOR, Pos.x, Pos.y);
11479 		delete[] armorSound;
11480 	}
11481 }
11482 
ShouldModifyMorale() const11483 bool Actor::ShouldModifyMorale() const
11484 {
11485 	// pst ignores it for pcs, treating it more like reputation
11486 	if (pstflags) {
11487 		return Modified[IE_EA] != EA_PC;
11488 	}
11489 
11490 	// in HoF, everyone else becomes immune to morale failure ("Mental Fortitude" in iwd2)
11491 	if (core->GetGame()->HOFMode) {
11492 		return Modified[IE_EA] == EA_PC;
11493 	}
11494 
11495 	return true;
11496 }
11497 
GetRaceName() const11498 const char* Actor::GetRaceName() const
11499 {
11500 	if (raceID2Name.count(BaseStats[IE_RACE])) {
11501 		return raceID2Name[BaseStats[IE_RACE]];
11502 	} else {
11503 		return nullptr;
11504 	}
11505 }
11506 
11507 }
11508