From 58532fd902151d8a94896e33cfcbb7928b04de4d Mon Sep 17 00:00:00 2001 From: NovaRain Date: Thu, 9 Jan 2020 11:41:50 +0800 Subject: [PATCH] Fixed duplicate critters being added to the target list for AI Edited hookscripts.txt to mention the fix. Some code correction. --- artifacts/scripting/hookscripts.txt | 8 ++- sfall/AI.cpp | 100 ++++++++++++++++++++++++---- sfall/AI.h | 8 ++- sfall/FalloutEngine.cpp | 2 +- sfall/FalloutEngine.h | 4 ++ sfall/FalloutStructs.h | 2 +- 6 files changed, 105 insertions(+), 19 deletions(-) diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 5c0be51b..b384a942 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -192,9 +192,15 @@ HOOK_FINDTARGET (hs_findtarget.int) Runs when the ai is trying to pick a target in combat. Fallout first chooses a list of 4 likely suspects, then normally sorts them in order of weakness/distance/etc depending on the ai caps of the attacker. This hook replaces that sorting function, allowing you to sort the targets in some arbitrary way. -The return values can include critters that weren't in the list of possible targets, but the additional targets may still be discarded later on in the combat turn if they are out of the attackers perception or the chance of a successful hit is too low. The list of possible targets often includes duplicated entries. +The return values can include critters that weren't in the list of possible targets, but the additional targets may still be discarded later on in the combat turn if they are out of the attackers perception or the chance of a successful hit is too low. The list of possible targets often includes duplicated entries, but this is fixed in sfall 4.2.3/3.8.23. Use sfall_return to give the 4 targets, in order of preference. If you want to specify less than 4 targets, fill in the extra spaces with 0's or pass -1 to skip the return value. +NOTE: The engine can choose targets by the following criteria: +1) The nearest enemy to the attacker. +2) The enemy that attacked the attacker. +3) The enemy that attacked an NPC from the same team as the attacker. +4) The enemy that is attacked by an NPC from the same team as the attacker. + critter arg1 - The attacker critter arg2 - A possible target critter arg3 - A possible target diff --git a/sfall/AI.cpp b/sfall/AI.cpp index d61d487e..82543248 100644 --- a/sfall/AI.cpp +++ b/sfall/AI.cpp @@ -24,10 +24,10 @@ #include "FalloutEngine.h" #include "SafeWrite.h" -typedef stdext::hash_map::const_iterator iter; +typedef stdext::hash_map::const_iterator iter; -static stdext::hash_map targets; -static stdext::hash_map sources; +static stdext::hash_map targets; +static stdext::hash_map sources; static void __declspec(naked) ai_try_attack_hook_FleeFix() { __asm { @@ -80,11 +80,80 @@ static void __declspec(naked) ai_check_drugs_hook() { } } +static bool __fastcall TargetExistInList(TGameObj* target, TGameObj** targetList) { + char i = 4; + do { + if (*targetList == target) return true; + targetList++; + } while (--i); + return false; +} + +static void __declspec(naked) ai_find_attackers_hack_target2() { + __asm { + mov edi, [esp + 0x24 - 0x24 + 4] // critter (target) + pushadc; + lea edx, [ebp - 4]; // start list of targets + mov ecx, edi; + call TargetExistInList; + test al, al; + popadc; + jnz skip; + inc edx; + mov [ebp], edi; +skip: + retn; + } +} + +static void __declspec(naked) ai_find_attackers_hack_target3() { + __asm { + mov edi, [esp + 0x24 - 0x20 + 4] // critter (target) + push eax; + push edx; + mov eax, 4; // count targets + lea edx, [ebp - 4 * 2]; // start list of targets +continue: + cmp edi, [edx]; + je break; // target == targetList + lea edx, [edx + 4]; // next target in list + dec al; + jnz continue; +break: + test al, al; + pop edx; + pop eax; + jz skip; + xor edi, edi; + retn; +skip: + inc edx; + retn; + } +} + +static void __declspec(naked) ai_find_attackers_hack_target4() { + __asm { + mov eax, [ecx + eax]; // critter (target) + pushadc; + lea edx, [esi - 4 * 3]; // start list of targets + mov ecx, eax; + call TargetExistInList; + test al, al; + popadc; + jnz skip; + inc edx; + mov [esi], eax; +skip: + retn; + } +} + //////////////////////////////////////////////////////////////////////////////// -static DWORD RetryCombatLastAP; static DWORD RetryCombatMinAP; static void __declspec(naked) RetryCombatHook() { + static DWORD RetryCombatLastAP = 0; __asm { mov RetryCombatLastAP, 0; retry: @@ -111,13 +180,13 @@ end: //////////////////////////////////////////////////////////////////////////////// -static void __fastcall CombatAttackHook(DWORD source, DWORD target) { - sources[target] = source; - targets[source] = target; +static void __fastcall CombatAttackHook(TGameObj* source, TGameObj* target) { + sources[target] = source; // who attacked the 'target' from the last time + targets[source] = target; // who was attacked by the 'source' from the last time } static void __declspec(naked) combat_attack_hook() { - _asm { + __asm { push ecx; push edx; push eax; @@ -131,7 +200,7 @@ static void __declspec(naked) combat_attack_hook() { } static DWORD combatDisabled; -void _stdcall AIBlockCombat(DWORD i) { +void __stdcall AIBlockCombat(DWORD i) { combatDisabled = i ? 1 : 0; } @@ -178,8 +247,6 @@ end: } void AIInit() { - //HookCall(0x42AE1D, ai_attack_hook); - //HookCall(0x42AE5C, ai_attack_hook); HookCall(0x426A95, combat_attack_hook); // combat_attack_this_ HookCall(0x42A796, combat_attack_hook); // ai_attack_ @@ -207,14 +274,19 @@ void AIInit() { HookCall(0x42ACE5, ai_try_attack_hook_FleeFix); // Disable fleeing when NPC cannot move closer to target BlockCall(0x42ADF6); // ai_try_attack_ + + // Fix for duplicate critters being added to the list of potential targets for AI + MakeCall(0x428E75, ai_find_attackers_hack_target2, 2); + MakeCall(0x428EB5, ai_find_attackers_hack_target3); + MakeCall(0x428EE5, ai_find_attackers_hack_target4, 1); } -DWORD _stdcall AIGetLastAttacker(DWORD target) { +TGameObj* _stdcall AIGetLastAttacker(TGameObj* target) { iter itr = sources.find(target); - return (itr != sources.end()) ? itr->second: 0; + return (itr != sources.end()) ? itr->second : 0; } -DWORD _stdcall AIGetLastTarget(DWORD source) { +TGameObj* _stdcall AIGetLastTarget(TGameObj* source) { iter itr = targets.find(source); return (itr != targets.end()) ? itr->second : 0; } diff --git a/sfall/AI.h b/sfall/AI.h index d2982744..689e770e 100644 --- a/sfall/AI.h +++ b/sfall/AI.h @@ -16,11 +16,15 @@ * along with this program. If not, see . */ +#pragma once + +#include "FalloutEngine.h" + void AIInit(); void _stdcall AICombatStart(); void _stdcall AICombatEnd(); -DWORD _stdcall AIGetLastAttacker(DWORD target); -DWORD _stdcall AIGetLastTarget(DWORD source); +TGameObj* _stdcall AIGetLastAttacker(TGameObj* target); +TGameObj* _stdcall AIGetLastTarget(TGameObj* source); void _stdcall AIBlockCombat(DWORD i); diff --git a/sfall/FalloutEngine.cpp b/sfall/FalloutEngine.cpp index 4a4441d7..c2c35a95 100644 --- a/sfall/FalloutEngine.cpp +++ b/sfall/FalloutEngine.cpp @@ -743,7 +743,7 @@ void __declspec(naked) DevPrintf(const char* fmt, ...) { __asm jmp debug_printf_; } #else -void DevPrintf(const char* fmt, ...) {} +void DevPrintf(...) {} #endif // Fallout2.exe was compiled using WATCOM compiler, which uses Watcom register calling convention. diff --git a/sfall/FalloutEngine.h b/sfall/FalloutEngine.h index 29ba810c..a04cd5a7 100644 --- a/sfall/FalloutEngine.h +++ b/sfall/FalloutEngine.h @@ -1018,7 +1018,11 @@ extern const DWORD xvfprintf_; // TODO: move these to different namespace // Prints debug message to debug.log file for develop build +#ifndef NDEBUG void DevPrintf(const char* fmt, ...); +#else +void DevPrintf(...); +#endif long __stdcall ItemGetType(TGameObj* item); long __stdcall ItemSize(TGameObj* item); diff --git a/sfall/FalloutStructs.h b/sfall/FalloutStructs.h index 62235f04..569c05b1 100644 --- a/sfall/FalloutStructs.h +++ b/sfall/FalloutStructs.h @@ -57,7 +57,7 @@ struct TGameObj { long itemCharges; long critterAP_weaponAmmoPid; char gap_44[16]; - long whoHitMe; + TGameObj* whoHitMe; char gap_58[12]; DWORD pid; long cid;