Compare commits
No commits in common. "master" and "legacy" have entirely different histories.
17 changed files with 673 additions and 1620 deletions
58
README.md
58
README.md
|
|
@ -1,53 +1,19 @@
|
|||
# Z-City NPC Integration
|
||||
|
||||
Adds better NPC integration for the Garry's Mod addon Z-City.
|
||||
I made this in an angry fugue after desperately trying to improve my frames by creating a more optimized euphoria ragdoll mod. Now that I'm done with that I figured out how tio actually fix my frames, and it was not this addon. However, I have another issue: NPCs with Z-City suck. Like they tank hits like crazy. You could unload a mag on someone's leg and there would be a non-zero chance they would still be perfectly fine. I'd like to turn my optimized Fedhoria mod into a base for a Z-City NPC improvement mod.
|
||||
|
||||
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.
|
||||
Some ideas:
|
||||
|
||||
- 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.
|
||||
- If an enemy breaks a leg, slow them down.
|
||||
- If an enemy breaks an arm, make their aim worse.
|
||||
- Break both legs? Hope you like writhing on the ground for the rest of your life!
|
||||
- Generally, enemies need to be closer to players in terms of fleshiness. Like a shot to the arm should knock them over, that shit hurts yknow.
|
||||
- Enemies should target downed enemies of hostile factions.
|
||||
|
||||
This mod is built to perform reasonably well, thus it includes optimizations, configurable performance settings, and an automatic corpse cleanup tool.
|
||||
Since theoretically the NPCs use the same health system that players use, these shouldn't be too hard to implement.
|
||||
|
||||
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.
|
||||
More optimistic ideas:
|
||||
|
||||
## 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.
|
||||
|
||||
## Compatibility
|
||||
|
||||
This should be compatible with:
|
||||
|
||||
- The majority of NPC AI improvement mods
|
||||
- Competently-programmed Z-City addons
|
||||
|
||||
It is, however, not compatbile with:
|
||||
|
||||
- Most mods that modify the behavior of death ragdolls (Reagdoll, Artagdoll, especially Fedhoria.)
|
||||
- Mods that automatically remove death ragdolls (it will delete all ragdolled NPCs, even if they're still alive! Try the built-in automatic corpse removal instead.)
|
||||
|
||||
## Todo list
|
||||
|
||||
MUST BE DONE BEFORE RELEASE:
|
||||
|
||||
- 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 to a limited degree.
|
||||
11
addon.json
11
addon.json
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"title" : "Z-City NPC Integration",
|
||||
"type" : "Effects",
|
||||
"tags" : [ "realism", "fun", "scenic" ],
|
||||
"ignore" :
|
||||
[
|
||||
"*.psd",
|
||||
"*.vcproj",
|
||||
"*.svn*"
|
||||
]
|
||||
}
|
||||
48
autorun/client/fedh_menu.lua
Normal file
48
autorun/client/fedh_menu.lua
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
local function PopulateSBXToolMenu(pnl)
|
||||
pnl:CheckBox("Enabled", "fedhoria_enabled")
|
||||
pnl:ControlHelp("Enable or disable the addon.")
|
||||
|
||||
pnl:CheckBox("Players", "fedhoria_players")
|
||||
pnl:ControlHelp("Enable or disable effect for players.")
|
||||
|
||||
pnl:CheckBox("NPCs", "fedhoria_npcs")
|
||||
pnl:ControlHelp("Enable or disable effect for NPCs.")
|
||||
|
||||
pnl:Help(" ")
|
||||
|
||||
pnl:NumSlider("Stumble time", "fedhoria_stumble_time", 0, 10, 3)
|
||||
pnl:ControlHelp("How long the ragdoll should stumble for.")
|
||||
|
||||
pnl:NumSlider("Die time", "fedhoria_dietime", 0, 10, 3)
|
||||
pnl:ControlHelp("How long before the ragdoll dies after drowning/being still for too long.")
|
||||
|
||||
pnl:NumSlider("Die time variation", "fedhoria_dietime_variation", 0, 10, 2)
|
||||
pnl:ControlHelp("A random number between 0 and the value you choose here will be added to the die timer.")
|
||||
|
||||
pnl:NumSlider("Wound grab chance", "fedhoria_woundgrab_chance", 0, 1, 3)
|
||||
pnl:ControlHelp("The chance the ragdoll will grab it's wound when shot.")
|
||||
|
||||
pnl:NumSlider("Wound grab time", "fedhoria_woundgrab_time", 0, 10, 3)
|
||||
pnl:ControlHelp("How long the ragdoll should hold its wound.")
|
||||
|
||||
pnl:Help(" ")
|
||||
|
||||
pnl:NumSlider("Activation range", "fedhoria_active_range", 0, 5000, 0)
|
||||
pnl:ControlHelp("How close the ragdoll has to be to a player to have euphoria physics")
|
||||
|
||||
pnl:CheckBox("Always activate on player kill", "fedhoria_always_active_on_player_kill")
|
||||
pnl:ControlHelp("If on, all player-killed ragdolls will be activated regardless of other factors.")
|
||||
end
|
||||
|
||||
if engine.ActiveGamemode() == "sandbox" then
|
||||
hook.Add("AddToolMenuCategories", "FedhoriaCategory", function()
|
||||
spawnmenu.AddToolCategory("Utilities", "Fedhoria", "Fedhoria")
|
||||
end)
|
||||
|
||||
hook.Add("PopulateToolMenu", "FedhoriaMenuSettings", function()
|
||||
spawnmenu.AddToolMenuOption("Utilities", "Fedhoria", "FedhoriaSettings", "Settings", "", "", function(pnl)
|
||||
pnl:ClearControls()
|
||||
PopulateSBXToolMenu(pnl)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
1
autorun/server/fedh_init.lua
Normal file
1
autorun/server/fedh_init.lua
Normal file
|
|
@ -0,0 +1 @@
|
|||
include("fedhoria.lua")
|
||||
|
|
@ -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
|
||||
219
fedhoria.lua
Normal file
219
fedhoria.lua
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
include("fedhoria/modules.lua")
|
||||
|
||||
local enabled = CreateConVar("fedhoria_enabled", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local players = CreateConVar("fedhoria_players", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local npcs = CreateConVar("fedhoria_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local active_range = CreateConVar("fedhoria_active_range", 300, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local always_active_on_player_kill = CreateConVar("fedhoria_always_active_on_player_kill", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
|
||||
local last_dmgpos = {}
|
||||
local last_hitgroup = {}
|
||||
local last_attacker = {}
|
||||
|
||||
hook.Add("CreateEntityRagdoll", "Fedhoria", function(ent, ragdoll)
|
||||
if (!enabled:GetBool() or !npcs:GetBool()) then return end
|
||||
local dmgpos = last_dmgpos[ent]
|
||||
|
||||
if last_hitgroup[ent] == HITGROUP_HEAD then return end
|
||||
|
||||
local active_by_default = false
|
||||
|
||||
if always_active_on_player_kill:GetBool() then
|
||||
if last_attacker[ent] and last_attacker[ent]:IsPlayer() then active_by_default = true end
|
||||
end
|
||||
|
||||
if !active_by_default then
|
||||
local entity_position = ragdoll:GetPos()
|
||||
local in_range = false
|
||||
local players = player.GetAll()
|
||||
|
||||
for i, loop_player in pairs(players) do
|
||||
local player_position = loop_player:GetPos()
|
||||
|
||||
local distance = entity_position:Distance(player_position)
|
||||
|
||||
if distance <= active_range:GetFloat() then
|
||||
in_range = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if !in_range then return end
|
||||
end
|
||||
|
||||
|
||||
|
||||
print(dmgpos)
|
||||
|
||||
local phys_bone, lpos
|
||||
|
||||
if dmgpos then
|
||||
phys_bone = ragdoll:GetClosestPhysBone(dmgpos)
|
||||
if phys_bone then
|
||||
local phys = ragdoll:GetPhysicsObjectNum(phys_bone)
|
||||
lpos = phys:WorldToLocal(dmgpos)
|
||||
end
|
||||
end
|
||||
|
||||
timer.Simple(0, function()
|
||||
if !IsValid(ragdoll) then return end
|
||||
|
||||
fedhoria.StartModule(ragdoll, "stumble_legs", phys_bone, lpos)
|
||||
last_dmgpos[ent] = nil
|
||||
end)
|
||||
end)
|
||||
|
||||
hook.Add("EntityTakeDamage", "Fedhoria", function(ent, dmginfo)
|
||||
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()
|
||||
last_attacker[ent] = dmginfo:GetAttacker()
|
||||
end)
|
||||
|
||||
hook.Add("ScaleNPCDamage", "Fedhoria", function(npc, hitgroup, dmginfo)
|
||||
if not IsValid(npc) then return end
|
||||
last_hitgroup[npc] = hitgroup
|
||||
print(last_hitgroup[npc] == HITGROUP_HEAD)
|
||||
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)]]
|
||||
|
||||
local once = true
|
||||
|
||||
--RagMod/TTT support
|
||||
hook.Add("OnEntityCreated", "Fedhoria", function(ent)
|
||||
--If RagMod isn't installed remove this hook
|
||||
if once then
|
||||
once = nil
|
||||
if (!RMA_Ragdolize and !CORPSE) then
|
||||
hook.Remove("OnEntityCreated", "Fedhoria")
|
||||
return
|
||||
end
|
||||
--these hooks fucks shit up
|
||||
if RMA_Ragdolize then
|
||||
hook.Remove( "PlayerDeath", "RM_PlayerDies")
|
||||
hook.Add( "PostPlayerDeath", "RemoveRagdoll", function(ply)
|
||||
if IsValid(ply.RM_Ragdoll) then
|
||||
SafeRemoveEntity(ply:GetRagdollEntity())
|
||||
ply:SpectateEntity(ply.RM_Ragdoll)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
if (!enabled:GetBool() or !players:GetBool() or !ent:IsRagdoll()) then return end
|
||||
timer.Simple(0, function()
|
||||
if !IsValid(ent) then return end
|
||||
if CORPSE then
|
||||
local ply = ent:GetDTEntity(CORPSE.dti.ENT_PLAYER)
|
||||
if (IsValid(ply) and ply:IsPlayer()) then
|
||||
fedhoria.StartModule(ent, "stumble_legs")
|
||||
return
|
||||
end
|
||||
end
|
||||
for _, ply in ipairs(player.GetAll()) do
|
||||
if (ply.RM_IsRagdoll and ply.RM_Ragdoll == ent) then
|
||||
fedhoria.StartModule(ent, "stumble_legs")
|
||||
return
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
local PLAYER = FindMetaTable("Player")
|
||||
|
||||
local oldCreateRagdoll = PLAYER.CreateRagdoll
|
||||
|
||||
local dolls = {}
|
||||
|
||||
local function CreateRagdoll(self)
|
||||
SafeRemoveEntity(dolls[self])
|
||||
|
||||
local ragdoll = ents.Create("prop_ragdoll")
|
||||
ragdoll:SetModel(self:GetModel())
|
||||
ragdoll:SetPos(self:GetPos())
|
||||
ragdoll:SetAngles(self:GetAngles())
|
||||
ragdoll:Spawn()
|
||||
|
||||
ragdoll:SetSkin(self:GetSkin())
|
||||
|
||||
for i = 0, self:GetNumBodyGroups() - 1 do
|
||||
ragdoll:SetBodygroup(i, self:GetBodygroup(i))
|
||||
end
|
||||
|
||||
for i = 0, ragdoll:GetPhysicsObjectCount()-1 do
|
||||
local phys = ragdoll:GetPhysicsObjectNum(i)
|
||||
local bone = ragdoll:TranslatePhysBoneToBone(i)
|
||||
local matrix = self:GetBoneMatrix(bone)
|
||||
local pos, ang = matrix:GetTranslation(), matrix:GetAngles()--self:GetBonePosition(bone)
|
||||
phys:SetPos(pos)
|
||||
phys:SetAngles(ang)
|
||||
phys:SetVelocity(self:GetVelocity())
|
||||
end
|
||||
|
||||
self:SpectateEntity(ragdoll)
|
||||
self:Spectate(OBS_MODE_CHASE)
|
||||
|
||||
dolls[self] = ragdoll
|
||||
end
|
||||
|
||||
local oldGetRagdollEntity = PLAYER.GetRagdollEntity
|
||||
|
||||
local function GetRagdollEntity(self)
|
||||
return dolls[self] or NULL
|
||||
end
|
||||
|
||||
if enabled:GetBool() then
|
||||
PLAYER.CreateRagdoll = CreateRagdoll
|
||||
PLAYER.GetRagdollEntity = GetRagdollEntity
|
||||
end
|
||||
|
||||
cvars.AddChangeCallback("fedhoria_enabled", function(name, old, new)
|
||||
if (new == "1") then
|
||||
if players:GetBool() then
|
||||
PLAYER.CreateRagdoll = CreateRagdoll
|
||||
PLAYER.GetRagdollEntity = GetRagdollEntity
|
||||
end
|
||||
else
|
||||
PLAYER.CreateRagdoll = oldCreateRagdoll
|
||||
PLAYER.GetRagdollEntity = oldGetRagdollEntity
|
||||
end
|
||||
end)
|
||||
|
||||
cvars.AddChangeCallback("fedhoria_players", function(name, old, new)
|
||||
if (new == "1") then
|
||||
if enabled:GetBool() then
|
||||
if (debug.getinfo(PLAYER.CreateRagdoll).short_src == "[C]") then
|
||||
PLAYER.CreateRagdoll = CreateRagdoll
|
||||
PLAYER.GetRagdollEntity = GetRagdollEntity
|
||||
end
|
||||
end
|
||||
else
|
||||
PLAYER.CreateRagdoll = oldCreateRagdoll
|
||||
PLAYER.GetRagdollEntity = oldGetRagdollEntity
|
||||
end
|
||||
end)
|
||||
|
||||
hook.Add("PostPlayerDeath", "Fedhoria", function(ply)
|
||||
if (!enabled:GetBool() or !players:GetBool()) then return end
|
||||
timer.Simple(0, function()
|
||||
if !IsValid(ply) then return end
|
||||
local ragdoll = ply:GetRagdollEntity()
|
||||
if (IsValid(ragdoll) and ragdoll:IsRagdoll()) then
|
||||
fedhoria.StartModule(ragdoll, "stumble_legs")
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
--RagMod Reworked support
|
||||
|
||||
hook.Add("RM_RagdollReady", "Fedhoria", function(ragdoll)
|
||||
if IsValid(ragdoll) then
|
||||
fedhoria.StartModule(ragdoll, "stumble_legs")
|
||||
end
|
||||
end)
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
zcnpci = {}
|
||||
fedhoria = {}
|
||||
|
||||
local modules = {}
|
||||
|
||||
function zcnpci.GetModule(name)
|
||||
function fedhoria.GetModule(name)
|
||||
if modules[name] then
|
||||
return modules[name]
|
||||
end
|
||||
local path = "zcnpci/modules/"..name..".lua"
|
||||
local path = "fedhoria/modules/"..name..".lua"
|
||||
if !file.Exists(path, "LUA") then
|
||||
print("zcnpci.GetModule failed, couldn't find module '"..name.."'")
|
||||
print("fedhoria.GetModule failed, couldn't find module '"..name.."'")
|
||||
return
|
||||
end
|
||||
local MODULE_old = MODULE
|
||||
|
|
@ -20,12 +20,12 @@ function zcnpci.GetModule(name)
|
|||
return modules[name]
|
||||
end
|
||||
|
||||
function zcnpci.GetModuleList()
|
||||
function fedhoria.GetModuleList()
|
||||
return modules
|
||||
end
|
||||
|
||||
function zcnpci.StartModule(ent, name, ...)
|
||||
if (!modules[name] and !zcnpci.GetModule(name)) then
|
||||
function fedhoria.StartModule(ent, name, ...)
|
||||
if (!modules[name] and !fedhoria.GetModule(name)) then
|
||||
return false
|
||||
end
|
||||
local contr = ents.Create("active_ragdoll_controller")
|
||||
|
|
@ -38,8 +38,8 @@ function zcnpci.StartModule(ent, name, ...)
|
|||
end
|
||||
|
||||
--preload modules
|
||||
for _, file_name in pairs(file.Find("zcnpci/modules/*.lua", "LUA")) do
|
||||
zcnpci.GetModule(file_name:sub(1, -5))
|
||||
for _, file_name in pairs(file.Find("fedhoria/modules/*.lua", "LUA")) do
|
||||
fedhoria.GetModule(file_name:sub(1, -5))
|
||||
end
|
||||
|
||||
local ENTITY = FindMetaTable("Entity")
|
||||
|
|
@ -75,7 +75,6 @@ function ENTITY:GetClosestPhysBone(pos)
|
|||
end
|
||||
|
||||
if !collides then return end
|
||||
if type(pos) != "Vector" then return end
|
||||
|
||||
local closest_bone
|
||||
local dist = math.huge
|
||||
331
fedhoria/modules/falling_legs.lua
Normal file
331
fedhoria/modules/falling_legs.lua
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
--[[-------------------------------------------------------------------------
|
||||
Скрипт для управления поведением падающих частей тела NPC (например, ног)
|
||||
Включает эффекты дергания и анимацию переката при ударе.
|
||||
Версия 2: Исправлены ошибки GetGameData и HitNormal.
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
MODULE = {}
|
||||
|
||||
MODULE.Model = "models/police.mdl" -- Модель, к которой применяется скрипт (вероятно, не используется напрямую в этом коде)
|
||||
|
||||
-- Список костей, которые МОГУТ быть затронуты физикой или анимацией.
|
||||
-- Для дергания будут использоваться только кости ног из этого списка.
|
||||
MODULE.BoneList =
|
||||
{
|
||||
"ValveBiped.Bip01_Pelvis",
|
||||
-- "ValveBiped.Bip01_Spine2",
|
||||
-- "ValveBiped.Bip01_Head1",
|
||||
-- "ValveBiped.Bip01_R_Upperarm",
|
||||
-- "ValveBiped.Bip01_R_Forearm",
|
||||
-- "ValveBiped.Bip01_R_Hand",
|
||||
-- "ValveBiped.Bip01_L_Upperarm",
|
||||
-- "ValveBiped.Bip01_L_Forearm",
|
||||
-- "ValveBiped.Bip01_L_Hand",
|
||||
"ValveBiped.Bip01_R_Thigh",
|
||||
"ValveBiped.Bip01_R_Calf",
|
||||
"ValveBiped.Bip01_R_Foot",
|
||||
"ValveBiped.Bip01_L_Thigh",
|
||||
"ValveBiped.Bip01_L_Calf",
|
||||
"ValveBiped.Bip01_L_Foot"
|
||||
}
|
||||
|
||||
-- Список костей, к которым БУДЕТ применяться эффект дергания.
|
||||
-- Отфильтровано из MODULE.BoneList, чтобы исключить таз и другие нежелательные кости.
|
||||
local twitchable_bone_names = {
|
||||
"ValveBiped.Bip01_R_Thigh",
|
||||
"ValveBiped.Bip01_R_Calf",
|
||||
"ValveBiped.Bip01_R_Foot",
|
||||
"ValveBiped.Bip01_L_Thigh",
|
||||
"ValveBiped.Bip01_L_Calf",
|
||||
"ValveBiped.Bip01_L_Foot"
|
||||
}
|
||||
|
||||
-- Локальные копии функций для оптимизации
|
||||
local math_Clamp = math.Clamp
|
||||
local math_Rand = math.Rand
|
||||
local math_random = math.random
|
||||
local table_Random = table.Random -- Используем table.Random для выбора случайного элемента
|
||||
local IsValid = IsValid -- Локальная копия IsValid
|
||||
local CurTime = CurTime -- Локальная копия CurTime
|
||||
local timer_Create = timer.Create
|
||||
local timer_Remove = timer.Remove
|
||||
local VectorRand = VectorRand
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Конвары (Настройки)
|
||||
---------------------------------------------------------------------------]]
|
||||
-- Подергивание
|
||||
local cv_twitch_enabled = CreateConVar("fedhoria_falling_twitch_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить дергание для падающих ног")
|
||||
local cv_twitch_interval_min = CreateConVar("fedhoria_falling_twitch_interval_min", "3", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Минимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_interval_max = CreateConVar("fedhoria_falling_twitch_interval_max", "6", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Максимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_force_min = CreateConVar("fedhoria_falling_twitch_min", "100", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. угловая скорость для дергания")
|
||||
local cv_twitch_force_max = CreateConVar("fedhoria_falling_twitch_max", "250", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Макс. угловая скорость для дергания")
|
||||
|
||||
-- Перекат
|
||||
local cv_anim_roll_enabled = CreateConVar("fedhoria_falling_anim_roll_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить анимацию при ударе о землю")
|
||||
local cv_anim_roll_min_delay = CreateConVar("fedhoria_falling_anim_roll_min_delay", "0.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. задержка перед возможной анимацией после удара (сек)")
|
||||
local cv_anim_roll_duration = CreateConVar("fedhoria_falling_anim_roll_duration", "3.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Продолжительность принудительной анимации (сек)")
|
||||
local cv_anim_roll_impact_threshold = CreateConVar("fedhoria_falling_anim_roll_impact_threshold", "300", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. скорость удара для запуска анимации")
|
||||
local cv_anim_roll_playback_rate = CreateConVar("fedhoria_falling_anim_roll_playback_rate", "3.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Скорость воспроизведения анимации")
|
||||
|
||||
-- Время жизни объекта после остановки
|
||||
local die_time = CreateConVar("fedhoria_dietime", 5, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
|
||||
local die_time_variation = CreateConVar("fedhoria_dietime_variation", 3, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Функция дергания
|
||||
Применяет случайный импульс к одной из костей ног.
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:DoTwitch()
|
||||
if not IsValid(self) or not IsValid(self:GetTarget()) then return end
|
||||
if not cv_twitch_enabled:GetBool() then return end
|
||||
|
||||
local phys = self:GetPhysicsObject()
|
||||
if not IsValid(phys) then return end
|
||||
|
||||
if #twitchable_bone_names == 0 then return end
|
||||
|
||||
local random_bone_name = table_Random(twitchable_bone_names)
|
||||
if not random_bone_name then return end
|
||||
|
||||
-- Получаем индекс кости по имени
|
||||
local bone_index = self:LookupBone(random_bone_name)
|
||||
if not bone_index then
|
||||
-- print("[Fedhoria Twitch] Bone index not found for name:", random_bone_name)
|
||||
return
|
||||
end
|
||||
|
||||
-- Получаем физический объект кости по ее индексу
|
||||
local phys_bone = self:GetPhysicsObjectNum(bone_index) -- Используем GetPhysicsObjectNum для получения физ. объекта конкретной кости
|
||||
|
||||
if IsValid(phys_bone) then
|
||||
local force = math_Rand(cv_twitch_force_min:GetFloat(), cv_twitch_force_max:GetFloat())
|
||||
local torque = VectorRand():GetNormal() * force
|
||||
phys_bone:AddAngleVelocity(torque)
|
||||
-- print("[Fedhoria Twitch] Applied torque", torque, "to bone", random_bone_name, "(index:", bone_index, ")")
|
||||
-- else
|
||||
-- print("[Fedhoria Twitch] Could not find a valid physics bone for index:", bone_index, "name:", random_bone_name)
|
||||
end
|
||||
|
||||
self:ScheduleNextTwitch()
|
||||
end
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Планирование следующего вызова DoTwitch
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:ScheduleNextTwitch()
|
||||
if not IsValid(self) then return end
|
||||
if not cv_twitch_enabled:GetBool() then return end
|
||||
|
||||
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
|
||||
timer_Remove(timer_name)
|
||||
|
||||
local delay = math_Rand(cv_twitch_interval_min:GetFloat(), cv_twitch_interval_max:GetFloat())
|
||||
|
||||
timer_Create(timer_name, delay, 1, function()
|
||||
if not IsValid(self) then return end
|
||||
self:DoTwitch()
|
||||
end)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Запуск анимации переката
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:StartAnimationRoll()
|
||||
if not cv_anim_roll_enabled:GetBool() then return end
|
||||
|
||||
local target = self:GetTarget()
|
||||
if not IsValid(target) then return end
|
||||
|
||||
local cur_time = CurTime()
|
||||
self.AnimationRollEndTime = cur_time + cv_anim_roll_duration:GetFloat()
|
||||
|
||||
local seq = self:LookupSequence("Choked_Barnacle")
|
||||
if seq and seq > 0 then
|
||||
self:ResetSequence(seq)
|
||||
self:SetPlaybackRate(cv_anim_roll_playback_rate:GetFloat())
|
||||
else
|
||||
self.AnimationRollEndTime = 0
|
||||
return
|
||||
end
|
||||
|
||||
--self.StartDie = nil
|
||||
end
|
||||
|
||||
function MODULE:Init()
|
||||
local seq = self:LookupSequence("Choked_Barnacle")
|
||||
if seq then self:ResetSequence(seq) end
|
||||
self:SetPlaybackRate(1)
|
||||
self.LastCollideTime = 0
|
||||
self.LastGroundCollideTime = 0
|
||||
self.StartDie = nil
|
||||
self.AnimationRollEndTime = 0
|
||||
|
||||
-- Добавляем демпфирование для всех костей
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
local bone_count = phys:GetBoneCount()
|
||||
for bone_index = 0, bone_count - 1 do
|
||||
local phys_bone = phys:GetBone(bone_index)
|
||||
if IsValid(phys_bone) then
|
||||
phys_bone:SetDamping(0.8, 0.9) -- Линейное и угловое демпфирование
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- запускаем систему дергания
|
||||
self:ScheduleNextTwitch()
|
||||
end
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Think-хук
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:Think()
|
||||
-- Место для дополнительной логики
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Хук столкновения физики (ИСПРАВЛЕН)
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:PhysicsCollide(data, phys)
|
||||
-- Проверяем валидность данных столкновения и наличие HitNormal
|
||||
if not data or not data.HitNormal then
|
||||
-- print("[Fedhoria Collide] Invalid collision data received.")
|
||||
return
|
||||
end
|
||||
|
||||
if data.HitEntity == self then return end
|
||||
|
||||
local cur_time = CurTime()
|
||||
self.LastCollideTime = cur_time
|
||||
|
||||
-- Проверка на столкновение с землей (нормаль смотрит вверх)
|
||||
if data.HitNormal.z > 0.7 then
|
||||
local impact_speed = data.Speed
|
||||
|
||||
-- print("[Fedhoria Collide] Ground collision detected. Speed:", impact_speed)
|
||||
|
||||
if cv_anim_roll_enabled:GetBool() and
|
||||
impact_speed > cv_anim_roll_impact_threshold:GetFloat() and
|
||||
cur_time > self.AnimationRollEndTime and
|
||||
cur_time > self.LastGroundCollideTime + cv_anim_roll_min_delay:GetFloat() then
|
||||
self:StartAnimationRoll()
|
||||
end
|
||||
|
||||
self.LastGroundCollideTime = cur_time
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Хук симуляции физики (ИСПРАВЛЕН)
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:PhysicsSimulate(phys, dt)
|
||||
local cur_time = CurTime()
|
||||
local target = self:GetTarget()
|
||||
if not IsValid(target) then self:Remove(); return false end
|
||||
|
||||
-- проверка на активную анимацию
|
||||
if cur_time < self.AnimationRollEndTime then
|
||||
--self.StartDie = nil -- сбрасываем таймер "смерти"
|
||||
return true -- используем стандартную физику
|
||||
end
|
||||
|
||||
-- логика для таймера исчезновения
|
||||
local f = 1 -- множитель силы (по умолчанию 1)
|
||||
if self.StartDie then
|
||||
f = math_Clamp(1 - (cur_time - self.StartDie) / die_time:GetFloat(), 0, 1)
|
||||
end
|
||||
|
||||
-- поддержка для RagMod
|
||||
if ragmod and ragmod:IsRagmodRagdoll(target) then
|
||||
local owner = target:GetOwningPlayer()
|
||||
f = (IsValid(owner) and owner:Alive()) and 1 or 0
|
||||
--self.StartDie = nil
|
||||
|
||||
if f <= 0 then self.AnimationRollEndTime = 0 end
|
||||
end
|
||||
|
||||
-- удаление, если истекло время жизни
|
||||
if (f <= 0) then
|
||||
self:Remove()
|
||||
return false
|
||||
end
|
||||
|
||||
local phys_bone_id = phys:GetID()
|
||||
|
||||
-- основная логика для корневой кости
|
||||
if (phys_bone_id == 0) then
|
||||
-- логика для воды
|
||||
if target:WaterLevel() > 0 then
|
||||
self.AnimationRollEndTime = 0
|
||||
local seq_choked = self:LookupSequence("Choked_Barnacle")
|
||||
if seq_choked then self:ResetSequence(seq_choked) end
|
||||
return false
|
||||
end
|
||||
|
||||
-- логика для анимации падения
|
||||
local vel = phys:GetVelocity()
|
||||
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")
|
||||
if seq_idle then
|
||||
self:ResetSequence(seq_idle)
|
||||
self:SetPlaybackRate(pbr)
|
||||
end
|
||||
end
|
||||
|
||||
-- фикс: безопасная работа с векторами
|
||||
local pos = phys:GetPos()
|
||||
if not pos then return true, f end -- если pos невалиден, ничего не делаем
|
||||
|
||||
self.last_pos = self.last_pos or pos
|
||||
if not self.last_pos then self.last_pos = pos end
|
||||
|
||||
local offset_sqr = (pos - self.last_pos):LengthSqr()
|
||||
self.last_pos = pos
|
||||
|
||||
if (offset_sqr < (10*10 * dt*dt) and not (ragmod and ragmod:IsRagmodRagdoll(target))) then
|
||||
self.StartDie = self.StartDie or cur_time
|
||||
else
|
||||
--self.StartDie = nil
|
||||
end
|
||||
|
||||
-- коррекция физики после столкновения
|
||||
local delta_collide = cur_time - self.LastCollideTime
|
||||
if (delta_collide < 0.2) then
|
||||
return true, 1 * f
|
||||
elseif (delta_collide < 1.2) then
|
||||
return true, (1 - (delta_collide - 0.2)) * f
|
||||
end
|
||||
|
||||
return true, f
|
||||
end
|
||||
|
||||
-- логика для других костей
|
||||
local delta_collide = cur_time - self.LastCollideTime
|
||||
if (delta_collide < 0.2) then
|
||||
return true, 1 * f
|
||||
elseif (delta_collide < 1.2) then
|
||||
return true, (1 - (delta_collide - 0.2)) * f
|
||||
end
|
||||
return true, f
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Хук удаления сущности
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:OnRemove()
|
||||
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
|
||||
timer_Remove(timer_name)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Регистрация модуля (если это часть системы модулей)
|
||||
---------------------------------------------------------------------------]]
|
||||
-- Пример регистрации:
|
||||
-- falling_legs_manager:RegisterModule("FedhoriaFallingLegs", MODULE)
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
-- здесь был BRAT.
|
||||
--здесь был BRAT.
|
||||
|
||||
MODULE = {}
|
||||
|
||||
|
|
@ -21,27 +21,26 @@ local table_HasValue = table.HasValue
|
|||
|
||||
local hand_offset = Vector(2, 0, 0)
|
||||
|
||||
-- Twitching
|
||||
local cv_twitch_enabled = CreateConVar("zcnpci_falling_twitch_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить дергание для падающих ног")
|
||||
local cv_twitch_interval_min = CreateConVar("zcnpci_falling_twitch_interval_min", "3", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Минимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_interval_max = CreateConVar("zcnpci_falling_twitch_interval_max", "6", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Максимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_force_min = CreateConVar("zcnpci_falling_twitch_min", "100", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. угловая скорость для дергания")
|
||||
local cv_twitch_force_max = CreateConVar("zcnpci_falling_twitch_max", "250", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Макс. угловая скорость для дергания")
|
||||
--twtiching
|
||||
local cv_twitch_enabled = CreateConVar("fedhoria_falling_twitch_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить дергание для падающих торсов")
|
||||
local cv_twitch_interval_min = CreateConVar("fedhoria_falling_twitch_interval_min", "3", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Минимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_interval_max = CreateConVar("fedhoria_falling_twitch_interval_max", "6", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Максимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_force_min = CreateConVar("fedhoria_falling_twitch_min", "100", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. угловая скорость для дергания")
|
||||
local cv_twitch_force_max = CreateConVar("fedhoria_falling_twitch_max", "250", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Макс. угловая скорость для дергания")
|
||||
local ignore_bone_indices_twitch = {0} -- игнор нахуй
|
||||
|
||||
-- Roll
|
||||
local cv_anim_roll_enabled = CreateConVar("zcnpci_falling_anim_roll_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить анимацию при ударе о землю")
|
||||
local cv_anim_roll_min_delay = CreateConVar("zcnpci_falling_anim_roll_min_delay", "0.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. задержка перед возможной анимацией после удара (сек)")
|
||||
local cv_anim_roll_duration = CreateConVar("zcnpci_falling_anim_roll_duration", "3.5", {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_enabled = CreateConVar("fedhoria_falling_anim_roll_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить анимацию 'idleonfire' при ударе о землю")
|
||||
local cv_anim_roll_min_delay = CreateConVar("fedhoria_falling_anim_roll_min_delay", "0.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. задержка перед возможной анимацией после удара (сек)")
|
||||
local cv_anim_roll_duration = CreateConVar("fedhoria_falling_anim_roll_duration", "3.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Продолжительность принудительной анимации 'idleonfire' (сек)")
|
||||
local cv_anim_roll_impact_threshold = CreateConVar("fedhoria_falling_anim_roll_impact_threshold", "300", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. скорость удара для запуска анимации")
|
||||
local cv_anim_roll_playback_rate = CreateConVar("fedhoria_falling_anim_roll_playback_rate", "3.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Скорость воспроизведения анимации 'idleonfire'")
|
||||
|
||||
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 die_time = CreateConVar("fedhoria_dietime", 5, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
|
||||
local die_time_variation = CreateConVar("fedhoria_dietime_variation", 3, {FCVAR_ARCHIVE, FCVAR_REPLICATED})
|
||||
|
||||
-- Writhing
|
||||
local writhing_strength = CreateConVar("zcnpci_writhing_strength", "1.0", {FCVAR_ARCHIVE, FCVAR_REPLICATED})
|
||||
|
||||
-- Do it!
|
||||
-- адно дергание
|
||||
function MODULE:DoTwitch()
|
||||
|
||||
if not IsValid(self) or not IsValid(self:GetTarget()) then return end
|
||||
|
|
@ -120,20 +119,17 @@ function MODULE:StartAnimationRoll()
|
|||
return
|
||||
end
|
||||
|
||||
target.StartDie = nil -- блять я заебался с этой хуйней
|
||||
self.StartDie = nil -- блять я заебался с этой хуйней
|
||||
-- print("[Fedhoria AnimRoll] StartAnimationRoll initiated. Speed:", cv_anim_roll_playback_rate:GetFloat(), "Duration:", cv_anim_roll_duration:GetFloat(), "End Time:", self.AnimationRollEndTime) -- Debug
|
||||
end
|
||||
|
||||
function MODULE:Init()
|
||||
local seq = self:LookupSequence("idleonfire")
|
||||
if seq then self:ResetSequence(seq) end
|
||||
|
||||
local target = self:GetTarget()
|
||||
|
||||
self:SetPlaybackRate(1)
|
||||
self.LastCollideTime = 0
|
||||
self.LastGroundCollideTime = 0
|
||||
target.StartDie = nil
|
||||
self.StartDie = nil
|
||||
self.AnimationRollEndTime = 0
|
||||
|
||||
-- идте нахуй с этой хуйней
|
||||
|
|
@ -176,15 +172,12 @@ 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
|
||||
-- print("[Fedhoria Sim] In Animation Roll. Time left:", self.AnimationRollEndTime - cur_time) -- Debug
|
||||
-- target.StartDie = nil -- Сбрасываем таймер смерти
|
||||
-- self.StartDie = nil -- Сбрасываем таймер смерти
|
||||
|
||||
return true -- стандартная физика пошла нахуй, у меня по ней 2
|
||||
-- return false -- если физика нахуй идет
|
||||
|
|
@ -192,34 +185,29 @@ function MODULE:PhysicsSimulate(phys, dt)
|
|||
|
||||
-- --- логика идет нахуй
|
||||
|
||||
-- Force multiplier
|
||||
local f = target.organism.consciousness * writhing_strength:GetFloat()
|
||||
-- логика для таймера
|
||||
local f = 1 -- сила есть ума не надо (по умолчанию 1)
|
||||
if self.StartDie then
|
||||
f = math_Clamp(1 - (cur_time - self.StartDie) / die_time:GetFloat(), 0, 1)
|
||||
end
|
||||
|
||||
print(f)
|
||||
|
||||
-- ебаная логика для regmod
|
||||
if ragmod and ragmod:IsRagmodRagdoll(target) then
|
||||
local owner = target:GetOwningPlayer()
|
||||
-- владелец идот нахуй
|
||||
f = (IsValid(owner) and owner:Alive()) and 1 or 0
|
||||
target.StartDie = nil
|
||||
self.StartDie = nil
|
||||
|
||||
if f <= 0 then self.AnimationRollEndTime = 0 end
|
||||
end
|
||||
|
||||
if !target or !target.organism then
|
||||
--
|
||||
if (f <= 0) then
|
||||
-- print("[Fedhoria Sim] Removing entity (f <= 0)") -- Debug
|
||||
self:Remove()
|
||||
return false -- Cut the bullshit
|
||||
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.spine2 == 1) or
|
||||
(target.organism.spine3 == 1)
|
||||
) then
|
||||
return false
|
||||
return false -- Прекращаем хуйню
|
||||
end
|
||||
|
||||
local phys_bone_id = phys:GetID() -- айди кости
|
||||
|
|
@ -239,12 +227,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
|
||||
|
|
@ -262,10 +245,10 @@ function MODULE:PhysicsSimulate(phys, dt)
|
|||
local offset_sqr = (pos - self.last_pos):LengthSqr()
|
||||
self.last_pos = pos
|
||||
if (offset_sqr < (10*10 * dt*dt) and not (ragmod and ragmod:IsRagmodRagdoll(target))) then -- uменьшил порог неподвижности
|
||||
target.StartDie = target.StartDie or cur_time -- идите нахуй
|
||||
-- print("[Fedhoria Sim] Torso seems stationary. StartDie:", target.StartDie) -- Debug
|
||||
self.StartDie = self.StartDie or cur_time -- идите нахуй
|
||||
-- print("[Fedhoria Sim] Torso seems stationary. StartDie:", self.StartDie) -- Debug
|
||||
else
|
||||
-- target.StartDie = nil -- нахуй таймер
|
||||
-- self.StartDie = nil -- нахуй таймер
|
||||
-- print("[Fedhoria Sim] Torso moved. Resetting StartDie.") -- Debug
|
||||
end
|
||||
|
||||
|
|
@ -18,9 +18,9 @@ MODULE.BoneList =
|
|||
"ValveBiped.Bip01_L_Foot"
|
||||
}
|
||||
|
||||
local stumble_time = CreateConVar("zcnpci_stumble_time", 2, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local grab_chance = CreateConVar("zcnpci_woundgrab_chance", 0.9, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local grab_time = CreateConVar("zcnpci_woundgrab_time", 5, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local stumble_time = CreateConVar("fedhoria_stumble_time", 2, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local grab_chance = CreateConVar("fedhoria_woundgrab_chance", 0.9, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local grab_time = CreateConVar("fedhoria_woundgrab_time", 5, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
|
||||
local math_atan2 = math.atan2
|
||||
local math_pi = math.pi
|
||||
|
|
@ -223,8 +223,8 @@ function MODULE:Init(phys_bone, lpos)
|
|||
end
|
||||
|
||||
local function DoFallingAnim(ent)
|
||||
local mod1 = zcnpci.StartModule(ent, "falling_legs")
|
||||
local mod2 = zcnpci.StartModule(ent, "falling_torso")
|
||||
local mod1 = fedhoria.StartModule(ent, "falling_legs")
|
||||
local mod2 = fedhoria.StartModule(ent, "falling_torso")
|
||||
end
|
||||
|
||||
function MODULE:OnRemove()
|
||||
|
|
@ -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
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
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.")
|
||||
|
||||
pnl:CheckBox("Allow modded NPCs", "zcnpci_allow_modded_npcs")
|
||||
pnl:ControlHelp("Enable or disables applying custom health system to modded NPCs.")
|
||||
|
||||
pnl:CheckBox("Auto enable for all humanoid NPCs", "zcnpci_auto_enable_humanoid_npcs")
|
||||
pnl:ControlHelp("If enabled, NPCs will automatically have the health system applied to them if they have a standard humanoid skeleton.")
|
||||
|
||||
pnl:Help("List of enabled modded NPC classes")
|
||||
|
||||
local text = vgui.Create("DTextEntry")
|
||||
text:SetMultiline(true)
|
||||
text:SetTall(200)
|
||||
text:SetValue(modded_npc_whitelist:GetString())
|
||||
|
||||
text.OnChange = function (self)
|
||||
RunConsoleCommand("zcnpci_set_modded_npc_whitelist", text:GetValue())
|
||||
end
|
||||
|
||||
pnl:AddItem(text)
|
||||
|
||||
pnl:Help("A list of NPC classes, seperated by spaces or line breaks. Can only be set by a superadmin or server operator.")
|
||||
end
|
||||
|
||||
local function PopulateRagdollSBXToolMenu(pnl)
|
||||
pnl:NumSlider("Stumble time", "zcnpci_stumble_time", 0, 10, 3)
|
||||
pnl:ControlHelp("How long the ragdoll should try to stumble for.")
|
||||
|
||||
pnl:NumSlider("Wound grab chance", "zcnpci_woundgrab_chance", 0, 1, 3)
|
||||
pnl:ControlHelp("The chance the ragdoll will grab it's wound when shot.")
|
||||
|
||||
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)
|
||||
pnl:NumSlider("NPC ticks per second", "zcnpci_tps", 1, 66, 0)
|
||||
pnl:ControlHelp("How many times NPCs should be processed per second. Will make them more responsive at the cost of performance.")
|
||||
|
||||
pnl:Help(" ")
|
||||
|
||||
pnl:Help(" ")
|
||||
|
||||
pnl:NumSlider("Activation range", "zcnpci_active_range", 0, 32768, 0)
|
||||
pnl:ControlHelp("How close the ragdoll has to be to a player to live when downed by direct damage. Enemies downed by other causes are unaffected.")
|
||||
|
||||
pnl:CheckBox("Always activate on player kill", "zcnpci_always_active_on_player_kill")
|
||||
pnl:ControlHelp("If on, all player-damaged ragdolls will be activated regardless of other factors.")
|
||||
|
||||
pnl:Help(" ")
|
||||
|
||||
pnl:Help(" ")
|
||||
|
||||
pnl:CheckBox("Enable automatic corpse removal", "zcnpci_enable_corpse_removal")
|
||||
pnl:ControlHelp("Enable or disable the removal of dead corpses.")
|
||||
|
||||
pnl:NumSlider("Corpse time between loops", "zcnpci_corpse_loop_time", 1, 66, 0)
|
||||
pnl:ControlHelp("How long should we wait in between checks?")
|
||||
|
||||
pnl:NumSlider("Max corpses", "zcnpci_max_corpses", -1, 64, 0)
|
||||
pnl:ControlHelp("The maximum amount of dead corpses that are allowed before one is removed. Set to -1 to disable")
|
||||
|
||||
pnl:NumSlider("Max time", "zcnpci_max_corpse_time", -1, 600, 2)
|
||||
pnl:ControlHelp("The maximum amount of time before a corpse is removed. Set to -1 to disable")
|
||||
|
||||
pnl:NumSlider("Max distance between player", "zcnpci_max_corpse_distance", -1, 32768, 2)
|
||||
pnl:ControlHelp("If the corpse is further than this distance, it will be removed. Set to -1 to disable")
|
||||
|
||||
pnl:NumSlider("Min distance between player", "zcnpci_min_corpse_distance", 0, 32768, 2)
|
||||
pnl:ControlHelp("If the corpse is closer than this distance, it will never be cleared.")
|
||||
|
||||
pnl:CheckBox("Treat crippled NPCs as dead", "zcnpci_treat_crippled_as_dead")
|
||||
pnl:ControlHelp("If on, bodies with amputations or both legs broken will be clearable.")
|
||||
|
||||
pnl:CheckBox("Treat unconscious NPCs as dead", "zcnpci_treat_unconscious_as_dead")
|
||||
pnl:ControlHelp("If on, bodies that are unconscious will be clearable.")
|
||||
|
||||
pnl:CheckBox("Treat near-death NPCs as dead", "zcnpci_treat_near_death_as_dead")
|
||||
pnl:ControlHelp("If on, bodies near death (severe blood loss, comas, etc.) will be clearable.")
|
||||
end
|
||||
|
||||
local function PopulateMainSBXToolMenu(pnl)
|
||||
pnl:CheckBox("Enabled", "zcnpci_enabled")
|
||||
pnl:ControlHelp("Enable or disable the addon.")
|
||||
|
||||
pnl:Help("This addon was developed by ToasterPanic.")
|
||||
|
||||
pnl:Help("It is based on Kazarei's Euphoria, which in turn is a fork of Fedhoria by Rama. Their work powers the ragdoll movement.")
|
||||
end
|
||||
|
||||
|
||||
if engine.ActiveGamemode() == "sandbox" then
|
||||
hook.Add("AddToolMenuCategories", "ZCNPCICategory", function()
|
||||
spawnmenu.AddToolCategory("Utilities", "zcnpci", "Z-City NPCi")
|
||||
end)
|
||||
|
||||
hook.Add("PopulateToolMenu", "ZCNPCIMenuSettings", function()
|
||||
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "ModdedNPCSettings", "Custom NPCs", "", "", function(pnl)
|
||||
pnl:ClearControls()
|
||||
PopulateCustomNPCSBXToolMenu(pnl)
|
||||
end)
|
||||
|
||||
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "PerformanceSettings", "Performance", "", "", function(pnl)
|
||||
pnl:ClearControls()
|
||||
PopulatePerformanceSBXToolMenu(pnl)
|
||||
end)
|
||||
|
||||
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "RagdollSettings", "Ragdoll", "", "", function(pnl)
|
||||
pnl:ClearControls()
|
||||
PopulateRagdollSBXToolMenu(pnl)
|
||||
end)
|
||||
|
||||
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "AISettings", "AI", "", "", function(pnl)
|
||||
pnl:ClearControls()
|
||||
PopulateAISBXToolMenu(pnl)
|
||||
end)
|
||||
|
||||
spawnmenu.AddToolMenuOption("Utilities", "zcnpci", "MainSettings", "Main", "", "", function(pnl)
|
||||
pnl:ClearControls()
|
||||
PopulateMainSBXToolMenu(pnl)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
|
@ -1 +0,0 @@
|
|||
include("zcnpci.lua")
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
430
lua/zcnpci.lua
430
lua/zcnpci.lua
|
|
@ -1,430 +0,0 @@
|
|||
include("zcnpci/modules.lua")
|
||||
|
||||
local enabled = CreateConVar("zcnpci_enabled", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local active_range = CreateConVar("zcnpci_active_range", 32768, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local always_active_on_player_kill = CreateConVar("zcnpci_always_active_on_player_kill", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local ticks_per_second = CreateConVar("zcnpci_tps", 20, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
|
||||
local remove_corpses = CreateConVar("zcnpci_enable_corpse_removal", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local corpse_loop_time = CreateConVar("zcnpci_corpse_loop_time", 2.0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local max_corpses = CreateConVar("zcnpci_max_corpses", 8, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local max_corpse_time = CreateConVar("zcnpci_max_corpse_time", 120, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local min_corpse_distance = CreateConVar("zcnpci_min_corpse_distance", 500, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local max_corpse_distance = CreateConVar("zcnpci_max_corpse_distance", 2500, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local no_corpse_removal_near_player = CreateConVar("zcnpci_disable_corpse_removal_near_player", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local treat_crippled_as_dead = CreateConVar("zcnpci_treat_crippled_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local treat_unconscious_as_dead = CreateConVar("zcnpci_treat_unconscious_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local treat_near_death_as_dead = CreateConVar("zcnpci_treat_near_death_as_dead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
|
||||
local allow_extended_base_npcs = CreateConVar("zcnpci_allow_extended_base_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local allow_modded_npcs = CreateConVar("zcnpci_allow_modded_npcs", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local auto_enable_humanoid_npcs = CreateConVar("zcnpci_auto_enable_humanoid_npcs", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_REPLICATED))
|
||||
local modded_npc_whitelist = CreateConVar("zcnpci_modded_npc_whitelist", "npc_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
|
||||
local set_modded_npc_whitelist = concommand.Add("zcnpci_set_modded_npc_whitelist", function(ply, cmd, args)
|
||||
if !IsValid(ply) or !args[1] then return end
|
||||
|
||||
if !ply:IsSuperAdmin() then return end
|
||||
|
||||
modded_npc_whitelist:SetString(args[1])
|
||||
end)
|
||||
|
||||
local last_dmgpos = {}
|
||||
local last_hitgroup = {}
|
||||
local last_attacker = {}
|
||||
|
||||
local npcs_to_fake = {}
|
||||
|
||||
-- These NPCs do not have organisms by default, despite being humanoid characters built into the game.
|
||||
local base_npc_whitelist = {
|
||||
"npc_kleiner",
|
||||
"npc_breen",
|
||||
"npc_barney",
|
||||
"npc_alyx",
|
||||
"npc_eli",
|
||||
"npc_gman",
|
||||
"npc_magnusson",
|
||||
"npc_mossman",
|
||||
"npc_odessa",
|
||||
"npc_monk"
|
||||
}
|
||||
|
||||
local corpses = {}
|
||||
|
||||
local 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
|
||||
|
||||
if !enabled:GetBool() then return end
|
||||
local dmgpos = last_dmgpos[ent]
|
||||
|
||||
local active_by_default = false
|
||||
|
||||
if always_active_on_player_kill:GetBool() then
|
||||
if last_attacker[ent] and last_attacker[ent]:IsPlayer() then active_by_default = true end
|
||||
end
|
||||
|
||||
if !active_by_default then
|
||||
local entity_position = ragdoll:GetPos()
|
||||
local in_range = false
|
||||
local players = player.GetAll()
|
||||
|
||||
for i, loop_player in pairs(players) do
|
||||
local player_position = loop_player:GetPos()
|
||||
|
||||
local distance = entity_position:Distance(player_position)
|
||||
|
||||
if distance <= active_range:GetFloat() then
|
||||
in_range = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if !in_range then return end
|
||||
end
|
||||
|
||||
local phys_bone, lpos
|
||||
|
||||
if dmgpos then
|
||||
phys_bone = ragdoll:GetClosestPhysBone(dmgpos)
|
||||
if phys_bone then
|
||||
local phys = ragdoll:GetPhysicsObjectNum(phys_bone)
|
||||
lpos = phys:WorldToLocal(dmgpos)
|
||||
end
|
||||
end
|
||||
|
||||
ragdoll.class_in_previous_life = ent:GetClass()
|
||||
|
||||
if !ragdoll.organism then
|
||||
ragdoll.inventory = {}
|
||||
ragdoll.inventory.Weapons = {}
|
||||
|
||||
hg.organism.Add(ragdoll)
|
||||
table.Merge(ragdoll.organism, ent.organism)
|
||||
|
||||
hook.Run("RagdollDeath", ent, ragdoll)
|
||||
|
||||
ragdoll.organism.owner = ragdoll
|
||||
ragdoll:CallOnRemove("organism", hg.organism.Remove, ragdoll)
|
||||
ragdoll.organism.owner.fullsend = true
|
||||
hg.send_bareinfo(ragdoll.organism)
|
||||
|
||||
ent.organism = nil
|
||||
end
|
||||
|
||||
if ent.npcfakeknockback then
|
||||
if dmgpos and (phys_bone != -1) then
|
||||
local phys = ragdoll:GetPhysicsObjectNum(phys_bone)
|
||||
phys:SetVelocity(ent.npcfakeknockback)
|
||||
else
|
||||
ragdoll:GetPhysicsObject():SetVelocity(ent.npcfakeknockback)
|
||||
end
|
||||
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)
|
||||
|
||||
zcnpci.StartModule(ragdoll, "stumble_legs", phys_bone, lpos)
|
||||
last_dmgpos[ent] = nil
|
||||
end)
|
||||
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
|
||||
|
||||
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST + DMG_CLUB + DMG_SLASH + DMG_GENERIC) then
|
||||
-- Reset ragdoll get up timer -- don't want someone getting up mid-curbstomp
|
||||
ent.StartDie = CurTime()
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
last_dmgpos[ent] = dmginfo:GetDamagePosition()
|
||||
last_attacker[ent] = dmginfo:GetAttacker()
|
||||
|
||||
if dmginfo:IsDamageType(DMG_BULLET + DMG_BUCKSHOT + DMG_BLAST) then
|
||||
if dmginfo:GetDamage() > 3 then
|
||||
table.insert(npcs_to_fake, ent)
|
||||
end
|
||||
elseif dmginfo:IsDamageType(DMG_CLUB + DMG_SLASH) then
|
||||
local 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
|
||||
|
||||
-- Kicks should knock NPCs down
|
||||
if IsValid(dmginfo:GetInflictor()) and fists and attacker.InLegKick and ((attacker.InLegKick + 0.1) > CurTime()) then
|
||||
table.insert(npcs_to_fake, ent)
|
||||
|
||||
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)
|
||||
|
||||
hook.Add("ScaleNPCDamage", "zcnpci", function(npc, hitgroup, dmginfo)
|
||||
if !IsValid(npc) then return end
|
||||
last_hitgroup[npc] = hitgroup
|
||||
end)
|
||||
|
||||
hook.Add("Tick", "zcnpci", function()
|
||||
local do_corpse_loop = (CurTime() - last_corpse_tick) > corpse_loop_time:GetFloat()
|
||||
local do_general_loop = (CurTime() - last_tick) > (1 / ticks_per_second:GetInt())
|
||||
|
||||
if do_general_loop then
|
||||
last_tick = CurTime()
|
||||
|
||||
if do_corpse_loop then
|
||||
last_corpse_tick = CurTime()
|
||||
|
||||
for i, ent in pairs(corpses) do
|
||||
if !IsValid(ent) then
|
||||
table.RemoveByValue(corpses, ent)
|
||||
end
|
||||
end
|
||||
|
||||
if (max_corpses:GetFloat() != -1) and (#corpses > max_corpses:GetFloat()) then
|
||||
if IsValid(corpses[1]) then
|
||||
corpses[1]:Remove()
|
||||
end
|
||||
|
||||
table.remove(corpses, 1)
|
||||
end
|
||||
end
|
||||
for i, ent in pairs(ents.GetAll()) do
|
||||
if !IsValid(ent) then continue end
|
||||
if !ent.organism then continue end
|
||||
|
||||
if ent:IsNPC() then
|
||||
-- Knock them down if something is off
|
||||
if (
|
||||
((ent.organism.lleg >= 1) and (ent.organism.rleg >= 1)) or
|
||||
ent.organism.llegamputated or
|
||||
ent.organism.rlegamputated or
|
||||
(ent.organism.consciousness <= 0.3) or
|
||||
(ent.organism.pain > 90) or
|
||||
ent:IsOnFire() or
|
||||
ent.neednpcfake or
|
||||
debug_ragdoll_all:GetBool()
|
||||
) then
|
||||
table.insert(npcs_to_fake, ent)
|
||||
end
|
||||
elseif do_corpse_loop and remove_corpses and ent.is_npc_corpse then
|
||||
local clearable = false
|
||||
|
||||
if !ent.organism.alive then clearable = true end
|
||||
if treat_crippled_as_dead:GetBool() and (
|
||||
ent.organism.llegamputated or ent.organism.rlegamputated or ent.organism.larmamputated or ent.organism.rarmamputated or
|
||||
((ent.organism.lleg >= 1) and (ent.organism.rleg >= 1))
|
||||
) then clearable = true end
|
||||
if treat_near_death_as_dead:GetBool() and (
|
||||
ent.organism.heartstop or
|
||||
(ent.organism.brain > 0.6) or
|
||||
!ent.organism.lungsfunction or
|
||||
(ent.organism.blood < 3000) or
|
||||
(ent.organism.pulse < 10)
|
||||
) then clearable = true end
|
||||
if treat_unconscious_as_dead:GetBool() and (ent.organism.consciousness <= 0.3)
|
||||
then clearable = true end
|
||||
|
||||
if !clearable then continue end
|
||||
|
||||
-- Add to corpse list if not there already
|
||||
if !table.HasValue(corpses, ent) then
|
||||
table.insert(corpses, ent)
|
||||
end
|
||||
|
||||
-- Get nearest player for future checks
|
||||
local nearby_player
|
||||
local player_too_close
|
||||
local players = player.GetAll()
|
||||
|
||||
for i, loop_player in pairs(players) do
|
||||
local player_position = loop_player:GetPos()
|
||||
|
||||
local distance = ent:GetPos():Distance(player_position)
|
||||
|
||||
if (distance <= max_corpse_distance:GetFloat()) or (max_corpse_distance:GetFloat() == -1) then
|
||||
nearby_player = loop_player
|
||||
end
|
||||
|
||||
if (distance <= min_corpse_distance:GetFloat()) then
|
||||
player_too_close = loop_player
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if player_too_close and IsValid(player_too_close) then continue
|
||||
elseif (max_corpse_distance:GetFloat() != -1) and !nearby_player then
|
||||
ent:Remove()
|
||||
continue
|
||||
end
|
||||
|
||||
if max_corpse_time:GetFloat() != -1 then
|
||||
if !ent.corpse_timestamp then ent.corpse_timestamp = CurTime()
|
||||
elseif (CurTime() - ent.corpse_timestamp) > max_corpse_time:GetFloat() then
|
||||
ent:Remove()
|
||||
continue
|
||||
end
|
||||
end
|
||||
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
|
||||
|
||||
local damage_info = DamageInfo()
|
||||
damage_info:SetDamage(999999)
|
||||
damage_info:SetAttacker(ent)
|
||||
damage_info:SetDamageForce(Vector(0, 0, 0))
|
||||
|
||||
ent:TakeDamageInfo(damage_info)
|
||||
|
||||
table.remove(npcs_to_fake, 1)
|
||||
end
|
||||
end)
|
||||
|
||||
hook.Add("OnEntityCreated", "zcnpci", function(ent)
|
||||
if !IsValid(ent) then return end
|
||||
|
||||
local add_organism = false
|
||||
|
||||
if allow_modded_npcs:GetBool() then
|
||||
local modded_npc_whitelist_string = modded_npc_whitelist:GetString()
|
||||
modded_npc_whitelist_string = string.Replace(modded_npc_whitelist_string, "\n", " ")
|
||||
|
||||
local modded_npc_whitelist_table = string.Split(modded_npc_whitelist_string, " ")
|
||||
|
||||
if table.HasValue(modded_npc_whitelist_table, ent:GetClass()) then add_organism = true end
|
||||
end
|
||||
|
||||
if allow_extended_base_npcs:GetBool() and table.HasValue(base_npc_whitelist, ent:GetClass()) then add_organism = true end
|
||||
|
||||
if add_organism then
|
||||
hg.organism.Add(ent)
|
||||
hg.organism.Clear(ent.organism)
|
||||
ent.organism.fakePlayer = true
|
||||
end
|
||||
end)
|
||||
|
||||
local once = true
|
||||
|
||||
local PLAYER = FindMetaTable("Player")
|
||||
|
||||
local oldCreateRagdoll = PLAYER.CreateRagdoll
|
||||
|
||||
local dolls = {}
|
||||
|
||||
local function CreateRagdoll(self)
|
||||
SafeRemoveEntity(dolls[self])
|
||||
|
||||
local ragdoll = ents.Create("prop_ragdoll")
|
||||
ragdoll:SetModel(self:GetModel())
|
||||
ragdoll:SetPos(self:GetPos())
|
||||
ragdoll:SetAngles(self:GetAngles())
|
||||
ragdoll:Spawn()
|
||||
|
||||
ragdoll:SetSkin(self:GetSkin())
|
||||
|
||||
for i = 0, self:GetNumBodyGroups() - 1 do
|
||||
ragdoll:SetBodygroup(i, self:GetBodygroup(i))
|
||||
end
|
||||
|
||||
for i = 0, ragdoll:GetPhysicsObjectCount()-1 do
|
||||
local phys = ragdoll:GetPhysicsObjectNum(i)
|
||||
local bone = ragdoll:TranslatePhysBoneToBone(i)
|
||||
local matrix = self:GetBoneMatrix(bone)
|
||||
local pos, ang = matrix:GetTranslation(), matrix:GetAngles()--self:GetBonePosition(bone)
|
||||
phys:SetPos(pos)
|
||||
phys:SetAngles(ang)
|
||||
phys:SetVelocity(self:GetVelocity())
|
||||
end
|
||||
|
||||
self:SpectateEntity(ragdoll)
|
||||
self:Spectate(OBS_MODE_CHASE)
|
||||
|
||||
dolls[self] = ragdoll
|
||||
end
|
||||
|
||||
local oldGetRagdollEntity = PLAYER.GetRagdollEntity
|
||||
|
||||
local function GetRagdollEntity(self)
|
||||
return dolls[self] or NULL
|
||||
end
|
||||
|
|
@ -1,639 +0,0 @@
|
|||
--[[-------------------------------------------------------------------------
|
||||
A script for controlling the behavior of falling NPC body parts (e.g., legs).
|
||||
Includes flinching effects and roll animation upon impact.
|
||||
Version 2: Fixed GetGameData and HitNormal errors.
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
MODULE = {}
|
||||
|
||||
MODULE.Model = "models/police.mdl" -- The model to which the script is applied (likely not used directly in this code)
|
||||
|
||||
-- A list of bones that MAY be affected by physics or animation.
|
||||
-- Only the leg bones from this list will be used for pulling.
|
||||
MODULE.BoneList =
|
||||
{
|
||||
"ValveBiped.Bip01_Pelvis",
|
||||
-- "ValveBiped.Bip01_Spine2",
|
||||
-- "ValveBiped.Bip01_Head1",
|
||||
-- "ValveBiped.Bip01_R_Upperarm",
|
||||
-- "ValveBiped.Bip01_R_Forearm",
|
||||
-- "ValveBiped.Bip01_R_Hand",
|
||||
-- "ValveBiped.Bip01_L_Upperarm",
|
||||
-- "ValveBiped.Bip01_L_Forearm",
|
||||
-- "ValveBiped.Bip01_L_Hand",
|
||||
"ValveBiped.Bip01_R_Thigh",
|
||||
"ValveBiped.Bip01_R_Calf",
|
||||
"ValveBiped.Bip01_R_Foot",
|
||||
"ValveBiped.Bip01_L_Thigh",
|
||||
"ValveBiped.Bip01_L_Calf",
|
||||
"ValveBiped.Bip01_L_Foot"
|
||||
}
|
||||
|
||||
-- A list of bones to which the jiggle effect WILL be applied.
|
||||
-- Filtered from MODULE.BoneList to exclude the pelvis and other unwanted bones.
|
||||
local twitchable_bone_names = {
|
||||
"ValveBiped.Bip01_R_Thigh",
|
||||
"ValveBiped.Bip01_R_Calf",
|
||||
"ValveBiped.Bip01_R_Foot",
|
||||
"ValveBiped.Bip01_L_Thigh",
|
||||
"ValveBiped.Bip01_L_Calf",
|
||||
"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
|
||||
local math_random = math.random
|
||||
local table_Random = table.Random -- We use `table.Random` to select a random element.
|
||||
local IsValid = IsValid -- Local copy of IsValid
|
||||
local CurTime = CurTime -- Local copy of CurTime
|
||||
local timer_Create = timer.Create
|
||||
local timer_Remove = timer.Remove
|
||||
local VectorRand = VectorRand
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Convars (Settings)
|
||||
---------------------------------------------------------------------------]]
|
||||
-- Twitching
|
||||
local cv_twitch_enabled = CreateConVar("zcnpci_falling_twitch_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить дергание для падающих ног")
|
||||
local cv_twitch_interval_min = CreateConVar("zcnpci_falling_twitch_interval_min", "3", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Минимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_interval_max = CreateConVar("zcnpci_falling_twitch_interval_max", "6", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Максимальный интервал между дерганиями (сек)")
|
||||
local cv_twitch_force_min = CreateConVar("zcnpci_falling_twitch_min", "100", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. угловая скорость для дергания")
|
||||
local cv_twitch_force_max = CreateConVar("zcnpci_falling_twitch_max", "250", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Макс. угловая скорость для дергания")
|
||||
|
||||
-- Roll
|
||||
local cv_anim_roll_enabled = CreateConVar("zcnpci_falling_anim_roll_enabled", "1", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Включить анимацию при ударе о землю")
|
||||
local cv_anim_roll_min_delay = CreateConVar("zcnpci_falling_anim_roll_min_delay", "0.5", {FCVAR_ARCHIVE, FCVAR_REPLICATED}, "Мин. задержка перед возможной анимацией после удара (сек)")
|
||||
local cv_anim_roll_duration = CreateConVar("zcnpci_falling_anim_roll_duration", "3.5", {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}, "Скорость воспроизведения анимации")
|
||||
|
||||
-- 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
|
||||
Applies a random impulse to one of the leg bones.
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:DoTwitch()
|
||||
if not IsValid(self) or not IsValid(self:GetTarget()) then return end
|
||||
if not cv_twitch_enabled:GetBool() then return end
|
||||
|
||||
local phys = self:GetPhysicsObject()
|
||||
if not IsValid(phys) then return end
|
||||
|
||||
if #twitchable_bone_names == 0 then return end
|
||||
|
||||
local random_bone_name = table_Random(twitchable_bone_names)
|
||||
if not random_bone_name then return end
|
||||
|
||||
-- Get bone index by name
|
||||
local bone_index = self:LookupBone(random_bone_name)
|
||||
if not bone_index then
|
||||
-- print("[Fedhoria Twitch] Bone index not found for name:", random_bone_name)
|
||||
return
|
||||
end
|
||||
|
||||
-- We retrieve the physical bone object by its index.
|
||||
local phys_bone = self:GetPhysicsObjectNum(bone_index) -- We use GetPhysicsObjectNum to retrieve the physics object for a specific bone.
|
||||
|
||||
if IsValid(phys_bone) then
|
||||
local force = math_Rand(cv_twitch_force_min:GetFloat(), cv_twitch_force_max:GetFloat())
|
||||
local torque = VectorRand():GetNormal() * force
|
||||
phys_bone:AddAngleVelocity(torque)
|
||||
-- print("[Fedhoria Twitch] Applied torque", torque, "to bone", random_bone_name, "(index:", bone_index, ")")
|
||||
-- else
|
||||
-- print("[Fedhoria Twitch] Could not find a valid physics bone for index:", bone_index, "name:", random_bone_name)
|
||||
end
|
||||
|
||||
self:ScheduleNextTwitch()
|
||||
end
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Planning the next DoTwitch call
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:ScheduleNextTwitch()
|
||||
if not IsValid(self) then return end
|
||||
if not cv_twitch_enabled:GetBool() then return end
|
||||
|
||||
local timer_name = "Fedhoria_FallingLegs_Twitch_" .. self:EntIndex()
|
||||
timer_Remove(timer_name)
|
||||
|
||||
local delay = math_Rand(cv_twitch_interval_min:GetFloat(), cv_twitch_interval_max:GetFloat())
|
||||
|
||||
timer_Create(timer_name, delay, 1, function()
|
||||
if not IsValid(self) then return end
|
||||
self:DoTwitch()
|
||||
end)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Starting the roll animation
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:StartAnimationRoll()
|
||||
if not cv_anim_roll_enabled:GetBool() then return end
|
||||
|
||||
local target = self:GetTarget()
|
||||
if not IsValid(target) then return end
|
||||
|
||||
local cur_time = CurTime()
|
||||
self.AnimationRollEndTime = cur_time + cv_anim_roll_duration:GetFloat()
|
||||
|
||||
local seq = self:LookupSequence("Choked_Barnacle")
|
||||
if seq and seq > 0 then
|
||||
self:ResetSequence(seq)
|
||||
self:SetPlaybackRate(cv_anim_roll_playback_rate:GetFloat())
|
||||
else
|
||||
self.AnimationRollEndTime = 0
|
||||
return
|
||||
end
|
||||
|
||||
--target.StartDie = nil
|
||||
end
|
||||
|
||||
function MODULE:Init()
|
||||
local seq = self:LookupSequence("Choked_Barnacle")
|
||||
if seq then self:ResetSequence(seq) end
|
||||
|
||||
local target = self:GetTarget()
|
||||
|
||||
self:SetPlaybackRate(1)
|
||||
self.LastCollideTime = 0
|
||||
self.LastGroundCollideTime = 0
|
||||
target.StartDie = nil
|
||||
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()
|
||||
|
||||
-- Add damping to all bones.
|
||||
local phys = self:GetPhysicsObject()
|
||||
if IsValid(phys) then
|
||||
local bone_count = phys:GetBoneCount()
|
||||
for bone_index = 0, bone_count - 1 do
|
||||
local phys_bone = phys:GetBone(bone_index)
|
||||
if IsValid(phys_bone) then
|
||||
phys_bone:SetDamping(0.8, 0.9) -- Linear and Angular Damping
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- запускаем систему дергания
|
||||
self:ScheduleNextTwitch()
|
||||
end
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Think-hook
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
function MODULE:Think()
|
||||
--if (CurTime() - self.LastThink) < 1 then return end
|
||||
local target = self:GetTarget()
|
||||
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
|
||||
|
||||
phys:Wake()
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Physics Collision Hook (FIXED)
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:PhysicsCollide(data, phys)
|
||||
-- Checking the validity of collision data and the presence of HitNormal.
|
||||
if not data or not data.HitNormal then
|
||||
-- print("[Fedhoria Collide] Invalid collision data received.")
|
||||
return
|
||||
end
|
||||
|
||||
if data.HitEntity == self then return end
|
||||
|
||||
local cur_time = CurTime()
|
||||
self.LastCollideTime = cur_time
|
||||
|
||||
-- Ground collision check (normal points upward)
|
||||
if data.HitNormal.z > 0.7 then
|
||||
local impact_speed = data.Speed
|
||||
|
||||
-- print("[Fedhoria Collide] Ground collision detected. Speed:", impact_speed)
|
||||
|
||||
if cv_anim_roll_enabled:GetBool() and
|
||||
impact_speed > cv_anim_roll_impact_threshold:GetFloat() and
|
||||
cur_time > self.AnimationRollEndTime and
|
||||
cur_time > self.LastGroundCollideTime + cv_anim_roll_min_delay:GetFloat() then
|
||||
self:StartAnimationRoll()
|
||||
end
|
||||
|
||||
self.LastGroundCollideTime = cur_time
|
||||
end
|
||||
end
|
||||
|
||||
function MODULE:EntityTakeDamage(ent, dmginfo)
|
||||
print("I TOOK DAMAGE")
|
||||
end
|
||||
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Physics Simulation Hook (FIXED)
|
||||
---------------------------------------------------------------------------]]
|
||||
function MODULE:PhysicsSimulate(phys, dt)
|
||||
if !SERVER then return end
|
||||
|
||||
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())
|
||||
|
||||
target.is_npc_corpse = true
|
||||
|
||||
if !target.StartDie then target.StartDie = cur_time end
|
||||
|
||||
-- Check for active animation
|
||||
if cur_time < self.AnimationRollEndTime then
|
||||
--target.StartDie = nil -- Resetting the "death" timer
|
||||
return true -- We use standard physics.
|
||||
end
|
||||
|
||||
-- Force multiplier
|
||||
local f = target.organism.consciousness * writhing_strength:GetFloat()
|
||||
|
||||
local minimum_down_timer = 0
|
||||
if target.StartDie then
|
||||
minimum_down_timer = math_Clamp((cur_time - target.StartDie) / minimum_down_time:GetFloat(), 0, 1)
|
||||
end
|
||||
|
||||
if !target.organism then
|
||||
self:Remove()
|
||||
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
|
||||
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
|
||||
|
||||
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 (
|
||||
(!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())
|
||||
|
||||
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()
|
||||
|
||||
ent:SetNotSolid(true)
|
||||
ent:SetNPCState(NPC_STATE_NONE)
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
local phys_bone_id = phys:GetID()
|
||||
|
||||
-- Main logic for the root bone
|
||||
if (phys_bone_id == 0) then
|
||||
-- Water Logic
|
||||
if target:WaterLevel() > 0 then
|
||||
self.AnimationRollEndTime = 0
|
||||
local seq_choked = self:LookupSequence("Choked_Barnacle")
|
||||
if seq_choked then self:ResetSequence(seq_choked) end
|
||||
return false
|
||||
end
|
||||
|
||||
-- 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
|
||||
|
||||
if self:GetSequence() ~= self:LookupSequence("Choked_Barnacle") or self:GetPlaybackRate() ~= pbr then
|
||||
local seq_idle = self:LookupSequence("Choked_Barnacle")
|
||||
if seq_idle then
|
||||
self:ResetSequence(seq_idle)
|
||||
self:SetPlaybackRate(pbr)
|
||||
end
|
||||
end
|
||||
|
||||
-- Fix: Safe vector handling
|
||||
local pos = phys:GetPos()
|
||||
if not pos then return true, f end -- If `pos` is invalid, do nothing.
|
||||
|
||||
self.last_pos = self.last_pos or pos
|
||||
if not self.last_pos then self.last_pos = pos end
|
||||
|
||||
local offset_sqr = (pos - self.last_pos):LengthSqr()
|
||||
self.last_pos = pos
|
||||
|
||||
if offset_sqr < (10*10 * dt*dt) then
|
||||
target.StartDie = target.StartDie or cur_time
|
||||
end
|
||||
|
||||
-- Physics correction after collision
|
||||
local delta_collide = cur_time - self.LastCollideTime
|
||||
if (delta_collide < 0.2) then
|
||||
return true, 1 * f
|
||||
elseif (delta_collide < 1.2) then
|
||||
return true, (1 - (delta_collide - 0.2)) * f
|
||||
end
|
||||
|
||||
return true, f
|
||||
end
|
||||
|
||||
-- Logic for other bones
|
||||
local delta_collide = cur_time - self.LastCollideTime
|
||||
if (delta_collide < 0.2) then
|
||||
return true, 1 * f
|
||||
elseif (delta_collide < 1.2) then
|
||||
return true, (1 - (delta_collide - 0.2)) * f
|
||||
end
|
||||
return true, f
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Entity Deletion Hook
|
||||
---------------------------------------------------------------------------]]
|
||||
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
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Module registration (if it is part of a module system)
|
||||
---------------------------------------------------------------------------]]
|
||||
-- Registration Example:
|
||||
-- falling_legs_manager:RegisterModule("FedhoriaFallingLegs", MODULE)
|
||||
|
||||
Loading…
Reference in a new issue