mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
Added a new mode to NPC combat control mod (WIP)
This commit is contained in:
@@ -35,6 +35,7 @@ MotionScanner=0
|
||||
;Set to 0 to disable
|
||||
;Set to 1 to control all critters in combat
|
||||
;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
|
||||
;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
|
||||
|
||||
Binary file not shown.
@@ -12,12 +12,19 @@
|
||||
*/
|
||||
|
||||
#include "..\headers\define.h"
|
||||
#include "..\headers\command.h"
|
||||
//#include "..\headers\command.h"
|
||||
#include "main.h"
|
||||
|
||||
#define OBJ_DATA_LIGHT_DISTANCE (0x6C)
|
||||
#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
|
||||
controlMode,
|
||||
noCheckArray,
|
||||
@@ -32,7 +39,7 @@ procedure AllowControl(variable pid) begin
|
||||
return false;
|
||||
end
|
||||
|
||||
procedure combatturn_handler begin
|
||||
procedure CombatTurn_Handler begin
|
||||
variable
|
||||
status := get_sfall_arg,
|
||||
critter := get_sfall_arg,
|
||||
@@ -98,7 +105,7 @@ procedure combatturn_handler begin
|
||||
end
|
||||
end
|
||||
|
||||
procedure gamemodechange_handler begin
|
||||
procedure GameModeChange_Handler begin
|
||||
if (inControl and not(get_game_mode BWAND COMBAT)) then begin
|
||||
inControl := false;
|
||||
npcControl := 0;
|
||||
@@ -106,7 +113,7 @@ procedure gamemodechange_handler begin
|
||||
end
|
||||
end
|
||||
|
||||
procedure inventorymove_handler begin
|
||||
procedure InventoryMove_Handler begin
|
||||
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
|
||||
display_msg(message_str_game(GAME_MSG_PROTO, 675));
|
||||
@@ -115,8 +122,9 @@ procedure inventorymove_handler begin
|
||||
end
|
||||
end
|
||||
|
||||
procedure invenwield_handler begin
|
||||
variable critter := get_sfall_arg,
|
||||
procedure InvenWield_Handler begin
|
||||
variable
|
||||
critter := get_sfall_arg,
|
||||
item := get_sfall_arg,
|
||||
slot := 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
|
||||
|
||||
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
|
||||
displayName := GetConfig("CombatControl", "DisplayName", 0);
|
||||
|
||||
@@ -160,10 +171,10 @@ procedure start begin
|
||||
perksList := GetConfigListInt("CombatControl", "PerksList");
|
||||
fix_array(perksList);
|
||||
|
||||
register_hook_proc(HOOK_COMBATTURN, combatturn_handler);
|
||||
register_hook_proc(HOOK_GAMEMODECHANGE, gamemodechange_handler);
|
||||
register_hook_proc(HOOK_INVENTORYMOVE, inventorymove_handler);
|
||||
register_hook_proc(HOOK_INVENWIELD, invenwield_handler);
|
||||
register_hook_proc(HOOK_COMBATTURN, CombatTurn_Handler);
|
||||
register_hook_proc(HOOK_GAMEMODECHANGE, GameModeChange_Handler);
|
||||
register_hook_proc(HOOK_INVENTORYMOVE, InventoryMove_Handler);
|
||||
register_hook_proc(HOOK_INVENWIELD, InvenWield_Handler);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Binary file not shown.
@@ -86,6 +86,7 @@ static std::vector<HackPair> hackAddr = {
|
||||
// module: PartyControl
|
||||
{0x422BDE, 1}, {0x4229EC, 1},
|
||||
{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
|
||||
{0x43C77C, 1}, {0x43C77D, 4}, // hookcall
|
||||
{0x478AC4, 1}, {0x478AC5, 4}, {0x496823, 5},
|
||||
|
||||
@@ -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)
|
||||
long IsPartyMemberByPid(long pid) {
|
||||
size_t patryCount = fo::var::partyMemberMaxCount;
|
||||
if (patryCount) {
|
||||
size_t partyCount = fo::var::partyMemberMaxCount;
|
||||
if (partyCount) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
drug_effect_event = 0, // critter use drug
|
||||
|
||||
@@ -3598,7 +3598,7 @@ void BugFixes::init()
|
||||
// Fix broken Print() script function
|
||||
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(0x49CB30, check_door_state_hack_open, 1);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "..\main.h"
|
||||
#include "..\FalloutEngine\Fallout2.h"
|
||||
#include "LoadGameHook.h"
|
||||
#include "PartyControl.h"
|
||||
|
||||
#include "MetaruleExtender.h"
|
||||
|
||||
@@ -35,6 +36,7 @@ static bool HorriganEncounterDisabled = false;
|
||||
enum class MetaruleFunction : long {
|
||||
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
|
||||
EXT_PARTY_ORDER_ATTACK = 999
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -61,6 +63,9 @@ static long __fastcall op_metarule3_ext(long metafunc, long* args) {
|
||||
case MetaruleFunction::CLEAR_KEYBOARD_BUFFER:
|
||||
__asm call fo::funcoffs::kb_clear_;
|
||||
break;
|
||||
case MetaruleFunction::EXT_PARTY_ORDER_ATTACK:
|
||||
PartyControl::MemberOrderAttackPatch();
|
||||
break;
|
||||
default:
|
||||
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));
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "HookScripts.h"
|
||||
#include "LoadGameHook.h"
|
||||
#include "ScriptExtender.h"
|
||||
//#include "Objects.h"
|
||||
|
||||
#include "PartyControl.h"
|
||||
|
||||
@@ -271,6 +272,9 @@ static void SetCurrentDude(fo::GameObject* npc) {
|
||||
}
|
||||
if (!exist) __asm call fo::funcoffs::critter_sneak_check_;
|
||||
}
|
||||
|
||||
//if (!isPartyMember) Objects::SetObjectUniqueID(npc);
|
||||
|
||||
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() {
|
||||
npcAutoLevelEnabled = GetConfigInt("Misc", "NPCAutoLevel", 0) != 0;
|
||||
if (npcAutoLevelEnabled) {
|
||||
@@ -663,6 +787,8 @@ void PartyControl::init() {
|
||||
Translate("sfall", "PartyAddictMsg", "Addict", addictMsg, 16);
|
||||
dlogr(" Done", DL_INIT);
|
||||
}
|
||||
|
||||
Translate("sfall", "PartyOrderAttack", "I'll take care of it.", partyOrderAttackMsg, 33);
|
||||
}
|
||||
|
||||
void PartyControl::exit() {
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
|
||||
// Returns pointer to "real" dude, which is different from "dude_obj" when controlling another critter
|
||||
static fo::GameObject* RealDudeObject();
|
||||
|
||||
static void MemberOrderAttackPatch();
|
||||
};
|
||||
|
||||
extern bool npcAutoLevelEnabled;
|
||||
|
||||
Reference in New Issue
Block a user