diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index c326a6b0..931917bb 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -240,6 +240,7 @@ #define npc_engine_level_up(toggle) sfall_func1("npc_engine_level_up", toggle) #define obj_under_cursor(crSwitch, inclDude) sfall_func2("obj_under_cursor", crSwitch, inclDude) #define outlined_object sfall_func0("outlined_object") +#define real_dude_obj sfall_func0("real_dude_obj") #define set_cursor_mode(mode) sfall_func1("set_cursor_mode", mode) #define set_flags(obj, flags) sfall_func2("set_flags", obj, flags) #define set_ini_setting(setting, value) sfall_func2("set_ini_setting", setting, value) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 76dbf302..32f7d74c 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -415,6 +415,9 @@ Some utility/math functions are available: > object sfall_func0("outlined_object") - returns an object that is currently highlighted by hovering the mouse above it +> void sfall_func0("real_dude_obj") +- returns the initial dude_obj after taking control of other critters + > int sfall_func0("get_cursor_mode") - returns the current cursor mode (0 - movement cursor, 1 - command cursor, 2 - targeting cursor) - mode 4 to 10 are Skilldex skills (yellow targeting cursor) diff --git a/sfall/FalloutEngine.cpp b/sfall/FalloutEngine.cpp index 96be785b..ace1ac54 100644 --- a/sfall/FalloutEngine.cpp +++ b/sfall/FalloutEngine.cpp @@ -1004,6 +1004,21 @@ long __stdcall StatLevel(TGameObj* critter, long statId) { } } +long __stdcall PerkLevel(TGameObj* critter, long perkId) { + __asm { + mov edx, perkId; + mov eax, critter; + call perk_level_; + } +} + +long __stdcall TraitLevel(long traitID) { + __asm { + mov eax, traitID; + call trait_level_; + } +} + long __stdcall QueueFindFirst(TGameObj* object, long qType) { __asm { mov edx, qType; diff --git a/sfall/FalloutEngine.h b/sfall/FalloutEngine.h index 422e0507..27929886 100644 --- a/sfall/FalloutEngine.h +++ b/sfall/FalloutEngine.h @@ -1028,6 +1028,10 @@ void __fastcall DisplayTargetInventory(long inventoryOffset, long visibleOffset, long __stdcall StatLevel(TGameObj* critter, long statId); +long __stdcall PerkLevel(TGameObj* critter, long perkId); + +long __stdcall TraitLevel(long traitID); + long __stdcall QueueFindFirst(TGameObj* object, long qType); long __stdcall NewObjId(); diff --git a/sfall/LoadGameHook.cpp b/sfall/LoadGameHook.cpp index 1ca7006a..3bce5778 100644 --- a/sfall/LoadGameHook.cpp +++ b/sfall/LoadGameHook.cpp @@ -147,13 +147,7 @@ static DWORD _stdcall CombatSaveTest() { return 0; } int ap = StatLevel(*ptr_obj_dude, STAT_max_move_points); - int bonusmove; - __asm { - mov edx, PERK_bonus_move; - mov eax, ds:[_obj_dude]; - call perk_level_; - mov bonusmove, eax; - } + int bonusmove = PerkLevel(*ptr_obj_dude, PERK_bonus_move); if (*(DWORD*)(*(DWORD*)_obj_dude + 0x40) != ap || bonusmove * 2 != *(DWORD*)_combat_free_move) { DisplayConsoleMessage(SaveFailMsg); return 0; diff --git a/sfall/PartyControl.cpp b/sfall/PartyControl.cpp index 187cc63c..562a0140 100644 --- a/sfall/PartyControl.cpp +++ b/sfall/PartyControl.cpp @@ -35,7 +35,7 @@ bool npcAutoLevelEnabled; bool npcEngineLevelUp = true; static DWORD Mode; -static int IsControllingNPC = 0; +static bool IsControllingNPC = false; static bool SkipCounterAnim = false; static std::vector Chars; static int DelayedExperience; @@ -52,7 +52,7 @@ static DWORD real_unspent_skill_points; static DWORD real_sneak_working; //static DWORD real_sneak_queue_time; static DWORD real_hand; -static DWORD real_itemButtonItems[6*2]; +static DWORD real_itemButtonItems[6 * 2]; static DWORD real_perkLevelDataList[PERK_count]; //static DWORD real_drug_gvar[6]; //static DWORD real_jet_gvar; @@ -74,10 +74,10 @@ static bool _stdcall IsInPidList(TGameObj* obj) { static void _stdcall SetInventoryCheck(bool skip) { if (skip) { SafeWrite16(0x46E7CD, 0x9090); //Inventory check - SafeWrite32(0x46E7Cf, 0x90909090); + SafeWrite32(0x46E7CF, 0x90909090); } else { SafeWrite16(0x46E7CD, 0x850F); //Inventory check - SafeWrite32(0x46E7Cf, 0x4B1); + SafeWrite32(0x46E7CF, 0x4B1); } } @@ -156,7 +156,7 @@ static void TakeControlOfNPC(TGameObj* npc) { *ptr_obj_dude = npc; *ptr_inven_dude = npc; - IsControllingNPC = 1; + IsControllingNPC = true; DelayedExperience = 0; SetInventoryCheck(true); @@ -165,10 +165,13 @@ static void TakeControlOfNPC(TGameObj* npc) { // restores the real dude state static void RestoreRealDudeState() { + assert(real_dude != nullptr); + *ptr_map_elevation = real_dude->elevation; *ptr_obj_dude = real_dude; *ptr_inven_dude = real_dude; + *ptr_inven_pid = real_dude->pid; *ptr_itemCurrentItem = real_hand; memcpy(ptr_itemButtonItems, real_itemButtonItems, sizeof(DWORD) * 6 * 2); @@ -183,8 +186,6 @@ static void RestoreRealDudeState() { *ptr_sneak_working = real_sneak_working; SkillSetTags(real_tag_skill, 4); - *ptr_inven_pid = real_dude->pid; - if (DelayedExperience > 0) { StatPcAddExperience(DelayedExperience); } @@ -196,7 +197,7 @@ static void RestoreRealDudeState() { InterfaceRedraw(); SetInventoryCheck(false); - IsControllingNPC = 0; + IsControllingNPC = false; real_dude = nullptr; } @@ -235,8 +236,8 @@ static void __declspec(naked) FidChangeHook() { je skip; push eax; mov eax, [eax+0x20]; // current fid - and eax, 0xffff0fff; - and edx, 0x0000f000; + and eax, 0xFFFF0FFF; + and edx, 0x0000F000; or edx, eax; // only change one octet with weapon type pop eax; skip: @@ -260,7 +261,7 @@ static void __stdcall DisplayCantDoThat() { // 1 skip handler, -1 don't skip int __stdcall PartyControl_SwitchHandHook(TGameObj* item) { - if (IsControllingNPC > 0 && ItemGetType(item) == item_type_weapon) { + if (IsControllingNPC && ItemGetType(item) == item_type_weapon) { int canUse; /* check below uses AI packets and skills to check if weapon is usable __asm { @@ -273,7 +274,7 @@ int __stdcall PartyControl_SwitchHandHook(TGameObj* item) { }*/ int fId = (*ptr_obj_dude)->artFID; char weaponCode = AnimCodeByWeapon(item); - fId = (fId & 0xffff0fff) | (weaponCode << 12); + fId = (fId & 0xFFFF0FFF) | (weaponCode << 12); // check if art with this weapon exists __asm { mov eax, fId; @@ -288,6 +289,19 @@ int __stdcall PartyControl_SwitchHandHook(TGameObj* item) { return -1; } +long __fastcall GetRealDudePerk(TGameObj* source, long perk) { + if (IsControllingNPC && source == real_dude) { + return real_perkLevelDataList[perk]; + } + return PerkLevel(source, perk); +} + +long __fastcall GetRealDudeTrait(TGameObj* source, long trait) { + if (IsControllingNPC && source == real_dude) { + return (trait == real_traits[0] || trait == real_traits[1]) ? 1 : 0; + } + return TraitLevel(trait); +} static void __declspec(naked) CombatWrapper_v2() { __asm { @@ -329,38 +343,42 @@ gonormal: static void __declspec(naked) stat_pc_add_experience_hook() { __asm { - xor eax, eax - cmp IsControllingNPC, eax - je skip - add DelayedExperience, esi - retn + cmp IsControllingNPC, 0; + je skip; + add DelayedExperience, esi; + retn; skip: - xchg esi, eax - jmp stat_pc_add_experience_ + xchg esi, eax; + jmp stat_pc_add_experience_; } } // prevents using sneak when controlling NPCs static void __declspec(naked) pc_flag_toggle_hook() { __asm { - cmp IsControllingNPC, 0 - je end - call DisplayCantDoThat - retn + cmp IsControllingNPC, 0; + je end; + call DisplayCantDoThat; + retn; end: - call pc_flag_toggle_ - retn + jmp pc_flag_toggle_; } } void __stdcall PartyControlReset() { - if (real_dude != nullptr && IsControllingNPC > 0) { + if (real_dude != nullptr && IsControllingNPC) { RestoreRealDudeState(); } } bool IsNpcControlled() { - return IsControllingNPC != 0; + return IsControllingNPC; +} + +TGameObj* RealDudeObject() { + return real_dude != nullptr + ? real_dude + : *ptr_obj_dude; } static char levelMsg[12], armorClassMsg[12], addictMsg[16]; @@ -442,15 +460,20 @@ void PartyControlInit() { } dlog_f(" Mode %d, Chars read: %d.\n", DL_INIT, Mode, Chars.size()); - HookCall(0x46EBEE, &FidChangeHook); + HookCall(0x46EBEE, FidChangeHook); MakeJump(0x422354, CombatHack_add_noncoms_); - HookCall(0x422D87, &CombatWrapper_v2); - HookCall(0x422E20, &CombatWrapper_v2); + HookCall(0x422D87, CombatWrapper_v2); + HookCall(0x422E20, CombatWrapper_v2); - HookCall(0x454218, &stat_pc_add_experience_hook); // call inside op_give_exp_points_hook - HookCall(0x4124F1, &pc_flag_toggle_hook); - HookCall(0x41279A, &pc_flag_toggle_hook); + HookCall(0x454218, stat_pc_add_experience_hook); // call inside op_give_exp_points_hook + HookCall(0x4124F1, pc_flag_toggle_hook); + HookCall(0x41279A, pc_flag_toggle_hook); + + // Gets dude perks and traits from script while controlling another NPC + // WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code + HookCall(0x458242, GetRealDudePerk); //op_has_trait_hook_ + HookCall(0x458326, GetRealDudeTrait); //op_has_trait_hook_ } else dlogr(" Disabled.", DL_INIT); diff --git a/sfall/PartyControl.h b/sfall/PartyControl.h index 8adca1f1..b51ad6ae 100644 --- a/sfall/PartyControl.h +++ b/sfall/PartyControl.h @@ -25,4 +25,5 @@ void PartyControlInit(); void __stdcall PartyControlReset(); int __stdcall PartyControl_SwitchHandHook(TGameObj* item); bool IsNpcControlled(); +TGameObj* RealDudeObject(); void NpcEngineLevelUpReset(); diff --git a/sfall/ScriptOps/MetaruleOp.hpp b/sfall/ScriptOps/MetaruleOp.hpp index 231fa3bf..2b8b77bb 100644 --- a/sfall/ScriptOps/MetaruleOp.hpp +++ b/sfall/ScriptOps/MetaruleOp.hpp @@ -130,6 +130,7 @@ static const SfallMetarule metaruleArray[] = { {"npc_engine_level_up", sf_npc_engine_level_up, 1, 1}, {"obj_under_cursor", sf_get_obj_under_cursor, 2, 2}, {"outlined_object", sf_outlined_object, 0, 0}, + {"real_dude_obj", sf_real_dude_obj, 0, 0}, {"set_cursor_mode", sf_set_cursor_mode, 1, 1}, {"set_flags", sf_set_flags, 2, 2}, {"set_ini_setting", sf_set_ini_setting, 2, 2}, diff --git a/sfall/ScriptOps/ObjectsOps.hpp b/sfall/ScriptOps/ObjectsOps.hpp index 6ade71f8..416782de 100644 --- a/sfall/ScriptOps/ObjectsOps.hpp +++ b/sfall/ScriptOps/ObjectsOps.hpp @@ -21,6 +21,7 @@ #include "main.h" #include "Inventory.h" +#include "PartyControl.h" #include "ScriptExtender.h" //script control functions @@ -596,6 +597,10 @@ static void sf_item_weight() { opHandler.setReturn(weight); } +static void sf_real_dude_obj() { + opHandler.setReturn(RealDudeObject()); +} + static void sf_lock_is_jammed() { TGameObj* obj = opHandler.arg(0).asObject(); int result; diff --git a/sfall/main.h b/sfall/main.h index 9a8497c0..e0616500 100644 --- a/sfall/main.h +++ b/sfall/main.h @@ -18,8 +18,12 @@ #pragma once +#include //#define WIN32_LEAN_AND_MEAN #include +#include +#include + #include "SafeWrite.h" #include "Logging.h" @@ -94,4 +98,4 @@ T SimplePatch(DWORD *addrs, int numAddrs, const char* iniSection, const char* in dlogr(" Done", DL_INIT); } return value; -} \ No newline at end of file +}