Compare commits

...

6 commits

6 changed files with 289 additions and 148 deletions

View file

@ -2,21 +2,31 @@
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. 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 be ragdolled like players can, and will wiggle around on the ground. They can get back up, too!
- NPCs can collapse due to unconsciousness or pain. - It takes all elements of Z-City's health system into account, such as unconsciousness and pain.
- NPCs can get back up after being ragdolled. - Various configurable settings are included, allowing you to tweak the mod to your liking.
- Various performance settings are included, with the biggest one being automatic corpse removal.
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 ## Known issues
- Kicking NPCs does not knock them down. This is not something I can easily fix -- I would have to override code in Z-City, which I would rather not do. - Bandages, tourniquets, etc. that are on the ragdoll or NPC body will not be there when they get back up. Don't know how to fix this unfortunately.
- NPCs currently instantly get up, with no animation. I plan on remedying this in the future. - NPCs currently instantly get up, with no animation. I plan on remedying this in the future.
- Enemy NPCs will not target living downed NPCs like how they do players. This will be fixed.
## Incompatibilities ## Compatibility
- Any mod that modifies the behavior of death ragdolls (Reagdoll, Artagdoll, Fedhoria) This should be compatible with:
- The majority of NPC AI improvement mods
- Competently-programmed Z-City addons
It is, however, not compatbile with:
- Most mods that modify the behavior of death ragdolls (Reagdoll, Artagdoll, especially 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 ## 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) 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:NumSlider("Stumble time", "zcnpci_stumble_time", 0, 10, 3)
pnl:ControlHelp("How long the ragdoll should try to stumble for.") pnl:ControlHelp("How long the ragdoll should try to stumble for.")
@ -20,8 +43,15 @@ local function PopulateRagdollSBXToolMenu(pnl)
end end
local function PopulatePerformanceSBXToolMenu(pnl) local function PopulatePerformanceSBXToolMenu(pnl)
pnl:NumSlider("Activation range", "zcnpci_active_range", 0, 5000, 0) pnl:NumSlider("NPC ticks per second", "zcnpci_tps", 1, 66, 0)
pnl:ControlHelp("How close the ragdoll has to be to a player to be living when downed by damage. Enemies downed by other causes are unaffected.") pnl:ControlHelp("How many times NPCs should be processed per second. Will make them more responsive at the cost of performance.")
pnl:Help(" ")
pnl:Help(" ")
pnl:NumSlider("Activation range", "zcnpci_active_range", 0, 32768, 0)
pnl:ControlHelp("How close the ragdoll has to be to a player to live when downed by direct damage. Enemies downed by other causes are unaffected.")
pnl:CheckBox("Always activate on player kill", "zcnpci_always_active_on_player_kill") pnl:CheckBox("Always activate on player kill", "zcnpci_always_active_on_player_kill")
pnl:ControlHelp("If on, all player-damaged ragdolls will be activated regardless of other factors.") pnl:ControlHelp("If on, all player-damaged ragdolls will be activated regardless of other factors.")
@ -33,6 +63,9 @@ local function PopulatePerformanceSBXToolMenu(pnl)
pnl:CheckBox("Enable automatic corpse removal", "zcnpci_enable_corpse_removal") pnl:CheckBox("Enable automatic corpse removal", "zcnpci_enable_corpse_removal")
pnl:ControlHelp("Enable or disable the removal of dead corpses.") pnl:ControlHelp("Enable or disable the removal of dead corpses.")
pnl:NumSlider("Corpse time between loops", "zcnpci_corpse_loop_time", 1, 66, 0)
pnl:ControlHelp("How long should we wait in between checks?")
pnl:NumSlider("Max corpses", "zcnpci_max_corpses", -1, 64, 0) pnl:NumSlider("Max corpses", "zcnpci_max_corpses", -1, 64, 0)
pnl:ControlHelp("The maximum amount of dead corpses that are allowed before one is removed. Set to -1 to disable") pnl:ControlHelp("The maximum amount of dead corpses that are allowed before one is removed. Set to -1 to disable")
@ -42,30 +75,53 @@ local function PopulatePerformanceSBXToolMenu(pnl)
pnl:NumSlider("Max distance between player", "zcnpci_max_corpse_distance", -1, 32768, 2) pnl:NumSlider("Max distance between player", "zcnpci_max_corpse_distance", -1, 32768, 2)
pnl:ControlHelp("If the corpse is further than this distance, it will be removed. Set to -1 to disable") pnl:ControlHelp("If the corpse is further than this distance, it will be removed. Set to -1 to disable")
pnl:CheckBox("Never remove corpses near the player", "zcnpci_disable_corpse_removal_near_player") pnl:NumSlider("Min distance between player", "zcnpci_min_corpse_distance", 0, 32768, 2)
pnl:ControlHelp("If on, corpses under the variable above in distance will never be cleared.") pnl:ControlHelp("If the corpse is closer than this distance, it will never be cleared.")
pnl:CheckBox("Treat crippled NPCs as dead", "zcnpci_treat_crippled_as_dead") pnl:CheckBox("Treat crippled NPCs as dead", "zcnpci_treat_crippled_as_dead")
pnl:ControlHelp("If on, corpses with amputations or both legs broken will be clearable.") pnl:ControlHelp("If on, bodies with amputations or both legs broken will be clearable.")
pnl:CheckBox("Treat unconscious NPCs as dead", "zcnpci_treat_unconscious_as_dead")
pnl:ControlHelp("If on, bodies that are unconscious will be clearable.")
pnl:CheckBox("Treat near-death NPCs as dead", "zcnpci_treat_near_death_as_dead") pnl:CheckBox("Treat near-death NPCs as dead", "zcnpci_treat_near_death_as_dead")
pnl:ControlHelp("If on, corpses near death (severe blood loss, comas, etc.) will be clearable.") pnl:ControlHelp("If on, bodies near death (severe blood loss, comas, etc.) will be clearable.")
end 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 if engine.ActiveGamemode() == "sandbox" then
hook.Add("AddToolMenuCategories", "ZCNPCICategory", function() hook.Add("AddToolMenuCategories", "ZCNPCICategory", function()
spawnmenu.AddToolCategory("Utilities", "zcnpci", "Z-City NPCi") spawnmenu.AddToolCategory("Utilities", "zcnpci", "Z-City NPCi")
end) end)
hook.Add("PopulateToolMenu", "ZCNPCIMenuSettings", function() hook.Add("PopulateToolMenu", "ZCNPCIMenuSettings", function()
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "RagdollSettings", "Ragdoll", "", "", function(pnl) spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "ModdedNPCSettings", "Custom NPCs", "", "", function(pnl)
pnl:ClearControls() pnl:ClearControls()
PopulateRagdollSBXToolMenu(pnl) PopulateCustomNPCSBXToolMenu(pnl)
end) end)
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "PerformanceSettings", "Performance", "", "", function(pnl) spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "PerformanceSettings", "Performance", "", "", function(pnl)
pnl:ClearControls() pnl:ClearControls()
PopulatePerformanceSBXToolMenu(pnl) PopulatePerformanceSBXToolMenu(pnl)
end) 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)
end end

View file

@ -1,23 +1,57 @@
include("zcnpci/modules.lua") include("zcnpci/modules.lua")
local enabled = CreateConVar("zcnpci_enabled", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local enabled = CreateConVar("zcnpci_enabled", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local active_range = CreateConVar("zcnpci_active_range", 3000, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local active_range = CreateConVar("zcnpci_active_range", 32768, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local always_active_on_player_kill = CreateConVar("zcnpci_always_active_on_player_kill", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local always_active_on_player_kill = CreateConVar("zcnpci_always_active_on_player_kill", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local ticks_per_second = CreateConVar("zcnpci_tps", 20, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local remove_corpses = CreateConVar("zcnpci_enable_corpse_removal", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local remove_corpses = CreateConVar("zcnpci_enable_corpse_removal", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local corpse_loop_time = CreateConVar("zcnpci_corpse_loop_time", 2.0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local max_corpses = CreateConVar("zcnpci_max_corpses", 8, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local max_corpses = CreateConVar("zcnpci_max_corpses", 8, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local max_corpse_time = CreateConVar("zcnpci_max_corpse_time", 120, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local max_corpse_time = CreateConVar("zcnpci_max_corpse_time", 120, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local min_corpse_distance = CreateConVar("zcnpci_min_corpse_distance", 500, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local max_corpse_distance = CreateConVar("zcnpci_max_corpse_distance", 2500, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local max_corpse_distance = CreateConVar("zcnpci_max_corpse_distance", 2500, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local no_corpse_removal_near_player = CreateConVar("zcnpci_disable_corpse_removal_near_player", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local no_corpse_removal_near_player = CreateConVar("zcnpci_disable_corpse_removal_near_player", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local treat_crippled_as_dead = CreateConVar("zcnpci_treat_crippled_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED)) local treat_crippled_as_dead = CreateConVar("zcnpci_treat_crippled_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
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 treat_near_death_as_dead = CreateConVar("zcnpci_treat_near_death_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local debug_ragdoll_all = CreateConVar("zcnpci_debug_ragdoll_all", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_PROTECTED)) 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", "nb_example", 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_dmgpos = {}
local last_hitgroup = {} local last_hitgroup = {}
local last_attacker = {} local last_attacker = {}
local npcs_to_fake = {}
-- 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 corpses = {}
local last_corpse_tick = CurTime() local last_corpse_tick = CurTime()
@ -66,6 +100,33 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
ragdoll.class_in_previous_life = ent:GetClass() 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)
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
if ent.npcfakeknockback then
ragdoll:GetPhysicsObject():SetVelocity(ent.npcfakeknockback)
end
-- For some reason, citizen types such as rebels, medics, refugees, etc. use a keyvalue.
-- That keyvalue does not transfer over to the ragdoll, despite it looking like the NPC,
-- and you have to manually save it to the ragdoll to be able to make it work.
-- Why is it like this? Fuck if I know, I just know I don't wanna deal with it again.
ragdoll.citizentype = ent:GetInternalVariable("citizentype")
timer.Simple(0, function() timer.Simple(0, function()
ragdoll.organism.alive = true ragdoll.organism.alive = true
@ -76,24 +137,60 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
end) end)
end) end)
hook.Add("EntityTakeDamage", "zcnpci", function(ent, dmginfo) hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
if !enabled:GetBool() then return end if !enabled:GetBool() or !IsValid(dmginfo) then return end
if (!ent:IsNPC() or dmginfo:GetDamage() < ent:Health()) then return end if !ent:IsNPC() then
if !ent.organism or !ent.StartDie then return end
last_dmgpos[ent] = dmginfo:GetDamagePosition() if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST + DMG_CLUB + DMG_SLASH + DMG_GENERIC) then
-- Reset ragdoll get up timer -- don't want someone getting up mid-curbstomp
ent.StartDie = CurTime()
end
return
end
last_dmgpos[ent] = dmginfo:GetDamage()
last_attacker[ent] = dmginfo:GetAttacker() last_attacker[ent] = dmginfo:GetAttacker()
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST) then
if dmginfo:GetDamage() > 3 then
table.insert(npcs_to_fake, ent)
end
elseif dmginfo:IsDamageType(DMG_CLUB + DMG_SLASH) then
local fists = dmginfo:GetAttacker():GetWeapon("weapon_hands_sh")
if !IsValid(fists) then fists = dmginfo:GetAttacker():GetWeapon("weapon_hg_coolhands") end
local attacker = dmginfo:GetAttacker()
print(fists.InLegKick)
-- Kicks should knock NPCs down
if IsValid(dmginfo:GetInflictor()) and IsValid(fists) and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then
table.insert(npcs_to_fake, ent)
print("ADD TO LIST!!!")
local attacker_angle = dmginfo:GetAttacker():EyeAngles()
local normal = attacker_angle:Forward(normal)
ent.npcfakeknockback = normal * dmginfo:GetDamage() * 135
end
end
end) end)
hook.Add("ScaleNPCDamage", "zcnpci", function(npc, hitgroup, dmginfo) hook.Add("ScaleNPCDamage", "zcnpci", function(npc, hitgroup, dmginfo)
if not IsValid(npc) then return end if !IsValid(npc) then return end
last_hitgroup[npc] = hitgroup last_hitgroup[npc] = hitgroup
end) end)
hook.Add("Think", "zcnpci", function() hook.Add("Tick", "zcnpci", function()
local do_corpse_loop = (CurTime() - last_corpse_tick) > 2.0 local do_corpse_loop = (CurTime() - last_corpse_tick) > corpse_loop_time:GetFloat()
local do_general_loop = (CurTime() - last_tick) > 0.1 local do_general_loop = (CurTime() - last_tick) > (1 / ticks_per_second:GetInt())
if do_general_loop then if do_general_loop then
last_tick = CurTime()
if do_corpse_loop then if do_corpse_loop then
last_corpse_tick = CurTime() last_corpse_tick = CurTime()
@ -111,7 +208,6 @@ hook.Add("Think", "zcnpci", function()
table.remove(corpses, 1) table.remove(corpses, 1)
end end
end end
for i, ent in pairs(ents.GetAll()) do for i, ent in pairs(ents.GetAll()) do
if !IsValid(ent) then continue end if !IsValid(ent) then continue end
if !ent.organism then continue end if !ent.organism then continue end
@ -125,15 +221,10 @@ hook.Add("Think", "zcnpci", function()
(ent.organism.consciousness <= 0.3) or (ent.organism.consciousness <= 0.3) or
(ent.organism.pain > 90) or (ent.organism.pain > 90) or
ent:IsOnFire() or ent:IsOnFire() or
ent.neednpcfake or
debug_ragdoll_all:GetBool() debug_ragdoll_all:GetBool()
) then ) then
local damage_info = DamageInfo() table.insert(npcs_to_fake, ent)
damage_info:SetDamage(ent:Health())
damage_info:SetAttacker(ent)
damage_info:SetDamageType( DMG_DIRECT )
damage_info:SetDamageForce(Vector(0, 0, 0))
ent:TakeDamageInfo(damage_info)
end end
elseif do_corpse_loop and remove_corpses and ent.is_npc_corpse then elseif do_corpse_loop and remove_corpses and ent.is_npc_corpse then
local clearable = false local clearable = false
@ -150,8 +241,10 @@ hook.Add("Think", "zcnpci", function()
(ent.organism.blood < 3000) or (ent.organism.blood < 3000) or
(ent.organism.pulse < 10) (ent.organism.pulse < 10)
) then clearable = true end ) then clearable = true end
if treat_unconscious_as_dead:GetBool() and (ent.organism.consciousness <= 0.3)
then clearable = true end
if !clearable then return end if !clearable then continue end
-- Add to corpse list if not there already -- Add to corpse list if not there already
if !table.HasValue(corpses, ent) then if !table.HasValue(corpses, ent) then
@ -160,6 +253,7 @@ hook.Add("Think", "zcnpci", function()
-- Get nearest player for future checks -- Get nearest player for future checks
local nearby_player local nearby_player
local player_too_close
local players = player.GetAll() local players = player.GetAll()
for i, loop_player in pairs(players) do for i, loop_player in pairs(players) do
@ -169,11 +263,15 @@ hook.Add("Think", "zcnpci", function()
if (distance <= max_corpse_distance:GetFloat()) or (max_corpse_distance:GetFloat() == -1) then if (distance <= max_corpse_distance:GetFloat()) or (max_corpse_distance:GetFloat() == -1) then
nearby_player = loop_player nearby_player = loop_player
end
if (distance <= min_corpse_distance:GetFloat()) then
player_too_close = loop_player
break break
end end
end end
if nearby_player and IsValid(nearby_player) and no_corpse_removal_near_player:GetBool() then continue if player_too_close and IsValid(player_too_close) then continue
elseif (max_corpse_distance:GetFloat() != -1) and !nearby_player then elseif (max_corpse_distance:GetFloat() != -1) and !nearby_player then
ent:Remove() ent:Remove()
continue continue
@ -185,12 +283,25 @@ hook.Add("Think", "zcnpci", function()
ent:Remove() ent:Remove()
continue continue
end end
print((CurTime() - ent.corpse_timestamp) > max_corpse_time:GetFloat())
end end
end end
end end
end end
PrintTable(npcs_to_fake)
-- NPC faking is in a seperate area because we want NPCs to ragdoll as soon as they can
for i,ent in pairs(npcs_to_fake) do
local damage_info = DamageInfo()
damage_info:SetDamage(0)
damage_info:SetAttacker(ent)
damage_info:SetDamageType( DMG_DIRECT )
damage_info:SetDamageForce(Vector(0, 0, 0))
ent:BecomeRagdoll(damage_info)
table.remove(npcs_to_fake, 1)
end
end) end)
--[[hook.Add("OnNPCKilled", "Fedhoria", function(ent, attacker, inflictor) --[[hook.Add("OnNPCKilled", "Fedhoria", function(ent, attacker, inflictor)
@ -200,46 +311,30 @@ end)
last_dmgpos[ent] = dmginfo:GetDamagePosition() last_dmgpos[ent] = dmginfo:GetDamagePosition()
end)]] end)]]
local once = true hook.Add("OnEntityCreated", "zcnpci", function(ent)
if !IsValid(ent) then return end
--RagMod/TTT support local add_organism = false
--[[hook.Add("OnEntityCreated", "Fedhoria", function(ent)
--If RagMod isn't installed remove this hook if allow_modded_npcs:GetBool() then
if once then local modded_npc_whitelist_string = modded_npc_whitelist:GetString()
once = nil modded_npc_whitelist_string = string.Replace(modded_npc_whitelist_string, "\n", " ")
if (!RMA_Ragdolize and !CORPSE) then
hook.Remove("OnEntityCreated", "Fedhoria") local modded_npc_whitelist_table = string.Split(modded_npc_whitelist_string, " ")
return
end if table.HasValue(modded_npc_whitelist_table, ent:GetClass()) then add_organism = true end
--these hooks fucks shit up
if RMA_Ragdolize then
hook.Remove( "PlayerDeath", "RM_PlayerDies")
hook.Add( "PostPlayerDeath", "RemoveRagdoll", function(ply)
if IsValid(ply.RM_Ragdoll) then
SafeRemoveEntity(ply:GetRagdollEntity())
ply:SpectateEntity(ply.RM_Ragdoll)
end
end)
end
end end
if (!enabled:GetBool() or !players:GetBool() or !ent:IsRagdoll()) then return end
timer.Simple(0, function() if allow_extended_base_npcs:GetBool() and table.HasValue(base_npc_whitelist, ent:GetClass()) then add_organism = true end
if !IsValid(ent) then return end
if CORPSE then if add_organism then
local ply = ent:GetDTEntity(CORPSE.dti.ENT_PLAYER) hg.organism.Add(ent)
if (IsValid(ply) and ply:IsPlayer()) then hg.organism.Clear(ent.organism)
fedhoria.StartModule(ent, "stumble_legs") ent.organism.fakePlayer = true
return end
end end)
end
for _, ply in ipairs(player.GetAll()) do local once = true
if (ply.RM_IsRagdoll and ply.RM_Ragdoll == ent) then
fedhoria.StartModule(ent, "stumble_legs")
return
end
end
end)
end)]]
local PLAYER = FindMetaTable("Player") local PLAYER = FindMetaTable("Player")
@ -283,36 +378,3 @@ local oldGetRagdollEntity = PLAYER.GetRagdollEntity
local function GetRagdollEntity(self) local function GetRagdollEntity(self)
return dolls[self] or NULL return dolls[self] or NULL
end end
--[[
if enabled:GetBool() then
PLAYER.CreateRagdoll = CreateRagdoll
PLAYER.GetRagdollEntity = GetRagdollEntity
end]]
--[[
cvars.AddChangeCallback("fedhoria_enabled", function(name, old, new)
if (new == "1") then
if players:GetBool() then
PLAYER.CreateRagdoll = CreateRagdoll
PLAYER.GetRagdollEntity = GetRagdollEntity
end
else
PLAYER.CreateRagdoll = oldCreateRagdoll
PLAYER.GetRagdollEntity = oldGetRagdollEntity
end
end)
cvars.AddChangeCallback("fedhoria_players", function(name, old, new)
if (new == "1") then
if enabled:GetBool() then
if (debug.getinfo(PLAYER.CreateRagdoll).short_src == "[C]") then
PLAYER.CreateRagdoll = CreateRagdoll
PLAYER.GetRagdollEntity = GetRagdollEntity
end
end
else
PLAYER.CreateRagdoll = oldCreateRagdoll
PLAYER.GetRagdollEntity = oldGetRagdollEntity
end
end)]]

View file

@ -75,6 +75,7 @@ function ENTITY:GetClosestPhysBone(pos)
end end
if !collides then return end if !collides then return end
if type(pos) != "Vector" then return end
local closest_bone local closest_bone
local dist = math.huge local dist = math.huge

View file

@ -149,16 +149,19 @@ function MODULE:StartAnimationRoll()
return return
end end
--self.StartDie = nil --target.StartDie = nil
end end
function MODULE:Init() function MODULE:Init()
local seq = self:LookupSequence("Choked_Barnacle") local seq = self:LookupSequence("Choked_Barnacle")
if seq then self:ResetSequence(seq) end if seq then self:ResetSequence(seq) end
local target = self:GetTarget()
self:SetPlaybackRate(1) self:SetPlaybackRate(1)
self.LastCollideTime = 0 self.LastCollideTime = 0
self.LastGroundCollideTime = 0 self.LastGroundCollideTime = 0
self.StartDie = nil target.StartDie = nil
self.AnimationRollEndTime = 0 self.AnimationRollEndTime = 0
self.StopProcessing = false self.StopProcessing = false
self.LastThink = CurTime() self.LastThink = CurTime()
@ -251,20 +254,20 @@ function MODULE:PhysicsSimulate(phys, dt)
target.is_npc_corpse = true target.is_npc_corpse = true
if !self.StartDie then self.StartDie = cur_time end if !target.StartDie then target.StartDie = cur_time end
-- Check for active animation -- Check for active animation
if cur_time < self.AnimationRollEndTime then if cur_time < self.AnimationRollEndTime then
--self.StartDie = nil -- Resetting the "death" timer --target.StartDie = nil -- Resetting the "death" timer
return true -- We use standard physics. return true -- We use standard physics.
end end
-- Logic for the disappearance timer -- Force multiplier
local f = 1 -- Force multiplier (default: 1) local f = target.organism.consciousness
local minimum_down_timer = 0 local minimum_down_timer = 0
if self.StartDie then if target.StartDie then
minimum_down_timer = math_Clamp((cur_time - self.StartDie) / minimum_down_time:GetFloat(), 0, 1) minimum_down_timer = math_Clamp((cur_time - target.StartDie) / minimum_down_time:GetFloat(), 0, 1)
end end
if !target.organism then if !target.organism then
@ -276,8 +279,8 @@ function MODULE:PhysicsSimulate(phys, dt)
-- If the NPC is dead, they probably aren't coming back; don't bother bringing them back to life -- If the NPC is dead, they probably aren't coming back; don't bother bringing them back to life
self:Remove() self:Remove()
return false -- Cut the bullshit 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 target.StartDie = cur_time
return false return false
end end
@ -288,19 +291,22 @@ function MODULE:PhysicsSimulate(phys, dt)
(target.class_in_previous_life != nil) and (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.llegamputated or target.organism.rlegamputated or target.organism.larmamputated or target.organism.rarmamputated) and
(target.organism.pain <= 80) and (target.organism.pain <= 80) and
(target.organism.consciousness > 0.65) and
!target:IsOnFire() !target:IsOnFire()
) then ) then
local ent = ents.Create(target.class_in_previous_life) local ent = ents.Create(target.class_in_previous_life)
ent:SetPos(target:GetPos()) ent:SetPos(target:GetPos())
ent:SetModel(target:GetModel()) ent:SetModel(target:GetModel())
ent:SetMaterial(target:GetMaterial())
if target:GetNumBodyGroups() > 0 then ent:SetSkin(target:GetSkin())
local i = 1
while (i <= target:GetNumBodyGroups()) do
ent:SetBodygroup(i, target:GetBodygroup(i))
i = i + 1 if target.citizentype then
end ent:SetKeyValue("citizentype", target.citizentype)
end
for i = 0, target:GetNumBodyGroups() - 1 do
ent:SetBodygroup(i, target:GetBodygroup(i))
end end
ent:Spawn() ent:Spawn()
@ -318,6 +324,12 @@ function MODULE:PhysicsSimulate(phys, dt)
ent:CallOnRemove("organism", hg.organism.Remove, ent) ent:CallOnRemove("organism", hg.organism.Remove, ent)
hg.send_bareinfo(ent.organism) hg.send_bareinfo(ent.organism)
ent:SetSkin(target:GetSkin())
for i = 0, target:GetNumBodyGroups() - 1 do
ent:SetBodygroup(i, target:GetBodygroup(i))
end
target:Remove() target:Remove()
end) end)
@ -362,7 +374,7 @@ function MODULE:PhysicsSimulate(phys, dt)
self.last_pos = pos self.last_pos = pos
if offset_sqr < (10*10 * dt*dt) then if offset_sqr < (10*10 * dt*dt) then
self.StartDie = self.StartDie or cur_time target.StartDie = target.StartDie or cur_time
end end
-- Physics correction after collision -- Physics correction after collision

View file

@ -114,17 +114,20 @@ function MODULE:StartAnimationRoll()
return return
end end
self.StartDie = nil -- блять я заебался с этой хуйней target.StartDie = nil -- блять я заебался с этой хуйней
-- print("[Fedhoria AnimRoll] StartAnimationRoll initiated. Speed:", cv_anim_roll_playback_rate:GetFloat(), "Duration:", cv_anim_roll_duration:GetFloat(), "End Time:", self.AnimationRollEndTime) -- Debug -- print("[Fedhoria AnimRoll] StartAnimationRoll initiated. Speed:", cv_anim_roll_playback_rate:GetFloat(), "Duration:", cv_anim_roll_duration:GetFloat(), "End Time:", self.AnimationRollEndTime) -- Debug
end end
function MODULE:Init() function MODULE:Init()
local seq = self:LookupSequence("idleonfire") local seq = self:LookupSequence("idleonfire")
if seq then self:ResetSequence(seq) end if seq then self:ResetSequence(seq) end
local target = self:GetTarget()
self:SetPlaybackRate(1) self:SetPlaybackRate(1)
self.LastCollideTime = 0 self.LastCollideTime = 0
self.LastGroundCollideTime = 0 self.LastGroundCollideTime = 0
self.StartDie = nil target.StartDie = nil
self.AnimationRollEndTime = 0 self.AnimationRollEndTime = 0
-- идте нахуй с этой хуйней -- идте нахуй с этой хуйней
@ -172,7 +175,7 @@ function MODULE:PhysicsSimulate(phys, dt)
-- проверка на физику -- проверка на физику
if cur_time < self.AnimationRollEndTime then if cur_time < self.AnimationRollEndTime then
-- print("[Fedhoria Sim] In Animation Roll. Time left:", self.AnimationRollEndTime - cur_time) -- Debug -- print("[Fedhoria Sim] In Animation Roll. Time left:", self.AnimationRollEndTime - cur_time) -- Debug
-- self.StartDie = nil -- Сбрасываем таймер смерти -- target.StartDie = nil -- Сбрасываем таймер смерти
return true -- стандартная физика пошла нахуй, у меня по ней 2 return true -- стандартная физика пошла нахуй, у меня по ней 2
-- return false -- если физика нахуй идет -- return false -- если физика нахуй идет
@ -180,18 +183,15 @@ function MODULE:PhysicsSimulate(phys, dt)
-- --- логика идет нахуй -- --- логика идет нахуй
-- логика для таймера -- Force multiplier
local f = 1 -- сила есть ума не надо (по умолчанию 1) local f = target.organism.consciousness
--[[if self.StartDie then
f = math_Clamp(1 - (cur_time - self.StartDie) / die_time:GetFloat(), 0, 1)
end]]
-- ебаная логика для regmod -- ебаная логика для regmod
if ragmod and ragmod:IsRagmodRagdoll(target) then if ragmod and ragmod:IsRagmodRagdoll(target) then
local owner = target:GetOwningPlayer() local owner = target:GetOwningPlayer()
-- владелец идот нахуй -- владелец идот нахуй
f = (IsValid(owner) and owner:Alive()) and 1 or 0 f = (IsValid(owner) and owner:Alive()) and 1 or 0
self.StartDie = nil target.StartDie = nil
if f <= 0 then self.AnimationRollEndTime = 0 end if f <= 0 then self.AnimationRollEndTime = 0 end
end end
@ -244,10 +244,10 @@ function MODULE:PhysicsSimulate(phys, dt)
local offset_sqr = (pos - self.last_pos):LengthSqr() local offset_sqr = (pos - self.last_pos):LengthSqr()
self.last_pos = pos self.last_pos = pos
if (offset_sqr < (10*10 * dt*dt) and not (ragmod and ragmod:IsRagmodRagdoll(target))) then -- uменьшил порог неподвижности if (offset_sqr < (10*10 * dt*dt) and not (ragmod and ragmod:IsRagmodRagdoll(target))) then -- uменьшил порог неподвижности
self.StartDie = self.StartDie or cur_time -- идите нахуй target.StartDie = target.StartDie or cur_time -- идите нахуй
-- print("[Fedhoria Sim] Torso seems stationary. StartDie:", self.StartDie) -- Debug -- print("[Fedhoria Sim] Torso seems stationary. StartDie:", target.StartDie) -- Debug
else else
-- self.StartDie = nil -- нахуй таймер -- target.StartDie = nil -- нахуй таймер
-- print("[Fedhoria Sim] Torso moved. Resetting StartDie.") -- Debug -- print("[Fedhoria Sim] Torso moved. Resetting StartDie.") -- Debug
end end