Added a fixed implementation of ai_check_drugs_ function

* which has all fixes/hook built-in.

Disabled old fixes for vanilla ai_check_drugs_ in AI.cpp/BugFixes.cpp.
This commit is contained in:
NovaRain
2021-08-17 12:22:57 +08:00
parent 3a0e64e3ce
commit e55db2b4da
11 changed files with 253 additions and 27 deletions
+6 -1
View File
@@ -23,6 +23,7 @@ WRAP_WATCOM_FFUNC4(fo::GameObject*, ai_best_weapon, fo::GameObject*, source, fo:
WRAP_WATCOM_FFUNC3(bool, ai_can_use_weapon, fo::GameObject*, critter, fo::GameObject*, item, DWORD, hitMode)
WRAP_WATCOM_FFUNC3(long, ai_have_ammo, fo::GameObject*, critter, fo::GameObject*, item, fo::GameObject**, outAmmo)
WRAP_WATCOM_FFUNC3(long, ai_pick_hit_mode, fo::GameObject*, source, fo::GameObject*, item, fo::GameObject*, target)
WRAP_WATCOM_FFUNC3(long, ai_magic_hands, fo::GameObject*, source, fo::GameObject*, object, long, msgNumber)
WRAP_WATCOM_FFUNC3(fo::GameObject*, ai_search_inven_weap, fo::GameObject*, source, long, apCheck, fo::GameObject*, target)
WRAP_WATCOM_FFUNC3(void, check_for_death, fo::GameObject*, critter, long, amountDamage, long*, flags)
WRAP_WATCOM_FFUNC6(long, combat_safety_invalidate_weapon_func, fo::GameObject*, source, fo::GameObject*, weapon, long, hitMode, fo::GameObject*, targetA, DWORD*, outSafeRange, fo::GameObject*, targetB)
@@ -37,7 +38,7 @@ WRAP_WATCOM_FFUNC3(void, intface_update_items, long, animate, long, modeLeft, lo
WRAP_WATCOM_FFUNC3(fo::GameObject*, inven_find_type, fo::GameObject*, critter, long, itemType, DWORD*, slot)
WRAP_WATCOM_FFUNC3(long, inven_wield, fo::GameObject*, critter, fo::GameObject*, item, long, slot)
WRAP_WATCOM_FFUNC3(long, item_add_force, fo::GameObject*, critter, fo::GameObject*, item, long, count)
WRAP_WATCOM_FFUNC3(long, item_mp_cost, fo::GameObject*, source, long, hitMode, long, isCalled)
WRAP_WATCOM_FFUNC3(long, item_remove_mult, fo::GameObject*, critter, fo::GameObject*, item, long, count) // WARNING: HOOK_REMOVEINVENOBJ uses the return address in this function
WRAP_WATCOM_FFUNC3(long, item_w_mp_cost, fo::GameObject*, source, long, hitMode, long, isCalled)
// Calculates path and returns it's length
WRAP_WATCOM_FFUNC6(long, make_path_func, fo::GameObject*, objectFrom, long, tileFrom, long, tileTo, char*, pathDataBuffer, long, checkTileTo, void*, blockingFunc)
@@ -46,6 +47,7 @@ WRAP_WATCOM_FFUNC3(long, message_find, DWORD*, msgFile, long, msgNumber, DWORD*,
WRAP_WATCOM_FFUNC4(long, mouse_click_in, long, x, long, y, long, x_offs, long, y_offs)
WRAP_WATCOM_FFUNC4(long, mouse_in, long, x, long, y, long, x_offs, long, y_offs)
WRAP_WATCOM_FFUNC3(fo::GameObject*, obj_blocking_at, fo::GameObject*, object, long, tile, long, elevation)
WRAP_WATCOM_FFUNC4(long, obj_connect, fo::GameObject*, object, long, tile, long, elevation, RECT*, rect)
WRAP_WATCOM_FFUNC4(long, obj_dist_with_tile, fo::GameObject*, source, long, sourceTile, fo::GameObject*, target, long, targetTile)
WRAP_WATCOM_FFUNC4(long, obj_move_to_tile, fo::GameObject*, object, long, tile, long, elevation, RECT*, rect)
WRAP_WATCOM_FFUNC3(long, obj_new_sid_inst, fo::GameObject*, object, long, sType, long, scriptIndex)
@@ -71,6 +73,8 @@ WRAP_WATCOM_FFUNC3(const char*, interpretGetString, fo::Program*, scriptPtr, DWO
/* stdcall */
WRAP_WATCOM_FUNC1(fo::AIcap*, ai_cap, fo::GameObject*, critter)
WRAP_WATCOM_FUNC2(void, ai_print_msg, fo::GameObject*, object, long, mode)
WRAP_WATCOM_FUNC2(fo::GameObject*, ai_retrieve_object, fo::GameObject*, critter, fo::GameObject*, item)
WRAP_WATCOM_FUNC2(fo::GameObject*, ai_search_environ, fo::GameObject*, critter, long, itemType)
WRAP_WATCOM_FUNC1(fo::Program*, allocateProgram, const char*, filePath)
WRAP_WATCOM_FUNC2(long, anim_can_use_door, fo::GameObject*, source, fo::GameObject*, object)
WRAP_WATCOM_FUNC1(bool, art_exists, long, artFid)
@@ -136,6 +140,7 @@ WRAP_WATCOM_FUNC2(long, is_within_perception, fo::GameObject*, source, fo::GameO
WRAP_WATCOM_FUNC1(long, isPartyMember, fo::GameObject*, obj)
WRAP_WATCOM_FUNC1(long, item_c_curr_size, fo::GameObject*, critter)
WRAP_WATCOM_FUNC1(long, item_caps_total, fo::GameObject*, object)
WRAP_WATCOM_FUNC2(long, item_d_take_drug, fo::GameObject*, source, fo::GameObject*, item)
WRAP_WATCOM_FUNC1(long, item_get_type, fo::GameObject*, item)
WRAP_WATCOM_FUNC2(fo::GameObject*, item_hit_with, fo::GameObject*, critter, long, hitMode)
WRAP_WATCOM_FUNC1(long, item_m_dec_charges, fo::GameObject*, item) // Returns 0 on success, -1 if the item has no charges
+181 -2
View File
@@ -4,11 +4,13 @@
*
*/
//#include "..\main.h"
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
#include "..\Modules\HookScripts\CombatHs.h"
#include "items.h"
#include "combatAI.h"
namespace game
@@ -22,7 +24,184 @@ bool CombatAI::ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon,
return sf::CanUseWeaponHook_Invoke(result, source, weapon, hitMode);
}
void CombatAI::init() { // TODO: add to main.cpp
static const long aiUseItemAPCost = 2;
static std::vector<long> healingItemPids = {fo::PID_STIMPAK, fo::PID_SUPER_STIMPAK, fo::PID_HEALING_POWDER};
static bool CheckHealingItems(fo::GameObject* item) {
return (std::find(healingItemPids.cbegin(), healingItemPids.cend(), item->protoId) != healingItemPids.cend());
}
// True - use failed
static bool UseItemDrugFunc(fo::GameObject* source, fo::GameObject* item) {
bool result = (game::Items::item_d_take_drug(source, item) == -1);
if (result) {
fo::func::item_add_force(source, item, 1);
} else {
fo::func::ai_magic_hands(source, item, 5000);
fo::func::obj_connect(item, source->tile, source->elevation, 0);
fo::func::obj_destroy(item);
}
return result;
}
static long drugUsePerfFixMode;
void __stdcall CombatAI::ai_check_drugs(fo::GameObject* source) {
if (fo::func::critter_body_type(source)) return; // Robotic/Quadruped cannot use drugs
DWORD slot = -1;
long noInvenDrug = 0;
bool drugWasUsed = false;
fo::GameObject* lastItem = nullptr; // combatAIInfoGetLastItem_(source); unused function, always returns 0
if (!lastItem) {
fo::AIcap* cap = fo::func::ai_cap(source);
if (!cap) return;
long hpPercent = 50;
long chance = 0;
switch ((fo::AIpref::chem_use_mode)cap->chem_use) {
case fo::AIpref::chem_use_mode::stims_when_hurt_little: // use only healing drugs
hpPercent = 60;
break;
case fo::AIpref::chem_use_mode::stims_when_hurt_lots: // use only healing drugs
hpPercent = 30;
break;
case fo::AIpref::chem_use_mode::sometimes:
if (!(fo::var::combatNumTurns % 3)) chance = 25; // every three turns
//hpPercent = 50;
break;
case fo::AIpref::chem_use_mode::anytime:
if (!(fo::var::combatNumTurns % 3)) chance = 75; // every three turns
//hpPercent = 50;
break;
case fo::AIpref::chem_use_mode::always:
chance = 100; // 99%
break;
case fo::AIpref::chem_use_mode::clean:
return; // exit: don't use drugs
}
long minHP = (hpPercent * fo::func::stat_level(source, fo::Stat::STAT_max_hit_points)) / 100;
// [FIX] for AI not checking minimum hp properly for using stimpaks (prevents premature fleeing)
if (cap->min_hp > minHP) minHP = cap->min_hp;
while (fo::func::stat_level(source, fo::Stat::STAT_current_hp) < minHP && source->critter.movePoints >= aiUseItemAPCost) {
fo::GameObject* itemFind = fo::func::inven_find_type(source, fo::ItemType::item_type_drug, &slot);
if (!itemFind) {
noInvenDrug = 2; // healing drugs were not found in the inventory (was 1)
break;
}
if (CheckHealingItems(itemFind) && !fo::func::item_remove_mult(source, itemFind, 1)) {
if (!UseItemDrugFunc(source, itemFind)) {
drugWasUsed = true;
}
if (source->critter.movePoints < aiUseItemAPCost) {
source->critter.movePoints = 0;
} else {
source->critter.movePoints -= aiUseItemAPCost;
}
slot = -1;
}
}
// use any drug (except healing drugs) if there is a chance of using it
if (!drugWasUsed && chance > 0 && fo::func::roll_random(0, 100) < chance) {
long usedCount = 0;
while (source->critter.movePoints >= aiUseItemAPCost) {
fo::GameObject* item = fo::func::inven_find_type(source, fo::ItemType::item_type_drug, &slot);
if (!item) {
noInvenDrug = 1;
break;
}
long counter = 0;
if (drugUsePerfFixMode > 0) {
fo::GameObject* firstFoundDrug = item;
do {
// [FIX] Allow using only the drugs listed in chem_primary_desire and healing drugs (AIDrugUsePerfFix == 2)
while (item->protoId != cap->chem_primary_desire[counter]) if (++counter > 2) break;
if (counter <= 2) break; // there is a match
// [FIX] for AI not taking chem_primary_desire in AI.txt as a preference when using drugs in the inventory
if (drugUsePerfFixMode == 1) {
item = fo::func::inven_find_type(source, fo::ItemType::item_type_drug, &slot);
if (!item) {
item = firstFoundDrug;
break;
}
counter = 0;
}
} while (counter < 3);
} else {
// if the durg is equal to the first item of preference, then check the next two (vanilla behavior)
while (item->protoId == cap->chem_primary_desire[counter]) if (++counter > 2) break;
}
// if the preference counter is less than 3, then AI can use the drug
if (counter < 3) {
// if the item is NOT a healing drug
if (!CheckHealingItems(item) && !fo::func::item_remove_mult(source, item, 1)) {
if (!UseItemDrugFunc(source, item)) {
drugWasUsed = true;
usedCount++;
}
if (source->critter.movePoints < aiUseItemAPCost) {
source->critter.movePoints = 0;
} else {
source->critter.movePoints -= aiUseItemAPCost;
}
slot = -1;
fo::AIpref::chem_use_mode chemUse = (fo::AIpref::chem_use_mode)cap->chem_use;
if (chemUse == fo::AIpref::chem_use_mode::sometimes ||
(chemUse == fo::AIpref::chem_use_mode::anytime && usedCount >= 2))
{
break;
}
}
}
}
}
}
// search for drugs on the map
if (lastItem || (!drugWasUsed /*&& noInvenDrug*/)) {
do {
if (!lastItem) lastItem = fo::func::ai_search_environ(source, fo::ItemType::item_type_drug);
if (!lastItem) lastItem = fo::func::ai_search_environ(source, fo::ItemType::item_type_misc_item);
if (lastItem) lastItem = fo::func::ai_retrieve_object(source, lastItem);
// [FIX] Prevent the use of healing drugs when not necessary
// noInvenDrug: is set to 2 that healing is required
if (lastItem && noInvenDrug != 2 && CheckHealingItems(lastItem)) {
long maxHP = fo::func::stat_level(source, fo::Stat::STAT_max_hit_points);
if (10 + source->critter.health >= maxHP) { // quick check current HP
return; // exit: don't use healing item
}
}
if (lastItem && !fo::func::item_remove_mult(source, lastItem, 1)) {
if (!UseItemDrugFunc(source, lastItem)) lastItem = nullptr;
if (source->critter.movePoints < aiUseItemAPCost) {
source->critter.movePoints = 0;
} else {
source->critter.movePoints -= aiUseItemAPCost;
}
}
} while (lastItem && source->critter.movePoints >= aiUseItemAPCost);
}
}
void CombatAI::init() {
drugUsePerfFixMode = sf::IniReader::GetConfigInt("Misc", "AIDrugUsePerfFix", 0);
if (drugUsePerfFixMode > 0) sf::dlogr("Applying AI drug use preference fix.", DL_FIX);
}
}
+4
View File
@@ -9,11 +9,15 @@
namespace game
{
#include "..\FalloutEngine\Fallout2.h"
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);
};
}
+7
View File
@@ -8,6 +8,7 @@
#include "..\FalloutEngine\Fallout2.h"
#include "..\Modules\HookScripts\CombatHs.h"
#include "..\Modules\HookScripts\ObjectHs.h"
#include "..\Modules\Perks.h"
#include "..\Game\stats.h"
@@ -21,6 +22,12 @@ namespace sf = sfall;
static constexpr int reloadCostAP = 2; // engine default reload AP cost
// Implementation of item_d_take_ engine function with the HOOK_USEOBJON hook
long Items::item_d_take_drug(fo::GameObject* source, fo::GameObject* item) {
if (sf::UseObjOnHook_Invoke(source, item, source) == -1) return -1;
return fo::func::item_d_take_drug(source, item);
}
long Items::item_count(fo::GameObject* who, fo::GameObject* item) {
for (int i = 0; i < who->invenSize; i++) {
auto tableItem = &who->invenTable[i];
+3
View File
@@ -13,6 +13,9 @@ class Items {
public:
static void init();
// Implementation of item_d_take_ engine function with the HOOK_USEOBJON hook
static long item_d_take_drug(fo::GameObject* source, fo::GameObject* item);
static long item_count(fo::GameObject* who, fo::GameObject* item);
static long item_weapon_range(fo::GameObject* source, fo::GameObject* weapon, long hitMode);
+10 -7
View File
@@ -22,6 +22,7 @@
#include "..\FalloutEngine\Fallout2.h"
#include "LoadGameHook.h"
#include "..\Game\combatAI.h"
#include "..\Game\items.h"
#include "AI.h"
@@ -112,8 +113,10 @@ end:
add esp, 4;
jmp combat_ai_hack_Ret;
tryHeal:
mov eax, esi;
call fo::funcoffs::ai_check_drugs_;
push ecx;
push esi; // mov eax, esi;
call game::CombatAI::ai_check_drugs; // call fo::funcoffs::ai_check_drugs_;
pop ecx;
cmp [esi + health], edx; // edx - minimum hp, below which NPC will run away
jge end;
retn; // flee
@@ -126,7 +129,7 @@ static void __declspec(naked) ai_check_drugs_hook() {
mov edx, dword ptr [esp + 0x34 - 0x1C + 4]; // ai cap
mov edx, [edx + 0x10]; // min_hp
cmp eax, edx; // curr_hp < cap.min_hp
cmovl edi, edx;
cmovl edi, edx; // min_hp <- cap.min_hp
retn;
}
}
@@ -663,12 +666,12 @@ void AI::init() {
// Fix to allow fleeing NPC to use drugs
MakeCall(0x42B1DC, combat_ai_hack);
// Fix for AI not checking minimum hp properly for using stimpaks (prevents premature fleeing)
HookCall(0x428579, ai_check_drugs_hook);
//HookCall(0x428579, ai_check_drugs_hook);
// Fix to prevent the use of healing drugs when not necessary
HookCall(0x4287D7, ai_check_drugs_hook_healing);
SafeWrite8(0x4285A8, 2); // set noInvenItem = 2
SafeWrite8(0x4287A0, 0x8C); // jnz > jl (noInvenItem < 1)
//HookCall(0x4287D7, ai_check_drugs_hook_healing);
//SafeWrite8(0x4285A8, 2); // set noInvenItem = 2
//SafeWrite8(0x4287A0, 0x8C); // jnz > jl (noInvenItem < 1)
// Fix for NPC stuck in fleeing mode when the hit chance of a target was too low
HookCall(0x42B1E3, combat_ai_hook_FleeFix);
+14 -14
View File
@@ -3641,20 +3641,20 @@ void BugFixes::init()
// Display a pop-up message box about death from radiation
HookCall(0x42D733, process_rads_hook_msg);
int drugUsePerfFix = IniReader::GetConfigInt("Misc", "AIDrugUsePerfFix", 0);
if (drugUsePerfFix > 0) {
dlog("Applying AI drug use preference fix.", DL_FIX);
if (drugUsePerfFix == 1) {
// Fix for AI not taking chem_primary_desire in AI.txt as drug use preference when using drugs in their inventory
MakeCall(0x42869D, ai_check_drugs_hack_break);
MakeCall(0x4286AB, ai_check_drugs_hack_check, 1);
MakeCall(0x4286C7, ai_check_drugs_hack_use);
}
// Fix to allow using only the drugs listed in chem_primary_desire and healing drugs (stimpaks and healing powder)
SafeWrite8(0x4286B1, CodeType::JumpZ); // jnz > jz (ai_check_drugs_)
SafeWrite8(0x4286C5, CodeType::JumpNZ); // jz > jnz (ai_check_drugs_)
dlogr(" Done", DL_FIX);
}
//int drugUsePerfFix = IniReader::GetConfigInt("Misc", "AIDrugUsePerfFix", 0);
//if (drugUsePerfFix > 0) {
// dlog("Applying AI drug use preference fix.", DL_FIX);
// if (drugUsePerfFix == 1) {
// // Fix for AI not taking chem_primary_desire in AI.txt as drug use preference when using drugs in their inventory
// MakeCall(0x42869D, ai_check_drugs_hack_break);
// MakeCall(0x4286AB, ai_check_drugs_hack_check, 1);
// MakeCall(0x4286C7, ai_check_drugs_hack_use);
// }
// // Fix to allow using only the drugs listed in chem_primary_desire and healing drugs (stimpaks and healing powder)
// SafeWrite8(0x4286B1, CodeType::JumpZ); // jnz > jz (ai_check_drugs_)
// SafeWrite8(0x4286C5, CodeType::JumpNZ); // jz > jnz (ai_check_drugs_)
// dlogr(" Done", DL_FIX);
//}
// Fix for chem_primary_desire values in party member AI packets not being saved correctly
HookCall(0x42803E, cai_cap_save_hook);
+24 -3
View File
@@ -10,6 +10,27 @@
namespace sfall
{
static long UseObjOnHook_Script(fo::GameObject* source, fo::GameObject* item, fo::GameObject* target) {
BeginHook();
argCount = 3;
args[0] = (DWORD)target; // target
args[1] = (DWORD)source; // user
args[2] = (DWORD)item; // item
RunHookScript(HOOK_USEOBJON);
long result = (cRet > 0) ? rets[0] : -1;
EndHook();
return result; // -1 - default handler
}
long UseObjOnHook_Invoke(fo::GameObject* source, fo::GameObject* item, fo::GameObject* target) {
if (!HookScripts::HookHasScript(HOOK_USEOBJON)) return -1;
return UseObjOnHook_Script(source, item, target);
}
static void __declspec(naked) UseObjOnHook() {
__asm {
HookBegin;
@@ -369,9 +390,9 @@ void Inject_UseObjOnHook() {
// the following hooks allows to catch drug use of AI and from action cursor
HookCalls(Drug_UseObjOnHook, {
0x4285DF, // ai_check_drugs
0x4286F8, // ai_check_drugs
0x4287F8, // ai_check_drugs
//0x4285DF, // ai_check_drugs
//0x4286F8, // ai_check_drugs
//0x4287F8, // ai_check_drugs
0x473573 // inven_action_cursor
});
}
+2
View File
@@ -13,4 +13,6 @@ namespace sfall
void Inject_ScriptProcedureHook2();
void Inject_AdjustPoisonHook();
void Inject_AdjustRadsHook();
long UseObjOnHook_Invoke(fo::GameObject* source, fo::GameObject* item, fo::GameObject* target);
}
+1
View File
@@ -6,6 +6,7 @@
#pragma once
#include "Game\combatAI.h"
#include "Game\inventory.h"
#include "Game\items.h"
#include "Game\render.h"
+1
View File
@@ -102,6 +102,7 @@ DWORD HRPAddress(DWORD addr) {
char falloutConfigName[65] = {0};
void InitReplacementHacks() {
game::CombatAI::init();
game::Inventory::init();
game::Items::init();
game::Render::init();