From b56cbd1d24a26df3e15be6883d4c7a6dfeac3dd3 Mon Sep 17 00:00:00 2001 From: toasterpanic Date: Thu, 28 May 2026 10:07:20 -0400 Subject: [PATCH] Modded & extended NPC support for organisms, new menu tabs, unconsciousness force multiplier --- README.md | 18 ++++--- lua/autorun/client/zcnpci_menu.lua | 61 ++++++++++++++++++++---- lua/zcnpci.lua | 70 ++++++++++++++++++++++++++++ lua/zcnpci/modules/falling_legs.lua | 9 ++-- lua/zcnpci/modules/falling_torso.lua | 7 +-- 5 files changed, 140 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index fffc991..3eae5a9 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,26 @@ Adds better NPC integration for the Garry's Mod addon Z-City. While Z-City has a convar that enables the custom health system on NPCs, it really isn't the best or most advanced thing in the world. This has irritated me since I first started using Z-City. Therefore, I've decided to take care of the problem myself with my own plugin. -- NPCs can now be alive while ragdolled, and can move around in that state. -- NPCs can collapse due to unconsciousness or pain. -- NPCs can get back up after being ragdolled. -- Various configurableperformance settings are included, with the biggest one being automatic corpse removal. +- NPCs can be ragdolled like players can, and will wiggle around on the ground. They can get back up, too! +- It takes all elements of Z-City's health system into account, such as unconsciousness and pain. +- Various configurable settings are included, allowing you to tweak the mod to your liking. -This plugin is based off of Kazarei's Euphoria, which in turn is a fork of Fedhoria by Rama. Credits to them for making the cool thing. +This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse remover. + +This addon is based off of Kazarei's Euphoria, which in turn is a fork of Fedhoria by Rama. Credits to them for making the cool thing. ## Known issues -- Kicking NPCs does not knock them down. The same goes for many melee attacks. This is not something I can easily fix -- I would have to override code in Z-City, which I would rather not do. +- Kicking NPCs does not knock them down. The same goes for many melee attacks. This is not something I know how to easily fix -- I would have to override code in Z-City, which I would rather not do. If you do know how to easily fix this, reach out, I would love to add it. +- Bandages, tourniquets, etc. that are on the ragdoll or NPC body will not be there when they get back up. + - NPCs currently instantly get up, with no animation. I plan on remedying this in the future. -- Enemy NPCs will not target living downed NPCs. This is an issue I plan on fixing. +- Enemy NPCs will not target living downed NPCs like how they do players. This will be fixed. ## Incompatibilities - Any mod that modifies the behavior of death ragdolls (Reagdoll, Artagdoll, Fedhoria) +- Mods that automatically remove death ragdolls (it will delete all ragdolled NPCs, even if they're still alive! Try the built-in automatic corpse removal instead.) ## Future plans diff --git a/lua/autorun/client/zcnpci_menu.lua b/lua/autorun/client/zcnpci_menu.lua index 1e013c0..58349c0 100644 --- a/lua/autorun/client/zcnpci_menu.lua +++ b/lua/autorun/client/zcnpci_menu.lua @@ -1,11 +1,34 @@ +local function PopulateCustomNPCSBXToolMenu(pnl) + local modded_npc_whitelist = CreateConVar("zcnpci_modded_npc_whitelist", "NPC-classes-here!", bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) + + pnl:Help("ZCNPCi has support for both modded NPCs, as well as base-game humanoid NPCs that Z-City never added. There is no guarantee that modded NPCs will work, but ones with similar skeletal structure to other base-game humanoid NPCs will generally work better.") + + pnl:CheckBox("Allow base-game extended NPCs", "zcnpci_allow_extended_base_npcs") + pnl:ControlHelp("Enable or disables applying custom health system to most other base-game humanoid NPCs, such as Kleiner.") + + pnl:CheckBox("Allow modded NPCs", "zcnpci_allow_modded_npcs") + pnl:ControlHelp("Enable or disables applying custom health system to modded NPCs.") + + pnl:CheckBox("Auto enable for all humanoid NPCs", "zcnpci_auto_enable_humanoid_npcs") + pnl:ControlHelp("If enabled, NPCs will automatically have the health system applied to them if they have a standard humanoid skeleton.") + + pnl:Help("List of enabled modded NPC classes") + + local text = vgui.Create("DTextEntry") + text:SetMultiline(true) + text:SetTall(200) + text:SetValue(modded_npc_whitelist:GetString()) + + text.OnChange = function (self) + RunConsoleCommand("zcnpci_set_modded_npc_whitelist", text:GetValue()) + end + + pnl:AddItem(text) + + pnl:Help("One NPC class per line. No spaces or other characters. Can only be set by a superadmin or server operator.") +end + local function PopulateRagdollSBXToolMenu(pnl) - pnl:CheckBox("Enabled", "zcnpci_enabled") - pnl:ControlHelp("Enable or disable the addon.") - - pnl:Help(" ") - - pnl:Help(" ") - pnl:NumSlider("Stumble time", "zcnpci_stumble_time", 0, 10, 3) pnl:ControlHelp("How long the ragdoll should try to stumble for.") @@ -55,20 +78,40 @@ local function PopulatePerformanceSBXToolMenu(pnl) pnl:ControlHelp("If on, bodies near death (severe blood loss, comas, etc.) will be clearable.") end +local function PopulateMainSBXToolMenu(pnl) + pnl:CheckBox("Enabled", "zcnpci_enabled") + pnl:ControlHelp("Enable or disable the addon.") + + pnl:Help("This addon was developed by ToasterPanic.") + + pnl:Help("It is based on Kazarei's Euphoria, which in turn is a fork of Fedhoria by Rama. Their work powers the ragdoll movement.") +end + + if engine.ActiveGamemode() == "sandbox" then hook.Add("AddToolMenuCategories", "ZCNPCICategory", function() spawnmenu.AddToolCategory("Utilities", "zcnpci", "Z-City NPCi") end) hook.Add("PopulateToolMenu", "ZCNPCIMenuSettings", function() - spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "RagdollSettings", "Ragdoll", "", "", function(pnl) + spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "ModdedNPCSettings", "Custom NPCs", "", "", function(pnl) pnl:ClearControls() - PopulateRagdollSBXToolMenu(pnl) + PopulateCustomNPCSBXToolMenu(pnl) end) spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "PerformanceSettings", "Performance", "", "", function(pnl) pnl:ClearControls() PopulatePerformanceSBXToolMenu(pnl) end) + + spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "RagdollSettings", "Ragdoll", "", "", function(pnl) + pnl:ClearControls() + PopulateRagdollSBXToolMenu(pnl) + end) + + spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "MainSettings", "Main", "", "", function(pnl) + pnl:ClearControls() + PopulateMainSBXToolMenu(pnl) + end) end) end \ No newline at end of file diff --git a/lua/zcnpci.lua b/lua/zcnpci.lua index ddb972a..3d6010f 100644 --- a/lua/zcnpci.lua +++ b/lua/zcnpci.lua @@ -14,12 +14,40 @@ local treat_crippled_as_dead = CreateConVar("zcnpci_treat_crippled_as_dead", 1, local treat_unconscious_as_dead = CreateConVar("zcnpci_treat_unconscious_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local treat_near_death_as_dead = CreateConVar("zcnpci_treat_near_death_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) +local allow_extended_base_npcs = CreateConVar("zcnpci_allow_extended_base_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) +local allow_modded_npcs = CreateConVar("zcnpci_allow_modded_npcs", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) +local auto_enable_humanoid_npcs = CreateConVar("zcnpci_auto_enable_humanoid_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) +local modded_npc_whitelist = CreateConVar("zcnpci_modded_npc_whitelist", "NPC-classes-here!", bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) + +-- The way I have to set up the npc whitelist client-side makes it so the host can't configure it without console commands, so we have to do this shit +local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelist", function(ply, cmd, args) + if !IsValid(ply) or !args[1] then return end + + if !ply:IsSuperAdmin() then return end + + modded_npc_whitelist:SetString(args[1]) +end) + local debug_ragdoll_all = CreateConVar("zcnpci_debug_ragdoll_all", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local last_dmgpos = {} local last_hitgroup = {} local last_attacker = {} +-- These NPCs do not have organisms by default, despite being humanoid characters built into the game. +local base_npc_whitelist = { + "npc_kleiner", + "npc_breen", + "npc_barney", + "npc_alyx", + "npc_eli", + "npc_gman", + "npc_magnusson", + "npc_mossman", + "npc_odessa", + "npc_monk" +} + local corpses = {} local last_corpse_tick = CurTime() @@ -68,6 +96,25 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll) ragdoll.class_in_previous_life = ent:GetClass() + if !ragdoll.organism then + ragdoll.inventory = {} + ragdoll.inventory.Weapons = {} + + hg.organism.Add(ragdoll) + table.Merge(ragdoll.organism, ent.organism) + + hook.Run("RagdollDeath", ent, ragdoll) + + --table.Merge(zb.net.list[rag], zb.net.list[ent]) + + ragdoll.organism.owner = ragdoll + ragdoll:CallOnRemove("organism", hg.organism.Remove, ragdoll) + ragdoll.organism.owner.fullsend = true + hg.send_bareinfo(ragdoll.organism) + + ent.organism = nil + end + timer.Simple(0, function() ragdoll.organism.alive = true @@ -209,6 +256,29 @@ end) last_dmgpos[ent] = dmginfo:GetDamagePosition() end)]] +hook.Add("OnEntityCreated", "zcnpci", function(ent) + if !IsValid(ent) then return end + + local add_organism = false + + if allow_modded_npcs:GetBool() then + local modded_npc_whitelist_string = modded_npc_whitelist:GetString() + modded_npc_whitelist_string = string.Replace(modded_npc_whitelist_string, "\n", " ") + + local modded_npc_whitelist_table = string.Split(modded_npc_whitelist_string, " ") + + if table.HasValue(modded_npc_whitelist_table, ent:GetClass()) then add_organism = true end + end + + if allow_extended_base_npcs:GetBool() and table.HasValue(base_npc_whitelist, ent:GetClass()) then add_organism = true end + + if add_organism then + hg.organism.Add(ent) + hg.organism.Clear(ent.organism) + ent.organism.fakePlayer = true + end +end) + local once = true --RagMod/TTT support diff --git a/lua/zcnpci/modules/falling_legs.lua b/lua/zcnpci/modules/falling_legs.lua index 35e9f99..9c91efb 100644 --- a/lua/zcnpci/modules/falling_legs.lua +++ b/lua/zcnpci/modules/falling_legs.lua @@ -259,8 +259,8 @@ function MODULE:PhysicsSimulate(phys, dt) return true -- We use standard physics. end - -- Logic for the disappearance timer - local f = 1 -- Force multiplier (default: 1) + -- Force multiplier + local f = target.organism.consciousness local minimum_down_timer = 0 if self.StartDie then @@ -276,7 +276,7 @@ function MODULE:PhysicsSimulate(phys, dt) -- If the NPC is dead, they probably aren't coming back; don't bother bringing them back to life self:Remove() return false -- Cut the bullshit - elseif (target.organism.consciousness <= 0.3) or ((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) then + elseif (target.organism.consciousness <= 0.5) or ((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) then self.StartDie = cur_time return false end @@ -288,6 +288,7 @@ function MODULE:PhysicsSimulate(phys, dt) (target.class_in_previous_life != nil) and !(target.organism.llegamputated or target.organism.rlegamputated or target.organism.larmamputated or target.organism.rarmamputated) and (target.organism.pain <= 80) and + (target.organism.consciousness > 0.65) and !target:IsOnFire() ) then local ent = ents.Create(target.class_in_previous_life) @@ -375,7 +376,7 @@ function MODULE:PhysicsSimulate(phys, dt) return true, f end - + -- Logic for other bones local delta_collide = cur_time - self.LastCollideTime if (delta_collide < 0.2) then diff --git a/lua/zcnpci/modules/falling_torso.lua b/lua/zcnpci/modules/falling_torso.lua index 9c8bd3e..e06f513 100644 --- a/lua/zcnpci/modules/falling_torso.lua +++ b/lua/zcnpci/modules/falling_torso.lua @@ -180,11 +180,8 @@ function MODULE:PhysicsSimulate(phys, dt) -- --- логика идет нахуй - -- логика для таймера - local f = 1 -- сила есть ума не надо (по умолчанию 1) - --[[if self.StartDie then - f = math_Clamp(1 - (cur_time - self.StartDie) / die_time:GetFloat(), 0, 1) - end]] + -- Force multiplier + local f = target.organism.consciousness -- ебаная логика для regmod if ragmod and ragmod:IsRagmodRagdoll(target) then