mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
415 lines
11 KiB
Lua
415 lines
11 KiB
Lua
--[[
|
|
| 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/
|
|
--]]
|
|
|
|
include("shared.lua")
|
|
|
|
-- Define GM12 fonts for compatibility
|
|
surface.CreateFont("DefaultBold", {font = "Tahoma",
|
|
size = 13,
|
|
weight = 1000})
|
|
surface.CreateFont("TabLarge", {font = "Tahoma",
|
|
size = 13,
|
|
weight = 700,
|
|
shadow = true, antialias = false})
|
|
surface.CreateFont("Trebuchet22", {font = "Trebuchet MS",
|
|
size = 22,
|
|
weight = 900})
|
|
|
|
include("corpse_shd.lua")
|
|
include("player_ext_shd.lua")
|
|
include("weaponry_shd.lua")
|
|
|
|
include("vgui/ColoredBox.lua")
|
|
include("vgui/SimpleIcon.lua")
|
|
include("vgui/ProgressBar.lua")
|
|
include("vgui/ScrollLabel.lua")
|
|
|
|
include("cl_radio.lua")
|
|
include("cl_disguise.lua")
|
|
include("cl_transfer.lua")
|
|
include("cl_targetid.lua")
|
|
include("cl_search.lua")
|
|
include("cl_radar.lua")
|
|
include("cl_tbuttons.lua")
|
|
include("cl_scoreboard.lua")
|
|
include("cl_tips.lua")
|
|
include("cl_help.lua")
|
|
include("cl_hud.lua")
|
|
include("cl_msgstack.lua")
|
|
include("cl_hudpickup.lua")
|
|
include("cl_keys.lua")
|
|
include("cl_wepswitch.lua")
|
|
include("cl_scoring.lua")
|
|
include("cl_scoring_events.lua")
|
|
include("cl_popups.lua")
|
|
include("cl_equip.lua")
|
|
include("cl_voice.lua")
|
|
|
|
function GM:Initialize()
|
|
MsgN("TTT Client initializing...")
|
|
|
|
GAMEMODE.round_state = ROUND_WAIT
|
|
|
|
LANG.Init()
|
|
|
|
self.BaseClass:Initialize()
|
|
end
|
|
|
|
function GM:InitPostEntity()
|
|
MsgN("TTT Client post-init...")
|
|
|
|
net.Start("TTT_Spectate")
|
|
net.WriteBool(GetConVar("ttt_spectator_mode"):GetBool())
|
|
net.SendToServer()
|
|
|
|
if not game.SinglePlayer() then
|
|
timer.Create("idlecheck", 5, 0, CheckIdle)
|
|
end
|
|
|
|
-- make sure player class extensions are loaded up, and then do some
|
|
-- initialization on them
|
|
if IsValid(LocalPlayer()) and LocalPlayer().GetTraitor then
|
|
GAMEMODE:ClearClientState()
|
|
end
|
|
|
|
timer.Create("cache_ents", 1, 0, GAMEMODE.DoCacheEnts)
|
|
|
|
RunConsoleCommand("_ttt_request_serverlang")
|
|
RunConsoleCommand("_ttt_request_rolelist")
|
|
end
|
|
|
|
function GM:DoCacheEnts()
|
|
RADAR:CacheEnts()
|
|
TBHUD:CacheEnts()
|
|
end
|
|
|
|
function GM:HUDClear()
|
|
RADAR:Clear()
|
|
TBHUD:Clear()
|
|
end
|
|
|
|
KARMA = {}
|
|
function KARMA.IsEnabled() return GetGlobalBool("ttt_karma", false) end
|
|
|
|
function GetRoundState() return GAMEMODE.round_state end
|
|
|
|
local function RoundStateChange(o, n)
|
|
if n == ROUND_PREP then
|
|
-- prep starts
|
|
GAMEMODE:ClearClientState()
|
|
GAMEMODE:CleanUpMap()
|
|
|
|
-- show warning to spec mode players
|
|
if GetConVar("ttt_spectator_mode"):GetBool() and IsValid(LocalPlayer())then
|
|
LANG.Msg("spec_mode_warning")
|
|
end
|
|
|
|
-- reset cached server language in case it has changed
|
|
RunConsoleCommand("_ttt_request_serverlang")
|
|
elseif n == ROUND_ACTIVE then
|
|
-- round starts
|
|
VOICE.CycleMuteState(MUTE_NONE)
|
|
|
|
CLSCORE:ClearPanel()
|
|
|
|
-- people may have died and been searched during prep
|
|
for _, p in player.Iterator() do
|
|
p.search_result = nil
|
|
end
|
|
|
|
-- clear blood decals produced during prep
|
|
RunConsoleCommand("r_cleardecals")
|
|
|
|
GAMEMODE.StartingPlayers = #util.GetAlivePlayers()
|
|
elseif n == ROUND_POST then
|
|
RunConsoleCommand("ttt_cl_traitorpopup_close")
|
|
end
|
|
|
|
-- stricter checks when we're talking about hooks, because this function may
|
|
-- be called with for example o = WAIT and n = POST, for newly connecting
|
|
-- players, which hooking code may not expect
|
|
if n == ROUND_PREP then
|
|
-- can enter PREP from any phase due to ttt_roundrestart
|
|
hook.Call("TTTPrepareRound", GAMEMODE)
|
|
elseif (o == ROUND_PREP) and (n == ROUND_ACTIVE) then
|
|
hook.Call("TTTBeginRound", GAMEMODE)
|
|
elseif (o == ROUND_ACTIVE) and (n == ROUND_POST) then
|
|
hook.Call("TTTEndRound", GAMEMODE)
|
|
end
|
|
|
|
-- whatever round state we get, clear out the voice flags
|
|
for k,v in player.Iterator() do
|
|
v.traitor_gvoice = false
|
|
end
|
|
end
|
|
|
|
concommand.Add("ttt_print_playercount", function() print(GAMEMODE.StartingPlayers) end)
|
|
|
|
--- optional sound cues on round start and end
|
|
CreateConVar("ttt_cl_soundcues", "0", FCVAR_ARCHIVE)
|
|
|
|
local cues = {
|
|
Sound("ttt/thump01e.mp3"),
|
|
Sound("ttt/thump02e.mp3")
|
|
};
|
|
local function PlaySoundCue()
|
|
if GetConVar("ttt_cl_soundcues"):GetBool() then
|
|
surface.PlaySound(table.Random(cues))
|
|
end
|
|
end
|
|
|
|
GM.TTTBeginRound = PlaySoundCue
|
|
GM.TTTEndRound = PlaySoundCue
|
|
|
|
--- usermessages
|
|
|
|
local function ReceiveRole()
|
|
local client = LocalPlayer()
|
|
local role = net.ReadUInt(2)
|
|
|
|
-- after a mapswitch, server might have sent us this before we are even done
|
|
-- loading our code
|
|
if not client.SetRole then return end
|
|
|
|
client:SetRole(role)
|
|
|
|
Msg("You are: ")
|
|
if client:IsTraitor() then MsgN("TRAITOR")
|
|
elseif client:IsDetective() then MsgN("DETECTIVE")
|
|
else MsgN("INNOCENT") end
|
|
end
|
|
net.Receive("TTT_Role", ReceiveRole)
|
|
|
|
local function ReceiveRoleList()
|
|
local role = net.ReadUInt(2)
|
|
local num_ids = net.ReadUInt(8)
|
|
|
|
for i=1, num_ids do
|
|
local eidx = net.ReadUInt(7) + 1 -- we - 1 worldspawn=0
|
|
|
|
local ply = player.GetByID(eidx)
|
|
if IsValid(ply) and ply.SetRole then
|
|
ply:SetRole(role)
|
|
|
|
if ply:IsTraitor() then
|
|
ply.traitor_gvoice = false -- assume traitorchat by default
|
|
end
|
|
end
|
|
end
|
|
end
|
|
net.Receive("TTT_RoleList", ReceiveRoleList)
|
|
|
|
-- Round state comm
|
|
local function ReceiveRoundState()
|
|
local o = GetRoundState()
|
|
GAMEMODE.round_state = net.ReadUInt(3)
|
|
|
|
if o != GAMEMODE.round_state then
|
|
RoundStateChange(o, GAMEMODE.round_state)
|
|
end
|
|
|
|
MsgN("Round state: " .. GAMEMODE.round_state)
|
|
end
|
|
net.Receive("TTT_RoundState", ReceiveRoundState)
|
|
|
|
-- Cleanup at start of new round
|
|
function GM:ClearClientState()
|
|
GAMEMODE:HUDClear()
|
|
|
|
local client = LocalPlayer()
|
|
if not client.SetRole then return end -- code not loaded yet
|
|
|
|
client:SetRole(ROLE_INNOCENT)
|
|
|
|
client.equipment_items = EQUIP_NONE
|
|
client.equipment_credits = 0
|
|
client.bought = {}
|
|
client.last_id = nil
|
|
client.radio = nil
|
|
client.called_corpses = {}
|
|
|
|
VOICE.InitBattery()
|
|
|
|
for _, p in player.Iterator() do
|
|
if IsValid(p) then
|
|
p.sb_tag = nil
|
|
p:SetRole(ROLE_INNOCENT)
|
|
p.search_result = nil
|
|
end
|
|
end
|
|
|
|
VOICE.CycleMuteState(MUTE_NONE)
|
|
RunConsoleCommand("ttt_mute_team_check", "0")
|
|
|
|
if GAMEMODE.ForcedMouse then
|
|
gui.EnableScreenClicker(false)
|
|
end
|
|
end
|
|
net.Receive("TTT_ClearClientState", GM.ClearClientState)
|
|
|
|
function GM:CleanUpMap()
|
|
-- Ragdolls sometimes stay around on clients. Deleting them can create issues
|
|
-- so all we can do is try to hide them.
|
|
for _, ent in ipairs(ents.FindByClass("prop_ragdoll")) do
|
|
if IsValid(ent) and CORPSE.GetPlayerNick(ent, "") != "" then
|
|
ent:SetNoDraw(true)
|
|
ent:SetSolid(SOLID_NONE)
|
|
ent:SetColor(Color(0,0,0,0))
|
|
|
|
-- Horrible hack to make targetid ignore this ent, because we can't
|
|
-- modify the collision group clientside.
|
|
ent.NoTarget = true
|
|
end
|
|
end
|
|
|
|
-- This cleans up decals since GMod v100
|
|
game.CleanUpMap()
|
|
end
|
|
|
|
-- server tells us to call this when our LocalPlayer has spawned
|
|
local function PlayerSpawn()
|
|
local as_spec = net.ReadBit() == 1
|
|
if as_spec then
|
|
TIPS.Show()
|
|
else
|
|
TIPS.Hide()
|
|
end
|
|
end
|
|
net.Receive("TTT_PlayerSpawned", PlayerSpawn)
|
|
|
|
local function PlayerDeath()
|
|
TIPS.Show()
|
|
end
|
|
net.Receive("TTT_PlayerDied", PlayerDeath)
|
|
|
|
function GM:ShouldDrawLocalPlayer(ply) return false end
|
|
|
|
local view = {origin = vector_origin, angles = angle_zero, fov=0}
|
|
function GM:CalcView( ply, origin, angles, fov )
|
|
view.origin = origin
|
|
view.angles = angles
|
|
view.fov = fov
|
|
|
|
-- first person ragdolling
|
|
if ply:Team() == TEAM_SPEC and ply:GetObserverMode() == OBS_MODE_IN_EYE then
|
|
local tgt = ply:GetObserverTarget()
|
|
if IsValid(tgt) and (not tgt:IsPlayer()) then
|
|
-- assume if we are in_eye and not speccing a player, we spec a ragdoll
|
|
local eyes = tgt:LookupAttachment("eyes") or 0
|
|
eyes = tgt:GetAttachment(eyes)
|
|
if eyes then
|
|
view.origin = eyes.Pos
|
|
view.angles = eyes.Ang
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local wep = ply:GetActiveWeapon()
|
|
if IsValid(wep) then
|
|
local func = wep.CalcView
|
|
if func then
|
|
view.origin, view.angles, view.fov = func( wep, ply, origin*1, angles*1, fov )
|
|
end
|
|
end
|
|
|
|
return view
|
|
end
|
|
|
|
function GM:AddDeathNotice() end
|
|
function GM:DrawDeathNotice() end
|
|
|
|
function GM:Tick()
|
|
local client = LocalPlayer()
|
|
if IsValid(client) then
|
|
if client:Alive() and client:Team() != TEAM_SPEC then
|
|
WSWITCH:Think()
|
|
RADIO:StoreTarget()
|
|
end
|
|
|
|
VOICE.Tick()
|
|
end
|
|
end
|
|
|
|
|
|
-- Simple client-based idle checking
|
|
local idle = {ang = nil, pos = nil, mx = 0, my = 0, t = 0}
|
|
function CheckIdle()
|
|
local client = LocalPlayer()
|
|
if not IsValid(client) then return end
|
|
|
|
if not idle.ang or not idle.pos then
|
|
-- init things
|
|
idle.ang = client:GetAngles()
|
|
idle.pos = client:GetPos()
|
|
idle.mx = gui.MouseX()
|
|
idle.my = gui.MouseY()
|
|
idle.t = CurTime()
|
|
|
|
return
|
|
end
|
|
|
|
if GetRoundState() == ROUND_ACTIVE and client:IsTerror() and client:Alive() then
|
|
local idle_limit = GetGlobalInt("ttt_idle_limit", 300) or 300
|
|
if idle_limit <= 0 then idle_limit = 300 end -- networking sucks sometimes
|
|
|
|
|
|
if client:GetAngles() != idle.ang then
|
|
-- Normal players will move their viewing angles all the time
|
|
idle.ang = client:GetAngles()
|
|
idle.t = CurTime()
|
|
elseif gui.MouseX() != idle.mx or gui.MouseY() != idle.my then
|
|
-- Players in eg. the Help will move their mouse occasionally
|
|
idle.mx = gui.MouseX()
|
|
idle.my = gui.MouseY()
|
|
idle.t = CurTime()
|
|
elseif client:GetPos():Distance(idle.pos) > 10 then
|
|
-- Even if players don't move their mouse, they might still walk
|
|
idle.pos = client:GetPos()
|
|
idle.t = CurTime()
|
|
elseif CurTime() > (idle.t + idle_limit) then
|
|
RunConsoleCommand("say", "(AUTOMATED MESSAGE) I have been moved to the Spectator team because I was idle/AFK.")
|
|
|
|
timer.Simple(0.3, function()
|
|
RunConsoleCommand("ttt_spectator_mode", 1)
|
|
net.Start("TTT_Spectate")
|
|
net.WriteBool(true)
|
|
net.SendToServer()
|
|
RunConsoleCommand("ttt_cl_idlepopup")
|
|
end)
|
|
elseif CurTime() > (idle.t + (idle_limit / 2)) then
|
|
-- will repeat
|
|
LANG.Msg("idle_warning")
|
|
end
|
|
end
|
|
end
|
|
|
|
function GM:OnEntityCreated(ent)
|
|
-- Make ragdolls look like the player that has died
|
|
if ent:IsRagdoll() then
|
|
local ply = CORPSE.GetPlayer(ent)
|
|
|
|
if IsValid(ply) then
|
|
-- Only copy any decals if this ragdoll was recently created
|
|
if ent:GetCreationTime() > CurTime() - 1 then
|
|
ent:SnatchModelInstance(ply)
|
|
end
|
|
|
|
-- Copy the color for the PlayerColor matproxy
|
|
local playerColor = ply:GetPlayerColor()
|
|
ent.GetPlayerColor = function()
|
|
return playerColor
|
|
end
|
|
end
|
|
end
|
|
|
|
return self.BaseClass.OnEntityCreated(self, ent)
|
|
end
|