include("shared.lua") COLOR_WHITE = Color(255,255,255,255) COLOR_BLACK = Color(0,0,0,255) COLOR_GREEN = Color(0,255,0,255) COLOR_RED = Color(255, 0, 0, 255) COLOR_YELLOW = Color(200, 200, 0, 255) COLOR_LGRAY = Color(200, 200, 200, 255) COLOR_BLUE = Color(0,0,255, 255) include("scoring_shd.lua") include("corpse_shd.lua") include("player_ext_shd.lua") include("vgui/SimpleIcon.lua") include("vgui/ProgressBar.lua") include("vgui/ScrollLabel.lua") include("cl_radio.lua") include("cl_disguise.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_popups.lua") include("cl_equip.lua") include("cl_voice.lua") function GM:Initialize() MsgN("TTT Client initializing...") GAMEMODE.round_state = ROUND_WAIT self.BaseClass:Initialize() end function GM:InitPostEntity() MsgN("TTT Client post-init...") GAMEMODE:ShowSplash() RunConsoleCommand("ttt_spectate", GetConVar("ttt_spectator_mode"):GetInt()) if GetConVar("myinfo_bytes"):GetInt() < 1000 then MsgN("Increasing net buffer size...") RunConsoleCommand("myinfo_bytes", "1024") end if not 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) 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 LocalPlayer():ChatPrint("You are in Spectator Mode and will not spawn when a round starts. To disable this mode, press F1, go to Settings and uncheck \"Spectate-only mode\".") end elseif n == ROUND_ACTIVE then -- round starts CycleMuteState(MUTE_NONE) CLSCORE:ClearPanel() for _, p in pairs(player.GetAll()) do if IsValid(p) then p.search_result = nil end end 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") elseif (o == ROUND_PREP) and (n == ROUND_ACTIVE) then hook.Call("TTTBeginRound") elseif (o == ROUND_ACTIVE) and (n == ROUND_POST) then hook.Call("TTTEndRound") end -- whatever round state we get, clear out the voice flags for k,v in pairs(player.GetAll()) do v.traitor_gvoice = false end end local function ReceiveRole(um) local client = LocalPlayer() local role = um:ReadChar() -- 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 usermessage.Hook("ttt_role", ReceiveRole) local function ReceiveRoleList(um) local role = um:ReadChar() local num_ids = um:ReadChar() for i=1, num_ids do local eidx = um:ReadShort() local ply = player.GetByID(eidx) if ValidEntity(ply) and ply.SetRole then ply:SetRole(role) if ply:IsTraitor() then ply.traitor_gvoice = false -- assume traitorchat by default end --print(ply, "is", RoleToString(ply)) end end end usermessage.Hook("role_list", ReceiveRoleList) -- Round state comm local function ReceiveRoundState(um) local o = GetRoundState() GAMEMODE.round_state = um:ReadChar() if o != GAMEMODE.round_state then RoundStateChange(o, GAMEMODE.round_state) end Msg("Round state: " .. FormatRoundState() .. "\n") end usermessage.Hook("round_state", 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 for _, p in pairs(player.GetAll()) do if IsValid(p) then p.sb_tag = nil p:SetRole(ROLE_INNOCENT) p.search_result = nil end end CycleMuteState(MUTE_NONE) RunConsoleCommand("ttt_mute_team_check", "0") if GAMEMODE.ForcedMouse then gui.EnableScreenClicker(false) end end usermessage.Hook("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 pairs(ents.FindByClass("prop_ragdoll")) do if ValidEntity(ent) and CORPSE.GetPlayerNick(ent, "") != "" then ent:SetNoDraw(true) ent:SetSolid(SOLID_NONE) ent:SetCollisionGroup(COLLISION_GROUP_WORLD) ent:SetColor(0,0,0,0) end end -- NPCs drop clientside ragdolls we need to clear out for _, cls in pairs({"class C_ClientRagdoll", "gib"}) do for _, ent in pairs(ents.FindByClass(cls)) do if IsValid(ent) then ent:Remove() end end end -- Kill all decals including sprays, which need a second clear RunConsoleCommand("r_cleardecals") RunConsoleCommand("r_cleardecals") end -- server tells us to call this when our LocalPlayer has spawned local function PlayerSpawn(um) local as_spec = um:ReadBool() if as_spec then TipsShow() else TipsHide() end end usermessage.Hook("plyspawned", PlayerSpawn) local function PlayerDeath() TipsShow() end usermessage.Hook("plydied", PlayerDeath) function GM:ShouldDrawLocalPlayer(ply) return false end function GM:AddDeathNotice() end function GM:DrawDeathNotice() end function GM:Tick() local client = LocalPlayer() if IsValid(client) and client:Alive() and client:Team() != TEAM_SPEC then WSWITCH:Think() RADIO:StoreTarget() 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.") RunConsoleCommand("ttt_spectator_mode", 1) Derma_Query("You were idle for " .. idle_limit .. " seconds and were moved into Spectator-only mode as a result. While you are in this mode, you will not spawn when a new round starts.\nYou can toggle Spectator-only mode at any time by pressing F1 and (un)checking the box in the Settings tab. You can also choose to disable it right now.", "Idle", "OK, do nothing", function() gui.EnableScreenClicker(false) end, "OK, disable Spectator-only mode for me now", function() RunConsoleCommand("ttt_spectator_mode", "0") gui.EnableScreenClicker(false) end) gui.EnableScreenClicker(true) elseif CurTime() > (idle.t + (idle_limit / 2)) then -- will repeat MSTACK:AddMessage("WARNING: YOU APPEAR TO BE IDLE/AFK, AND WILL BE MADE TO SPECTATE UNLESS YOU SHOW ACTIVITY!") end end end