2017-05-23 22:28:42 +08:00
|
|
|
/*
|
2017-04-01 21:11:19 +07:00
|
|
|
|
|
|
|
|
NPC Combat Control
|
2017-05-23 22:28:42 +08:00
|
|
|
|
2023-07-02 20:40:35 +08:00
|
|
|
Previously was part of sfall itself, now a separate mod.
|
|
|
|
|
Features:
|
|
|
|
|
- allows you to take control of your party members or other NPCs during combat
|
|
|
|
|
- configurable list of perk IDs for perks being inherited from the player (optional, not all perks can work)
|
|
|
|
|
- allows the player to gain a positive reputation when killing NPCs while controlling other critters (optional)
|
|
|
|
|
- a notification box to display the name of the controlled critter above the interface bar (optional)
|
2017-05-23 22:28:42 +08:00
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
NOTE: this script requires compiler from sfall modderspack with -s option
|
|
|
|
|
(short circuit evaluation)
|
2017-05-23 22:28:42 +08:00
|
|
|
|
2023-10-05 09:24:34 +08:00
|
|
|
version 1.4
|
2017-04-01 21:11:19 +07:00
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
2026-04-14 11:27:22 +08:00
|
|
|
#pragma sce
|
|
|
|
|
|
2023-03-05 10:18:59 +08:00
|
|
|
#include "..\headers\global.h"
|
|
|
|
|
#include "..\headers\critrpid.h"
|
2017-04-01 21:11:19 +07:00
|
|
|
#include "main.h"
|
|
|
|
|
|
2023-06-24 14:17:38 +08:00
|
|
|
#ifndef DEBUG
|
|
|
|
|
#define DEBUGMSG(x)
|
|
|
|
|
#else
|
|
|
|
|
#define DEBUGMSG(x) debug_msg(x);
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-01-21 12:59:45 +08:00
|
|
|
procedure start;
|
|
|
|
|
procedure AllowControl(variable pid);
|
2021-10-13 13:01:24 +08:00
|
|
|
procedure SetLight(variable critter, variable int, variable dist);
|
2021-01-21 12:59:45 +08:00
|
|
|
procedure CombatTurn_Handler;
|
|
|
|
|
procedure GameModeChange_Handler;
|
|
|
|
|
procedure InventoryMove_Handler;
|
|
|
|
|
procedure InvenWield_Handler;
|
2022-06-06 12:54:02 +08:00
|
|
|
procedure SetGlobalVar_Handler;
|
2021-01-21 12:59:45 +08:00
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
variable
|
|
|
|
|
controlMode,
|
2024-03-27 11:18:53 +08:00
|
|
|
pidList, perksList, addedPerkLevels,
|
2019-03-03 11:00:30 +08:00
|
|
|
lightInt, lightDist, npcControl,
|
2019-07-12 07:36:47 +08:00
|
|
|
displayName, displayNameColor, isShowTag,
|
2021-10-13 13:01:24 +08:00
|
|
|
inControl := false,
|
|
|
|
|
dudeLightInt, dudeLightDist;
|
2017-05-23 22:28:42 +08:00
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
|
2023-10-05 09:24:34 +08:00
|
|
|
// returns non-zero for a controlled NPC
|
2019-02-20 21:26:23 +08:00
|
|
|
procedure AllowControl(variable pid) begin
|
2023-10-05 09:24:34 +08:00
|
|
|
if (pidList) then begin
|
|
|
|
|
return scan_array(pidList, pid bwand 0xFFFFFF) != -1;
|
|
|
|
|
end
|
|
|
|
|
return party_member_obj(pid);
|
2021-10-13 13:01:24 +08:00
|
|
|
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
|
2019-02-20 21:26:23 +08:00
|
|
|
end
|
|
|
|
|
|
2021-01-21 12:59:45 +08:00
|
|
|
procedure CombatTurn_Handler begin
|
2017-04-01 21:11:19 +07:00
|
|
|
variable
|
|
|
|
|
status := get_sfall_arg,
|
2017-05-23 22:28:42 +08:00
|
|
|
critter := get_sfall_arg,
|
2024-03-27 11:18:53 +08:00
|
|
|
pid, i, perkID, level;
|
2017-05-23 22:28:42 +08:00
|
|
|
|
2023-06-24 14:17:38 +08:00
|
|
|
DEBUGMSG("Combat Turn: " + status + ", by " + obj_name(critter) + "/" + critter + ", arg3: " + get_sfall_arg)
|
2019-03-03 11:00:30 +08:00
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
pid := obj_pid(critter);
|
2021-10-13 13:01:24 +08:00
|
|
|
if (pid == PID_PLAYER and inControl == false) then return;
|
2022-08-09 08:35:26 +08:00
|
|
|
|
|
|
|
|
if (pid != PID_PLAYER and controlMode != 1 and AllowControl(pid) == false) then begin
|
2023-06-24 14:17:38 +08:00
|
|
|
DEBUGMSG("Skip control.")
|
|
|
|
|
|
2022-08-09 08:35:26 +08:00
|
|
|
if (npcControl) then begin
|
|
|
|
|
npcControl := 0;
|
2023-06-24 14:17:38 +08:00
|
|
|
set_dude_obj(real_dude_obj);
|
2022-08-09 08:35:26 +08:00
|
|
|
call SetLight(dude_obj, dudeLightInt, dudeLightDist); // restore dude light
|
2023-06-24 14:17:38 +08:00
|
|
|
move_to(dude_obj, dude_tile, dude_elevation);
|
|
|
|
|
|
|
|
|
|
DEBUGMSG("Set dude after NPC control.")
|
2022-08-09 08:35:26 +08:00
|
|
|
end
|
|
|
|
|
return;
|
|
|
|
|
end
|
2021-10-13 13:01:24 +08:00
|
|
|
|
|
|
|
|
if (status == 1) then begin
|
2019-02-20 21:26:23 +08:00
|
|
|
set_dude_obj(critter);
|
2023-06-24 14:17:38 +08:00
|
|
|
|
|
|
|
|
DEBUGMSG("Take control of: " + obj_name(critter))
|
2021-10-13 13:01:24 +08:00
|
|
|
|
2019-02-20 21:26:23 +08:00
|
|
|
if (critter != real_dude_obj) then begin
|
2021-10-13 13:01:24 +08:00
|
|
|
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
|
2019-03-03 11:00:30 +08:00
|
|
|
npcControl := critter;
|
2021-10-13 13:01:24 +08:00
|
|
|
|
|
|
|
|
lightDist := get_object_data(critter, OBJ_DATA_LIGHT_DISTANCE);
|
|
|
|
|
lightInt := get_object_data(critter, OBJ_DATA_LIGHT_INTENSITY);
|
|
|
|
|
|
2019-07-14 10:38:17 +08:00
|
|
|
// set perks (only work with 4.1.8+)
|
2024-03-27 11:18:53 +08:00
|
|
|
foreach (i: perkID in perksList) begin
|
|
|
|
|
addedPerkLevels[i] := 0;
|
|
|
|
|
|
2019-07-14 10:38:17 +08:00
|
|
|
level := has_trait(TRAIT_PERK, real_dude_obj, perkID);
|
2024-03-27 11:18:53 +08:00
|
|
|
if (level) then begin
|
|
|
|
|
addedPerkLevels[i] := level;
|
|
|
|
|
critter_add_trait(critter, TRAIT_PERK, perkID, level);
|
|
|
|
|
end
|
2019-07-14 10:38:17 +08:00
|
|
|
end
|
2023-10-05 09:24:34 +08:00
|
|
|
// special handling if dude has Jinxed trait/perk
|
|
|
|
|
if (has_trait(TRAIT_TRAIT, real_dude_obj, TRAIT_jinxed) or has_trait(TRAIT_PERK, real_dude_obj, PERK_jinxed_perk)) then begin
|
|
|
|
|
critter_add_trait(critter, TRAIT_PERK, PERK_jinxed_perk, 1);
|
|
|
|
|
end
|
2022-08-09 08:35:26 +08:00
|
|
|
intface_redraw;
|
2021-10-13 13:01:24 +08:00
|
|
|
|
2019-03-03 11:00:30 +08:00
|
|
|
obj_set_light_level(critter, 100, 4);
|
2018-07-13 11:25:42 +08:00
|
|
|
inControl := true;
|
2021-10-13 13:01:24 +08:00
|
|
|
end else if (npcControl) then begin
|
|
|
|
|
call SetLight(dude_obj, dudeLightInt, dudeLightDist); // restore dude light
|
2022-08-09 08:35:26 +08:00
|
|
|
npcControl := 0; // for dude control
|
|
|
|
|
intface_redraw;
|
2018-07-11 10:03:03 +08:00
|
|
|
end
|
2021-10-13 13:01:24 +08:00
|
|
|
|
|
|
|
|
if (inControl) then begin
|
2019-11-02 08:45:28 +08:00
|
|
|
// check preference setting _combat_highlight
|
2021-10-13 13:01:24 +08:00
|
|
|
if (read_byte(0x56D38C)) then set_outline(critter, OUTLINE_GREY); // TODO: replace read_byte with normal function
|
|
|
|
|
|
2019-07-12 07:36:47 +08:00
|
|
|
// center the screen on the controlled critter/dude and remove roof tiles
|
2018-07-13 11:25:42 +08:00
|
|
|
move_to(dude_obj, dude_tile, dude_elevation);
|
2021-10-13 13:01:24 +08:00
|
|
|
|
2018-11-01 17:25:29 +08:00
|
|
|
if (displayName and critter != real_dude_obj) then begin
|
2018-07-13 11:25:42 +08:00
|
|
|
set_iface_tag_text(displayName, obj_name(critter), displayNameColor);
|
|
|
|
|
show_iface_tag(displayName);
|
2019-07-12 07:36:47 +08:00
|
|
|
isShowTag := true;
|
2018-07-13 11:25:42 +08:00
|
|
|
end
|
2018-07-10 12:05:15 +08:00
|
|
|
end
|
2021-10-13 13:01:24 +08:00
|
|
|
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
|
|
|
|
|
|
2019-10-15 22:17:08 +08:00
|
|
|
// remove perks before switching control (only work with 4.1.8+)
|
2019-11-07 09:45:57 +08:00
|
|
|
if (dude_obj != real_dude_obj) then begin
|
2023-06-24 14:17:38 +08:00
|
|
|
DEBUGMSG("Remove perks after NPC control.")
|
2024-03-27 11:18:53 +08:00
|
|
|
foreach (i: perkID in perksList) begin
|
|
|
|
|
level := addedPerkLevels[i]; //has_trait(TRAIT_PERK, real_dude_obj, perkID);
|
2019-11-10 18:09:12 +08:00
|
|
|
if (level) then critter_rm_trait(critter, TRAIT_PERK, perkID, level);
|
2019-10-15 22:17:08 +08:00
|
|
|
end
|
2023-10-05 09:24:34 +08:00
|
|
|
// special handling for Jinxed
|
|
|
|
|
critter_rm_trait(critter, TRAIT_PERK, PERK_jinxed_perk, -1);
|
2019-10-15 22:17:08 +08:00
|
|
|
end
|
2021-10-13 13:01:24 +08:00
|
|
|
|
|
|
|
|
if (status < 0) then begin
|
|
|
|
|
set_dude_obj(real_dude_obj);
|
2022-08-09 08:35:26 +08:00
|
|
|
call SetLight(dude_obj, dudeLightInt, dudeLightDist);
|
2019-02-20 21:26:23 +08:00
|
|
|
end
|
2017-04-01 21:11:19 +07:00
|
|
|
end
|
2018-07-13 11:25:42 +08:00
|
|
|
end
|
|
|
|
|
|
2021-01-21 12:59:45 +08:00
|
|
|
procedure GameModeChange_Handler begin
|
2021-10-13 13:01:24 +08:00
|
|
|
if (inControl and (get_game_mode bwand COMBAT) == 0) then begin
|
2019-02-20 21:26:23 +08:00
|
|
|
inControl := false;
|
2019-07-12 07:36:47 +08:00
|
|
|
npcControl := 0;
|
2019-02-20 21:26:23 +08:00
|
|
|
move_to(dude_obj, dude_tile, dude_elevation);
|
2023-10-05 09:24:34 +08:00
|
|
|
DEBUGMSG("Move to dude after NPC control.")
|
2019-02-20 21:26:23 +08:00
|
|
|
end
|
2017-04-01 21:11:19 +07:00
|
|
|
end
|
|
|
|
|
|
2021-01-21 12:59:45 +08:00
|
|
|
procedure InventoryMove_Handler begin
|
2019-07-12 07:36:47 +08:00
|
|
|
if (npcControl and get_sfall_arg == 3) then begin // armor slot
|
2018-11-01 17:25:29 +08:00
|
|
|
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));
|
|
|
|
|
set_sfall_return(true);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-01-21 12:59:45 +08:00
|
|
|
procedure InvenWield_Handler begin
|
|
|
|
|
variable
|
|
|
|
|
critter := get_sfall_arg,
|
2019-04-06 09:33:27 +08:00
|
|
|
item := get_sfall_arg,
|
|
|
|
|
slot := get_sfall_arg,
|
|
|
|
|
event := get_sfall_arg;
|
|
|
|
|
|
|
|
|
|
// Fix weapon duplication when equipping in combat
|
2019-04-07 10:59:02 +08:00
|
|
|
if (event and slot == INVEN_TYPE_RIGHT_HAND and AllowControl(obj_pid(critter))) then begin
|
|
|
|
|
set_flags(item, get_flags(item) bwand bwnot(FLAG_LEFT_HAND));
|
2019-04-06 09:33:27 +08:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-06-06 12:54:02 +08:00
|
|
|
procedure SetGlobalVar_Handler begin
|
|
|
|
|
if (npcControl and get_sfall_arg == GVAR_PLAYER_REPUTATION and obj_pid(dude_obj) != PID_PLAYER) then begin
|
|
|
|
|
variable value := global_var(GVAR_PLAYER_REPUTATION);
|
|
|
|
|
if (get_sfall_arg > value) then begin
|
|
|
|
|
set_sfall_arg(1, value);
|
|
|
|
|
set_sfall_return(value);
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
procedure start begin
|
2023-06-24 14:17:38 +08:00
|
|
|
if game_loaded then begin
|
|
|
|
|
if (sfall_ver_major < 4) then return;
|
2019-02-20 21:26:23 +08:00
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
controlMode := GetConfig("CombatControl", "Mode", 0);
|
2021-01-21 12:59:45 +08:00
|
|
|
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;
|
2022-06-24 09:36:45 +08:00
|
|
|
end else if (controlMode == 1 and GetIniConfig("Debugging", "Enable", 0, "ddraw.ini") == 0) then begin
|
|
|
|
|
controlMode := 2;
|
2021-01-21 12:59:45 +08:00
|
|
|
end
|
2022-06-24 09:36:45 +08:00
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
if (controlMode > 0) then begin
|
2019-02-20 21:26:23 +08:00
|
|
|
displayName := GetConfig("CombatControl", "DisplayName", 0);
|
|
|
|
|
|
2021-10-15 10:06:02 +08:00
|
|
|
if (displayName) then begin
|
2019-02-20 21:26:23 +08:00
|
|
|
variable nameTag := add_iface_tag;
|
|
|
|
|
debug_msg("NPC control - added new box: " + nameTag);
|
|
|
|
|
if (nameTag <= 0) then begin // box added?
|
|
|
|
|
variable maxBoxCount := get_ini_setting("ddraw.ini|Misc|BoxBarCount");
|
|
|
|
|
if (maxBoxCount < 0) then maxBoxCount := 9;
|
|
|
|
|
else maxBoxCount += 4;
|
|
|
|
|
if (displayName < 5 or displayName > maxBoxCount) then displayName := 0;
|
|
|
|
|
end else begin
|
|
|
|
|
displayName := nameTag;
|
|
|
|
|
end
|
|
|
|
|
displayNameColor := GetConfig("CombatControl", "DisplayNameColor", 0);
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-01 21:11:19 +07:00
|
|
|
pidList := GetConfigListInt("CombatControl", "PIDList");
|
2021-10-13 13:01:24 +08:00
|
|
|
if (len_array(pidList) > 0) then
|
|
|
|
|
fix_array(pidList);
|
|
|
|
|
else
|
|
|
|
|
pidList := 0;
|
2017-05-23 22:28:42 +08:00
|
|
|
|
2019-07-14 10:38:17 +08:00
|
|
|
perksList := GetConfigListInt("CombatControl", "PerksList");
|
|
|
|
|
fix_array(perksList);
|
|
|
|
|
|
2024-03-27 11:18:53 +08:00
|
|
|
addedPerkLevels := create_array_list(len_array(perksList));
|
|
|
|
|
|
2021-01-21 12:59:45 +08:00
|
|
|
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);
|
2022-06-06 12:54:02 +08:00
|
|
|
|
|
|
|
|
if (GetConfig("CombatControl", "GainReputation", 0) == 0) then begin
|
|
|
|
|
register_hook_proc(HOOK_SETGLOBALVAR, SetGlobalVar_Handler);
|
|
|
|
|
end
|
2021-10-15 10:06:02 +08:00
|
|
|
end else begin
|
|
|
|
|
exit;
|
2017-04-01 21:11:19 +07:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|