From 2aa77552f5e74bc4ce12517903a991b32c4d0c18 Mon Sep 17 00:00:00 2001 From: NovaRain Date: Sun, 24 Jan 2021 22:54:20 +0800 Subject: [PATCH] Added an option about max HP calculation to the stats ini file Added an update to the HP stats when the map is loaded first time. Code edits to Animations.cpp. Minor code edits. --- artifacts/config_files/Stats.ini | 4 + sfall/AI.cpp | 2 +- sfall/Animations.cpp | 17 +-- sfall/BugFixes.cpp | 4 +- sfall/DebugEditor.cpp | 2 +- sfall/Define.h | 23 +++ sfall/FalloutFuncs_def.h | 5 + sfall/FalloutStructs.h | 237 ++++++++++++++++++++++++++++++- sfall/Objects.cpp | 12 +- sfall/ScriptOps/ObjectsOps.hpp | 6 +- sfall/ScriptOps/StatsOps.hpp | 16 +-- sfall/Stats.cpp | 71 ++++++--- sfall/Stats.h | 1 + 13 files changed, 349 insertions(+), 51 deletions(-) diff --git a/artifacts/config_files/Stats.ini b/artifacts/config_files/Stats.ini index 82b86166..447764a6 100644 --- a/artifacts/config_files/Stats.ini +++ b/artifacts/config_files/Stats.ini @@ -6,6 +6,10 @@ ;multi[x] - where x is from 0 to 6, the multiplier of st/pe/en/etc. ;shift[x] - shifts the base stat before the multiplication +[Main] +;Set to 1 to take the bonus values of SPECIAL stats into account when calculating maximum hit points +;Set to 0 for the original behavior (bonus stats do not affect maximum hit points) +HPDependOnBonusStats=0 ;max hp [7] diff --git a/sfall/AI.cpp b/sfall/AI.cpp index 41e7cea3..9f76c087 100644 --- a/sfall/AI.cpp +++ b/sfall/AI.cpp @@ -34,7 +34,7 @@ static std::tr1::unordered_map sources; // Returns the friendly critter or any blocking object in the line of fire TGameObj* AI_CheckShootAndFriendlyInLineOfFire(TGameObj* object, long targetTile, long team) { - if (object && object->Type() == OBJ_TYPE_CRITTER && object->critter.teamNum != team) { // is not friendly fire + if (object && object->IsCritter() && object->critter.teamNum != team) { // is not friendly fire long objTile = object->tile; if (objTile == targetTile) return nullptr; diff --git a/sfall/Animations.cpp b/sfall/Animations.cpp index e2d2af79..f74189b9 100644 --- a/sfall/Animations.cpp +++ b/sfall/Animations.cpp @@ -22,14 +22,14 @@ #include "FalloutEngine.h" #include "LoadGameHook.h" -static const int animRecordSize = 2656; +static const int animRecordSize = sizeof(AnimationSet); static const int sadSize = 3240; static int animationLimit = 32; //pointers to new animation struct arrays -static BYTE *anim_set; -static BYTE *sad; +static std::vector new_anim_set; +static std::vector new_sad; static DWORD animSetAddr = FO_VAR_anim_set; static DWORD sadAddr = FO_VAR_sad; @@ -241,11 +241,11 @@ void ApplyAnimationsAtOncePatches(signed char aniMax) { if (aniMax <= 32) return; //allocate memory to store larger animation struct arrays - anim_set = new BYTE[animRecordSize * (aniMax + 1)]; - sad = new BYTE[sadSize * (aniMax + 1)]; + new_anim_set.resize(aniMax + 1); + new_sad.resize(sadSize * (aniMax + 1)); - animSetAddr = reinterpret_cast(anim_set); - sadAddr = reinterpret_cast(sad); + animSetAddr = reinterpret_cast(new_anim_set.data()); + sadAddr = reinterpret_cast(new_sad.data()); //set general animation limit check (old 20) aniMax-12 -- +12 reserved for PC movement(4) + other critical animations(8)? SafeWrite8(0x413C07, aniMax - 12); @@ -362,7 +362,4 @@ void Animations_Init() { } void Animations_Exit() { - if (animationLimit <= 32) return; - delete[] anim_set; - delete[] sad; } diff --git a/sfall/BugFixes.cpp b/sfall/BugFixes.cpp index 977ba865..3e14109e 100644 --- a/sfall/BugFixes.cpp +++ b/sfall/BugFixes.cpp @@ -1387,7 +1387,7 @@ end: static int __stdcall ItemCountFix(TGameObj* who, TGameObj* item) { int count = 0; for (int i = 0; i < who->invenSize; i++) { - TGameObj::TInvenRec* tableItem = &who->invenTable[i]; + TGameObj::InvenItem* tableItem = &who->invenTable[i]; if (tableItem->object == item) { count += tableItem->count; } else if (ItemGetType(tableItem->object) == item_type_container) { @@ -2058,7 +2058,7 @@ static void __stdcall combat_attack_gcsd() { if ((*ptr_main_ctd).targetDamage > (*ptr_gcsd)->maxDamage) { (*ptr_main_ctd).targetDamage = (*ptr_gcsd)->maxDamage; } - if (damage > (*ptr_main_ctd).targetDamage && (*ptr_main_ctd).target->Type() == OBJ_TYPE_CRITTER) { + if (damage > (*ptr_main_ctd).targetDamage && (*ptr_main_ctd).target->IsCritter()) { long cHP = (*ptr_main_ctd).target->critter.health; if (cHP > (*ptr_gcsd)->maxDamage && cHP <= damage) { (*ptr_main_ctd).targetFlags &= ~DAM_DEAD; // unset diff --git a/sfall/DebugEditor.cpp b/sfall/DebugEditor.cpp index 4d5002b6..998d939d 100644 --- a/sfall/DebugEditor.cpp +++ b/sfall/DebugEditor.cpp @@ -104,7 +104,7 @@ static void RunEditorInternal(SOCKET &s) { for (int tile = 0; tile < 40000; tile++) { TGameObj* obj = ObjFindFirstAtTile(elv, tile); while (obj) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { vec.push_back(reinterpret_cast(obj)); } obj = ObjFindNextAtTile(); diff --git a/sfall/Define.h b/sfall/Define.h index 869a6309..cae632eb 100644 --- a/sfall/Define.h +++ b/sfall/Define.h @@ -187,6 +187,19 @@ enum GlobalVar : long GVAR_CAR_PLACED_TILE = 633, }; +// Physical material type, used for items and tiles. +enum Material : long +{ + MAT_Glass = 0x0, + MAT_Metal = 0x1, + MAT_Plastic = 0x2, + MAT_Wood = 0x3, + MAT_Dirt = 0x4, + MAT_Stone = 0x5, + MAT_Cement = 0x6, + MAT_Leather = 0x7 +}; + namespace ObjectFlag { enum ObjectFlag : DWORD { Mouse_3d = 0x1, @@ -411,6 +424,16 @@ enum Trait : long TRAIT_count = 16, }; +enum ScenerySubType : long +{ + SCENTYPE_DOOR = 0, + SCENTYPE_STAIRS = 1, + SCENTYPE_ELEVATOR = 2, + SCENTYPE_LADDER_BOTTOM = 3, + SCENTYPE_LADDER_TOP = 4, + SCENTYPE_GENERIC = 5 +}; + // proto.h: stats // enum Stat : long diff --git a/sfall/FalloutFuncs_def.h b/sfall/FalloutFuncs_def.h index 3b30ba23..27095154 100644 --- a/sfall/FalloutFuncs_def.h +++ b/sfall/FalloutFuncs_def.h @@ -182,6 +182,8 @@ WRAP_WATCOM_FUNC2(long, PerkLevel, perk_level_, TGameObj*, critter, long, perkId WRAP_WATCOM_FUNC6(long, PickDeath, pick_death_, TGameObj*, attacker, TGameObj*, target, TGameObj*, weapon, long, amount, long, anim, long, hitFromBack) WRAP_WATCOM_FUNC0(void, ProcessBk, process_bk_) WRAP_WATCOM_FUNC0(void, ProtoDudeUpdateGender, proto_dude_update_gender_) +// Places pointer to a prototype structure into ptrPtr and returns 0 on success or -1 on failure +WRAP_WATCOM_FUNC2(long, ProtoPtr, proto_ptr_, long, pid, sProto**, ptrPtr) WRAP_WATCOM_FUNC2(void, QueueClearType, queue_clear_type_, long, qType, void*, func) // removes all events of the given type and performs func before removal WRAP_WATCOM_FUNC2(void*, QueueFindFirst, queue_find_first_, TGameObj*, object, long, qType) WRAP_WATCOM_FUNC2(void*, QueueFindNext, queue_find_next_, TGameObj*, object, long, qType) @@ -204,7 +206,10 @@ WRAP_WATCOM_FUNC2(long, ScrPtr, scr_ptr_, long, scriptId, TScript**, scriptPtr) WRAP_WATCOM_FUNC1(long, ScrRemove, scr_remove_, long, scriptID) WRAP_WATCOM_FUNC1(void, SetFocusFunc, set_focus_func_, void*, func) WRAP_WATCOM_FUNC1(long, SkillIsTagged, skill_is_tagged_, long, skill) +WRAP_WATCOM_FUNC2(long, StatGetBase, stat_get_base_, TGameObj*, critter, long, statID) WRAP_WATCOM_FUNC2(long, StatGetBaseDirect, stat_get_base_direct_, TGameObj*, critter, long, statID) +//WRAP_WATCOM_FUNC2(void, SkillGetTags, skill_get_tags_, long*, tags, long, num) +//WRAP_WATCOM_FUNC2(void, SkillSetTags, skill_set_tags_, long*, tags, long, num) WRAP_WATCOM_FUNC2(long, StatLevel, stat_level_, TGameObj*, critter, long, statId) WRAP_WATCOM_FUNC1(long, TextFont, text_font_, long, fontNum) WRAP_WATCOM_FUNC2(long, TileDist, tile_dist_, long, scrTile, long, dstTile) diff --git a/sfall/FalloutStructs.h b/sfall/FalloutStructs.h index 16b2d186..d2fae88b 100644 --- a/sfall/FalloutStructs.h +++ b/sfall/FalloutStructs.h @@ -46,6 +46,29 @@ struct sArt { long total; }; +struct AnimationSet { + long currentAnim; + long counter; + long animCounter; + long flags; + struct Animation { + long number; + long source; + long target; + long data1; + long elevation; + long animCode; + long delay; + long(__fastcall *callFunc)(DWORD, DWORD); + long(__fastcall *callFunc3)(DWORD, DWORD); + long flags; + long data2; + long frmPtr; + } animations[55]; +}; + +static_assert(sizeof(AnimationSet) == 2656, "Incorrect AnimationSet definition."); + // Bounding rectangle, used by tile_refresh_rect and related functions. struct BoundRect { long x; @@ -77,7 +100,7 @@ struct TGameObj { long elevation; long invenSize; long invenMax; - struct TInvenRec { + struct InvenItem { TGameObj *object; long count; } *invenTable; @@ -509,6 +532,218 @@ struct PremadeChar { char unknown[20]; }; +// In-memory PROTO structure, not the same as PRO file format. +struct sProto { + struct Tile { + long scriptId; + Material material; + }; + + struct Item { + struct Weapon { + long animationCode; + long minDamage; + long maxDamage; + long damageType; + long maxRange[2]; + long projectilePid; + long minStrength; + long movePointCost[2]; + long critFailTable; + long perk; + long burstRounds; + long caliber; + long ammoPid; + long maxAmmo; + // shot sound ID + long soundId; + long gap_68; + }; + + struct Ammo { + long caliber; + long packSize; + long acAdjust; + long drAdjust; + long damageMult; + long damageDiv; + char gap_3c[48]; + }; + + struct Armor { + long armorClass; + // for each DamageType + long damageResistance[7]; + // for each DamageType + long damageThreshold[7]; + long perk; + long maleFID; + long femaleFID; + }; + + struct Container { + // container size capacity (not weight) + long maxSize; + // 1 - has use animation, 0 - no animation + long openFlags; + }; + + struct Drug { + long stats[3]; + long immediateEffect[3]; + struct DelayedEffect { + // delay for the effect + long duration; + // effect amount for each stat + long effect[3]; + } delayed[2]; + long addictionRate; + long addictionEffect; + long addictionOnset; + char gap_68[4]; + }; + + struct Misc { + long powerPid; + long powerType; + long maxCharges; + }; + + struct Key { + long keyCode; + }; + + long flags; + long flagsExt; + long scriptId; // SID 0x0Y00XXXX: Y - type: 0=s_system, 1=s_spatial, 2=s_time, 3=s_item, 4=s_critter; XXXX - index in scripts.lst; 0xFFFFFFFF no attached script + ItemType type; + + union { + Weapon weapon; + Ammo ammo; + Armor armor; + Container container; + Drug drug; + Misc misc; + Key key; + }; + Material material; // should be at 0x6C + long size; + long weight; + long cost; + long inventoryFid; + BYTE soundId; + }; + + struct Critter { + struct Stats { + long strength; + long perception; + long endurance; + long charisma; + long intelligence; + long agility; + long luck; + long health; + // max move points (action points) + long movePoints; + long armorClass; + // not used by engine + long unarmedDamage; + long meleeDamage; + long carryWeight; + long sequence; + long healingRate; + long criticalChance; + long betterCriticals; + // for each DamageType + long damageThreshold[7]; + // for each DamageType + long damageResistance[7]; + long radiationResistance; + long poisonResistance; + long age; + long gender; + }; + + long flags; + long flagsExt; + long scriptId; + long critterFlags; + + Stats base; + Stats bonus; + + long skills[SKILL_count]; + + long bodyType; + long experience; + long killType; + long damageType; + long headFid; + long aiPacket; + long teamNum; + }; + + struct Scenery { + struct Door { + long openFlags; + long keyCode; + }; + struct Stairs { + long elevationAndTile; + long mapId; + }; + struct Elevator { + long id; + long level; + }; + + long flags; + long flagsExt; + long scriptId; + ScenerySubType type; + union { + Door door; + Stairs stairs; + Elevator elevator; + }; + Material material; + char gap_30[4]; + BYTE soundId; + }; + + struct Wall { + long flags; + long flagsExt; + long scriptId; + Material material; + }; + + struct Misc { + long flags; + long flagsExt; + }; + + long pid; + long messageNum; + long fid; + // range 0-8 in hexes + long lightDistance; + // range 0 - 65536 + long lightIntensity; + union { + Tile tile; + Item item; + Critter critter; + Scenery scenery; + Wall wall; + Misc misc; + }; +}; + +static_assert(offsetof(sProto, item) + offsetof(sProto::Item, material) == 0x6C, "Incorrect sProto definition."); + struct ScriptListInfoItem { char fileName[16]; long numLocalVars; diff --git a/sfall/Objects.cpp b/sfall/Objects.cpp index ec3b18c6..48fb0705 100644 --- a/sfall/Objects.cpp +++ b/sfall/Objects.cpp @@ -18,6 +18,7 @@ #include "main.h" #include "FalloutEngine.h" +#include "Stats.h" #include "Objects.h" @@ -89,14 +90,17 @@ pickNewID: // skip PM range (18000 - 83535) } } -// Reassigns object IDs to all critters upon first loading a map +// Reassigns object IDs to all critters upon first loading a map and updates their max HP stat static void map_fix_critter_id() { long npcStartID = 4096; TGameObj* obj = ObjFindFirst(); while (obj) { - if (obj->Type() == OBJ_TYPE_CRITTER && obj->id < PLAYER_ID) { - obj->id = npcStartID++; - SetScriptObjectID(obj); + if (obj->IsCritter()) { + if (obj->id < PLAYER_ID) { + obj->id = npcStartID++; + SetScriptObjectID(obj); + } + Stats_UpdateHPStat(obj); } obj = ObjFindNext(); } diff --git a/sfall/ScriptOps/ObjectsOps.hpp b/sfall/ScriptOps/ObjectsOps.hpp index 5cfe3fa0..ee6867dc 100644 --- a/sfall/ScriptOps/ObjectsOps.hpp +++ b/sfall/ScriptOps/ObjectsOps.hpp @@ -69,7 +69,7 @@ static void __stdcall op_set_script2() { ScrRemove(object->scriptId); object->scriptId = 0xFFFFFFFF; } - if (object->Type() == OBJ_TYPE_CRITTER) { + if (object->IsCritter()) { scriptType = Scripts::SCRIPT_CRITTER; } else { scriptType = Scripts::SCRIPT_ITEM; @@ -283,7 +283,7 @@ static void __stdcall op_make_path2() { func = getBlockingFunc(type); // if the object is not a critter, then there is no need to check tile (tileTo) for blocking - long checkFlag = (objFrom->Type() == OBJ_TYPE_CRITTER); + long checkFlag = (objFrom->IsCritter()); char pathData[800]; long pathLength = MakePathFunc(objFrom, objFrom->tile, tileTo, pathData, checkFlag, (void*)func); @@ -362,7 +362,7 @@ static void __stdcall op_get_party_members2() { DWORD* partyMemberList = *ptr_partyMemberList; for (int i = 0; i < actualCount; i++) { TGameObj* obj = reinterpret_cast(partyMemberList[i * 4]); - if (includeHidden || (obj->Type() == OBJ_TYPE_CRITTER && !CritterIsDead(obj) && !(obj->flags & ObjectFlag::Mouse_3d))) { + if (includeHidden || (obj->IsCritter() && !CritterIsDead(obj) && !(obj->flags & ObjectFlag::Mouse_3d))) { arrays[arrayId].push_back((long)obj); } } diff --git a/sfall/ScriptOps/StatsOps.hpp b/sfall/ScriptOps/StatsOps.hpp index 16ef6e31..c67a0d6a 100644 --- a/sfall/ScriptOps/StatsOps.hpp +++ b/sfall/ScriptOps/StatsOps.hpp @@ -119,7 +119,7 @@ static void __stdcall op_set_critter_base_stat2() { &valArg = opHandler.arg(2); if (obj && statArg.isInt() && valArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { int stat = statArg.rawValue(); if (stat >= 0 && stat < STAT_max_stat) { char* proto = GetProtoPtr(obj->protoId); @@ -145,7 +145,7 @@ static void __stdcall op_set_critter_extra_stat2() { &valArg = opHandler.arg(2); if (obj && statArg.isInt() && valArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { int stat = statArg.rawValue(); if (stat >= 0 && stat < STAT_max_stat) { char* proto = GetProtoPtr(obj->protoId); @@ -171,7 +171,7 @@ static void __stdcall op_get_critter_base_stat2() { const ScriptValue &statArg = opHandler.arg(1); if (obj && statArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { int stat = statArg.rawValue(); if (stat >= 0 && stat < STAT_max_stat) { char* proto = GetProtoPtr(obj->protoId); @@ -198,7 +198,7 @@ static void __stdcall op_get_critter_extra_stat2() { const ScriptValue &statArg = opHandler.arg(1); if (obj && statArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { int stat = statArg.rawValue(); if (stat >= 0 && stat < STAT_max_stat) { char* proto = GetProtoPtr(obj->protoId); @@ -383,7 +383,7 @@ static void __stdcall op_set_critter_current_ap2() { const ScriptValue &apArg = opHandler.arg(1); if (obj && apArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { long ap = apArg.rawValue(); if (ap < 0) ap = 0; obj->critter.movePoints = ap; @@ -447,7 +447,7 @@ static void __stdcall op_set_critter_hit_chance_mod2() { &modArg = opHandler.arg(2); if (obj && maxArg.isInt() && modArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { SetHitChanceMax(obj, maxArg.rawValue(), modArg.rawValue()); } else { opHandler.printOpcodeError(objNotCritter, "set_critter_hit_chance_mod"); @@ -485,7 +485,7 @@ static void __stdcall op_set_critter_pickpocket_mod2() { &modArg = opHandler.arg(2); if (obj && maxArg.isInt() && modArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { SetPickpocketMax(obj, maxArg.rawValue(), modArg.rawValue()); } else { opHandler.printOpcodeError(objNotCritter, "set_critter_pickpocket_mod"); @@ -522,7 +522,7 @@ static void __stdcall op_set_critter_skill_mod2() { const ScriptValue &maxArg = opHandler.arg(1); if (obj && maxArg.isInt()) { - if (obj->Type() == OBJ_TYPE_CRITTER) { + if (obj->IsCritter()) { SetSkillMax(obj, maxArg.rawValue()); } else { opHandler.printOpcodeError(objNotCritter, "set_critter_skill_mod"); diff --git a/sfall/Stats.cpp b/sfall/Stats.cpp index 93746b25..61a622d9 100644 --- a/sfall/Stats.cpp +++ b/sfall/Stats.cpp @@ -22,6 +22,9 @@ #include "Stats.h" +static bool engineDerivedStats = true; +static bool derivedHPwBonus = false; // recalculate the hit points with bonus stat values + static DWORD statMaximumsPC[STAT_max_stat]; static DWORD statMinimumsPC[STAT_max_stat]; static DWORD statMaximumsNPC[STAT_max_stat]; @@ -139,32 +142,31 @@ end: } } -static void __declspec(naked) __stdcall ProtoPtr(DWORD pid, int** proto) { - __asm { - mov eax, [esp + 4]; - mov edx, [esp + 8]; - call proto_ptr_; - retn 8; +static long __stdcall RecalcStat(int stat, int statsValue[]) { + double sum = 0; + for (int i = STAT_st; i <= STAT_lu; i++) { + sum += (statsValue[i] + statFormulas[stat].shift[i]) * statFormulas[stat].multi[i]; } + long calcStatValue = statFormulas[stat].base + (int)floor(sum); + return (calcStatValue < statFormulas[stat].min) ? statFormulas[stat].min : calcStatValue; } static void __stdcall StatRecalcDerived(TGameObj* critter) { - int baseStats[7]; - for (int stat = STAT_st; stat <= STAT_lu; stat++) baseStats[stat] = StatLevel(critter, stat); + long* proto; + if (ProtoPtr(critter->protoId, (sProto**)&proto) == -1) return; - int* proto; - ProtoPtr(critter->protoId, &proto); + int baseStats[7], levelStats[7]; + for (int stat = STAT_st; stat <= STAT_lu; stat++) { + levelStats[stat] = StatLevel(critter, stat); + if (!derivedHPwBonus) baseStats[stat] = StatGetBase(critter, stat); + } - for (int i = STAT_max_hit_points; i <= STAT_poison_resist; i++) { - if (i >= STAT_dmg_thresh && i <= STAT_dmg_resist_explosion) continue; + ((sProto*)proto)->critter.base.health = RecalcStat(STAT_max_hit_points, (derivedHPwBonus) ? levelStats : baseStats); - double sum = 0; - for (int stat = STAT_st; stat <= STAT_lu; stat++) { - sum += (baseStats[stat] + statFormulas[i].shift[stat]) * statFormulas[i].multi[stat]; - } - long calcStat = statFormulas[i].base + (int)floor(sum); - if (calcStat < statFormulas[i].min) calcStat = statFormulas[i].min; - proto[9 + i] = calcStat; // offset from base_stat_srength + for (int stat = STAT_max_move_points; stat <= STAT_poison_resist; stat++) { + if (stat >= STAT_dmg_thresh && stat <= STAT_dmg_resist_explosion) continue; + // offset from base_stat_srength + proto[9 + stat] = RecalcStat(stat, levelStats); } } @@ -180,6 +182,29 @@ static void __declspec(naked) stat_recalc_derived_hack() { } } +void Stats_UpdateHPStat(TGameObj* critter) { + if (engineDerivedStats) return; + + sProto* proto; + if (ProtoPtr(critter->protoId, &proto) == -1) return; + + auto getStatFunc = (derivedHPwBonus) ? StatLevel : StatGetBase; + + double sum = 0; + for (int stat = STAT_st; stat <= STAT_lu; stat++) { + sum += (getStatFunc(critter, stat) + statFormulas[STAT_max_hit_points].shift[stat]) * statFormulas[STAT_max_hit_points].multi[stat]; + } + long calcStatValue = statFormulas[STAT_max_hit_points].base + (int)floor(sum); + if (calcStatValue < statFormulas[STAT_max_hit_points].min) calcStatValue = statFormulas[STAT_max_hit_points].min; + + if (proto->critter.base.health != calcStatValue) { + DebugPrintf("\nWarning: critter PID: %d, ID: %d, has an incorrect base value %d (must be %d) of the max HP stat.", + critter->protoId, critter->id, proto->critter.base.health, calcStatValue); + } + proto->critter.base.health = calcStatValue; + critter->critter.health = calcStatValue + proto->critter.bonus.health; +} + static void __declspec(naked) stat_set_base_hack_allow() { static const DWORD StatSetBaseRet = 0x4AF559; __asm { @@ -301,7 +326,7 @@ void Stats_Init() { MakeCall(0x4AF09C, CalcApToAcBonus, 3); // stat_level_ - // Allow set_critter_stat function to change STAT_unused and STAT_dmg_* stats for the player + // Allow set_critter_stat function to change the base stats of STAT_unused and STAT_dmg_* for the player MakeCall(0x4AF54E, stat_set_base_hack_allow); MakeCall(0x455D65, op_set_critter_stat_hack); // STAT_unused for other critters @@ -334,6 +359,7 @@ void Stats_Init() { std::string statsFile = GetConfigString("Misc", "DerivedStats", "", MAX_PATH); if (!statsFile.empty()) { + engineDerivedStats = false; MakeJump(0x4AF6FC, stat_recalc_derived_hack); // overrides function // STAT_st + STAT_en * 2 + 15 @@ -364,10 +390,13 @@ void Stats_Init() { // STAT_en * 5 statFormulas[STAT_poison_resist].multi[STAT_en] = 5; // poison resist - char key[6], buf2[256], buf3[256]; const char* statFile = statsFile.insert(0, ".\\").c_str(); if (GetFileAttributes(statFile) == INVALID_FILE_ATTRIBUTES) return; + derivedHPwBonus = (iniGetInt("Main", "HPDependOnBonusStats", 0, statFile) != 0); + + char key[6], buf2[256], buf3[256]; + for (int i = STAT_max_hit_points; i <= STAT_poison_resist; i++) { if (i >= STAT_dmg_thresh && i <= STAT_dmg_resist_explosion) continue; diff --git a/sfall/Stats.h b/sfall/Stats.h index 121320d8..c4d5197e 100644 --- a/sfall/Stats.h +++ b/sfall/Stats.h @@ -20,6 +20,7 @@ void Stats_Init(); void Stats_OnGameLoad(); +void Stats_UpdateHPStat(TGameObj* critter); long __stdcall GetStatMax(int stat, int isNPC); long __stdcall GetStatMin(int stat, int isNPC);