Modded & extended NPC support for organisms, new menu tabs, unconsciousness force multiplier

This commit is contained in:
toasterpanic 2026-05-28 10:07:20 -04:00
parent 66f34f8aa1
commit b56cbd1d24
5 changed files with 140 additions and 25 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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