---- Communicating game state to players local umsg = umsg local string = string local table = table local pairs = pairs -- Basic status message to everyone function GameMsg(msg) --PrintMessage(HUD_PRINTTALK, msg) umsg.Start("game_msg") umsg.String(msg) umsg.Bool(false) umsg.End() end function CustomMsg(ply_or_rfilter, msg, clr) clr = clr or COLOR_WHITE umsg.Start("game_msg_color", ply_or_rfilter) umsg.String(msg) umsg.Short(clr.r) umsg.Short(clr.g) umsg.Short(clr.b) umsg.End() end -- Basic status message to single player or a recipientfilter function PlayerMsg(ply_or_rfilter, msg, traitor_only) umsg.Start("game_msg", ply_or_rfilter) umsg.String(msg) umsg.Bool(traitor_only) umsg.End() end -- Traitor-specific message that will appear in a special color function TraitorMsg(ply_or_rfilter, msg) PlayerMsg(ply_or_rfilter, msg, true) end -- Traitorchat local function RoleChatMsg(role, msg) for k, v in pairs(player.GetAll()) do if ValidEntity(v) and v:IsRole(role) then v:ChatPrint(msg) end end end -- Round start info popup function ShowRoundStartPopup() for k, v in pairs(player.GetAll()) do if ValidEntity(v) and v:Team() == TEAM_TERROR and v:Alive() then v:ConCommand("ttt_cl_startpopup") end end end local function GetPlayerFilter(pred) local filter = RecipientFilter() for k, v in pairs(player.GetAll()) do if ValidEntity(v) and pred(v) then filter:AddPlayer(v) end end return filter end -- TODO: could roll this into a GetRoleFilter function GetTraitorFilter(alive_only) return GetPlayerFilter(function(p) return p:GetTraitor() and (not alive_only or p:Alive()) end) end function GetDetectiveFilter(alive_only) return GetPlayerFilter(function(p) return p:IsDetective() and (not alive_only or p:Alive()) end) end function GetInnocentFilter(alive_only) return GetPlayerFilter(function(p) return (not p:IsTraitor()) and (not alive_only or p:Alive()) end) end ---- Communication control CreateConVar("ttt_limit_spectator_chat", "1", FCVAR_ARCHIVE + FCVAR_NOTIFY) CreateConVar("ttt_limit_spectator_voice", "1", FCVAR_ARCHIVE + FCVAR_NOTIFY) local mumbles = {"mumble", "mm", "hmm", "hum", "mum", "mbm", "mble", "ham", "mammaries", "political situation", "mrmm", "hrm", "uzbekistan", "mumu", "cheese export", "hmhm", "mmh", "mumble", "mphrrt", "mrh", "hmm", "mumble", "mbmm", "hmml", "mfrrm"} -- While a round is active, spectators can only talk among themselves. When they -- try to speak to all players they could divulge information about who killed -- them. So we mumblify them. In detective mode, we shut them up entirely. function GM:PlayerSay(ply, text, to_all) if not ValidEntity(ply) then return end if not to_all and ply:Team() != TEAM_SPEC and GetRoundState() == ROUND_ACTIVE then if ply:IsTraitor() then -- traitor chat handling RoleChatMsg(ROLE_TRAITOR, "(TRAITOR) ".. ply:Nick() .. "\t" .. text) elseif ply:IsDetective() then RoleChatMsg(ROLE_DETECTIVE, "(DETECTIVE) ".. ply:Nick() .. "\t" .. text) else ply:PrintMessage(HUD_PRINTTALK, "When innocent, use global chat to communicate.") end return "" end if to_all and GetRoundState() == ROUND_ACTIVE and ply:Team() == TEAM_SPEC then if DetectiveMode() then ply:PrintMessage(HUD_PRINTTALK, "While a round is in progress, spectators cannot speak to living players. Use team chat to speak with fellow spectators.") return "" end if not GetConVar("ttt_limit_spectator_chat"):GetBool() then return end local filtered = {} for k, v in pairs(string.Explode(" ", text)) do -- grab word characters and whitelisted interpunction -- necessary or leetspeek will be used (by trolls especially) local word, interp = string.match(v, "(%a*)([%.,;!%?]*)") if word != "" then table.insert(filtered, mumbles[math.random(1, #mumbles)] .. interp) end end -- make sure we have something to say if table.Count(filtered) < 1 then table.insert(filtered, mumbles[math.random(1, #mumbles)]) end table.insert( filtered, 1, "[MUMBLED]") return table.concat(filtered, " ") end return text end -- Mute players when we are about to run map cleanup, because it might cause -- net buffer overflows on clients. local mute_all = false function MuteForRestart(state) mute_all = state end local loc_voice = CreateConVar("ttt_locational_voice", "0") -- Of course voice has to be limited as well function GM:PlayerCanHearPlayersVoice( listener, speaker ) -- Enforced silence if mute_all then return false, false end if (not IsValid(speaker)) or (not IsValid(listener)) or (listener == speaker) then return false, false end -- limited if specific convar is on, or we're in detective mode local limit = DetectiveMode() or GetConVar("ttt_limit_spectator_voice"):GetBool() -- Spectators should not be heard by living players during round if speaker:IsSpec() and (not listener:IsSpec()) and limit and GetRoundState() == ROUND_ACTIVE then return false, false end -- Specific mute if listener:IsSpec() and listener.mute_team == speaker:Team() then return false, false end -- Specs should not hear each other locationally if speaker:IsSpec() and listener:IsSpec() then return true, false end -- Traitors "team"chat by default, non-locationally if speaker:IsActiveTraitor() then if speaker.traitor_gvoice then return true, loc_voice:GetBool() elseif listener:IsActiveTraitor() then return true, false else -- unless traitor_gvoice is true, normal innos can't hear speaker return false, false end end return true, loc_voice:GetBool() end local function SendTraitorVoiceState(speaker, state) -- send umsg to living traitors that this is traitor-only talk local rf = GetTraitorFilter(true) -- tiny umsg in the hope of it arriving asap umsg.Start("tvo", rf) umsg.Short(speaker:EntIndex()) umsg.Bool(state) umsg.End() end local function TraitorGlobalVoice(ply, cmd, args) if not ValidEntity(ply) or not ply:IsActiveTraitor() then return end if not #args == 1 then return end local state = tonumber(args[1]) ply.traitor_gvoice = (state == 1) SendTraitorVoiceState(ply, ply.traitor_gvoice) end concommand.Add("tvog", TraitorGlobalVoice) local function MuteTeam(ply, cmd, args) if not ValidEntity(ply) then return end if not #args == 1 and tonumber(args[1]) then return end if not ply:IsSpec() then ply.mute_team = -1 return end local t = tonumber(args[1]) ply.mute_team = t local name = (t != 0) and team.GetName(t) or "None" ply:ChatPrint(name .. " muted.") end concommand.Add("ttt_mute_team", MuteTeam) local ttt_lastwords = CreateConVar("ttt_lastwords_chatprint", "0") local LastWordContext = { [KILL_NORMAL] = "", [KILL_SUICIDE] = " *kills self*", [KILL_FALL] = " *SPLUT*", [KILL_BURN] = " *crackle*" }; local function LastWords(ply, cmd, args) if ValidEntity(ply) and not ply:Alive() and #args > 1 then local id = args[1] if tonumber(id) == ply.last_words_id then -- never allow multiple last word stuff ply.last_words_id = nil -- we will be storing this on the ragdoll local rag = ply.server_ragdoll if not (IsValid(rag) and rag.player_ragdoll) then rag = nil end --- last id'd person local last_seen = tonumber(args[2]) if last_seen then local ent = Entity(last_seen) if IsValid(ent) and ent:IsPlayer() and rag then rag.lastid = {ent=ent, t=CurTime()} end end --- last words local words = string.Trim(args[3]) -- nothing of interest if string.len(words) < 2 then return end if ttt_lastwords:GetBool() or ply.death_type == KILL_FALL then -- only append "--" if there's no ending interpunction local final = string.match(words, "[\.\!\?]$") != nil -- add optional context relating to death type local context = LastWordContext[ply.death_type] or "" -- turns out I need to know if this guy was detective so his name -- can be blue local role = ply:IsDetective() and "(D)" or "(I)" -- somewhat readable even without clientside parsing local text = "(LAST WORDS)" .. role .. ply:Nick() .. "\t" .. words .. (final and "" or "--") .. context PrintMessage(HUD_PRINTTALK, text) end if rag then rag.last_words = words end else -- the only way we might get an invalid id is by a cunt trying to be -- clever in abusing this feature, so we won't let him have another -- go. Hey if you were thinking about trying that and you're reading -- this trying to find SWEETT HAX: you are a pathetic loser. ply.last_words_id = nil end end end concommand.Add("_deathrec", LastWords)