Added a new mode to NPC combat control mod (WIP)

This commit is contained in:
NovaRain
2021-01-21 12:59:45 +08:00
parent 88050850f7
commit 9e403e0e6b
11 changed files with 234 additions and 15 deletions
+1
View File
@@ -35,6 +35,7 @@ MotionScanner=0
;Set to 0 to disable ;Set to 0 to disable
;Set to 1 to control all critters in combat ;Set to 1 to control all critters in combat
;Set to 2 to control all party members ;Set to 2 to control all party members
;Set to 3 to order party members to attack a specified target instead of controlling them by yourself
Mode=0 Mode=0
;If you want to control only specific critters, uncomment the PIDList line and set a comma delimited list of PIDs ;If you want to control only specific critters, uncomment the PIDList line and set a comma delimited list of PIDs
;PIDList=62,89,97,107,160,161 ;PIDList=62,89,97,107,160,161
Binary file not shown.
+22 -11
View File
@@ -12,12 +12,19 @@
*/ */
#include "..\headers\define.h" #include "..\headers\define.h"
#include "..\headers\command.h" //#include "..\headers\command.h"
#include "main.h" #include "main.h"
#define OBJ_DATA_LIGHT_DISTANCE (0x6C) #define OBJ_DATA_LIGHT_DISTANCE (0x6C)
#define OBJ_DATA_LIGHT_INTENSITY (0x70) #define OBJ_DATA_LIGHT_INTENSITY (0x70)
procedure start;
procedure AllowControl(variable pid);
procedure CombatTurn_Handler;
procedure GameModeChange_Handler;
procedure InventoryMove_Handler;
procedure InvenWield_Handler;
variable variable
controlMode, controlMode,
noCheckArray, noCheckArray,
@@ -32,7 +39,7 @@ procedure AllowControl(variable pid) begin
return false; return false;
end end
procedure combatturn_handler begin procedure CombatTurn_Handler begin
variable variable
status := get_sfall_arg, status := get_sfall_arg,
critter := get_sfall_arg, critter := get_sfall_arg,
@@ -98,7 +105,7 @@ procedure combatturn_handler begin
end end
end end
procedure gamemodechange_handler begin procedure GameModeChange_Handler begin
if (inControl and not(get_game_mode BWAND COMBAT)) then begin if (inControl and not(get_game_mode BWAND COMBAT)) then begin
inControl := false; inControl := false;
npcControl := 0; npcControl := 0;
@@ -106,7 +113,7 @@ procedure gamemodechange_handler begin
end end
end end
procedure inventorymove_handler begin procedure InventoryMove_Handler begin
if (npcControl and get_sfall_arg == 3) then begin // armor slot if (npcControl and get_sfall_arg == 3) then begin // armor slot
if (obj_pid(dude_obj) == PID_MARCUS or proto_data(obj_pid(dude_obj), cr_body_type) != CR_BODY_BIPED) then begin if (obj_pid(dude_obj) == PID_MARCUS or proto_data(obj_pid(dude_obj), cr_body_type) != CR_BODY_BIPED) then begin
display_msg(message_str_game(GAME_MSG_PROTO, 675)); display_msg(message_str_game(GAME_MSG_PROTO, 675));
@@ -115,8 +122,9 @@ procedure inventorymove_handler begin
end end
end end
procedure invenwield_handler begin procedure InvenWield_Handler begin
variable critter := get_sfall_arg, variable
critter := get_sfall_arg,
item := get_sfall_arg, item := get_sfall_arg,
slot := get_sfall_arg, slot := get_sfall_arg,
event := get_sfall_arg; event := get_sfall_arg;
@@ -135,7 +143,10 @@ procedure start begin
set_perk_level(PERK_gecko_skinning_perk, 999); // prevent it from appearing in the perk selection window set_perk_level(PERK_gecko_skinning_perk, 999); // prevent it from appearing in the perk selection window
controlMode := GetConfig("CombatControl", "Mode", 0); controlMode := GetConfig("CombatControl", "Mode", 0);
if (controlMode > 2) then controlMode := 0; if (controlMode >= 3) then begin
if (controlMode == 3) then metarule3(999, 0, 0, 0); // enable feature: order party members to attack a specified target
controlMode := 0;
end
if (controlMode > 0) then begin if (controlMode > 0) then begin
displayName := GetConfig("CombatControl", "DisplayName", 0); displayName := GetConfig("CombatControl", "DisplayName", 0);
@@ -160,10 +171,10 @@ procedure start begin
perksList := GetConfigListInt("CombatControl", "PerksList"); perksList := GetConfigListInt("CombatControl", "PerksList");
fix_array(perksList); fix_array(perksList);
register_hook_proc(HOOK_COMBATTURN, combatturn_handler); register_hook_proc(HOOK_COMBATTURN, CombatTurn_Handler);
register_hook_proc(HOOK_GAMEMODECHANGE, gamemodechange_handler); register_hook_proc(HOOK_GAMEMODECHANGE, GameModeChange_Handler);
register_hook_proc(HOOK_INVENTORYMOVE, inventorymove_handler); register_hook_proc(HOOK_INVENTORYMOVE, InventoryMove_Handler);
register_hook_proc(HOOK_INVENWIELD, invenwield_handler); register_hook_proc(HOOK_INVENWIELD, InvenWield_Handler);
end end
end end
end end
Binary file not shown.
+1
View File
@@ -86,6 +86,7 @@ static std::vector<HackPair> hackAddr = {
// module: PartyControl // module: PartyControl
{0x422BDE, 1}, {0x4229EC, 1}, {0x422BDE, 1}, {0x4229EC, 1},
{0x49EB09, 1}, {0x49EB0A, 4}, {0x458242, 1}, {0x458243, 4}, {0x458326, 1}, {0x458327, 4}, {0x42E25B, 1}, {0x42E25C, 4}, // hookcalls {0x49EB09, 1}, {0x49EB0A, 4}, {0x458242, 1}, {0x458243, 4}, {0x458326, 1}, {0x458327, 4}, {0x42E25B, 1}, {0x42E25C, 4}, // hookcalls
{0x44C4A7, 1}, {0x44C4A8, 4}, {0x44C75F, 1}, {0x44C760, 4}, {0x44C69A, 1}, {0x44C69B, 4}, {0x44B830, 1}, {0x44B831, 4}, // hookcalls for party order attack
// module: Perks // module: Perks
{0x43C77C, 1}, {0x43C77D, 4}, // hookcall {0x43C77C, 1}, {0x43C77D, 4}, // hookcall
{0x478AC4, 1}, {0x478AC5, 4}, {0x496823, 5}, {0x478AC4, 1}, {0x478AC5, 4}, {0x496823, 5},
+3 -3
View File
@@ -186,10 +186,10 @@ void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set) {
// Returns the position of party member in the existing table (begins from 1) // Returns the position of party member in the existing table (begins from 1)
long IsPartyMemberByPid(long pid) { long IsPartyMemberByPid(long pid) {
size_t patryCount = fo::var::partyMemberMaxCount; size_t partyCount = fo::var::partyMemberMaxCount;
if (patryCount) { if (partyCount) {
DWORD* memberPids = fo::var::partyMemberPidList; // pids from party.txt DWORD* memberPids = fo::var::partyMemberPidList; // pids from party.txt
for (size_t i = 0; i < patryCount; i++) { for (size_t i = 0; i < partyCount; i++) {
if (memberPids[i] == pid) return i + 1; if (memberPids[i] == pid) return i + 1;
} }
} }
+73
View File
@@ -802,6 +802,79 @@ namespace WinFlags {
}; };
} }
namespace AIpref {
enum distance : long
{
stay_close = 0,
charge = 1,
snipe = 2,
on_your_own = 3,
stay = 4
};
enum disposition : long
{
none = -1,
custom = 0,
coward = 1,
defensive = 2,
aggressive = 3,
berserk = 4
};
enum class attack_who : long
{
whomever_attacking_me = 0,
strongest = 1,
weakest = 2,
whomever = 3,
closest = 4,
};
enum class run_away_mode : long
{
none = -1, // get the value from the cap.min_hp (in cai_get_min_hp_)
coward = 0, // 0%
finger_hurts = 1, // 25% of the lost amount of health
bleeding = 2, // 40% of the lost amount of health
not_feeling_good = 3, // 60% of the lost amount of health
tourniquet = 4, // 75% of the lost amount of health
never = 5 // 100%
};
enum class weapon_pref : long
{
no_pref = 0,
melee = 1,
melee_over_ranged = 2,
ranged_over_melee = 3,
ranged = 4,
unarmed = 5,
unarmed_over_thrown = 6, // not available in party member control panel
random = 7 // not available in party member control panel
};
enum class area_attack_mode : long
{
no_pref = -1, // special logic for NPC (not available in party member control panel)
always = 0,
sometimes = 1, // use random value from cap.secondary_freq
be_sure = 2, // 85% hit chance
be_careful = 3, // 50% hit chance
be_absolutely_sure = 4, // 95% hit chance
};
enum class chem_use_mode : long
{
clean = 0,
stims_when_hurt_little = 1,
stims_when_hurt_lots = 2,
sometimes = 3,
anytime = 4,
always = 5,
};
}
enum QueueType : long enum QueueType : long
{ {
drug_effect_event = 0, // critter use drug drug_effect_event = 0, // critter use drug
+1 -1
View File
@@ -3598,7 +3598,7 @@ void BugFixes::init()
// Fix broken Print() script function // Fix broken Print() script function
HookCall(0x461AD4, (void*)fo::funcoffs::windowOutput_); HookCall(0x461AD4, (void*)fo::funcoffs::windowOutput_);
// Fix for the flags of non-door objects being set/unset when opening/closing objects (e.g. using obj_close/open functions) // Fix for the flags of non-door objects being set/unset when using obj_close/open functions
MakeCall(0x49CBF7, check_door_state_hack_close, 2); MakeCall(0x49CBF7, check_door_state_hack_close, 2);
MakeCall(0x49CB30, check_door_state_hack_open, 1); MakeCall(0x49CB30, check_door_state_hack_open, 1);
} }
+5
View File
@@ -19,6 +19,7 @@
#include "..\main.h" #include "..\main.h"
#include "..\FalloutEngine\Fallout2.h" #include "..\FalloutEngine\Fallout2.h"
#include "LoadGameHook.h" #include "LoadGameHook.h"
#include "PartyControl.h"
#include "MetaruleExtender.h" #include "MetaruleExtender.h"
@@ -35,6 +36,7 @@ static bool HorriganEncounterDisabled = false;
enum class MetaruleFunction : long { enum class MetaruleFunction : long {
SET_HORRIGAN_ENCOUNTER = 200, // sets the number of days for the Frank Horrigan encounter or disable encounter SET_HORRIGAN_ENCOUNTER = 200, // sets the number of days for the Frank Horrigan encounter or disable encounter
CLEAR_KEYBOARD_BUFFER = 201, // clears the keyboard input buffer, should be used in the HOOK_KEYPRESS hook to clear keyboard events in some cases CLEAR_KEYBOARD_BUFFER = 201, // clears the keyboard input buffer, should be used in the HOOK_KEYPRESS hook to clear keyboard events in some cases
EXT_PARTY_ORDER_ATTACK = 999
}; };
/* /*
@@ -61,6 +63,9 @@ static long __fastcall op_metarule3_ext(long metafunc, long* args) {
case MetaruleFunction::CLEAR_KEYBOARD_BUFFER: case MetaruleFunction::CLEAR_KEYBOARD_BUFFER:
__asm call fo::funcoffs::kb_clear_; __asm call fo::funcoffs::kb_clear_;
break; break;
case MetaruleFunction::EXT_PARTY_ORDER_ATTACK:
PartyControl::MemberOrderAttackPatch();
break;
default: default:
fo::func::debug_printf("\nOPCODE ERROR: metarule3(%d, ...) - metarule function number does not exist.\n > Script: %s, procedure %s.", fo::func::debug_printf("\nOPCODE ERROR: metarule3(%d, ...) - metarule function number does not exist.\n > Script: %s, procedure %s.",
metafunc, fo::var::currentProgram->fileName, fo::func::findCurrentProc(fo::var::currentProgram)); metafunc, fo::var::currentProgram->fileName, fo::func::findCurrentProc(fo::var::currentProgram));
+126
View File
@@ -23,6 +23,7 @@
#include "HookScripts.h" #include "HookScripts.h"
#include "LoadGameHook.h" #include "LoadGameHook.h"
#include "ScriptExtender.h" #include "ScriptExtender.h"
//#include "Objects.h"
#include "PartyControl.h" #include "PartyControl.h"
@@ -271,6 +272,9 @@ static void SetCurrentDude(fo::GameObject* npc) {
} }
if (!exist) __asm call fo::funcoffs::critter_sneak_check_; if (!exist) __asm call fo::funcoffs::critter_sneak_check_;
} }
//if (!isPartyMember) Objects::SetObjectUniqueID(npc);
fo::func::intface_redraw(); fo::func::intface_redraw();
} }
@@ -622,6 +626,126 @@ static void __declspec(naked) gdControlUpdateInfo_hook() {
} }
} }
static bool partyOrderPickTargetLoop;
static char partyOrderAttackMsg[33];
// disables the display of the hit chance value when picking a target
static void __declspec(naked) gmouse_bk_process_hack() {
__asm {
mov edx, -1; // mode
mov eax, ds:[FO_VAR_gmouse_3d_current_mode];
test partyOrderPickTargetLoop, 0xFF;
cmovnz eax, edx;
retn;
}
}
static void __fastcall action_attack_to(long unused, fo::GameObject* partyMember) {
fo::func::gmouse_set_cursor(20);
fo::func::gmouse_3d_set_mode(2);
fo::GameObject* targetObject = nullptr;
fo::GameObject* validTarget = nullptr;
long outlineColor; // backup color
fo::BoundRect rect;
partyOrderPickTargetLoop = true;
do {
fo::GameObject* underObject = fo::func::object_under_mouse(1, 0, fo::var::map_elevation);
if (targetObject && targetObject != underObject) {
targetObject->outline = outlineColor;
fo::func::obj_bound(targetObject, &rect);
if (!outlineColor) {
rect.x--;
rect.y--;
rect.offx += 2;
rect.offy += 2;
}
fo::func::tile_refresh_rect(&rect, fo::var::map_elevation);
targetObject = validTarget = nullptr;
}
if (underObject && underObject != targetObject && underObject->IsCritter() && underObject->critter.teamNum != partyMember->critter.teamNum) {
if (underObject->critter.IsNotDead()) {
outlineColor = underObject->outline;
if (underObject->critter.combatState) {
underObject->outline = 254 << 8;
validTarget = underObject;
} else {
underObject->outline = 10 << 8;
}
fo::func::obj_bound(underObject, &rect);
fo::func::tile_refresh_rect(&rect, fo::var::map_elevation);
targetObject = underObject;
}
}
if (validTarget && *(DWORD*)FO_VAR_mouse_buttons == 1) break; // left mouse button
} while (*(DWORD*)FO_VAR_mouse_buttons != 2 && fo::func::get_input() != 27); // 27 - escape code
if (validTarget && *(DWORD*)FO_VAR_mouse_buttons == 1) {
partyMember->critter.whoHitMe = validTarget;
validTarget->outline = outlineColor;
fo::AIcap* cap = fo::func::ai_cap(partyMember);
if (cap->disposition == fo::AIpref::disposition::custom) {
cap->attack_who = (long)fo::AIpref::attack_who::whomever;
}
std::strcpy((char*)FO_VAR_attack_str, partyOrderAttackMsg);
fo::func::ai_print_msg(partyMember, 0); // float message
}
partyOrderPickTargetLoop = false;
//*(DWORD*)FO_VAR_mouse_buttons = 0;
fo::func::gmouse_set_cursor(0);
fo::func::gmouse_3d_set_mode(1);
}
static void __declspec(naked) gmouse_handle_event_hook() {
__asm {
test ds:[FO_VAR_combat_state], 1;
jnz action_attack_to;
jmp fo::funcoffs::action_talk_to_;
}
}
static void __declspec(naked) gmouse_handle_event_hack() {
__asm {
test ds:[FO_VAR_combat_state], 1;
jnz pick;
retn;
pick:
mov eax, edi; // critter
call fo::funcoffs::isPartyMember_;
test eax, eax;
jz end;
mov bl, 1;
mov eax, 5;
mov [esp + 4], eax; // actionMenuList
mov word ptr ds:[FO_VAR_gmouse_3d_action_nums][5*2], 81; // index in INTRFACE.LST (ACTIONI.FRM)
end:
or eax, 1;
retn;
}
}
static void __declspec(naked) gmouse_handle_event_hook_restore() {
__asm {
mov word ptr ds:[FO_VAR_gmouse_3d_action_nums][5*2], 263; // index in INTRFACE.LST (TALKN.FRM)
jmp fo::funcoffs::map_enable_bk_processes_;
}
}
void PartyControl::MemberOrderAttackPatch() {
MakeCall(0x44C4A7, gmouse_handle_event_hack, 2);
HookCall(0x44C75F, gmouse_handle_event_hook);
HookCall(0x44C69A, gmouse_handle_event_hook_restore);
MakeCall(0x44B830, gmouse_bk_process_hack);
}
static void NpcAutoLevelPatch() { static void NpcAutoLevelPatch() {
npcAutoLevelEnabled = GetConfigInt("Misc", "NPCAutoLevel", 0) != 0; npcAutoLevelEnabled = GetConfigInt("Misc", "NPCAutoLevel", 0) != 0;
if (npcAutoLevelEnabled) { if (npcAutoLevelEnabled) {
@@ -663,6 +787,8 @@ void PartyControl::init() {
Translate("sfall", "PartyAddictMsg", "Addict", addictMsg, 16); Translate("sfall", "PartyAddictMsg", "Addict", addictMsg, 16);
dlogr(" Done", DL_INIT); dlogr(" Done", DL_INIT);
} }
Translate("sfall", "PartyOrderAttack", "I'll take care of it.", partyOrderAttackMsg, 33);
} }
void PartyControl::exit() { void PartyControl::exit() {
+2
View File
@@ -37,6 +37,8 @@ public:
// Returns pointer to "real" dude, which is different from "dude_obj" when controlling another critter // Returns pointer to "real" dude, which is different from "dude_obj" when controlling another critter
static fo::GameObject* RealDudeObject(); static fo::GameObject* RealDudeObject();
static void MemberOrderAttackPatch();
}; };
extern bool npcAutoLevelEnabled; extern bool npcAutoLevelEnabled;