Improved the party control and fixed mod related bugs

Updated gl_partycontrol script.
This commit is contained in:
NovaRain
2021-10-13 13:01:24 +08:00
parent 2e97fe38ff
commit 101238e125
10 changed files with 128 additions and 79 deletions
Binary file not shown.
+54 -30
View File
@@ -7,7 +7,7 @@
NOTE: this script requires compiler from sfall modderspack with -s option
(short circuit evaluation)
version 1.1
version 1.2
*/
@@ -17,9 +17,11 @@
#define OBJ_DATA_LIGHT_DISTANCE (0x6C)
#define OBJ_DATA_LIGHT_INTENSITY (0x70)
#define PID_PLAYER (16777216)
procedure start;
procedure AllowControl(variable pid);
procedure SetLight(variable critter, variable int, variable dist);
procedure CombatTurn_Handler;
procedure GameModeChange_Handler;
procedure InventoryMove_Handler;
@@ -31,12 +33,17 @@ variable
pidList, perksList,
lightInt, lightDist, npcControl,
displayName, displayNameColor, isShowTag,
inControl := false;
inControl := false,
dudeLightInt, dudeLightDist;
procedure AllowControl(variable pid) begin
if (noCheckArray or scan_array(pidList, pid bwand 0xFFFFFF) != -1) and (party_member_obj(pid)) then return true;
return false;
return ((pidList == 0 or scan_array(pidList, pid bwand 0xFFFFFF) != -1) and (party_member_obj(pid)));
end
procedure SetLight(variable critter, variable lInt, variable lDist) begin
if (lInt) then lInt := round((lInt / 65536.0) * 100); // calc percent of intensity
obj_set_light_level(critter, lInt, lDist); // TODO: make sfall obj_set_light_level function
end
procedure CombatTurn_Handler begin
@@ -46,67 +53,82 @@ procedure CombatTurn_Handler begin
pid, perkID, level;
//display_msg("Combat Turn: " + status + ", by " + obj_name(critter) + "/" + critter + ", arg3: " + get_sfall_arg);
if npcControl then begin
if lightInt then lightInt := round((lightInt / 65536.0) * 100); // calc percent of intensity
obj_set_light_level(npcControl, lightInt, lightDist); // restore light for prev. controlled NPC
end
pid := obj_pid(critter);
if (status == 1 and (AllowControl(pid) or controlMode == 1)) then begin
if (pid == PID_PLAYER and inControl == false) then return;
if (pid != PID_PLAYER and controlMode != 1 and AllowControl(pid) == false) then return;
if (status == 1) then begin
set_dude_obj(critter);
//display_msg("Take control of: " + obj_name(critter));
if (critter != real_dude_obj) then begin
if not(npcControl) then obj_set_light_level(real_dude_obj, 0, 0); // dude off light
if (npcControl == 0) then begin
dudeLightDist := get_object_data(real_dude_obj, OBJ_DATA_LIGHT_DISTANCE);
dudeLightInt := get_object_data(real_dude_obj, OBJ_DATA_LIGHT_INTENSITY);
obj_set_light_level(real_dude_obj, 0, 0); // dude off light
end
npcControl := critter;
lightDist := get_object_data(critter, OBJ_DATA_LIGHT_DISTANCE);
lightInt := get_object_data(critter, OBJ_DATA_LIGHT_INTENSITY);
// set perks (only work with 4.1.8+)
foreach (perkID in perksList) begin
level := has_trait(TRAIT_PERK, real_dude_obj, perkID);
if (level) then critter_add_trait(critter, TRAIT_PERK, perkID, level);
end
intface_redraw;
lightDist := get_object_data(critter, OBJ_DATA_LIGHT_DISTANCE);
lightInt := get_object_data(critter, OBJ_DATA_LIGHT_INTENSITY);
obj_set_light_level(critter, 100, 4);
inControl := true;
end else if npcControl then begin
obj_set_light_level(dude_obj, 100, 4); // set dude default light
end else if (npcControl) then begin
call SetLight(dude_obj, dudeLightInt, dudeLightDist); // restore dude light
npcControl := 0; // dude control
end
if inControl then begin
if (inControl) then begin
// check preference setting _combat_highlight
if (read_byte(0x56D38C)) then set_outline(critter, OUTLINE_GREY);
if (read_byte(0x56D38C)) then set_outline(critter, OUTLINE_GREY); // TODO: replace read_byte with normal function
// center the screen on the controlled critter/dude and remove roof tiles
move_to(dude_obj, dude_tile, dude_elevation);
if (displayName and critter != real_dude_obj) then begin
set_iface_tag_text(displayName, obj_name(critter), displayNameColor);
show_iface_tag(displayName);
isShowTag := true;
end
end
end else if inControl then begin
end else begin
if (npcControl) then begin
call SetLight(critter, lightInt, lightDist); // restore light for controlled NPC
end
if (isShowTag) then begin
hide_iface_tag(displayName);
isShowTag := false;
end
// remove perks before switching control (only work with 4.1.8+)
if (dude_obj != real_dude_obj) then begin
foreach (perkID in perksList) begin
level := has_trait(TRAIT_PERK, real_dude_obj, perkID);
if (level) then critter_rm_trait(critter, TRAIT_PERK, perkID, level);
end
set_dude_obj(0); // w/o redraw interface bar
//display_msg("Return control to real dude!");
end
if isShowTag then begin
hide_iface_tag(displayName);
isShowTag := false;
end
if (status < 0 or AllowControl(pid) == false) then begin
if (dude_obj != real_dude_obj) then begin
set_dude_obj(real_dude_obj);
obj_set_light_level(dude_obj, 100, 4);
npcControl := 0;
end
if (status < 0) then begin
set_dude_obj(real_dude_obj);
call SetLight(dude_obj, dudeLightInt, dudeLightDist); //obj_set_light_level(dude_obj, 100, 4);
end
end
end
procedure GameModeChange_Handler begin
if (inControl and not(get_game_mode BWAND COMBAT)) then begin
if (inControl and (get_game_mode bwand COMBAT) == 0) then begin
inControl := false;
npcControl := 0;
move_to(dude_obj, dude_tile, dude_elevation);
@@ -165,8 +187,10 @@ procedure start begin
end
pidList := GetConfigListInt("CombatControl", "PIDList");
fix_array(pidList);
noCheckArray := (len_array(pidList) == 0);
if (len_array(pidList) > 0) then
fix_array(pidList);
else
pidList := 0;
perksList := GetConfigListInt("CombatControl", "PerksList");
fix_array(perksList);
+1
View File
@@ -39,6 +39,7 @@
#define FO_VAR_combat_end_due_to_load 0x517F98
#define FO_VAR_combat_free_move 0x56D39C
#define FO_VAR_combat_list 0x56D390
#define FO_VAR_combat_obj 0x51805C
#define FO_VAR_combat_state 0x510944
#define FO_VAR_combat_turn_obj 0x56D388
#define FO_VAR_combat_turn_running 0x51093C
+2
View File
@@ -25,7 +25,9 @@ VAR_(colorTable, DWORD)
VAR_(combat_end_due_to_load, DWORD)
VAR_(combat_free_move, DWORD)
VARD(combat_list, fo::GameObject*)
VAR_(combat_obj, fo::GameObject*)
VAR_(combat_state, DWORD)
VAR_(combat_turn_obj, fo::GameObject*)
VAR_(combat_turn_running, DWORD)
VAR_(combatNumTurns, DWORD)
VAR3(crit_succ_eff, fo::CritInfo, 20, 9, 6) // 20 critters with 9 body parts and 6 effects each
+1 -1
View File
@@ -373,7 +373,7 @@ void __stdcall SetHitChanceMax(fo::GameObject* critter, DWORD maximum, DWORD mod
}
void __stdcall SetNoBurstMode(fo::GameObject* critter, bool on) {
if (critter == fo::var::obj_dude || critter->IsNotCritter()) return;
if (critter->protoId == fo::PID_Player || critter->IsNotCritter()) return;
long id = Objects::SetObjectUniqueID(critter);
for (size_t i = 0; i < noBursts.size(); i++) {
+1 -1
View File
@@ -330,7 +330,7 @@ skip:
// Returns the individual critter prototype, or null if it is missing
long* __fastcall CritterStats::GetProto(fo::GameObject* critter) {
if (protoMem.empty() || critter->protoId == fo::PID_Player) return nullptr;
if (critter->protoId == fo::PID_Player || protoMem.empty()) return nullptr;
if (lastGetProtoID.id == critter->id) return lastGetProtoID.proto;
auto itMem = protoMem.find(critter->id);
+7 -2
View File
@@ -374,8 +374,9 @@ skip:
}
}
// hooks combat_turn function
static long combatTurnResult = 0;
// hooks combat_turn function
static long __fastcall CombatTurnHook_Script(fo::GameObject* critter, long dudeBegin) {
BeginHook();
argCount = 3;
@@ -390,12 +391,16 @@ static long __fastcall CombatTurnHook_Script(fo::GameObject* critter, long dudeB
if (cRet > 0 && rets[0] == 1) { // skip turn
goto endHook; // exit hook
}
// set_sfall_return is not used, proceed normally
combatTurnResult = args[0] = fo::func::combat_turn(critter, dudeBegin);
combatTurnResult = fo::func::combat_turn(critter, dudeBegin);
args[0] = combatTurnResult;
if (fo::var::combat_end_due_to_load && combatTurnResult == -1) goto endHook; // don't run end of turn hook when the game was loaded during the combat
//cRet = 0; // reset number of return values
RunHookScript(HOOK_COMBATTURN); // End of turn
if (cRet > 0 && rets[0] == -1) combatTurnResult = -1; // override result of turn
endHook:
+1 -1
View File
@@ -69,7 +69,7 @@ void InventoryKeyPressedHook(DWORD dxKey, bool pressed) {
////////////////////////////////////////////////////////////////////////////////
static int __stdcall CritterGetMaxSize(fo::GameObject* critter) {
if (critter == fo::var::obj_dude) return invSizeMaxLimit;
if (critter->protoId == fo::PID_Player) return invSizeMaxLimit;
if (sizeLimitMode != 3) { // selected mode 1 or 2
if (!(sizeLimitMode & 2) || !(fo::func::isPartyMember(critter))) return 0; // if mode 2 is selected, check this party member, otherwise 0
+60 -43
View File
@@ -1,20 +1,20 @@
/*
* sfall
* Copyright (C) 2008-2016 The sfall team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
* sfall
* Copyright (C) 2008-2016 The sfall team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
@@ -40,7 +40,7 @@ bool npcAutoLevelEnabled;
bool npcEngineLevelUp = true;
static bool isControllingNPC = false;
static bool skipCounterAnim = false;
static char skipCounterAnim;
static int delayedExperience;
static bool switchHandHookInjected = false;
@@ -161,8 +161,10 @@ static void SaveRealDudeState() {
realDude.isSaved = true;
if (skipCounterAnim) SafeWriteBatch<BYTE>(0, {0x422BDE, 0x4229EC}); // no animate
if (skipCounterAnim == 1) {
skipCounterAnim++;
SafeWrite8(0x422BDE, 0); // no animate
}
if (isDebug) fo::func::debug_printf("\n[SFALL] Save dude state.");
}
@@ -250,6 +252,7 @@ static void SetCurrentDude(fo::GameObject* npc) {
fo::util::ToggleNpcFlag(npc, 4, isAddict); // for show/hide addiction box (fix bug)
// switch main dude_obj pointers - this should be done last!
fo::var::combat_turn_obj = npc;
fo::var::obj_dude = npc;
fo::var::inven_dude = npc;
fo::var::inven_pid = npc->protoId;
@@ -323,9 +326,13 @@ static void RestoreRealDudeState(bool redraw = true) {
fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar] = realDude.addictGvar[7];
if (realDude.extendAddictGvar) RestoreAddictGvarState();
if (skipCounterAnim) SafeWriteBatch<BYTE>(1, {0x422BDE, 0x4229EC}); // restore
if (redraw) fo::func::intface_redraw();
if (redraw) {
if (skipCounterAnim == 2) {
skipCounterAnim--;
SafeWrite8(0x422BDE, 1); // restore
}
fo::func::intface_redraw();
}
realDude.isSaved = false;
isControllingNPC = false;
@@ -459,6 +466,10 @@ pcName:
static void PartyControlReset() {
if (realDude.obj_dude != nullptr && isControllingNPC) {
RestoreRealDudeState(false);
if (skipCounterAnim == 2) {
skipCounterAnim = 1; // skipCounterAnim--;
SafeWrite8(0x422BDE, 1); // restore
}
}
realDude.obj_dude = nullptr;
realDude.isSaved = false;
@@ -528,6 +539,11 @@ static void NPCWeaponTweak() {
}
void PartyControl::SwitchToCritter(fo::GameObject* critter) {
if (skipCounterAnim == 2 && critter && critter == realDude.obj_dude) {
skipCounterAnim--;
SafeWrite8(0x422BDE, 1); // restore
}
if (isControllingNPC) {
if (fo::util::IsNpcFlag(fo::var::obj_dude, 0)) { // sneak flag
/* saves the sneak state for the currently controlled NPC and clears its events before switching */
@@ -555,32 +571,33 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) {
}
NPCWeaponTweak();
if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(); // return control to dude
if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(critter != nullptr); // return control to dude
}
if (critter != nullptr && critter != PartyControl::RealDudeObject()) {
if (critter && critter != PartyControl::RealDudeObject()) {
if (!isControllingNPC && !realDude.isSaved) {
SaveRealDudeState();
}
SetCurrentDude(critter);
if (switchHandHookInjected) return;
switchHandHookInjected = true;
// if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook();
if (!switchHandHookInjected) {
switchHandHookInjected = true;
//if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook();
ScriptExtender::OnMapExit() += []() {
if (!partySneakWorking.empty()) {
// unset active sneak flags for controlled NPCs when exiting the map
for (const auto& member : partySneakWorking) fo::util::ToggleNpcFlag(member.object, 0, false);
partySneakWorking.clear();
}
};
HookCall(0x42E25B, pc_flag_off_hook);
HookCall(0x49EB09, proto_name_hook);
ScriptExtender::OnMapExit() += []() {
if (!partySneakWorking.empty()) {
// unset active sneak flags for controlled NPCs when exiting the map
for (const auto& member : partySneakWorking) fo::util::ToggleNpcFlag(member.object, 0, false);
partySneakWorking.clear();
}
};
HookCall(0x42E25B, pc_flag_off_hook);
HookCall(0x49EB09, proto_name_hook);
// Gets dude perks and traits from script while controlling another NPC
// WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code
HookCall(0x458242, GetRealDudePerk); // op_has_trait_
HookCall(0x458326, GetRealDudeTrait); // op_has_trait_
// Gets dude perks and traits from script while controlling another NPC
// WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code
HookCall(0x458242, GetRealDudePerk); // op_has_trait_
HookCall(0x458326, GetRealDudeTrait); // op_has_trait_
}
}
}
@@ -796,7 +813,7 @@ static void __fastcall SetOrderTarget(fo::GameObject* attacker) {
if (target) attacker->critter.whoHitMe = target;
}
static void __declspec(naked) combat_ai_hook_targer() {
static void __declspec(naked) combat_ai_hook_target() {
__asm {
push ecx;
mov ecx, eax;
@@ -813,7 +830,7 @@ void PartyControl::OrderAttackPatch() {
HookCall(0x44C69A, gmouse_handle_event_hook_restore);
MakeCall(0x44B830, gmouse_bk_process_hack);
HookCall(0x42B235, combat_ai_hook_targer);
HookCall(0x42B235, combat_ai_hook_target);
LoadGameHook::OnCombatEnd() += []() {
partyOrderAttackTarget.clear();
};
@@ -849,7 +866,7 @@ void PartyControl::init() {
NpcAutoLevelPatch();
skipCounterAnim = (IniReader::GetConfigInt("Misc", "SpeedInterfaceCounterAnims", 0) == 3);
skipCounterAnim = (IniReader::GetConfigInt("Misc", "SpeedInterfaceCounterAnims", 0) == 3) ? 1 : 0;
// Display party member's current level & AC & addict flag
if (IniReader::GetConfigInt("Misc", "PartyMemberExtraInfo", 0)) {
+1 -1
View File
@@ -61,7 +61,7 @@ static void __declspec(naked) stat_level_hack() {
static int __fastcall check_stat_level(int value, DWORD stat) {
int valLimit;
if (cCritter == fo::var::obj_dude) {
if (cCritter->protoId == fo::PID_Player) {
valLimit = statMinimumsPC[stat];
if (value < valLimit) return valLimit;
valLimit = statMaximumsPC[stat];