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