Backported three game hooks from 4.x:

* ROLLCHECK, BESTWEAPON, CANUSEWEAPON

Updated documents.
This commit is contained in:
NovaRain
2023-06-06 21:46:03 +08:00
parent 7f0398c618
commit 7322634780
13 changed files with 276 additions and 4 deletions
+1 -1
View File
@@ -131,7 +131,7 @@ OverrideMusicDir=1
;This will slightly increase the startup time of the game on older computers
AutoSearchSFX=1
;Uncomment these lines to override the names of sound files for game modes
;Uncomment these lines to override the names of sound files used by the engine
;Filenames are limited to 8 characters (without extension)
;MainMenuMusic=07desert
;WorldMapMusic=23world
+6
View File
@@ -70,6 +70,11 @@
#define HOOK_STDPROCEDURE_END (41)
#define HOOK_TARGETOBJECT (42)
#define HOOK_ENCOUNTER (43)
//#define HOOK_ADJUSTPOISON (44) // unimplemented
//#define HOOK_ADJUSTRADS (45) // unimplemented
#define HOOK_ROLLCHECK (46)
#define HOOK_BESTWEAPON (47)
#define HOOK_CANUSEWEAPON (48)
// Valid arguments to list_begin
#define LIST_CRITTERS (0)
@@ -261,6 +266,7 @@
#define check_pid(pid) (get_proto_data(pid, 0) != -1)
// sets the status of an unusable weapon that cannot be used in combat
// use the HOOK_CANUSEWEAPON hook with weapon_is_unusable macro to override the engine value
#define set_weapon_unusable(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwor 0x00000010)
#define set_weapon_usable(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0xFFFFFFEF)
#define weapon_is_unusable(item) (get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0x00000010)
+59
View File
@@ -795,3 +795,62 @@ int arg2 - 1 when the encounter occurs is a special encounter, 0 otherwise
int ret0 - overrides the map ID, or pass -1 for event type 0 to cancel the encounter and continue traveling
int ret1 - pass 1 to cancel the encounter and load the specified map from the ret0 (only for event type 0)
```
-------------------------------------------
#### `HOOK_ROLLCHECK (hs_rollcheck.int)`
Runs when a game event performs a random roll to check the chance of success or failure.
```
int arg0 - event type:
1 - checks the chance of an attack hitting the target
2 - checks the chance of a bullet from a burst hitting the target (for burst attacks)
3 - checks the chance when using skills (not listed below)
4 - check the chance of using Repair skill
5 - check the chance of using Doctor skill
6 - check the chance of using Steal skill for the thief (usually the player)
7 - the second Steal skill chance check for the target to catch the thief, in which the target's failure is the thief's success result
int arg1 - the value of roll result (see ROLL_* constants), which is calculated as:
for ROLL_CRITICAL_SUCCESS: random(1, 100) <= (random_chance / 10) + bonus
for ROLL_CRITICAL_FAILURE: random(1, 100) <= -random_chance / 10
int arg2 - the chance value
int arg3 - the bonus value, used when checking critical success
int arg4 - random chance, calculated as: (chance - random(1, 100)), where a negative value is a failure check (ROLL_FAILURE)
int ret0 - overrides the roll result
```
-------------------------------------------
#### `HOOK_BESTWEAPON (hs_bestweapon.int)`
Runs when the AI decides which weapon is the best while searching the inventory for a weapon to equip in combat.\
This also runs when the player presses the "Use Best Weapon" button on the party member control panel.
```
Critter arg0 - the critter searching for a weapon
Item arg1 - the best weapon chosen from two items
Item arg2 - the first choice of weapon
Item arg3 - the second choice of weapon
Critter arg4 - the target of the critter (can be 0)
Item ret0 - overrides the chosen best weapon
```
-------------------------------------------
#### `HOOK_CANUSEWEAPON (hs_canuseweapon.int)`
Run when the AI checks whether it can use a weapon, or when the game checks whether the player can use an item (weapon) in hand slot.\
For AI, this mostly happens when NPCs try to find weapons in their inventory or on the map.\
For the player, this happens when the game updates the item data for active item slots on the interface bar.
```
Critter arg0 - the critter doing the check
Item arg1 - the item being checked
int arg2 - attack type (see ATKTYPE_* constants), or -1 for dude_obj
int arg3 - original result of engine function: 1 - can use, 0 - cannot use
int ret0 - overrides the result of engine function. Any non-zero value allows using the weapon
```
+4 -1
View File
@@ -47,7 +47,8 @@ FUNC(action_use_skill_on_, 0x41255C)
FUNC(add_bar_box_, 0x4616F0)
FUNC(adjust_ac_, 0x4715F8)
FUNC(adjust_fid_, 0x4716E8)
FUNC(ai_can_use_weapon_, 0x4298EC) // (GameObject *aCritter<eax>, int aWeapon<edx>, int a2Or3<ebx>) returns 1 or 0
FUNC(ai_best_weapon_, 0x4293BC)
FUNC(ai_can_use_weapon_, 0x4298EC) // (fo::GameObject *aCritter<eax>, int aWeapon<edx>, int a2Or3<ebx>) returns 1 or 0
FUNC(ai_cap_, 0x4280B4)
FUNC(ai_check_drugs_, 0x428480)
FUNC(ai_danger_source_, 0x428F4C)
@@ -630,6 +631,8 @@ FUNC(register_object_turn_towards_, 0x414C50) // int aObj<eax>, int aTile<edx>
FUNC(remove_bk_process_, 0x4C8DC4)
FUNC(report_explosion_, 0x413144)
FUNC(reset_box_bar_win_, 0x4614A0)
FUNC(roll_check_, 0x4A3000)
FUNC(roll_check_critical_, 0x4A3030)
FUNC(roll_random_, 0x4A30C0)
FUNC(runProgram_, 0x46E154) // eax - programPtr, called once for each program after first loaded - hooks program to game and UI events
FUNC(scr_build_lookup_table_, 0x4A49D0)
+1
View File
@@ -20,6 +20,7 @@
changed to stdcall wrappers instead for stability
*/
WRAP_WATCOM_FUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, buf, BYTE*, count)
WRAP_WATCOM_FUNC3(bool, ai_can_use_weapon, fo::GameObject*, critter, fo::GameObject*, item, DWORD, hitMode)
WRAP_WATCOM_FUNC3(long, ai_have_ammo, fo::GameObject*, critter, fo::GameObject*, item, fo::GameObject**, outAmmo)
WRAP_WATCOM_FUNC3(long, ai_pick_hit_mode, fo::GameObject*, source, fo::GameObject*, item, fo::GameObject*, target)
WRAP_WATCOM_FUNC3(long, ai_magic_hands, fo::GameObject*, source, fo::GameObject*, object, long, msgNumber)
+7 -1
View File
@@ -19,7 +19,13 @@ namespace game
namespace sf = sfall;
static const long aiUseItemAPCost = 2;
/*
// Implementation of ai_can_use_weapon_ engine function with the HOOK_CANUSEWEAPON hook
bool CombatAI::ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon, long hitMode) {
bool result = fo::func::ai_can_use_weapon(source, weapon, hitMode);
return sf::CanUseWeaponHook_Invoke(result, source, weapon, hitMode);
}
*/
static long drugUsePerfFixMode;
void __stdcall CombatAI::ai_check_drugs(fo::GameObject* source) {
+2
View File
@@ -15,6 +15,8 @@ class CombatAI {
public:
static void init();
//static bool ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon, long hitMode);
static void __stdcall ai_check_drugs(fo::GameObject* source);
};
+5
View File
@@ -106,6 +106,11 @@ static HooksInjectInfo injectHooks[] = {
{HOOK_STDPROCEDURE_END, Inject_ScriptProcedureHook2, false},
{HOOK_TARGETOBJECT, Inject_TargetObjectHook, false},
{HOOK_ENCOUNTER, Inject_EncounterHook, false},
{-1, nullptr, false}, // dummy
{-1, nullptr, false}, // dummy
{HOOK_ROLLCHECK, Inject_RollCheckHook, false},
{HOOK_BESTWEAPON, Inject_BestWeaponHook, false},
{HOOK_CANUSEWEAPON, Inject_CanUseWeaponHook, false},
};
void HookScripts::InjectingHook(int hookId) {
+5
View File
@@ -69,6 +69,11 @@ enum HookType
HOOK_STDPROCEDURE_END = 41,
HOOK_TARGETOBJECT = 42,
HOOK_ENCOUNTER = 43,
// HOOK_ADJUSTPOISON = 44, // unimplemented
// HOOK_ADJUSTRADS = 45, // unimplemented
HOOK_ROLLCHECK = 46,
HOOK_BESTWEAPON = 47,
HOOK_CANUSEWEAPON = 48,
HOOK_COUNT
};
+115 -1
View File
@@ -228,7 +228,11 @@ static void __fastcall FindTargetHook_Script(DWORD* target, fo::GameObject* atta
}
EndHook();
}
/*
void FindTargetHook_Invoke(fo::GameObject* targets[], fo::GameObject* attacker) {
if (HookScripts::HookHasScript(HOOK_FINDTARGET)) FindTargetHook_Script((DWORD*)targets, attacker);
}
*/
static void __declspec(naked) FindTargetHook() {
__asm {
push eax;
@@ -426,6 +430,99 @@ default:
}
}
static fo::GameObject* __stdcall BestWeaponHook_Script(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target) {
BeginHook();
argCount = 5;
args[0] = (DWORD)source;
args[1] = (DWORD)bestWeapon;
args[2] = (DWORD)weapon1;
args[3] = (DWORD)weapon2;
args[4] = (DWORD)target;
RunHookScript(HOOK_BESTWEAPON);
if (cRet > 0) bestWeapon = (fo::GameObject*)rets[0];
EndHook();
return bestWeapon;
}
static void __declspec(naked) ai_search_inven_weap_hook() {
__asm {
push ecx; // target
push ebx; // weapon2 (secondary)
push edx; // weapon1 (primary)
push eax; // source
call fo::funcoffs::ai_best_weapon_;
push eax; // bestWeapon
call BestWeaponHook_Script;
retn;
}
}
/*
fo::GameObject* BestWeaponHook_Invoke(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target) {
return (HookScripts::HookHasScript(HOOK_BESTWEAPON))
? BestWeaponHook_Script(bestWeapon, source, weapon1, weapon2, target)
: bestWeapon;
}
*/
static bool __stdcall CanUseWeaponHook_Script(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode) {
BeginHook();
argCount = 4;
args[0] = (DWORD)source;
args[1] = (DWORD)weapon;
args[2] = hitMode;
args[3] = result;
RunHookScript(HOOK_CANUSEWEAPON);
if (cRet > 0) result = (rets[0]) ? true : false;
EndHook();
return result; // only 0 and 1
}
static void __declspec(naked) AICanUseWeaponHook() {
__asm {
push ecx;
push ebx; // hitMode
push edx; // weapon
push eax; // source
call fo::funcoffs::ai_can_use_weapon_;
push eax; // result
call CanUseWeaponHook_Script;
//and eax, 1;
pop ecx;
retn;
}
}
static void __declspec(naked) CanUseWeaponHook() {
__asm {
push ecx;
push edx;
push -1; // hitMode (indefinite)
push eax; // weapon
push ds:[FO_VAR_obj_dude]; // source
call fo::funcoffs::can_use_weapon_; // 1 - can't use
xor al, 1;
push eax; // result
call CanUseWeaponHook_Script;
xor al, 1; // invert
pop edx;
pop ecx;
retn;
}
}
/*
bool CanUseWeaponHook_Invoke(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode) {
return (HookScripts::HookHasScript(HOOK_CANUSEWEAPON))
? CanUseWeaponHook_Script(result, source, weapon, hitMode)
: result;
}
*/
////////////////////////////////////////////////////////////////////////////////
void Inject_ToHitHook() {
@@ -502,6 +599,21 @@ void Inject_TargetObjectHook() {
SafeWrite8(0x44C26E, 0x17);
}
void Inject_BestWeaponHook() {
HookCall(0x429A59, ai_search_inven_weap_hook);
}
void Inject_CanUseWeaponHook() {
const DWORD aiCanUseWpnHkAddr[] = {
0x429A1B, // ai_search_inven_weap_
0x429CF2, // ai_search_environ_
0x429E1C // ai_pick_hit_mode_
};
HookCalls(AICanUseWeaponHook, aiCanUseWpnHkAddr);
const DWORD canUseWpnHkAddr[] = {0x45F05E, 0x45F1C1, 0x45F203, 0x45F36A}; // intface_update_items_
HookCalls(CanUseWeaponHook, canUseWpnHkAddr);
}
void InitCombatHookScripts() {
HookScripts::LoadHookScript("hs_tohit", HOOK_TOHIT);
HookScripts::LoadHookScript("hs_afterhitroll", HOOK_AFTERHITROLL);
@@ -512,6 +624,8 @@ void InitCombatHookScripts() {
HookScripts::LoadHookScript("hs_ammocost", HOOK_AMMOCOST);
HookScripts::LoadHookScript("hs_onexplosion", HOOK_ONEXPLOSION);
HookScripts::LoadHookScript("hs_targetobject", HOOK_TARGETOBJECT);
HookScripts::LoadHookScript("hs_bestweapon", HOOK_BESTWEAPON);
HookScripts::LoadHookScript("hs_canuseweapon", HOOK_CANUSEWEAPON);
}
}
+5
View File
@@ -16,9 +16,14 @@ void Inject_ItemDamageHook();
void Inject_AmmoCostHook();
void Inject_OnExplosionHook();
void Inject_TargetObjectHook();
void Inject_BestWeaponHook();
void Inject_CanUseWeaponHook();
int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds);
long CalcApCostHook_Invoke(fo::GameObject* source, long hitMode, long isCalled, long cost, fo::GameObject* weapon);
//void FindTargetHook_Invoke(fo::GameObject* targets[], fo::GameObject* attacker);
//fo::GameObject* BestWeaponHook_Invoke(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target);
//bool CanUseWeaponHook_Invoke(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode);
}
+65
View File
@@ -631,6 +631,66 @@ cancelEnc:
}
}
static long __stdcall RollCheckHook_Script(long roll, long chance, long bonus, long randomChance, long calledFrom) {
long hookType;
switch (calledFrom - 5) {
case 0x42388E: // compute_attack_
case 0x4234D1: // compute_spray_
hookType = 1; // single and burst attack hit event
break;
case 0x42356C: // compute_spray_
hookType = 2; // burst attack bullet hit event
break;
case 0x4AAB29: // skill_result_
hookType = 3; // common skill check event
break;
case 0x4AB3B6: // skill_use_
hookType = 4; // SKILL_REPAIR
break;
case 0x4AB8B5: // skill_use_
hookType = 5; // SKILL_DOCTOR
break;
case 0x4ABC9F: // skill_check_stealing_
hookType = 6; // SKILL_STEAL - source stealing check event
break;
case 0x4ABCE6: // skill_check_stealing_
hookType = 7; // SKILL_STEAL - target stealing check event (fail for success stealing)
break;
default:
return roll; // unsupported hook
}
BeginHook();
argCount = 5;
args[0] = hookType;
args[1] = roll;
args[2] = chance;
args[3] = bonus;
args[4] = randomChance;
RunHookScript(HOOK_ROLLCHECK);
if (cRet) roll = rets[0];
EndHook();
return roll;
}
static void __declspec(naked) roll_check_hook() {
__asm {
push ecx;
push [esp + 0xC + 8]; // calledFrom
push ebx; // random chance value
push esi; // bonus
push edi; // chance value
call fo::funcoffs::roll_check_critical_;
push eax; // roll result
call RollCheckHook_Script;
pop ecx;
retn;
}
}
void Inject_BarterPriceHook() {
const DWORD barterPriceHkAddr[] = {
0x474D4C, // barter_attempt_transaction_ (offers button)
@@ -710,6 +770,10 @@ void Inject_EncounterHook() {
HookCall(0x4C095C, wmRndEncounterOccurred_hook);
}
void Inject_RollCheckHook() {
HookCall(0x4A3020, roll_check_hook);
}
void InitMiscHookScripts() {
HookScripts::LoadHookScript("hs_barterprice", HOOK_BARTERPRICE);
HookScripts::LoadHookScript("hs_useskillon", HOOK_USESKILLON);
@@ -722,6 +786,7 @@ void InitMiscHookScripts() {
HookScripts::LoadHookScript("hs_resttimer", HOOK_RESTTIMER);
HookScripts::LoadHookScript("hs_explosivetimer", HOOK_EXPLOSIVETIMER);
HookScripts::LoadHookScript("hs_encounter", HOOK_ENCOUNTER);
HookScripts::LoadHookScript("hs_rollcheck", HOOK_ROLLCHECK);
}
}
+1
View File
@@ -17,6 +17,7 @@ void Inject_SetGlobalVarHook();
void Inject_RestTimerHook();
void Inject_ExplosiveTimerHook();
void Inject_EncounterHook();
void Inject_RollCheckHook();
long PerceptionRangeHook_Invoke(fo::GameObject* watcher, fo::GameObject* target, long type, long result);