mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 13:53:45 +03:00
Upload
This commit is contained in:
477
gamemodes/terrortown/gamemode/corpse.lua
Normal file
477
gamemodes/terrortown/gamemode/corpse.lua
Normal file
@@ -0,0 +1,477 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
---- Corpse functions
|
||||
|
||||
-- namespaced because we have no ragdoll metatable
|
||||
CORPSE = {}
|
||||
|
||||
include("corpse_shd.lua")
|
||||
|
||||
--- networked data abstraction layer
|
||||
local dti = CORPSE.dti
|
||||
|
||||
function CORPSE.SetFound(rag, state)
|
||||
--rag:SetNWBool("found", state)
|
||||
rag:SetDTBool(dti.BOOL_FOUND, state)
|
||||
end
|
||||
|
||||
function CORPSE.SetPlayerNick(rag, ply_or_name)
|
||||
-- don't have datatable strings, so use a dt entity for common case of
|
||||
-- still-connected player, and if the player is gone, fall back to nw string
|
||||
local name = ply_or_name
|
||||
if IsValid(ply_or_name) then
|
||||
name = ply_or_name:Nick()
|
||||
rag:SetDTEntity(dti.ENT_PLAYER, ply_or_name)
|
||||
end
|
||||
|
||||
rag:SetNWString("nick", name)
|
||||
end
|
||||
|
||||
function CORPSE.SetCredits(rag, credits)
|
||||
--rag:SetNWInt("credits", credits)
|
||||
rag:SetDTInt(dti.INT_CREDITS, credits)
|
||||
end
|
||||
|
||||
|
||||
--- ragdoll creation and search
|
||||
|
||||
-- If detective mode, announce when someone's body is found
|
||||
local bodyfound = CreateConVar("ttt_announce_body_found", "1")
|
||||
|
||||
function GM:TTTCanIdentifyCorpse(ply, corpse, was_traitor)
|
||||
-- return true to allow corpse identification, false to disallow
|
||||
return true
|
||||
end
|
||||
|
||||
local function IdentifyBody(ply, rag)
|
||||
if not ply:IsTerror() then return end
|
||||
|
||||
-- simplified case for those who die and get found during prep
|
||||
if GetRoundState() == ROUND_PREP then
|
||||
CORPSE.SetFound(rag, true)
|
||||
return
|
||||
end
|
||||
|
||||
if not hook.Run("TTTCanIdentifyCorpse", ply, rag, (rag.was_role == ROLE_TRAITOR)) then
|
||||
return
|
||||
end
|
||||
|
||||
local finder = ply:Nick()
|
||||
local nick = CORPSE.GetPlayerNick(rag, "")
|
||||
local traitor = (rag.was_role == ROLE_TRAITOR)
|
||||
|
||||
-- Announce body
|
||||
if bodyfound:GetBool() and not CORPSE.GetFound(rag, false) then
|
||||
local roletext = nil
|
||||
local role = rag.was_role
|
||||
if role == ROLE_TRAITOR then
|
||||
roletext = "body_found_t"
|
||||
elseif role == ROLE_DETECTIVE then
|
||||
roletext = "body_found_d"
|
||||
else
|
||||
roletext = "body_found_i"
|
||||
end
|
||||
|
||||
LANG.Msg("body_found", {finder = finder,
|
||||
victim = nick,
|
||||
role = LANG.Param(roletext)})
|
||||
end
|
||||
|
||||
-- Register find
|
||||
if not CORPSE.GetFound(rag, false) then
|
||||
-- will return either false or a valid ply
|
||||
local deadply = player.GetBySteamID64(rag.sid64)
|
||||
if deadply then
|
||||
deadply:SetNWBool("body_found", true)
|
||||
|
||||
if traitor then
|
||||
-- update innocent's list of traitors
|
||||
SendConfirmedTraitors(GetInnocentFilter(false))
|
||||
end
|
||||
SCORE:HandleBodyFound(ply, deadply)
|
||||
end
|
||||
hook.Call( "TTTBodyFound", GAMEMODE, ply, deadply, rag )
|
||||
CORPSE.SetFound(rag, true)
|
||||
end
|
||||
|
||||
-- Handle kill list
|
||||
for k, vicsid64 in ipairs(rag.kills) do
|
||||
-- filter out disconnected
|
||||
local vic = player.GetBySteamID64(vicsid64)
|
||||
|
||||
-- is this an unconfirmed dead?
|
||||
if IsValid(vic) and (not vic:GetNWBool("body_found", false)) then
|
||||
LANG.Msg("body_confirm", {finder = finder, victim = vic:Nick()})
|
||||
|
||||
-- update scoreboard status
|
||||
vic:SetNWBool("body_found", true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Covert identify concommand for traitors
|
||||
local function IdentifyCommand(ply, cmd, args)
|
||||
if not IsValid(ply) then return end
|
||||
if #args != 2 then return end
|
||||
|
||||
local eidx = tonumber(args[1])
|
||||
local id = tonumber(args[2])
|
||||
if (not eidx) or (not id) then return end
|
||||
|
||||
if (not ply.search_id) or ply.search_id.id != id or ply.search_id.eidx != eidx then
|
||||
ply.search_id = nil
|
||||
return
|
||||
end
|
||||
|
||||
ply.search_id = nil
|
||||
|
||||
local rag = Entity(eidx)
|
||||
if IsValid(rag) and rag.player_ragdoll and rag:GetPos():Distance(ply:GetPos()) < 128 then
|
||||
if not CORPSE.GetFound(rag, false) then
|
||||
IdentifyBody(ply, rag)
|
||||
end
|
||||
end
|
||||
end
|
||||
concommand.Add("ttt_confirm_death", IdentifyCommand)
|
||||
|
||||
-- Call detectives to a corpse
|
||||
local function CallDetective(ply, cmd, args)
|
||||
if not IsValid(ply) then return end
|
||||
if #args != 1 then return end
|
||||
if not ply:IsActive() then return end
|
||||
|
||||
local eidx = tonumber(args[1])
|
||||
if not eidx then return end
|
||||
|
||||
local rag = Entity(eidx)
|
||||
if not (IsValid(rag) and rag.player_ragdoll) then return end
|
||||
|
||||
if ((rag.last_detective_call or 0) < (CurTime() - 5)) and (rag:GetPos():Distance(ply:GetPos()) < 128) then
|
||||
|
||||
rag.last_detective_call = CurTime()
|
||||
|
||||
if CORPSE.GetFound(rag, false) then
|
||||
-- show indicator to detectives
|
||||
net.Start("TTT_CorpseCall")
|
||||
net.WriteVector(rag:GetPos())
|
||||
net.Send(GetDetectiveFilter(true))
|
||||
|
||||
LANG.Msg("body_call", {player = ply:Nick(),
|
||||
victim = CORPSE.GetPlayerNick(rag, "someone")})
|
||||
|
||||
else
|
||||
LANG.Msg(ply, "body_call_error")
|
||||
end
|
||||
end
|
||||
end
|
||||
concommand.Add("ttt_call_detective", CallDetective)
|
||||
|
||||
local function bitsRequired(num)
|
||||
local bits, max = 0, 1
|
||||
while max <= num do
|
||||
bits = bits + 1
|
||||
max = max + max
|
||||
end
|
||||
return bits
|
||||
end
|
||||
|
||||
local plyBits = bitsRequired(game.MaxPlayers()) -- first game.MaxPlayers() of entities are for players.
|
||||
|
||||
function GM:TTTCanSearchCorpse(ply, corpse, is_covert, is_long_range, was_traitor)
|
||||
-- return true to allow corpse search, false to disallow.
|
||||
return true
|
||||
end
|
||||
|
||||
-- Send a usermessage to client containing search results
|
||||
function CORPSE.ShowSearch(ply, rag, covert, long_range)
|
||||
if not IsValid(ply) or not IsValid(rag) then return end
|
||||
|
||||
if rag:IsOnFire() then
|
||||
LANG.Msg(ply, "body_burning")
|
||||
return
|
||||
end
|
||||
|
||||
if not hook.Run("TTTCanSearchCorpse", ply, rag, covert, long_range, (rag.was_role == ROLE_TRAITOR)) then
|
||||
return
|
||||
end
|
||||
|
||||
-- init a heap of data we'll be sending
|
||||
local nick = CORPSE.GetPlayerNick(rag)
|
||||
local traitor = (rag.was_role == ROLE_TRAITOR)
|
||||
local role = rag.was_role
|
||||
local eq = rag.equipment or EQUIP_NONE
|
||||
local c4 = rag.bomb_wire or 0
|
||||
local dmg = rag.dmgtype or DMG_GENERIC
|
||||
local wep = rag.dmgwep or ""
|
||||
local words = rag.last_words or ""
|
||||
local hshot = rag.was_headshot or false
|
||||
local dtime = rag.time or 0
|
||||
|
||||
local owner = player.GetBySteamID64(rag.sid64)
|
||||
owner = IsValid(owner) and owner:EntIndex() or 0
|
||||
|
||||
-- basic sanity check
|
||||
if nick == nil or eq == nil or role == nil then return end
|
||||
|
||||
if DetectiveMode() and not covert then
|
||||
IdentifyBody(ply, rag)
|
||||
end
|
||||
|
||||
local credits = CORPSE.GetCredits(rag, 0)
|
||||
if ply:IsActiveSpecial() and credits > 0 and (not long_range) then
|
||||
LANG.Msg(ply, "body_credits", {num = credits})
|
||||
ply:AddCredits(credits)
|
||||
|
||||
CORPSE.SetCredits(rag, 0)
|
||||
|
||||
ServerLog(ply:Nick() .. " took " .. credits .. " credits from the body of " .. nick .. "\n")
|
||||
SCORE:HandleCreditFound(ply, nick, credits)
|
||||
end
|
||||
|
||||
-- time of death relative to current time (saves bits)
|
||||
if dtime != 0 then
|
||||
dtime = math.Round(CurTime() - dtime)
|
||||
end
|
||||
|
||||
-- identifier so we know whether a ttt_confirm_death was legit
|
||||
ply.search_id = { eidx = rag:EntIndex(), id = rag:EntIndex() + dtime }
|
||||
|
||||
-- time of dna sample decay relative to current time
|
||||
local stime = 0
|
||||
if rag.killer_sample then
|
||||
stime = math.max(0, rag.killer_sample.t - CurTime())
|
||||
end
|
||||
|
||||
-- build list of people this traitor killed
|
||||
local kill_entids = {}
|
||||
for k, vicsid64 in ipairs(rag.kills) do
|
||||
-- also send disconnected players as a marker
|
||||
local vic = player.GetBySteamID64(vicsid64)
|
||||
table.insert(kill_entids, IsValid(vic) and vic:EntIndex() or 0)
|
||||
end
|
||||
|
||||
local lastid = 0
|
||||
if rag.lastid and ply:IsActiveDetective() then
|
||||
-- if the person this victim last id'd has since disconnected, send 0 to
|
||||
-- indicate this
|
||||
lastid = IsValid(rag.lastid.ent) and rag.lastid.ent:EntIndex() or 0
|
||||
end
|
||||
|
||||
-- Send a message with basic info
|
||||
net.Start("TTT_RagdollSearch")
|
||||
net.WriteUInt(rag:EntIndex(), 16) -- 16 bits
|
||||
net.WriteUInt(owner, plyBits) -- 128 max players. ( 8 bits )
|
||||
net.WriteString(nick)
|
||||
net.WriteUInt(eq, bitsRequired(EQUIP_MAX)) -- Equipment ( default: 3 bits )
|
||||
net.WriteUInt(role, 2) -- ( 2 bits )
|
||||
net.WriteUInt(c4, bitsRequired(C4_WIRE_COUNT)) -- 0 -> 2^bits ( default c4: 3 bits )
|
||||
net.WriteUInt(dmg, 30) -- DMG_BUCKSHOT is the highest. ( 30 bits )
|
||||
net.WriteString(wep)
|
||||
net.WriteBool(hshot) -- ( 1 bit )
|
||||
net.WriteUInt(dtime, 15)
|
||||
net.WriteUInt(stime, 15)
|
||||
|
||||
net.WriteUInt(#kill_entids, 8)
|
||||
for k, idx in ipairs(kill_entids) do
|
||||
net.WriteUInt(idx, plyBits)
|
||||
end
|
||||
|
||||
net.WriteUInt(lastid, plyBits)
|
||||
|
||||
-- Who found this, so if we get this from a detective we can decide not to
|
||||
-- show a window
|
||||
net.WriteUInt(ply:EntIndex(), plyBits)
|
||||
|
||||
net.WriteString(words)
|
||||
|
||||
-- 93 + string data + plyBits * (3 + #kill_entids)
|
||||
|
||||
-- If found by detective, send to all, else just the finder
|
||||
if ply:IsActiveDetective() then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(ply)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Returns a sample for use in dna scanner if the kill fits certain constraints,
|
||||
-- else returns nil
|
||||
local function GetKillerSample(victim, attacker, dmg)
|
||||
-- only guns and melee damage, not explosions
|
||||
if not (dmg:IsBulletDamage() or dmg:IsDamageType(DMG_SLASH) or dmg:IsDamageType(DMG_CLUB)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if not (IsValid(victim) and IsValid(attacker) and attacker:IsPlayer()) then return end
|
||||
|
||||
-- NPCs for which a player is damage owner (meaning despite the NPC dealing
|
||||
-- the damage, the attacker is a player) should not cause the player's DNA to
|
||||
-- end up on the corpse.
|
||||
local infl = dmg:GetInflictor()
|
||||
if IsValid(infl) and infl:IsNPC() then return end
|
||||
|
||||
local dist = victim:GetPos():Distance(attacker:GetPos())
|
||||
if dist > GetConVar("ttt_killer_dna_range"):GetFloat() then return nil end
|
||||
|
||||
local sample = {}
|
||||
sample.killer = attacker
|
||||
sample.killer_sid = attacker:SteamID() -- backwards compatibility; use sample.killer_sid64 instead
|
||||
sample.killer_sid64 = attacker:SteamID64()
|
||||
sample.victim = victim
|
||||
sample.t = CurTime() + (-1 * (0.019 * dist)^2 + GetConVar("ttt_killer_dna_basetime"):GetFloat())
|
||||
|
||||
return sample
|
||||
end
|
||||
|
||||
local crimescene_keys = {"Fraction", "HitBox", "Normal", "HitPos", "StartPos"}
|
||||
local poseparams = {
|
||||
"aim_yaw", "move_yaw", "aim_pitch",
|
||||
-- "spine_yaw", "head_yaw", "head_pitch"
|
||||
};
|
||||
|
||||
local function GetSceneDataFromPlayer(ply)
|
||||
local data = {
|
||||
pos = ply:GetPos(),
|
||||
ang = ply:GetAngles(),
|
||||
sequence = ply:GetSequence(),
|
||||
cycle = ply:GetCycle()
|
||||
};
|
||||
|
||||
for _, param in pairs(poseparams) do
|
||||
data[param] = ply:GetPoseParameter(param)
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
local function GetSceneData(victim, attacker, dmginfo)
|
||||
-- only for guns for now, hull traces don't work well etc
|
||||
if not dmginfo:IsBulletDamage() then return end
|
||||
|
||||
local scene = {}
|
||||
|
||||
if victim.hit_trace then
|
||||
scene.hit_trace = table.CopyKeys(victim.hit_trace, crimescene_keys)
|
||||
else
|
||||
return scene
|
||||
end
|
||||
|
||||
scene.victim = GetSceneDataFromPlayer(victim)
|
||||
|
||||
if IsValid(attacker) and attacker:IsPlayer() then
|
||||
scene.killer = GetSceneDataFromPlayer(attacker)
|
||||
|
||||
local att = attacker:LookupAttachment("anim_attachment_RH")
|
||||
local angpos = attacker:GetAttachment(att)
|
||||
if not angpos then
|
||||
scene.hit_trace.StartPos = attacker:GetShootPos()
|
||||
else
|
||||
scene.hit_trace.StartPos = angpos.Pos
|
||||
end
|
||||
end
|
||||
|
||||
return scene
|
||||
end
|
||||
|
||||
local rag_collide = CreateConVar("ttt_ragdoll_collide", "0")
|
||||
|
||||
-- Creates client or server ragdoll depending on settings
|
||||
function CORPSE.Create(ply, attacker, dmginfo)
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
local efn = ply.effect_fn
|
||||
ply.effect_fn = nil
|
||||
|
||||
local rag = ents.Create("prop_ragdoll")
|
||||
if not IsValid(rag) then return nil end
|
||||
|
||||
rag:SetPos(ply:GetPos())
|
||||
rag:SetModel(ply:GetModel())
|
||||
rag:SetSkin(ply:GetSkin())
|
||||
for key, value in pairs(ply:GetBodyGroups()) do
|
||||
rag:SetBodygroup(value.id, ply:GetBodygroup(value.id))
|
||||
end
|
||||
rag:SetAngles(ply:GetAngles())
|
||||
rag:SetColor(ply:GetColor())
|
||||
|
||||
rag:Spawn()
|
||||
rag:Activate()
|
||||
|
||||
-- nonsolid to players, but can be picked up and shot
|
||||
rag:SetCollisionGroup(rag_collide:GetBool() and COLLISION_GROUP_WEAPON or COLLISION_GROUP_DEBRIS_TRIGGER)
|
||||
|
||||
-- flag this ragdoll as being a player's
|
||||
rag.player_ragdoll = true
|
||||
rag.sid64 = ply:SteamID64()
|
||||
|
||||
rag.sid = ply:SteamID() -- backwards compatibility; use rag.sid64 instead
|
||||
rag.uqid = ply:UniqueID() -- backwards compatibility; use rag.sid64 instead
|
||||
|
||||
-- network data
|
||||
CORPSE.SetPlayerNick(rag, ply)
|
||||
CORPSE.SetFound(rag, false)
|
||||
CORPSE.SetCredits(rag, ply:GetCredits())
|
||||
|
||||
-- if someone searches this body they can find info on the victim and the
|
||||
-- death circumstances
|
||||
rag.equipment = ply:GetEquipmentItems()
|
||||
rag.was_role = ply:GetRole()
|
||||
rag.bomb_wire = ply.bomb_wire
|
||||
rag.dmgtype = dmginfo:GetDamageType()
|
||||
|
||||
local wep = util.WeaponFromDamage(dmginfo)
|
||||
rag.dmgwep = IsValid(wep) and wep:GetClass() or ""
|
||||
|
||||
rag.was_headshot = (ply.was_headshot and dmginfo:IsBulletDamage())
|
||||
rag.time = CurTime()
|
||||
rag.kills = table.Copy(ply.kills)
|
||||
|
||||
rag.killer_sample = GetKillerSample(ply, attacker, dmginfo)
|
||||
|
||||
-- crime scene data
|
||||
rag.scene = GetSceneData(ply, attacker, dmginfo)
|
||||
|
||||
|
||||
-- position the bones
|
||||
local num = rag:GetPhysicsObjectCount()-1
|
||||
local v = ply:GetVelocity()
|
||||
|
||||
-- bullets have a lot of force, which feels better when shooting props,
|
||||
-- but makes bodies fly, so dampen that here
|
||||
if dmginfo:IsDamageType(DMG_BULLET) or dmginfo:IsDamageType(DMG_SLASH) then
|
||||
v = v / 5
|
||||
end
|
||||
|
||||
for i=0, num do
|
||||
local bone = rag:GetPhysicsObjectNum(i)
|
||||
if IsValid(bone) then
|
||||
local bp, ba = ply:GetBonePosition(rag:TranslatePhysBoneToBone(i))
|
||||
if bp and ba then
|
||||
bone:SetPos(bp)
|
||||
bone:SetAngles(ba)
|
||||
end
|
||||
|
||||
-- not sure if this will work:
|
||||
bone:SetVelocity(v)
|
||||
end
|
||||
end
|
||||
|
||||
-- create advanced death effects (knives)
|
||||
if efn then
|
||||
-- next frame, after physics is happy for this ragdoll
|
||||
timer.Simple(0, function() if IsValid(rag) then efn(rag) end end)
|
||||
end
|
||||
|
||||
hook.Run("TTTOnCorpseCreated", rag, ply)
|
||||
|
||||
return rag -- we'll be speccing this
|
||||
end
|
||||
Reference in New Issue
Block a user