mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
692 lines
19 KiB
Lua
692 lines
19 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/
|
|
--]]
|
|
|
|
---- Voicechat popup, radio commands, text chat stuff
|
|
|
|
DEFINE_BASECLASS("gamemode_base")
|
|
|
|
local GetTranslation = LANG.GetTranslation
|
|
local GetPTranslation = LANG.GetParamTranslation
|
|
local string = string
|
|
|
|
local function LastWordsRecv()
|
|
local sender = net.ReadPlayer()
|
|
local words = net.ReadString()
|
|
|
|
local was_detective = IsValid(sender) and sender:IsDetective()
|
|
local nick = IsValid(sender) and sender:Nick() or "<Unknown>"
|
|
|
|
chat.AddText(Color( 150, 150, 150 ),
|
|
Format("(%s) ", string.upper(GetTranslation("last_words"))),
|
|
was_detective and Color(50, 200, 255) or Color(0, 200, 0),
|
|
nick,
|
|
COLOR_WHITE,
|
|
": " .. words)
|
|
end
|
|
net.Receive("TTT_LastWordsMsg", LastWordsRecv)
|
|
|
|
local function RoleChatRecv()
|
|
-- virtually always our role, but future equipment might allow listening in
|
|
local role = net.ReadUInt(2)
|
|
local sender = net.ReadPlayer()
|
|
if not IsValid(sender) then return end
|
|
|
|
local text = net.ReadString()
|
|
|
|
if role == ROLE_TRAITOR then
|
|
chat.AddText(Color( 255, 30, 40 ),
|
|
Format("(%s) ", string.upper(GetTranslation("traitor"))),
|
|
Color( 255, 200, 20),
|
|
sender:Nick(),
|
|
Color( 255, 255, 200),
|
|
": " .. text)
|
|
|
|
elseif role == ROLE_DETECTIVE then
|
|
chat.AddText(Color( 20, 100, 255 ),
|
|
Format("(%s) ", string.upper(GetTranslation("detective"))),
|
|
Color( 25, 200, 255),
|
|
sender:Nick(),
|
|
Color( 200, 255, 255),
|
|
": " .. text)
|
|
end
|
|
end
|
|
net.Receive("TTT_RoleChat", RoleChatRecv)
|
|
|
|
-- special processing for certain special chat types
|
|
function GM:ChatText(idx, name, text, type)
|
|
|
|
if type == "joinleave" then
|
|
if string.find(text, "Changed name during a round") then
|
|
-- prevent nick from showing up
|
|
chat.AddText(LANG.GetTranslation("name_kick"))
|
|
return true
|
|
end
|
|
end
|
|
|
|
return BaseClass.ChatText(self, idx, name, text, type)
|
|
end
|
|
|
|
|
|
-- Detectives have a blue name, in both chat and radio messages
|
|
local function AddDetectiveText(ply, text)
|
|
chat.AddText(Color(50, 200, 255),
|
|
ply:Nick(),
|
|
Color(255,255,255),
|
|
": " .. text)
|
|
end
|
|
|
|
function GM:OnPlayerChat(ply, text, teamchat, dead)
|
|
if not IsValid(ply) then return BaseClass.OnPlayerChat(self, ply, text, teamchat, dead) end
|
|
|
|
if ply:IsActiveDetective() then
|
|
AddDetectiveText(ply, text)
|
|
return true
|
|
end
|
|
|
|
local team = ply:Team() == TEAM_SPEC
|
|
|
|
if team and not dead then
|
|
dead = true
|
|
end
|
|
|
|
if teamchat and ((not team and not ply:IsSpecial()) or team) then
|
|
teamchat = false
|
|
end
|
|
|
|
return BaseClass.OnPlayerChat(self, ply, text, teamchat, dead)
|
|
end
|
|
|
|
local last_chat = ""
|
|
function GM:ChatTextChanged(text)
|
|
last_chat = text
|
|
end
|
|
|
|
function ChatInterrupt()
|
|
local client = LocalPlayer()
|
|
local id = net.ReadUInt(32)
|
|
|
|
local last_seen = IsValid(client.last_id) and client.last_id:EntIndex() or 0
|
|
|
|
local last_words = "."
|
|
if last_chat == "" then
|
|
if RADIO.LastRadio.t > CurTime() - 2 then
|
|
last_words = RADIO.LastRadio.msg
|
|
end
|
|
else
|
|
last_words = last_chat
|
|
end
|
|
|
|
RunConsoleCommand("_deathrec", tostring(id), tostring(last_seen), last_words)
|
|
end
|
|
net.Receive("TTT_InterruptChat", ChatInterrupt)
|
|
|
|
--- Radio
|
|
|
|
RADIO = {}
|
|
RADIO.Show = false
|
|
|
|
RADIO.StoredTarget = {nick="", t=0}
|
|
RADIO.LastRadio = {msg="", t=0}
|
|
|
|
-- [key] -> command
|
|
RADIO.Commands = {
|
|
{cmd="yes", text="quick_yes", format=false},
|
|
{cmd="no", text="quick_no", format=false},
|
|
{cmd="help", text="quick_help", format=false},
|
|
{cmd="imwith", text="quick_imwith", format=true},
|
|
{cmd="see", text="quick_see", format=true},
|
|
{cmd="suspect", text="quick_suspect", format=true},
|
|
{cmd="traitor", text="quick_traitor", format=true},
|
|
{cmd="innocent", text="quick_inno", format=true},
|
|
{cmd="check", text="quick_check", format=false}
|
|
};
|
|
|
|
local radioframe = nil
|
|
|
|
function RADIO:ShowRadioCommands(state)
|
|
if not state then
|
|
if radioframe and radioframe:IsValid() then
|
|
radioframe:Remove()
|
|
radioframe = nil
|
|
|
|
-- don't capture keys
|
|
self.Show = false
|
|
end
|
|
else
|
|
local client = LocalPlayer()
|
|
if not IsValid(client) then return end
|
|
|
|
if not radioframe then
|
|
|
|
local w, h = 200, 300
|
|
|
|
radioframe = vgui.Create("DForm")
|
|
radioframe:SetName(GetTranslation("quick_title"))
|
|
radioframe:SetSize(w, h)
|
|
radioframe:SetMouseInputEnabled(false)
|
|
radioframe:SetKeyboardInputEnabled(false)
|
|
|
|
radioframe:CenterVertical()
|
|
|
|
|
|
-- This is not how you should do things
|
|
radioframe.ForceResize = function(s)
|
|
local w, label = 0, nil
|
|
for k,v in pairs(s.Items) do
|
|
label = v:GetChild(0)
|
|
if label:GetWide() > w then
|
|
w = label:GetWide()
|
|
end
|
|
end
|
|
s:SetWide(w + 20)
|
|
end
|
|
|
|
for key, command in pairs(self.Commands) do
|
|
local dlabel = vgui.Create("DLabel", radioframe)
|
|
local id = key .. ": "
|
|
local txt = id
|
|
if command.format then
|
|
txt = txt .. GetPTranslation(command.text, {player = GetTranslation("quick_nobody")})
|
|
else
|
|
txt = txt .. GetTranslation(command.text)
|
|
end
|
|
|
|
dlabel:SetText(txt)
|
|
dlabel:SetFont("TabLarge")
|
|
dlabel:SetTextColor(COLOR_WHITE)
|
|
dlabel:SizeToContents()
|
|
|
|
if command.format then
|
|
dlabel.target = nil
|
|
dlabel.id = id
|
|
dlabel.txt = GetTranslation(command.text)
|
|
dlabel.Think = function(s)
|
|
local tgt, v = RADIO:GetTarget()
|
|
if s.target != tgt then
|
|
s.target = tgt
|
|
|
|
tgt = string.Interp(s.txt, {player = RADIO.ToPrintable(tgt)})
|
|
if v then
|
|
tgt = util.Capitalize(tgt)
|
|
end
|
|
|
|
s:SetText(s.id .. tgt)
|
|
s:SizeToContents()
|
|
radioframe:ForceResize()
|
|
end
|
|
end
|
|
end
|
|
|
|
radioframe:AddItem(dlabel)
|
|
end
|
|
|
|
radioframe:ForceResize()
|
|
end
|
|
|
|
radioframe:MakePopup()
|
|
|
|
-- grabs input on init(), which happens in makepopup
|
|
radioframe:SetMouseInputEnabled(false)
|
|
radioframe:SetKeyboardInputEnabled(false)
|
|
|
|
-- capture slot keys while we're open
|
|
self.Show = true
|
|
|
|
timer.Create("radiocmdshow", 3, 1,
|
|
function() RADIO:ShowRadioCommands(false) end)
|
|
end
|
|
end
|
|
|
|
function RADIO:SendCommand(slotidx)
|
|
local c = self.Commands[slotidx]
|
|
if c then
|
|
RunConsoleCommand("ttt_radio", c.cmd)
|
|
|
|
self:ShowRadioCommands(false)
|
|
end
|
|
end
|
|
|
|
function RADIO:GetTargetType()
|
|
if not IsValid(LocalPlayer()) then return end
|
|
local trace = LocalPlayer():GetEyeTrace(MASK_SHOT)
|
|
|
|
if not trace or (not trace.Hit) or (not IsValid(trace.Entity)) then return end
|
|
|
|
local ent = trace.Entity
|
|
|
|
if ent:IsPlayer() and ent:IsTerror() then
|
|
if ent:GetNWBool("disguised", false) then
|
|
return "quick_disg", true
|
|
else
|
|
return ent, false
|
|
end
|
|
elseif ent:GetClass() == "prop_ragdoll" and CORPSE.GetPlayerNick(ent, "") != "" then
|
|
|
|
if DetectiveMode() and not CORPSE.GetFound(ent, false) then
|
|
return "quick_corpse", true
|
|
else
|
|
return ent, false
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function RADIO.ToPrintable(target)
|
|
if isstring(target) then
|
|
return GetTranslation(target)
|
|
elseif IsValid(target) then
|
|
if target:IsPlayer() then
|
|
return target:Nick()
|
|
elseif target:GetClass() == "prop_ragdoll" then
|
|
return GetPTranslation("quick_corpse_id", {player = CORPSE.GetPlayerNick(target, "A Terrorist")})
|
|
end
|
|
end
|
|
end
|
|
|
|
function RADIO:GetTarget()
|
|
local client = LocalPlayer()
|
|
if IsValid(client) then
|
|
|
|
local current, vague = self:GetTargetType()
|
|
if current then return current, vague end
|
|
|
|
local stored = self.StoredTarget
|
|
if stored.target and stored.t > (CurTime() - 3) then
|
|
return stored.target, stored.vague
|
|
end
|
|
end
|
|
return "quick_nobody", true
|
|
end
|
|
|
|
function RADIO:StoreTarget()
|
|
local current, vague = self:GetTargetType()
|
|
if current then
|
|
self.StoredTarget.target = current
|
|
self.StoredTarget.vague = vague
|
|
self.StoredTarget.t = CurTime()
|
|
end
|
|
end
|
|
|
|
-- Radio commands are a console cmd instead of directly sent from RADIO, because
|
|
-- this way players can bind keys to them
|
|
local function RadioCommand(ply, cmd, arg)
|
|
if not IsValid(ply) or #arg != 1 then
|
|
print("ttt_radio failed, too many arguments?")
|
|
return
|
|
end
|
|
|
|
if RADIO.LastRadio.t > (CurTime() - 0.5) then return end
|
|
|
|
local msg_type = arg[1]
|
|
local target, vague = RADIO:GetTarget()
|
|
local msg_name = nil
|
|
|
|
-- this will not be what is shown, but what is stored in case this message
|
|
-- has to be used as last words (which will always be english for now)
|
|
local text = nil
|
|
|
|
for _, msg in pairs(RADIO.Commands) do
|
|
if msg.cmd == msg_type then
|
|
local eng = LANG.GetTranslationFromLanguage(msg.text, "english")
|
|
text = msg.format and string.Interp(eng, {player = RADIO.ToPrintable(target)}) or eng
|
|
|
|
msg_name = msg.text
|
|
break
|
|
end
|
|
end
|
|
|
|
if not text then
|
|
print("ttt_radio failed, argument not valid radiocommand")
|
|
return
|
|
end
|
|
|
|
if vague then
|
|
text = util.Capitalize(text)
|
|
end
|
|
|
|
RADIO.LastRadio.t = CurTime()
|
|
RADIO.LastRadio.msg = text
|
|
|
|
-- target is either a lang string or an entity
|
|
target = isstring(target) and target or tostring(target:EntIndex())
|
|
|
|
RunConsoleCommand("_ttt_radio_send", msg_name, tostring(target))
|
|
end
|
|
|
|
local function RadioComplete(cmd, arg)
|
|
local c = {}
|
|
for k, cmd in pairs(RADIO.Commands) do
|
|
local rcmd = "ttt_radio " .. cmd.cmd
|
|
table.insert(c, rcmd)
|
|
end
|
|
return c
|
|
end
|
|
concommand.Add("ttt_radio", RadioCommand, RadioComplete)
|
|
|
|
|
|
local function RadioMsgRecv()
|
|
local sender = net.ReadPlayer()
|
|
local msg = net.ReadString()
|
|
local param = net.ReadString()
|
|
|
|
if not (IsValid(sender) and sender:IsPlayer()) then return end
|
|
|
|
GAMEMODE:PlayerSentRadioCommand(sender, msg, param)
|
|
|
|
-- if param is a language string, translate it
|
|
-- else it's a nickname
|
|
local lang_param = LANG.GetNameParam(param)
|
|
if lang_param then
|
|
if lang_param == "quick_corpse_id" then
|
|
-- special case where nested translation is needed
|
|
param = GetPTranslation(lang_param, {player = net.ReadString()})
|
|
else
|
|
param = GetTranslation(lang_param)
|
|
end
|
|
end
|
|
|
|
local text = GetPTranslation(msg, {player = param})
|
|
|
|
-- don't want to capitalize nicks, but everything else is fair game
|
|
if lang_param then
|
|
text = util.Capitalize(text)
|
|
end
|
|
|
|
if sender:IsDetective() then
|
|
AddDetectiveText(sender, text)
|
|
else
|
|
chat.AddText(sender,
|
|
COLOR_WHITE,
|
|
": " .. text)
|
|
end
|
|
end
|
|
net.Receive("TTT_RadioMsg", RadioMsgRecv)
|
|
|
|
|
|
local radio_gestures = {
|
|
quick_yes = ACT_GMOD_GESTURE_AGREE,
|
|
quick_no = ACT_GMOD_GESTURE_DISAGREE,
|
|
quick_see = ACT_GMOD_GESTURE_WAVE,
|
|
quick_check = ACT_SIGNAL_GROUP,
|
|
quick_suspect = ACT_SIGNAL_HALT
|
|
};
|
|
|
|
function GM:PlayerSentRadioCommand(ply, name, target)
|
|
local act = radio_gestures[name]
|
|
if act then
|
|
ply:AnimPerformGesture(act)
|
|
end
|
|
end
|
|
|
|
|
|
--- voicechat stuff
|
|
VOICE = {}
|
|
|
|
local MutedState = nil
|
|
|
|
-- voice popups, copied from base gamemode and modified
|
|
|
|
g_VoicePanelList = nil
|
|
|
|
-- 255 at 100
|
|
-- 5 at 5000
|
|
local function VoiceNotifyThink(pnl)
|
|
if not (IsValid(pnl) and LocalPlayer() and IsValid(pnl.ply)) then return end
|
|
if not (GetGlobalBool("ttt_locational_voice", false) and (not pnl.ply:IsSpec()) and (pnl.ply != LocalPlayer())) then return end
|
|
if LocalPlayer():IsActiveTraitor() && pnl.ply:IsActiveTraitor() then return end
|
|
|
|
local d = LocalPlayer():GetPos():Distance(pnl.ply:GetPos())
|
|
|
|
pnl:SetAlpha(math.max(-0.1 * d + 255, 15))
|
|
end
|
|
|
|
local PlayerVoicePanels = {}
|
|
|
|
function GM:PlayerStartVoice( ply )
|
|
local client = LocalPlayer()
|
|
if not IsValid(g_VoicePanelList) or not IsValid(client) then return end
|
|
|
|
-- There'd be an extra one if voice_loopback is on, so remove it.
|
|
GAMEMODE:PlayerEndVoice(ply, true)
|
|
|
|
if not IsValid(ply) then return end
|
|
|
|
-- Tell server this is global
|
|
if client == ply then
|
|
if client:IsActiveTraitor() then
|
|
if (not client:KeyDown(IN_SPEED)) and (not client:KeyDownLast(IN_SPEED)) then
|
|
client.traitor_gvoice = true
|
|
RunConsoleCommand("tvog", "1")
|
|
else
|
|
client.traitor_gvoice = false
|
|
RunConsoleCommand("tvog", "0")
|
|
end
|
|
end
|
|
|
|
VOICE.SetSpeaking(true)
|
|
end
|
|
|
|
local pnl = g_VoicePanelList:Add("VoiceNotify")
|
|
pnl:Setup(ply)
|
|
pnl:Dock(TOP)
|
|
|
|
local oldThink = pnl.Think
|
|
pnl.Think = function( self )
|
|
oldThink( self )
|
|
VoiceNotifyThink( self )
|
|
end
|
|
|
|
local shade = Color(0, 0, 0, 150)
|
|
pnl.Paint = function(s, w, h)
|
|
if not IsValid(s.ply) then return end
|
|
draw.RoundedBox(4, 0, 0, w, h, s.Color)
|
|
draw.RoundedBox(4, 1, 1, w-2, h-2, shade)
|
|
end
|
|
|
|
if client:IsActiveTraitor() then
|
|
if ply == client then
|
|
if not client.traitor_gvoice then
|
|
pnl.Color = Color(200, 20, 20, 255)
|
|
end
|
|
elseif ply:IsActiveTraitor() then
|
|
if not ply.traitor_gvoice then
|
|
pnl.Color = Color(200, 20, 20, 255)
|
|
end
|
|
end
|
|
end
|
|
|
|
if ply:IsActiveDetective() then
|
|
pnl.Color = Color(20, 20, 200, 255)
|
|
end
|
|
|
|
PlayerVoicePanels[ply] = pnl
|
|
|
|
-- run ear gesture
|
|
if not (ply:IsActiveTraitor() and (not ply.traitor_gvoice)) then
|
|
ply:AnimPerformGesture(ACT_GMOD_IN_CHAT)
|
|
end
|
|
end
|
|
|
|
local function ReceiveVoiceState()
|
|
local idx = net.ReadUInt(7) + 1 -- we -1 serverside
|
|
local state = net.ReadBit() == 1
|
|
|
|
-- prevent glitching due to chat starting/ending across round boundary
|
|
if GAMEMODE.round_state != ROUND_ACTIVE then return end
|
|
if (not IsValid(LocalPlayer())) or (not LocalPlayer():IsActiveTraitor()) then return end
|
|
|
|
local ply = player.GetByID(idx)
|
|
if IsValid(ply) then
|
|
ply.traitor_gvoice = state
|
|
|
|
if IsValid(PlayerVoicePanels[ply]) then
|
|
PlayerVoicePanels[ply].Color = state and Color(0,200,0) or Color(200, 0, 0)
|
|
end
|
|
end
|
|
end
|
|
net.Receive("TTT_TraitorVoiceState", ReceiveVoiceState)
|
|
|
|
local function VoiceClean()
|
|
for ply, pnl in pairs( PlayerVoicePanels ) do
|
|
if (not IsValid(pnl)) or (not IsValid(ply)) then
|
|
GAMEMODE:PlayerEndVoice(ply)
|
|
end
|
|
end
|
|
end
|
|
timer.Create( "VoiceClean", 10, 0, VoiceClean )
|
|
|
|
|
|
function GM:PlayerEndVoice(ply, no_reset)
|
|
if IsValid( PlayerVoicePanels[ply] ) then
|
|
PlayerVoicePanels[ply]:Remove()
|
|
PlayerVoicePanels[ply] = nil
|
|
end
|
|
|
|
if IsValid(ply) and not no_reset then
|
|
ply.traitor_gvoice = false
|
|
end
|
|
|
|
if ply == LocalPlayer() then
|
|
VOICE.SetSpeaking(false)
|
|
end
|
|
end
|
|
|
|
local function CreateVoiceVGUI()
|
|
g_VoicePanelList = vgui.Create( "DPanel" )
|
|
|
|
g_VoicePanelList:ParentToHUD()
|
|
g_VoicePanelList:SetPos(25, 25)
|
|
g_VoicePanelList:SetSize(200, ScrH() - 200)
|
|
g_VoicePanelList:SetPaintBackground(false)
|
|
|
|
MutedState = vgui.Create("DLabel")
|
|
MutedState:SetPos(ScrW() - 200, ScrH() - 50)
|
|
MutedState:SetSize(200, 50)
|
|
MutedState:SetFont("Trebuchet18")
|
|
MutedState:SetText("")
|
|
MutedState:SetTextColor(Color(240, 240, 240, 250))
|
|
MutedState:SetVisible(false)
|
|
end
|
|
hook.Add( "InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI )
|
|
|
|
local MuteStates = {MUTE_NONE, MUTE_TERROR, MUTE_ALL, MUTE_SPEC}
|
|
|
|
local MuteText = {
|
|
[MUTE_NONE] = "",
|
|
[MUTE_TERROR] = "mute_living",
|
|
[MUTE_ALL] = "mute_all",
|
|
[MUTE_SPEC] = "mute_specs"
|
|
};
|
|
|
|
local function SetMuteState(state)
|
|
if MutedState then
|
|
MutedState:SetText(string.upper(GetTranslation(MuteText[state])))
|
|
MutedState:SetVisible(state != MUTE_NONE)
|
|
end
|
|
end
|
|
|
|
local mute_state = MUTE_NONE
|
|
function VOICE.CycleMuteState(force_state)
|
|
mute_state = force_state or next(MuteText, mute_state)
|
|
|
|
if not mute_state then mute_state = MUTE_NONE end
|
|
|
|
SetMuteState(mute_state)
|
|
|
|
return mute_state
|
|
end
|
|
|
|
local battery_max = 100
|
|
local battery_min = 10
|
|
function VOICE.InitBattery()
|
|
LocalPlayer().voice_battery = battery_max
|
|
end
|
|
|
|
local function GetRechargeRate()
|
|
local r = GetGlobalFloat("ttt_voice_drain_recharge", 0.05)
|
|
if LocalPlayer().voice_battery < battery_min then
|
|
r = r / 2
|
|
end
|
|
return r
|
|
end
|
|
|
|
local function GetDrainRate()
|
|
if not GetGlobalBool("ttt_voice_drain", false) then return 0 end
|
|
|
|
if GetRoundState() != ROUND_ACTIVE then return 0 end
|
|
local ply = LocalPlayer()
|
|
if (not IsValid(ply)) or ply:IsSpec() then return 0 end
|
|
|
|
if ply:IsAdmin() or ply:IsDetective() then
|
|
return GetGlobalFloat("ttt_voice_drain_admin", 0)
|
|
else
|
|
return GetGlobalFloat("ttt_voice_drain_normal", 0)
|
|
end
|
|
end
|
|
|
|
local function IsTraitorChatting(client)
|
|
return client:IsActiveTraitor() and (not client.traitor_gvoice)
|
|
end
|
|
|
|
function VOICE.Tick()
|
|
if not GetGlobalBool("ttt_voice_drain", false) then return end
|
|
|
|
local client = LocalPlayer()
|
|
if VOICE.IsSpeaking() and (not IsTraitorChatting(client)) then
|
|
client.voice_battery = client.voice_battery - GetDrainRate()
|
|
|
|
if not VOICE.CanSpeak() then
|
|
client.voice_battery = 0
|
|
RunConsoleCommand("-voicerecord")
|
|
end
|
|
elseif client.voice_battery < battery_max then
|
|
client.voice_battery = client.voice_battery + GetRechargeRate()
|
|
end
|
|
end
|
|
|
|
-- Player:IsSpeaking() does not work for localplayer
|
|
function VOICE.IsSpeaking() return LocalPlayer().speaking end
|
|
function VOICE.SetSpeaking(state) LocalPlayer().speaking = state end
|
|
|
|
function VOICE.CanSpeak()
|
|
if not GetGlobalBool("ttt_voice_drain", false) then return true end
|
|
|
|
return LocalPlayer().voice_battery > battery_min or IsTraitorChatting(LocalPlayer())
|
|
end
|
|
|
|
local speaker = surface.GetTextureID("voice/icntlk_sv")
|
|
function VOICE.Draw(client)
|
|
local b = client.voice_battery
|
|
if b >= battery_max then return end
|
|
|
|
local x = 25
|
|
local y = 10
|
|
local w = 200
|
|
local h = 6
|
|
|
|
if b < battery_min and CurTime() % 0.2 < 0.1 then
|
|
surface.SetDrawColor(200, 0, 0, 155)
|
|
else
|
|
surface.SetDrawColor(0, 200, 0, 255)
|
|
end
|
|
surface.DrawOutlinedRect(x, y, w, h)
|
|
|
|
surface.SetTexture(speaker)
|
|
surface.DrawTexturedRect(5, 5, 16, 16)
|
|
|
|
x = x + 1
|
|
y = y + 1
|
|
w = w - 2
|
|
h = h - 2
|
|
|
|
surface.SetDrawColor(0, 200, 0, 150)
|
|
surface.DrawRect(x, y, w * math.Clamp((client.voice_battery - 10) / 90, 0, 1), h)
|
|
end
|