Compare commits
21 commits
stop-tackl
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9ae5bccd4 | ||
|
|
3790041d17 | ||
|
|
9498fc4697 | ||
|
|
ad9d0424bc | ||
|
|
02557dacd1 | ||
|
|
3b45d259b9 | ||
|
|
303d596a31 | ||
|
|
c9ba1e8709 | ||
|
|
cd7b77914b | ||
|
|
08bd3ecdf1 | ||
|
|
4c4d59838a | ||
|
|
f6dbe81dbe | ||
|
|
6906b0ba6f | ||
|
|
e8112ffd8a | ||
|
|
23fb275fdb | ||
|
|
72c4515402 | ||
|
|
5af8fdc14a | ||
|
|
786e997351 | ||
|
|
278a0a9d7d | ||
|
|
259a3da4e1 | ||
|
|
d9415d880f |
9 changed files with 702 additions and 107 deletions
38
README.md
38
README.md
|
|
@ -1,20 +1,23 @@
|
||||||
# Z-City NPC Integration
|
# 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 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 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.
|
||||||
|
|
||||||
- NPCs can be ragdolled like players can, and will wiggle around on the ground. They can get back up, too!
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
|
||||||
This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse remover.
|
This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse cleanup tool.
|
||||||
|
|
||||||
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.
|
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
|
||||||
|
|
||||||
- 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.
|
- 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.
|
||||||
- NPCs currently instantly get up, with no animation. I plan on remedying this in the future.
|
- 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.
|
||||||
- Enemy NPCs will not target living downed NPCs like how they do players. This will be fixed.
|
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
|
|
@ -28,10 +31,23 @@ It is, however, not compatbile with:
|
||||||
- Most mods that modify the behavior of death ragdolls (Reagdoll, Artagdoll, especially Fedhoria.)
|
- 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.)
|
- 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
|
## Todo list
|
||||||
|
|
||||||
No guarantees any of this will become real.
|
MUST BE DONE BEFORE RELEASE:
|
||||||
|
|
||||||
- NPCs will back out of combat when injured.
|
- Fire / falling / pain reaction (more rapid flailing)
|
||||||
- Friendly NPCs can heal each other.
|
- NPCs should not unfake if being grabbed
|
||||||
- NPCs can heal themselves.
|
|
||||||
|
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.
|
||||||
|
|
@ -1,8 +1,20 @@
|
||||||
|
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 function PopulateCustomNPCSBXToolMenu(pnl)
|
||||||
local modded_npc_whitelist = CreateConVar("zcnpci_modded_npc_whitelist", "NPC-classes-here!", bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
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("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: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:ControlHelp("Enable or disables applying custom health system to most other base-game humanoid NPCs, such as Kleiner.")
|
||||||
|
|
||||||
|
|
@ -25,7 +37,7 @@ local function PopulateCustomNPCSBXToolMenu(pnl)
|
||||||
|
|
||||||
pnl:AddItem(text)
|
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.")
|
pnl:Help("A list of NPC classes, seperated by spaces or line breaks. Can only be set by a superadmin or server operator.")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function PopulateRagdollSBXToolMenu(pnl)
|
local function PopulateRagdollSBXToolMenu(pnl)
|
||||||
|
|
@ -38,8 +50,29 @@ local function PopulateRagdollSBXToolMenu(pnl)
|
||||||
pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3)
|
pnl:NumSlider("Wound grab time", "zcnpci_woundgrab_time", 0, 10, 3)
|
||||||
pnl:ControlHelp("How long the ragdoll should hold its wound.")
|
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: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: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
|
end
|
||||||
|
|
||||||
local function PopulatePerformanceSBXToolMenu(pnl)
|
local function PopulatePerformanceSBXToolMenu(pnl)
|
||||||
|
|
@ -119,6 +152,11 @@ if engine.ActiveGamemode() == "sandbox" then
|
||||||
PopulateRagdollSBXToolMenu(pnl)
|
PopulateRagdollSBXToolMenu(pnl)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "AISettings", "AI", "", "", function(pnl)
|
||||||
|
pnl:ClearControls()
|
||||||
|
PopulateAISBXToolMenu(pnl)
|
||||||
|
end)
|
||||||
|
|
||||||
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "MainSettings", "Main", "", "", function(pnl)
|
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "MainSettings", "Main", "", "", function(pnl)
|
||||||
pnl:ClearControls()
|
pnl:ClearControls()
|
||||||
PopulateMainSBXToolMenu(pnl)
|
PopulateMainSBXToolMenu(pnl)
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ end
|
||||||
function ENT:Initialize()
|
function ENT:Initialize()
|
||||||
if !self.Module then return end
|
if !self.Module then return end
|
||||||
|
|
||||||
self:SetModel(self.Module.Model)
|
if self.Module.Model then self:SetModel(self.Module.Model) end
|
||||||
|
|
||||||
for key, value in pairs(self.Module) do
|
for key, value in pairs(self.Module) do
|
||||||
if !self[key] then
|
if !self[key] then
|
||||||
|
|
|
||||||
120
lua/entities/npc_ragdoll_target.lua
Normal file
120
lua/entities/npc_ragdoll_target.lua
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
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
|
||||||
131
lua/entities/npc_ragdoll_unfaker.lua
Normal file
131
lua/entities/npc_ragdoll_unfaker.lua
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
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
|
||||||
102
lua/zcnpci.lua
102
lua/zcnpci.lua
|
|
@ -19,7 +19,9 @@ 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_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 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 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))
|
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))
|
||||||
|
|
||||||
-- 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
|
-- 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)
|
local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelist", function(ply, cmd, args)
|
||||||
|
|
@ -30,8 +32,6 @@ local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelis
|
||||||
modded_npc_whitelist:SetString(args[1])
|
modded_npc_whitelist:SetString(args[1])
|
||||||
end)
|
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 = {}
|
||||||
|
|
@ -57,6 +57,45 @@ local corpses = {}
|
||||||
local last_corpse_tick = CurTime()
|
local last_corpse_tick = CurTime()
|
||||||
local last_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)
|
hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
|
||||||
if !ent.organism then return end
|
if !ent.organism then return end
|
||||||
|
|
||||||
|
|
@ -118,8 +157,15 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
|
||||||
end
|
end
|
||||||
|
|
||||||
if ent.npcfakeknockback then
|
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)
|
ragdoll:GetPhysicsObject():SetVelocity(ent.npcfakeknockback)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local velocity = ragdoll:GetPhysicsObject():GetVelocity()
|
||||||
|
|
||||||
-- For some reason, citizen types such as rebels, medics, refugees, etc. use a keyvalue.
|
-- 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,
|
-- That keyvalue does not transfer over to the ragdoll, despite it looking like the NPC,
|
||||||
|
|
@ -127,10 +173,19 @@ hook.Add("CreateEntityRagdoll", "zcnpci", function(ent, ragdoll)
|
||||||
-- Why is it like this? Fuck if I know, I just know I don't wanna deal with it again.
|
-- 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.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()
|
timer.Simple(0, function()
|
||||||
|
if !IsValid(ragdoll) then return end
|
||||||
|
|
||||||
ragdoll.organism.alive = true
|
ragdoll.organism.alive = true
|
||||||
|
|
||||||
if !IsValid(ragdoll) then return end
|
ragdoll:SetRenderMode(RENDERMODE_NORMAL)
|
||||||
|
|
||||||
zcnpci.StartModule(ragdoll, "stumble_legs", phys_bone, lpos)
|
zcnpci.StartModule(ragdoll, "stumble_legs", phys_bone, lpos)
|
||||||
last_dmgpos[ent] = nil
|
last_dmgpos[ent] = nil
|
||||||
|
|
@ -139,6 +194,7 @@ end)
|
||||||
|
|
||||||
hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
|
hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
|
||||||
if !enabled:GetBool() or !IsValid(dmginfo) then return end
|
if !enabled:GetBool() or !IsValid(dmginfo) then return end
|
||||||
|
|
||||||
if !ent:IsNPC() then
|
if !ent:IsNPC() then
|
||||||
if !ent.organism or !ent.StartDie then return end
|
if !ent.organism or !ent.StartDie then return end
|
||||||
|
|
||||||
|
|
@ -150,31 +206,33 @@ hook.Add("HomigradDamage", "zcnpci", function(ent, dmginfo)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
last_dmgpos[ent] = dmginfo:GetDamage()
|
last_dmgpos[ent] = dmginfo:GetDamagePosition()
|
||||||
last_attacker[ent] = dmginfo:GetAttacker()
|
last_attacker[ent] = dmginfo:GetAttacker()
|
||||||
|
|
||||||
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST) then
|
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST) then
|
||||||
if dmginfo:GetDamage() > 1.5 then
|
if dmginfo:GetDamage() > 3 then
|
||||||
ent:TakeDamage(ent:Health())
|
table.insert(npcs_to_fake, ent)
|
||||||
end
|
end
|
||||||
elseif dmginfo:IsDamageType(DMG_CLUB + DMG_SLASH) then
|
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()
|
local attacker = dmginfo:GetAttacker()
|
||||||
|
if !IsValid(attacker) then return end
|
||||||
|
|
||||||
print(fists.InLegKick)
|
if attacker.HasWeapon == nil then return end
|
||||||
|
|
||||||
|
local fists = attacker:HasWeapon("weapon_hands_sh")
|
||||||
|
if !fists then fists = attacker:HasWeapon("weapon_hg_coolhands") end
|
||||||
|
|
||||||
-- Kicks should knock NPCs down
|
-- Kicks should knock NPCs down
|
||||||
if IsValid(dmginfo:GetInflictor()) and IsValid(fists) and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then
|
if IsValid(dmginfo:GetInflictor()) and fists and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then
|
||||||
table.insert(npcs_to_fake, ent)
|
table.insert(npcs_to_fake, ent)
|
||||||
print("ADD TO LIST!!!")
|
|
||||||
|
|
||||||
local attacker_angle = dmginfo:GetAttacker():EyeAngles()
|
local attacker_angle = dmginfo:GetAttacker():EyeAngles()
|
||||||
|
|
||||||
local normal = attacker_angle:Forward(normal)
|
local normal = attacker_angle:Forward(normal)
|
||||||
|
|
||||||
ent.npcfakeknockback = normal * dmginfo:GetDamage() * 135
|
ent.npcfakeknockback = normal * dmginfo:GetDamage() * 135
|
||||||
|
|
||||||
|
last_dmgpos[ent] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -288,29 +346,21 @@ hook.Add("Tick", "zcnpci", function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
PrintTable(npcs_to_fake)
|
-- NPC faking is in a seperate area because we want NPCs to fake as soon as they can
|
||||||
|
|
||||||
-- 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
|
for i,ent in pairs(npcs_to_fake) do
|
||||||
|
ent.organism_no_damage = true
|
||||||
|
|
||||||
local damage_info = DamageInfo()
|
local damage_info = DamageInfo()
|
||||||
damage_info:SetDamage(0)
|
damage_info:SetDamage(999999)
|
||||||
damage_info:SetAttacker(ent)
|
damage_info:SetAttacker(ent)
|
||||||
damage_info:SetDamageType( DMG_DIRECT )
|
|
||||||
damage_info:SetDamageForce(Vector(0, 0, 0))
|
damage_info:SetDamageForce(Vector(0, 0, 0))
|
||||||
|
|
||||||
ent:BecomeRagdoll(damage_info)
|
ent:TakeDamageInfo(damage_info)
|
||||||
|
|
||||||
table.remove(npcs_to_fake, 1)
|
table.remove(npcs_to_fake, 1)
|
||||||
end
|
end
|
||||||
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)
|
hook.Add("OnEntityCreated", "zcnpci", function(ent)
|
||||||
if !IsValid(ent) then return end
|
if !IsValid(ent) then return end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,31 @@ local twitchable_bone_names = {
|
||||||
"ValveBiped.Bip01_L_Foot"
|
"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 Copies of Functions for Optimization
|
||||||
local math_Clamp = math.Clamp
|
local math_Clamp = math.Clamp
|
||||||
local math_Rand = math.Rand
|
local math_Rand = math.Rand
|
||||||
|
|
@ -69,7 +94,24 @@ 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}, "Скорость воспроизведения анимации")
|
local cv_anim_roll_playback_rate = CreateConVar("zcnpci_falling_anim_roll_playback_rate", "3.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Скорость воспроизведения анимации")
|
||||||
|
|
||||||
-- Down time
|
-- Down time
|
||||||
local minimum_down_time = CreateConVar("zcnpci_down_time", "3", {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})
|
||||||
|
|
||||||
--[[-------------------------------------------------------------------------
|
--[[-------------------------------------------------------------------------
|
||||||
Tug function
|
Tug function
|
||||||
|
|
@ -165,7 +207,15 @@ function MODULE:Init()
|
||||||
self.AnimationRollEndTime = 0
|
self.AnimationRollEndTime = 0
|
||||||
self.StopProcessing = false
|
self.StopProcessing = false
|
||||||
self.LastThink = CurTime()
|
self.LastThink = CurTime()
|
||||||
self.LastPhysProcess = 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()
|
||||||
|
|
||||||
-- Add damping to all bones.
|
-- Add damping to all bones.
|
||||||
local phys = self:GetPhysicsObject()
|
local phys = self:GetPhysicsObject()
|
||||||
|
|
@ -189,13 +239,29 @@ end
|
||||||
---------------------------------------------------------------------------]]
|
---------------------------------------------------------------------------]]
|
||||||
|
|
||||||
function MODULE:Think()
|
function MODULE:Think()
|
||||||
if (CurTime() - self.LastThink) < 1 then return end
|
--if (CurTime() - self.LastThink) < 1 then return end
|
||||||
|
local target = self:GetTarget()
|
||||||
if !IsValid(target) then return end
|
if !IsValid(target) then return end
|
||||||
|
|
||||||
self.LastThink = CurTime()
|
self.LastThink = CurTime()
|
||||||
|
|
||||||
local phys = target:GetPhysicsObject()
|
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
|
if !IsValid(phys) or !phys:IsAsleep() then return end
|
||||||
|
|
||||||
phys:Wake()
|
phys:Wake()
|
||||||
|
|
@ -242,15 +308,79 @@ end
|
||||||
Physics Simulation Hook (FIXED)
|
Physics Simulation Hook (FIXED)
|
||||||
---------------------------------------------------------------------------]]
|
---------------------------------------------------------------------------]]
|
||||||
function MODULE:PhysicsSimulate(phys, dt)
|
function MODULE:PhysicsSimulate(phys, dt)
|
||||||
if self.StopProcessing then return false end
|
if !SERVER then return end
|
||||||
--print((CurTime() - self.LastPhysProcess) < 0.05)
|
|
||||||
--if (CurTime() - self.LastPhysProcess) < (1 /) then return false end
|
|
||||||
|
|
||||||
self.LastPhysProcess = CurTime()
|
|
||||||
|
|
||||||
local cur_time = CurTime()
|
local cur_time = CurTime()
|
||||||
local target = self:GetTarget()
|
local target = self:GetTarget()
|
||||||
if not IsValid(target) then self:Remove(); return false end
|
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())
|
||||||
|
|
||||||
target.is_npc_corpse = true
|
target.is_npc_corpse = true
|
||||||
|
|
||||||
|
|
@ -263,7 +393,7 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Force multiplier
|
-- Force multiplier
|
||||||
local f = target.organism.consciousness
|
local f = target.organism.consciousness * writhing_strength:GetFloat()
|
||||||
|
|
||||||
local minimum_down_timer = 0
|
local minimum_down_timer = 0
|
||||||
if target.StartDie then
|
if target.StartDie then
|
||||||
|
|
@ -275,18 +405,58 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
return false -- Cut the bullshit
|
return false -- Cut the bullshit
|
||||||
end
|
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 (!target.organism.alive) then
|
||||||
-- 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.5) or ((target.organism.lleg >= 0.85) and (target.organism.rleg >= 0.85)) then
|
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
|
||||||
target.StartDie = cur_time
|
target.StartDie = cur_time
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Getting up
|
if (self.LastPosCheck:DistToSqr(target:GetPos()) > (movement_sensitivity:GetFloat() ^ 2)) then
|
||||||
-- Don't need to check for consciousness and the like because we've done it already above
|
self.LastPosCheck = target:GetPos()
|
||||||
|
target.StartDie = cur_time
|
||||||
|
end
|
||||||
|
|
||||||
|
local distance_check = util.TraceLine({
|
||||||
|
start = target:GetPos(),
|
||||||
|
endpos = target:GetPos() + Vector(0, 0, -128),
|
||||||
|
filter = {target},
|
||||||
|
mask = MASK_SOLID
|
||||||
|
}).Hit
|
||||||
|
|
||||||
|
if !distance_check then
|
||||||
|
target.StartDie = cur_time
|
||||||
|
end
|
||||||
|
|
||||||
|
if ((CurTime() - self.LastFakeUpCheck) >= 1.0) and (can_unfake:GetBool()) then
|
||||||
|
self.LastFakeUpCheck = CurTime()
|
||||||
|
|
||||||
|
-- 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 }
|
||||||
|
})
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
(!tracehull.Hit) and
|
||||||
(minimum_down_timer >= 1.0) and
|
(minimum_down_timer >= 1.0) and
|
||||||
(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
|
||||||
|
|
@ -301,6 +471,9 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
|
|
||||||
ent:SetSkin(target:GetSkin())
|
ent:SetSkin(target:GetSkin())
|
||||||
|
|
||||||
|
ent:AddFlags(target.respawn_data.flags)
|
||||||
|
ent:AddSpawnFlags(target.respawn_data.spawn_flags)
|
||||||
|
|
||||||
if target.citizentype then
|
if target.citizentype then
|
||||||
ent:SetKeyValue("citizentype", target.citizentype)
|
ent:SetKeyValue("citizentype", target.citizentype)
|
||||||
end
|
end
|
||||||
|
|
@ -311,6 +484,9 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
|
|
||||||
ent:Spawn()
|
ent:Spawn()
|
||||||
|
|
||||||
|
ent:SetNotSolid(true)
|
||||||
|
ent:SetNPCState(NPC_STATE_NONE)
|
||||||
|
|
||||||
timer.Simple(0, function()
|
timer.Simple(0, function()
|
||||||
hg.organism.Add(ent)
|
hg.organism.Add(ent)
|
||||||
table.Merge(ent.organism, target.organism)
|
table.Merge(ent.organism, target.organism)
|
||||||
|
|
@ -321,6 +497,8 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
ent.organism.alive = true
|
ent.organism.alive = true
|
||||||
ent.organism.owner = ent
|
ent.organism.owner = ent
|
||||||
|
|
||||||
|
target.organism = nil
|
||||||
|
|
||||||
ent:CallOnRemove("organism", hg.organism.Remove, ent)
|
ent:CallOnRemove("organism", hg.organism.Remove, ent)
|
||||||
hg.send_bareinfo(ent.organism)
|
hg.send_bareinfo(ent.organism)
|
||||||
|
|
||||||
|
|
@ -330,14 +508,49 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
ent:SetBodygroup(i, target:GetBodygroup(i))
|
ent:SetBodygroup(i, target:GetBodygroup(i))
|
||||||
end
|
end
|
||||||
|
|
||||||
target:Remove()
|
self.unfaker:Spawn()
|
||||||
|
self.unfaker:Activate()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
self.StopProcessing = true
|
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()
|
self:Remove()
|
||||||
|
end)
|
||||||
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local phys_bone_id = phys:GetID()
|
local phys_bone_id = phys:GetID()
|
||||||
|
|
||||||
|
|
@ -353,7 +566,17 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
|
|
||||||
-- Logic for fall animation
|
-- Logic for fall animation
|
||||||
local vel = phys:GetVelocity()
|
local vel = phys:GetVelocity()
|
||||||
local pbr = math_Clamp(vel.z / -600, 0.5, 1.5)
|
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
|
||||||
|
|
||||||
if self:GetSequence() ~= self:LookupSequence("Choked_Barnacle") or self:GetPlaybackRate() ~= pbr then
|
if self:GetSequence() ~= self:LookupSequence("Choked_Barnacle") or self:GetPlaybackRate() ~= pbr then
|
||||||
local seq_idle = self:LookupSequence("Choked_Barnacle")
|
local seq_idle = self:LookupSequence("Choked_Barnacle")
|
||||||
|
|
@ -404,6 +627,8 @@ end
|
||||||
function MODULE:OnRemove()
|
function MODULE:OnRemove()
|
||||||
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
|
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
|
||||||
timer_Remove(timer_name)
|
timer_Remove(timer_name)
|
||||||
|
|
||||||
|
if IsValid(self.unfaker) then self.unfaker:Remove() end
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-------------------------------------------------------------------------
|
--[[-------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,12 @@ 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_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 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!
|
-- Do it!
|
||||||
function MODULE:DoTwitch()
|
function MODULE:DoTwitch()
|
||||||
|
|
||||||
|
|
@ -170,7 +176,10 @@ end
|
||||||
function MODULE:PhysicsSimulate(phys, dt)
|
function MODULE:PhysicsSimulate(phys, dt)
|
||||||
local cur_time = CurTime()
|
local cur_time = CurTime()
|
||||||
local target = self:GetTarget()
|
local target = self:GetTarget()
|
||||||
if not IsValid(target) then self:Remove(); return false end -- если цели нет, то идем нахуй
|
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 cur_time < self.AnimationRollEndTime then
|
if cur_time < self.AnimationRollEndTime then
|
||||||
|
|
@ -184,7 +193,7 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
-- --- логика идет нахуй
|
-- --- логика идет нахуй
|
||||||
|
|
||||||
-- Force multiplier
|
-- Force multiplier
|
||||||
local f = target.organism.consciousness
|
local f = target.organism.consciousness * writhing_strength:GetFloat()
|
||||||
|
|
||||||
-- ебаная логика для regmod
|
-- ебаная логика для regmod
|
||||||
if ragmod and ragmod:IsRagmodRagdoll(target) then
|
if ragmod and ragmod:IsRagmodRagdoll(target) then
|
||||||
|
|
@ -205,7 +214,11 @@ 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) then
|
elseif (
|
||||||
|
(target.organism.consciousness <= 0.4) or
|
||||||
|
(target.organism.spine2 == 1) or
|
||||||
|
(target.organism.spine3 == 1)
|
||||||
|
) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -226,7 +239,12 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
|
|
||||||
-- логика для анимации переката
|
-- логика для анимации переката
|
||||||
local vel = phys:GetVelocity()
|
local vel = phys:GetVelocity()
|
||||||
local pbr = math_Clamp(vel.z / -600, 0.5, 1.5)
|
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
|
||||||
|
|
||||||
-- падения и ебаная скорость
|
-- падения и ебаная скорость
|
||||||
-- если мы не в.. а впрочем иди нахуй
|
-- если мы не в.. а впрочем иди нахуй
|
||||||
if self:GetSequence() ~= self:LookupSequence("idleonfire") or self:GetPlaybackRate() ~= pbr then
|
if self:GetSequence() ~= self:LookupSequence("idleonfire") or self:GetPlaybackRate() ~= pbr then
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,8 @@ local trace = {output={}}
|
||||||
local tr = trace.output
|
local tr = trace.output
|
||||||
|
|
||||||
function MODULE:PhysicsSimulate(phys, dt)
|
function MODULE:PhysicsSimulate(phys, dt)
|
||||||
|
if self.FakeUp then return end
|
||||||
|
|
||||||
local phys_bone = phys:GetID()
|
local phys_bone = phys:GetID()
|
||||||
|
|
||||||
local target = self:GetTarget()
|
local target = self:GetTarget()
|
||||||
|
|
@ -291,6 +293,11 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
|
|
||||||
local st = stumble_time:GetFloat()
|
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
|
if (st <= 0) then
|
||||||
self:Remove()
|
self:Remove()
|
||||||
return false
|
return false
|
||||||
|
|
@ -298,16 +305,6 @@ function MODULE:PhysicsSimulate(phys, dt)
|
||||||
|
|
||||||
local f = 1 - (CurTime() - self.Created) / st
|
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
|
if (f <= 0) then
|
||||||
self:Remove()
|
self:Remove()
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue