Compare commits

..

No commits in common. "master" and "stop-tackling-people-you-cunts" have entirely different histories.

9 changed files with 108 additions and 703 deletions

View file

@ -1,23 +1,20 @@
# Z-City NPC Integration
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 bug-free 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 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.
- NPCs are responsive to damage from many sources -- bullets, fire, melee (especially kicks!) and more.
- NPCs can also see and target downed NPCs, and will prioritize standing NPCs over them when needed.
- Everything is configurable, so you can modify and disable features as you like to create your preferred experience.
- Various configurable settings are included, allowing you to tweak the mod to your liking.
This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse cleanup tool.
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
- 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 right now, though if anyone experienced with Z-City's codebase would know how to I would appreciate knowing.
- The NPC getting up animation is a little ugly. However, creating that animation was an adventure on levels that I would personally dislike re-experiencing.
- 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.
- Enemy NPCs will not target living downed NPCs like how they do players. This will be fixed.
## Compatibility
@ -31,23 +28,10 @@ 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.)
## Todo list
## Future plans
MUST BE DONE BEFORE RELEASE:
No guarantees any of this will become real.
- Fire / falling / pain reaction (more rapid flailing)
- NPCs should not unfake if being grabbed
Stuff to do:
- Posturing (like when you get hit) more similar to Z-City
- Facial expressions
- Generally improve stumbling (foot stepping)
- Stop NPCs from dropping their guns maybe?
Post release ideas:
- Localization support for other languages
- ReaSFX integration would probably be nice. I mean there's so many SFX packs to choose from, so I don't even have to pack it in
- To be entirely honest Fedhoria isn't the best addon out there for euphoria physics. Maybe rebase to another addon (or alternatively build a new one?)
- NPC targeting works, but works really weirdly (sometimes it just. keeps targeting corpses when it shouldn't.) It actually isn't THAT much of an issue because it doesn't feel totally outlandish but I would still love to fix it.
- NPCs will back out of combat when injured.
- Friendly NPCs can heal each other.
- NPCs can heal themselves.

View file

@ -1,20 +1,8 @@
local function PopulateAISBXToolMenu(pnl)
pnl:CheckBox("Allow NPCs to target downed NPCs", "zcnpci_npc_targeting_enabled")
pnl:ControlHelp("If enabled, NPCs target downed hostile NPCs.")
pnl:CheckBox("NPCs should target head", "zcnpci_target_should_follow_head")
pnl:ControlHelp("Should NPCs target the player's head? This can result in more effective targeting.")
pnl:CheckBox("NPCs shouldn't target unconscious NPCs", "zcnpci_no_unconscious_targeting")
end
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:Help("As long as the NPC has a compatible skeleton, support will be provided for modded NPCs on a best-effort basis.")
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.")
@ -37,7 +25,7 @@ local function PopulateCustomNPCSBXToolMenu(pnl)
pnl:AddItem(text)
pnl:Help("A list of NPC classes, seperated by spaces or line breaks. Can only be set by a superadmin or server operator.")
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)
@ -49,30 +37,9 @@ local function PopulateRagdollSBXToolMenu(pnl)
pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3)
pnl:ControlHelp("How long the ragdoll should hold its wound.")
pnl:NumSlider("Writhing strength", "zcnpci_writhing_strength", 0, 5, 3)
pnl:ControlHelp("How hard should the ragdoll wriggle/writhe.")
pnl:NumSlider("Flailing speed multiplier", "zcnpci_flailing_playback_rate", 0, 12, 3)
pnl:ControlHelp("How fast or slow should the NPC flail by default.")
pnl:NumSlider("Flailing speed multiplier when panicking", "zcnpci_flailing_playback_rate", 0, 12, 3)
pnl:ControlHelp("How fast or slow should the NPC flail when panicking (on fire, falling, etc.).")
pnl:NumSlider("Death timer", "zcnpci_death_timer", -1, 600, 3)
pnl:ControlHelp("After an NPC is ragdolled for longer than this timer, they will die instantly. Set to -1 to disable.")
pnl:CheckBox("Allow getting up", "zcnpci_unfake_enabled")
pnl:ControlHelp("If enabled, NPCs will get back up if able.")
pnl:NumSlider("Get up time", "zcnpci_unfake_time", 0, 5, 3)
pnl:ControlHelp("How long the getting up animation lasts. Setting to 0 will disable the getting up animation.")
pnl:NumSlider("Minimum down time", "zcnpci_down_time", 0, 20, 3)
pnl:ControlHelp("The minimum amount of time the ragdoll should be down before trying to get back up.")
pnl:NumSlider("Movement sensitivity", "zcnpci_movement_sensitivity", -1, 256, 3)
pnl:ControlHelp("How far does the NPC have to move to reset the getting up timer. Set to -1 to disable.")
end
local function PopulatePerformanceSBXToolMenu(pnl)
@ -152,11 +119,6 @@ if engine.ActiveGamemode() == "sandbox" then
PopulateRagdollSBXToolMenu(pnl)
end)
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "AISettings", "AI", "", "", function(pnl)
pnl:ClearControls()
PopulateAISBXToolMenu(pnl)
end)
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "MainSettings", "Main", "", "", function(pnl)
pnl:ClearControls()
PopulateMainSBXToolMenu(pnl)

View file

@ -97,7 +97,7 @@ end
function ENT:Initialize()
if !self.Module then return end
if self.Module.Model then self:SetModel(self.Module.Model) end
self:SetModel(self.Module.Model)
for key, value in pairs(self.Module) do
if !self[key] then

View file

@ -1,120 +0,0 @@
AddCSLuaFile()
ENT.Type = "ai"
ENT.Base = "base_ai"
ENT.AutomaticFrameAdvance = true
local debug_show_ragdoll_targets = CreateConVar("zcnpci_debug_show_ragdoll_targets", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local should_follow_head = CreateConVar("zcnpci_target_should_follow_head", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local stop_targeting_when_unconscious = CreateConVar("zcnpci_no_unconscious_targeting", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local vector_offset_to_shoot_at = Vector(0, 0, 8)
local function UpdateRelationship(ent, me)
if !IsValid(ent) or !ent:IsNPC() or (ent:GetClass() == "npc_ragdoll_target") then return end
if ent:Disposition(me.dummy_entity) == D_HT then
--print("Ragdoll target of class "..me.ragdoll_to_follow.class_in_previous_life.." is hated by a "..ent:GetClass())
ent:AddEntityRelationship(me, D_HT, -1)
else
ent:AddEntityRelationship(me, D_NU, -1)
end
end
local function RemoveRelationship(ent, me)
if !IsValid(ent) or !ent:IsNPC() or (ent:GetClass() == "npc_ragdoll_target") then return end
ent:AddEntityRelationship(me, D_NU, -1)
ent:ClearEnemyMemory(me)
if ent:GetEnemy() == me then
ent:SetEnemy(nil)
ent:ClearSchedule()
end
end
function ENT:Classify()
return CLASS_NONE
end
function ENT:Initialize()
if SERVER then
self:SetModel("models/maxofs2d/hover_basic.mdl")
self:SetCollisionGroup(COLLISION_GROUP_NONE)
self:SetHullType(HULL_TINY_CENTERED)
self:SetNoDraw(!debug_show_ragdoll_targets:GetBool())
self.dummy_entity = ents.Create(self.ragdoll_to_follow.class_in_previous_life)
self.last_stop_targeting = nil
-- Have to use this collision group otherwise ragdoll will obstruct visibility of target
self.ragdoll_to_follow:SetCollisionGroup(COLLISION_GROUP_DEBRIS)
hook.Add("OnEntityCreated", "zcnpci-"..self:GetCreationID(), function(new_entity)
if !IsValid(new_entity) or !new_entity:IsNPC() then return end
if !self.last_stop_targeting then
UpdateRelationship(new_entity, self)
end
end)
end
end
function ENT:Think()
if SERVER then
local ragdoll = self.ragdoll_to_follow
if !IsValid(ragdoll) then self:Remove(); return end
if should_follow_head:GetBool() then
local head = ragdoll:GetPhysicsObjectNum(ragdoll:TranslateBoneToPhysBone(ragdoll:LookupBone("ValveBiped.Bip01_Head1")))
self:SetPos(head:GetPos() + vector_offset_to_shoot_at)
else
self:SetPos(ragdoll:GetPos())
end
self:SetNoDraw(!debug_show_ragdoll_targets:GetBool())
if !ragdoll.organism then return true end
if !ragdoll.organism.alive then self:Remove(); return end
local should_stop_targeting = !stop_targeting_when_unconscious:GetBool() or (
(ragdoll.organism.consciousness <= 0.4) or
(ragdoll.organism.critical)
)
if (should_stop_targeting != self.last_stop_targeting) then
self.last_stop_targeting = should_stop_targeting
if should_stop_targeting then
self:SetColor(Color(255, 0, 0))
for i,ent in ipairs(ents.GetAll()) do
RemoveRelationship(ent, self)
end
else
self:SetColor(Color(0, 255, 0))
for i,ent in ipairs(ents.GetAll()) do
UpdateRelationship(ent, self)
end
end
end
self:NextThink(CurTime())
end
return true
end
function ENT:OnRemove()
if SERVER then
for i,ent in ipairs(ents.GetAll()) do
RemoveRelationship(ent, self)
end
self.dummy_entity:Remove()
hook.Remove("OnEntityCreated", "zcnpci-"..self:GetCreationID())
end
end

View file

@ -1,131 +0,0 @@
AddCSLuaFile()
ENT.Type = "anim"
ENT.Base = "base_anim"
ENT.AutomaticFrameAdvance = true
local bones_to_animate = {
"ValveBiped.Bip01_Pelvis",
"ValveBiped.Bip01_Spine2",
"ValveBiped.Bip01_Head1",
"ValveBiped.Bip01_R_Thigh",
"ValveBiped.Bip01_L_Thigh",
"ValveBiped.Bip01_L_Calf",
"ValveBiped.Bip01_R_Calf",
"ValveBiped.Bip01_R_Foot",
"ValveBiped.Bip01_L_Foot",
"ValveBiped.Bip01_R_Upperarm",
"ValveBiped.Bip01_L_Upperarm",
"ValveBiped.Bip01_R_Forearm",
"ValveBiped.Bip01_L_Forearm",
"ValveBiped.Bip01_R_Hand",
"ValveBiped.Bip01_L_Hand",
}
-- Copy pasted directly from the GMOD wiki with small edits
local function SetRagdollPos(ragdoll, pos)
for i = 0, ragdoll:GetPhysicsObjectCount() - 1 do
local phys = ragdoll:GetPhysicsObjectNum(i)
local localPos = ragdoll:WorldToLocal( phys:GetPos() )
phys:SetPos(pos + localPos)
end
end
function ENT:Initialize()
if SERVER then
print("I AM SERVERSIDE")
self:SetModel("models/dav0r/hoverball.mdl")
end
if CLIENT then
print("I AM CLIENTSIDE")
self:SetNoDraw(true)
end
end
function ENT:Think()
if CLIENT then
--hook.Add("physics")
local old_ragdoll = self:GetNWEntity("parent")
local old_npc = self:GetNWEntity("parent_npc")
if IsValid(old_ragdoll) and IsValid(old_npc) then
local ragdoll = self.ragdoll
if !self.ready_to_animate then
self.ragdoll = ClientsideRagdoll(old_ragdoll:GetModel())
ragdoll = self.ragdoll
self.ragdoll:SetNoDraw(false)
self.ragdoll:DrawShadow(true)
SetRagdollPos(self.ragdoll, old_ragdoll:GetPos())
print(self.ragdoll)
print("SHOW UP YOU PIECE OF SHIT")
for i, name in pairs(bones_to_animate) do
local object = ragdoll:GetPhysicsObjectNum(ragdoll:TranslateBoneToPhysBone(ragdoll:LookupBone(name)))
local parent_object = old_ragdoll:GetBoneMatrix(old_ragdoll:LookupBone(name))
print(parent_object)
if parent_object == nil then continue end
object:SetPos(parent_object:GetTranslation())
object:SetAngles(parent_object:GetAngles())
end
self.ready_to_animate = true
end
local fake_start = self:GetNWFloat("fake_start")
local fake_end = self:GetNWFloat("fake_end")
local progress = (CurTime() - fake_start) / (fake_end - fake_start)
for i, name in pairs(bones_to_animate) do
local object = ragdoll:GetPhysicsObjectNum(ragdoll:TranslateBoneToPhysBone(ragdoll:LookupBone(name)))
local parent_bone = old_npc:LookupBone(name)
if parent_bone == -1 then continue end
local parent_bone_matrix = old_npc:GetBoneMatrix(parent_bone)
if !parent_bone_matrix then continue end
local parent_bone_pos, parent_bone_angle = parent_bone_matrix:GetTranslation(), parent_bone_matrix:GetAngles()
local old_bone_pos, old_bone_angle = object:GetPos(), object:GetAngles()
--parent_bone_angle.y = parent_bone_angle.y + 90
local shadow_data = {
secondstoarrive = 0.01,
pos = LerpVector(progress, old_bone_pos, parent_bone_pos),
angle = LerpAngle(progress, old_bone_angle, parent_bone_angle),
maxspeed = 400 * 4,
maxangular = 2000 * 4,
maxspeeddamp = 120 * 4,
maxangularspeeddamp = 600 * 4,
}
-- Can't set position inside physics tick, crashes the game instantly.
-- Instead, send a shadow for it to follow (I think? I don't know GMod is kinda funky)
object:ComputeShadowControl(shadow_data)
object:Wake()
--i = i + 1
end
end
end
self:NextThink(CurTime() + 0.1)
return true
end
function ENT:OnRemove()
if CLIENT then
if self.ragdoll then
self.ragdoll:Remove()
end
end
end

View file

@ -19,9 +19,7 @@ local treat_near_death_as_dead = CreateConVar("zcnpci_treat_near_death_as_dead"
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_example_class", bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
local debug_ragdoll_all = CreateConVar("zcnpci_debug_ragdoll_all", 0, 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)
@ -32,6 +30,8 @@ local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelis
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 = {}
@ -57,45 +57,6 @@ local corpses = {}
local last_corpse_tick = CurTime()
local last_tick = CurTime()
local hook_table = hook.GetTable()
local homigrad_damage_hook = hook_table.EntityTakeDamage["homigrad-damage"]
local homigrad_player_spawn_hook = hook_table.player_spawn["homigrad-spawn3"]
-- We need to override the default homigrad damage hook so it doesn't cause damage to ragdolls we are tryign to fake
hook.Add("EntityTakeDamage", "homigrad-damage", function(ent, dmginfo)
if ent:IsNPC() and ent.organism_no_damage then return false end
homigrad_damage_hook(ent, dmginfo)
end)
-- This disables player bullseye spawning, which fixes issues with combine targeting with ai_ignoreplayers
hook.Add("player_spawn", "homigrad-spawn3", function(data)
local ply = Player(data.userid)
if not IsValid(ply) then return end
ply.bull = {
IsValid = function() return true end,
Remove = function() return end
}
homigrad_player_spawn_hook(data)
end)
-- This is called right after the NPC bullseye is created for player ragdolls, which allows us to replace it with a dummy.
-- Fixes similar issues mentioned in previous comment
hook.Add("Ragdoll_Create", "zcnpci-override", function(ply, ragdoll)
if IsValid(ragdoll.bull) then
ragdoll.bull:Remove()
end
ragdoll.bull = {
IsValid = function() return true end,
SetPos = function() return true end,
Remove = function() return end
}
end)
hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
if !ent.organism then return end
@ -157,35 +118,19 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
end
if ent.npcfakeknockback then
if dmgpos and (phys_bone != -1) then
local phys = ragdoll:GetPhysicsObjectNum(phys_bone)
phys:SetVelocity(ent.npcfakeknockback)
else
ragdoll:GetPhysicsObject():SetVelocity(ent.npcfakeknockback)
end
ragdoll:GetPhysicsObject():SetVelocity(ent.npcfakeknockback)
end
local velocity = ragdoll:GetPhysicsObject():GetVelocity()
-- 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")
ragdoll.respawn_data = {
flags = ent:GetFlags(),
spawn_flags = ent:GetSpawnFlags()
}
ragdoll:AddCallback("PhysicsCollide", function(outEnt, data) hook.Run("Ragdoll Collide", ragdoll, data) end)
timer.Simple(0, function()
if !IsValid(ragdoll) then return end
ragdoll.organism.alive = true
ragdoll:SetRenderMode(RENDERMODE_NORMAL)
if !IsValid(ragdoll) then return end
zcnpci.StartModule(ragdoll, "stumble_legs", phys_bone, lpos)
last_dmgpos[ent] = nil
@ -194,7 +139,6 @@ end)
hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
if !enabled:GetBool() or !IsValid(dmginfo) then return end
if !ent:IsNPC() then
if !ent.organism or !ent.StartDie then return end
@ -206,33 +150,31 @@ hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
return
end
last_dmgpos[ent] = dmginfo:GetDamagePosition()
last_dmgpos[ent] = dmginfo:GetDamage()
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)
if dmginfo:GetDamage() > 1.5 then
ent:TakeDamage(ent:Health())
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()
if !IsValid(attacker) then return end
if attacker.HasWeapon == nil then return end
local fists = attacker:HasWeapon("weapon_hands_sh")
if !fists then fists = attacker:HasWeapon("weapon_hg_coolhands") end
print(fists.InLegKick)
-- Kicks should knock NPCs down
if IsValid(dmginfo:GetInflictor()) and fists and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then
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
last_dmgpos[ent] = nil
end
end
end)
@ -345,22 +287,30 @@ hook.Add("Tick", "zcnpci", function()
end
end
end
-- NPC faking is in a seperate area because we want NPCs to fake as soon as they can
for i,ent in pairs(npcs_to_fake) do
ent.organism_no_damage = true
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(999999)
damage_info:SetDamage(0)
damage_info:SetAttacker(ent)
damage_info:SetDamageType( DMG_DIRECT )
damage_info:SetDamageForce(Vector(0, 0, 0))
ent:TakeDamageInfo(damage_info)
ent:BecomeRagdoll(damage_info)
table.remove(npcs_to_fake, 1)
end
end)
--[[hook.Add("OnNPCKilled", "Fedhoria", function(ent, attacker, inflictor)
if (!enabled:GetBool() or !npcs:GetBool()) then return end
if (!ent:IsNPC() or dmginfo:GetDamage() < ent:Health()) then return end
last_dmgpos[ent] = dmginfo:GetDamagePosition()
end)]]
hook.Add("OnEntityCreated", "zcnpci", function(ent)
if !IsValid(ent) then return end

View file

@ -40,31 +40,6 @@ local twitchable_bone_names = {
"ValveBiped.Bip01_L_Foot"
}
local fakeup_bone_names = {
"ValveBiped.Bip01_Pelvis",
"ValveBiped.Bip01_Spine2",
"ValveBiped.Bip01_Head1",
"ValveBiped.Bip01_R_Thigh",
"ValveBiped.Bip01_L_Thigh",
"ValveBiped.Bip01_L_Calf",
"ValveBiped.Bip01_R_Calf",
"ValveBiped.Bip01_R_Foot",
"ValveBiped.Bip01_L_Foot",
"ValveBiped.Bip01_R_Upperarm",
"ValveBiped.Bip01_L_Upperarm",
"ValveBiped.Bip01_R_Forearm",
"ValveBiped.Bip01_L_Forearm",
"ValveBiped.Bip01_R_Hand",
"ValveBiped.Bip01_L_Hand",
}
local fakeup_bone_down_names = {
"ValveBiped.Bip01_R_Forearm",
"ValveBiped.Bip01_L_Forearm",
"ValveBiped.Bip01_R_Thigh",
"ValveBiped.Bip01_L_Thigh",
}
-- Local Copies of Functions for Optimization
local math_Clamp = math.Clamp
local math_Rand = math.Rand
@ -94,24 +69,7 @@ local cv_anim_roll_impact_threshold = CreateConVar("zcnpci_falling_anim_roll_imp
local cv_anim_roll_playback_rate = CreateConVar("zcnpci_falling_anim_roll_playback_rate", "3.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Скорость воспроизведения анимации")
-- Down time
local minimum_down_time = CreateConVar("zcnpci_down_time", "5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Down time!!!!!!!!!")
-- Unfaking
local can_unfake = CreateConVar("zcnpci_unfake_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Whether an NPC can unfake")
local unfake_time = CreateConVar("zcnpci_unfake_time", "1.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Time it takes for an NPC to unfake")
local movement_sensitivity = CreateConVar("zcnpci_movement_sensitivity", "40", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
local death_timer = CreateConVar("zcnpci_death_timer", "-1", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
-- Targeting
local npc_targeting_enabled = CreateConVar("zcnpci_npc_targeting_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Should NPCs target downed NPCs")
-- Flailing
local flailing_playback_rate = CreateConVar("zcnpci_flailing_playback_rate", "1.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
local flailing_panic_multiplier = CreateConVar("zcnpci_flailing_panic_multiplier", "2.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
local flailing_panic_airbone_check_distance = CreateConVar("zcnpci_flailing_panic_airbone_check_distance", "128", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
-- Writhing
local writhing_strength = CreateConVar("zcnpci_writhing_strength", "1.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
local minimum_down_time = CreateConVar("zcnpci_down_time", "3", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Down time!!!!!!!!!")
--[[-------------------------------------------------------------------------
Tug function
@ -207,15 +165,7 @@ function MODULE:Init()
self.AnimationRollEndTime = 0
self.StopProcessing = false
self.LastThink = CurTime()
self.LastFakeUpCheck = CurTime()
self.LastPosCheck = target:GetPos()
self.SpawnTimestamp = CurTime()
self.bullseye = ents.Create("npc_ragdoll_target")
self.bullseye:SetPos(target:GetPos())
self.bullseye.ragdoll_to_follow = target
self.bullseye:Spawn()
self.bullseye:Activate()
self.LastPhysProcess = CurTime()
-- Add damping to all bones.
local phys = self:GetPhysicsObject()
@ -239,28 +189,12 @@ end
---------------------------------------------------------------------------]]
function MODULE:Think()
--if (CurTime() - self.LastThink) < 1 then return end
local target = self:GetTarget()
if (CurTime() - self.LastThink) < 1 then return end
if !IsValid(target) then return end
self.LastThink = CurTime()
local phys = target:GetPhysicsObject()
if target.FakeUp then
local parent = self.FakeParent
if !IsValid(parent) then return end
if !self.ModelBoneList then return end
--[[for i,v in pairs(self.ModelBoneList) do
local object = target:GetPhysicsObjectNum(target:TranslateBoneToPhysBone(target:LookupBone(v)))
if !IsValid(object) then continue end
object:EnableCollisions(false)
end]]
end
if !IsValid(phys) or !phys:IsAsleep() then return end
@ -308,79 +242,15 @@ end
Physics Simulation Hook (FIXED)
---------------------------------------------------------------------------]]
function MODULE:PhysicsSimulate(phys, dt)
if !SERVER then return end
if self.StopProcessing then return false end
--print((CurTime() - self.LastPhysProcess) < 0.05)
--if (CurTime() - self.LastPhysProcess) < (1 /) then return false end
self.LastPhysProcess = CurTime()
local cur_time = CurTime()
local target = self:GetTarget()
if not IsValid(target) then self:Remove(); self.bullseye:Remove(); return false end
if target.FakeUp then
local parent = self.FakeParent
if !IsValid(parent) then
self:Remove()
return false
end
if !self.ModelBoneList then
self.ModelBoneList = {}
local i = 0
while i < target:GetBoneCount() do
local name = target:GetBoneName(i)
local object = target:LookupBone(name)
if object == -1 then i = i + 1; continue end
self.ModelBoneList[name] = target:GetBoneMatrix(object)
i = i + 1
end
end
local animation_progress = (CurTime() - self.FakeUpStart) / (self.FakeUpEnd - self.FakeUpStart)
for i, name in pairs(fakeup_bone_names) do
local object = target:GetPhysicsObjectNum(target:TranslateBoneToPhysBone(target:LookupBone(name)))
local parent_bone = parent:LookupBone(name)
if parent_bone == -1 then continue end
local parent_bone_matrix = parent:GetBoneMatrix(parent_bone)
if !parent_bone_matrix then continue end
local parent_bone_pos, parent_bone_angle = parent_bone_matrix:GetTranslation(), parent_bone_matrix:GetAngles()
local old_bone_pos, old_bone_angle = object:GetPos(), object:GetAngles()
--parent_bone_angle.y = parent_bone_angle.y + 90
local shadow_data = {
secondstoarrive = 0.01,
pos = LerpVector(animation_progress, old_bone_pos, parent_bone_pos),
angle = LerpAngle(animation_progress, old_bone_angle, parent_bone_angle),
maxspeed = 400,
maxangular = 2000,
maxspeeddamp = 60,
maxangularspeeddamp = 600,
}
-- Can't set position inside physics tick, crashes the game instantly.
-- Instead, send a shadow for it to follow (I think? I don't know GMod is kinda funky)
object:ComputeShadowControl(shadow_data)
object:Wake()
--i = i + 1
end
return false
end
--print((CurTime() - self.LastPhysProcess) < 0.05)
--if (CurTime() - self.LastPhysProcess) < (1 /) then return false end())
phys:Wake()
--self.bullseye:SetAngles(target:EyeAngles())
if not IsValid(target) then self:Remove(); return false end
target.is_npc_corpse = true
@ -393,7 +263,7 @@ function MODULE:PhysicsSimulate(phys, dt)
end
-- Force multiplier
local f = target.organism.consciousness * writhing_strength:GetFloat()
local f = target.organism.consciousness
local minimum_down_timer = 0
if target.StartDie then
@ -405,151 +275,68 @@ function MODULE:PhysicsSimulate(phys, dt)
return false -- Cut the bullshit
end
if (death_timer:GetFloat() != -1) and ((CurTime() - self.SpawnTimestamp) > death_timer:GetFloat()) then
target.organism.alive = false
end
if (!target.organism.alive) then
-- 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.4) or
((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) or
(target.organism.spine1 == 1) or
(target.organism.spine2 == 1) or
(target.organism.spine3 == 1)
) then
elseif (target.organism.consciousness <= 0.5) or ((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) then
target.StartDie = cur_time
return false
end
if (self.LastPosCheck:DistToSqr(target:GetPos()) > (movement_sensitivity:GetFloat() ^ 2)) then
self.LastPosCheck = target:GetPos()
target.StartDie = cur_time
end
-- Getting up
-- Don't need to check for consciousness and the like because we've done it already above
if (
(minimum_down_timer >= 1.0) 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.pain <= 80) and
(target.organism.consciousness > 0.65) and
!target:IsOnFire()
) then
local ent = ents.Create(target.class_in_previous_life)
ent:SetPos(target:GetPos())
ent:SetModel(target:GetModel())
ent:SetMaterial(target:GetMaterial())
local distance_check = util.TraceLine({
start = target:GetPos(),
endpos = target:GetPos() + Vector(0, 0, -128),
filter = {target},
mask = MASK_SOLID
}).Hit
ent:SetSkin(target:GetSkin())
if !distance_check then
target.StartDie = cur_time
end
if target.citizentype then
ent:SetKeyValue("citizentype", target.citizentype)
end
if ((CurTime() - self.LastFakeUpCheck) >= 1.0) and (can_unfake:GetBool()) then
self.LastFakeUpCheck = CurTime()
for i = 0, target:GetNumBodyGroups() - 1 do
ent:SetBodygroup(i, target:GetBodygroup(i))
end
-- This basically checks if the NPC will be in collision with anything when it spawns.
-- Side note: the mins and maxs values are slightly larger than the default npc_citizen
-- hitboxes, mostly because I assume that most humanoid NPCs will be around that size.
local tracehull = util.TraceHull({
start = target:GetPos(),
endpos = target:GetPos(),
mins = Vector(-20, -20, 0),
maxs = Vector(20, 20, 84),
mask = MASK_NPCSOLID,
filter = { target }
})
ent:Spawn()
if (
(!tracehull.Hit) and
(minimum_down_timer >= 1.0) 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.pain <= 80) and
(target.organism.consciousness > 0.65) and
!target:IsOnFire()
) then
local ent = ents.Create(target.class_in_previous_life)
ent:SetPos(target:GetPos())
ent:SetModel(target:GetModel())
ent:SetMaterial(target:GetMaterial())
timer.Simple(0, function()
hg.organism.Add(ent)
table.Merge(ent.organism, target.organism)
ent.tourniquets = table.Copy(target.tourniquets)
ent.bandaged_limbs = table.Copy(target.bandaged_limbs)
ent.organism.alive = true
ent.organism.owner = ent
ent:CallOnRemove("organism", hg.organism.Remove, ent)
hg.send_bareinfo(ent.organism)
ent:SetSkin(target:GetSkin())
ent:AddFlags(target.respawn_data.flags)
ent:AddSpawnFlags(target.respawn_data.spawn_flags)
if target.citizentype then
ent:SetKeyValue("citizentype", target.citizentype)
end
for i = 0, target:GetNumBodyGroups() - 1 do
ent:SetBodygroup(i, target:GetBodygroup(i))
end
ent:Spawn()
target:Remove()
end)
ent:SetNotSolid(true)
ent:SetNPCState(NPC_STATE_NONE)
self.StopProcessing = true
timer.Simple(0, function()
hg.organism.Add(ent)
table.Merge(ent.organism, target.organism)
ent.tourniquets = table.Copy(target.tourniquets)
ent.bandaged_limbs = table.Copy(target.bandaged_limbs)
ent.organism.alive = true
ent.organism.owner = ent
target.organism = nil
ent:CallOnRemove("organism", hg.organism.Remove, ent)
hg.send_bareinfo(ent.organism)
ent:SetSkin(target:GetSkin())
for i = 0, target:GetNumBodyGroups() - 1 do
ent:SetBodygroup(i, target:GetBodygroup(i))
end
self.unfaker:Spawn()
self.unfaker:Activate()
end)
target.FakeUp = true
self.FakeParent = ent
self.FakeUpTime = unfake_time:GetFloat()
self.FakeUpEnd = CurTime() + self.FakeUpTime
self.FakeUpStart = CurTime()
ent:SetAngles(Angle(0, math.random(-180, 180), 0))
local phys = target:GetPhysicsObject()
phys:EnableGravity(false)
target:SetNotSolid(true)
target:SetMoveType(MOVETYPE_NONE)
self.unfaker = ents.Create("npc_ragdoll_unfaker")
self.unfaker:SetPos(target:GetPos())
self.unfaker:SetNWEntity("parent", target)
self.unfaker:SetNWEntity("parent_npc", ent)
self.unfaker:SetNWFloat("fake_start", self.FakeUpStart)
self.unfaker:SetNWFloat("fake_end", self.FakeUpEnd)
target:SetRenderMode(RENDERMODE_NONE)
ent:SetRenderMode(RENDERMODE_NONE)
timer.Simple(self.FakeUpTime, function()
if !IsValid(ent) then return end
ent:SetNotSolid(false)
ent:SetNPCState(NPC_STATE_IDLE)
ent:SetRenderMode(RENDERMODE_NORMAL)
target:Remove()
self:Remove()
end)
return false
end
self:Remove()
return false
end
local phys_bone_id = phys:GetID()
@ -566,17 +353,7 @@ function MODULE:PhysicsSimulate(phys, dt)
-- Logic for fall animation
local vel = phys:GetVelocity()
local pbr = math_Clamp(vel.z / -600, 0.5, 1.5) * flailing_playback_rate:GetFloat()
target.panicking = (
target:IsOnFire() or
(!distance_check) or
(target.organism.pain > 80)
)
if target.panicking then
pbr = pbr * flailing_panic_multiplier:GetFloat()
end
local pbr = math_Clamp(vel.z / -600, 0.5, 1.5)
if self:GetSequence() ~= self:LookupSequence("Choked_Barnacle") or self:GetPlaybackRate() ~= pbr then
local seq_idle = self:LookupSequence("Choked_Barnacle")
@ -627,8 +404,6 @@ end
function MODULE:OnRemove()
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
timer_Remove(timer_name)
if IsValid(self.unfaker) then self.unfaker:Remove() end
end
--[[-------------------------------------------------------------------------

View file

@ -35,12 +35,6 @@ local cv_anim_roll_duration = CreateConVar("zcnpci_falling_anim_roll_duration",
local cv_anim_roll_impact_threshold = CreateConVar("zcnpci_falling_anim_roll_impact_threshold", "300", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. скорость удара для запуска анимации")
local cv_anim_roll_playback_rate = CreateConVar("zcnpci_falling_anim_roll_playback_rate", "3.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Скорость воспроизведения анимации")
local flailing_playback_rate = CreateConVar("zcnpci_flailing_playback_rate", "1.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
local flailing_panic_multiplier = CreateConVar("zcnpci_flailing_panic_multiplier", "2.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
-- Writhing
local writhing_strength = CreateConVar("zcnpci_writhing_strength", "1.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
-- Do it!
function MODULE:DoTwitch()
@ -129,7 +123,7 @@ function MODULE:Init()
if seq then self:ResetSequence(seq) end
local target = self:GetTarget()
self:SetPlaybackRate(1)
self.LastCollideTime = 0
self.LastGroundCollideTime = 0
@ -176,10 +170,7 @@ end
function MODULE:PhysicsSimulate(phys, dt)
local cur_time = CurTime()
local target = self:GetTarget()
if !IsValid(target) then self:Remove(); return false end -- если цели нет, то идем нахуй
if !target.organism then self:Remove(); return false end
if target.FakeUp then self.AnimationRollEndTime = 0; return false end
if not IsValid(target) then self:Remove(); return false end -- если цели нет, то идем нахуй
-- проверка на физику
if cur_time < self.AnimationRollEndTime then
@ -193,7 +184,7 @@ function MODULE:PhysicsSimulate(phys, dt)
-- --- логика идет нахуй
-- Force multiplier
local f = target.organism.consciousness * writhing_strength:GetFloat()
local f = target.organism.consciousness
-- ебаная логика для regmod
if ragmod and ragmod:IsRagmodRagdoll(target) then
@ -214,11 +205,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.4) or
(target.organism.spine2 == 1) or
(target.organism.spine3 == 1)
) then
elseif (target.organism.consciousness <= 0.3) then
return false
end
@ -239,12 +226,7 @@ function MODULE:PhysicsSimulate(phys, dt)
-- логика для анимации переката
local vel = phys:GetVelocity()
local pbr = math_Clamp(vel.z / -600, 0.5, 1.5) * flailing_playback_rate:GetFloat()
if target.panicking then
pbr = pbr * flailing_panic_multiplier:GetFloat()
end
local pbr = math_Clamp(vel.z / -600, 0.5, 1.5)
-- падения и ебаная скорость
-- если мы не в.. а впрочем иди нахуй
if self:GetSequence() ~= self:LookupSequence("idleonfire") or self:GetPlaybackRate() ~= pbr then

View file

@ -279,8 +279,6 @@ local trace = {output={}}
local tr = trace.output
function MODULE:PhysicsSimulate(phys, dt)
if self.FakeUp then return end
local phys_bone = phys:GetID()
local target = self:GetTarget()
@ -293,11 +291,6 @@ function MODULE:PhysicsSimulate(phys, dt)
local st = stumble_time:GetFloat()
-- End stumbling if player is dead
if !target.organism or !target.organism.alive then
st = 0
end
if (st <= 0) then
self:Remove()
return false
@ -305,6 +298,16 @@ function MODULE:PhysicsSimulate(phys, dt)
local f = 1 - (CurTime() - self.Created) / st
--RagMod Reworked support
if ragmod and ragmod:IsRagmodRagdoll(target) then
local owner = target:GetOwningPlayer()
if !IsValid(owner) or !owner:Alive() then
f = 0
else
f = 1
end
end
if (f <= 0) then
self:Remove()
return false