From 8830c6ceafe2ace63963bb67c0282d371695c6e0 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Thu, 25 Sep 2025 09:29:18 +0000 Subject: [PATCH 01/25] Add files via upload --- gamemodes/ultimateph/gamemode/cl_colors.lua | 17 + .../ultimateph/gamemode/cl_endroundboard.lua | 48 ++- .../ultimateph/gamemode/cl_helpscreen.lua | 28 +- gamemodes/ultimateph/gamemode/cl_hud.lua | 108 +----- gamemodes/ultimateph/gamemode/cl_init.lua | 6 +- .../ultimateph/gamemode/cl_scoreboard.lua | 144 ++++---- gamemodes/ultimateph/gamemode/cl_taunt.lua | 69 ++-- gamemodes/ultimateph/gamemode/sh_disguise.lua | 18 + gamemodes/ultimateph/gamemode/sh_taunt.lua | 5 - gamemodes/ultimateph/gamemode/shared.lua | 4 +- gamemodes/ultimateph/gamemode/sv_disguise.lua | 318 +++++++++--------- gamemodes/ultimateph/gamemode/sv_mapvote.lua | 5 +- gamemodes/ultimateph/gamemode/sv_respawn.lua | 2 +- gamemodes/ultimateph/gamemode/sv_rounds.lua | 5 +- 14 files changed, 415 insertions(+), 362 deletions(-) create mode 100644 gamemodes/ultimateph/gamemode/cl_colors.lua diff --git a/gamemodes/ultimateph/gamemode/cl_colors.lua b/gamemodes/ultimateph/gamemode/cl_colors.lua new file mode 100644 index 0000000..e14d24f --- /dev/null +++ b/gamemodes/ultimateph/gamemode/cl_colors.lua @@ -0,0 +1,17 @@ +PHDark = Color(73, 77, 100) +PHDarker = Color(54, 58, 79) +PHDarkest = Color(36, 39, 58) +PHWhite = Color(202, 211, 245) +PHLessWhite = Color(184, 192, 224) +PHLesserWhite = Color(165, 173, 203) +PHTeal = Color(166, 218, 149) +PHPink = Color(245, 189, 230) +PHMauve = Color(198, 160, 246) +PHRed = Color(237, 135, 150) +PHMaroon = Color(238, 153, 160) +PHPeach = Color(245, 169, 127) +PHYellow = Color(238, 212, 159) +PHGreen = Color(139, 213, 202) +PHSky = Color(145, 215, 227) +PHSapphire = Color(125, 196, 228) +PHBlue = Color(138, 173, 244) diff --git a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua index ddc4ba9..e9d2a5a 100644 --- a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua @@ -1,13 +1,39 @@ local menu +include("cl_colors.lua") local function createEndRoundMenu() menu = vgui.Create("DFrame") menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) menu:Center() menu:MakePopup() + menu:ShowCloseButton(false) menu:SetMouseInputEnabled(true) menu:SetKeyboardInputEnabled(false) menu:SetDeleteOnClose(false) + menu:SetDraggable(false) + + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('marlett') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + closeButton.OnCursorEntered = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) + end + end + closeButton.OnCursorExited = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 40) + closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton.DoClick = function() + menu:Close() + end local matBlurScreen = Material("pp/blurscreen") function menu:Paint(w, h) @@ -28,18 +54,18 @@ local function createEndRoundMenu() -- 2 pixel thick black border on the OUTSIDE of the panel -- Temporarily disable clipping so we can draw outside the bounds of menu - surface.SetDrawColor(0, 0, 0, 230) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) DisableClipping(true) surface.DrawOutlinedRect(-1, -1, w + 2, h + 2) surface.DrawOutlinedRect(-2, -2, w + 4, h + 4) DisableClipping(false) -- Title bar rectangle (the title bar is always 22 pixels in height) - surface.SetDrawColor(40, 40, 40, 230) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) surface.DrawRect(0, 0, w, 22) -- Light grey background on lower area - surface.SetDrawColor(60, 60, 60, 230) + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) surface.DrawRect(0, 22, w, h) end @@ -67,7 +93,7 @@ local function createEndRoundMenu() menu.setWinningTeamText = function(winState) if winState == WIN_NONE then winner:SetText("Round tied") - winner:SetColor(Color(150, 150, 150)) + winner:SetColor(PHWhite) else winner:SetText(team.GetName(winState) .. " win!") winner:SetColor(team.GetColor(winState)) @@ -80,7 +106,7 @@ local function createEndRoundMenu() function awards:Paint(w, h) -- Add a dark rectangle over the area for the awards to visually separate it from rest of the menu - surface.SetDrawColor(20, 20, 20, 150) + surface.SetDrawColor(PHDark.r, PHDark.g, PHDark.b) surface.DrawRect(0, 0, w, h) end @@ -101,9 +127,9 @@ local function createEndRoundMenu() containerPanel:SetTall(draw.GetFontHeight("RobotoHUD-20")) function containerPanel:Paint(w, h) - surface.SetDrawColor(50, 50, 50) - draw.DrawText(award.name, "RobotoHUD-10", 0, 0, Color(220, 220, 220), 0) - draw.DrawText(award.desc, "RobotoHUD-10", 0, draw.GetFontHeight("RobotoHUD-10"), Color(120, 120, 120), 0) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + draw.DrawText(award.name, "RobotoHUD-10", 0, 0, PHWhite, 0) + draw.DrawText(award.desc, "RobotoHUD-10", 0, draw.GetFontHeight("RobotoHUD-10"), PHLesserWhite, 0) draw.DrawText(award.winnerName, "RobotoHUD-15", w, (h / 2) - (draw.GetFontHeight("RobotoHUD-20") / 2), team.GetColor(award.winnerTeam), 2) end @@ -111,14 +137,14 @@ local function createEndRoundMenu() end end - -- Timer at bottom right showing how long until next round/mapvote + -- Timer at bottom right showing how long until next round local resultsTimeLeft = vgui.Create("DPanel", resultsPanel) resultsTimeLeft:Dock(BOTTOM) resultsTimeLeft:SetTall(draw.GetFontHeight("RobotoHUD-15")) function resultsTimeLeft:Paint(w, h) -- "Extend" the dark rectangle from awards:Paint to make a larger seamless rectangle - surface.SetDrawColor(20, 20, 20, 150) + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) surface.DrawRect(0, 0, w, h) if GAMEMODE:GetGameState() == ROUND_POST then @@ -126,7 +152,7 @@ local function createEndRoundMenu() local roundTime = settings.NextRoundTime || 30 local time = math.max(0, roundTime - GAMEMODE:GetStateRunningTime()) -- TODO: Say "Mapvote in..." if last round - draw.DrawText("Next round in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, Color(150, 150, 150), 2) + draw.DrawText("Next round in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, PHWhite, 2) end end diff --git a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua index 6ca7a37..4b845f7 100644 --- a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua +++ b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua @@ -18,6 +18,7 @@ The aim of the props is to hide from the hunters and not get killed. ]] local menu +include("cl_colors.lua") local function createHelpMenu() menu = vgui.Create("DFrame") @@ -27,12 +28,35 @@ local function createHelpMenu() menu:MakePopup() menu:SetKeyboardInputEnabled(false) menu:SetDeleteOnClose(false) - menu:ShowCloseButton(true) + menu:ShowCloseButton(false) menu:SetTitle("") menu:SetVisible(false) + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('marlett') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) + end + closeButton.OnCursorEntered = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b, 245)) + end + end + closeButton.OnCursorExited = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) + closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton.DoClick = function() + menu:Close() + end + function menu:Paint(w, h) - surface.SetDrawColor(40, 40, 40, 230) + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b, 245) surface.DrawRect(0, 0, w, h) surface.SetFont("RobotoHUD-25") draw.ShadowText("Help", "RobotoHUD-25", 8, 2, Color(132, 199, 29), 0) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index b7aa8b0..d9b0b67 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -1,3 +1,4 @@ +include("cl_colors.lua") local function createRoboto(s) surface.CreateFont("RobotoHUD-" .. s , { font = "Roboto-Bold", @@ -49,7 +50,7 @@ function GM:DrawGameHUD() ply = self:GetCSpectatee() end - self:DrawHealth(ply) +-- self:DrawHealth(ply) if ply != LocalPlayer() then local col = team.GetColor(ply:Team()) @@ -111,102 +112,8 @@ end local polyTex = surface.GetTextureID("VGUI/white.vmt") -local function drawPoly(x, y, w, h, percent) - local points = 40 - if percent > 0.5 then - local vertexes = {} - local hpoints = points / 2 - local base = math.pi * 1.5 - local mul = 1 / hpoints * math.pi - for i = (1 - percent) * 2 * hpoints, hpoints do - table.insert(vertexes, {x = x + w / 2 + math.cos(i * mul + base) * w / 2, y = y + h / 2 + math.sin(i * mul + base) * h / 2}) - end - - table.insert(vertexes, {x = x + w / 2, y = y + h}) - table.insert(vertexes, {x = x + w / 2, y = y + h / 2}) - - surface.SetTexture(polyTex) - surface.DrawPoly(vertexes) - end - - local vertexes = {} - local hpoints = points / 2 - local base = math.pi * 0.5 - local mul = 1 / hpoints * math.pi - local p = 0 - if percent < 0.5 then - p = (1 - percent * 2) - end - - for i = p * hpoints, hpoints do - table.insert(vertexes, {x = x + w / 2 + math.cos(i * mul + base) * w / 2, y = y + h / 2 + math.sin(i * mul + base) * h / 2}) - end - table.insert(vertexes, {x = x + w / 2, y = y}) - table.insert(vertexes, {x = x + w / 2, y = y + h / 2}) - - surface.SetTexture(polyTex) - surface.DrawPoly(vertexes) -end - -function GM:DrawHealth(ply) - local x = 20 - local w, h = math.ceil(ScrW() * 0.09), 80 - h = w - local y = ScrH() - 20 - h - local ps = 0.05 - - surface.SetDrawColor(50, 50, 50, 180) - drawPoly(x, y, w, h, 1) - - render.ClearStencil() - render.SetStencilEnable(true) - render.SetStencilFailOperation(STENCILOPERATION_KEEP) - render.SetStencilZFailOperation(STENCILOPERATION_KEEP) - render.SetStencilPassOperation(STENCILOPERATION_REPLACE) - render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) - render.SetStencilWriteMask(1) - render.SetStencilTestMask(1) - render.SetStencilReferenceValue(1) - render.SetBlend(0) - render.OverrideDepthEnable(true, false) - - surface.SetDrawColor(26, 120, 245, 1) - drawPoly(x + w * ps, y + h * ps, w * (1 - 2 * ps), h * (1 - 2 * ps), 1) - - render.SetStencilEnable(true) - render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) - render.SetStencilPassOperation(STENCILOPERATION_REPLACE) - render.SetStencilReferenceValue(1) - - local health = ply:Health() - local maxhealth = math.max(health, ply:GetHMaxHealth()) - local nh = math.Round((h - ps * 2) * math.Clamp(health / maxhealth, 0, 1)) - local tcol = table.Copy(team.GetColor(ply:Team())) - tcol.a = 150 - surface.SetDrawColor(tcol) - surface.DrawRect(x, y + h - ps - nh, w, nh) - - draw.ShadowText(math.Round(health) .. "", "RobotoHUD-25", x + w / 2, y + h / 2, color_white, 1, 1) - - render.SetStencilEnable(false) - render.SetStencilWriteMask(0) - render.SetStencilReferenceValue(0) - render.SetStencilTestMask(0) - render.SetStencilEnable(false) - render.OverrideDepthEnable(false) - render.SetBlend(1) - - cam.IgnoreZ(false) - - if ply:IsDisguised() && ply:DisguiseRotationLocked() then - local fg = draw.GetFontHeight("RobotoHUD-15") - draw.ShadowText("ROTATION", "RobotoHUD-15", x + w + 20, y + h / 2 - fg / 2, color_white, 0, 1) - draw.ShadowText("LOCK", "RobotoHUD-15", x + w + 20, y + h / 2 + fg / 2, color_white, 0, 1) - end -end - function GM:HUDShouldDraw(name) - if name == "CHudHealth" then return false end + if name == "CHudHealth" then return true end if name == "CHudVoiceStatus" then return false end if name == "CHudVoiceSelfStatus" then return false end @@ -237,12 +144,18 @@ function GM:DrawRoundTimer() local settings = self:GetRoundSettings() local roundTime = settings.RoundTime || 5 * 60 local time = math.max(0, roundTime - self:GetStateRunningTime()) + net.Receive("rounds", function(len) + self.rounds = net.ReadInt(5) + end) + if self.rounds == nil then + self.rounds = 1 + end local m = math.floor(time / 60) local s = math.floor(time % 60) m = tostring(m) s = s < 10 && "0" .. s || tostring(s) local fh = draw.GetFontHeight("RobotoHUD-L15") * 1 - draw.ShadowText("Props win in", "RobotoHUD-L15", ScrW() / 2, 20, color_white, 1, 3) + draw.ShadowText("Round "..self.rounds.." / "..self.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, 20, color_white, 1, 3) draw.ShadowText(m .. ":" .. s, "RobotoHUD-20", ScrW() / 2, fh + 20, color_white, 1, 3) end end @@ -256,3 +169,4 @@ function GM:PreDrawHUD() end end end + diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index 8b6057b..71491a9 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -76,13 +76,10 @@ function GM:CalcView(ply, pos, angles, fov) local mins = ply:GetNWVector("disguiseMins") local view = {} local reach = (maxs.z - mins.z) - if reach <= GetConVar("ph_props_camdistance_min"):GetInt() then reach = GetConVar("ph_props_camdistance_min"):GetInt() end - reach = reach * GetConVar("ph_props_camdistance_mult"):GetInt() - if reach >= GetConVar("ph_props_camdistance_max"):GetInt() then reach = GetConVar("ph_props_camdistance_max"):GetInt() end @@ -115,6 +112,7 @@ function GM:CalcView(ply, pos, angles, fov) endpos = pos - angles:Forward() * 100, mins = Vector( -4, -4, -4 ), maxs = Vector( 4, 4, 4 ), + whitelist = true, } if trace.Hit then @@ -123,7 +121,7 @@ function GM:CalcView(ply, pos, angles, fov) pos = pos - angles:Forward() * 100 end - return { origin=pos, angles=angles, drawviewer=true } + return { origin=pos, angles=angles } end end diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index 7db08aa..5770b70 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -1,8 +1,31 @@ -if GAMEMODE && IsValid(GAMEMODE.ScoreboardPanel) then +if GAMEMODE && IsValid(GAMEMODE.ScoreboardPanel) then GAMEMODE.ScoreboardPanel:Remove() end local menu +include("cl_colors.lua") + +GroupColors = {} +GroupColors["owner"] = PHGreen +GroupColors["superadmin"] = PHPink +GroupColors["headadmin"] = PHMauve +GroupColors["admin"] = PHYellow +GroupColors["moderator"] = PHBlue +GroupColors["operator"] = PHPink +GroupColors["superowner"] = PHRed +GroupColors["trusted"] = PHPeach +GroupColors["user"] = PHTeal + +GroupNames = {} +GroupNames["owner"] = "Owner" +GroupNames["superadmin"] = "Super Admin" +GroupNames["headadmin"] = "Head Admin" +GroupNames["admin"] = "Admin" +GroupNames["moderator"] = "Moderator" +GroupNames["operator"] = "Operator" +GroupNames["superowner"] = "Super Owner" +GroupNames["trusted"] = "Trusted" +GroupNames["user"] = "User" surface.CreateFont("ScoreboardPlayer" , { font = "coolvetica", @@ -12,12 +35,6 @@ surface.CreateFont("ScoreboardPlayer" , { italic = false }) -local function colMul(color, mul) - color.r = math.Clamp(math.Round(color.r * mul), 0, 255) - color.g = math.Clamp(math.Round(color.g * mul), 0, 255) - color.b = math.Clamp(math.Round(color.b * mul), 0, 255) -end - local muted = Material("icon32/muted.png", "noclamp") local skull = Material("husklesph/skull.png", "noclamp") @@ -25,12 +42,17 @@ local function addPlayerItem(self, mlist, ply, pteam) local but = vgui.Create("DButton") but.player = ply but.ctime = CurTime() - but:SetTall(draw.GetFontHeight("RobotoHUD-20") + 4) + but:SetTall(draw.GetFontHeight("RobotoHUD-20")) but:SetText("") function but:Paint(w, h) - surface.SetDrawColor(color_black) - + if GroupColors[ply:GetUserGroup()] ~= nil then + surface.SetDrawColor(Color(GroupColors[ply:GetUserGroup()].r, GroupColors[ply:GetUserGroup()].g, GroupColors[ply:GetUserGroup()].b)) + else + surface.SetDrawColor(PHGreen) + end + self:DrawFilledRect() + if IsValid(ply) && ply:IsPlayer() then local s = 4 if !ply:Alive() then @@ -39,7 +61,7 @@ local function addPlayerItem(self, mlist, ply, pteam) surface.DrawTexturedRect(s, h / 2 - 16, 32, 32) s = s + 32 + 4 end - + if ply:IsMuted() then surface.SetMaterial(muted) @@ -50,8 +72,14 @@ local function addPlayerItem(self, mlist, ply, pteam) end local col = color_white - draw.ShadowText(ply:Ping(), "RobotoHUD-L20", w - 4, 0, col, 2) - draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) + draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - 4, 0, col, 2) + if GroupNames[ply:GetUserGroup()] then + draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHDarkest, 0) + s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + 4 + draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) + else + draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) + end end end @@ -100,12 +128,11 @@ local function makeTeamList(parent, pteam) local hs = math.Round(draw.GetFontHeight("RobotoHUD-25") * 1.1) function pnl:Paint(w, h) - surface.SetDrawColor(220, 220, 220, 50) - surface.SetDrawColor(68, 68, 68, 120) + surface.SetDrawColor(PHDarkest) surface.DrawLine(0, hs, 0, h - 1) surface.DrawLine(w - 1, hs, w - 1, h - 1) surface.DrawLine(0, h - 1, w, h - 1) - surface.SetDrawColor(55, 55, 55, 120) + surface.SetDrawColor(PHDarker) surface.DrawRect(1, hs, w - 2, h - hs) end @@ -123,8 +150,8 @@ local function makeTeamList(parent, pteam) function headp:Paint(w, h) surface.SetDrawColor(68, 68, 68, 255) - draw.RoundedBoxEx(4, 0, 0, w, h, Color(68, 68, 68, 120), true, true, false, false) - draw.ShadowText(team.GetName(pteam), "RobotoHUD-25", 6, 0, team.GetColor(pteam), 0) + draw.RoundedBoxEx(4, 0, 0, w, h, PHDarkest, true, true, false, false) + draw.SimpleText(team.GetName(pteam), "RobotoHUD-25", 6, 0, team.GetColor(pteam), 0) end local but = vgui.Create("DButton", headp) @@ -155,7 +182,7 @@ local function makeTeamList(parent, pteam) col.b = col.b * 1.2 end - draw.ShadowText("Join team", "RobotoHUD-20", 2, h / 2 - th / 2, col, 0) + draw.SimpleText("Join team", "RobotoHUD-20", 2, h / 2 - th / 2, col, 0) end mlist = vgui.Create("DScrollPanel", pnl) @@ -179,8 +206,8 @@ local function makeTeamList(parent, pteam) local col = Color(190, 190, 190) function head:Paint(w, h) - draw.ShadowText("Name", "RobotoHUD-15", 4, 0, col, 0) - draw.ShadowText("Ping", "RobotoHUD-15", w - 4, 0, col, 2) + draw.SimpleText("Name", "RobotoHUD-15", 4, 0, col, 0) + draw.SimpleText("Ping", "RobotoHUD-15", w - 4, 0, col, 2) end mlist:AddItem(head) @@ -213,29 +240,11 @@ local function createScoreboardPanel() end function menu:Paint(w, h) - surface.SetDrawColor(40, 40, 40, 230) + surface.SetDrawColor(0, 0, 0, 0) surface.DrawRect(0, 0, w, h) end - - menu.Credits = vgui.Create("DPanel", menu) - menu.Credits:Dock(TOP) - menu.Credits:DockMargin(0, 0, 0, 4) - - function menu.Credits:Paint(w, h) - surface.SetFont("RobotoHUD-25") - local t = GAMEMODE.Name || "" - local tw = surface.GetTextSize(t) - draw.ShadowText(t, "RobotoHUD-25", 4, 0, Color(199, 49, 29), 0) - draw.ShadowText(tostring(GAMEMODE.Version || "error") .. ", maintained by DataNext, code by many cool people :)", "RobotoHUD-L12", 4 + tw + 24, h * 0.9, Color(220, 220, 220), 0, 4) - end - - function menu.Credits:PerformLayout() - surface.SetFont("RobotoHUD-25") - local _, h = surface.GetTextSize(GAMEMODE.Name || "") - self:SetTall(h) - end - - local bottom = vgui.Create("DPanel", menu) + + local bottom = vgui.Create("DPanel", menu) bottom:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) bottom:Dock(BOTTOM) bottom:DockMargin(0, 8, 0, 0) @@ -244,6 +253,7 @@ local function createScoreboardPanel() local tw = surface.GetTextSize("Spectate") function bottom:Paint(w, h) + draw.RoundedBox(0, 0, 0, w, h, PHDarker) local c for k, ply in pairs(team.GetPlayers(TEAM_SPEC)) do if c then @@ -264,17 +274,16 @@ local function createScoreboardPanel() but:DockMargin(0, 0, 4, 0) but:SetWide(tw + 8) - function but:Paint(w, h) - local col = Color(90, 90, 90, 160) - local colt = Color(190, 190, 190) - if self:IsDown() then - colMul(colt, 0.5) - elseif self:IsHovered() then - colMul(colt, 1.2) - end + function but:Paint(w, h) + local col = PHWhite + if self:IsDown() then + col = PHLessWhite + elseif self:IsHovered() then + col = PHLessWhite + end - draw.RoundedBox(4, 0, 0, w, h, col) - draw.ShadowText("Spectate", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) + draw.RoundedBox(0, 0, 0, w, h, PHDarkest) + draw.SimpleText("Spectate", "RobotoHUD-15", w / 2, h / 2, col, 1, 1) end function but:DoClick() @@ -285,7 +294,7 @@ local function createScoreboardPanel() main:Dock(FILL) function main:Paint(w, h) - surface.SetDrawColor(40, 40, 40, 230) + surface.SetDrawColor(40, 40, 40, 0) end menu.HuntersList = makeTeamList(main, TEAM_HUNTER) @@ -306,17 +315,13 @@ end function GM:ScoreboardHide() if IsValid(menu) then menu:Close() + DermaMenu() end end function GM:DoScoreboardActionPopup(ply) local actions = DermaMenu() - if ply:IsAdmin() then - local admin = actions:AddOption("Is an Admin") - admin:SetIcon("icon16/shield.png") - end - if ply != LocalPlayer() then local t = "Mute" if ply:IsMuted() then @@ -333,5 +338,28 @@ function GM:DoScoreboardActionPopup(ply) end end + local t = "View Steam Profile" + local steamprofile = actions:AddOption(t) + steamprofile:SetIcon("icon16/application.png") + + function steamprofile:DoClick() + if IsValid(ply) then + gui.OpenURL("https://steamcommunity.com/profiles/".. ply:SteamID64()) + end + end + +-- this requires ulx + if LocalPlayer():IsAdmin() then + local t = "Force Team Switch" + local switchteams = actions:AddOption(t) + switchteams:SetIcon("icon16/shield.png") + + function switchteams:DoClick() + if IsValid(ply) then + LocalPlayer():ConCommand("ulx teamswitch ".. ply:Nick()) + end + end + end + actions:Open() end diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index 241a384..4274ed9 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -1,15 +1,10 @@ include("sh_taunt.lua") +include("cl_colors.lua") local menu local lastCursorX local lastCursorY -local function colMul(color, mul) - color.r = math.Clamp(math.Round(color.r * mul), 0, 255) - color.g = math.Clamp(math.Round(color.g * mul), 0, 255) - color.b = math.Clamp(math.Round(color.b * mul), 0, 255) -end - local function saveCursor() lastCursorX, lastCursorY = input.GetCursorPos() end @@ -41,11 +36,12 @@ local function fillList(mlist, taunts, cat) but:SetText("") function but:Paint(w, h) - local col = Color(255, 255, 255) if self:IsDown() then - colMul(col, 0.5) + col = PHLesserWhite elseif self:IsHovered() then - colMul(col, 0.8) + col = PHLesserWhite + else + col = PHWhite end draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, 0, 1) draw.ShadowText(math.Round(t.soundDuration * 10) / 10 .. "s", "RobotoHUD-L10", w, h / 2, col, 2, 1) @@ -72,17 +68,16 @@ local function addCat(clist, name, taunts, mlist) but.CatName = name function but:Paint(w, h) - local col = Color(68, 68, 68, 160) - local colt = Color(190, 190, 190) + local colt = PHWhite if !self.Selected then - colMul(col, 0.7) + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) if self:IsDown() then - colMul(colt, 0.5) + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) elseif self:IsHovered() then - colMul(colt, 1.2) + col = Color(PHDark.r, PHDark.g, PHDark.b) end else - colMul(colt, 1.2) + col = Color(PHDark.r, PHDark.g, PHDark.b) end draw.RoundedBoxEx(4, 0, 0, w, h, col, true, false, true, false) @@ -138,17 +133,40 @@ local function openTauntMenu() menu:SetKeyboardInputEnabled(false) menu:SetDeleteOnClose(false) menu:SetDraggable(false) - menu:ShowCloseButton(true) + menu:ShowCloseButton(false) menu:DockPadding(8, 8 + draw.GetFontHeight("RobotoHUD-25"), 8, 8) + + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('marlett') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + closeButton.OnCursorEntered = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) + end + end + closeButton.OnCursorExited = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) + closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton.DoClick = function() + menu:Close() + end function menu:Paint(w, h) - surface.SetDrawColor(40, 40, 40, 230) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) surface.DrawRect(0, 0, w, h) surface.SetFont("RobotoHUD-25") local t = "Taunts" local tw, th = surface.GetTextSize(t) - draw.ShadowText(t, "RobotoHUD-25", 8, 2, Color(49, 142, 219), 0) - draw.ShadowText(TauntMenuPhrase, "RobotoHUD-L15", 8 + tw + 16, 2 + th * 0.90, Color(220, 220, 220), 0, 4) + draw.SimpleText(t, "RobotoHUD-25", 8, 2, PHBlue, 0) + draw.SimpleText(TauntMenuPhrase, "RobotoHUD-L15", 8 + tw + 16, 2 + th * 0.90, PHWhite, 0, 4) end local leftpnl = vgui.Create("DPanel", menu) @@ -165,12 +183,13 @@ local function openTauntMenu() but:SetText("") function but:Paint(w, h) - local col = Color(68, 68, 68, 160) - local colt = Color(190, 190, 190) + local colt = PHWhite if self:IsDown() then - colMul(colt, 0.5) + col = Color(PHDark.r, PHDark.g, PHDark.b) elseif self:IsHovered() then - colMul(colt, 1.2) + col = Color(PHDark.r, PHDark.g, PHDark.b) + else + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) end draw.RoundedBoxEx(4, 0, 0, w, h, col, true, true, true, true) @@ -204,9 +223,9 @@ local function openTauntMenu() mlist:Dock(FILL) function mlist:Paint(w, h) - surface.SetDrawColor(68, 68, 68, 160) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(55, 55, 55, 120) + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) surface.DrawRect(1, 1, w - 2, h - 2) end diff --git a/gamemodes/ultimateph/gamemode/sh_disguise.lua b/gamemodes/ultimateph/gamemode/sh_disguise.lua index 5011a97..9347d41 100644 --- a/gamemodes/ultimateph/gamemode/sh_disguise.lua +++ b/gamemodes/ultimateph/gamemode/sh_disguise.lua @@ -107,6 +107,10 @@ function PlayerMeta:CalculateRotatedDisguiseMinsMaxs() end function PlayerMeta:DisguiseRotationLocked() + if !self:IsDisguised() then + return self:GetNWBool("undisguiseRotationLock") + end + return self:GetNWBool("disguiseRotationLock") end @@ -141,3 +145,17 @@ function GM:PlayerCanDisguiseCurrentTarget(ply) return false, nil end + +hook.Add('UpdateAnimation', 'PropUndisguiseLock', function(ply) + if ply:IsDisguised() or !GAMEMODE.PropTpose:GetBool() or !ply:GetNWBool("undisguiseRotationLock") then + return + end + + local ang = ply:GetNWAngle("undisguiseRotationLockAng") + + if ang == ply:GetRenderAngles() then + return + end + + ply:SetRenderAngles(ang) +end) \ No newline at end of file diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index 5e731b0..4b1c17f 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -108,11 +108,6 @@ local function addTaunt(name, snd, pteam, sex, cats, duration, allowedModels) table.insert(AllowedTauntSounds[v], t) dur = dur + SoundDuration(v) count = count + 1 - - if SERVER then - -- network the taunt - resource.AddFile("sound/" .. v) - end end t.soundDuration = dur / count diff --git a/gamemodes/ultimateph/gamemode/shared.lua b/gamemodes/ultimateph/gamemode/shared.lua index 91ee5b6..7e6d54c 100644 --- a/gamemodes/ultimateph/gamemode/shared.lua +++ b/gamemodes/ultimateph/gamemode/shared.lua @@ -30,8 +30,8 @@ function PlayerMeta:IsProp() return self:Team() == TEAM_PROP end GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT team.SetUp(TEAM_SPEC, "Spectators", Color(120, 120, 120), false) -- Setting Joinable to false allows us to use team.BestAutoJoinTeam and have it only include the Hunters/Props teams. -team.SetUp(TEAM_HUNTER, "Hunters", Color(255, 150, 50)) -team.SetUp(TEAM_PROP, "Props", Color(50, 150, 255)) +team.SetUp(TEAM_HUNTER, "Hunters", Color(138, 173, 244)) +team.SetUp(TEAM_PROP, "Props", Color(237, 135, 150)) function GM:GetGameState() return self.GameState diff --git a/gamemodes/ultimateph/gamemode/sv_disguise.lua b/gamemodes/ultimateph/gamemode/sv_disguise.lua index 6e652f4..4588478 100644 --- a/gamemodes/ultimateph/gamemode/sv_disguise.lua +++ b/gamemodes/ultimateph/gamemode/sv_disguise.lua @@ -1,153 +1,165 @@ -include("sh_disguise.lua") - -local PlayerMeta = FindMetaTable("Player") - -function GM:PlayerDisguise(ply) - local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(ply) - if canDisguise then - if ply.LastDisguise && ply.LastDisguise + 1 > CurTime() then - return - end - - ply:DisguiseAsProp(target) - end -end - -function PlayerMeta:DisguiseAsProp(ent) - local hullxy, hullz = ent:GetPropSize() - if !self:CanFitHull(hullxy, hullxy, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to change") - return - end - - if !self:IsDisguised() then - self.OldPlayerModel = self:GetModel() - end - - self:Flashlight(false) - - -- create an entity for the disguise - -- we can't use a clientside entity as it needs a shadow - local dent = self:GetNWEntity("disguiseEntity") - if !IsValid(dent) then - dent = ents.Create("ph_disguise") - self:SetNWEntity("disguiseEntity", dent) - dent.PropOwner = self - dent:SetPos(self:GetPos()) - dent:Spawn() - end - dent:SetModel(ent:GetModel()) - - self:SetNWBool("disguised", true) - self:SetNWString("disguiseModel", ent:GetModel()) - self:SetNWVector("disguiseMins", ent:OBBMins()) - self:SetNWVector("disguiseMaxs", ent:OBBMaxs()) - self:SetNWInt("disguiseSkin", ent:GetSkin()) - self:SetNWBool("disguiseRotationLock", false) - self:SetColor(Color(255, 0, 0, 0)) - self:SetRenderMode(RENDERMODE_NONE) - self:SetModel(ent:GetModel()) - self:SetNoDraw(false) - self:DrawShadow(false) - GAMEMODE:PlayerSetNewHull(self, hullxy, hullz, hullz) - - local maxHealth = 1 - local volume = 1 - local phys = ent:GetPhysicsObject() - if IsValid(phys) then - maxHealth = math.Clamp(math.Round(phys:GetVolume() / 230), 1, 200) - volume = phys:GetVolume() - end - - self.PercentageHealth = math.min(self:Health() / self:GetHMaxHealth(), self.PercentageHealth || 1) - local per = math.Clamp(self.PercentageHealth * maxHealth, 1, 200) - self:SetHealth(per) - self:SetHMaxHealth(maxHealth) - self:SetNWFloat("disguiseVolume", volume) - - self:CalculateSpeed() - - local offset = Vector(0, 0, ent:OBBMaxs().z - self:OBBMins().z + 10) - self:SetViewOffset(offset) - self:SetViewOffsetDucked(offset) - - self:EmitSound("weapons/bugbait/bugbait_squeeze" .. math.random(1, 3) .. ".wav") - self.LastDisguise = CurTime() - - local eff = EffectData() - eff:SetOrigin(self:GetPos() + Vector(0, 0, 1)) - eff:SetScale(hullxy) - eff:SetMagnitude(hullz) - util.Effect("ph_disguise", eff, true, true) -end - -function PlayerMeta:IsDisguised() - return self:GetNWBool("disguised", false) -end - -function PlayerMeta:UnDisguise() - local dent = self:GetNWEntity("disguiseEntity") - if IsValid(dent) then - dent:Remove() - end - - self.PercentageHealth = nil - self:SetNWBool("disguised", false) - self:SetColor(Color(255, 255, 255, 255)) - self:SetNoDraw(false) - self:DrawShadow(true) - self:SetRenderMode(RENDERMODE_NORMAL) - GAMEMODE:PlayerSetNewHull(self) - if self.OldPlayerModel then - self:SetModel(self.OldPlayerModel) - self.OldPlayerModel = nil - end - - self:SetViewOffset(Vector(0, 0, 64)) - self:SetViewOffsetDucked(Vector(0, 0, 28)) - - self:CalculateSpeed() -end - -function PlayerMeta:DisguiseLockRotation() - if !self:IsDisguised() then return end - - local mins, maxs = self:CalculateRotatedDisguiseMinsMaxs() - local hullx = math.Round((maxs.x - mins.x) / 2) - local hully = math.Round((maxs.y - mins.y) / 2) - local hullz = math.Round(maxs.z - mins.z) - if !self:CanFitHull(hullx, hully, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to lock rotation, move into a more open area") - return - end - - local ang = self:EyeAngles() - self:SetNWBool("disguiseRotationLock", true) - self:SetNWFloat("disguiseRotationLockYaw", ang.y) - GAMEMODE:PlayerSetHull(self, hullx, hully, hullz, hullz) -end - -function PlayerMeta:DisguiseUnlockRotation() - local maxs = self:GetNWVector("disguiseMaxs") - local mins = self:GetNWVector("disguiseMins") - local hullxy = math.Round(math.Max(maxs.x - mins.x, maxs.y - mins.y) / 2) - local hullz = math.Round(maxs.z - mins.z) - if !self:CanFitHull(hullxy, hullxy, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to unlock rotation, move into a more open area") - return - end - - self:SetNWBool("disguiseRotationLock", false) - GAMEMODE:PlayerSetHull(self, hullxy, hullxy, hullz, hullz) -end - -concommand.Add("ph_lockrotation", function(ply, com, args) - if !IsValid(ply) then return end - if !ply:IsDisguised() then return end - - if ply:DisguiseRotationLocked() then - ply:DisguiseUnlockRotation() - else - ply:DisguiseLockRotation() - end -end) +include("sh_disguise.lua") + +local PlayerMeta = FindMetaTable("Player") + +function GM:PlayerDisguise(ply) + local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(ply) + if canDisguise then + if ply.LastDisguise && ply.LastDisguise + 1 > CurTime() then + return + end + + ply:DisguiseAsProp(target) + end +end + +function PlayerMeta:DisguiseAsProp(ent) + local hullxy, hullz = ent:GetPropSize() + if !self:CanFitHull(hullxy, hullxy, hullz) then + self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to change") + return + end + + if !self:IsDisguised() then + self.OldPlayerModel = self:GetModel() + end + + self:Flashlight(false) + + -- create an entity for the disguise + -- we can't use a clientside entity as it needs a shadow + local dent = self:GetNWEntity("disguiseEntity") + if !IsValid(dent) then + dent = ents.Create("ph_disguise") + self:SetNWEntity("disguiseEntity", dent) + dent.PropOwner = self + dent:SetPos(self:GetPos()) + dent:Spawn() + end + dent:SetModel(ent:GetModel()) + + self:SetNWBool("disguised", true) + self:SetNWString("disguiseModel", ent:GetModel()) + self:SetNWVector("disguiseMins", ent:OBBMins()) + self:SetNWVector("disguiseMaxs", ent:OBBMaxs()) + self:SetNWInt("disguiseSkin", ent:GetSkin()) + self:SetNWBool("disguiseRotationLock", false) + self:SetColor(Color(255, 0, 0, 0)) + self:SetRenderMode(RENDERMODE_NONE) + self:SetModel(ent:GetModel()) + self:SetNoDraw(false) + self:DrawShadow(false) + GAMEMODE:PlayerSetNewHull(self, hullxy, hullz, hullz) + + local maxHealth = 1 + local volume = 1 + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + maxHealth = math.Clamp(math.Round(phys:GetVolume() / 230), 1, 200) + volume = phys:GetVolume() + end + + self.PercentageHealth = math.min(self:Health() / self:GetHMaxHealth(), self.PercentageHealth || 1) + local per = math.Clamp(self.PercentageHealth * maxHealth, 1, 200) + self:SetHealth(per) + self:SetHMaxHealth(maxHealth) + self:SetNWFloat("disguiseVolume", volume) + + self:CalculateSpeed() + + local offset = Vector(0, 0, ent:OBBMaxs().z - self:OBBMins().z + 10) + self:SetViewOffset(offset) + self:SetViewOffsetDucked(offset) + + self:EmitSound("weapons/bugbait/bugbait_squeeze" .. math.random(1, 3) .. ".wav") + self.LastDisguise = CurTime() + + local eff = EffectData() + eff:SetOrigin(self:GetPos() + Vector(0, 0, 1)) + eff:SetScale(hullxy) + eff:SetMagnitude(hullz) + util.Effect("ph_disguise", eff, true, true) +end + +function PlayerMeta:IsDisguised() + return self:GetNWBool("disguised", false) +end + +function PlayerMeta:UnDisguise() + local dent = self:GetNWEntity("disguiseEntity") + if IsValid(dent) then + dent:Remove() + end + + self.PercentageHealth = nil + self:SetNWBool("disguised", false) + self:SetNWBool("undisguiseRotationLock", false) + self:SetColor(Color(255, 255, 255, 255)) + self:SetNoDraw(false) + self:DrawShadow(true) + self:SetRenderMode(RENDERMODE_NORMAL) + GAMEMODE:PlayerSetNewHull(self) + if self.OldPlayerModel then + self:SetModel(self.OldPlayerModel) + self.OldPlayerModel = nil + end + + self:SetViewOffset(Vector(0, 0, 64)) + self:SetViewOffsetDucked(Vector(0, 0, 28)) + + self:CalculateSpeed() +end + +function PlayerMeta:DisguiseLockRotation() + if !self:IsDisguised() then + local ang = self:GetRenderAngles() + self:SetNWAngle("undisguiseRotationLockAng", ang) + self:SetNWBool("undisguiseRotationLock", true) + return + end + + local mins, maxs = self:CalculateRotatedDisguiseMinsMaxs() + local hullx = math.Round((maxs.x - mins.x) / 2) + local hully = math.Round((maxs.y - mins.y) / 2) + local hullz = math.Round(maxs.z - mins.z) + if !self:CanFitHull(hullx, hully, hullz) then + self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to lock rotation, move into a more open area") + return + end + + local ang = self:EyeAngles() + self:SetNWBool("disguiseRotationLock", true) + self:SetNWFloat("disguiseRotationLockYaw", ang.y) + GAMEMODE:PlayerSetHull(self, hullx, hully, hullz, hullz) +end + +function PlayerMeta:DisguiseUnlockRotation() + if !self:IsDisguised() then + self:SetNWBool("undisguiseRotationLock", false) + return + end + + local maxs = self:GetNWVector("disguiseMaxs") + local mins = self:GetNWVector("disguiseMins") + local hullxy = math.Round(math.Max(maxs.x - mins.x, maxs.y - mins.y) / 2) + local hullz = math.Round(maxs.z - mins.z) + if !self:CanFitHull(hullxy, hullxy, hullz) then + self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to unlock rotation, move into a more open area") + return + end + + self:SetNWBool("disguiseRotationLock", false) + GAMEMODE:PlayerSetHull(self, hullxy, hullxy, hullz, hullz) +end + +concommand.Add("ph_lockrotation", function(ply, com, args) + if !IsValid(ply) or ply:Team() ~= TEAM_PROP then + return + end + + if ply:DisguiseRotationLocked() then + ply:DisguiseUnlockRotation() + else + ply:DisguiseLockRotation() + end +end) diff --git a/gamemodes/ultimateph/gamemode/sv_mapvote.lua b/gamemodes/ultimateph/gamemode/sv_mapvote.lua index f9782ec..8b3464e 100644 --- a/gamemodes/ultimateph/gamemode/sv_mapvote.lua +++ b/gamemodes/ultimateph/gamemode/sv_mapvote.lua @@ -127,10 +127,10 @@ function GM:StartMapVote() local initHookTbl = hook.GetTable().Initialize if initHookTbl && initHookTbl.MapVoteConfigSetup then self:SetGameState(ROUND_MAPVOTE) - MapVote.Start() + MapVote.Start() return end - + -- allow developers to override builtin mapvote if hook.GetTable().PHStartMapVote then self:SetGameState(ROUND_MAPVOTE) @@ -250,4 +250,3 @@ concommand.Add("ph_votemap", function(ply, com, args) GAMEMODE:NetworkMapVotes() end end) - diff --git a/gamemodes/ultimateph/gamemode/sv_respawn.lua b/gamemodes/ultimateph/gamemode/sv_respawn.lua index 60ee9c8..f788a0e 100644 --- a/gamemodes/ultimateph/gamemode/sv_respawn.lua +++ b/gamemodes/ultimateph/gamemode/sv_respawn.lua @@ -3,7 +3,7 @@ function GM:CanRespawn(ply) return false end - if self:GetGameState() == ROUND_WAIT then + if self:GetGameState() == ROUND_WAIT or self:GetGameState() == ROUND_HIDE then if ply.NextSpawnTime && ply.NextSpawnTime > CurTime() then return end if ply:KeyPressed(IN_JUMP) || ply:KeyPressed(IN_ATTACK) then diff --git a/gamemodes/ultimateph/gamemode/sv_rounds.lua b/gamemodes/ultimateph/gamemode/sv_rounds.lua index c95b6b0..22cd9aa 100644 --- a/gamemodes/ultimateph/gamemode/sv_rounds.lua +++ b/gamemodes/ultimateph/gamemode/sv_rounds.lua @@ -3,6 +3,7 @@ include("sv_awards.lua") util.AddNetworkString("gamestate") util.AddNetworkString("round_victor") util.AddNetworkString("gamerules") +util.AddNetworkString("rounds") GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() @@ -146,6 +147,9 @@ function GM:SetupRound() self:CleanupMap() self.Rounds = self.Rounds + 1 + net.Start("rounds") + net.WriteInt(self.Rounds, 5) + net.Broadcast() if self.Rounds == self.RoundLimit:GetInt() then GlobalChatMsg(Color(255, 0, 0), "This is the LAST ROUND!") @@ -324,4 +328,3 @@ local function ForceEndRound(ply, command, args) end end concommand.Add("ph_endround", ForceEndRound) - From b5130b97b2f26f7f0a063b4ab8436fc2dc237823 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Thu, 25 Sep 2025 22:24:24 +1000 Subject: [PATCH 02/25] change to /n for new lines, fix deaf hunters, allow disguised props to sprint in light of new convars, clean up hooks --- gamemodes/ultimateph/gamemode/cl_aimlaser.lua | 50 +- .../ultimateph/gamemode/cl_bannedmodels.lua | 346 ++--- gamemodes/ultimateph/gamemode/cl_chatmsg.lua | 20 +- gamemodes/ultimateph/gamemode/cl_disguise.lua | 138 +- .../ultimateph/gamemode/cl_endroundboard.lua | 660 ++++---- .../ultimateph/gamemode/cl_fixplayercolor.lua | 76 +- gamemodes/ultimateph/gamemode/cl_health.lua | 10 +- .../ultimateph/gamemode/cl_helpscreen.lua | 164 +- gamemodes/ultimateph/gamemode/cl_hud.lua | 344 ++--- gamemodes/ultimateph/gamemode/cl_init.lua | 340 ++--- gamemodes/ultimateph/gamemode/cl_killfeed.lua | 232 +-- gamemodes/ultimateph/gamemode/cl_mapvote.lua | 112 +- gamemodes/ultimateph/gamemode/cl_ragdoll.lua | 56 +- gamemodes/ultimateph/gamemode/cl_rounds.lua | 112 +- .../ultimateph/gamemode/cl_scoreboard.lua | 730 ++++----- gamemodes/ultimateph/gamemode/cl_spectate.lua | 44 +- gamemodes/ultimateph/gamemode/cl_taunt.lua | 508 +++---- .../ultimateph/gamemode/cl_voicepanels.lua | 228 +-- gamemodes/ultimateph/gamemode/init.lua | 256 ++-- gamemodes/ultimateph/gamemode/sh_disguise.lua | 322 ++-- gamemodes/ultimateph/gamemode/sh_init.lua | 2 +- gamemodes/ultimateph/gamemode/sh_taunt.lua | 332 ++-- gamemodes/ultimateph/gamemode/shared.lua | 183 +-- gamemodes/ultimateph/gamemode/sv_awards.lua | 230 +-- .../ultimateph/gamemode/sv_bannedmodels.lua | 176 +-- gamemodes/ultimateph/gamemode/sv_chatmsg.lua | 40 +- gamemodes/ultimateph/gamemode/sv_health.lua | 22 +- gamemodes/ultimateph/gamemode/sv_killfeed.lua | 156 +- gamemodes/ultimateph/gamemode/sv_mapvote.lua | 504 +++--- gamemodes/ultimateph/gamemode/sv_player.lua | 1351 ++++++++--------- .../ultimateph/gamemode/sv_playercolor.lua | 20 +- gamemodes/ultimateph/gamemode/sv_ragdoll.lua | 322 ++-- gamemodes/ultimateph/gamemode/sv_realism.lua | 56 +- gamemodes/ultimateph/gamemode/sv_respawn.lua | 30 +- gamemodes/ultimateph/gamemode/sv_rounds.lua | 660 ++++---- gamemodes/ultimateph/gamemode/sv_spectate.lua | 266 ++-- gamemodes/ultimateph/gamemode/sv_taunt.lua | 306 ++-- gamemodes/ultimateph/gamemode/sv_teams.lua | 174 +-- gamemodes/ultimateph/gamemode/sv_version.lua | 106 +- 39 files changed, 4837 insertions(+), 4847 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_aimlaser.lua b/gamemodes/ultimateph/gamemode/cl_aimlaser.lua index 2c0082e..3c83d4d 100644 --- a/gamemodes/ultimateph/gamemode/cl_aimlaser.lua +++ b/gamemodes/ultimateph/gamemode/cl_aimlaser.lua @@ -8,36 +8,36 @@ local dot_color = Color(255,255,255) local hunters_aim = {} function GM:PostDrawTranslucentRenderables() - hunters_aim = {} - self:GetHuntersAim() + hunters_aim = {} + self:GetHuntersAim() - -- When "ph_hunter_aim_laser" set to 0, nobody can see the beam - -- set to 1 == spectator only, set to 2 == props and spectator - -- Also, we don't want the hunters to see the ray, to prevent eventual visual bugs - local visibility = GetConVar("ph_hunter_aim_laser"):GetInt() + -- When "ph_hunter_aim_laser" set to 0, nobody can see the beam + -- set to 1 == spectator only, set to 2 == props and spectator + -- Also, we don't want the hunters to see the ray, to prevent eventual visual bugs + local visibility = GetConVar("ph_hunter_aim_laser"):GetInt() - if visibility == 0 then return end + if visibility == 0 then return end - if visibility == 1 and not LocalPlayer():IsSpectator() then return end - if visibility == 2 and not (LocalPlayer():IsSpectator() or LocalPlayer():IsProp()) then return end + if visibility == 1 and not LocalPlayer():IsSpectator() then return end + if visibility == 2 and not (LocalPlayer():IsSpectator() or LocalPlayer():IsProp()) then return end - for _, h_aim in pairs(hunters_aim) do - render.SetMaterial(beamMat) - render.DrawBeam(h_aim.eye_pos, h_aim.looking.HitPos, ray_width, 0, 1, ray_color) + for _, h_aim in pairs(hunters_aim) do + render.SetMaterial(beamMat) + render.DrawBeam(h_aim.eye_pos, h_aim.looking.HitPos, ray_width, 0, 1, ray_color) - render.SetMaterial(dotMat) - local size = math.random(8, 16) - render.DrawQuadEasy(h_aim.looking.HitPos + h_aim.looking.HitNormal, h_aim.looking.HitNormal, size, size, dot_color, 0) - end + render.SetMaterial(dotMat) + local size = math.random(8, 16) + render.DrawQuadEasy(h_aim.looking.HitPos + h_aim.looking.HitNormal, h_aim.looking.HitNormal, size, size, dot_color, 0) + end end function GM:GetHuntersAim() - for _, ply in pairs(player.GetAll()) do - if ply:IsHunter() then - local t = {} - t.eye_pos = ply:EyePos() - t.looking = ply:GetEyeTrace() - table.insert(hunters_aim, t) - end - end -end \ No newline at end of file + for _, ply in pairs(player.GetAll()) do + if ply:IsHunter() then + local t = {} + t.eye_pos = ply:EyePos() + t.looking = ply:GetEyeTrace() + table.insert(hunters_aim, t) + end + end +end diff --git a/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua b/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua index a300f29..374bcaf 100644 --- a/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua +++ b/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua @@ -1,173 +1,173 @@ --- This file provides the functionality for accessing the banned models --- menu. It is used locally to determine if a model is viable (aka --- whether or not it gets an outline when moused over). - -GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. -local menu - -function GM:IsModelBanned(model) - return self.BannedModels[model] == true -end - -function GM:AddBannedModel(model) - if self.BannedModels[model] == true then return end - - self.BannedModels[model] = true - menu.AddModel(model) -end - -function GM:RemoveBannedModel(model) - if self.BannedModels[model] != true then return end - - self.BannedModels[model] = nil - menu.RemoveModel(model) -end - -net.Receive("ph_bannedmodels_getall", function(len) - GAMEMODE.BannedModels = {} - - local model = net.ReadString() - while model != "" do - GAMEMODE:AddBannedModel(model) - model = net.ReadString() - end -end) - -net.Receive("ph_bannedmodels_add", function(len) - local model = net.ReadString() - GAMEMODE:AddBannedModel(model) -end) - -net.Receive("ph_bannedmodels_remove", function(len) - local model = net.ReadString() - GAMEMODE:RemoveBannedModel(model) -end) - -concommand.Add("ph_bannedmodels_menu", function(client) - menu:SetVisible(true) -end) - --- This is all the code to create the banned models menu. -function GM:CreateBannedModelsMenu() - -- The main window that will contain all of the functionality for display, adding, and - -- removing banned models. - menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) - menu:SetTitle("Banned Models") - menu:Center() - menu:SetDraggable(true) - menu:ShowCloseButton(true) - menu:MakePopup() - menu:SetDeleteOnClose(false) - menu:SetVisible(false) - - -- Create a text box at the top of the window so the player can input new banned models. - local entry = vgui.Create("DTextEntry", menu) - entry:Dock(TOP) - if LocalPlayer():IsAdmin() then - entry:SetPlaceholderText("Hover for usage information.") - entry:SetTooltip([[ - To ban a model from usage put the path - to the model into this text box and - press Enter. - EXAMPLE INPUTS - models/props/cs_assault/money.mdl - models/props_borealis/bluebarrel001.mdl - models/props/cs_office/projector_remote.mdl]]) - else - entry:SetPlaceholderText("You must be an admin to ban models.") - entry:SetTooltip("You must be an admin to ban models.") - entry:SetEnabled(false) - end - - function entry:OnEnter() - -- This client side check is for informational purposes. - if !LocalPlayer():IsAdmin() then - chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") - return - end - - local modelToBan = entry:GetText() - -- This client side check is for informational purposes. - if modelToBan == "" then - chat.AddText(Color(255, 50, 50), "Error when attempting to ban model: no input text was given.") - return - end - -- This client side check is for informational purposes. - if GAMEMODE:IsModelBanned(modelToBan) then - chat.AddText(Color(255, 50, 50), "That model is already banned.") - return - end - - net.Start("ph_bannedmodels_add") - net.WriteString(modelToBan) - net.SendToServer() - entry:SetText("") - end - - -- Makes a scroll bar on the right side in the event that there are a LOT of - -- banned models and we need to be able to scroll. Will not appear unless - -- there are enough items in the list to require it. - local scrollPanel = vgui.Create("DScrollPanel", menu) - scrollPanel:Dock(FILL) - - local modelIconWidth = 128 - local modelIconHeight = 128 - local usableWidthForModelIcons = menu:GetWide() - scrollPanel:GetVBar():GetWide() -- Don't want icons to overlap the scroll bar. - local numCols = math.floor(usableWidthForModelIcons / modelIconWidth) - -- This offset is almost right, but there's 10 pixels more on the left than on the right and I can't figure out why. :( - local leftOffset = (usableWidthForModelIcons - (modelIconWidth * numCols)) / 2 - - -- This is the grid that will give the icons a nice layout. - local grid = vgui.Create("DGrid", scrollPanel) - menu.grid = grid - grid:SetCols(numCols) - grid:SetPos(leftOffset, 10) - grid:SetColWide(modelIconWidth) - grid:SetRowHeight(modelIconHeight) - - -- Allows functions outside of the menu to update the icons. Useful for updating the menu - -- live if it's open when a net message is received to add a model. - menu.AddModel = function(model) - local modelIcon = vgui.Create("SpawnIcon") - modelIcon:SetPos(75, 75) - modelIcon:SetSize(modelIconWidth, modelIconHeight) - modelIcon:SetEnabled(false) - modelIcon:SetCursor("arrow") - modelIcon:SetModel(model) - grid:AddItem(modelIcon) - - local unbanButton = vgui.Create("DButton", modelIcon) - unbanButton:SetSize(modelIconWidth, modelIconHeight / 4) - unbanButton:SetText("Unban Model") - unbanButton:SetPos(0, modelIconHeight - unbanButton:GetTall()) - unbanButton:SetVisible(false) - - function unbanButton:DoClick() - -- This client side check is for informational purposes. - if !LocalPlayer():IsAdmin() then - chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") - return - end - - net.Start("ph_bannedmodels_remove") - net.WriteString(self:GetParent():GetModelName()) - net.SendToServer() - end - - -- Logic for showing/hiding the unban button. - function modelIcon:Think() - self:GetChild(1):SetVisible(LocalPlayer():IsAdmin() && (self:IsHovered() || self:IsChildHovered())) - end - end - - -- Same as menu.AddModel but for removing models from the grid. - menu.RemoveModel = function(model) - for _, value in pairs(menu.grid:GetItems()) do - if value:GetModelName() == model then - menu.grid:RemoveItem(value) - break - end - end - end -end +-- This file provides the functionality for accessing the banned models +-- menu. It is used locally to determine if a model is viable (aka +-- whether or not it gets an outline when moused over). + +GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. +local menu + +function GM:IsModelBanned(model) + return self.BannedModels[model] == true +end + +function GM:AddBannedModel(model) + if self.BannedModels[model] == true then return end + + self.BannedModels[model] = true + menu.AddModel(model) +end + +function GM:RemoveBannedModel(model) + if self.BannedModels[model] != true then return end + + self.BannedModels[model] = nil + menu.RemoveModel(model) +end + +net.Receive("ph_bannedmodels_getall", function(len) + GAMEMODE.BannedModels = {} + + local model = net.ReadString() + while model != "" do + GAMEMODE:AddBannedModel(model) + model = net.ReadString() + end +end) + +net.Receive("ph_bannedmodels_add", function(len) + local model = net.ReadString() + GAMEMODE:AddBannedModel(model) +end) + +net.Receive("ph_bannedmodels_remove", function(len) + local model = net.ReadString() + GAMEMODE:RemoveBannedModel(model) +end) + +concommand.Add("ph_bannedmodels_menu", function(client) + menu:SetVisible(true) +end) + +-- This is all the code to create the banned models menu. +function GM:CreateBannedModelsMenu() + -- The main window that will contain all of the functionality for display, adding, and + -- removing banned models. + menu = vgui.Create("DFrame") + menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) + menu:SetTitle("Banned Models") + menu:Center() + menu:SetDraggable(true) + menu:ShowCloseButton(true) + menu:MakePopup() + menu:SetDeleteOnClose(false) + menu:SetVisible(false) + + -- Create a text box at the top of the window so the player can input new banned models. + local entry = vgui.Create("DTextEntry", menu) + entry:Dock(TOP) + if LocalPlayer():IsAdmin() then + entry:SetPlaceholderText("Hover for usage information.") + entry:SetTooltip([[ + To ban a model from usage put the path + to the model into this text box and + press Enter. + EXAMPLE INPUTS + models/props/cs_assault/money.mdl + models/props_borealis/bluebarrel001.mdl + models/props/cs_office/projector_remote.mdl]]) + else + entry:SetPlaceholderText("You must be an admin to ban models.") + entry:SetTooltip("You must be an admin to ban models.") + entry:SetEnabled(false) + end + + function entry:OnEnter() + -- This client side check is for informational purposes. + if !LocalPlayer():IsAdmin() then + chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") + return + end + + local modelToBan = entry:GetText() + -- This client side check is for informational purposes. + if modelToBan == "" then + chat.AddText(Color(255, 50, 50), "Error when attempting to ban model: no input text was given.") + return + end + -- This client side check is for informational purposes. + if GAMEMODE:IsModelBanned(modelToBan) then + chat.AddText(Color(255, 50, 50), "That model is already banned.") + return + end + + net.Start("ph_bannedmodels_add") + net.WriteString(modelToBan) + net.SendToServer() + entry:SetText("") + end + + -- Makes a scroll bar on the right side in the event that there are a LOT of + -- banned models and we need to be able to scroll. Will not appear unless + -- there are enough items in the list to require it. + local scrollPanel = vgui.Create("DScrollPanel", menu) + scrollPanel:Dock(FILL) + + local modelIconWidth = 128 + local modelIconHeight = 128 + local usableWidthForModelIcons = menu:GetWide() - scrollPanel:GetVBar():GetWide() -- Don't want icons to overlap the scroll bar. + local numCols = math.floor(usableWidthForModelIcons / modelIconWidth) + -- This offset is almost right, but there's 10 pixels more on the left than on the right and I can't figure out why. :( + local leftOffset = (usableWidthForModelIcons - (modelIconWidth * numCols)) / 2 + + -- This is the grid that will give the icons a nice layout. + local grid = vgui.Create("DGrid", scrollPanel) + menu.grid = grid + grid:SetCols(numCols) + grid:SetPos(leftOffset, 10) + grid:SetColWide(modelIconWidth) + grid:SetRowHeight(modelIconHeight) + + -- Allows functions outside of the menu to update the icons. Useful for updating the menu + -- live if it's open when a net message is received to add a model. + menu.AddModel = function(model) + local modelIcon = vgui.Create("SpawnIcon") + modelIcon:SetPos(75, 75) + modelIcon:SetSize(modelIconWidth, modelIconHeight) + modelIcon:SetEnabled(false) + modelIcon:SetCursor("arrow") + modelIcon:SetModel(model) + grid:AddItem(modelIcon) + + local unbanButton = vgui.Create("DButton", modelIcon) + unbanButton:SetSize(modelIconWidth, modelIconHeight / 4) + unbanButton:SetText("Unban Model") + unbanButton:SetPos(0, modelIconHeight - unbanButton:GetTall()) + unbanButton:SetVisible(false) + + function unbanButton:DoClick() + -- This client side check is for informational purposes. + if !LocalPlayer():IsAdmin() then + chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") + return + end + + net.Start("ph_bannedmodels_remove") + net.WriteString(self:GetParent():GetModelName()) + net.SendToServer() + end + + -- Logic for showing/hiding the unban button. + function modelIcon:Think() + self:GetChild(1):SetVisible(LocalPlayer():IsAdmin() && (self:IsHovered() || self:IsChildHovered())) + end + end + + -- Same as menu.AddModel but for removing models from the grid. + menu.RemoveModel = function(model) + for _, value in pairs(menu.grid:GetItems()) do + if value:GetModelName() == model then + menu.grid:RemoveItem(value) + break + end + end + end +end diff --git a/gamemodes/ultimateph/gamemode/cl_chatmsg.lua b/gamemodes/ultimateph/gamemode/cl_chatmsg.lua index 712fd7c..8b59c46 100644 --- a/gamemodes/ultimateph/gamemode/cl_chatmsg.lua +++ b/gamemodes/ultimateph/gamemode/cl_chatmsg.lua @@ -1,10 +1,10 @@ --- This file provides the functionality for printing chat messages to the client --- that are sent by the server. This is used instead of something like MsgC or --- PrintMessage because we want the messages to be colored (something PrintMessage --- can't do) and we want to be able to send messages from the server (MsgC can't do --- this). - -net.Receive("ph_chatmsg", function(len) - local tbl = net.ReadTable() - chat.AddText(unpack(tbl)) -end) +-- This file provides the functionality for printing chat messages to the client +-- that are sent by the server. This is used instead of something like MsgC or +-- PrintMessage because we want the messages to be colored (something PrintMessage +-- can't do) and we want to be able to send messages from the server (MsgC can't do +-- this). + +net.Receive("ph_chatmsg", function(len) + local tbl = net.ReadTable() + chat.AddText(unpack(tbl)) +end) diff --git a/gamemodes/ultimateph/gamemode/cl_disguise.lua b/gamemodes/ultimateph/gamemode/cl_disguise.lua index 5d5d742..2941661 100644 --- a/gamemodes/ultimateph/gamemode/cl_disguise.lua +++ b/gamemodes/ultimateph/gamemode/cl_disguise.lua @@ -1,69 +1,69 @@ -include("sh_disguise.lua") - -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:IsDisguised() - return self:GetNWBool("disguised", false) -end - -local function renderDis(self) - for k, ply in pairs(player.GetAll()) do - if ply:Alive() && ply:IsDisguised() then - local model = ply:GetNWString("disguiseModel") - if model && model != "" then - local ent = ply:GetNWEntity("disguiseEntity") - if IsValid(ent) then - local mins = ply:GetNWVector("disguiseMins") - local maxs = ply:GetNWVector("disguiseMaxs") - local ang = ply:EyeAngles() - ang.p = 0 - ang.r = 0 - if ply:DisguiseRotationLocked() then - ang.y = ply:GetNWFloat("disguiseRotationLockYaw") - end - local pos = ply:GetPos() + Vector(0, 0, -mins.z) - local center = (maxs + mins) / 2 - center.z = 0 - center:Rotate(ang) - ent:SetPos(pos - center) - ent:SetAngles(ang) - ent:SetSkin(ply:GetNWInt("disguiseSkin", 1)) - end - end - end - end -end - -function GM:RenderDisguises() - cam.Start3D(EyePos(), EyeAngles()) - local b, err = pcall(renderDis, self) - cam.End3D() - if !b then - MsgC(Color(255, 0, 0), err .. "\n") - end -end - -function GM:RenderDisguiseHalo() - local client = LocalPlayer() - if client:IsProp() then - local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(client) - if canDisguise then - local col = Color(50, 220, 50) - local hullxy, hullz = target:GetPropSize() - if !client:CanFitHull(hullxy, hullxy, hullz) then - col = Color(220, 50, 50) - end - halo.Add({target}, col, 2, 2, 2, true, true) - end - - local tab = {} - for k, ply in pairs(player.GetAll()) do - if ply != client && ply:IsProp() && ply:IsDisguised() then - if IsValid(ply.PropMod) then - table.insert(tab, ply.PropMod) - end - end - end - halo.Add(tab, team.GetColor(TEAM_PROP), 2, 2, 2, true, false) - end -end +include("sh_disguise.lua") + +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:IsDisguised() + return self:GetNWBool("disguised", false) +end + +local function renderDis(self) + for k, ply in pairs(player.GetAll()) do + if ply:Alive() && ply:IsDisguised() then + local model = ply:GetNWString("disguiseModel") + if model && model != "" then + local ent = ply:GetNWEntity("disguiseEntity") + if IsValid(ent) then + local mins = ply:GetNWVector("disguiseMins") + local maxs = ply:GetNWVector("disguiseMaxs") + local ang = ply:EyeAngles() + ang.p = 0 + ang.r = 0 + if ply:DisguiseRotationLocked() then + ang.y = ply:GetNWFloat("disguiseRotationLockYaw") + end + local pos = ply:GetPos() + Vector(0, 0, -mins.z) + local center = (maxs + mins) / 2 + center.z = 0 + center:Rotate(ang) + ent:SetPos(pos - center) + ent:SetAngles(ang) + ent:SetSkin(ply:GetNWInt("disguiseSkin", 1)) + end + end + end + end +end + +function GM:RenderDisguises() + cam.Start3D(EyePos(), EyeAngles()) + local b, err = pcall(renderDis, self) + cam.End3D() + if !b then + MsgC(Color(255, 0, 0), err .. "\n") + end +end + +function GM:RenderDisguiseHalo() + local client = LocalPlayer() + if client:IsProp() then + local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(client) + if canDisguise then + local col = Color(50, 220, 50) + local hullxy, hullz = target:GetPropSize() + if !client:CanFitHull(hullxy, hullxy, hullz) then + col = Color(220, 50, 50) + end + halo.Add({target}, col, 2, 2, 2, true, true) + end + + local tab = {} + for k, ply in pairs(player.GetAll()) do + if ply != client && ply:IsProp() && ply:IsDisguised() then + if IsValid(ply.PropMod) then + table.insert(tab, ply.PropMod) + end + end + end + halo.Add(tab, team.GetColor(TEAM_PROP), 2, 2, 2, true, false) + end +end diff --git a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua index e9d2a5a..89dcebc 100644 --- a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua @@ -1,330 +1,330 @@ -local menu -include("cl_colors.lua") - -local function createEndRoundMenu() - menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) - menu:Center() - menu:MakePopup() - menu:ShowCloseButton(false) - menu:SetMouseInputEnabled(true) - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - menu:SetDraggable(false) - - local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('marlett') - closeButton:SetText('r') - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - closeButton.OnCursorEntered = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) - end - end - closeButton.OnCursorExited = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - end - closeButton:SetColor(PHWhite) - closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 40) - closeButton:SetPos(menu:GetWide() / 1.05, 0) - closeButton.DoClick = function() - menu:Close() - end - - local matBlurScreen = Material("pp/blurscreen") - function menu:Paint(w, h) - -- Create a blured background to the entire menu. This makes the content easier - -- to read against the semi-transparent background. - local x, y = self:LocalToScreen(0, 0) - local Fraction = 0.4 - - surface.SetMaterial(matBlurScreen) - surface.SetDrawColor(255, 255, 255, 255) - - for i = 0.33, 1, 0.33 do - matBlurScreen:SetFloat("$blur", Fraction * 5 * i) - matBlurScreen:Recompute() - if render then render.UpdateScreenEffectTexture() end - surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) - end - - -- 2 pixel thick black border on the OUTSIDE of the panel - -- Temporarily disable clipping so we can draw outside the bounds of menu - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - DisableClipping(true) - surface.DrawOutlinedRect(-1, -1, w + 2, h + 2) - surface.DrawOutlinedRect(-2, -2, w + 4, h + 4) - DisableClipping(false) - - -- Title bar rectangle (the title bar is always 22 pixels in height) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawRect(0, 0, w, 22) - - -- Light grey background on lower area - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) - surface.DrawRect(0, 22, w, h) - end - - menu.changeTitle = function(newTitle) - menu:SetTitle(newTitle) - end - - -- Results section (this is just a container for winner, awards, and resultsTimeLeft) - local resultsPanel = vgui.Create("DPanel", menu) - resultsPanel:Dock(FILL) - - function resultsPanel:Paint(w, h) end - - menu.setResultsPanelVisibility = function(isVisible) - resultsPanel:SetVisible(isVisible) - end - - -- Text label at the top specifying who won - local winner = vgui.Create("DLabel", resultsPanel) - winner:Dock(TOP) - winner:SetTall(draw.GetFontHeight("RobotoHUD-30")) - winner:SetFont("RobotoHUD-30") - winner:SetContentAlignment(5) -- Center - - menu.setWinningTeamText = function(winState) - if winState == WIN_NONE then - winner:SetText("Round tied") - winner:SetColor(PHWhite) - else - winner:SetText(team.GetName(winState) .. " win!") - winner:SetColor(team.GetColor(winState)) - end - end - - -- Middle area where the player awards are listed - local awards = vgui.Create("DScrollPanel", resultsPanel) - awards:Dock(FILL) - - function awards:Paint(w, h) - -- Add a dark rectangle over the area for the awards to visually separate it from rest of the menu - surface.SetDrawColor(PHDark.r, PHDark.g, PHDark.b) - surface.DrawRect(0, 0, w, h) - end - - local canvas = awards:GetCanvas() - canvas:DockPadding(0, 0, 0, 0) - - function canvas:OnChildAdded(child) - -- Awards fill from top to bottom - child:Dock(TOP) - child:DockMargin(10, 10, 10, 0) - end - - menu.setPlayerAwards = function(allAwards) - awards:Clear() - - for _, award in pairs(allAwards) do - local containerPanel = vgui.Create("DPanel") - containerPanel:SetTall(draw.GetFontHeight("RobotoHUD-20")) - - function containerPanel:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - draw.DrawText(award.name, "RobotoHUD-10", 0, 0, PHWhite, 0) - draw.DrawText(award.desc, "RobotoHUD-10", 0, draw.GetFontHeight("RobotoHUD-10"), PHLesserWhite, 0) - draw.DrawText(award.winnerName, "RobotoHUD-15", w, (h / 2) - (draw.GetFontHeight("RobotoHUD-20") / 2), team.GetColor(award.winnerTeam), 2) - end - - awards:AddItem(containerPanel) - end - end - - -- Timer at bottom right showing how long until next round - local resultsTimeLeft = vgui.Create("DPanel", resultsPanel) - resultsTimeLeft:Dock(BOTTOM) - resultsTimeLeft:SetTall(draw.GetFontHeight("RobotoHUD-15")) - - function resultsTimeLeft:Paint(w, h) - -- "Extend" the dark rectangle from awards:Paint to make a larger seamless rectangle - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) - surface.DrawRect(0, 0, w, h) - - if GAMEMODE:GetGameState() == ROUND_POST then - local settings = GAMEMODE:GetRoundSettings() - local roundTime = settings.NextRoundTime || 30 - local time = math.max(0, roundTime - GAMEMODE:GetStateRunningTime()) - -- TODO: Say "Mapvote in..." if last round - draw.DrawText("Next round in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, PHWhite, 2) - end - end - - -- Map vote section (container for mapList and mapVoteTimeLeft) - local votemapPanel = vgui.Create("DPanel", menu) - votemapPanel:Dock(FILL) - - function votemapPanel:Paint(w, h) end - - menu.setVotemapPanelVisibility = function(isVisible) - votemapPanel:SetVisible(isVisible) - end - - -- List containing map images, map names, and map votes - local mapList = vgui.Create("DScrollPanel", votemapPanel) - menu.MapVoteList = mapList - mapList:Dock(FILL) - mapList:DockMargin(0, 0, 0, 0) - - function mapList:Paint(w, h) - surface.SetDrawColor(20, 20, 20, 150) - surface.DrawRect(0, 0, w, h) - end - - local canvas = mapList:GetCanvas() - canvas:DockPadding(20, 0, 20, 0) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 15, 0, 0) - end - - -- Text showing time until map vote ends - local mapVoteTimeLeft = vgui.Create("DPanel", votemapPanel) - mapVoteTimeLeft:Dock(BOTTOM) - mapVoteTimeLeft:SetTall(draw.GetFontHeight("RobotoHUD-15")) - - function mapVoteTimeLeft:Paint(w, h) - surface.SetDrawColor(20, 20, 20, 150) - surface.DrawRect(0, 0, w, h) - - if GAMEMODE:GetGameState() == ROUND_MAPVOTE then - local voteTime = GAMEMODE.MapVoteTime || 30 - local time = math.max(0, voteTime - GAMEMODE:GetMapVoteRunningTime()) - draw.SimpleText("Voting ends in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, Color(150, 150, 150), 2) - end - end -end - -function GM:EndRoundMenuResults(res) - self:OpenEndRoundMenu() - - menu.changeTitle("Round Results") - menu.setResultsPanelVisibility(true) - menu.setVotemapPanelVisibility(false) - menu.Results = res - menu.setPlayerAwards(res.playerAwards) - menu.setWinningTeamText(res.winningTeam) -end - -function GM:EndRoundMapVote() - self:OpenEndRoundMenu() - - menu.changeTitle("Map Vote") - menu.setResultsPanelVisibility(false) - menu.setVotemapPanelVisibility(true) - menu.MapVoteList:Clear() - - for k, map in pairs(self.MapList) do - local but = vgui.Create("DButton") - but:SetText("") - but:SetTall(128) - - local png - local path = "maps/" .. map .. ".png" - if file.Exists(path, "GAME") then - png = Material(path, "noclamp") - else - local path = "maps/thumb/" .. map .. ".png" - if file.Exists(path, "GAME") then - png = Material(path, "noclamp") - else - local path = "materials/maps/" .. map .. ".png" - if file.Exists(path, "GAME") then - png = Material(path, "noclamp") - end - end - end - - local dname = map:gsub("^%a%a%a?_", ""):gsub("_?v[%d%.%-]+$", "") - dname = dname:gsub("[_]", " "):gsub("([%a])([%a]+)", function(a, b) return a:upper() .. b end) - local z = tonumber(util.CRC(dname):sub(1, 8)) - local mcol = Color(z % 255, z / 255 % 255, z / 255 / 255 % 255, 50) - local gray = Color(150, 150, 150) - - but.VotesScroll = 0 - but.VotesScrollDir = 1 - - function but:Paint(w, h) - if self.Hovered then - surface.SetDrawColor(50, 50, 50, 50) - surface.DrawRect(0, 0, w, h) - end - - draw.SimpleText(dname, "RobotoHUD-15", 128 + 20, 20, color_white, 0) - local fg = draw.GetFontHeight("RobotoHUD-15") - draw.SimpleText(map, "RobotoHUD-L10", 128 + 20, 20 + fg, gray, 0) - if png then - surface.SetMaterial(png) - surface.SetDrawColor(255, 255, 255, 255) - surface.DrawTexturedRect(0, 0, 128, 128) - else - surface.SetDrawColor(50, 50, 50, 255) - surface.DrawRect(0, 0, 128, 128) - surface.SetDrawColor(mcol) - surface.DrawRect(20, 20, 128 - 40, 128 - 40) - end - - local votes = 0 - if GAMEMODE.MapVotesByMap[map] then - votes = #GAMEMODE.MapVotesByMap[map] - end - - local fg2 = draw.GetFontHeight("RobotoHUD-L10") - if votes > 0 then - draw.SimpleText(votes .. (votes > 1 && " votes" || " vote"), "RobotoHUD-L10", 128 + 20, 20 + fg + 20 + fg2, color_white, 0) - end - - local i = 0 - for ply, map2 in pairs(GAMEMODE.MapVotes) do - if IsValid(ply) && map2 == map then - draw.SimpleText(ply:Nick(), "RobotoHUD-L10", w, i * fg2 - self.VotesScroll, gray, 2) - i = i + 1 - end - end - - if i * fg2 > 128 then - self.VotesScroll = self.VotesScroll + FrameTime() * 14 * self.VotesScrollDir - if self.VotesScroll > i * fg2 - 128 then - self.VotesScrollDir = -1 - elseif self.VotesScroll < 0 then - self.VotesScrollDir = 1 - end - end - end - - function but:DoClick() - RunConsoleCommand("ph_votemap", map) - end - - menu.MapVoteList:AddItem(but) - end -end - -function GM:OpenEndRoundMenu() - if !IsValid(menu) then - createEndRoundMenu() - end - - menu:SetVisible(true) -end - -function GM:CloseEndRoundMenu() - if IsValid(menu) then - menu:Close() - end -end - -function GM:ToggleEndRoundMenuVisibility() - if IsValid(menu) && menu:IsVisible() then - GAMEMODE:CloseEndRoundMenu() - else - GAMEMODE:OpenEndRoundMenu() - end -end +local menu +include("cl_colors.lua") + +local function createEndRoundMenu() + menu = vgui.Create("DFrame") + menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) + menu:Center() + menu:MakePopup() + menu:ShowCloseButton(false) + menu:SetMouseInputEnabled(true) + menu:SetKeyboardInputEnabled(false) + menu:SetDeleteOnClose(false) + menu:SetDraggable(false) + + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('marlett') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + closeButton.OnCursorEntered = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) + end + end + closeButton.OnCursorExited = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 40) + closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton.DoClick = function() + menu:Close() + end + + local matBlurScreen = Material("pp/blurscreen") + function menu:Paint(w, h) + -- Create a blured background to the entire menu. This makes the content easier + -- to read against the semi-transparent background. + local x, y = self:LocalToScreen(0, 0) + local Fraction = 0.4 + + surface.SetMaterial(matBlurScreen) + surface.SetDrawColor(255, 255, 255, 255) + + for i = 0.33, 1, 0.33 do + matBlurScreen:SetFloat("$blur", Fraction * 5 * i) + matBlurScreen:Recompute() + if render then render.UpdateScreenEffectTexture() end + surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) + end + + -- 2 pixel thick black border on the OUTSIDE of the panel + -- Temporarily disable clipping so we can draw outside the bounds of menu + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + DisableClipping(true) + surface.DrawOutlinedRect(-1, -1, w + 2, h + 2) + surface.DrawOutlinedRect(-2, -2, w + 4, h + 4) + DisableClipping(false) + + -- Title bar rectangle (the title bar is always 22 pixels in height) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + surface.DrawRect(0, 0, w, 22) + + -- Light grey background on lower area + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) + surface.DrawRect(0, 22, w, h) + end + + menu.changeTitle = function(newTitle) + menu:SetTitle(newTitle) + end + + -- Results section (this is just a container for winner, awards, and resultsTimeLeft) + local resultsPanel = vgui.Create("DPanel", menu) + resultsPanel:Dock(FILL) + + function resultsPanel:Paint(w, h) end + + menu.setResultsPanelVisibility = function(isVisible) + resultsPanel:SetVisible(isVisible) + end + + -- Text label at the top specifying who won + local winner = vgui.Create("DLabel", resultsPanel) + winner:Dock(TOP) + winner:SetTall(draw.GetFontHeight("RobotoHUD-30")) + winner:SetFont("RobotoHUD-30") + winner:SetContentAlignment(5) -- Center + + menu.setWinningTeamText = function(winState) + if winState == WIN_NONE then + winner:SetText("Round tied") + winner:SetColor(PHWhite) + else + winner:SetText(team.GetName(winState) .. " win!") + winner:SetColor(team.GetColor(winState)) + end + end + + -- Middle area where the player awards are listed + local awards = vgui.Create("DScrollPanel", resultsPanel) + awards:Dock(FILL) + + function awards:Paint(w, h) + -- Add a dark rectangle over the area for the awards to visually separate it from rest of the menu + surface.SetDrawColor(PHDark.r, PHDark.g, PHDark.b) + surface.DrawRect(0, 0, w, h) + end + + local canvas = awards:GetCanvas() + canvas:DockPadding(0, 0, 0, 0) + + function canvas:OnChildAdded(child) + -- Awards fill from top to bottom + child:Dock(TOP) + child:DockMargin(10, 10, 10, 0) + end + + menu.setPlayerAwards = function(allAwards) + awards:Clear() + + for _, award in pairs(allAwards) do + local containerPanel = vgui.Create("DPanel") + containerPanel:SetTall(draw.GetFontHeight("RobotoHUD-20")) + + function containerPanel:Paint(w, h) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + draw.DrawText(award.name, "RobotoHUD-10", 0, 0, PHWhite, 0) + draw.DrawText(award.desc, "RobotoHUD-10", 0, draw.GetFontHeight("RobotoHUD-10"), PHLesserWhite, 0) + draw.DrawText(award.winnerName, "RobotoHUD-15", w, (h / 2) - (draw.GetFontHeight("RobotoHUD-20") / 2), team.GetColor(award.winnerTeam), 2) + end + + awards:AddItem(containerPanel) + end + end + + -- Timer at bottom right showing how long until next round + local resultsTimeLeft = vgui.Create("DPanel", resultsPanel) + resultsTimeLeft:Dock(BOTTOM) + resultsTimeLeft:SetTall(draw.GetFontHeight("RobotoHUD-15")) + + function resultsTimeLeft:Paint(w, h) + -- "Extend" the dark rectangle from awards:Paint to make a larger seamless rectangle + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) + surface.DrawRect(0, 0, w, h) + + if GAMEMODE:GetGameState() == ROUND_POST then + local settings = GAMEMODE:GetRoundSettings() + local roundTime = settings.NextRoundTime || 30 + local time = math.max(0, roundTime - GAMEMODE:GetStateRunningTime()) + -- TODO: Say "Mapvote in..." if last round + draw.DrawText("Next round in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, PHWhite, 2) + end + end + + -- Map vote section (container for mapList and mapVoteTimeLeft) + local votemapPanel = vgui.Create("DPanel", menu) + votemapPanel:Dock(FILL) + + function votemapPanel:Paint(w, h) end + + menu.setVotemapPanelVisibility = function(isVisible) + votemapPanel:SetVisible(isVisible) + end + + -- List containing map images, map names, and map votes + local mapList = vgui.Create("DScrollPanel", votemapPanel) + menu.MapVoteList = mapList + mapList:Dock(FILL) + mapList:DockMargin(0, 0, 0, 0) + + function mapList:Paint(w, h) + surface.SetDrawColor(20, 20, 20, 150) + surface.DrawRect(0, 0, w, h) + end + + local canvas = mapList:GetCanvas() + canvas:DockPadding(20, 0, 20, 0) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 15, 0, 0) + end + + -- Text showing time until map vote ends + local mapVoteTimeLeft = vgui.Create("DPanel", votemapPanel) + mapVoteTimeLeft:Dock(BOTTOM) + mapVoteTimeLeft:SetTall(draw.GetFontHeight("RobotoHUD-15")) + + function mapVoteTimeLeft:Paint(w, h) + surface.SetDrawColor(20, 20, 20, 150) + surface.DrawRect(0, 0, w, h) + + if GAMEMODE:GetGameState() == ROUND_MAPVOTE then + local voteTime = GAMEMODE.MapVoteTime || 30 + local time = math.max(0, voteTime - GAMEMODE:GetMapVoteRunningTime()) + draw.SimpleText("Voting ends in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, Color(150, 150, 150), 2) + end + end +end + +function GM:EndRoundMenuResults(res) + self:OpenEndRoundMenu() + + menu.changeTitle("Round Results") + menu.setResultsPanelVisibility(true) + menu.setVotemapPanelVisibility(false) + menu.Results = res + menu.setPlayerAwards(res.playerAwards) + menu.setWinningTeamText(res.winningTeam) +end + +function GM:EndRoundMapVote() + self:OpenEndRoundMenu() + + menu.changeTitle("Map Vote") + menu.setResultsPanelVisibility(false) + menu.setVotemapPanelVisibility(true) + menu.MapVoteList:Clear() + + for k, map in pairs(self.MapList) do + local but = vgui.Create("DButton") + but:SetText("") + but:SetTall(128) + + local png + local path = "maps/" .. map .. ".png" + if file.Exists(path, "GAME") then + png = Material(path, "noclamp") + else + local path = "maps/thumb/" .. map .. ".png" + if file.Exists(path, "GAME") then + png = Material(path, "noclamp") + else + local path = "materials/maps/" .. map .. ".png" + if file.Exists(path, "GAME") then + png = Material(path, "noclamp") + end + end + end + + local dname = map:gsub("^%a%a%a?_", ""):gsub("_?v[%d%.%-]+$", "") + dname = dname:gsub("[_]", " "):gsub("([%a])([%a]+)", function(a, b) return a:upper() .. b end) + local z = tonumber(util.CRC(dname):sub(1, 8)) + local mcol = Color(z % 255, z / 255 % 255, z / 255 / 255 % 255, 50) + local gray = Color(150, 150, 150) + + but.VotesScroll = 0 + but.VotesScrollDir = 1 + + function but:Paint(w, h) + if self.Hovered then + surface.SetDrawColor(50, 50, 50, 50) + surface.DrawRect(0, 0, w, h) + end + + draw.SimpleText(dname, "RobotoHUD-15", 128 + 20, 20, color_white, 0) + local fg = draw.GetFontHeight("RobotoHUD-15") + draw.SimpleText(map, "RobotoHUD-L10", 128 + 20, 20 + fg, gray, 0) + if png then + surface.SetMaterial(png) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(0, 0, 128, 128) + else + surface.SetDrawColor(50, 50, 50, 255) + surface.DrawRect(0, 0, 128, 128) + surface.SetDrawColor(mcol) + surface.DrawRect(20, 20, 128 - 40, 128 - 40) + end + + local votes = 0 + if GAMEMODE.MapVotesByMap[map] then + votes = #GAMEMODE.MapVotesByMap[map] + end + + local fg2 = draw.GetFontHeight("RobotoHUD-L10") + if votes > 0 then + draw.SimpleText(votes .. (votes > 1 && " votes" || " vote"), "RobotoHUD-L10", 128 + 20, 20 + fg + 20 + fg2, color_white, 0) + end + + local i = 0 + for ply, map2 in pairs(GAMEMODE.MapVotes) do + if IsValid(ply) && map2 == map then + draw.SimpleText(ply:Nick(), "RobotoHUD-L10", w, i * fg2 - self.VotesScroll, gray, 2) + i = i + 1 + end + end + + if i * fg2 > 128 then + self.VotesScroll = self.VotesScroll + FrameTime() * 14 * self.VotesScrollDir + if self.VotesScroll > i * fg2 - 128 then + self.VotesScrollDir = -1 + elseif self.VotesScroll < 0 then + self.VotesScrollDir = 1 + end + end + end + + function but:DoClick() + RunConsoleCommand("ph_votemap", map) + end + + menu.MapVoteList:AddItem(but) + end +end + +function GM:OpenEndRoundMenu() + if !IsValid(menu) then + createEndRoundMenu() + end + + menu:SetVisible(true) +end + +function GM:CloseEndRoundMenu() + if IsValid(menu) then + menu:Close() + end +end + +function GM:ToggleEndRoundMenuVisibility() + if IsValid(menu) && menu:IsVisible() then + GAMEMODE:CloseEndRoundMenu() + else + GAMEMODE:OpenEndRoundMenu() + end +end diff --git a/gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua b/gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua index cac258f..d49163d 100644 --- a/gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua +++ b/gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua @@ -1,38 +1,38 @@ -local EntityMeta = FindMetaTable("Entity") - -function EntityMeta:GetPlayerColor() - return self:GetNWVector("playerColor") || Vector() -end - --- Proxies --- { --- PlayerColor --- { --- resultVar $color2 --- } --- } - -matproxy.Add({ - name = "PlayerColor", - init = function(self, mat, values) - -- Store the name of the variable we want to set - self.ResultTo = values.resultvar - end, - bind = function(self, mat, ent) - if !IsValid(ent) then return end - - if ent.GetPlayerColorOverride then -- clientside entities can't override functions, so we need an additional one for it - local col = ent:GetPlayerColorOverride() - if isvector(col) then - mat:SetVector(self.ResultTo, col) - end - elseif ent.GetPlayerColor then - local col = ent:GetPlayerColor() - if isvector(col) then - mat:SetVector(self.ResultTo, col) - end - else - mat:SetVector(self.ResultTo, Vector(62.0 / 255.0, 88.0 / 255.0, 106.0 / 255.0)) - end - end -}) +local EntityMeta = FindMetaTable("Entity") + +function EntityMeta:GetPlayerColor() + return self:GetNWVector("playerColor") || Vector() +end + +-- Proxies +-- { +-- PlayerColor +-- { +-- resultVar $color2 +-- } +-- } + +matproxy.Add({ + name = "PlayerColor", + init = function(self, mat, values) + -- Store the name of the variable we want to set + self.ResultTo = values.resultvar + end, + bind = function(self, mat, ent) + if !IsValid(ent) then return end + + if ent.GetPlayerColorOverride then -- clientside entities can't override functions, so we need an additional one for it + local col = ent:GetPlayerColorOverride() + if isvector(col) then + mat:SetVector(self.ResultTo, col) + end + elseif ent.GetPlayerColor then + local col = ent:GetPlayerColor() + if isvector(col) then + mat:SetVector(self.ResultTo, col) + end + else + mat:SetVector(self.ResultTo, Vector(62.0 / 255.0, 88.0 / 255.0, 106.0 / 255.0)) + end + end +}) diff --git a/gamemodes/ultimateph/gamemode/cl_health.lua b/gamemodes/ultimateph/gamemode/cl_health.lua index d16c38a..8ff7e90 100644 --- a/gamemodes/ultimateph/gamemode/cl_health.lua +++ b/gamemodes/ultimateph/gamemode/cl_health.lua @@ -1,5 +1,5 @@ -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:GetHMaxHealth() - return self:GetNWFloat("HMaxHealth", 100) || 100 -end +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:GetHMaxHealth() + return self:GetNWFloat("HMaxHealth", 100) || 100 +end diff --git a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua index 4b845f7..04da46e 100644 --- a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua +++ b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua @@ -1,82 +1,82 @@ -local helpText = [[ -== CONTROLS == -LEFT CLICK - Disguises as the prop you are looking at -C - Locks your prop's rotation when disguised -F3 - Taunt menu - - -== OBJECTIVES == -The aim of the hunters is to find and kill all the props. -Don't shoot too many actual props, as guessing incorrectly costs health! - -The aim of the props is to hide from the hunters and not get killed. - - -== COMMANDS == -"ph_taunt_random" plays a random taunt. -"ph_taunt " plays a taunt given a filename. -]] - -local menu -include("cl_colors.lua") - -local function createHelpMenu() - menu = vgui.Create("DFrame") - GAMEMODE.HelpMenu = menu - menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) - menu:Center() - menu:MakePopup() - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - menu:ShowCloseButton(false) - menu:SetTitle("") - menu:SetVisible(false) - - local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('marlett') - closeButton:SetText('r') - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) - end - closeButton.OnCursorEntered = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b, 245)) - end - end - closeButton.OnCursorExited = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) - end - end - closeButton:SetColor(PHWhite) - closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) - closeButton:SetPos(menu:GetWide() / 1.05, 0) - closeButton.DoClick = function() - menu:Close() - end - - function menu:Paint(w, h) - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b, 245) - surface.DrawRect(0, 0, w, h) - surface.SetFont("RobotoHUD-25") - draw.ShadowText("Help", "RobotoHUD-25", 8, 2, Color(132, 199, 29), 0) - end - - local text = vgui.Create("DLabel", menu) - text:SetText(helpText) - text:SetWrap(true) - text:SetFont("RobotoHUD-10") - text:Dock(FILL) - - function text:Paint(w, h) end -end - -local function toggleHelpMenu() - if !IsValid(menu) then - createHelpMenu() - end - - menu:SetVisible(!menu:IsVisible()) -end - -net.Receive("ph_openhelpmenu", toggleHelpMenu) +local helpText = [[ +== CONTROLS == +LEFT CLICK - Disguises as the prop you are looking at +C - Locks your prop's rotation when disguised +F3 - Taunt menu + + +== OBJECTIVES == +The aim of the hunters is to find and kill all the props. +Don't shoot too many actual props, as guessing incorrectly costs health! + +The aim of the props is to hide from the hunters and not get killed. + + +== COMMANDS == +"ph_taunt_random" plays a random taunt. +"ph_taunt " plays a taunt given a filename. +]] + +local menu +include("cl_colors.lua") + +local function createHelpMenu() + menu = vgui.Create("DFrame") + GAMEMODE.HelpMenu = menu + menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) + menu:Center() + menu:MakePopup() + menu:SetKeyboardInputEnabled(false) + menu:SetDeleteOnClose(false) + menu:ShowCloseButton(false) + menu:SetTitle("") + menu:SetVisible(false) + + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('marlett') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) + end + closeButton.OnCursorEntered = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b, 245)) + end + end + closeButton.OnCursorExited = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) + closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton.DoClick = function() + menu:Close() + end + + function menu:Paint(w, h) + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b, 245) + surface.DrawRect(0, 0, w, h) + surface.SetFont("RobotoHUD-25") + draw.ShadowText("Help", "RobotoHUD-25", 8, 2, Color(132, 199, 29), 0) + end + + local text = vgui.Create("DLabel", menu) + text:SetText(helpText) + text:SetWrap(true) + text:SetFont("RobotoHUD-10") + text:Dock(FILL) + + function text:Paint(w, h) end +end + +local function toggleHelpMenu() + if !IsValid(menu) then + createHelpMenu() + end + + menu:SetVisible(!menu:IsVisible()) +end + +net.Receive("ph_openhelpmenu", toggleHelpMenu) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index d9b0b67..4c87718 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -1,172 +1,172 @@ -include("cl_colors.lua") -local function createRoboto(s) - surface.CreateFont("RobotoHUD-" .. s , { - font = "Roboto-Bold", - size = math.Round(ScrW() / 1000 * s), - weight = 700, - antialias = true, - italic = false - }) - - surface.CreateFont("RobotoHUD-L" .. s , { - font = "Roboto", - size = math.Round(ScrW() / 1000 * s), - weight = 500, - antialias = true, - italic = false - }) -end - -for i = 5, 50, 5 do - createRoboto(i) -end -createRoboto(8) -createRoboto(12) - -function draw.ShadowText(n, f, x, y, c, px, py, shadowColor) - draw.SimpleText(n, f, x + 1, y + 1, shadowColor || color_black, px, py) - draw.SimpleText(n, f, x, y, c, px, py) -end - -function GM:HUDPaint() - self:DrawGameHUD() - self:DrawRoundTimer() -end - -local helpKeysProps = { - {"attack", "Disguise as prop"}, - {"menu_context", "Lock prop rotation"}, - {"gm_showspare1", "Taunt"} -} - -local function keyName(str) - str = input.LookupBinding(str) - return str:upper() -end - -function GM:DrawGameHUD() - local ply = LocalPlayer() - if self:IsCSpectating() && IsValid(self:GetCSpectatee()) && self:GetCSpectatee():IsPlayer() then - ply = self:GetCSpectatee() - end - --- self:DrawHealth(ply) - - if ply != LocalPlayer() then - local col = team.GetColor(ply:Team()) - draw.ShadowText(ply:Nick(), "RobotoHUD-30", ScrW() / 2, ScrH() - 4, col, 1, 4) - end - - local tr = ply:GetEyeTraceNoCursor() - local shouldDraw = hook.Run("HUDShouldDraw", "PropHuntersPlayerNames") - if shouldDraw != false then - -- draw names - if IsValid(tr.Entity) && tr.Entity:IsPlayer() && tr.HitPos:Distance(tr.StartPos) < 500 then - -- hunters can only see their teams names - if !ply:IsHunter() || ply:Team() == tr.Entity:Team() then - self.LastLooked = tr.Entity - self.LookedFade = CurTime() - end - end - - if IsValid(self.LastLooked) && self.LookedFade + 2 > CurTime() then - local name = self.LastLooked:Nick() || "error" - local col = table.Copy(team.GetColor(self.LastLooked:Team())) - col.a = (1 - (CurTime() - self.LookedFade) / 2) * 255 - draw.ShadowText(name, "RobotoHUD-20", ScrW() / 2, ScrH() / 2 + 80, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, col.a)) - end - end - - local help - if LocalPlayer():Alive() then - if LocalPlayer():IsProp() then - if self:GetGameState() == ROUND_HIDE || (self:GetGameState() == ROUND_SEEK && !LocalPlayer():IsDisguised()) then - help = helpKeysProps - end - end - end - - if help then - local fh = draw.GetFontHeight("RobotoHUD-L15") - local h = #help * fh - local x = 20 - local y = ScrH() / 2 - h / 2 - local i = 0 - local tw = 0 - for k, t in pairs(help) do - surface.SetFont("RobotoHUD-15") - local name = keyName(t[1]) - local w = surface.GetTextSize(name) - tw = math.max(tw, w) - end - - for k, t in pairs(help) do - surface.SetFont("RobotoHUD-15") - local name = keyName(t[1]) - draw.ShadowText(name, "RobotoHUD-15", x + tw / 2, y + i * fh, color_white, 1, 0) - draw.ShadowText(t[2], "RobotoHUD-L15", x + tw + 10, y + i * fh, color_white, 0, 0) - i = i + 1 - end - end -end - -local polyTex = surface.GetTextureID("VGUI/white.vmt") - -function GM:HUDShouldDraw(name) - if name == "CHudHealth" then return true end - if name == "CHudVoiceStatus" then return false end - if name == "CHudVoiceSelfStatus" then return false end - - return true -end - -function GM:DrawRoundTimer() - if self:GetGameState() == ROUND_WAIT then - local time = math.ceil(self.StartWaitTime:GetFloat() - self:GetStateRunningTime()) - if time > 0 then - draw.ShadowText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScrH() / 10 - draw.GetFontHeight("RobotoHUD-40") / 4, color_white, 1, 4) - draw.ShadowText("Game starts in " .. tostring(time) .. " second" .. (time > 1 && "s" || ""), "RobotoHUD-15", ScrW() / 2, ScrH() / 10, color_white, 1, 1) - else - draw.ShadowText("Not enough players to start game", "RobotoHUD-25", ScrW() / 2, ScrH() / 10 - draw.GetFontHeight("RobotoHUD-40") / 4, color_white, 1, 4) - draw.ShadowText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScrH() / 10, color_white, 1, 1) - end - elseif self:GetGameState() == ROUND_HIDE then - local time = math.ceil(self.HidingTime:GetInt() - self:GetStateRunningTime()) - if time > 0 then - draw.ShadowText("Hunters will be released in", "RobotoHUD-15", ScrW() / 2, ScrH() / 3 - draw.GetFontHeight("RobotoHUD-40") / 2, color_white, 1, 4) - draw.ShadowText(time, "RobotoHUD-40", ScrW() / 2, ScrH() / 3, color_white, 1, 1) - end - elseif self:GetGameState() == ROUND_SEEK then - if self:GetStateRunningTime() < 2 then - draw.ShadowText("GO!", "RobotoHUD-50", ScrW() / 2, ScrH() / 3, color_white, 1, 1) - end - - local settings = self:GetRoundSettings() - local roundTime = settings.RoundTime || 5 * 60 - local time = math.max(0, roundTime - self:GetStateRunningTime()) - net.Receive("rounds", function(len) - self.rounds = net.ReadInt(5) - end) - if self.rounds == nil then - self.rounds = 1 - end - local m = math.floor(time / 60) - local s = math.floor(time % 60) - m = tostring(m) - s = s < 10 && "0" .. s || tostring(s) - local fh = draw.GetFontHeight("RobotoHUD-L15") * 1 - draw.ShadowText("Round "..self.rounds.." / "..self.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, 20, color_white, 1, 3) - draw.ShadowText(m .. ":" .. s, "RobotoHUD-20", ScrW() / 2, fh + 20, color_white, 1, 3) - end -end - -function GM:PreDrawHUD() - local client = LocalPlayer() - if self:GetGameState() == ROUND_HIDE then - if client:IsHunter() then - surface.SetDrawColor(25, 25, 25, 255) - surface.DrawRect(-10, -10, ScrW() + 20, ScrH() + 20) - end - end -end - +include("cl_colors.lua") +local function createRoboto(s) + surface.CreateFont("RobotoHUD-" .. s , { + font = "Roboto-Bold", + size = math.Round(ScrW() / 1000 * s), + weight = 700, + antialias = true, + italic = false + }) + + surface.CreateFont("RobotoHUD-L" .. s , { + font = "Roboto", + size = math.Round(ScrW() / 1000 * s), + weight = 500, + antialias = true, + italic = false + }) +end + +for i = 5, 50, 5 do + createRoboto(i) +end +createRoboto(8) +createRoboto(12) + +function draw.ShadowText(n, f, x, y, c, px, py, shadowColor) + draw.SimpleText(n, f, x + 1, y + 1, shadowColor || color_black, px, py) + draw.SimpleText(n, f, x, y, c, px, py) +end + +function GM:HUDPaint() + self:DrawGameHUD() + self:DrawRoundTimer() +end + +local helpKeysProps = { + {"attack", "Disguise as prop"}, + {"menu_context", "Lock prop rotation"}, + {"gm_showspare1", "Taunt"} +} + +local function keyName(str) + str = input.LookupBinding(str) + return str:upper() +end + +function GM:DrawGameHUD() + local ply = LocalPlayer() + if self:IsCSpectating() && IsValid(self:GetCSpectatee()) && self:GetCSpectatee():IsPlayer() then + ply = self:GetCSpectatee() + end + +-- self:DrawHealth(ply) + + if ply != LocalPlayer() then + local col = team.GetColor(ply:Team()) + draw.ShadowText(ply:Nick(), "RobotoHUD-30", ScrW() / 2, ScrH() - 4, col, 1, 4) + end + + local tr = ply:GetEyeTraceNoCursor() + local shouldDraw = hook.Run("HUDShouldDraw", "PropHuntersPlayerNames") + if shouldDraw != false then + -- draw names + if IsValid(tr.Entity) && tr.Entity:IsPlayer() && tr.HitPos:Distance(tr.StartPos) < 500 then + -- hunters can only see their teams names + if !ply:IsHunter() || ply:Team() == tr.Entity:Team() then + self.LastLooked = tr.Entity + self.LookedFade = CurTime() + end + end + + if IsValid(self.LastLooked) && self.LookedFade + 2 > CurTime() then + local name = self.LastLooked:Nick() || "error" + local col = table.Copy(team.GetColor(self.LastLooked:Team())) + col.a = (1 - (CurTime() - self.LookedFade) / 2) * 255 + draw.ShadowText(name, "RobotoHUD-20", ScrW() / 2, ScrH() / 2 + 80, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, col.a)) + end + end + + local help + if LocalPlayer():Alive() then + if LocalPlayer():IsProp() then + if self:GetGameState() == ROUND_HIDE || (self:GetGameState() == ROUND_SEEK && !LocalPlayer():IsDisguised()) then + help = helpKeysProps + end + end + end + + if help then + local fh = draw.GetFontHeight("RobotoHUD-L15") + local h = #help * fh + local x = 20 + local y = ScrH() / 2 - h / 2 + local i = 0 + local tw = 0 + for k, t in pairs(help) do + surface.SetFont("RobotoHUD-15") + local name = keyName(t[1]) + local w = surface.GetTextSize(name) + tw = math.max(tw, w) + end + + for k, t in pairs(help) do + surface.SetFont("RobotoHUD-15") + local name = keyName(t[1]) + draw.ShadowText(name, "RobotoHUD-15", x + tw / 2, y + i * fh, color_white, 1, 0) + draw.ShadowText(t[2], "RobotoHUD-L15", x + tw + 10, y + i * fh, color_white, 0, 0) + i = i + 1 + end + end +end + +local polyTex = surface.GetTextureID("VGUI/white.vmt") + +function GM:HUDShouldDraw(name) + if name == "CHudHealth" then return true end + if name == "CHudVoiceStatus" then return false end + if name == "CHudVoiceSelfStatus" then return false end + + return true +end + +function GM:DrawRoundTimer() + if self:GetGameState() == ROUND_WAIT then + local time = math.ceil(self.StartWaitTime:GetFloat() - self:GetStateRunningTime()) + if time > 0 then + draw.ShadowText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScrH() / 10 - draw.GetFontHeight("RobotoHUD-40") / 4, color_white, 1, 4) + draw.ShadowText("Game starts in " .. tostring(time) .. " second" .. (time > 1 && "s" || ""), "RobotoHUD-15", ScrW() / 2, ScrH() / 10, color_white, 1, 1) + else + draw.ShadowText("Not enough players to start game", "RobotoHUD-25", ScrW() / 2, ScrH() / 10 - draw.GetFontHeight("RobotoHUD-40") / 4, color_white, 1, 4) + draw.ShadowText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScrH() / 10, color_white, 1, 1) + end + elseif self:GetGameState() == ROUND_HIDE then + local time = math.ceil(self.HidingTime:GetInt() - self:GetStateRunningTime()) + if time > 0 then + draw.ShadowText("Hunters will be released in", "RobotoHUD-15", ScrW() / 2, ScrH() / 3 - draw.GetFontHeight("RobotoHUD-40") / 2, color_white, 1, 4) + draw.ShadowText(time, "RobotoHUD-40", ScrW() / 2, ScrH() / 3, color_white, 1, 1) + end + elseif self:GetGameState() == ROUND_SEEK then + if self:GetStateRunningTime() < 2 then + draw.ShadowText("GO!", "RobotoHUD-50", ScrW() / 2, ScrH() / 3, color_white, 1, 1) + end + + local settings = self:GetRoundSettings() + local roundTime = settings.RoundTime || 5 * 60 + local time = math.max(0, roundTime - self:GetStateRunningTime()) + net.Receive("rounds", function(len) + self.rounds = net.ReadInt(5) + end) + if self.rounds == nil then + self.rounds = 1 + end + local m = math.floor(time / 60) + local s = math.floor(time % 60) + m = tostring(m) + s = s < 10 && "0" .. s || tostring(s) + local fh = draw.GetFontHeight("RobotoHUD-L15") * 1 + draw.ShadowText("Round "..self.rounds.." / "..self.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, 20, color_white, 1, 3) + draw.ShadowText(m .. ":" .. s, "RobotoHUD-20", ScrW() / 2, fh + 20, color_white, 1, 3) + end +end + +function GM:PreDrawHUD() + local client = LocalPlayer() + if self:GetGameState() == ROUND_HIDE then + if client:IsHunter() then + surface.SetDrawColor(25, 25, 25, 255) + surface.DrawRect(-10, -10, ScrW() + 20, ScrH() + 20) + end + end +end + diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index 71491a9..9886a5a 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -1,170 +1,170 @@ -include("shared.lua") -include("cl_fixplayercolor.lua") -include("cl_ragdoll.lua") -include("cl_chatmsg.lua") -include("cl_rounds.lua") -include("cl_hud.lua") -include("cl_scoreboard.lua") -include("cl_spectate.lua") -include("cl_health.lua") -include("cl_killfeed.lua") -include("cl_voicepanels.lua") -include("cl_helpscreen.lua") -include("cl_disguise.lua") -include("cl_taunt.lua") -include("cl_endroundboard.lua") -include("cl_mapvote.lua") -include("cl_bannedmodels.lua") -include("cl_aimlaser.lua") -include("sh_init.lua") - -function GM:InitPostEntity() - net.Start("clientIPE") - net.SendToServer() - - -- Sync the banned models - self:CreateBannedModelsMenu() - net.Start("ph_bannedmodels_getall") - net.SendToServer() -end - -function GM:PostDrawViewModel(vm, ply, weapon) - if weapon.UseHands || !weapon:IsScripted() then - local hands = LocalPlayer():GetHands() - if IsValid(hands) then hands:DrawModel() end - end -end - -function GM:RenderScene(origin, angles, fov) - local client = LocalPlayer() - if IsValid(client) then - local wep = client:GetActiveWeapon() - if IsValid(wep) && wep.PostDrawTranslucentRenderables then - local errored, retval = pcall(wep.PostDrawTranslucentRenderables, wep) - if !errored then - print(retval) - end - end - end -end - -function GM:PreDrawHalos() - self:RenderDisguiseHalo() -end - -local function lerp(from, to, step) - if from < to then - return math.min(from + step, to) - end - - return math.max(from - step, to) -end - -local camDis, camHeight = 0, 0 -function GM:CalcView(ply, pos, angles, fov) - if self:IsCSpectating() && IsValid(self:GetCSpectatee()) then - ply = self:GetCSpectatee() - end - - if ply:IsPlayer() && !ply:Alive() then - ply = ply:GetRagdollEntity() - end - - if IsValid(ply) then - if ply:IsPlayer() && ply:IsDisguised() then - local maxs = ply:GetNWVector("disguiseMaxs") - local mins = ply:GetNWVector("disguiseMins") - local view = {} - local reach = (maxs.z - mins.z) - if reach <= GetConVar("ph_props_camdistance_min"):GetInt() then - reach = GetConVar("ph_props_camdistance_min"):GetInt() - end - reach = reach * GetConVar("ph_props_camdistance_mult"):GetInt() - if reach >= GetConVar("ph_props_camdistance_max"):GetInt() then - reach = GetConVar("ph_props_camdistance_max"):GetInt() - end - - local trace = {} - trace.start = ply:GetPropEyePos() - trace.endpos = trace.start + angles:Forward() * -reach - local tab = ents.FindByClass("prop_ragdoll") - table.insert(tab, ply) - trace.filter = tab - - local a = 3 - trace.mins = Vector(math.max(-a, mins.x), math.max(-a, mins.y), math.max(-a, mins.z)) - trace.maxs = Vector(math.min(a, maxs.x), math.min(a, maxs.y), math.min(a, maxs.z)) - tr = util.TraceHull(trace) - camDis = lerp(camDis, (tr.HitPos - trace.start):Length(), FrameTime() * 300) - camHeight = lerp(camHeight, (ply:GetPropEyePos() - ply:GetPos()).z, FrameTime() * 300) - local camPos = trace.start * 1 - camPos.z = ply:GetPos().z + camHeight - view.origin = camPos + (trace.endpos - trace.start):GetNormal() * camDis - view.angles = angles - view.fov = fov - return view - end - end --- Thirdperson for undisguised props - if ply:IsPlayer() && GetConVar("ph_props_undisguised_thirdperson"):GetBool() && ply:Team() == TEAM_PROP && ply:IsDisguised() == false then - local trace = util.TraceHull{ -- the following has been modified from ulx-commands by Timmy - start = pos, - endpos = pos - angles:Forward() * 100, - mins = Vector( -4, -4, -4 ), - maxs = Vector( 4, 4, 4 ), - whitelist = true, - } - - if trace.Hit then - pos = trace.HitPos - else - pos = pos - angles:Forward() * 100 - end - - return { origin=pos, angles=angles } - end -end - -function GM:ShouldDrawLocalPlayer(ply) - if ply:IsPlayer() && GetConVar("ph_props_undisguised_thirdperson"):GetBool() && ply:Team() == TEAM_PROP then - return true - end - - return false -end - -net.Receive("hull_set", function(len) - local ply = net.ReadEntity() - if !IsValid(ply) then return end - local hullx = net.ReadFloat() - local hully = net.ReadFloat() - local hullz = net.ReadFloat() - local duckz = net.ReadFloat() - GAMEMODE:PlayerSetHull(ply, hullx, hully, hullz, duckz) -end) - -function GM:RenderScene() - self:RenderDisguises() -end - -function GM:EntityRemoved(ent) - if IsValid(ent.PropMod) then - ent.PropMod:Remove() - end -end - -concommand.Add("+menu_context", function() - if (GAMEMODE:GetGameState() == ROUND_POST && !timer.Exists("ph_timer_show_results_delay")) || GAMEMODE:GetGameState() == ROUND_MAPVOTE then - GAMEMODE:ToggleEndRoundMenuVisibility() - else - RunConsoleCommand("ph_lockrotation") - end -end) - -net.Receive("player_model_sex", function() - local sex = net.ReadString() - if #sex == 0 then - sex = nil - end - GAMEMODE.PlayerModelSex = sex -end) +include("shared.lua") +include("cl_fixplayercolor.lua") +include("cl_ragdoll.lua") +include("cl_chatmsg.lua") +include("cl_rounds.lua") +include("cl_hud.lua") +include("cl_scoreboard.lua") +include("cl_spectate.lua") +include("cl_health.lua") +include("cl_killfeed.lua") +include("cl_voicepanels.lua") +include("cl_helpscreen.lua") +include("cl_disguise.lua") +include("cl_taunt.lua") +include("cl_endroundboard.lua") +include("cl_mapvote.lua") +include("cl_bannedmodels.lua") +include("cl_aimlaser.lua") +include("sh_init.lua") + +function GM:InitPostEntity() + net.Start("clientIPE") + net.SendToServer() + + -- Sync the banned models + self:CreateBannedModelsMenu() + net.Start("ph_bannedmodels_getall") + net.SendToServer() +end + +function GM:PostDrawViewModel(vm, ply, weapon) + if weapon.UseHands || !weapon:IsScripted() then + local hands = LocalPlayer():GetHands() + if IsValid(hands) then hands:DrawModel() end + end +end + +function GM:RenderScene(origin, angles, fov) + local client = LocalPlayer() + if IsValid(client) then + local wep = client:GetActiveWeapon() + if IsValid(wep) && wep.PostDrawTranslucentRenderables then + local errored, retval = pcall(wep.PostDrawTranslucentRenderables, wep) + if !errored then + print(retval) + end + end + end +end + +function GM:PreDrawHalos() + self:RenderDisguiseHalo() +end + +local function lerp(from, to, step) + if from < to then + return math.min(from + step, to) + end + + return math.max(from - step, to) +end + +local camDis, camHeight = 0, 0 +function GM:CalcView(ply, pos, angles, fov) + if self:IsCSpectating() && IsValid(self:GetCSpectatee()) then + ply = self:GetCSpectatee() + end + + if ply:IsPlayer() && !ply:Alive() then + ply = ply:GetRagdollEntity() + end + + if IsValid(ply) then + if ply:IsPlayer() && ply:IsDisguised() then + local maxs = ply:GetNWVector("disguiseMaxs") + local mins = ply:GetNWVector("disguiseMins") + local view = {} + local reach = (maxs.z - mins.z) + if reach <= GetConVar("ph_props_camdistance_min"):GetInt() then + reach = GetConVar("ph_props_camdistance_min"):GetInt() + end + reach = reach * GetConVar("ph_props_camdistance_mult"):GetInt() + if reach >= GetConVar("ph_props_camdistance_max"):GetInt() then + reach = GetConVar("ph_props_camdistance_max"):GetInt() + end + + local trace = {} + trace.start = ply:GetPropEyePos() + trace.endpos = trace.start + angles:Forward() * -reach + local tab = ents.FindByClass("prop_ragdoll") + table.insert(tab, ply) + trace.filter = tab + + local a = 3 + trace.mins = Vector(math.max(-a, mins.x), math.max(-a, mins.y), math.max(-a, mins.z)) + trace.maxs = Vector(math.min(a, maxs.x), math.min(a, maxs.y), math.min(a, maxs.z)) + tr = util.TraceHull(trace) + camDis = lerp(camDis, (tr.HitPos - trace.start):Length(), FrameTime() * 300) + camHeight = lerp(camHeight, (ply:GetPropEyePos() - ply:GetPos()).z, FrameTime() * 300) + local camPos = trace.start * 1 + camPos.z = ply:GetPos().z + camHeight + view.origin = camPos + (trace.endpos - trace.start):GetNormal() * camDis + view.angles = angles + view.fov = fov + return view + end + end +-- Thirdperson for undisguised props + if ply:IsPlayer() && GetConVar("ph_props_undisguised_thirdperson"):GetBool() && ply:Team() == TEAM_PROP && ply:IsDisguised() == false then + local trace = util.TraceHull{ -- the following has been modified from ulx-commands by Timmy + start = pos, + endpos = pos - angles:Forward() * 100, + mins = Vector( -4, -4, -4 ), + maxs = Vector( 4, 4, 4 ), + whitelist = true, + } + + if trace.Hit then + pos = trace.HitPos + else + pos = pos - angles:Forward() * 100 + end + + return { origin=pos, angles=angles } + end +end + +function GM:ShouldDrawLocalPlayer(ply) + if ply:IsPlayer() && GetConVar("ph_props_undisguised_thirdperson"):GetBool() && ply:Team() == TEAM_PROP then + return true + end + + return false +end + +net.Receive("hull_set", function(len) + local ply = net.ReadEntity() + if !IsValid(ply) then return end + local hullx = net.ReadFloat() + local hully = net.ReadFloat() + local hullz = net.ReadFloat() + local duckz = net.ReadFloat() + GAMEMODE:PlayerSetHull(ply, hullx, hully, hullz, duckz) +end) + +function GM:RenderScene() + self:RenderDisguises() +end + +function GM:EntityRemoved(ent) + if IsValid(ent.PropMod) then + ent.PropMod:Remove() + end +end + +concommand.Add("+menu_context", function() + if (GAMEMODE:GetGameState() == ROUND_POST && !timer.Exists("ph_timer_show_results_delay")) || GAMEMODE:GetGameState() == ROUND_MAPVOTE then + GAMEMODE:ToggleEndRoundMenuVisibility() + else + RunConsoleCommand("ph_lockrotation") + end +end) + +net.Receive("player_model_sex", function() + local sex = net.ReadString() + if #sex == 0 then + sex = nil + end + GAMEMODE.PlayerModelSex = sex +end) diff --git a/gamemodes/ultimateph/gamemode/cl_killfeed.lua b/gamemodes/ultimateph/gamemode/cl_killfeed.lua index 1c85347..b2c39ab 100644 --- a/gamemodes/ultimateph/gamemode/cl_killfeed.lua +++ b/gamemodes/ultimateph/gamemode/cl_killfeed.lua @@ -1,116 +1,116 @@ -local killFeedEvents = {} - -local KILL_FEED_MESSAGE_TIMEOUT = 10 -- How long a message stays in the kill feed before starting to fade away. -local VISUALS_UPDATE_INTERVAL = 0.05 -- Tick speed of the visual update timer. Smaller number = smoother visuals. -local FADE_TIME = 1.5 -- How long it takes for an expiring kill feed message to fade away. - -local NUM_FADE_TICKS = math.floor(FADE_TIME / VISUALS_UPDATE_INTERVAL) -local FADE_AMOUNT = math.floor(255 / NUM_FADE_TICKS) - -local FONT = "RobotoHUD-15" -- Font used for kill feed messages. - -function GM:ClearKillFeed() - killFeedEvents = {} -end - --- This timer handles the visual updates for kill feed messages. --- New messages are given a "slide in" effect wherein they slide in from off-screen. --- Expiring messages are given a fade out effect. -timer.Create("ph_timer_kill_feed_visuals", VISUALS_UPDATE_INTERVAL, 0, function() - for i, event in ipairs(killFeedEvents) do - local finalPos = ScrW() - 10 - event.eventWidth - -- Handle slide in effect. - if event.new then - -- The distance to slide in a single "frame" is going to be proportional to how much distance is - -- left to the final position. This will be scaled up by 50% to make things go faster. - local subtractAmt = math.floor((event.x - finalPos) * 0.5) - -- Always move by at least one pixel. - if subtractAmt < 1 then - subtractAmt = 1 - end - - event.x = event.x - subtractAmt - if event.x < finalPos then - event.x = finalPos - event.new = false - end - -- Handle the fade out effect. - elseif event.entryTime + KILL_FEED_MESSAGE_TIMEOUT < CurTime() then - if event.attackerName then - event.attackerColor.a = event.attackerColor.a - FADE_AMOUNT - end - event.victimColor.a = event.victimColor.a - FADE_AMOUNT - event.messageColor.a = event.messageColor.a - FADE_AMOUNT - -- Remove message from the kill feed once it is completely transparent. - if event.messageColor.a < 0 then - table.remove(killFeedEvents, i) - end - end - end -end) - -net.Receive("ph_kill_feed_add", function(len) - local event = net.ReadTable() - event.entryTime = CurTime() - event.new = true -- New events are events that are in the process of "sliding in" to view. - - -- Starting x position. Individual draw statements will be relative to this value. - event.x = ScrW() -- This will be offscreen. - - -- Calculate the final width of the entire kill feed event. - surface.SetFont(FONT) - event.eventWidth = surface.GetTextSize(event.victimName) + surface.GetTextSize(" ") + surface.GetTextSize(event.message) - if event.attackerName then - event.eventWidth = event.eventWidth + surface.GetTextSize(event.attackerName) + surface.GetTextSize(" ") - end - - -- New events will start at the top. - event.y = 10 - - -- Shift existing events downward. - for _, kfEvent in ipairs(killFeedEvents) do - kfEvent.y = kfEvent.y + draw.GetFontHeight(FONT) - end - - table.insert(killFeedEvents, event) -end) - --- The shadow transparency will be the same as the provided color transparency. --- Returns the width and height of the NON SHADOW text. -local function drawShadowText(text, font, x, y, color) - draw.SimpleText(text, font, x + 1, y + 1, Color(0, 0, 0, color.a)) - return draw.SimpleText(text, font, x, y, color) -end - -local function drawKillFeedHUD() - for _, event in ipairs(killFeedEvents) do - local widthOffset = event.x - - -- New events need to have clipping disabled so they can be drawn while partially off-screen. - local oldClippingValue - if event.new then - oldClippingValue = DisableClipping(true) - end - - -- If Murder: [Attacker] [Message] [Victim] - -- If Suicide: [Victim] [Message] - - if event.attackerName then - widthOffset = widthOffset + drawShadowText(event.attackerName .. " ", FONT, widthOffset, event.y, event.attackerColor) - else - widthOffset = widthOffset + drawShadowText(event.victimName .. " ", FONT, widthOffset, event.y, event.victimColor) - end - - widthOffset = widthOffset + drawShadowText(event.message .. (event.attackerName && " " || ""), FONT, widthOffset, event.y, event.messageColor) - - if event.attackerName then - drawShadowText(event.victimName, FONT, widthOffset, event.y, event.victimColor) - end - - if event.new then - DisableClipping(oldClippingValue) - end - end -end - -hook.Add("HUDPaint", "ph_kill_feed_hud_paint", drawKillFeedHUD) +local killFeedEvents = {} + +local KILL_FEED_MESSAGE_TIMEOUT = 10 -- How long a message stays in the kill feed before starting to fade away. +local VISUALS_UPDATE_INTERVAL = 0.05 -- Tick speed of the visual update timer. Smaller number = smoother visuals. +local FADE_TIME = 1.5 -- How long it takes for an expiring kill feed message to fade away. + +local NUM_FADE_TICKS = math.floor(FADE_TIME / VISUALS_UPDATE_INTERVAL) +local FADE_AMOUNT = math.floor(255 / NUM_FADE_TICKS) + +local FONT = "RobotoHUD-15" -- Font used for kill feed messages. + +function GM:ClearKillFeed() + killFeedEvents = {} +end + +-- This timer handles the visual updates for kill feed messages. +-- New messages are given a "slide in" effect wherein they slide in from off-screen. +-- Expiring messages are given a fade out effect. +timer.Create("ph_timer_kill_feed_visuals", VISUALS_UPDATE_INTERVAL, 0, function() + for i, event in ipairs(killFeedEvents) do + local finalPos = ScrW() - 10 - event.eventWidth + -- Handle slide in effect. + if event.new then + -- The distance to slide in a single "frame" is going to be proportional to how much distance is + -- left to the final position. This will be scaled up by 50% to make things go faster. + local subtractAmt = math.floor((event.x - finalPos) * 0.5) + -- Always move by at least one pixel. + if subtractAmt < 1 then + subtractAmt = 1 + end + + event.x = event.x - subtractAmt + if event.x < finalPos then + event.x = finalPos + event.new = false + end + -- Handle the fade out effect. + elseif event.entryTime + KILL_FEED_MESSAGE_TIMEOUT < CurTime() then + if event.attackerName then + event.attackerColor.a = event.attackerColor.a - FADE_AMOUNT + end + event.victimColor.a = event.victimColor.a - FADE_AMOUNT + event.messageColor.a = event.messageColor.a - FADE_AMOUNT + -- Remove message from the kill feed once it is completely transparent. + if event.messageColor.a < 0 then + table.remove(killFeedEvents, i) + end + end + end +end) + +net.Receive("ph_kill_feed_add", function(len) + local event = net.ReadTable() + event.entryTime = CurTime() + event.new = true -- New events are events that are in the process of "sliding in" to view. + + -- Starting x position. Individual draw statements will be relative to this value. + event.x = ScrW() -- This will be offscreen. + + -- Calculate the final width of the entire kill feed event. + surface.SetFont(FONT) + event.eventWidth = surface.GetTextSize(event.victimName) + surface.GetTextSize(" ") + surface.GetTextSize(event.message) + if event.attackerName then + event.eventWidth = event.eventWidth + surface.GetTextSize(event.attackerName) + surface.GetTextSize(" ") + end + + -- New events will start at the top. + event.y = 10 + + -- Shift existing events downward. + for _, kfEvent in ipairs(killFeedEvents) do + kfEvent.y = kfEvent.y + draw.GetFontHeight(FONT) + end + + table.insert(killFeedEvents, event) +end) + +-- The shadow transparency will be the same as the provided color transparency. +-- Returns the width and height of the NON SHADOW text. +local function drawShadowText(text, font, x, y, color) + draw.SimpleText(text, font, x + 1, y + 1, Color(0, 0, 0, color.a)) + return draw.SimpleText(text, font, x, y, color) +end + +local function drawKillFeedHUD() + for _, event in ipairs(killFeedEvents) do + local widthOffset = event.x + + -- New events need to have clipping disabled so they can be drawn while partially off-screen. + local oldClippingValue + if event.new then + oldClippingValue = DisableClipping(true) + end + + -- If Murder: [Attacker] [Message] [Victim] + -- If Suicide: [Victim] [Message] + + if event.attackerName then + widthOffset = widthOffset + drawShadowText(event.attackerName .. " ", FONT, widthOffset, event.y, event.attackerColor) + else + widthOffset = widthOffset + drawShadowText(event.victimName .. " ", FONT, widthOffset, event.y, event.victimColor) + end + + widthOffset = widthOffset + drawShadowText(event.message .. (event.attackerName && " " || ""), FONT, widthOffset, event.y, event.messageColor) + + if event.attackerName then + drawShadowText(event.victimName, FONT, widthOffset, event.y, event.victimColor) + end + + if event.new then + DisableClipping(oldClippingValue) + end + end +end + +hook.Add("HUDPaint", "ph_kill_feed_hud_paint", drawKillFeedHUD) diff --git a/gamemodes/ultimateph/gamemode/cl_mapvote.lua b/gamemodes/ultimateph/gamemode/cl_mapvote.lua index 240fb53..2fc034f 100644 --- a/gamemodes/ultimateph/gamemode/cl_mapvote.lua +++ b/gamemodes/ultimateph/gamemode/cl_mapvote.lua @@ -1,56 +1,56 @@ -GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 -GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() - -net.Receive("ph_mapvote", function(len) - GAMEMODE.MapVoteStart = net.ReadFloat() - GAMEMODE.MapVoteTime = net.ReadFloat() - - local mapList = {} - while true do - local k = net.ReadUInt(16) - if k <= 0 then break end - local map = net.ReadString() - table.insert(mapList, map) - end - - GAMEMODE.SelfMapVote = nil - GAMEMODE.MapVotes = {} - GAMEMODE.MapVotesByMap = {} - GAMEMODE.MapList = mapList - - GAMEMODE:EndRoundMapVote() -end) - -net.Receive("ph_mapvotevotes", function(len) - local mapVotes = {} - while true do - local k = net.ReadUInt(8) - if k <= 0 then break end - local ply = net.ReadEntity() - local map = net.ReadString() - mapVotes[ply] = map - end - - GAMEMODE.SelfMapVote = nil - - local byMap = {} - for ply, map in pairs(mapVotes) do - byMap[map] = byMap[map] || {} - table.insert(byMap[map], ply) - - if ply == LocalPlayer() then - GAMEMODE.SelfMapVote = map - end - end - - GAMEMODE.MapVotes = mapVotes - GAMEMODE.MapVotesByMap = byMap -end) - -function GM:GetMapVoteStart() - return self.MapVoteStart -end - -function GM:GetMapVoteRunningTime() - return CurTime() - self.MapVoteStart -end +GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 +GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() + +net.Receive("ph_mapvote", function(len) + GAMEMODE.MapVoteStart = net.ReadFloat() + GAMEMODE.MapVoteTime = net.ReadFloat() + + local mapList = {} + while true do + local k = net.ReadUInt(16) + if k <= 0 then break end + local map = net.ReadString() + table.insert(mapList, map) + end + + GAMEMODE.SelfMapVote = nil + GAMEMODE.MapVotes = {} + GAMEMODE.MapVotesByMap = {} + GAMEMODE.MapList = mapList + + GAMEMODE:EndRoundMapVote() +end) + +net.Receive("ph_mapvotevotes", function(len) + local mapVotes = {} + while true do + local k = net.ReadUInt(8) + if k <= 0 then break end + local ply = net.ReadEntity() + local map = net.ReadString() + mapVotes[ply] = map + end + + GAMEMODE.SelfMapVote = nil + + local byMap = {} + for ply, map in pairs(mapVotes) do + byMap[map] = byMap[map] || {} + table.insert(byMap[map], ply) + + if ply == LocalPlayer() then + GAMEMODE.SelfMapVote = map + end + end + + GAMEMODE.MapVotes = mapVotes + GAMEMODE.MapVotesByMap = byMap +end) + +function GM:GetMapVoteStart() + return self.MapVoteStart +end + +function GM:GetMapVoteRunningTime() + return CurTime() - self.MapVoteStart +end diff --git a/gamemodes/ultimateph/gamemode/cl_ragdoll.lua b/gamemodes/ultimateph/gamemode/cl_ragdoll.lua index b077741..ea81d27 100644 --- a/gamemodes/ultimateph/gamemode/cl_ragdoll.lua +++ b/gamemodes/ultimateph/gamemode/cl_ragdoll.lua @@ -1,28 +1,28 @@ -local PlayerMeta = FindMetaTable("Player") -local EntityMeta = FindMetaTable("Entity") - -if !PlayerMeta.GetRagdollEntityOld then - PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity -end - -function PlayerMeta:GetRagdollEntity() - local ent = self:GetNWEntity("DeathRagdoll") - if IsValid(ent) then - return ent - end - - return self:GetRagdollEntityOld() -end - -if !EntityMeta.GetRagdollOwnerOld then - EntityMeta.GetRagdollOwnerOld = EntityMeta.GetRagdollOwner -end - -function EntityMeta:GetRagdollOwner() - local ent = self:GetNWEntity("RagdollOwner") - if IsValid(ent) then - return ent - end - - return self:GetRagdollOwnerOld() -end +local PlayerMeta = FindMetaTable("Player") +local EntityMeta = FindMetaTable("Entity") + +if !PlayerMeta.GetRagdollEntityOld then + PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity +end + +function PlayerMeta:GetRagdollEntity() + local ent = self:GetNWEntity("DeathRagdoll") + if IsValid(ent) then + return ent + end + + return self:GetRagdollEntityOld() +end + +if !EntityMeta.GetRagdollOwnerOld then + EntityMeta.GetRagdollOwnerOld = EntityMeta.GetRagdollOwner +end + +function EntityMeta:GetRagdollOwner() + local ent = self:GetNWEntity("RagdollOwner") + if IsValid(ent) then + return ent + end + + return self:GetRagdollOwnerOld() +end diff --git a/gamemodes/ultimateph/gamemode/cl_rounds.lua b/gamemodes/ultimateph/gamemode/cl_rounds.lua index ad304a7..5047077 100644 --- a/gamemodes/ultimateph/gamemode/cl_rounds.lua +++ b/gamemodes/ultimateph/gamemode/cl_rounds.lua @@ -1,56 +1,56 @@ -GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT -GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() - -function GM:GetGameState() - return self.GameState -end - -function GM:GetStateStart() - return self.StateStart -end - -function GM:GetStateRunningTime() - return CurTime() - self.StateStart -end - -net.Receive("gamestate", function(len) - GAMEMODE.GameState = net.ReadUInt(32) - GAMEMODE.StateStart = net.ReadDouble() - - if GAMEMODE.GameState == ROUND_HIDE then - GAMEMODE.UpgradesNotif = {} - GAMEMODE.ClearKillFeed() - end - - if GAMEMODE.GameState != ROUND_SEEK then - GAMEMODE:CloseEndRoundMenu() - end -end) - -net.Receive("round_victor", function(len) - local tab = {} - tab.winningTeam = net.ReadUInt(8) - tab.playerAwards = net.ReadTable() - - -- open the results panel - timer.Create("ph_timer_show_results_delay", 2, 1, function() - GAMEMODE:EndRoundMenuResults(tab) - end) -end) - -net.Receive("gamerules", function() - local settings = {} - while net.ReadUInt(8) != 0 do - local k = net.ReadString() - local t = net.ReadUInt(8) - local v = net.ReadType(t) - settings[k] = v - end - - GAMEMODE.RoundSettings = settings -end) - -function GM:GetRoundSettings() - self.RoundSettings = self.RoundSettings || {} - return self.RoundSettings -end +GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT +GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() + +function GM:GetGameState() + return self.GameState +end + +function GM:GetStateStart() + return self.StateStart +end + +function GM:GetStateRunningTime() + return CurTime() - self.StateStart +end + +net.Receive("gamestate", function(len) + GAMEMODE.GameState = net.ReadUInt(32) + GAMEMODE.StateStart = net.ReadDouble() + + if GAMEMODE.GameState == ROUND_HIDE then + GAMEMODE.UpgradesNotif = {} + GAMEMODE.ClearKillFeed() + end + + if GAMEMODE.GameState != ROUND_SEEK then + GAMEMODE:CloseEndRoundMenu() + end +end) + +net.Receive("round_victor", function(len) + local tab = {} + tab.winningTeam = net.ReadUInt(8) + tab.playerAwards = net.ReadTable() + + -- open the results panel + timer.Create("ph_timer_show_results_delay", 2, 1, function() + GAMEMODE:EndRoundMenuResults(tab) + end) +end) + +net.Receive("gamerules", function() + local settings = {} + while net.ReadUInt(8) != 0 do + local k = net.ReadString() + local t = net.ReadUInt(8) + local v = net.ReadType(t) + settings[k] = v + end + + GAMEMODE.RoundSettings = settings +end) + +function GM:GetRoundSettings() + self.RoundSettings = self.RoundSettings || {} + return self.RoundSettings +end diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index 5770b70..d8f9e65 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -1,365 +1,365 @@ -if GAMEMODE && IsValid(GAMEMODE.ScoreboardPanel) then - GAMEMODE.ScoreboardPanel:Remove() -end - -local menu -include("cl_colors.lua") - -GroupColors = {} -GroupColors["owner"] = PHGreen -GroupColors["superadmin"] = PHPink -GroupColors["headadmin"] = PHMauve -GroupColors["admin"] = PHYellow -GroupColors["moderator"] = PHBlue -GroupColors["operator"] = PHPink -GroupColors["superowner"] = PHRed -GroupColors["trusted"] = PHPeach -GroupColors["user"] = PHTeal - -GroupNames = {} -GroupNames["owner"] = "Owner" -GroupNames["superadmin"] = "Super Admin" -GroupNames["headadmin"] = "Head Admin" -GroupNames["admin"] = "Admin" -GroupNames["moderator"] = "Moderator" -GroupNames["operator"] = "Operator" -GroupNames["superowner"] = "Super Owner" -GroupNames["trusted"] = "Trusted" -GroupNames["user"] = "User" - -surface.CreateFont("ScoreboardPlayer" , { - font = "coolvetica", - size = 32, - weight = 500, - antialias = true, - italic = false -}) - -local muted = Material("icon32/muted.png", "noclamp") -local skull = Material("husklesph/skull.png", "noclamp") - -local function addPlayerItem(self, mlist, ply, pteam) - local but = vgui.Create("DButton") - but.player = ply - but.ctime = CurTime() - but:SetTall(draw.GetFontHeight("RobotoHUD-20")) - but:SetText("") - - function but:Paint(w, h) - if GroupColors[ply:GetUserGroup()] ~= nil then - surface.SetDrawColor(Color(GroupColors[ply:GetUserGroup()].r, GroupColors[ply:GetUserGroup()].g, GroupColors[ply:GetUserGroup()].b)) - else - surface.SetDrawColor(PHGreen) - end - self:DrawFilledRect() - - if IsValid(ply) && ply:IsPlayer() then - local s = 4 - if !ply:Alive() then - surface.SetMaterial(skull) - surface.SetDrawColor(220, 220, 220, 255) - surface.DrawTexturedRect(s, h / 2 - 16, 32, 32) - s = s + 32 + 4 - end - - if ply:IsMuted() then - surface.SetMaterial(muted) - - -- draw mute icon - surface.SetDrawColor(150, 150, 150, 255) - surface.DrawTexturedRect(s, h / 2 - 16, 32, 32) - s = s + 32 + 4 - end - - local col = color_white - draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - 4, 0, col, 2) - if GroupNames[ply:GetUserGroup()] then - draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHDarkest, 0) - s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + 4 - draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) - else - draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) - end - end - end - - function but:DoClick() - if IsValid(ply) then - GAMEMODE:DoScoreboardActionPopup(ply) - end - end - - mlist:AddItem(but) -end - -local function doPlayerItems(self, mlist, pteam) - for k, ply in pairs(team.GetPlayers(pteam)) do - local found = false - for t, v in pairs(mlist:GetCanvas():GetChildren()) do - if v.player == ply then - found = true - v.ctime = CurTime() - end - end - - if !found then - addPlayerItem(self, mlist, ply, pteam) - end - end - - local del = false - for t, v in pairs(mlist:GetCanvas():GetChildren()) do - if !v.perm && v.ctime != CurTime() then - v:Remove() - del = true - end - end - - -- make sure the rest of the elements are moved up - if del then - timer.Simple(0, function() mlist:GetCanvas():InvalidateLayout() end) - end -end - -local function makeTeamList(parent, pteam) - local mlist - local pnl = vgui.Create("DPanel", parent) - pnl:DockPadding(0, 0, 0, 0) - local hs = math.Round(draw.GetFontHeight("RobotoHUD-25") * 1.1) - - function pnl:Paint(w, h) - surface.SetDrawColor(PHDarkest) - surface.DrawLine(0, hs, 0, h - 1) - surface.DrawLine(w - 1, hs, w - 1, h - 1) - surface.DrawLine(0, h - 1, w, h - 1) - surface.SetDrawColor(PHDarker) - surface.DrawRect(1, hs, w - 2, h - hs) - end - - function pnl:Think() - if !self.RefreshWait || self.RefreshWait < CurTime() then - self.RefreshWait = CurTime() + 0.1 - doPlayerItems(self, mlist, pteam) - end - end - - local headp = vgui.Create("DPanel", pnl) - headp:DockMargin(0, 0, 0, 4) - headp:Dock(TOP) - headp:SetTall(hs) - - function headp:Paint(w, h) - surface.SetDrawColor(68, 68, 68, 255) - draw.RoundedBoxEx(4, 0, 0, w, h, PHDarkest, true, true, false, false) - draw.SimpleText(team.GetName(pteam), "RobotoHUD-25", 6, 0, team.GetColor(pteam), 0) - end - - local but = vgui.Create("DButton", headp) - but:Dock(RIGHT) - but:SetText("") - surface.SetFont("RobotoHUD-20") - local tw, th = surface.GetTextSize("Join team") - but:SetWide(tw + 6) - - function but:DoClick() - RunConsoleCommand("ph_jointeam", pteam) - end - - function but:Paint(w, h) - surface.SetDrawColor(team.GetColor(pteam)) - surface.SetDrawColor(color_black) - - local col = table.Copy(team.GetColor(pteam)) - if self:IsDown() then - surface.SetDrawColor(12, 50, 50, 120) - col.r = col.r * 0.8 - col.g = col.g * 0.8 - col.b = col.b * 0.8 - elseif self:IsHovered() then - surface.SetDrawColor(255, 255, 255, 30) - col.r = col.r * 1.2 - col.g = col.g * 1.2 - col.b = col.b * 1.2 - end - - draw.SimpleText("Join team", "RobotoHUD-20", 2, h / 2 - th / 2, col, 0) - end - - mlist = vgui.Create("DScrollPanel", pnl) - mlist:Dock(FILL) - - function mlist:Paint(w, h) - end - - -- child positioning - local canvas = mlist:GetCanvas() - canvas:DockPadding(8, 8, 8, 8) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) - end - - local head = vgui.Create("DPanel") - head:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.05) - head.perm = true - local col = Color(190, 190, 190) - - function head:Paint(w, h) - draw.SimpleText("Name", "RobotoHUD-15", 4, 0, col, 0) - draw.SimpleText("Ping", "RobotoHUD-15", w - 4, 0, col, 2) - end - - mlist:AddItem(head) - return pnl -end - -function GM:ScoreboardRoundResults(results) - self:ScoreboardShow() - menu.ResultsPanel.Results = results - menu.ResultsPanel:InvalidateLayout() -end - -local function createScoreboardPanel() - menu = vgui.Create("DFrame") - GAMEMODE.ScoreboardPanel = menu - menu:SetSize(ScrW() * 0.8, ScrH() * 0.8) - menu:Center() - menu:MakePopup() - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - menu:SetDraggable(false) - menu:ShowCloseButton(false) - menu:SetTitle("") - menu:DockPadding(8, 8, 8, 8) - - function menu:PerformLayout() - if IsValid(menu.HuntersList) then - menu.HuntersList:SetWidth((self:GetWide() - 16) * 0.5) - end - end - - function menu:Paint(w, h) - surface.SetDrawColor(0, 0, 0, 0) - surface.DrawRect(0, 0, w, h) - end - - local bottom = vgui.Create("DPanel", menu) - bottom:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - bottom:Dock(BOTTOM) - bottom:DockMargin(0, 8, 0, 0) - - surface.SetFont("RobotoHUD-15") - local tw = surface.GetTextSize("Spectate") - - function bottom:Paint(w, h) - draw.RoundedBox(0, 0, 0, w, h, PHDarker) - local c - for k, ply in pairs(team.GetPlayers(TEAM_SPEC)) do - if c then - c = c .. ", " .. ply:Nick() - else - c = ply:Nick() - end - end - - if c then - draw.ShadowText(c, "RobotoHUD-10", tw + 8 + 4, h / 2, color_white, 0, 1) - end - end - - local but = vgui.Create("DButton", bottom) - but:Dock(LEFT) - but:SetText("") - but:DockMargin(0, 0, 4, 0) - but:SetWide(tw + 8) - - function but:Paint(w, h) - local col = PHWhite - if self:IsDown() then - col = PHLessWhite - elseif self:IsHovered() then - col = PHLessWhite - end - - draw.RoundedBox(0, 0, 0, w, h, PHDarkest) - draw.SimpleText("Spectate", "RobotoHUD-15", w / 2, h / 2, col, 1, 1) - end - - function but:DoClick() - RunConsoleCommand("ph_jointeam", TEAM_SPEC) - end - - local main = vgui.Create("DPanel", menu) - main:Dock(FILL) - - function main:Paint(w, h) - surface.SetDrawColor(40, 40, 40, 0) - end - - menu.HuntersList = makeTeamList(main, TEAM_HUNTER) - menu.HuntersList:Dock(LEFT) - menu.HuntersList:DockMargin(0, 0, 8, 0) - menu.PropsList = makeTeamList(main, TEAM_PROP) - menu.PropsList:Dock(FILL) -end - -function GM:ScoreboardShow() - if !IsValid(menu) then - createScoreboardPanel() - end - - menu:SetVisible(true) -end - -function GM:ScoreboardHide() - if IsValid(menu) then - menu:Close() - DermaMenu() - end -end - -function GM:DoScoreboardActionPopup(ply) - local actions = DermaMenu() - - if ply != LocalPlayer() then - local t = "Mute" - if ply:IsMuted() then - t = "Unmute" - end - - local mute = actions:AddOption(t) - mute:SetIcon("icon16/sound_mute.png") - - function mute:DoClick() - if IsValid(ply) then - ply:SetMuted(!ply:IsMuted()) - end - end - end - - local t = "View Steam Profile" - local steamprofile = actions:AddOption(t) - steamprofile:SetIcon("icon16/application.png") - - function steamprofile:DoClick() - if IsValid(ply) then - gui.OpenURL("https://steamcommunity.com/profiles/".. ply:SteamID64()) - end - end - --- this requires ulx - if LocalPlayer():IsAdmin() then - local t = "Force Team Switch" - local switchteams = actions:AddOption(t) - switchteams:SetIcon("icon16/shield.png") - - function switchteams:DoClick() - if IsValid(ply) then - LocalPlayer():ConCommand("ulx teamswitch ".. ply:Nick()) - end - end - end - - actions:Open() -end +if GAMEMODE && IsValid(GAMEMODE.ScoreboardPanel) then + GAMEMODE.ScoreboardPanel:Remove() +end + +local menu +include("cl_colors.lua") + +GroupColors = {} +GroupColors["owner"] = PHGreen +GroupColors["superadmin"] = PHPink +GroupColors["headadmin"] = PHMauve +GroupColors["admin"] = PHYellow +GroupColors["moderator"] = PHBlue +GroupColors["operator"] = PHPink +GroupColors["superowner"] = PHRed +GroupColors["trusted"] = PHPeach +GroupColors["user"] = PHTeal + +GroupNames = {} +GroupNames["owner"] = "Owner" +GroupNames["superadmin"] = "Super Admin" +GroupNames["headadmin"] = "Head Admin" +GroupNames["admin"] = "Admin" +GroupNames["moderator"] = "Moderator" +GroupNames["operator"] = "Operator" +GroupNames["superowner"] = "Super Owner" +GroupNames["trusted"] = "Trusted" +GroupNames["user"] = "User" + +surface.CreateFont("ScoreboardPlayer" , { + font = "coolvetica", + size = 32, + weight = 500, + antialias = true, + italic = false +}) + +local muted = Material("icon32/muted.png", "noclamp") +local skull = Material("husklesph/skull.png", "noclamp") + +local function addPlayerItem(self, mlist, ply, pteam) + local but = vgui.Create("DButton") + but.player = ply + but.ctime = CurTime() + but:SetTall(draw.GetFontHeight("RobotoHUD-20")) + but:SetText("") + + function but:Paint(w, h) + if GroupColors[ply:GetUserGroup()] ~= nil then + surface.SetDrawColor(Color(GroupColors[ply:GetUserGroup()].r, GroupColors[ply:GetUserGroup()].g, GroupColors[ply:GetUserGroup()].b)) + else + surface.SetDrawColor(PHGreen) + end + self:DrawFilledRect() + + if IsValid(ply) && ply:IsPlayer() then + local s = 4 + if !ply:Alive() then + surface.SetMaterial(skull) + surface.SetDrawColor(220, 220, 220, 255) + surface.DrawTexturedRect(s, h / 2 - 16, 32, 32) + s = s + 32 + 4 + end + + if ply:IsMuted() then + surface.SetMaterial(muted) + + -- draw mute icon + surface.SetDrawColor(150, 150, 150, 255) + surface.DrawTexturedRect(s, h / 2 - 16, 32, 32) + s = s + 32 + 4 + end + + local col = color_white + draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - 4, 0, col, 2) + if GroupNames[ply:GetUserGroup()] then + draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHDarkest, 0) + s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + 4 + draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) + else + draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) + end + end + end + + function but:DoClick() + if IsValid(ply) then + GAMEMODE:DoScoreboardActionPopup(ply) + end + end + + mlist:AddItem(but) +end + +local function doPlayerItems(self, mlist, pteam) + for k, ply in pairs(team.GetPlayers(pteam)) do + local found = false + for t, v in pairs(mlist:GetCanvas():GetChildren()) do + if v.player == ply then + found = true + v.ctime = CurTime() + end + end + + if !found then + addPlayerItem(self, mlist, ply, pteam) + end + end + + local del = false + for t, v in pairs(mlist:GetCanvas():GetChildren()) do + if !v.perm && v.ctime != CurTime() then + v:Remove() + del = true + end + end + + -- make sure the rest of the elements are moved up + if del then + timer.Simple(0, function() mlist:GetCanvas():InvalidateLayout() end) + end +end + +local function makeTeamList(parent, pteam) + local mlist + local pnl = vgui.Create("DPanel", parent) + pnl:DockPadding(0, 0, 0, 0) + local hs = math.Round(draw.GetFontHeight("RobotoHUD-25") * 1.1) + + function pnl:Paint(w, h) + surface.SetDrawColor(PHDarkest) + surface.DrawLine(0, hs, 0, h - 1) + surface.DrawLine(w - 1, hs, w - 1, h - 1) + surface.DrawLine(0, h - 1, w, h - 1) + surface.SetDrawColor(PHDarker) + surface.DrawRect(1, hs, w - 2, h - hs) + end + + function pnl:Think() + if !self.RefreshWait || self.RefreshWait < CurTime() then + self.RefreshWait = CurTime() + 0.1 + doPlayerItems(self, mlist, pteam) + end + end + + local headp = vgui.Create("DPanel", pnl) + headp:DockMargin(0, 0, 0, 4) + headp:Dock(TOP) + headp:SetTall(hs) + + function headp:Paint(w, h) + surface.SetDrawColor(68, 68, 68, 255) + draw.RoundedBoxEx(4, 0, 0, w, h, PHDarkest, true, true, false, false) + draw.SimpleText(team.GetName(pteam), "RobotoHUD-25", 6, 0, team.GetColor(pteam), 0) + end + + local but = vgui.Create("DButton", headp) + but:Dock(RIGHT) + but:SetText("") + surface.SetFont("RobotoHUD-20") + local tw, th = surface.GetTextSize("Join team") + but:SetWide(tw + 6) + + function but:DoClick() + RunConsoleCommand("ph_jointeam", pteam) + end + + function but:Paint(w, h) + surface.SetDrawColor(team.GetColor(pteam)) + surface.SetDrawColor(color_black) + + local col = table.Copy(team.GetColor(pteam)) + if self:IsDown() then + surface.SetDrawColor(12, 50, 50, 120) + col.r = col.r * 0.8 + col.g = col.g * 0.8 + col.b = col.b * 0.8 + elseif self:IsHovered() then + surface.SetDrawColor(255, 255, 255, 30) + col.r = col.r * 1.2 + col.g = col.g * 1.2 + col.b = col.b * 1.2 + end + + draw.SimpleText("Join team", "RobotoHUD-20", 2, h / 2 - th / 2, col, 0) + end + + mlist = vgui.Create("DScrollPanel", pnl) + mlist:Dock(FILL) + + function mlist:Paint(w, h) + end + + -- child positioning + local canvas = mlist:GetCanvas() + canvas:DockPadding(8, 8, 8, 8) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, 4) + end + + local head = vgui.Create("DPanel") + head:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.05) + head.perm = true + local col = Color(190, 190, 190) + + function head:Paint(w, h) + draw.SimpleText("Name", "RobotoHUD-15", 4, 0, col, 0) + draw.SimpleText("Ping", "RobotoHUD-15", w - 4, 0, col, 2) + end + + mlist:AddItem(head) + return pnl +end + +function GM:ScoreboardRoundResults(results) + self:ScoreboardShow() + menu.ResultsPanel.Results = results + menu.ResultsPanel:InvalidateLayout() +end + +local function createScoreboardPanel() + menu = vgui.Create("DFrame") + GAMEMODE.ScoreboardPanel = menu + menu:SetSize(ScrW() * 0.8, ScrH() * 0.8) + menu:Center() + menu:MakePopup() + menu:SetKeyboardInputEnabled(false) + menu:SetDeleteOnClose(false) + menu:SetDraggable(false) + menu:ShowCloseButton(false) + menu:SetTitle("") + menu:DockPadding(8, 8, 8, 8) + + function menu:PerformLayout() + if IsValid(menu.HuntersList) then + menu.HuntersList:SetWidth((self:GetWide() - 16) * 0.5) + end + end + + function menu:Paint(w, h) + surface.SetDrawColor(0, 0, 0, 0) + surface.DrawRect(0, 0, w, h) + end + + local bottom = vgui.Create("DPanel", menu) + bottom:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) + bottom:Dock(BOTTOM) + bottom:DockMargin(0, 8, 0, 0) + + surface.SetFont("RobotoHUD-15") + local tw = surface.GetTextSize("Spectate") + + function bottom:Paint(w, h) + draw.RoundedBox(0, 0, 0, w, h, PHDarker) + local c + for k, ply in pairs(team.GetPlayers(TEAM_SPEC)) do + if c then + c = c .. ", " .. ply:Nick() + else + c = ply:Nick() + end + end + + if c then + draw.ShadowText(c, "RobotoHUD-10", tw + 8 + 4, h / 2, color_white, 0, 1) + end + end + + local but = vgui.Create("DButton", bottom) + but:Dock(LEFT) + but:SetText("") + but:DockMargin(0, 0, 4, 0) + but:SetWide(tw + 8) + + function but:Paint(w, h) + local col = PHWhite + if self:IsDown() then + col = PHLessWhite + elseif self:IsHovered() then + col = PHLessWhite + end + + draw.RoundedBox(0, 0, 0, w, h, PHDarkest) + draw.SimpleText("Spectate", "RobotoHUD-15", w / 2, h / 2, col, 1, 1) + end + + function but:DoClick() + RunConsoleCommand("ph_jointeam", TEAM_SPEC) + end + + local main = vgui.Create("DPanel", menu) + main:Dock(FILL) + + function main:Paint(w, h) + surface.SetDrawColor(40, 40, 40, 0) + end + + menu.HuntersList = makeTeamList(main, TEAM_HUNTER) + menu.HuntersList:Dock(LEFT) + menu.HuntersList:DockMargin(0, 0, 8, 0) + menu.PropsList = makeTeamList(main, TEAM_PROP) + menu.PropsList:Dock(FILL) +end + +function GM:ScoreboardShow() + if !IsValid(menu) then + createScoreboardPanel() + end + + menu:SetVisible(true) +end + +function GM:ScoreboardHide() + if IsValid(menu) then + menu:Close() + DermaMenu() + end +end + +function GM:DoScoreboardActionPopup(ply) + local actions = DermaMenu() + + if ply != LocalPlayer() then + local t = "Mute" + if ply:IsMuted() then + t = "Unmute" + end + + local mute = actions:AddOption(t) + mute:SetIcon("icon16/sound_mute.png") + + function mute:DoClick() + if IsValid(ply) then + ply:SetMuted(!ply:IsMuted()) + end + end + end + + local t = "View Steam Profile" + local steamprofile = actions:AddOption(t) + steamprofile:SetIcon("icon16/application.png") + + function steamprofile:DoClick() + if IsValid(ply) then + gui.OpenURL("https://steamcommunity.com/profiles/".. ply:SteamID64()) + end + end + +-- this requires ulx + if LocalPlayer():IsAdmin() then + local t = "Force Team Switch" + local switchteams = actions:AddOption(t) + switchteams:SetIcon("icon16/shield.png") + + function switchteams:DoClick() + if IsValid(ply) then + LocalPlayer():ConCommand("ulx teamswitch ".. ply:Nick()) + end + end + end + + actions:Open() +end diff --git a/gamemodes/ultimateph/gamemode/cl_spectate.lua b/gamemodes/ultimateph/gamemode/cl_spectate.lua index 0a923b2..2430c26 100644 --- a/gamemodes/ultimateph/gamemode/cl_spectate.lua +++ b/gamemodes/ultimateph/gamemode/cl_spectate.lua @@ -1,22 +1,22 @@ -net.Receive("spectating_status", function(length) - GAMEMODE.SpectateMode = net.ReadInt(8) - GAMEMODE.Spectating = false - GAMEMODE.Spectatee = nil - if GAMEMODE.SpectateMode >= 0 then - GAMEMODE.Spectating = true - GAMEMODE.Spectatee = net.ReadEntity() - end - -end) - -function GM:IsCSpectating() - return self.Spectating -end - -function GM:GetCSpectatee() - return self.Spectatee -end - -function GM:GetCSpectateMode() - return self.SpectateMode -end +net.Receive("spectating_status", function(length) + GAMEMODE.SpectateMode = net.ReadInt(8) + GAMEMODE.Spectating = false + GAMEMODE.Spectatee = nil + if GAMEMODE.SpectateMode >= 0 then + GAMEMODE.Spectating = true + GAMEMODE.Spectatee = net.ReadEntity() + end + +end) + +function GM:IsCSpectating() + return self.Spectating +end + +function GM:GetCSpectatee() + return self.Spectatee +end + +function GM:GetCSpectateMode() + return self.SpectateMode +end diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index 4274ed9..9b917ba 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -1,254 +1,254 @@ -include("sh_taunt.lua") -include("cl_colors.lua") - -local menu -local lastCursorX -local lastCursorY - -local function saveCursor() - lastCursorX, lastCursorY = input.GetCursorPos() -end - -local function restoreCursor() - if !lastCursorX then return end - - input.SetCursorPos(lastCursorX, lastCursorY) - lastCursorX = nil - lastCursorY = nil -end - -local function fillList(mlist, taunts, cat) - menu.CurrentTaunts = taunts - menu.CurrentTauntCat = cat - for k, v in pairs(menu.CatList:GetCanvas():GetChildren()) do - v.Selected = false - if v.CatName == cat then - v.Selected = true - end - end - - mlist:Clear() - for k, t in pairs(taunts) do - if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end - - local but = vgui.Create("DButton") - but:SetTall(draw.GetFontHeight("RobotoHUD-L15") * 1.0) - but:SetText("") - - function but:Paint(w, h) - if self:IsDown() then - col = PHLesserWhite - elseif self:IsHovered() then - col = PHLesserWhite - else - col = PHWhite - end - draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, 0, 1) - draw.ShadowText(math.Round(t.soundDuration * 10) / 10 .. "s", "RobotoHUD-L10", w, h / 2, col, 2, 1) - end - - function but:DoClick() - RunConsoleCommand("ph_taunt", t.sound[math.random(#t.sound)]) - saveCursor() - menu:Close() - end - - mlist:AddItem(but) - end -end - -local function addCat(clist, name, taunts, mlist) - local dname = name:lower():gsub("[_]", " ") - dname = dname:sub(1, 1):upper() .. dname:sub(2) - - local but = vgui.Create("DButton") - but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - but:SetText("") - but.Selected = false - but.CatName = name - - function but:Paint(w, h) - local colt = PHWhite - if !self.Selected then - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - if self:IsDown() then - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - elseif self:IsHovered() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - end - else - col = Color(PHDark.r, PHDark.g, PHDark.b) - end - - draw.RoundedBoxEx(4, 0, 0, w, h, col, true, false, true, false) - draw.ShadowText(dname, "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) - end - - function but:DoClick() - fillList(mlist, taunts, name) - self.Selected = true - end - - clist:AddItem(but) - return but -end - -local function fillCats(clist, mlist) - clist:Clear() - local all = addCat(clist, "all", Taunts, mlist) - for k, taunts in pairs(TauntCategories) do - local c = 0 - for a, t in pairs(taunts) do - if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end - - c = c + 1 - end - - if c > 0 then - addCat(clist, k, taunts, mlist) - end - end - - all.Selected = true -end - -local function openTauntMenu() - restoreCursor() - if IsValid(menu) then - fillCats(menu.CatList, menu.TauntList) - fillList(menu.TauntList, menu.CurrentTaunts, menu.CurrentTauntCat) - if menu:IsVisible() then - saveCursor() - end - - menu:SetVisible(!menu:IsVisible()) - return - end - - menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) - menu:Center() - menu:SetTitle("") - menu:MakePopup() - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - menu:SetDraggable(false) - menu:ShowCloseButton(false) - menu:DockPadding(8, 8 + draw.GetFontHeight("RobotoHUD-25"), 8, 8) - - local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('marlett') - closeButton:SetText('r') - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - closeButton.OnCursorEntered = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) - end - end - closeButton.OnCursorExited = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - end - closeButton:SetColor(PHWhite) - closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) - closeButton:SetPos(menu:GetWide() / 1.05, 0) - closeButton.DoClick = function() - menu:Close() - end - - function menu:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawRect(0, 0, w, h) - surface.SetFont("RobotoHUD-25") - local t = "Taunts" - local tw, th = surface.GetTextSize(t) - draw.SimpleText(t, "RobotoHUD-25", 8, 2, PHBlue, 0) - draw.SimpleText(TauntMenuPhrase, "RobotoHUD-L15", 8 + tw + 16, 2 + th * 0.90, PHWhite, 0, 4) - end - - local leftpnl = vgui.Create("DPanel", menu) - leftpnl:Dock(LEFT) - leftpnl:DockMargin(0, 0, 0, 0) - leftpnl:SetWide(menu:GetWide() * 0.3) - function leftpnl:Paint(w, h) - end - - local but = vgui.Create("DButton", leftpnl) - but:Dock(BOTTOM) - but:DockMargin(0, 4, 4, 0) - but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - but:SetText("") - - function but:Paint(w, h) - local colt = PHWhite - if self:IsDown() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - elseif self:IsHovered() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - else - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - end - - draw.RoundedBoxEx(4, 0, 0, w, h, col, true, true, true, true) - draw.ShadowText("Random", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) - end - - function but:DoClick() - RunConsoleCommand("ph_taunt_random") - saveCursor() - menu:Close() - end - - local clist = vgui.Create("DScrollPanel", leftpnl) - menu.CatList = clist - clist:Dock(FILL) - clist:DockMargin(0, 0, 0, 0) - - function clist:Paint(w, h) - end - - local canvas = clist:GetCanvas() - canvas:DockPadding(0, 0, 0, 0) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) - end - - local mlist = vgui.Create("DScrollPanel", menu) - menu.TauntList = mlist - mlist:Dock(FILL) - - function mlist:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) - surface.DrawRect(1, 1, w - 2, h - 2) - end - - -- child positioning - local canvas = mlist:GetCanvas() - canvas:DockPadding(8, 8, 8, 8) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) - end - - fillList(mlist, Taunts) - fillCats(clist, mlist, "all") -end - -concommand.Add("ph_menu_taunt", openTauntMenu) -net.Receive("open_taunt_menu", openTauntMenu) - -net.Receive("ph_set_taunt_menu_phrase", function() - value_new = net.ReadString() - - -- Prevent this from being done more than once. GMod is weird. - if TauntMenuPhrase == value_new then return end - TauntMenuPhrase = value_new -end) +include("sh_taunt.lua") +include("cl_colors.lua") + +local menu +local lastCursorX +local lastCursorY + +local function saveCursor() + lastCursorX, lastCursorY = input.GetCursorPos() +end + +local function restoreCursor() + if !lastCursorX then return end + + input.SetCursorPos(lastCursorX, lastCursorY) + lastCursorX = nil + lastCursorY = nil +end + +local function fillList(mlist, taunts, cat) + menu.CurrentTaunts = taunts + menu.CurrentTauntCat = cat + for k, v in pairs(menu.CatList:GetCanvas():GetChildren()) do + v.Selected = false + if v.CatName == cat then + v.Selected = true + end + end + + mlist:Clear() + for k, t in pairs(taunts) do + if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end + + local but = vgui.Create("DButton") + but:SetTall(draw.GetFontHeight("RobotoHUD-L15") * 1.0) + but:SetText("") + + function but:Paint(w, h) + if self:IsDown() then + col = PHLesserWhite + elseif self:IsHovered() then + col = PHLesserWhite + else + col = PHWhite + end + draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, 0, 1) + draw.ShadowText(math.Round(t.soundDuration * 10) / 10 .. "s", "RobotoHUD-L10", w, h / 2, col, 2, 1) + end + + function but:DoClick() + RunConsoleCommand("ph_taunt", t.sound[math.random(#t.sound)]) + saveCursor() + menu:Close() + end + + mlist:AddItem(but) + end +end + +local function addCat(clist, name, taunts, mlist) + local dname = name:lower():gsub("[_]", " ") + dname = dname:sub(1, 1):upper() .. dname:sub(2) + + local but = vgui.Create("DButton") + but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) + but:SetText("") + but.Selected = false + but.CatName = name + + function but:Paint(w, h) + local colt = PHWhite + if !self.Selected then + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) + if self:IsDown() then + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) + elseif self:IsHovered() then + col = Color(PHDark.r, PHDark.g, PHDark.b) + end + else + col = Color(PHDark.r, PHDark.g, PHDark.b) + end + + draw.RoundedBoxEx(4, 0, 0, w, h, col, true, false, true, false) + draw.ShadowText(dname, "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) + end + + function but:DoClick() + fillList(mlist, taunts, name) + self.Selected = true + end + + clist:AddItem(but) + return but +end + +local function fillCats(clist, mlist) + clist:Clear() + local all = addCat(clist, "all", Taunts, mlist) + for k, taunts in pairs(TauntCategories) do + local c = 0 + for a, t in pairs(taunts) do + if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end + + c = c + 1 + end + + if c > 0 then + addCat(clist, k, taunts, mlist) + end + end + + all.Selected = true +end + +local function openTauntMenu() + restoreCursor() + if IsValid(menu) then + fillCats(menu.CatList, menu.TauntList) + fillList(menu.TauntList, menu.CurrentTaunts, menu.CurrentTauntCat) + if menu:IsVisible() then + saveCursor() + end + + menu:SetVisible(!menu:IsVisible()) + return + end + + menu = vgui.Create("DFrame") + menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) + menu:Center() + menu:SetTitle("") + menu:MakePopup() + menu:SetKeyboardInputEnabled(false) + menu:SetDeleteOnClose(false) + menu:SetDraggable(false) + menu:ShowCloseButton(false) + menu:DockPadding(8, 8 + draw.GetFontHeight("RobotoHUD-25"), 8, 8) + + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('marlett') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + closeButton.OnCursorEntered = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) + end + end + closeButton.OnCursorExited = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) + closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton.DoClick = function() + menu:Close() + end + + function menu:Paint(w, h) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + surface.DrawRect(0, 0, w, h) + surface.SetFont("RobotoHUD-25") + local t = "Taunts" + local tw, th = surface.GetTextSize(t) + draw.SimpleText(t, "RobotoHUD-25", 8, 2, PHBlue, 0) + draw.SimpleText(TauntMenuPhrase, "RobotoHUD-L15", 8 + tw + 16, 2 + th * 0.90, PHWhite, 0, 4) + end + + local leftpnl = vgui.Create("DPanel", menu) + leftpnl:Dock(LEFT) + leftpnl:DockMargin(0, 0, 0, 0) + leftpnl:SetWide(menu:GetWide() * 0.3) + function leftpnl:Paint(w, h) + end + + local but = vgui.Create("DButton", leftpnl) + but:Dock(BOTTOM) + but:DockMargin(0, 4, 4, 0) + but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) + but:SetText("") + + function but:Paint(w, h) + local colt = PHWhite + if self:IsDown() then + col = Color(PHDark.r, PHDark.g, PHDark.b) + elseif self:IsHovered() then + col = Color(PHDark.r, PHDark.g, PHDark.b) + else + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) + end + + draw.RoundedBoxEx(4, 0, 0, w, h, col, true, true, true, true) + draw.ShadowText("Random", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) + end + + function but:DoClick() + RunConsoleCommand("ph_taunt_random") + saveCursor() + menu:Close() + end + + local clist = vgui.Create("DScrollPanel", leftpnl) + menu.CatList = clist + clist:Dock(FILL) + clist:DockMargin(0, 0, 0, 0) + + function clist:Paint(w, h) + end + + local canvas = clist:GetCanvas() + canvas:DockPadding(0, 0, 0, 0) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, 4) + end + + local mlist = vgui.Create("DScrollPanel", menu) + menu.TauntList = mlist + mlist:Dock(FILL) + + function mlist:Paint(w, h) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + surface.DrawOutlinedRect(0, 0, w, h) + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) + surface.DrawRect(1, 1, w - 2, h - 2) + end + + -- child positioning + local canvas = mlist:GetCanvas() + canvas:DockPadding(8, 8, 8, 8) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, 4) + end + + fillList(mlist, Taunts) + fillCats(clist, mlist, "all") +end + +concommand.Add("ph_menu_taunt", openTauntMenu) +net.Receive("open_taunt_menu", openTauntMenu) + +net.Receive("ph_set_taunt_menu_phrase", function() + value_new = net.ReadString() + + -- Prevent this from being done more than once. GMod is weird. + if TauntMenuPhrase == value_new then return end + TauntMenuPhrase = value_new +end) diff --git a/gamemodes/ultimateph/gamemode/cl_voicepanels.lua b/gamemodes/ultimateph/gamemode/cl_voicepanels.lua index 0a51a9b..ac2d9ea 100644 --- a/gamemodes/ultimateph/gamemode/cl_voicepanels.lua +++ b/gamemodes/ultimateph/gamemode/cl_voicepanels.lua @@ -1,114 +1,114 @@ -local PANEL = {} -local PlayerVoicePanels = {} - -function PANEL:Init() - self.Avatar = vgui.Create("AvatarImage", self) - self.Avatar:Dock(LEFT) - self.Avatar:SetSize(32, 32) - self.Color = color_transparent - self:SetSize(250, 32 + 8) - self:DockPadding(4, 4, 4, 4) - self:DockMargin(2, 2, 2, 2) - self:Dock(BOTTOM) -end - -function PANEL:Setup(ply) - self.ply = ply - self.Avatar:SetPlayer(ply) - self.Color = team.GetColor(ply:Team()) - self:InvalidateLayout() -end - -function PANEL:Paint(w, h) - if !IsValid(self.ply) then return end - - local volume = self.ply:VoiceVolume() - col = team.GetColor(self.ply:Team()) - - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawRect(0, 0, w, h) - surface.SetDrawColor(col.r, col.g, col.b, 120) - surface.DrawRect(0, 0, w, h) - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(col.r, col.g, col.b, 120) - surface.DrawRect(0, 0, math.floor(w * volume), h) - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawOutlinedRect(0, 0, w * volume, h) - - draw.ShadowText(self.ply:Nick(), "GModNotify", 4 + 32 + 4, h / 2, color_white, 0, 1) -end - -function PANEL:Think() - if self.fadeAnim then - self.fadeAnim:Run() - end -end - -function PANEL:FadeOut(anim, delta, data) - if anim.Finished then - if IsValid(PlayerVoicePanels[self.ply]) then - PlayerVoicePanels[self.ply]:Remove() - PlayerVoicePanels[self.ply] = nil - return - end - - return - end - - self:SetAlpha(255 - (255 * delta)) -end - -derma.DefineControl("VoiceNotify", "", PANEL, "DPanel") - -function GM:PlayerStartVoice(ply) - if !IsValid(g_VoicePanelList) then return end - - -- There'd be an exta one if voice_loopback is on, so remove it. - GAMEMODE:PlayerEndVoice(ply) - - if IsValid(PlayerVoicePanels[ply]) then - if PlayerVoicePanels[ply].fadeAnim then - PlayerVoicePanels[ply].fadeAnim:Stop() - PlayerVoicePanels[ply].fadeAnim = nil - end - - PlayerVoicePanels[ply]:SetAlpha(255) - return - end - - if !IsValid(ply) then return end - - local pnl = g_VoicePanelList:Add("VoiceNotify") - pnl:Setup(ply) - PlayerVoicePanels[ply] = pnl -end - -local function VoiceClean() - for k, v in pairs(PlayerVoicePanels) do - if !IsValid(k) || !k:IsPlayer() then - GAMEMODE:PlayerEndVoice(k) - end - end -end -timer.Create("VoiceClean", 10, 0, VoiceClean) - -function GM:PlayerEndVoice(ply) - if IsValid(PlayerVoicePanels[ply]) then - if PlayerVoicePanels[ply].fadeAnim then return end - - PlayerVoicePanels[ply].fadeAnim = Derma_Anim("FadeOut", PlayerVoicePanels[ply], PlayerVoicePanels[ply].FadeOut) - PlayerVoicePanels[ply].fadeAnim:Start(2) - end -end - -local function CreateVoiceVGUI() - g_VoicePanelList = vgui.Create("DPanel") - g_VoicePanelList:ParentToHUD() - g_VoicePanelList:SetPos(ScrW() - 300, 100) - g_VoicePanelList:SetSize(250, ScrH() - 200) - g_VoicePanelList:SetPaintBackground(false) - -end - -hook.Add("InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI) +local PANEL = {} +local PlayerVoicePanels = {} + +function PANEL:Init() + self.Avatar = vgui.Create("AvatarImage", self) + self.Avatar:Dock(LEFT) + self.Avatar:SetSize(32, 32) + self.Color = color_transparent + self:SetSize(250, 32 + 8) + self:DockPadding(4, 4, 4, 4) + self:DockMargin(2, 2, 2, 2) + self:Dock(BOTTOM) +end + +function PANEL:Setup(ply) + self.ply = ply + self.Avatar:SetPlayer(ply) + self.Color = team.GetColor(ply:Team()) + self:InvalidateLayout() +end + +function PANEL:Paint(w, h) + if !IsValid(self.ply) then return end + + local volume = self.ply:VoiceVolume() + col = team.GetColor(self.ply:Team()) + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(col.r, col.g, col.b, 120) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawOutlinedRect(0, 0, w, h) + surface.SetDrawColor(col.r, col.g, col.b, 120) + surface.DrawRect(0, 0, math.floor(w * volume), h) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawOutlinedRect(0, 0, w * volume, h) + + draw.ShadowText(self.ply:Nick(), "GModNotify", 4 + 32 + 4, h / 2, color_white, 0, 1) +end + +function PANEL:Think() + if self.fadeAnim then + self.fadeAnim:Run() + end +end + +function PANEL:FadeOut(anim, delta, data) + if anim.Finished then + if IsValid(PlayerVoicePanels[self.ply]) then + PlayerVoicePanels[self.ply]:Remove() + PlayerVoicePanels[self.ply] = nil + return + end + + return + end + + self:SetAlpha(255 - (255 * delta)) +end + +derma.DefineControl("VoiceNotify", "", PANEL, "DPanel") + +function GM:PlayerStartVoice(ply) + if !IsValid(g_VoicePanelList) then return end + + -- There'd be an exta one if voice_loopback is on, so remove it. + GAMEMODE:PlayerEndVoice(ply) + + if IsValid(PlayerVoicePanels[ply]) then + if PlayerVoicePanels[ply].fadeAnim then + PlayerVoicePanels[ply].fadeAnim:Stop() + PlayerVoicePanels[ply].fadeAnim = nil + end + + PlayerVoicePanels[ply]:SetAlpha(255) + return + end + + if !IsValid(ply) then return end + + local pnl = g_VoicePanelList:Add("VoiceNotify") + pnl:Setup(ply) + PlayerVoicePanels[ply] = pnl +end + +local function VoiceClean() + for k, v in pairs(PlayerVoicePanels) do + if !IsValid(k) || !k:IsPlayer() then + GAMEMODE:PlayerEndVoice(k) + end + end +end +timer.Create("VoiceClean", 10, 0, VoiceClean) + +function GM:PlayerEndVoice(ply) + if IsValid(PlayerVoicePanels[ply]) then + if PlayerVoicePanels[ply].fadeAnim then return end + + PlayerVoicePanels[ply].fadeAnim = Derma_Anim("FadeOut", PlayerVoicePanels[ply], PlayerVoicePanels[ply].FadeOut) + PlayerVoicePanels[ply].fadeAnim:Start(2) + end +end + +local function CreateVoiceVGUI() + g_VoicePanelList = vgui.Create("DPanel") + g_VoicePanelList:ParentToHUD() + g_VoicePanelList:SetPos(ScrW() - 300, 100) + g_VoicePanelList:SetSize(250, ScrH() - 200) + g_VoicePanelList:SetPaintBackground(false) + +end + +hook.Add("InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI) diff --git a/gamemodes/ultimateph/gamemode/init.lua b/gamemodes/ultimateph/gamemode/init.lua index cc26a6f..7194290 100644 --- a/gamemodes/ultimateph/gamemode/init.lua +++ b/gamemodes/ultimateph/gamemode/init.lua @@ -1,128 +1,128 @@ -AddCSLuaFile("shared.lua") - -local rootFolder = (GM || GAMEMODE).Folder:sub(11) .. "/gamemode/" - --- add cs lua all the cl_ and sh_ files -local files = file.Find(rootFolder .. "*", "LUA") -for k, v in pairs(files) do - if v:sub(1, 3) == "cl_" || v:sub(1, 3) == "sh_" then - AddCSLuaFile(rootFolder .. v) - end -end - -util.AddNetworkString("clientIPE") -util.AddNetworkString("ph_openhelpmenu") -util.AddNetworkString("player_model_sex") - -include("sv_chatmsg.lua") -include("shared.lua") -include("sv_ragdoll.lua") -include("sv_playercolor.lua") -include("sv_player.lua") -include("sv_realism.lua") -include("sv_rounds.lua") -include("sv_spectate.lua") -include("sv_respawn.lua") -include("sv_health.lua") -include("sv_killfeed.lua") -include("sv_disguise.lua") -include("sv_teams.lua") -include("sv_taunt.lua") -include("sv_mapvote.lua") -include("sv_bannedmodels.lua") -include("sv_version.lua") -include("sh_init.lua") - -resource.AddFile("materials/husklesph/skull.png") -resource.AddFile("sound/husklesph/hphaaaaa2.mp3") - -function GM:Initialize() - self.RoundWaitForPlayers = CurTime() - self.DeathRagdolls = {} - self:LoadMapList() - self:LoadBannedModels() - self:StartAutoTauntTimer() -end - -hook.Add("Think", "StartupVersionCheck", function() - hook.Remove("Think", "StartupVersionCheck") - GAMEMODE:CheckForNewVersion() -end) - -function GM:InitPostEntity() - self:InitPostEntityAndMapCleanup() - - RunConsoleCommand("mp_show_voice_icons", "0") -end - -function GM:InitPostEntityAndMapCleanup() - for k, ent in pairs(ents.GetAll()) do - if ent:GetClass():find("door") then - ent:Fire("unlock", "", 0) - end - end -end - -function GM:Think() - self:RoundsThink() - self:SpectateThink() -end - -function GM:PlayerNoClip(ply) - timer.Simple(0, function() ply:CalculateSpeed() end) - return ply:IsSuperAdmin() || ply:GetMoveType() == MOVETYPE_NOCLIP -end - -function GM:EntityTakeDamage(ent, dmginfo) - if IsValid(ent) then - if ent:IsPlayer() then - if IsValid(dmginfo:GetAttacker()) then - local attacker = dmginfo:GetAttacker() - if attacker:IsPlayer() then - if attacker:Team() == ent:Team() then - return true - end - end - end - end - - if ent:IsDisguisableAs() then - local att = dmginfo:GetAttacker() - if IsValid(att) && att:IsPlayer() && att:IsHunter() then - if bit.band(dmginfo:GetDamageType(), DMG_CRUSH) != DMG_CRUSH then - local tdmg = DamageInfo() - tdmg:SetDamage(math.min(dmginfo:GetDamage(), math.max(self.HunterDamagePenalty:GetInt(), 1))) - tdmg:SetDamageType(DMG_AIRBOAT) - tdmg:SetDamageForce(Vector(0, 0, 0)) - att:TakeDamageInfo(tdmg) - - -- increase stat for end of round (Angriest Hunter) - att.PropDmgPenalty = (att.PropDmgPenalty || 0) + tdmg:GetDamage() - end - end - end - end -end - -function file.ReadDataAndContent(path) - local f = file.Read(path, "DATA") - if f then return f end - f = file.Read(GAMEMODE.Folder .. "/content/data/" .. path, "GAME") - return f -end - -function GM:CleanupMap() - game.CleanUpMap() - hook.Call("InitPostEntityAndMapCleanup", self) - hook.Call("MapCleanup", self) -end - -function GM:ShowHelp(ply) - net.Start("ph_openhelpmenu") - net.Send(ply) -end - -function GM:ShowSpare1(ply) - net.Start("open_taunt_menu") - net.Send(ply) -end +AddCSLuaFile("shared.lua") + +local rootFolder = (GM || GAMEMODE).Folder:sub(11) .. "/gamemode/" + +-- add cs lua all the cl_ and sh_ files +local files = file.Find(rootFolder .. "*", "LUA") +for k, v in pairs(files) do + if v:sub(1, 3) == "cl_" || v:sub(1, 3) == "sh_" then + AddCSLuaFile(rootFolder .. v) + end +end + +util.AddNetworkString("clientIPE") +util.AddNetworkString("ph_openhelpmenu") +util.AddNetworkString("player_model_sex") + +include("sv_chatmsg.lua") +include("shared.lua") +include("sv_ragdoll.lua") +include("sv_playercolor.lua") +include("sv_player.lua") +include("sv_realism.lua") +include("sv_rounds.lua") +include("sv_spectate.lua") +include("sv_respawn.lua") +include("sv_health.lua") +include("sv_killfeed.lua") +include("sv_disguise.lua") +include("sv_teams.lua") +include("sv_taunt.lua") +include("sv_mapvote.lua") +include("sv_bannedmodels.lua") +include("sv_version.lua") +include("sh_init.lua") + +resource.AddFile("materials/husklesph/skull.png") +resource.AddFile("sound/husklesph/hphaaaaa2.mp3") + +function GM:Initialize() + self.RoundWaitForPlayers = CurTime() + self.DeathRagdolls = {} + self:LoadMapList() + self:LoadBannedModels() + self:StartAutoTauntTimer() +end + +hook.Add("Think", "StartupVersionCheck", function() + hook.Remove("Think", "StartupVersionCheck") + GAMEMODE:CheckForNewVersion() +end) + +function GM:InitPostEntity() + self:InitPostEntityAndMapCleanup() + + RunConsoleCommand("mp_show_voice_icons", "0") +end + +function GM:InitPostEntityAndMapCleanup() + for k, ent in pairs(ents.GetAll()) do + if ent:GetClass():find("door") then + ent:Fire("unlock", "", 0) + end + end +end + +function GM:Think() + self:RoundsThink() + self:SpectateThink() +end + +function GM:PlayerNoClip(ply) + timer.Simple(0, function() ply:CalculateSpeed() end) + return ply:IsSuperAdmin() || ply:GetMoveType() == MOVETYPE_NOCLIP +end + +function GM:EntityTakeDamage(ent, dmginfo) + if IsValid(ent) then + if ent:IsPlayer() then + if IsValid(dmginfo:GetAttacker()) then + local attacker = dmginfo:GetAttacker() + if attacker:IsPlayer() then + if attacker:Team() == ent:Team() then + return true + end + end + end + end + + if ent:IsDisguisableAs() then + local att = dmginfo:GetAttacker() + if IsValid(att) && att:IsPlayer() && att:IsHunter() then + if bit.band(dmginfo:GetDamageType(), DMG_CRUSH) != DMG_CRUSH then + local tdmg = DamageInfo() + tdmg:SetDamage(math.min(dmginfo:GetDamage(), math.max(self.HunterDamagePenalty:GetInt(), 1))) + tdmg:SetDamageType(DMG_AIRBOAT) + tdmg:SetDamageForce(Vector(0, 0, 0)) + att:TakeDamageInfo(tdmg) + + -- increase stat for end of round (Angriest Hunter) + att.PropDmgPenalty = (att.PropDmgPenalty || 0) + tdmg:GetDamage() + end + end + end + end +end + +function file.ReadDataAndContent(path) + local f = file.Read(path, "DATA") + if f then return f end + f = file.Read(GAMEMODE.Folder .. "/content/data/" .. path, "GAME") + return f +end + +function GM:CleanupMap() + game.CleanUpMap() + hook.Call("InitPostEntityAndMapCleanup", self) + hook.Call("MapCleanup", self) +end + +function GM:ShowHelp(ply) + net.Start("ph_openhelpmenu") + net.Send(ply) +end + +function GM:ShowSpare1(ply) + net.Start("open_taunt_menu") + net.Send(ply) +end diff --git a/gamemodes/ultimateph/gamemode/sh_disguise.lua b/gamemodes/ultimateph/gamemode/sh_disguise.lua index 9347d41..56c40e4 100644 --- a/gamemodes/ultimateph/gamemode/sh_disguise.lua +++ b/gamemodes/ultimateph/gamemode/sh_disguise.lua @@ -1,161 +1,161 @@ -local PlayerMeta = FindMetaTable("Player") -local EntityMeta = FindMetaTable("Entity") - -local allowClasses = {"prop_physics", "prop_physics_multiplayer"} - -function PlayerMeta:CanDisguiseAsProp(ent) - if !self:Alive() then return false end - if !self:IsProp() then return false end - if !IsValid(ent) then return false end - - if !table.HasValue(allowClasses, ent:GetClass()) then - return false - end - - return true -end - -function EntityMeta:IsDisguisableAs() - if !table.HasValue(allowClasses, self:GetClass()) then - return false - end - - return true -end - -function PlayerMeta:CanFitHull(hullx, hully, hullz) - local trace = {} - trace.start = self:GetPos() - trace.endpos = self:GetPos() - trace.filter = self - trace.maxs = Vector(hullx, hully, hullz) - trace.mins = Vector(-hullx, -hully, 0) - local tr = util.TraceHull(trace) - if tr.Hit then - return false - end - - return true -end - -function EntityMeta:GetPropSize() - local hullxy = math.Round(math.Max(self:OBBMaxs().x - self:OBBMins().x, self:OBBMaxs().y - self:OBBMins().y) / 2) - local hullz = math.Round(self:OBBMaxs().z - self:OBBMins().z) - return hullxy, hullz -end - -function PlayerMeta:GetPropEyePos() - if !self:IsDisguised() then - return self:GetShootPos() - end - - local maxs = self:GetNWVector("disguiseMaxs") - local mins = self:GetNWVector("disguiseMins") - local reach = (maxs.z - mins.z) + 10 - local trace = {} - trace.start = self:GetPos() + Vector(0, 0, 1.5) - trace.endpos = trace.start + Vector(0, 0, reach + 5) - local tab = ents.FindByClass("prop_ragdoll") - table.insert(tab, self) - trace.filter = tab - - local tr = util.TraceLine(trace) - return trace.start + (trace.endpos - trace.start):GetNormal() * math.Clamp(trace.start:Distance(tr.HitPos) - 5, 0, reach) -end - -function PlayerMeta:GetPropEyeTrace() - if !self:IsDisguised() then - local tr = self:GetEyeTraceNoCursor() - local trace = {} - trace.start = tr.StartPos - trace.endpos = trace.start + self:GetAimVector() * 100000 - trace.filter = self - trace.mask = MASK_SHOT - return util.TraceLine(trace) - end - - local trace = {} - trace.start = self:GetPropEyePos() - trace.endpos = trace.start + self:GetAimVector() * 100000 - trace.filter = self - trace.mask = MASK_SHOT - local tr = util.TraceLine(trace) - return tr -end - -local function checkCorner(mins, maxs, corner, ang) - corner:Rotate(ang) - mins.x = math.min(mins.x, corner.x) - mins.y = math.min(mins.y, corner.y) - maxs.x = math.max(maxs.x, corner.x) - maxs.y = math.max(maxs.y, corner.y) -end - -function PlayerMeta:CalculateRotatedDisguiseMinsMaxs() - local maxs = self:GetNWVector("disguiseMaxs") - local mins = self:GetNWVector("disguiseMins") - local ang = self:EyeAngles() - ang.p = 0 - - local nmins, nmaxs = Vector(0, 0, mins.z), Vector(0, 0, maxs.z) - checkCorner(nmins, nmaxs, Vector(maxs.x, maxs.y), ang) - checkCorner(nmins, nmaxs, Vector(maxs.x, mins.y), ang) - checkCorner(nmins, nmaxs, Vector(mins.x, mins.y), ang) - checkCorner(nmins, nmaxs, Vector(mins.x, maxs.y), ang) - - return nmins, nmaxs -end - -function PlayerMeta:DisguiseRotationLocked() - if !self:IsDisguised() then - return self:GetNWBool("undisguiseRotationLock") - end - - return self:GetNWBool("disguiseRotationLock") -end - -function GM:PlayerCanDisguiseCurrentTarget(ply) - if !IsValid(ply) then return false, nil end - - local horizLeniency = 50 - local minHLeniency = 100 - local verticalLeniency = 100 - - if ply:IsProp() then - local tr = ply:GetPropEyeTrace() - if IsValid(tr.Entity) then - if self:IsModelBanned(tr.Entity:GetModel()) then return false, nil end - - local testPos = Vector(tr.StartPos.x, tr.StartPos.y, 0) - local hitPosition = Vector(tr.HitPos.x, tr.HitPos.y, 0) - local hitZ = tr.HitPos.z - local propCurZ = ply:GetPos().z - local propMaxZ = ply:OBBMaxs().z + propCurZ - local propMinZ = ply:OBBMins().z + propCurZ - local withinZRange = hitZ >= propMinZ - verticalLeniency && hitZ <= propMaxZ + verticalLeniency - local propXY = ply:GetPropSize() - local withinHorizRange = hitPosition:Distance(testPos) < math.max(propXY + horizLeniency, minHLeniency) - if withinHorizRange && withinZRange then - if ply:CanDisguiseAsProp(tr.Entity) then - return true, tr.Entity - end - end - end - end - - return false, nil -end - -hook.Add('UpdateAnimation', 'PropUndisguiseLock', function(ply) - if ply:IsDisguised() or !GAMEMODE.PropTpose:GetBool() or !ply:GetNWBool("undisguiseRotationLock") then - return - end - - local ang = ply:GetNWAngle("undisguiseRotationLockAng") - - if ang == ply:GetRenderAngles() then - return - end - - ply:SetRenderAngles(ang) -end) \ No newline at end of file +local PlayerMeta = FindMetaTable("Player") +local EntityMeta = FindMetaTable("Entity") + +local allowClasses = {"prop_physics", "prop_physics_multiplayer"} + +function PlayerMeta:CanDisguiseAsProp(ent) + if !self:Alive() then return false end + if !self:IsProp() then return false end + if !IsValid(ent) then return false end + + if !table.HasValue(allowClasses, ent:GetClass()) then + return false + end + + return true +end + +function EntityMeta:IsDisguisableAs() + if !table.HasValue(allowClasses, self:GetClass()) then + return false + end + + return true +end + +function PlayerMeta:CanFitHull(hullx, hully, hullz) + local trace = {} + trace.start = self:GetPos() + trace.endpos = self:GetPos() + trace.filter = self + trace.maxs = Vector(hullx, hully, hullz) + trace.mins = Vector(-hullx, -hully, 0) + local tr = util.TraceHull(trace) + if tr.Hit then + return false + end + + return true +end + +function EntityMeta:GetPropSize() + local hullxy = math.Round(math.Max(self:OBBMaxs().x - self:OBBMins().x, self:OBBMaxs().y - self:OBBMins().y) / 2) + local hullz = math.Round(self:OBBMaxs().z - self:OBBMins().z) + return hullxy, hullz +end + +function PlayerMeta:GetPropEyePos() + if !self:IsDisguised() then + return self:GetShootPos() + end + + local maxs = self:GetNWVector("disguiseMaxs") + local mins = self:GetNWVector("disguiseMins") + local reach = (maxs.z - mins.z) + 10 + local trace = {} + trace.start = self:GetPos() + Vector(0, 0, 1.5) + trace.endpos = trace.start + Vector(0, 0, reach + 5) + local tab = ents.FindByClass("prop_ragdoll") + table.insert(tab, self) + trace.filter = tab + + local tr = util.TraceLine(trace) + return trace.start + (trace.endpos - trace.start):GetNormal() * math.Clamp(trace.start:Distance(tr.HitPos) - 5, 0, reach) +end + +function PlayerMeta:GetPropEyeTrace() + if !self:IsDisguised() then + local tr = self:GetEyeTraceNoCursor() + local trace = {} + trace.start = tr.StartPos + trace.endpos = trace.start + self:GetAimVector() * 100000 + trace.filter = self + trace.mask = MASK_SHOT + return util.TraceLine(trace) + end + + local trace = {} + trace.start = self:GetPropEyePos() + trace.endpos = trace.start + self:GetAimVector() * 100000 + trace.filter = self + trace.mask = MASK_SHOT + local tr = util.TraceLine(trace) + return tr +end + +local function checkCorner(mins, maxs, corner, ang) + corner:Rotate(ang) + mins.x = math.min(mins.x, corner.x) + mins.y = math.min(mins.y, corner.y) + maxs.x = math.max(maxs.x, corner.x) + maxs.y = math.max(maxs.y, corner.y) +end + +function PlayerMeta:CalculateRotatedDisguiseMinsMaxs() + local maxs = self:GetNWVector("disguiseMaxs") + local mins = self:GetNWVector("disguiseMins") + local ang = self:EyeAngles() + ang.p = 0 + + local nmins, nmaxs = Vector(0, 0, mins.z), Vector(0, 0, maxs.z) + checkCorner(nmins, nmaxs, Vector(maxs.x, maxs.y), ang) + checkCorner(nmins, nmaxs, Vector(maxs.x, mins.y), ang) + checkCorner(nmins, nmaxs, Vector(mins.x, mins.y), ang) + checkCorner(nmins, nmaxs, Vector(mins.x, maxs.y), ang) + + return nmins, nmaxs +end + +function PlayerMeta:DisguiseRotationLocked() + if !self:IsDisguised() then + return self:GetNWBool("undisguiseRotationLock") + end + + return self:GetNWBool("disguiseRotationLock") +end + +function GM:PlayerCanDisguiseCurrentTarget(ply) + if !IsValid(ply) then return false, nil end + + local horizLeniency = 50 + local minHLeniency = 100 + local verticalLeniency = 100 + + if ply:IsProp() then + local tr = ply:GetPropEyeTrace() + if IsValid(tr.Entity) then + if self:IsModelBanned(tr.Entity:GetModel()) then return false, nil end + + local testPos = Vector(tr.StartPos.x, tr.StartPos.y, 0) + local hitPosition = Vector(tr.HitPos.x, tr.HitPos.y, 0) + local hitZ = tr.HitPos.z + local propCurZ = ply:GetPos().z + local propMaxZ = ply:OBBMaxs().z + propCurZ + local propMinZ = ply:OBBMins().z + propCurZ + local withinZRange = hitZ >= propMinZ - verticalLeniency && hitZ <= propMaxZ + verticalLeniency + local propXY = ply:GetPropSize() + local withinHorizRange = hitPosition:Distance(testPos) < math.max(propXY + horizLeniency, minHLeniency) + if withinHorizRange && withinZRange then + if ply:CanDisguiseAsProp(tr.Entity) then + return true, tr.Entity + end + end + end + end + + return false, nil +end + +hook.Add('UpdateAnimation', 'PropUndisguiseLock', function(ply) + if ply:IsDisguised() or !GAMEMODE.PropTpose:GetBool() or !ply:GetNWBool("undisguiseRotationLock") then + return + end + + local ang = ply:GetNWAngle("undisguiseRotationLockAng") + + if ang == ply:GetRenderAngles() then + return + end + + ply:SetRenderAngles(ang) +end) diff --git a/gamemodes/ultimateph/gamemode/sh_init.lua b/gamemodes/ultimateph/gamemode/sh_init.lua index 6ced0fe..e247c15 100644 --- a/gamemodes/ultimateph/gamemode/sh_init.lua +++ b/gamemodes/ultimateph/gamemode/sh_init.lua @@ -34,7 +34,7 @@ GM.AutoTauntMin = CreateConVar("ph_auto_taunt_delay_min", 60, bit.bor(FCVAR_ARCH GM.AutoTauntMax = CreateConVar("ph_auto_taunt_delay_max", 120, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Maximum time to go without taunting") GM.AutoTauntPropsOnly = CreateConVar("ph_auto_taunt_props_only", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Enable auto taunt for props only") -GM.Secrets = CreateConVar("ph_secrets", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Enable secrets") +GM.Secrets = CreateConVar("ph_secrets", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Enable secrets") GM.WalkSpeed = CreateConVar("ph_walk_speed", 200, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Walk Speed") GM.RunSpeed = CreateConVar("ph_run_speed", 150, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Run Speed") diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index 4b1c17f..db76539 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -1,166 +1,166 @@ -Taunts = {} -TauntCategories = {} -AllowedTauntSounds = {} -TauntMenuPhrase = "make annoying fart sounds" - -function FilenameToSoundname(filename) - local sndName = string.Trim(filename) - sndName = string.Replace(sndName, "/", "_") - return string.Replace(sndName, ".", "_") -end - -function PlayerModelTauntAllowed(ply, whitelist) - if whitelist == nil then return true end - - local mod = ply:GetModel() - mod = player_manager.TranslateToPlayerModelName(mod) - local models = player_manager.AllValidModels() - for _, v in pairs(whitelist) do - if !models[v] then - -- v was not a name, so check it as a path - v = string.lower(v) - v = player_manager.TranslateToPlayerModelName(v) - end - - if mod == v then return true end - end - - return false -end - -local function teamNameToNum(pteam) - pteam = pteam:lower() - if pteam == "prop" || pteam == "props" then - return TEAM_PROP - elseif pteam == "hunter" || pteam == "hunters" then - return TEAM_HUNTER - end - return nil -end - -local function teamNameTableToNumTable(pteams) - local ret = {} - for i, pteam in ipairs(pteams) do - ret[i] = teamNameToNum(pteam) - end - return ret -end - -function TauntAllowedForPlayer(ply, tauntTable) - if tauntTable.sex then - if GAMEMODE && GAMEMODE.PlayerModelSex then - if tauntTable.sex != GAMEMODE.PlayerModelSex then - return false - end - elseif tauntTable.sex != ply.ModelSex then - return false - end - end - - if type(tauntTable.team) == "table" then - if !table.HasValue(tauntTable.team, ply:Team()) then - return false - end - elseif tauntTable.team != ply:Team() then - return false - end - - return PlayerModelTauntAllowed(ply, tauntTable.allowedModels) -end - --- display name, table of sound files, team (name or id), sex (nil for both), table of category ids, [duration in seconds] -local function addTaunt(name, snd, pteam, sex, cats, duration, allowedModels) - if !name || type(name) != "string" then return end - if type(snd) != "table" then snd = {tostring(snd)} end - if #snd == 0 then error("No sounds for " .. name) return end - - local t = {} - t.sound = snd - t.categories = cats - if type(pteam) == "string" then - t.team = teamNameToNum(pteam) - elseif type(pteam) == "table" then - t.team = teamNameTableToNumTable(pteam) - else - t.team = tonumber(pteam) - end - - if sex && #sex > 0 then - t.sex = sex - if sex == "both" || sex == "nil" then - t.sex = nil - end - end - - t.name = name - t.allowedModels = allowedModels - - local dur, count = 0, 0 - for k, v in pairs(snd) do - sound.Add({ - name = FilenameToSoundname(v), - channel = CHAN_AUTO, - level = 75, - sound = v - }) - - if !AllowedTauntSounds[v] then AllowedTauntSounds[v] = {} end - table.insert(AllowedTauntSounds[v], t) - dur = dur + SoundDuration(v) - count = count + 1 - end - - t.soundDuration = dur / count - if tonumber(duration) then - t.soundDuration = tonumber(duration) - t.soundDurationOverride = tonumber(duration) - end - - table.insert(Taunts, t) - if cats then - for k, cat in pairs(cats) do - if !TauntCategories[cat] then TauntCategories[cat] = {} end - table.insert(TauntCategories[cat], t) - end - end -end - -local tempG = {} -tempG.addTaunt = addTaunt - --- inherit from _G -local meta = {} -meta.__index = _G -meta.__newindex = _G -setmetatable(tempG, meta) - -local function loadTaunts(rootFolder) - local files = file.Find(rootFolder .. "*.lua", "LUA") - for k, v in pairs(files) do - local filePath = rootFolder .. v - AddCSLuaFile(filePath) - - local f = CompileFile(filePath) - if !f then - return - end - - setfenv(f, tempG) - local b, err = pcall(f) - - local s = SERVER && "Server" || "Client" - local c = SERVER && 90 || 0 - if !b then - MsgC(Color(255, 50, 50 + c), s .. " loading taunts failed: " .. filePath .. "\nError: " .. err .. "\n") - else - MsgC(Color(50, 255, 50 + c), s .. " loaded taunts file: " .. filePath .. "\n") - end - end -end - -function GM:LoadTaunts() - loadTaunts((GM || GAMEMODE).Folder:sub(11) .. "/gamemode/taunts/") - loadTaunts("ultimateph/taunts/") -end - -GM:LoadTaunts() +Taunts = {} +TauntCategories = {} +AllowedTauntSounds = {} +TauntMenuPhrase = "make annoying fart sounds" + +function FilenameToSoundname(filename) + local sndName = string.Trim(filename) + sndName = string.Replace(sndName, "/", "_") + return string.Replace(sndName, ".", "_") +end + +function PlayerModelTauntAllowed(ply, whitelist) + if whitelist == nil then return true end + + local mod = ply:GetModel() + mod = player_manager.TranslateToPlayerModelName(mod) + local models = player_manager.AllValidModels() + for _, v in pairs(whitelist) do + if !models[v] then + -- v was not a name, so check it as a path + v = string.lower(v) + v = player_manager.TranslateToPlayerModelName(v) + end + + if mod == v then return true end + end + + return false +end + +local function teamNameToNum(pteam) + pteam = pteam:lower() + if pteam == "prop" || pteam == "props" then + return TEAM_PROP + elseif pteam == "hunter" || pteam == "hunters" then + return TEAM_HUNTER + end + return nil +end + +local function teamNameTableToNumTable(pteams) + local ret = {} + for i, pteam in ipairs(pteams) do + ret[i] = teamNameToNum(pteam) + end + return ret +end + +function TauntAllowedForPlayer(ply, tauntTable) + if tauntTable.sex then + if GAMEMODE && GAMEMODE.PlayerModelSex then + if tauntTable.sex != GAMEMODE.PlayerModelSex then + return false + end + elseif tauntTable.sex != ply.ModelSex then + return false + end + end + + if type(tauntTable.team) == "table" then + if !table.HasValue(tauntTable.team, ply:Team()) then + return false + end + elseif tauntTable.team != ply:Team() then + return false + end + + return PlayerModelTauntAllowed(ply, tauntTable.allowedModels) +end + +-- display name, table of sound files, team (name or id), sex (nil for both), table of category ids, [duration in seconds] +local function addTaunt(name, snd, pteam, sex, cats, duration, allowedModels) + if !name || type(name) != "string" then return end + if type(snd) != "table" then snd = {tostring(snd)} end + if #snd == 0 then error("No sounds for " .. name) return end + + local t = {} + t.sound = snd + t.categories = cats + if type(pteam) == "string" then + t.team = teamNameToNum(pteam) + elseif type(pteam) == "table" then + t.team = teamNameTableToNumTable(pteam) + else + t.team = tonumber(pteam) + end + + if sex && #sex > 0 then + t.sex = sex + if sex == "both" || sex == "nil" then + t.sex = nil + end + end + + t.name = name + t.allowedModels = allowedModels + + local dur, count = 0, 0 + for k, v in pairs(snd) do + sound.Add({ + name = FilenameToSoundname(v), + channel = CHAN_AUTO, + level = 75, + sound = v + }) + + if !AllowedTauntSounds[v] then AllowedTauntSounds[v] = {} end + table.insert(AllowedTauntSounds[v], t) + dur = dur + SoundDuration(v) + count = count + 1 + end + + t.soundDuration = dur / count + if tonumber(duration) then + t.soundDuration = tonumber(duration) + t.soundDurationOverride = tonumber(duration) + end + + table.insert(Taunts, t) + if cats then + for k, cat in pairs(cats) do + if !TauntCategories[cat] then TauntCategories[cat] = {} end + table.insert(TauntCategories[cat], t) + end + end +end + +local tempG = {} +tempG.addTaunt = addTaunt + +-- inherit from _G +local meta = {} +meta.__index = _G +meta.__newindex = _G +setmetatable(tempG, meta) + +local function loadTaunts(rootFolder) + local files = file.Find(rootFolder .. "*.lua", "LUA") + for k, v in pairs(files) do + local filePath = rootFolder .. v + AddCSLuaFile(filePath) + + local f = CompileFile(filePath) + if !f then + return + end + + setfenv(f, tempG) + local b, err = pcall(f) + + local s = SERVER && "Server" || "Client" + local c = SERVER && 90 || 0 + if !b then + MsgC(Color(255, 50, 50 + c), s .. " loading taunts failed: " .. filePath .. "\nError: " .. err .. "\n") + else + MsgC(Color(50, 255, 50 + c), s .. " loaded taunts file: " .. filePath .. "\n") + end + end +end + +function GM:LoadTaunts() + loadTaunts((GM || GAMEMODE).Folder:sub(11) .. "/gamemode/taunts/") + loadTaunts("ultimateph/taunts/") +end + +GM:LoadTaunts() diff --git a/gamemodes/ultimateph/gamemode/shared.lua b/gamemodes/ultimateph/gamemode/shared.lua index 7e6d54c..f92d3de 100644 --- a/gamemodes/ultimateph/gamemode/shared.lua +++ b/gamemodes/ultimateph/gamemode/shared.lua @@ -1,90 +1,93 @@ -local PlayerMeta = FindMetaTable("Player") -local tabFile = file.Read(GM.Folder .. "/ultimateph.txt", "GAME") || "" -local tab = util.KeyValuesToTable(tabFile) - -GM.Name = tab["title"] || "Prop Hunters - Utlimate Edition" -GM.Author = "DataNext, Zikaeroh, MechanicalMind" --- Credits to waddlesworth for the logo and icon -GM.Email = "N/A" -GM.Website = "N/A" -GM.Version = tab["version"] || "unknown" - -ROUND_WAIT = 1 -ROUND_HIDE = 2 -ROUND_SEEK = 3 -ROUND_POST = 4 -ROUND_MAPVOTE = 5 - -TEAM_SPEC = 1 -TEAM_HUNTER = 2 -TEAM_PROP = 3 - -WIN_NONE = TEAM_SPEC -WIN_HUNTER = TEAM_HUNTER -WIN_PROP = TEAM_PROP - -function PlayerMeta:IsSpectator() return self:Team() == TEAM_SPEC end -function PlayerMeta:IsHunter() return self:Team() == TEAM_HUNTER end -function PlayerMeta:IsProp() return self:Team() == TEAM_PROP end - -GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT - -team.SetUp(TEAM_SPEC, "Spectators", Color(120, 120, 120), false) -- Setting Joinable to false allows us to use team.BestAutoJoinTeam and have it only include the Hunters/Props teams. -team.SetUp(TEAM_HUNTER, "Hunters", Color(138, 173, 244)) -team.SetUp(TEAM_PROP, "Props", Color(237, 135, 150)) - -function GM:GetGameState() - return self.GameState -end - -function GM:PlayerSetNewHull(ply, s, hullz, duckz) - self:PlayerSetHull(ply, s, s, hullz, duckz) -end - -function GM:PlayerSetHull(ply, hullx, hully, hullz, duckz) - hullx = hullx || 16 - hully = hully || 16 - hullz = hullz || 72 - duckz = duckz || hullz / 2 - ply:SetHull(Vector(-hullx, -hully, 0), Vector(hullx, hully, hullz)) - ply:SetHullDuck(Vector(-hullx, -hully, 0), Vector(hullx, hully, duckz)) - - if SERVER then - net.Start("hull_set") - net.WriteEntity(ply) - net.WriteFloat(hullx) - net.WriteFloat(hully) - net.WriteFloat(hullz) - net.WriteFloat(duckz) - net.Broadcast() - -- TODO send on player spawn - end -end - -function GM:EntityEmitSound( t ) - if GetConVar("ph_hunter_deaf_onhiding"):GetBool() && self:GetGameState() == ROUND_HIDE then - for _, ply in pairs(player.GetAll()) do - if ply:IsHunter() then - return false - else - return nil - end - end - end -end - -function GM:PlayerFootstep( ply, pos, foot, sound, volume, filter ) - if GetConVar("ph_props_silent_footsteps"):GetBool() then - if ply:IsProp() then - return true - end - end -end - -hook.Add('CalcMainActivity', 'PropTpose', function(ply) - if GetConVar("ph_props_tpose"):GetBool() then - if ply:IsProp() then - return ACT_INVALID - end - end -end) +include("cl_colors.lua") +local PlayerMeta = FindMetaTable("Player") +local tabFile = file.Read(GM.Folder .. "/ultimateph.txt", "GAME") || "" +local tab = util.KeyValuesToTable(tabFile) + +GM.Name = tab["title"] || "Prop Hunters - Utlimate Edition" +GM.Author = "DataNext, Zikaeroh, MechanicalMind" +-- Credits to waddlesworth for the logo and icon +GM.Email = "N/A" +GM.Website = "N/A" +GM.Version = tab["version"] || "unknown" + +ROUND_WAIT = 1 +ROUND_HIDE = 2 +ROUND_SEEK = 3 +ROUND_POST = 4 +ROUND_MAPVOTE = 5 + +TEAM_SPEC = 1 +TEAM_HUNTER = 2 +TEAM_PROP = 3 + +WIN_NONE = TEAM_SPEC +WIN_HUNTER = TEAM_HUNTER +WIN_PROP = TEAM_PROP + +function PlayerMeta:IsSpectator() return self:Team() == TEAM_SPEC end +function PlayerMeta:IsHunter() return self:Team() == TEAM_HUNTER end +function PlayerMeta:IsProp() return self:Team() == TEAM_PROP end + +GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT + +team.SetUp(TEAM_SPEC, "Spectators", PHWhite, false) -- Setting Joinable to false allows us to use team.BestAutoJoinTeam and have it only include the Hunters/Props teams. +team.SetUp(TEAM_HUNTER, "Hunters", PHBlue) +team.SetUp(TEAM_PROP, "Props", PHRed) + +function GM:GetGameState() + return self.GameState +end + +function GM:PlayerSetNewHull(ply, s, hullz, duckz) + self:PlayerSetHull(ply, s, s, hullz, duckz) +end + +function GM:PlayerSetHull(ply, hullx, hully, hullz, duckz) + hullx = hullx || 16 + hully = hully || 16 + hullz = hullz || 72 + duckz = duckz || hullz / 2 + ply:SetHull(Vector(-hullx, -hully, 0), Vector(hullx, hully, hullz)) + ply:SetHullDuck(Vector(-hullx, -hully, 0), Vector(hullx, hully, duckz)) + + if SERVER then + net.Start("hull_set") + net.WriteEntity(ply) + net.WriteFloat(hullx) + net.WriteFloat(hully) + net.WriteFloat(hullz) + net.WriteFloat(duckz) + net.Broadcast() + -- TODO send on player spawn + end +end + +function GM:EntityEmitSound(t) + if not GetConVar("ph_hunter_deaf_onhiding"):GetBool() or self:GetGameState() ~= ROUND_HIDE then + return + end + + for _, ply in ipairs(player.GetHumans()) do -- ipairs is preferred when working with tables. player.GetHumans() to ignore bots + if not ply:IsHunter() then + continue -- if the player is not a hunter, ignore them + end + + return false + end +end + +function GM:PlayerFootstep( ply, pos, foot, sound, volume, filter ) + if not GetConVar("ph_props_silent_footsteps"):GetBool() or not ply:IsProp() then + return + end + + return true +end + +hook.Add('CalcMainActivity', 'PropTpose', function(ply) + if not GetConVar("ph_props_tpose"):GetBool() or not ply:IsProp() then + return + end + + return ACT_INVALID +end) diff --git a/gamemodes/ultimateph/gamemode/sv_awards.lua b/gamemodes/ultimateph/gamemode/sv_awards.lua index e41fb1f..e22cbb2 100644 --- a/gamemodes/ultimateph/gamemode/sv_awards.lua +++ b/gamemodes/ultimateph/gamemode/sv_awards.lua @@ -1,115 +1,115 @@ -PlayerAwards = {} - -PlayerAwards.LastPropStanding = { - name = "Longest Survivor", - desc = "Prop who survived longest", - getWinner = function() - if GAMEMODE.LastRoundResult == WIN_HUNTER then - return GAMEMODE.LastPropDeath - end - - return nil - end -} - -PlayerAwards.LeastMovement = { - name = "Least Movement", - desc = "Prop who moved the least", - getWinner = function() - local minPly - - for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do - if !ply:IsProp() then continue end - - if !minPly || ply.PropMovement < minPly.PropMovement then - minPly = ply - end - end - - return minPly - end -} - -PlayerAwards.MostTaunts = { - name = "Most Taunts", - desc = "Prop who taunted the most", - getWinner = function() - local maxPly - - for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do - if !ply:IsProp() then continue end - - if !maxPly || ply.TauntAmount > maxPly.TauntAmount then - maxPly = ply - end - end - - if maxPly && maxPly.TauntAmount > 0 then return maxPly end - return nil - end -} - -PlayerAwards.FirstHunterKill = { - name = "First Blood", - desc = "Hunter who had the first kill", - getWinner = function() - return GAMEMODE.FirstHunterKill - end -} - -PlayerAwards.MostKills = { - name = "Most Kills", - desc = "Hunter who had the most kills", - getWinner = function() - local maxPly - - for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do - if !ply:IsHunter() then continue end - - if !killsPly || ply.HunterKills > maxPly.HunterKills then - maxPly = ply - end - end - - if maxPly && maxPly.HunterKills > 0 then return maxPly end - return nil - end -} - -PlayerAwards.PropDamage = { - name = "Angriest Player", - desc = "Hunter who shot at props the most", - getWinner = function() - local maxPly - - for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do - if !ply:IsHunter() then continue end - - if !maxPly || ply.PropDmgPenalty > maxPly.PropDmgPenalty then - maxPly = ply - end - end - - if maxPly && maxPly.PropDmgPenalty > 0 then return maxPly end - return nil - end -} - -PlayerAwards.MostMovement = { - name = "Most Movement", - desc = "Prop who moved the most", - getWinner = function() - local maxPly - - for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do - if !ply:IsProp() then continue end - - if !maxPly || ply.PropMovement > maxPly.PropMovement then - maxPly = ply - end - end - - if maxPly && maxPly.PropMovement > 0 then return maxPly end - return nil - end -} +PlayerAwards = {} + +PlayerAwards.LastPropStanding = { + name = "Longest Survivor", + desc = "Prop who survived longest", + getWinner = function() + if GAMEMODE.LastRoundResult == WIN_HUNTER then + return GAMEMODE.LastPropDeath + end + + return nil + end +} + +PlayerAwards.LeastMovement = { + name = "Least Movement", + desc = "Prop who moved the least", + getWinner = function() + local minPly + + for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do + if !ply:IsProp() then continue end + + if !minPly || ply.PropMovement < minPly.PropMovement then + minPly = ply + end + end + + return minPly + end +} + +PlayerAwards.MostTaunts = { + name = "Most Taunts", + desc = "Prop who taunted the most", + getWinner = function() + local maxPly + + for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do + if !ply:IsProp() then continue end + + if !maxPly || ply.TauntAmount > maxPly.TauntAmount then + maxPly = ply + end + end + + if maxPly && maxPly.TauntAmount > 0 then return maxPly end + return nil + end +} + +PlayerAwards.FirstHunterKill = { + name = "First Blood", + desc = "Hunter who had the first kill", + getWinner = function() + return GAMEMODE.FirstHunterKill + end +} + +PlayerAwards.MostKills = { + name = "Most Kills", + desc = "Hunter who had the most kills", + getWinner = function() + local maxPly + + for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do + if !ply:IsHunter() then continue end + + if !killsPly || ply.HunterKills > maxPly.HunterKills then + maxPly = ply + end + end + + if maxPly && maxPly.HunterKills > 0 then return maxPly end + return nil + end +} + +PlayerAwards.PropDamage = { + name = "Angriest Player", + desc = "Hunter who shot at props the most", + getWinner = function() + local maxPly + + for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do + if !ply:IsHunter() then continue end + + if !maxPly || ply.PropDmgPenalty > maxPly.PropDmgPenalty then + maxPly = ply + end + end + + if maxPly && maxPly.PropDmgPenalty > 0 then return maxPly end + return nil + end +} + +PlayerAwards.MostMovement = { + name = "Most Movement", + desc = "Prop who moved the most", + getWinner = function() + local maxPly + + for _, ply in ipairs(GAMEMODE:GetPlayingPlayers()) do + if !ply:IsProp() then continue end + + if !maxPly || ply.PropMovement > maxPly.PropMovement then + maxPly = ply + end + end + + if maxPly && maxPly.PropMovement > 0 then return maxPly end + return nil + end +} diff --git a/gamemodes/ultimateph/gamemode/sv_bannedmodels.lua b/gamemodes/ultimateph/gamemode/sv_bannedmodels.lua index a3c99d6..28b7336 100644 --- a/gamemodes/ultimateph/gamemode/sv_bannedmodels.lua +++ b/gamemodes/ultimateph/gamemode/sv_bannedmodels.lua @@ -1,88 +1,88 @@ --- This file is what controls what models are banned. Models that are banned --- cannot be chosen as a disguise. - -GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. - -util.AddNetworkString("ph_bannedmodels_getall") -util.AddNetworkString("ph_bannedmodels_add") -util.AddNetworkString("ph_bannedmodels_remove") - -function GM:IsModelBanned(model) - return self.BannedModels[model] == true -end - -function GM:AddBannedModel(model) - if self.BannedModels[model] == true then return end - - self.BannedModels[model] = true - self:SaveBannedModels() -end - -function GM:RemoveBannedModel(model) - if self.BannedModels[model] != true then return end - - self.BannedModels[model] = nil - self:SaveBannedModels() -end - -function GM:SaveBannedModels() - -- ensure the folders are there - if !file.Exists("ultimateph/", "DATA") then - file.CreateDir("ultimateph") - end - - local txt = "" - for key, value in pairs(self.BannedModels) do - if value then - txt = txt .. key .. "\r\n" - end - end - - file.Write("ultimateph/bannedmodels.txt", txt) -end - -function GM:LoadBannedModels() - local bannedModels = file.Read("ultimateph/bannedmodels.txt", "DATA") - if bannedModels then - for match in bannedModels:gmatch("[^\r\n]+") do - self:AddBannedModel(match) - end - end -end - -net.Receive("ph_bannedmodels_getall", function(len, ply) - net.Start("ph_bannedmodels_getall") - - for key, value in pairs(GAMEMODE.BannedModels) do - if value then - net.WriteString(key) - end - end - - net.WriteString("") - net.Send(ply) -end) - -net.Receive("ph_bannedmodels_add", function(len, ply) - if !ply:IsAdmin() then return end - - local model = net.ReadString() - if model == "" then return end - - GAMEMODE:AddBannedModel(model) - net.Start("ph_bannedmodels_add") - net.WriteString(model) - net.Broadcast() -end) - -net.Receive("ph_bannedmodels_remove", function(len, ply) - if !ply:IsAdmin() then return end - - local model = net.ReadString() - if model == "" then return end - - GAMEMODE:RemoveBannedModel(model) - net.Start("ph_bannedmodels_remove") - net.WriteString(model) - net.Broadcast() -end) +-- This file is what controls what models are banned. Models that are banned +-- cannot be chosen as a disguise. + +GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. + +util.AddNetworkString("ph_bannedmodels_getall") +util.AddNetworkString("ph_bannedmodels_add") +util.AddNetworkString("ph_bannedmodels_remove") + +function GM:IsModelBanned(model) + return self.BannedModels[model] == true +end + +function GM:AddBannedModel(model) + if self.BannedModels[model] == true then return end + + self.BannedModels[model] = true + self:SaveBannedModels() +end + +function GM:RemoveBannedModel(model) + if self.BannedModels[model] != true then return end + + self.BannedModels[model] = nil + self:SaveBannedModels() +end + +function GM:SaveBannedModels() + -- ensure the folders are there + if !file.Exists("ultimateph/", "DATA") then + file.CreateDir("ultimateph") + end + + local txt = "" + for key, value in pairs(self.BannedModels) do + if value then + txt = txt .. key .. "\r\n" + end + end + + file.Write("ultimateph/bannedmodels.txt", txt) +end + +function GM:LoadBannedModels() + local bannedModels = file.Read("ultimateph/bannedmodels.txt", "DATA") + if bannedModels then + for match in bannedModels:gmatch("[^\r\n]+") do + self:AddBannedModel(match) + end + end +end + +net.Receive("ph_bannedmodels_getall", function(len, ply) + net.Start("ph_bannedmodels_getall") + + for key, value in pairs(GAMEMODE.BannedModels) do + if value then + net.WriteString(key) + end + end + + net.WriteString("") + net.Send(ply) +end) + +net.Receive("ph_bannedmodels_add", function(len, ply) + if !ply:IsAdmin() then return end + + local model = net.ReadString() + if model == "" then return end + + GAMEMODE:AddBannedModel(model) + net.Start("ph_bannedmodels_add") + net.WriteString(model) + net.Broadcast() +end) + +net.Receive("ph_bannedmodels_remove", function(len, ply) + if !ply:IsAdmin() then return end + + local model = net.ReadString() + if model == "" then return end + + GAMEMODE:RemoveBannedModel(model) + net.Start("ph_bannedmodels_remove") + net.WriteString(model) + net.Broadcast() +end) diff --git a/gamemodes/ultimateph/gamemode/sv_chatmsg.lua b/gamemodes/ultimateph/gamemode/sv_chatmsg.lua index 277be86..158e0b7 100644 --- a/gamemodes/ultimateph/gamemode/sv_chatmsg.lua +++ b/gamemodes/ultimateph/gamemode/sv_chatmsg.lua @@ -1,20 +1,20 @@ --- The file provides the functionality for sending chat messages to players from --- the server. This is used instead of PrintMessage because we want to have --- colored messages (PrintMessage can't do this). - -util.AddNetworkString("ph_chatmsg") -local PlayerMeta = FindMetaTable("Player") - --- Sends a message to an individual player. -function PlayerMeta:PlayerChatMsg(...) - net.Start("ph_chatmsg") - net.WriteTable({...}) - net.Send(self) -end - --- Sends a message to every player. -function GlobalChatMsg(...) - net.Start("ph_chatmsg") - net.WriteTable({...}) - net.Broadcast() -end +-- The file provides the functionality for sending chat messages to players from +-- the server. This is used instead of PrintMessage because we want to have +-- colored messages (PrintMessage can't do this). + +util.AddNetworkString("ph_chatmsg") +local PlayerMeta = FindMetaTable("Player") + +-- Sends a message to an individual player. +function PlayerMeta:PlayerChatMsg(...) + net.Start("ph_chatmsg") + net.WriteTable({...}) + net.Send(self) +end + +-- Sends a message to every player. +function GlobalChatMsg(...) + net.Start("ph_chatmsg") + net.WriteTable({...}) + net.Broadcast() +end diff --git a/gamemodes/ultimateph/gamemode/sv_health.lua b/gamemodes/ultimateph/gamemode/sv_health.lua index 4210c72..4096a8b 100644 --- a/gamemodes/ultimateph/gamemode/sv_health.lua +++ b/gamemodes/ultimateph/gamemode/sv_health.lua @@ -1,11 +1,11 @@ -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:SetHMaxHealth(amo) - self.HMaxHealth = amo - self:SetNWFloat("HMaxHealth", amo) - self:SetMaxHealth(amo) -end - -function PlayerMeta:GetHMaxHealth() - return self.HMaxHealth || 100 -end +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:SetHMaxHealth(amo) + self.HMaxHealth = amo + self:SetNWFloat("HMaxHealth", amo) + self:SetMaxHealth(amo) +end + +function PlayerMeta:GetHMaxHealth() + return self.HMaxHealth || 100 +end diff --git a/gamemodes/ultimateph/gamemode/sv_killfeed.lua b/gamemodes/ultimateph/gamemode/sv_killfeed.lua index a7bee8f..8bb057d 100644 --- a/gamemodes/ultimateph/gamemode/sv_killfeed.lua +++ b/gamemodes/ultimateph/gamemode/sv_killfeed.lua @@ -1,78 +1,78 @@ -util.AddNetworkString("ph_kill_feed_add") - -local DMG_CLUB_GENERIC = bit.bor(DMG_CLUB, DMG_GENERIC) -local DMG_SLOWBURN_BURN = bit.bor(DMG_SLOWBURN, DMG_BURN) -local DMG_BLAST_SURFACE_BLAST = bit.bor(DMG_BLAST_SURFACE, DMG_BLAST) -local DMG_SONIC_SHOCK = bit.bor(DMG_SONIC, DMG_SHOCK) -local DMG_PLASMA_ENERGYBEAM = bit.bor(DMG_PLASMA, DMG_ENERGYBEAM) -local DMG_NERVEGAS_POISON = bit.bor(DMG_NERVEGAS, DMG_POISON) -local DMG_DISSOLVE_ACID = bit.bor(DMG_DISSOLVE, DMG_ACID) - -local attackedMessages = {} -attackedMessages[DMG_CLUB_GENERIC] = {"killed", "destroyed"} -attackedMessages[DMG_CRUSH] = {"threw a prop at", "crushed"} -attackedMessages[DMG_BULLET] = {"shot", "fed lead to"} -attackedMessages[DMG_SLASH] = {"cut", "sliced"} -attackedMessages[DMG_SLOWBURN_BURN] = {"incinerated", "cooked"} -attackedMessages[DMG_VEHICLE] = {"ran over", "flattened"} -attackedMessages[DMG_FALL] = {"pushed", "tripped"} -attackedMessages[DMG_BLAST_SURFACE_BLAST] = {"blew up", "blasted"} -attackedMessages[DMG_SONIC_SHOCK] = {"electrocuted", "zapped"} -attackedMessages[DMG_PLASMA_ENERGYBEAM] = {"atomized", "disintegrated"} -attackedMessages[DMG_DROWN] = {"drowned"} -attackedMessages[DMG_NERVEGAS_POISON] = {"poisoned"} -attackedMessages[DMG_RADIATION] = {"irradiated"} -attackedMessages[DMG_DISSOLVE_ACID] = {"dissolved"} -attackedMessages[DMG_DIRECT] = {"mysteriously killed"} -attackedMessages[DMG_BUCKSHOT] = {"swiss cheesed", "shotgunned"} -attackedMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props - -local suicideMessages = {} -suicideMessages[DMG_CLUB_GENERIC] = {"couldn't take it anymore", "killed themself"} -suicideMessages[DMG_CRUSH] = {"was crushed to death"} -suicideMessages[DMG_BULLET] = {"shot themself"} -suicideMessages[DMG_SLASH] = {"got a paper cut"} -suicideMessages[DMG_SLOWBURN_BURN] = {"burned to death"} -suicideMessages[DMG_VEHICLE] = {"ran themself over"} -suicideMessages[DMG_FALL] = {"fell over"} -suicideMessages[DMG_BLAST_SURFACE_BLAST] = {"blew themself up"} -suicideMessages[DMG_SONIC_SHOCK] = {"electrocuted themself"} -suicideMessages[DMG_PLASMA_ENERGYBEAM] = {"looked into a laser"} -suicideMessages[DMG_DROWN] = {"drowned", "couldn't swim"} -suicideMessages[DMG_NERVEGAS_POISON] = {"ate some hemlock", "couldn't find an antidote"} -suicideMessages[DMG_RADIATION] = {"handled too much uranium"} -suicideMessages[DMG_DISSOLVE_ACID] = {"spilled acid on themself"} -suicideMessages[DMG_DIRECT] = {"mysteriously died"} -suicideMessages[DMG_BUCKSHOT] = {"shotgunned themself"} -suicideMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props - -local function getKillMessage(dmgInfo, tblToUse) - local message - for dmgType, messages in pairs(tblToUse) do - if dmgInfo:IsDamageType(dmgType) then - message = table.Random(messages) - end - end - - return message -end - -function GM:AddKillFeed(ply, attacker, dmgInfo) - local killData = { - victimName = ply:Nick(), - victimColor = team.GetColor(ply:Team()), - messageColor = Color(255, 255, 255, 255) - } - - if IsValid(attacker) && attacker:IsPlayer() && ply != attacker then - killData.attackerName = attacker:Nick() - killData.attackerColor = team.GetColor(attacker:Team()) - killData.message = getKillMessage(dmgInfo, attackedMessages) || "killed" - else - killData.message = getKillMessage(dmgInfo, suicideMessages) || "died" - end - - net.Start("ph_kill_feed_add") - net.WriteTable(killData) - net.Broadcast() -end +util.AddNetworkString("ph_kill_feed_add") + +local DMG_CLUB_GENERIC = bit.bor(DMG_CLUB, DMG_GENERIC) +local DMG_SLOWBURN_BURN = bit.bor(DMG_SLOWBURN, DMG_BURN) +local DMG_BLAST_SURFACE_BLAST = bit.bor(DMG_BLAST_SURFACE, DMG_BLAST) +local DMG_SONIC_SHOCK = bit.bor(DMG_SONIC, DMG_SHOCK) +local DMG_PLASMA_ENERGYBEAM = bit.bor(DMG_PLASMA, DMG_ENERGYBEAM) +local DMG_NERVEGAS_POISON = bit.bor(DMG_NERVEGAS, DMG_POISON) +local DMG_DISSOLVE_ACID = bit.bor(DMG_DISSOLVE, DMG_ACID) + +local attackedMessages = {} +attackedMessages[DMG_CLUB_GENERIC] = {"killed", "destroyed"} +attackedMessages[DMG_CRUSH] = {"threw a prop at", "crushed"} +attackedMessages[DMG_BULLET] = {"shot", "fed lead to"} +attackedMessages[DMG_SLASH] = {"cut", "sliced"} +attackedMessages[DMG_SLOWBURN_BURN] = {"incinerated", "cooked"} +attackedMessages[DMG_VEHICLE] = {"ran over", "flattened"} +attackedMessages[DMG_FALL] = {"pushed", "tripped"} +attackedMessages[DMG_BLAST_SURFACE_BLAST] = {"blew up", "blasted"} +attackedMessages[DMG_SONIC_SHOCK] = {"electrocuted", "zapped"} +attackedMessages[DMG_PLASMA_ENERGYBEAM] = {"atomized", "disintegrated"} +attackedMessages[DMG_DROWN] = {"drowned"} +attackedMessages[DMG_NERVEGAS_POISON] = {"poisoned"} +attackedMessages[DMG_RADIATION] = {"irradiated"} +attackedMessages[DMG_DISSOLVE_ACID] = {"dissolved"} +attackedMessages[DMG_DIRECT] = {"mysteriously killed"} +attackedMessages[DMG_BUCKSHOT] = {"swiss cheesed", "shotgunned"} +attackedMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props + +local suicideMessages = {} +suicideMessages[DMG_CLUB_GENERIC] = {"couldn't take it anymore", "killed themself"} +suicideMessages[DMG_CRUSH] = {"was crushed to death"} +suicideMessages[DMG_BULLET] = {"shot themself"} +suicideMessages[DMG_SLASH] = {"got a paper cut"} +suicideMessages[DMG_SLOWBURN_BURN] = {"burned to death"} +suicideMessages[DMG_VEHICLE] = {"ran themself over"} +suicideMessages[DMG_FALL] = {"fell over"} +suicideMessages[DMG_BLAST_SURFACE_BLAST] = {"blew themself up"} +suicideMessages[DMG_SONIC_SHOCK] = {"electrocuted themself"} +suicideMessages[DMG_PLASMA_ENERGYBEAM] = {"looked into a laser"} +suicideMessages[DMG_DROWN] = {"drowned", "couldn't swim"} +suicideMessages[DMG_NERVEGAS_POISON] = {"ate some hemlock", "couldn't find an antidote"} +suicideMessages[DMG_RADIATION] = {"handled too much uranium"} +suicideMessages[DMG_DISSOLVE_ACID] = {"spilled acid on themself"} +suicideMessages[DMG_DIRECT] = {"mysteriously died"} +suicideMessages[DMG_BUCKSHOT] = {"shotgunned themself"} +suicideMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props + +local function getKillMessage(dmgInfo, tblToUse) + local message + for dmgType, messages in pairs(tblToUse) do + if dmgInfo:IsDamageType(dmgType) then + message = table.Random(messages) + end + end + + return message +end + +function GM:AddKillFeed(ply, attacker, dmgInfo) + local killData = { + victimName = ply:Nick(), + victimColor = team.GetColor(ply:Team()), + messageColor = Color(255, 255, 255, 255) + } + + if IsValid(attacker) && attacker:IsPlayer() && ply != attacker then + killData.attackerName = attacker:Nick() + killData.attackerColor = team.GetColor(attacker:Team()) + killData.message = getKillMessage(dmgInfo, attackedMessages) || "killed" + else + killData.message = getKillMessage(dmgInfo, suicideMessages) || "died" + end + + net.Start("ph_kill_feed_add") + net.WriteTable(killData) + net.Broadcast() +end diff --git a/gamemodes/ultimateph/gamemode/sv_mapvote.lua b/gamemodes/ultimateph/gamemode/sv_mapvote.lua index 8b3464e..49b638b 100644 --- a/gamemodes/ultimateph/gamemode/sv_mapvote.lua +++ b/gamemodes/ultimateph/gamemode/sv_mapvote.lua @@ -1,252 +1,252 @@ --- mapvote - -util.AddNetworkString("ph_mapvote") -util.AddNetworkString("ph_mapvotevotes") - -GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 -GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() - -function GM:IsMapVoting() - return self.MapVoting -end - -function GM:GetMapVoteStart() - return self.MapVoteStart -end - -function GM:GetMapVoteRunningTime() - return CurTime() - self.MapVoteStart -end - -function GM:RotateMap() - local map = game.GetMap() - local index - for k, map2 in pairs(self.MapList) do - if map == map2 then - index = k - end - end - - if !index then index = 1 end - index = index + 1 - - if index > #self.MapList then - index = 1 - end - - local nextMap = self.MapList[index] - self:ChangeMapTo(nextMap) -end - -function GM:ChangeMapTo(map) - if map == game.GetMap() then - self.Rounds = 0 - self:SetGameState(ROUND_WAIT) - return - end - - print("[ultimateph] Rotate changing map to " .. map) - GlobalChatMsg("Changing map to ", map) - hook.Call("OnChangeMap", GAMEMODE) - timer.Simple(5, function() - RunConsoleCommand("changelevel", map) - end) -end - -GM.MapList = {} - -local defaultMapList = { - "cs_italy", - "cs_office", - "cs_compound", - "cs_assault" -} - -function GM:SaveMapList() - -- ensure the folders are there - if !file.Exists("ultimateph/", "DATA") then - file.CreateDir("ultimateph") - end - - local txt = "" - for k, map in pairs(self.MapList) do - txt = txt .. map .. "\r\n" - end - - file.Write("ultimateph/maplist.txt", txt) -end - -function GM:LoadMapList() - local jason = file.ReadDataAndContent("ultimateph/maplist.txt") - if jason then - local tbl = {} - for map in jason:gmatch("[^\r\n]+") do - table.insert(tbl, map) - end - - self.MapList = tbl - else - local tbl = {} - - for k, map in pairs(defaultMapList) do - if file.Exists("maps/" .. map .. ".bsp", "GAME") then - table.insert(tbl, map) - end - end - - local files = file.Find("maps/*", "GAME") - for k, v in pairs(files) do - local name = v:match("([^%.]+)%.bsp$") - if name then - if name:sub(1, 3) == "ph_" then - table.insert(tbl, name) - end - end - end - - self.MapList = tbl - self:SaveMapList() - end - - for k, map in pairs(self.MapList) do - local path = "maps/" .. map .. ".png" - if file.Exists(path, "GAME") then - resource.AddSingleFile(path) - else - local path = "maps/thumb/" .. map .. ".png" - if file.Exists(path, "GAME") then - resource.AddSingleFile(path) - end - end - end -end - -function GM:StartMapVote() - -- Check if we're using the MapVote addon. If so, ignore the builtin mapvote logic. - -- MapVote Workshop Link: https://steamcommunity.com/sharedfiles/filedetails/?id=151583504 - local initHookTbl = hook.GetTable().Initialize - if initHookTbl && initHookTbl.MapVoteConfigSetup then - self:SetGameState(ROUND_MAPVOTE) - MapVote.Start() - return - end - - -- allow developers to override builtin mapvote - if hook.GetTable().PHStartMapVote then - self:SetGameState(ROUND_MAPVOTE) - hook.Run("PHStartMapVote") - return - end - - self.MapVoteStart = CurTime() - self.MapVoteTime = 30 - self.MapVoting = true - self.MapVotes = {} - - -- randomise the order of maps so people choose different ones - local maps = {} - for k, v in pairs(self.MapList) do - table.insert(maps, math.random(#maps) + 1, v) - end - - self.MapList = maps - self:SetGameState(ROUND_MAPVOTE) - self:NetworkMapVoteStart() -end - -function GM:MapVoteThink() - if self.MapVoting then - if self:GetMapVoteRunningTime() >= self.MapVoteTime then - self.MapVoting = false - local votes = {} - for ply, map in pairs(self.MapVotes) do - if IsValid(ply) && ply:IsPlayer() then - votes[map] = (votes[map] || 0) + 1 - end - end - - local maxvotes = 0 - for k, v in pairs(votes) do - if v > maxvotes then - maxvotes = v - end - end - - local maps = {} - for k, v in pairs(votes) do - if v == maxvotes then - table.insert(maps, k) - end - end - - if #maps > 0 then - self:ChangeMapTo(table.Random(maps)) - else - GlobalChatMsg("Map change failed, not enough votes") - print("Map change failed, not enough votes") - self:SetGameState(ROUND_WAIT) - end - end - end -end - -function GM:NetworkMapVoteStart(ply) - net.Start("ph_mapvote") - net.WriteFloat(self.MapVoteStart) - net.WriteFloat(self.MapVoteTime) - - for k, map in pairs(self.MapList) do - net.WriteUInt(k, 16) - net.WriteString(map) - end - net.WriteUInt(0, 16) - - if ply then - net.Send(ply) - else - net.Broadcast() - end - - self:NetworkMapVotes() -end - -function GM:NetworkMapVotes(ply) - net.Start("ph_mapvotevotes") - - for k, map in pairs(self.MapVotes) do - net.WriteUInt(1, 8) - net.WriteEntity(k) - net.WriteString(map) - end - net.WriteUInt(0, 8) - - if ply then - net.Send(ply) - else - net.Broadcast() - end -end - -concommand.Add("ph_votemap", function(ply, com, args) - if GAMEMODE.MapVoting then - if #args < 1 then - return - end - - local found - for k, v in pairs(GAMEMODE.MapList) do - if v:lower() == args[1]:lower() then - found = v - break - end - end - - if !found then - ply:ChatPrint("Invalid map " .. args[1]) - return - end - - GAMEMODE.MapVotes[ply] = found - GAMEMODE:NetworkMapVotes() - end -end) +-- mapvote + +util.AddNetworkString("ph_mapvote") +util.AddNetworkString("ph_mapvotevotes") + +GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 +GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() + +function GM:IsMapVoting() + return self.MapVoting +end + +function GM:GetMapVoteStart() + return self.MapVoteStart +end + +function GM:GetMapVoteRunningTime() + return CurTime() - self.MapVoteStart +end + +function GM:RotateMap() + local map = game.GetMap() + local index + for k, map2 in pairs(self.MapList) do + if map == map2 then + index = k + end + end + + if !index then index = 1 end + index = index + 1 + + if index > #self.MapList then + index = 1 + end + + local nextMap = self.MapList[index] + self:ChangeMapTo(nextMap) +end + +function GM:ChangeMapTo(map) + if map == game.GetMap() then + self.Rounds = 0 + self:SetGameState(ROUND_WAIT) + return + end + + print("[ultimateph] Rotate changing map to " .. map) + GlobalChatMsg("Changing map to ", map) + hook.Call("OnChangeMap", GAMEMODE) + timer.Simple(5, function() + RunConsoleCommand("changelevel", map) + end) +end + +GM.MapList = {} + +local defaultMapList = { + "cs_italy", + "cs_office", + "cs_compound", + "cs_assault" +} + +function GM:SaveMapList() + -- ensure the folders are there + if !file.Exists("ultimateph/", "DATA") then + file.CreateDir("ultimateph") + end + + local txt = "" + for k, map in pairs(self.MapList) do + txt = txt .. map .. "\r\n" + end + + file.Write("ultimateph/maplist.txt", txt) +end + +function GM:LoadMapList() + local jason = file.ReadDataAndContent("ultimateph/maplist.txt") + if jason then + local tbl = {} + for map in jason:gmatch("[^\r\n]+") do + table.insert(tbl, map) + end + + self.MapList = tbl + else + local tbl = {} + + for k, map in pairs(defaultMapList) do + if file.Exists("maps/" .. map .. ".bsp", "GAME") then + table.insert(tbl, map) + end + end + + local files = file.Find("maps/*", "GAME") + for k, v in pairs(files) do + local name = v:match("([^%.]+)%.bsp$") + if name then + if name:sub(1, 3) == "ph_" then + table.insert(tbl, name) + end + end + end + + self.MapList = tbl + self:SaveMapList() + end + + for k, map in pairs(self.MapList) do + local path = "maps/" .. map .. ".png" + if file.Exists(path, "GAME") then + resource.AddSingleFile(path) + else + local path = "maps/thumb/" .. map .. ".png" + if file.Exists(path, "GAME") then + resource.AddSingleFile(path) + end + end + end +end + +function GM:StartMapVote() + -- Check if we're using the MapVote addon. If so, ignore the builtin mapvote logic. + -- MapVote Workshop Link: https://steamcommunity.com/sharedfiles/filedetails/?id=151583504 + local initHookTbl = hook.GetTable().Initialize + if initHookTbl && initHookTbl.MapVoteConfigSetup then + self:SetGameState(ROUND_MAPVOTE) + MapVote.Start() + return + end + + -- allow developers to override builtin mapvote + if hook.GetTable().PHStartMapVote then + self:SetGameState(ROUND_MAPVOTE) + hook.Run("PHStartMapVote") + return + end + + self.MapVoteStart = CurTime() + self.MapVoteTime = 30 + self.MapVoting = true + self.MapVotes = {} + + -- randomise the order of maps so people choose different ones + local maps = {} + for k, v in pairs(self.MapList) do + table.insert(maps, math.random(#maps) + 1, v) + end + + self.MapList = maps + self:SetGameState(ROUND_MAPVOTE) + self:NetworkMapVoteStart() +end + +function GM:MapVoteThink() + if self.MapVoting then + if self:GetMapVoteRunningTime() >= self.MapVoteTime then + self.MapVoting = false + local votes = {} + for ply, map in pairs(self.MapVotes) do + if IsValid(ply) && ply:IsPlayer() then + votes[map] = (votes[map] || 0) + 1 + end + end + + local maxvotes = 0 + for k, v in pairs(votes) do + if v > maxvotes then + maxvotes = v + end + end + + local maps = {} + for k, v in pairs(votes) do + if v == maxvotes then + table.insert(maps, k) + end + end + + if #maps > 0 then + self:ChangeMapTo(table.Random(maps)) + else + GlobalChatMsg("Map change failed, not enough votes") + print("Map change failed, not enough votes") + self:SetGameState(ROUND_WAIT) + end + end + end +end + +function GM:NetworkMapVoteStart(ply) + net.Start("ph_mapvote") + net.WriteFloat(self.MapVoteStart) + net.WriteFloat(self.MapVoteTime) + + for k, map in pairs(self.MapList) do + net.WriteUInt(k, 16) + net.WriteString(map) + end + net.WriteUInt(0, 16) + + if ply then + net.Send(ply) + else + net.Broadcast() + end + + self:NetworkMapVotes() +end + +function GM:NetworkMapVotes(ply) + net.Start("ph_mapvotevotes") + + for k, map in pairs(self.MapVotes) do + net.WriteUInt(1, 8) + net.WriteEntity(k) + net.WriteString(map) + end + net.WriteUInt(0, 8) + + if ply then + net.Send(ply) + else + net.Broadcast() + end +end + +concommand.Add("ph_votemap", function(ply, com, args) + if GAMEMODE.MapVoting then + if #args < 1 then + return + end + + local found + for k, v in pairs(GAMEMODE.MapList) do + if v:lower() == args[1]:lower() then + found = v + break + end + end + + if !found then + ply:ChatPrint("Invalid map " .. args[1]) + return + end + + GAMEMODE.MapVotes[ply] = found + GAMEMODE:NetworkMapVotes() + end +end) diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index 0d17878..21c0b7f 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -1,682 +1,669 @@ -local PlayerMeta = FindMetaTable("Player") - -function GM:PlayerInitialSpawn(ply) - self:RoundsSetupPlayer(ply) - ply:SetTeam(team.BestAutoJoinTeam()) - - if self:GetGameState() != ROUND_WAIT then - ply:CSpectate() - timer.Simple(0, function() - if IsValid(ply) then - ply:KillSilent() - end - end) - end - - self.LastPlayerSpawn = CurTime() - - if self:IsMapVoting() then - self:NetworkMapVoteStart(ply) - end -end - -function GM:PlayerLoadedLocalPlayer(ply) - self:SetTauntMenuPhrase(self.TauntMenuPhrase:GetString(), ply) -end - -net.Receive("clientIPE", function(len, ply) - if !ply.ClientIPE then - ply.ClientIPE = true - hook.Call("PlayerLoadedLocalPlayer", GAMEMODE, ply) - end -end) - -function GM:PlayerDisconnected(ply) - ply:SetTeam(TEAM_HUNTER) -end - -util.AddNetworkString("hull_set") - -function GM:PlayerSpawn(ply) - ply:UnCSpectate() - - player_manager.OnPlayerSpawn(ply) - player_manager.RunClass(ply, "Spawn") - - hook.Call("PlayerLoadout", GAMEMODE, ply) - hook.Call("PlayerSetModel", GAMEMODE, ply) - - ply:UnDisguise() - ply:CalculateSpeed() - - ply:SetHMaxHealth(100) - ply:SetHealth(ply:GetHMaxHealth()) - - GAMEMODE:PlayerSetNewHull(ply) - self:PlayerSetupHands(ply) - - local col = team.GetColor(ply:Team()) - local vec = Vector(col.r / 255, col.g / 255, col.b / 255) - ply:SetPlayerColor(vec) - - ply.LastSpawnTime = CurTime() -end - -function GM:PlayerSetupHands(ply) - local oldhands = ply:GetHands() - if IsValid(oldhands) then oldhands:Remove() end - - local hands = ents.Create("gmod_hands") - if IsValid(hands) then - ply:SetHands(hands) - hands:SetOwner(ply) - - -- Which hands should we use? - local cl_playermodel = ply:GetInfo("cl_playermodel") - local info = player_manager.TranslatePlayerHands(cl_playermodel) - if info then - hands:SetModel(info.model) - hands:SetSkin(info.skin) - hands:SetBodyGroups(info.body) - end - - -- Attach them to the viewmodel - local vm = ply:GetViewModel(0) - hands:AttachToViewmodel(vm) - - vm:DeleteOnRemove(hands) - ply:DeleteOnRemove(hands) - - hands:Spawn() - end -end - -function PlayerMeta:CalculateSpeed() - -- set the defaults - local settings = { - walkSpeed = GAMEMODE.WalkSpeed:GetInt(), - runSpeed = GAMEMODE.RunSpeed:GetInt(), - jumpPower = GAMEMODE.JumpPower:GetInt(), - canRun = true, - canMove = true, - canJump = true - } - - -- speed penalty for small objects (popcan, small bottles, mouse, etc) - if self:IsDisguised() then - if GAMEMODE.PropsSmallSize:GetFloat() > 0 then - local mul = math.Clamp(self:GetNWFloat("disguiseVolume", 1) / GAMEMODE.PropsSmallSize:GetFloat(), 0.5, 1) - settings.walkSpeed = settings.walkSpeed * mul - end - - if settings.runSpeed > settings.walkSpeed then - settings.runSpeed = settings.walkSpeed - end - - settings.jumpPower = settings.jumpPower * GAMEMODE.PropsJumpPower:GetFloat() - end - - hook.Call("PlayerCalculateSpeed", ply, settings) - - -- set out new speeds - if settings.canRun then - self:SetRunSpeed(settings.runSpeed || 1) - else - self:SetRunSpeed(settings.walkSpeed || 1) - end - - if self:GetMoveType() != MOVETYPE_NOCLIP then - if settings.canMove then - self:SetMoveType(MOVETYPE_WALK) - else - self:SetMoveType(MOVETYPE_NONE) - end - end - - self.CanRun = settings.canRun - self:SetWalkSpeed(settings.walkSpeed || 1) - self:SetJumpPower(settings.jumpPower || 1) -end - -function GM:PlayerLoadout(ply) - if ply:IsHunter() then - ply:Give("weapon_crowbar") - ply:Give("weapon_smg1") - ply:Give("weapon_shotgun") - ply:Give("weapon_357") - - ply:GiveAmmo(45 * 10, "SMG1") - ply:GiveAmmo(6 * 10, "buckshot") - ply:GiveAmmo(6 * 10, "357") - local amo = self.HunterGrenadeAmount:GetInt() - if amo > 0 then - ply:GiveAmmo(amo, "SMG1_Grenade") - end - end -end - -local playerModels = {} -local function addModel(model, sex, pteam) - local t = {} - t.model = model - t.sex = sex - - if type(pteam) == "string" then - pteam = pteam:lower() - if pteam == "prop" || pteam == "props" then - t.team = TEAM_PROP - elseif pteam == "hunter" || pteam == "hunters" then - t.team = TEAM_HUNTER - else - t.team = nil - end - else - t.team = nil - end - - table.insert(playerModels, t) -end - -local function removeModel(model) - for k, p, i in pairs(playerModels) do - if p.model == model then - table.remove(playerModels, k) - break - end - end -end - --- local defaultPlayerModels = {"male01", "male02", "male03", "male04", "male05", "male06", "male07", "male08", "male09", "female01", "female02", "female03", "female04", "female05", "female06", "refugee01", "refugee02", "refugee03", "refugee04"} -local function removeBasicDefaultModels() - -- for k, p in pairs(playerModels) do - -- for r, m in pairs(defaultPlayerModels) do - -- if p.model == m then - -- table.remove(playerModels, k) - -- end - -- end - -- end - - removeModel("male01") - removeModel("male02") - removeModel("male03") - removeModel("male04") - removeModel("male05") - removeModel("male06") - removeModel("male07") - removeModel("male08") - removeModel("male09") - removeModel("female01") - removeModel("female02") - removeModel("female03") - removeModel("female04") - removeModel("female05") - removeModel("female06") - removeModel("refugee01") - removeModel("refugee02") - removeModel("refugee03") - removeModel("refugee04") -end - -local tempG = {} -tempG.addModel = addModel -tempG.removeBasicDefaultModels = removeBasicDefaultModels -tempG.removeModel = removeModel - --- inherit from _G -local meta = {} -meta.__index = _G -meta.__newindex = _G -setmetatable(tempG, meta) - -local function loadModels(rootFolder) - local files = file.Find(rootFolder .. "*.lua", "LUA") - for k, v in pairs(files) do - local filePath = rootFolder .. v - AddCSLuaFile(filePath) - - local f = CompileFile(filePath) - if !f then - return - end - - setfenv(f, tempG) - local b, err = pcall(f) - - local s = SERVER && "Server" || "Client" - local c = SERVER && 90 || 0 - if !b then - MsgC(Color(255, 50, 50 + c), s .. " loading models failed: " .. filePath .. "\nError: " .. err .. "\n") - else - MsgC(Color(50, 255, 50 + c), s .. " loaded models file: " .. filePath .. "\n") - end - end -end - -function GM:LoadModels() - loadModels((GM || GAMEMODE).Folder:sub(11) .. "/gamemode/models/") - loadModels("ultimateph/models/") -end - -GM:LoadModels() - -function GM:PlayerSetModel(ply) - local cl_playermodel = ply:GetInfo("cl_playermodel") - local playerModel = table.Random(playerModels) - - while !(playerModel.team == ply:Team() || playerModel.team == nil) do - playerModel = table.Random(playerModels) - end - - cl_playermodel = playerModel.model - - local modelname = player_manager.TranslatePlayerModel(cl_playermodel) - util.PrecacheModel(modelname) - ply:SetModel(modelname) - ply.ModelSex = playerModel.sex - - net.Start("player_model_sex") - net.WriteString(playerModel.sex) - net.Send(ply) -end - -function GM:PlayerDeathSound() - return true -end - --- This is only a shallow copy. -local function mergeTables(...) - local args = {...} - local newTable = {} - for _, tbl in ipairs(args) do - for _, value in ipairs(tbl) do - table.insert(newTable, value) - end - end - - return newTable -end - -------------------------------- -------------------------------- -------------------------------- - --- Most of the following code from this comment block down to the next is from TTT. --- The code provides the functionality for automatically generating spawnpoints if --- necessary. Prophunters sort-of-not-really had a system for doing this but it --- basically just killed players every time or got them stuck in each other so it --- was not very useful. The TTT code has been tweaked to work better with Prophunters. - --- Nice Fisher-Yates implementation, from Wikipedia -local rand = math.random -local function shuffleTable(t) - local n = #t - while n > 2 do - -- n is now the last pertinent index - local k = rand(n) -- 1 <= k <= n - -- Quick swap - t[n], t[k] = t[k], t[n] - n = n - 1 - end - - return t -end - -function GM:IsSpawnpointSuitable(ply, spwn, force, rigged) - if !IsValid(ply) || ply:IsSpectator() then return true end - if !rigged && (!IsValid(spwn) || !spwn:IsInWorld()) then return false end - - -- spwn is normally an ent, but we sometimes use a vector for jury rigged - -- positions - local pos = rigged && spwn || spwn:GetPos() - if !util.IsInWorld(pos) then return false end - - local blocking = ents.FindInBox(pos + Vector(-32, -32, 0), pos + Vector(32, 32, 64)) -- Changed from (-16, -16, 0) (16, 16, 64) - for _, blockingEnt in ipairs(blocking) do - if IsValid(blockingEnt) && blockingEnt:IsPlayer() && !blockingEnt:IsSpectator() && blockingEnt:Alive() then - if force then - blockingEnt:Kill() - blockingEnt:PlayerChatMsg(Color(200, 20, 20), "You were killed because there are not enough spawnpoints.") - for _, value in ipairs(player.GetAll()) do - if value:IsAdmin() || value:IsSuperAdmin() then - value:PlayerChatMsg(Color(200, 20, 20), "Not enough spawnpoints; " .. blockingEnt:Nick() .. " has been killed to spawn " .. ply:Nick() .. ".") - end - end - else - return false - end - end - end - - return true -end - --- TTT only had a single table for spawnpoints but we're going to use three different ones --- so that we can try to group teams together. -local propSpawnTypes = {"info_player_terrorist", "info_player_axis", -"info_player_combine", "info_player_pirate", "info_player_viking", -"diprip_start_team_blue", "info_player_blue", "info_player_human"} - -local hunterSpawnTypes = {"info_player_counterterrorist", -"info_player_allies", "info_player_rebel", "info_player_knight", -"diprip_start_team_red", "info_player_red", "info_player_zombie"} - --- These spawn types should ideally only be used for spectators. Hunters/props spawning here --- will probably fall out of the world. -local spectatorSpawnTypes = {"info_player_start", "gmod_player_start", -"info_player_teamspawn", "ins_spawnpoint", "aoc_spawnpoint", -"dys_spawn_point", "info_player_coop", "info_player_deathmatch"} - -local function getSpawnEnts(plyTeam, force_all) - local tblToUse - if plyTeam == TEAM_PROP then - tblToUse = propSpawnTypes - elseif plyTeam == TEAM_HUNTER then - tblToUse = hunterSpawnTypes - else - tblToUse = spectatorSpawnTypes - end - - local tbl = {} - for _, classname in ipairs(tblToUse) do - for _, e in ipairs(ents.FindByClass(classname)) do - if IsValid(e) && !e.BeingRemoved then - table.insert(tbl, e) - end - end - end - - -- If necessary, ignore the plyTeam restriction and use ALL spawnpoints. - if force_all || #tbl == 0 then - local allSpawnTypes = mergeTables(propSpawnTypes, hunterSpawnTypes, spectatorSpawnTypes) - for _, classname in ipairs(allSpawnTypes) do - for _, e in ipairs(ents.FindByClass(classname)) do - if IsValid(e) && !e.BeingRemoved then - table.insert(tbl, e) - end - end - end - end - - shuffleTable(tbl) - return tbl -end - --- Generate points next to and above the spawn that we can test for suitability (a "3x3 grid") -local function pointsAroundSpawn(spwn) - if !IsValid(spwn) then return {} end - - local pos = spwn:GetPos() - local w, _ = 50, 72 -- Increased from the default 36, 72 as it seems to work better with Prophunters. - - -- all rigged positions - -- could be done without typing them out, but would take about as much time - return { - pos + Vector(w, 0, 0), - pos + Vector(0, w, 0), - pos + Vector(w, w, 0), - pos + Vector(-w, 0, 0), - pos + Vector(0, -w, 0), - pos + Vector(-w, -w, 0), - pos + Vector(-w, w, 0), - pos + Vector(w, -w, 0) - } -end - -function GM:PlayerSelectSpawn(ply) - local plyTeam = ply:Team() - - -- Should be true when the first player joins the game - if !self.SpawnPoints then - self.SpawnPoints = {} - end - - -- Should be true for each first player on a team - if !self.SpawnPoints[plyTeam] || table.IsEmpty(self.SpawnPoints[plyTeam]) || !IsTableOfEntitiesValid(self.SpawnPoints[plyTeam]) then - self.SpawnPoints[plyTeam] = getSpawnEnts(plyTeam, false) - -- One might think that we have to regenerate our spawnpoint - -- cache. Otherwise, any rigged spawn entities would not get reused, and - -- MORE new entities would be made instead. In reality, the map cleanup at - -- round start will remove our rigged spawns, and we'll have to create new - -- ones anyway. - end - - if table.IsEmpty(self.SpawnPoints[plyTeam]) then - Error("No spawn entity found!\n") - return - end - - -- Just always shuffle, it's not that costly and should help spawn - -- randomness. - shuffleTable(self.SpawnPoints[plyTeam]) - - -- Optimistic attempt: assume there are sufficient spawns for all and one is - -- free - for _, spwn in pairs(self.SpawnPoints[plyTeam]) do - if self:IsSpawnpointSuitable(ply, spwn, false) then - return spwn - end - end - - -- That did not work, so now look around spawns - local picked = nil - for _, spwn in pairs(self.SpawnPoints[plyTeam]) do - picked = spwn -- just to have something if all else fails - - -- See if we can jury rig a spawn near this one - local rigged = pointsAroundSpawn(spwn) - for _, rig in pairs(rigged) do - if self:IsSpawnpointSuitable(ply, rig, false, true) then - local spawnType - if ply:IsProp() then - spawnType = "info_player_terrorist" - elseif ply:IsHunter() then - spawnType = "info_player_counterterrorist" - else - spawnType = "info_player_start" - end - - local rigSpwn = ents.Create(spawnType) - if IsValid(rigSpwn) then - rigSpwn:SetPos(rig) - rigSpwn:Spawn() - - ErrorNoHalt("ULTIMATEPH WARNING: Map has too few spawn points, using a rigged spawn for " .. tostring(ply:Nick()) .. "\n") - - self.HaveRiggedSpawn = true - return rigSpwn - end - end - end - end - - -- Last attempt, force one (this could kill other players) - for _, spwn in pairs(self.SpawnPoints[plyTeam]) do - if self:IsSpawnpointSuitable(ply, spwn, true) then - return spwn - end - end - - return picked -end - --- TTT code ends here. - ------------------------------------ ------------------------------------ ------------------------------------ - -function GM:PlayerDeathThink(ply) - if self:CanRespawn(ply) then - ply:UnCSpectate() - ply:Spawn() - else - self:ChooseSpectatee(ply) - end -end - -local defaultDeathsound = Sound("ambient/voices/f_scream1.wav") -local deathsoundsFile = file.Read(GM.Folder .. "/ph_deathsounds.txt", "GAME") || "" -local deathsounds = util.KeyValuesToTable(deathsoundsFile, true, true) - -for _, v in pairs(deathsounds) do - if type(v) == "string" then - resource.AddFile(Sound(v)) - continue - end - - for _, s in ipairs(v) do - resource.AddFile(Sound(s)) - end -end - -local function chooseDeathsound(key) - local ds = deathsounds[key] - if !ds then return nil end - if type(ds) == "string" then return ds end - if #ds == 0 then return nil end - return table.Random(ds) -end - -local function randomDeathsound(ply) - return chooseDeathsound(ply:SteamID()) || chooseDeathsound("default") || defaultDeathsound -end - -function GM:DoPlayerDeath(ply, attacker, dmginfo) - if ply:IsDisguised() && ply:IsProp() then - ply:EmitSound(randomDeathsound(ply)) - end - - if ply.TauntsUsed then - for k, v in pairs(ply.TauntsUsed) do - ply:StopSound(k) - end - end - - ply.TauntsUsed = {} - ply.TauntEnd = nil - ply.AutoTauntDeadline = nil - - -- are they a prop - if ply:IsProp() then - -- set the last death award - self.LastPropDeath = ply - end - - ply:UnDisguise() - ply:Freeze(false) -- why?, *sigh* - ply:CreateRagdoll() - - local ent = ply:GetNWEntity("DeathRagdoll") - if IsValid(ent) then - ply:CSpectate(OBS_MODE_CHASE, ent) - end - - ply:AddDeaths(1) - - if IsValid(attacker) && attacker:IsPlayer() then - if attacker == ply then - attacker:AddFrags(-1) - else - attacker:AddFrags(1) - - -- did a hunter kill a prop - if attacker:IsHunter() && ply:IsProp() then - -- increase their round kills - attacker.HunterKills = (attacker.HunterKills || 0) + 1 - - -- set the first hunter kill award - if self.FirstHunterKill == nil then - self.FirstHunterKill = attacker - end - end - end - end - - self:AddKillFeed(ply, attacker, dmginfo) -end - -function GM:PlayerDeath(ply, inflictor, attacker) - ply.NextSpawnTime = CurTime() + 1 - ply.DeathTime = CurTime() - - -- time until player can spectate another player - ply.SpectateTime = CurTime() + 2 -end - -function GM:KeyPress(ply, key) - if ply:Alive() then - if key == IN_ATTACK then - self:PlayerDisguise(ply) - end - end -end - -function GM:PlayerSwitchFlashlight(ply) - if ply:IsDisguised() then - return false - end - - return true -end - -function GM:PlayerShouldTaunt(ply, actid) - return false -end - -function GM:PlayerCanSeePlayersChat(text, teamOnly, listener, speaker) - if !IsValid(speaker) then return false end - local canhear = self:PlayerCanHearChatVoice(listener, speaker, "chat", teamOnly) - return canhear -end - -function GM:StartCommand(ply, cmd) - if ply:IsBot() then - cmd:SetForwardMove(0) - cmd:SetSideMove(0) - cmd:SetViewAngles(Angle(0, 0, 0)) - end -end - -local sv_alltalk = GetConVar("sv_alltalk") -function GM:PlayerCanHearPlayersVoice(listener, talker) - if !IsValid(talker) then return false end - return self:PlayerCanHearChatVoice(listener, talker, "voice") -end - -function GM:PlayerCanHearChatVoice(listener, talker, typ, teamOnly) - if typ == "chat" && teamOnly then - if listener:Team() != talker:Team() then - return false - end - end - - if sv_alltalk:GetBool() then - return true - end - - if self:GetGameState() == ROUND_POST || self:GetGameState() == ROUND_WAIT then - return true - end - - -- spectators and dead players can hear everyone - if listener:IsSpectator() || !listener:Alive() then - return true - end - - -- if the player is dead or a spectator we can't hear them - if !talker:Alive() || talker:IsSpectator() then - return false - end - - return true -end - -function GM:PlayerCanPickupWeapon(ply, wep) - if IsValid(wep) then - if ply:IsProp() then - return false - end - end - - return true -end +local PlayerMeta = FindMetaTable("Player") + +function GM:PlayerInitialSpawn(ply) + self:RoundsSetupPlayer(ply) + ply:SetTeam(team.BestAutoJoinTeam()) + + if self:GetGameState() != ROUND_WAIT then + ply:CSpectate() + timer.Simple(0, function() + if IsValid(ply) then + ply:KillSilent() + end + end) + end + + self.LastPlayerSpawn = CurTime() + + if self:IsMapVoting() then + self:NetworkMapVoteStart(ply) + end +end + +function GM:PlayerLoadedLocalPlayer(ply) + self:SetTauntMenuPhrase(self.TauntMenuPhrase:GetString(), ply) +end + +net.Receive("clientIPE", function(len, ply) + if !ply.ClientIPE then + ply.ClientIPE = true + hook.Call("PlayerLoadedLocalPlayer", GAMEMODE, ply) + end +end) + +function GM:PlayerDisconnected(ply) + ply:SetTeam(TEAM_HUNTER) +end + +util.AddNetworkString("hull_set") + +function GM:PlayerSpawn(ply) + ply:UnCSpectate() + + player_manager.OnPlayerSpawn(ply) + player_manager.RunClass(ply, "Spawn") + + hook.Call("PlayerLoadout", GAMEMODE, ply) + hook.Call("PlayerSetModel", GAMEMODE, ply) + + ply:UnDisguise() + ply:CalculateSpeed() + + ply:SetHMaxHealth(100) + ply:SetHealth(ply:GetHMaxHealth()) + + GAMEMODE:PlayerSetNewHull(ply) + self:PlayerSetupHands(ply) + + local col = team.GetColor(ply:Team()) + local vec = Vector(col.r / 255, col.g / 255, col.b / 255) + ply:SetPlayerColor(vec) + + ply.LastSpawnTime = CurTime() +end + +function GM:PlayerSetupHands(ply) + local oldhands = ply:GetHands() + if IsValid(oldhands) then oldhands:Remove() end + + local hands = ents.Create("gmod_hands") + if IsValid(hands) then + ply:SetHands(hands) + hands:SetOwner(ply) + + -- Which hands should we use? + local cl_playermodel = ply:GetInfo("cl_playermodel") + local info = player_manager.TranslatePlayerHands(cl_playermodel) + if info then + hands:SetModel(info.model) + hands:SetSkin(info.skin) + hands:SetBodyGroups(info.body) + end + + -- Attach them to the viewmodel + local vm = ply:GetViewModel(0) + hands:AttachToViewmodel(vm) + + vm:DeleteOnRemove(hands) + ply:DeleteOnRemove(hands) + + hands:Spawn() + end +end + +function PlayerMeta:CalculateSpeed() + -- set the defaults + local settings = { + walkSpeed = GAMEMODE.WalkSpeed:GetInt(), + runSpeed = GAMEMODE.RunSpeed:GetInt(), + jumpPower = GAMEMODE.JumpPower:GetInt(), + canRun = true, + canMove = true, + canJump = true + } + + -- speed penalty for small objects (popcan, small bottles, mouse, etc) + if self:IsDisguised() then + if GAMEMODE.PropsSmallSize:GetFloat() > 0 then + local mul = math.Clamp(self:GetNWFloat("disguiseVolume", 1) / GAMEMODE.PropsSmallSize:GetFloat(), 0.5, 1) + settings.walkSpeed = settings.walkSpeed * mul + end + + settings.jumpPower = settings.jumpPower * GAMEMODE.PropsJumpPower:GetFloat() + end + + hook.Call("PlayerCalculateSpeed", ply, settings) + + -- set out new speeds + if settings.canRun then + self:SetRunSpeed(settings.runSpeed || 1) + else + self:SetRunSpeed(settings.walkSpeed || 1) + end + + if self:GetMoveType() != MOVETYPE_NOCLIP then + if settings.canMove then + self:SetMoveType(MOVETYPE_WALK) + else + self:SetMoveType(MOVETYPE_NONE) + end + end + + self.CanRun = settings.canRun + self:SetWalkSpeed(settings.walkSpeed || 1) + self:SetJumpPower(settings.jumpPower || 1) +end + +function GM:PlayerLoadout(ply) + if ply:IsHunter() then + ply:Give("weapon_crowbar") + ply:Give("weapon_smg1") + ply:Give("weapon_shotgun") + ply:Give("weapon_357") + + ply:GiveAmmo(45 * 10, "SMG1") + ply:GiveAmmo(6 * 10, "buckshot") + ply:GiveAmmo(6 * 10, "357") + local amo = self.HunterGrenadeAmount:GetInt() + if amo > 0 then + ply:GiveAmmo(amo, "SMG1_Grenade") + end + end +end + +local playerModels = {} +local function addModel(model, sex, pteam) + local t = {} + t.model = model + t.sex = sex + + if type(pteam) == "string" then + pteam = pteam:lower() + if pteam == "prop" || pteam == "props" then + t.team = TEAM_PROP + elseif pteam == "hunter" || pteam == "hunters" then + t.team = TEAM_HUNTER + else + t.team = nil + end + else + t.team = nil + end + + table.insert(playerModels, t) +end + +local function removeModel(model) + for k, p, i in pairs(playerModels) do + if p.model == model then + table.remove(playerModels, k) + break + end + end +end + +local function removeBasicDefaultModels() + removeModel("male01") + removeModel("male02") + removeModel("male03") + removeModel("male04") + removeModel("male05") + removeModel("male06") + removeModel("male07") + removeModel("male08") + removeModel("male09") + removeModel("female01") + removeModel("female02") + removeModel("female03") + removeModel("female04") + removeModel("female05") + removeModel("female06") + removeModel("refugee01") + removeModel("refugee02") + removeModel("refugee03") + removeModel("refugee04") +end + +local tempG = {} +tempG.addModel = addModel +tempG.removeBasicDefaultModels = removeBasicDefaultModels +tempG.removeModel = removeModel + +-- inherit from _G +local meta = {} +meta.__index = _G +meta.__newindex = _G +setmetatable(tempG, meta) + +local function loadModels(rootFolder) + local files = file.Find(rootFolder .. "*.lua", "LUA") + for k, v in pairs(files) do + local filePath = rootFolder .. v + AddCSLuaFile(filePath) + + local f = CompileFile(filePath) + if !f then + return + end + + setfenv(f, tempG) + local b, err = pcall(f) + + local s = SERVER && "Server" || "Client" + local c = SERVER && 90 || 0 + if !b then + MsgC(Color(255, 50, 50 + c), s .. " loading models failed: " .. filePath .. "\nError: " .. err .. "\n") + else + MsgC(Color(50, 255, 50 + c), s .. " loaded models file: " .. filePath .. "\n") + end + end +end + +function GM:LoadModels() + loadModels((GM || GAMEMODE).Folder:sub(11) .. "/gamemode/models/") + loadModels("ultimateph/models/") +end + +GM:LoadModels() + +function GM:PlayerSetModel(ply) + local cl_playermodel = ply:GetInfo("cl_playermodel") + local playerModel = table.Random(playerModels) + + while !(playerModel.team == ply:Team() || playerModel.team == nil) do + playerModel = table.Random(playerModels) + end + + cl_playermodel = playerModel.model + + local modelname = player_manager.TranslatePlayerModel(cl_playermodel) + util.PrecacheModel(modelname) + ply:SetModel(modelname) + ply.ModelSex = playerModel.sex + + net.Start("player_model_sex") + net.WriteString(playerModel.sex) + net.Send(ply) +end + +function GM:PlayerDeathSound() + return true +end + +-- This is only a shallow copy. +local function mergeTables(...) + local args = {...} + local newTable = {} + for _, tbl in ipairs(args) do + for _, value in ipairs(tbl) do + table.insert(newTable, value) + end + end + + return newTable +end + +------------------------------- +------------------------------- +------------------------------- + +-- Most of the following code from this comment block down to the next is from TTT. +-- The code provides the functionality for automatically generating spawnpoints if +-- necessary. Prophunters sort-of-not-really had a system for doing this but it +-- basically just killed players every time or got them stuck in each other so it +-- was not very useful. The TTT code has been tweaked to work better with Prophunters. + +-- Nice Fisher-Yates implementation, from Wikipedia +local rand = math.random +local function shuffleTable(t) + local n = #t + while n > 2 do + -- n is now the last pertinent index + local k = rand(n) -- 1 <= k <= n + -- Quick swap + t[n], t[k] = t[k], t[n] + n = n - 1 + end + + return t +end + +function GM:IsSpawnpointSuitable(ply, spwn, force, rigged) + if !IsValid(ply) || ply:IsSpectator() then return true end + if !rigged && (!IsValid(spwn) || !spwn:IsInWorld()) then return false end + + -- spwn is normally an ent, but we sometimes use a vector for jury rigged + -- positions + local pos = rigged && spwn || spwn:GetPos() + if !util.IsInWorld(pos) then return false end + + local blocking = ents.FindInBox(pos + Vector(-32, -32, 0), pos + Vector(32, 32, 64)) -- Changed from (-16, -16, 0) (16, 16, 64) + for _, blockingEnt in ipairs(blocking) do + if IsValid(blockingEnt) && blockingEnt:IsPlayer() && !blockingEnt:IsSpectator() && blockingEnt:Alive() then + if force then + blockingEnt:Kill() + blockingEnt:PlayerChatMsg(Color(200, 20, 20), "You were killed because there are not enough spawnpoints.") + for _, value in ipairs(player.GetAll()) do + if value:IsAdmin() || value:IsSuperAdmin() then + value:PlayerChatMsg(Color(200, 20, 20), "Not enough spawnpoints; " .. blockingEnt:Nick() .. " has been killed to spawn " .. ply:Nick() .. ".") + end + end + else + return false + end + end + end + + return true +end + +-- TTT only had a single table for spawnpoints but we're going to use three different ones +-- so that we can try to group teams together. +local propSpawnTypes = {"info_player_terrorist", "info_player_axis", +"info_player_combine", "info_player_pirate", "info_player_viking", +"diprip_start_team_blue", "info_player_blue", "info_player_human"} + +local hunterSpawnTypes = {"info_player_counterterrorist", +"info_player_allies", "info_player_rebel", "info_player_knight", +"diprip_start_team_red", "info_player_red", "info_player_zombie"} + +-- These spawn types should ideally only be used for spectators. Hunters/props spawning here +-- will probably fall out of the world. +local spectatorSpawnTypes = {"info_player_start", "gmod_player_start", +"info_player_teamspawn", "ins_spawnpoint", "aoc_spawnpoint", +"dys_spawn_point", "info_player_coop", "info_player_deathmatch"} + +local function getSpawnEnts(plyTeam, force_all) + local tblToUse + if plyTeam == TEAM_PROP then + tblToUse = propSpawnTypes + elseif plyTeam == TEAM_HUNTER then + tblToUse = hunterSpawnTypes + else + tblToUse = spectatorSpawnTypes + end + + local tbl = {} + for _, classname in ipairs(tblToUse) do + for _, e in ipairs(ents.FindByClass(classname)) do + if IsValid(e) && !e.BeingRemoved then + table.insert(tbl, e) + end + end + end + + -- If necessary, ignore the plyTeam restriction and use ALL spawnpoints. + if force_all || #tbl == 0 then + local allSpawnTypes = mergeTables(propSpawnTypes, hunterSpawnTypes, spectatorSpawnTypes) + for _, classname in ipairs(allSpawnTypes) do + for _, e in ipairs(ents.FindByClass(classname)) do + if IsValid(e) && !e.BeingRemoved then + table.insert(tbl, e) + end + end + end + end + + shuffleTable(tbl) + return tbl +end + +-- Generate points next to and above the spawn that we can test for suitability (a "3x3 grid") +local function pointsAroundSpawn(spwn) + if !IsValid(spwn) then return {} end + + local pos = spwn:GetPos() + local w, _ = 50, 72 -- Increased from the default 36, 72 as it seems to work better with Prophunters. + + -- all rigged positions + -- could be done without typing them out, but would take about as much time + return { + pos + Vector(w, 0, 0), + pos + Vector(0, w, 0), + pos + Vector(w, w, 0), + pos + Vector(-w, 0, 0), + pos + Vector(0, -w, 0), + pos + Vector(-w, -w, 0), + pos + Vector(-w, w, 0), + pos + Vector(w, -w, 0) + } +end + +function GM:PlayerSelectSpawn(ply) + local plyTeam = ply:Team() + + -- Should be true when the first player joins the game + if !self.SpawnPoints then + self.SpawnPoints = {} + end + + -- Should be true for each first player on a team + if !self.SpawnPoints[plyTeam] || table.IsEmpty(self.SpawnPoints[plyTeam]) || !IsTableOfEntitiesValid(self.SpawnPoints[plyTeam]) then + self.SpawnPoints[plyTeam] = getSpawnEnts(plyTeam, false) + -- One might think that we have to regenerate our spawnpoint + -- cache. Otherwise, any rigged spawn entities would not get reused, and + -- MORE new entities would be made instead. In reality, the map cleanup at + -- round start will remove our rigged spawns, and we'll have to create new + -- ones anyway. + end + + if table.IsEmpty(self.SpawnPoints[plyTeam]) then + Error("No spawn entity found!\n") + return + end + + -- Just always shuffle, it's not that costly and should help spawn + -- randomness. + shuffleTable(self.SpawnPoints[plyTeam]) + + -- Optimistic attempt: assume there are sufficient spawns for all and one is + -- free + for _, spwn in pairs(self.SpawnPoints[plyTeam]) do + if self:IsSpawnpointSuitable(ply, spwn, false) then + return spwn + end + end + + -- That did not work, so now look around spawns + local picked = nil + for _, spwn in pairs(self.SpawnPoints[plyTeam]) do + picked = spwn -- just to have something if all else fails + + -- See if we can jury rig a spawn near this one + local rigged = pointsAroundSpawn(spwn) + for _, rig in pairs(rigged) do + if self:IsSpawnpointSuitable(ply, rig, false, true) then + local spawnType + if ply:IsProp() then + spawnType = "info_player_terrorist" + elseif ply:IsHunter() then + spawnType = "info_player_counterterrorist" + else + spawnType = "info_player_start" + end + + local rigSpwn = ents.Create(spawnType) + if IsValid(rigSpwn) then + rigSpwn:SetPos(rig) + rigSpwn:Spawn() + + ErrorNoHalt("ULTIMATEPH WARNING: Map has too few spawn points, using a rigged spawn for " .. tostring(ply:Nick()) .. "\n") + + self.HaveRiggedSpawn = true + return rigSpwn + end + end + end + end + + -- Last attempt, force one (this could kill other players) + for _, spwn in pairs(self.SpawnPoints[plyTeam]) do + if self:IsSpawnpointSuitable(ply, spwn, true) then + return spwn + end + end + + return picked +end + +-- TTT code ends here. + +----------------------------------- +----------------------------------- +----------------------------------- + +function GM:PlayerDeathThink(ply) + if self:CanRespawn(ply) then + ply:UnCSpectate() + ply:Spawn() + else + self:ChooseSpectatee(ply) + end +end + +local defaultDeathsound = Sound("ambient/voices/f_scream1.wav") +local deathsoundsFile = file.Read(GM.Folder .. "/ph_deathsounds.txt", "GAME") || "" +local deathsounds = util.KeyValuesToTable(deathsoundsFile, true, true) + +for _, v in pairs(deathsounds) do + if type(v) == "string" then + resource.AddFile(Sound(v)) + continue + end + + for _, s in ipairs(v) do + resource.AddFile(Sound(s)) + end +end + +local function chooseDeathsound(key) + local ds = deathsounds[key] + if !ds then return nil end + if type(ds) == "string" then return ds end + if #ds == 0 then return nil end + return table.Random(ds) +end + +local function randomDeathsound(ply) + return chooseDeathsound(ply:SteamID()) || chooseDeathsound("default") || defaultDeathsound +end + +function GM:DoPlayerDeath(ply, attacker, dmginfo) + if ply:IsDisguised() && ply:IsProp() then + ply:EmitSound(randomDeathsound(ply)) + end + + if ply.TauntsUsed then + for k, v in pairs(ply.TauntsUsed) do + ply:StopSound(k) + end + end + + ply.TauntsUsed = {} + ply.TauntEnd = nil + ply.AutoTauntDeadline = nil + + -- are they a prop + if ply:IsProp() then + -- set the last death award + self.LastPropDeath = ply + end + + ply:UnDisguise() + ply:Freeze(false) -- why?, *sigh* + ply:CreateRagdoll() + + local ent = ply:GetNWEntity("DeathRagdoll") + if IsValid(ent) then + ply:CSpectate(OBS_MODE_CHASE, ent) + end + + ply:AddDeaths(1) + + if IsValid(attacker) && attacker:IsPlayer() then + if attacker == ply then + attacker:AddFrags(-1) + else + attacker:AddFrags(1) + + -- did a hunter kill a prop + if attacker:IsHunter() && ply:IsProp() then + -- increase their round kills + attacker.HunterKills = (attacker.HunterKills || 0) + 1 + + -- set the first hunter kill award + if self.FirstHunterKill == nil then + self.FirstHunterKill = attacker + end + end + end + end + + self:AddKillFeed(ply, attacker, dmginfo) +end + +function GM:PlayerDeath(ply, inflictor, attacker) + ply.NextSpawnTime = CurTime() + 1 + ply.DeathTime = CurTime() + + -- time until player can spectate another player + ply.SpectateTime = CurTime() + 2 +end + +function GM:KeyPress(ply, key) + if ply:Alive() then + if key == IN_ATTACK then + self:PlayerDisguise(ply) + end + end +end + +function GM:PlayerSwitchFlashlight(ply) + if ply:IsDisguised() then + return false + end + + return true +end + +function GM:PlayerShouldTaunt(ply, actid) + return false +end + +function GM:PlayerCanSeePlayersChat(text, teamOnly, listener, speaker) + if !IsValid(speaker) then return false end + local canhear = self:PlayerCanHearChatVoice(listener, speaker, "chat", teamOnly) + return canhear +end + +function GM:StartCommand(ply, cmd) + if ply:IsBot() then + cmd:SetForwardMove(0) + cmd:SetSideMove(0) + cmd:SetViewAngles(Angle(0, 0, 0)) + end +end + +local sv_alltalk = GetConVar("sv_alltalk") +function GM:PlayerCanHearPlayersVoice(listener, talker) + if !IsValid(talker) then return false end + return self:PlayerCanHearChatVoice(listener, talker, "voice") +end + +function GM:PlayerCanHearChatVoice(listener, talker, typ, teamOnly) + if typ == "chat" && teamOnly then + if listener:Team() != talker:Team() then + return false + end + end + + if sv_alltalk:GetInt() == 3 then -- sv_alltalk is not a bool. 0 = team only with poximity, 1 = team only without proximity, 2 = everybody with proxitiy, 3 = everybody without proximity + return true + end + + if self:GetGameState() == ROUND_POST || self:GetGameState() == ROUND_WAIT then + return true + end + + -- spectators and dead players can hear everyone + if listener:IsSpectator() || !listener:Alive() then + return true + end + + -- if the player is dead or a spectator we can't hear them + if !talker:Alive() || talker:IsSpectator() then + return false + end + + return true +end + +function GM:PlayerCanPickupWeapon(ply, wep) + if IsValid(wep) then + if ply:IsProp() then + return false + end + end + + return true +end diff --git a/gamemodes/ultimateph/gamemode/sv_playercolor.lua b/gamemodes/ultimateph/gamemode/sv_playercolor.lua index 0bc8ad6..2788cbc 100644 --- a/gamemodes/ultimateph/gamemode/sv_playercolor.lua +++ b/gamemodes/ultimateph/gamemode/sv_playercolor.lua @@ -1,10 +1,10 @@ -local EntityMeta = FindMetaTable("Entity") - -function EntityMeta:GetPlayerColor() - return self.playerColor || Vector() -end - -function EntityMeta:SetPlayerColor(vec) - self.playerColor = vec - self:SetNWVector("playerColor", vec) -end +local EntityMeta = FindMetaTable("Entity") + +function EntityMeta:GetPlayerColor() + return self.playerColor || Vector() +end + +function EntityMeta:SetPlayerColor(vec) + self.playerColor = vec + self:SetNWVector("playerColor", vec) +end diff --git a/gamemodes/ultimateph/gamemode/sv_ragdoll.lua b/gamemodes/ultimateph/gamemode/sv_ragdoll.lua index 1fbd298..3c7940a 100644 --- a/gamemodes/ultimateph/gamemode/sv_ragdoll.lua +++ b/gamemodes/ultimateph/gamemode/sv_ragdoll.lua @@ -1,161 +1,161 @@ -local PlayerMeta = FindMetaTable("Player") -local EntityMeta = FindMetaTable("Entity") - -local dtypes = {} -dtypes[DMG_GENERIC] = "" -dtypes[DMG_CRUSH] = "Blunt Force" -dtypes[DMG_BULLET] = "Bullet" -dtypes[DMG_SLASH] = "Laceration" -dtypes[DMG_BURN] = "Fire" -dtypes[DMG_VEHICLE] = "Blunt Force" -dtypes[DMG_FALL] = "Fall force" -dtypes[DMG_BLAST] = "Explosion" -dtypes[DMG_CLUB] = "Blunt Force" -dtypes[DMG_SHOCK] = "Shock" -dtypes[DMG_SONIC] = "Sonic" -dtypes[DMG_ENERGYBEAM] = "Enery" -dtypes[DMG_DROWN] = "Hydration" -dtypes[DMG_PARALYZE] = "Paralyzation" -dtypes[DMG_NERVEGAS] = "Nervegas" -dtypes[DMG_POISON] = "Poison" -dtypes[DMG_RADIATION] = "Radiation" -dtypes[DMG_DROWNRECOVER] = "" -dtypes[DMG_ACID] = "Acid" -dtypes[DMG_PLASMA] = "Plasma" -dtypes[DMG_AIRBOAT] = "Energy" -dtypes[DMG_DISSOLVE] = "Energy" -dtypes[DMG_BLAST_SURFACE] = "" -dtypes[DMG_DIRECT] = "Fire" -dtypes[DMG_BUCKSHOT] = "Bullet" - -local DeathRagdollsPerPlayer = 3 -local DeathRagdollsPerServer = 22 - -if !PlayerMeta.CreateRagdollOld then - PlayerMeta.CreateRagdollOld = PlayerMeta.CreateRagdoll -end - -function PlayerMeta:CreateRagdoll(attacker, dmginfo) - local ent = self:GetNWEntity("DeathRagdoll") - - -- remove old player ragdolls - if !self.DeathRagdolls then self.DeathRagdolls = {} end - - local countPlayerRagdolls = 1 - for k, rag in pairs(self.DeathRagdolls) do - if IsValid(rag) then - countPlayerRagdolls = countPlayerRagdolls + 1 - else - self.DeathRagdolls[k] = nil - end - end - - if DeathRagdollsPerPlayer >= 0 && countPlayerRagdolls > DeathRagdollsPerPlayer then - for i = 0, countPlayerRagdolls do - if countPlayerRagdolls > DeathRagdollsPerPlayer then - self.DeathRagdolls[1]:Remove() - table.remove(self.DeathRagdolls, 1) - countPlayerRagdolls = countPlayerRagdolls - 1 - else - break - end - end - end - - -- remove old server ragdolls - local c2 = 1 - for k, rag in pairs(GAMEMODE.DeathRagdolls) do - if IsValid(rag) then - c2 = c2 + 1 - else - GAMEMODE.DeathRagdolls[k] = nil - end - end - - if DeathRagdollsPerServer >= 0 && c2 > DeathRagdollsPerServer then - for i = 0, c2 do - if c2 > DeathRagdollsPerServer then - GAMEMODE.DeathRagdolls[1]:Remove() - table.remove(GAMEMODE.DeathRagdolls, 1) - c2 = c2 - 1 - else - break - end - end - end - - local Data = duplicator.CopyEntTable(self) - if !util.IsValidRagdoll(Data.Model) then - return - end - - local ent = ents.Create("prop_ragdoll") - duplicator.DoGeneric(ent, Data) - ent:Spawn() - ent:SetCollisionGroup(COLLISION_GROUP_WEAPON) - ent:Fire("kill", "", 60 * 8) - if ent.SetPlayerColor then - ent:SetPlayerColor(self:GetPlayerColor()) - end - - ent:SetNWEntity("RagdollOwner", self) - - ent.Corpse = {} - ent.Corpse.Name = self:Nick() - ent.Corpse.CauseDeath = "" - ent.Corpse.Attacker = "" - if IsValid(attacker) && attacker:IsPlayer() then - if attacker == self then - if ent.Corpse.CauseDeath == "" then - ent.Corpse.CauseDeath = "Suicide" - end - else - ent.Corpse.Attacker = attacker:Nick() - end - -- inflicter doesn't work, do on GM:PlayerDeath - end - - -- set velocities - local Vel = self:GetVelocity() - local iNumPhysObjects = ent:GetPhysicsObjectCount() - for Bone = 0, iNumPhysObjects-1 do - local PhysObj = ent:GetPhysicsObjectNum(Bone) - if IsValid(PhysObj) then - local Pos, Ang = self:GetBonePosition(ent:TranslatePhysBoneToBone(Bone)) - PhysObj:SetPos(Pos) - PhysObj:SetAngles(Ang) - PhysObj:AddVelocity(Vel) - end - end - - -- finish up - self:SetNWEntity("DeathRagdoll", ent) - table.insert(self.DeathRagdolls, ent) - table.insert(GAMEMODE.DeathRagdolls, ent) -end - -if !PlayerMeta.GetRagdollEntityOld then - PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity -end - -function PlayerMeta:GetRagdollEntity() - local ent = self:GetNWEntity("DeathRagdoll") - if IsValid(ent) then - return ent - end - - return self:GetRagdollEntityOld() -end - -if !PlayerMeta.GetRagdollOwnerOld then - PlayerMeta.GetRagdollOwnerOld = PlayerMeta.GetRagdollOwner -end - -function EntityMeta:GetRagdollOwner() - local ent = self:GetNWEntity("RagdollOwner") - if IsValid(ent) then - return ent - end - - return self:GetRagdollOwnerOld() -end +local PlayerMeta = FindMetaTable("Player") +local EntityMeta = FindMetaTable("Entity") + +local dtypes = {} +dtypes[DMG_GENERIC] = "" +dtypes[DMG_CRUSH] = "Blunt Force" +dtypes[DMG_BULLET] = "Bullet" +dtypes[DMG_SLASH] = "Laceration" +dtypes[DMG_BURN] = "Fire" +dtypes[DMG_VEHICLE] = "Blunt Force" +dtypes[DMG_FALL] = "Fall force" +dtypes[DMG_BLAST] = "Explosion" +dtypes[DMG_CLUB] = "Blunt Force" +dtypes[DMG_SHOCK] = "Shock" +dtypes[DMG_SONIC] = "Sonic" +dtypes[DMG_ENERGYBEAM] = "Enery" +dtypes[DMG_DROWN] = "Hydration" +dtypes[DMG_PARALYZE] = "Paralyzation" +dtypes[DMG_NERVEGAS] = "Nervegas" +dtypes[DMG_POISON] = "Poison" +dtypes[DMG_RADIATION] = "Radiation" +dtypes[DMG_DROWNRECOVER] = "" +dtypes[DMG_ACID] = "Acid" +dtypes[DMG_PLASMA] = "Plasma" +dtypes[DMG_AIRBOAT] = "Energy" +dtypes[DMG_DISSOLVE] = "Energy" +dtypes[DMG_BLAST_SURFACE] = "" +dtypes[DMG_DIRECT] = "Fire" +dtypes[DMG_BUCKSHOT] = "Bullet" + +local DeathRagdollsPerPlayer = 3 +local DeathRagdollsPerServer = 22 + +if !PlayerMeta.CreateRagdollOld then + PlayerMeta.CreateRagdollOld = PlayerMeta.CreateRagdoll +end + +function PlayerMeta:CreateRagdoll(attacker, dmginfo) + local ent = self:GetNWEntity("DeathRagdoll") + + -- remove old player ragdolls + if !self.DeathRagdolls then self.DeathRagdolls = {} end + + local countPlayerRagdolls = 1 + for k, rag in pairs(self.DeathRagdolls) do + if IsValid(rag) then + countPlayerRagdolls = countPlayerRagdolls + 1 + else + self.DeathRagdolls[k] = nil + end + end + + if DeathRagdollsPerPlayer >= 0 && countPlayerRagdolls > DeathRagdollsPerPlayer then + for i = 0, countPlayerRagdolls do + if countPlayerRagdolls > DeathRagdollsPerPlayer then + self.DeathRagdolls[1]:Remove() + table.remove(self.DeathRagdolls, 1) + countPlayerRagdolls = countPlayerRagdolls - 1 + else + break + end + end + end + + -- remove old server ragdolls + local c2 = 1 + for k, rag in pairs(GAMEMODE.DeathRagdolls) do + if IsValid(rag) then + c2 = c2 + 1 + else + GAMEMODE.DeathRagdolls[k] = nil + end + end + + if DeathRagdollsPerServer >= 0 && c2 > DeathRagdollsPerServer then + for i = 0, c2 do + if c2 > DeathRagdollsPerServer then + GAMEMODE.DeathRagdolls[1]:Remove() + table.remove(GAMEMODE.DeathRagdolls, 1) + c2 = c2 - 1 + else + break + end + end + end + + local Data = duplicator.CopyEntTable(self) + if !util.IsValidRagdoll(Data.Model) then + return + end + + local ent = ents.Create("prop_ragdoll") + duplicator.DoGeneric(ent, Data) + ent:Spawn() + ent:SetCollisionGroup(COLLISION_GROUP_WEAPON) + ent:Fire("kill", "", 60 * 8) + if ent.SetPlayerColor then + ent:SetPlayerColor(self:GetPlayerColor()) + end + + ent:SetNWEntity("RagdollOwner", self) + + ent.Corpse = {} + ent.Corpse.Name = self:Nick() + ent.Corpse.CauseDeath = "" + ent.Corpse.Attacker = "" + if IsValid(attacker) && attacker:IsPlayer() then + if attacker == self then + if ent.Corpse.CauseDeath == "" then + ent.Corpse.CauseDeath = "Suicide" + end + else + ent.Corpse.Attacker = attacker:Nick() + end + -- inflicter doesn't work, do on GM:PlayerDeath + end + + -- set velocities + local Vel = self:GetVelocity() + local iNumPhysObjects = ent:GetPhysicsObjectCount() + for Bone = 0, iNumPhysObjects-1 do + local PhysObj = ent:GetPhysicsObjectNum(Bone) + if IsValid(PhysObj) then + local Pos, Ang = self:GetBonePosition(ent:TranslatePhysBoneToBone(Bone)) + PhysObj:SetPos(Pos) + PhysObj:SetAngles(Ang) + PhysObj:AddVelocity(Vel) + end + end + + -- finish up + self:SetNWEntity("DeathRagdoll", ent) + table.insert(self.DeathRagdolls, ent) + table.insert(GAMEMODE.DeathRagdolls, ent) +end + +if !PlayerMeta.GetRagdollEntityOld then + PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity +end + +function PlayerMeta:GetRagdollEntity() + local ent = self:GetNWEntity("DeathRagdoll") + if IsValid(ent) then + return ent + end + + return self:GetRagdollEntityOld() +end + +if !PlayerMeta.GetRagdollOwnerOld then + PlayerMeta.GetRagdollOwnerOld = PlayerMeta.GetRagdollOwner +end + +function EntityMeta:GetRagdollOwner() + local ent = self:GetNWEntity("RagdollOwner") + if IsValid(ent) then + return ent + end + + return self:GetRagdollOwnerOld() +end diff --git a/gamemodes/ultimateph/gamemode/sv_realism.lua b/gamemodes/ultimateph/gamemode/sv_realism.lua index a127e70..676a05c 100644 --- a/gamemodes/ultimateph/gamemode/sv_realism.lua +++ b/gamemodes/ultimateph/gamemode/sv_realism.lua @@ -1,28 +1,28 @@ -function GM:RealismThink() - for k, ply in pairs(player.GetAll()) do - if ply:Alive() then - -- don't increase velocity when jumping off ground - if ply:KeyPressed(IN_JUMP) && ply.PrevOnGround then - ply.LastJump = CurTime() - - local curVel = ply:GetVelocity() - local newVel = ply.PrevSpeed * 1 - newVel.z = curVel.z - ply:SetLocalVelocity(newVel) - end - - ply.PrevSpeed = ply:GetVelocity() - ply.PrevOnGround = ply:OnGround() - end - end -end - --- minimum velocity to trigger function is 530 -function GM:GetFallDamage(ply, vel) - if vel > 530 then - local minvel = vel - 530 - local dmg = math.ceil(minvel / 278 * GAMEMODE.FallDMGMult:GetInt()) - if GAMEMODE.FallDMGNonLethal:GetBool() && ply:Health() <= dmg then dmg = ply:Health() - 1 end - return dmg - end -end +function GM:RealismThink() + for k, ply in pairs(player.GetAll()) do + if ply:Alive() then + -- don't increase velocity when jumping off ground + if ply:KeyPressed(IN_JUMP) && ply.PrevOnGround then + ply.LastJump = CurTime() + + local curVel = ply:GetVelocity() + local newVel = ply.PrevSpeed * 1 + newVel.z = curVel.z + ply:SetLocalVelocity(newVel) + end + + ply.PrevSpeed = ply:GetVelocity() + ply.PrevOnGround = ply:OnGround() + end + end +end + +-- minimum velocity to trigger function is 530 +function GM:GetFallDamage(ply, vel) + if vel > 530 then + local minvel = vel - 530 + local dmg = math.ceil(minvel / 278 * GAMEMODE.FallDMGMult:GetInt()) + if GAMEMODE.FallDMGNonLethal:GetBool() && ply:Health() <= dmg then dmg = ply:Health() - 1 end + return dmg + end +end diff --git a/gamemodes/ultimateph/gamemode/sv_respawn.lua b/gamemodes/ultimateph/gamemode/sv_respawn.lua index f788a0e..919ace1 100644 --- a/gamemodes/ultimateph/gamemode/sv_respawn.lua +++ b/gamemodes/ultimateph/gamemode/sv_respawn.lua @@ -1,15 +1,15 @@ -function GM:CanRespawn(ply) - if ply:IsSpectator() then - return false - end - - if self:GetGameState() == ROUND_WAIT or self:GetGameState() == ROUND_HIDE then - if ply.NextSpawnTime && ply.NextSpawnTime > CurTime() then return end - - if ply:KeyPressed(IN_JUMP) || ply:KeyPressed(IN_ATTACK) then - return true - end - end - - return false -end +function GM:CanRespawn(ply) + if ply:IsSpectator() then + return false + end + + if self:GetGameState() == ROUND_WAIT or self:GetGameState() == ROUND_HIDE then + if ply.NextSpawnTime && ply.NextSpawnTime > CurTime() then return end + + if ply:KeyPressed(IN_JUMP) || ply:KeyPressed(IN_ATTACK) then + return true + end + end + + return false +end diff --git a/gamemodes/ultimateph/gamemode/sv_rounds.lua b/gamemodes/ultimateph/gamemode/sv_rounds.lua index 22cd9aa..5d78c87 100644 --- a/gamemodes/ultimateph/gamemode/sv_rounds.lua +++ b/gamemodes/ultimateph/gamemode/sv_rounds.lua @@ -1,330 +1,330 @@ -include("sv_awards.lua") - -util.AddNetworkString("gamestate") -util.AddNetworkString("round_victor") -util.AddNetworkString("gamerules") -util.AddNetworkString("rounds") - -GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT -GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() -GM.Rounds = GAMEMODE && GAMEMODE.Rounds || 0 - -local function mapTimeLimitTimerResult() - if GAMEMODE.Rounds < GAMEMODE.RoundLimit:GetInt() then -- Only change if we haven't already hit the round limit - GAMEMODE.Rounds = GAMEMODE.RoundLimit:GetInt() - 1 -- Allows for 1 extra round before hitting the limit - end -end - -local function changeMapTimeLimitTimer(oldValueMinutes, newValueMinutes) - local timerName = "ph_timer_map_time_limit" - if newValueMinutes == -1 then -- Timer should be disabled - timer.Remove(timerName) - else - local newValueSeconds = math.floor(newValueMinutes) * 60 - - -- If a timer exists then take its elapsed time into account when calculating our new time limit. - if timer.Exists(timerName) then - local oldValueSeconds = math.floor(oldValueMinutes) * 60 - newValueSeconds = newValueSeconds - (oldValueSeconds - timer.TimeLeft(timerName)) - end - - -- Timers don't execute their callback if given a negative time. - -- newValueSeconds will be negative if oldValueMinutes was greater than newValueMinutes. - if newValueSeconds < 0 then - mapTimeLimitTimerResult() - else - -- This will create or update the timer with the new value. - timer.Create(timerName, newValueSeconds, 1, mapTimeLimitTimerResult) - end - end -end - -cvars.AddChangeCallback("ph_map_time_limit", function(convar, oldValue, newValue) - changeMapTimeLimitTimer(tonumber(oldValue), tonumber(newValue)) -end) - -function GM:GetGameState() - return self.GameState -end - -function GM:GetStateStart() - return self.StateStart -end - -function GM:GetStateRunningTime() - return CurTime() - self.StateStart -end - -function GM:GetPlayingPlayers() - local players = {} - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() && ply:GetNWBool("RoundInGame") then - table.insert(players, ply) - end - end - - return players -end - -function GM:SetGameState(state) - self.GameState = state - self.StateStart = CurTime() - self:NetworkGameState() -end - -function GM:NetworkGameState(ply) - net.Start("gamestate") - net.WriteUInt(self.GameState || ROUND_WAIT, 32) - net.WriteDouble(self.StateStart || 0) - net.Broadcast() -end - -function GM:GetRoundSettings() - self.RoundSettings = self.RoundSettings || {} - return self.RoundSettings -end - -function GM:NetworkGameSettings(ply) - net.Start("gamerules") - - if self.RoundSettings then - for k, v in pairs(self.RoundSettings) do - net.WriteUInt(1, 8) - net.WriteString(k) - net.WriteType(v) - end - end - net.WriteUInt(0, 8) - - if ply == nil then - net.Broadcast() - else - net.Send(ply) - end -end - -function GM:SetupRound() - local c = 0 - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() then -- ignore spectators - c = c + 1 - end - end - - if c < 2 then - GlobalChatMsg("Not enough players to start round") - self:SetGameState(ROUND_WAIT) - return - end - - self:BalanceTeams() - - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() then -- ignore spectators - ply:SetNWBool("RoundInGame", true) - ply:KillSilent() - ply:UnCSpectate() - ply:Spawn() - - local col = team.GetColor(ply:Team()) - ply:SetPlayerColor(Vector(col.r / 255, col.g / 255, col.b / 255)) - - if ply:IsHunter() then - ply:Freeze(true) - end - - ply.PropDmgPenalty = 0 - ply.PropMovement = 0 - ply.HunterKills = 0 - ply.TauntAmount = 0 - ply.TauntsUsed = {} - ply.TauntEnd = nil - ply.AutoTauntDeadline = nil - else - ply:SetNWBool("RoundInGame", false) - end - end - - self:CleanupMap() - self.Rounds = self.Rounds + 1 - net.Start("rounds") - net.WriteInt(self.Rounds, 5) - net.Broadcast() - - if self.Rounds == self.RoundLimit:GetInt() then - GlobalChatMsg(Color(255, 0, 0), "This is the LAST ROUND!") - - if self.Secrets:GetBool() then - BroadcastLua("surface.PlaySound('husklesph/hphaaaaa2.mp3')") - end - end - - hook.Run("OnSetupRound") - self:SetGameState(ROUND_HIDE) -end - -function GM:StartRound() - self.LastPropDeath = nil - self.FirstHunterKill = nil - - local hunters, props = 0, 0 - for k, ply in pairs(self:GetPlayingPlayers()) do - ply:Freeze(false) - ply.PropDmgPenalty = 0 - ply.PropMovement = 0 - ply.HunterKills = 0 - ply.TauntAmount = 0 - if ply:IsHunter() then - hunters = hunters + 1 - elseif ply:IsProp() then - props = props + 1 - end - end - - local c = 0 - for k, ent in pairs(ents.GetAll()) do - if ent.IsDisguisableAs && ent:IsDisguisableAs() then - c = c + 1 - end - end - - self.RoundSettings = {} - if self.RoundTime:GetInt() > 0 then - self.RoundSettings.RoundTime = self.RoundTime:GetInt() - else - self.RoundSettings.RoundTime = math.Round((c * 0.5 / hunters + 60 * 4) * math.sqrt(props / hunters)) - end - self.RoundSettings.PropsCamDistanceMult = self.PropsCamDistanceMult:GetFloat() - print("Round time is " .. (self.RoundSettings.RoundTime / 60) .. " (" .. c .. " props)") - self:NetworkGameSettings() - self:SetGameState(ROUND_SEEK) - GlobalChatMsg("Round has started") -end - -function GM:EndRound(winningTeam) - if winningTeam == WIN_NONE then - GlobalChatMsg("Tie everybody loses") - else - GlobalChatMsg(team.GetColor(winningTeam), team.GetName(winningTeam), " win") - end - - self.LastRoundResult = winningTeam - - hook.Run("PH".. winningTeam, ply) - hook.Run("PHEndRound", ply) - - local awards = {} - for awardKey, award in pairs(PlayerAwards) do -- PlayerAwards comes from sv_awards.lua - local result = award.getWinner() - - -- nil values cannot exist in awards otherwise the net.WriteTable below will break - if !result then - continue - elseif type(result) == "Player" && IsValid(result) then - awards[awardKey] = { - name = award.name, - desc = award.desc, - winnerName = result:Nick(), - winnerTeam = result:Team() - } - hook.Run("PHAward", result, award) - else - ErrorNoHalt("ULTIMATEPH WARNING: EndRound Player Award gave non Player object: " .. type(result)) - end - end - - net.Start("round_victor") - net.WriteUInt(winningTeam, 8) - net.WriteTable(awards) - net.Broadcast() - - self.RoundSettings.NextRoundTime = self.PostRoundTime:GetInt() - self:NetworkGameSettings() - self:SetGameState(ROUND_POST) -end - -function GM:RoundsSetupPlayer(ply) - -- start off not participating - ply:SetNWBool("RoundInGame", false) - - -- send game state - self:NetworkGameState(ply) -end - -function GM:CheckForVictory() - -- Check if time limit expired - local settings = self:GetRoundSettings() - local roundTime = settings.RoundTime || 5 * 60 - if self:GetStateRunningTime() > roundTime then - self:EndRound(WIN_PROP) - return - end - - -- Check if there are still living players on either team - local huntersAlive, propsAlive = false, false - for _, ply in pairs(self:GetPlayingPlayers()) do - if !ply:Alive() then continue end - - huntersAlive = huntersAlive || ply:IsHunter() - propsAlive = propsAlive || ply:IsProp() - end - - if !huntersAlive && !propsAlive then - self:EndRound(WIN_NONE) - elseif !huntersAlive then - self:EndRound(WIN_PROP) - elseif !propsAlive then - self:EndRound(WIN_HUNTER) - end -end - -function GM:RoundsThink() - if self:GetGameState() == ROUND_WAIT then - local c = 0 - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() then -- ignore spectators - c = c + 1 - end - end - - if c >= 2 && self.RoundWaitForPlayers + self.StartWaitTime:GetFloat() < CurTime() then - self:SetupRound() - end - elseif self:GetGameState() == ROUND_HIDE then - if self:GetStateRunningTime() > self.HidingTime:GetInt() then - self:StartRound() - end - elseif self:GetGameState() == ROUND_SEEK then - self:CheckForVictory() - for k, ply in pairs(self:GetPlayingPlayers()) do - if ply:IsProp() && ply:Alive() then - ply.PropMovement = (ply.PropMovement || 0) + ply:GetVelocity():Length() - end - end - elseif self:GetGameState() == ROUND_POST then - if self:GetStateRunningTime() > (self.RoundSettings.NextRoundTime || 30) then - if self.RoundLimit:GetInt() > 0 && self.Rounds >= self.RoundLimit:GetInt() then - self:StartMapVote() - else - if self.LastRoundResult != WIN_PROP || !self.PropsWinStayProps:GetBool() then - self:SwapTeams() - end - - self:SetupRound() - end - end - elseif self:GetGameState() == ROUND_MAPVOTE then - self:MapVoteThink() - end -end - -local function ForceEndRound(ply, command, args) - -- ply is nil on dedicated server console - if !IsValid(ply) || ply:IsAdmin() || ply:IsSuperAdmin() || cvars.Bool("sv_cheats", 0) then - GAMEMODE.RoundSettings = GAMEMODE.RoundSettings || {} - GAMEMODE:EndRound(WIN_NONE) - else - ply:PrintMessage(HUD_PRINTCONSOLE, "You must be a GMod Admin or SuperAdmin on the server to use this command, or sv_cheats must be enabled.") - end -end -concommand.Add("ph_endround", ForceEndRound) +include("sv_awards.lua") + +util.AddNetworkString("gamestate") +util.AddNetworkString("round_victor") +util.AddNetworkString("gamerules") +util.AddNetworkString("rounds") + +GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT +GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() +GM.Rounds = GAMEMODE && GAMEMODE.Rounds || 0 + +local function mapTimeLimitTimerResult() + if GAMEMODE.Rounds < GAMEMODE.RoundLimit:GetInt() then -- Only change if we haven't already hit the round limit + GAMEMODE.Rounds = GAMEMODE.RoundLimit:GetInt() - 1 -- Allows for 1 extra round before hitting the limit + end +end + +local function changeMapTimeLimitTimer(oldValueMinutes, newValueMinutes) + local timerName = "ph_timer_map_time_limit" + if newValueMinutes == -1 then -- Timer should be disabled + timer.Remove(timerName) + else + local newValueSeconds = math.floor(newValueMinutes) * 60 + + -- If a timer exists then take its elapsed time into account when calculating our new time limit. + if timer.Exists(timerName) then + local oldValueSeconds = math.floor(oldValueMinutes) * 60 + newValueSeconds = newValueSeconds - (oldValueSeconds - timer.TimeLeft(timerName)) + end + + -- Timers don't execute their callback if given a negative time. + -- newValueSeconds will be negative if oldValueMinutes was greater than newValueMinutes. + if newValueSeconds < 0 then + mapTimeLimitTimerResult() + else + -- This will create or update the timer with the new value. + timer.Create(timerName, newValueSeconds, 1, mapTimeLimitTimerResult) + end + end +end + +cvars.AddChangeCallback("ph_map_time_limit", function(convar, oldValue, newValue) + changeMapTimeLimitTimer(tonumber(oldValue), tonumber(newValue)) +end) + +function GM:GetGameState() + return self.GameState +end + +function GM:GetStateStart() + return self.StateStart +end + +function GM:GetStateRunningTime() + return CurTime() - self.StateStart +end + +function GM:GetPlayingPlayers() + local players = {} + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() && ply:GetNWBool("RoundInGame") then + table.insert(players, ply) + end + end + + return players +end + +function GM:SetGameState(state) + self.GameState = state + self.StateStart = CurTime() + self:NetworkGameState() +end + +function GM:NetworkGameState(ply) + net.Start("gamestate") + net.WriteUInt(self.GameState || ROUND_WAIT, 32) + net.WriteDouble(self.StateStart || 0) + net.Broadcast() +end + +function GM:GetRoundSettings() + self.RoundSettings = self.RoundSettings || {} + return self.RoundSettings +end + +function GM:NetworkGameSettings(ply) + net.Start("gamerules") + + if self.RoundSettings then + for k, v in pairs(self.RoundSettings) do + net.WriteUInt(1, 8) + net.WriteString(k) + net.WriteType(v) + end + end + net.WriteUInt(0, 8) + + if ply == nil then + net.Broadcast() + else + net.Send(ply) + end +end + +function GM:SetupRound() + local c = 0 + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() then -- ignore spectators + c = c + 1 + end + end + + if c < 2 then + GlobalChatMsg("Not enough players to start round") + self:SetGameState(ROUND_WAIT) + return + end + + self:BalanceTeams() + + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() then -- ignore spectators + ply:SetNWBool("RoundInGame", true) + ply:KillSilent() + ply:UnCSpectate() + ply:Spawn() + + local col = team.GetColor(ply:Team()) + ply:SetPlayerColor(Vector(col.r / 255, col.g / 255, col.b / 255)) + + if ply:IsHunter() then + ply:Freeze(true) + end + + ply.PropDmgPenalty = 0 + ply.PropMovement = 0 + ply.HunterKills = 0 + ply.TauntAmount = 0 + ply.TauntsUsed = {} + ply.TauntEnd = nil + ply.AutoTauntDeadline = nil + else + ply:SetNWBool("RoundInGame", false) + end + end + + self:CleanupMap() + self.Rounds = self.Rounds + 1 + net.Start("rounds") + net.WriteInt(self.Rounds, 5) + net.Broadcast() + + if self.Rounds == self.RoundLimit:GetInt() then + GlobalChatMsg(Color(255, 0, 0), "This is the LAST ROUND!") + + if self.Secrets:GetBool() then + BroadcastLua("surface.PlaySound('husklesph/hphaaaaa2.mp3')") + end + end + + hook.Run("OnSetupRound") + self:SetGameState(ROUND_HIDE) +end + +function GM:StartRound() + self.LastPropDeath = nil + self.FirstHunterKill = nil + + local hunters, props = 0, 0 + for k, ply in pairs(self:GetPlayingPlayers()) do + ply:Freeze(false) + ply.PropDmgPenalty = 0 + ply.PropMovement = 0 + ply.HunterKills = 0 + ply.TauntAmount = 0 + if ply:IsHunter() then + hunters = hunters + 1 + elseif ply:IsProp() then + props = props + 1 + end + end + + local c = 0 + for k, ent in pairs(ents.GetAll()) do + if ent.IsDisguisableAs && ent:IsDisguisableAs() then + c = c + 1 + end + end + + self.RoundSettings = {} + if self.RoundTime:GetInt() > 0 then + self.RoundSettings.RoundTime = self.RoundTime:GetInt() + else + self.RoundSettings.RoundTime = math.Round((c * 0.5 / hunters + 60 * 4) * math.sqrt(props / hunters)) + end + self.RoundSettings.PropsCamDistanceMult = self.PropsCamDistanceMult:GetFloat() + print("Round time is " .. (self.RoundSettings.RoundTime / 60) .. " (" .. c .. " props)") + self:NetworkGameSettings() + self:SetGameState(ROUND_SEEK) + GlobalChatMsg("Round has started") +end + +function GM:EndRound(winningTeam) + if winningTeam == WIN_NONE then + GlobalChatMsg("Tie everybody loses") + else + GlobalChatMsg(team.GetColor(winningTeam), team.GetName(winningTeam), " win") + end + + self.LastRoundResult = winningTeam + + hook.Run("PH".. winningTeam, ply) + hook.Run("PHEndRound", ply) + + local awards = {} + for awardKey, award in pairs(PlayerAwards) do -- PlayerAwards comes from sv_awards.lua + local result = award.getWinner() + + -- nil values cannot exist in awards otherwise the net.WriteTable below will break + if !result then + continue + elseif type(result) == "Player" && IsValid(result) then + awards[awardKey] = { + name = award.name, + desc = award.desc, + winnerName = result:Nick(), + winnerTeam = result:Team() + } + hook.Run("PHAward", result, award) + else + ErrorNoHalt("ULTIMATEPH WARNING: EndRound Player Award gave non Player object: " .. type(result)) + end + end + + net.Start("round_victor") + net.WriteUInt(winningTeam, 8) + net.WriteTable(awards) + net.Broadcast() + + self.RoundSettings.NextRoundTime = self.PostRoundTime:GetInt() + self:NetworkGameSettings() + self:SetGameState(ROUND_POST) +end + +function GM:RoundsSetupPlayer(ply) + -- start off not participating + ply:SetNWBool("RoundInGame", false) + + -- send game state + self:NetworkGameState(ply) +end + +function GM:CheckForVictory() + -- Check if time limit expired + local settings = self:GetRoundSettings() + local roundTime = settings.RoundTime || 5 * 60 + if self:GetStateRunningTime() > roundTime then + self:EndRound(WIN_PROP) + return + end + + -- Check if there are still living players on either team + local huntersAlive, propsAlive = false, false + for _, ply in pairs(self:GetPlayingPlayers()) do + if !ply:Alive() then continue end + + huntersAlive = huntersAlive || ply:IsHunter() + propsAlive = propsAlive || ply:IsProp() + end + + if !huntersAlive && !propsAlive then + self:EndRound(WIN_NONE) + elseif !huntersAlive then + self:EndRound(WIN_PROP) + elseif !propsAlive then + self:EndRound(WIN_HUNTER) + end +end + +function GM:RoundsThink() + if self:GetGameState() == ROUND_WAIT then + local c = 0 + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() then -- ignore spectators + c = c + 1 + end + end + + if c >= 2 && self.RoundWaitForPlayers + self.StartWaitTime:GetFloat() < CurTime() then + self:SetupRound() + end + elseif self:GetGameState() == ROUND_HIDE then + if self:GetStateRunningTime() > self.HidingTime:GetInt() then + self:StartRound() + end + elseif self:GetGameState() == ROUND_SEEK then + self:CheckForVictory() + for k, ply in pairs(self:GetPlayingPlayers()) do + if ply:IsProp() && ply:Alive() then + ply.PropMovement = (ply.PropMovement || 0) + ply:GetVelocity():Length() + end + end + elseif self:GetGameState() == ROUND_POST then + if self:GetStateRunningTime() > (self.RoundSettings.NextRoundTime || 30) then + if self.RoundLimit:GetInt() > 0 && self.Rounds >= self.RoundLimit:GetInt() then + self:StartMapVote() + else + if self.LastRoundResult != WIN_PROP || !self.PropsWinStayProps:GetBool() then + self:SwapTeams() + end + + self:SetupRound() + end + end + elseif self:GetGameState() == ROUND_MAPVOTE then + self:MapVoteThink() + end +end + +local function ForceEndRound(ply, command, args) + -- ply is nil on dedicated server console + if !IsValid(ply) || ply:IsAdmin() || ply:IsSuperAdmin() || cvars.Bool("sv_cheats", 0) then + GAMEMODE.RoundSettings = GAMEMODE.RoundSettings || {} + GAMEMODE:EndRound(WIN_NONE) + else + ply:PrintMessage(HUD_PRINTCONSOLE, "You must be a GMod Admin or SuperAdmin on the server to use this command, or sv_cheats must be enabled.") + end +end +concommand.Add("ph_endround", ForceEndRound) diff --git a/gamemodes/ultimateph/gamemode/sv_spectate.lua b/gamemodes/ultimateph/gamemode/sv_spectate.lua index 40be2a0..d54988e 100644 --- a/gamemodes/ultimateph/gamemode/sv_spectate.lua +++ b/gamemodes/ultimateph/gamemode/sv_spectate.lua @@ -1,133 +1,133 @@ -util.AddNetworkString("spectating_status") - -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:CSpectate(mode, spectatee) - mode = mode || OBS_MODE_IN_EYE - self:Spectate(mode) - if IsValid(spectatee) then - self:SpectateEntity(spectatee) - self.Spectatee = spectatee - else - self:SpectateEntity(Entity(-1)) - self.Spectatee = nil - end - - self.SpectateMode = mode - self.Spectating = true - net.Start("spectating_status") - net.WriteInt(self.SpectateMode || -1, 8) - net.WriteEntity(self.Spectatee || Entity(-1)) - net.Send(self) -end - -function PlayerMeta:UnCSpectate(mode, spectatee) - self:UnSpectate() - self.SpectateMode = nil - self.Spectatee = nil - self.Spectating = false - net.Start("spectating_status") - net.WriteInt(-1, 8) - net.WriteEntity(Entity(-1)) - net.Send(self) -end - -function PlayerMeta:IsCSpectating() - return self.Spectating -end - -function PlayerMeta:GetCSpectatee() - return self.Spectatee -end - -function PlayerMeta:GetCSpectateMode() - return self.SpectateMode -end - -function GM:SpectateThink() - for k, ply in pairs(player.GetAll()) do - if ply:IsCSpectating() && IsValid(ply:GetCSpectatee()) && (!ply.LastSpectatePosSet || ply.LastSpectatePosSet < CurTime()) then - ply.LastSpectatePosSet = CurTime() + 0.25 - ply:SetPos(ply:GetCSpectatee():GetPos()) - end - end -end - -function GM:SpectateNext(ply, direction) - direction = direction || 1 - local players = {} - local index = 1 - for k, v in pairs(player.GetAll()) do - if v != ply then - -- can only spectate same team and alive - if v:Alive() && (v:Team() == ply:Team() || ply:IsSpectator()) then - table.insert(players, v) - if v == ply:GetCSpectatee() then - index = #players - end - end - end - end - - if #players > 0 then - index = index + direction - if index > #players then - index = 1 - end - - if index < 1 then - index = #players - end - - local ent = players[index] - if IsValid(ent) then - if ent:IsPlayer() && ent:IsHunter() then - ply:CSpectate(OBS_MODE_IN_EYE, ent) - else - ply:CSpectate(OBS_MODE_CHASE, ent) - end - else - if IsValid(ply:GetRagdollEntity()) then - if ply:GetCSpectatee() != ply:GetRagdollEntity() then - ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) - end - else - ply:CSpectate(OBS_MODE_ROAMING) - end - end - else - if IsValid(ply:GetRagdollEntity()) then - if ply:GetCSpectatee() != ply:GetRagdollEntity() then - ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) - end - else - ply:CSpectate(OBS_MODE_ROAMING) - end - end -end - -function GM:ChooseSpectatee(ply) - if !ply.SpectateTime || ply.SpectateTime < CurTime() then - if ply:KeyPressed(IN_JUMP) then - if ply:GetCSpectateMode() != OBS_MODE_ROAMING && (ply:IsSpectator() || self.DeadSpectateRoam:GetBool()) then - ply:CSpectate(OBS_MODE_ROAMING) - end - else - local direction - if ply:KeyPressed(IN_ATTACK) then - direction = 1 - elseif ply:KeyPressed(IN_ATTACK2) then - direction = -1 - end - - if direction then - self:SpectateNext(ply, direction) - end - end - end - - -- if invalid or dead - if !IsValid(ply:GetCSpectatee()) && ply:GetCSpectateMode() != OBS_MODE_ROAMING then - self:SpectateNext(ply) - end -end +util.AddNetworkString("spectating_status") + +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:CSpectate(mode, spectatee) + mode = mode || OBS_MODE_IN_EYE + self:Spectate(mode) + if IsValid(spectatee) then + self:SpectateEntity(spectatee) + self.Spectatee = spectatee + else + self:SpectateEntity(Entity(-1)) + self.Spectatee = nil + end + + self.SpectateMode = mode + self.Spectating = true + net.Start("spectating_status") + net.WriteInt(self.SpectateMode || -1, 8) + net.WriteEntity(self.Spectatee || Entity(-1)) + net.Send(self) +end + +function PlayerMeta:UnCSpectate(mode, spectatee) + self:UnSpectate() + self.SpectateMode = nil + self.Spectatee = nil + self.Spectating = false + net.Start("spectating_status") + net.WriteInt(-1, 8) + net.WriteEntity(Entity(-1)) + net.Send(self) +end + +function PlayerMeta:IsCSpectating() + return self.Spectating +end + +function PlayerMeta:GetCSpectatee() + return self.Spectatee +end + +function PlayerMeta:GetCSpectateMode() + return self.SpectateMode +end + +function GM:SpectateThink() + for k, ply in pairs(player.GetAll()) do + if ply:IsCSpectating() && IsValid(ply:GetCSpectatee()) && (!ply.LastSpectatePosSet || ply.LastSpectatePosSet < CurTime()) then + ply.LastSpectatePosSet = CurTime() + 0.25 + ply:SetPos(ply:GetCSpectatee():GetPos()) + end + end +end + +function GM:SpectateNext(ply, direction) + direction = direction || 1 + local players = {} + local index = 1 + for k, v in pairs(player.GetAll()) do + if v != ply then + -- can only spectate same team and alive + if v:Alive() && (v:Team() == ply:Team() || ply:IsSpectator()) then + table.insert(players, v) + if v == ply:GetCSpectatee() then + index = #players + end + end + end + end + + if #players > 0 then + index = index + direction + if index > #players then + index = 1 + end + + if index < 1 then + index = #players + end + + local ent = players[index] + if IsValid(ent) then + if ent:IsPlayer() && ent:IsHunter() then + ply:CSpectate(OBS_MODE_IN_EYE, ent) + else + ply:CSpectate(OBS_MODE_CHASE, ent) + end + else + if IsValid(ply:GetRagdollEntity()) then + if ply:GetCSpectatee() != ply:GetRagdollEntity() then + ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) + end + else + ply:CSpectate(OBS_MODE_ROAMING) + end + end + else + if IsValid(ply:GetRagdollEntity()) then + if ply:GetCSpectatee() != ply:GetRagdollEntity() then + ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) + end + else + ply:CSpectate(OBS_MODE_ROAMING) + end + end +end + +function GM:ChooseSpectatee(ply) + if !ply.SpectateTime || ply.SpectateTime < CurTime() then + if ply:KeyPressed(IN_JUMP) then + if ply:GetCSpectateMode() != OBS_MODE_ROAMING && (ply:IsSpectator() || self.DeadSpectateRoam:GetBool()) then + ply:CSpectate(OBS_MODE_ROAMING) + end + else + local direction + if ply:KeyPressed(IN_ATTACK) then + direction = 1 + elseif ply:KeyPressed(IN_ATTACK2) then + direction = -1 + end + + if direction then + self:SpectateNext(ply, direction) + end + end + end + + -- if invalid or dead + if !IsValid(ply:GetCSpectatee()) && ply:GetCSpectateMode() != OBS_MODE_ROAMING then + self:SpectateNext(ply) + end +end diff --git a/gamemodes/ultimateph/gamemode/sv_taunt.lua b/gamemodes/ultimateph/gamemode/sv_taunt.lua index ba996d6..86c3d06 100644 --- a/gamemodes/ultimateph/gamemode/sv_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sv_taunt.lua @@ -1,153 +1,153 @@ -include("sh_taunt.lua") - -util.AddNetworkString("open_taunt_menu") - -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:CanTaunt() - if !self:Alive() then - return false - end - - if self.TauntEnd && self.TauntEnd > CurTime() then - return false - end - - return true -end - -function PlayerMeta:EmitTaunt(filename, durationOverride) - local duration = SoundDuration(filename) - if filename:match("%.mp3$") then - duration = durationOverride || 1 - end - - local sndName = FilenameToSoundname(filename) - - self:EmitSound(sndName) - self.TauntEnd = CurTime() + duration + 0.1 - self.TauntAmount = (self.TauntAmount || 0) + 1 - self.AutoTauntDeadline = nil - - if !self.TauntsUsed then self.TauntsUsed = {} end - self.TauntsUsed[sndName] = true -end - -local function ForEachTaunt(ply, taunts, func) - for k, v in pairs(taunts) do - if !TauntAllowedForPlayer(ply, v) then continue end - - if func(k, v) then return end - end -end - -local function DoTaunt(ply, snd) - if !IsValid(ply) then return end - if !ply:CanTaunt() then return end - - local ats = AllowedTauntSounds[snd] - if !ats then return end - - local t - ForEachTaunt(ply, ats, function(k, v) - t = v - return true - end) - - if !t then - return - end - - ply:EmitTaunt(snd, t.soundDurationOverride) -end - -local function DoRandomTaunt(ply) - if !IsValid(ply) then return end - if !ply:CanTaunt() then return end - - local potential = {} - ForEachTaunt(ply, Taunts, function(k, v) - table.insert(potential, v) - end) - - if #potential == 0 then return end - - local t = potential[math.random(#potential)] - local snd = t.sound[math.random(#t.sound)] - - ply:EmitTaunt(snd, t.soundDurationOverride) -end - -concommand.Add("ph_taunt", function(ply, com, args, full) - DoTaunt(ply, args[1] || "") -end) - -concommand.Add("ph_taunt_random", function(ply, com, args, full) - DoRandomTaunt(ply) -end) - -util.AddNetworkString("ph_set_taunt_menu_phrase") -function GM:SetTauntMenuPhrase(phrase, ply) - net.Start("ph_set_taunt_menu_phrase") - net.WriteString(phrase) - - if ply then - net.Send(ply) - else - net.Broadcast() - end -end - -cvars.AddChangeCallback("ph_taunt_menu_phrase", function(convar_name, value_old, value_new) - (GM || GAMEMODE):SetTauntMenuPhrase(value_new) -end) - -function GM:AutoTauntCheck() - if self.GameState != ROUND_SEEK then return end - - local propsOnly = self.AutoTauntPropsOnly:GetBool() - local minDeadline = self.AutoTauntMin:GetInt() - local maxDeadline = self.AutoTauntMax:GetInt() - local badMinMax = minDeadline <= 0 || maxDeadline <= 0 || minDeadline > maxDeadline - - for i, ply in ipairs(player.GetAll()) do - if propsOnly && !ply:IsProp() then - ply.AutoTauntDeadline = nil - continue - end - - local begin - if ply.AutoTauntDeadline then - local secsLeft = ply.AutoTauntDeadline - CurTime() - if secsLeft > 0 then - continue - end - - if !ply.TauntEnd || CurTime() > ply.AutoTauntDeadline then - DoRandomTaunt(ply) - begin = ply.TauntEnd - end - end - if !begin then begin = CurTime() end - - if badMinMax then continue end - - local delta = math.random(minDeadline, maxDeadline) - ply.AutoTauntDeadline = begin + delta - end -end - -function GM:StartAutoTauntTimer() - timer.Remove("AutoTauntCheck") - local start = self.AutoTauntEnabled:GetBool() - - if start then - timer.Create("AutoTauntCheck", 5, 0, function() - self:AutoTauntCheck() - end) - end -end - -cvars.AddChangeCallback("ph_auto_taunt", function(convar_name, value_old, value_new) - (GM || GAMEMODE):StartAutoTauntTimer() -end) +include("sh_taunt.lua") + +util.AddNetworkString("open_taunt_menu") + +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:CanTaunt() + if !self:Alive() then + return false + end + + if self.TauntEnd && self.TauntEnd > CurTime() then + return false + end + + return true +end + +function PlayerMeta:EmitTaunt(filename, durationOverride) + local duration = SoundDuration(filename) + if filename:match("%.mp3$") then + duration = durationOverride || 1 + end + + local sndName = FilenameToSoundname(filename) + + self:EmitSound(sndName) + self.TauntEnd = CurTime() + duration + 0.1 + self.TauntAmount = (self.TauntAmount || 0) + 1 + self.AutoTauntDeadline = nil + + if !self.TauntsUsed then self.TauntsUsed = {} end + self.TauntsUsed[sndName] = true +end + +local function ForEachTaunt(ply, taunts, func) + for k, v in pairs(taunts) do + if !TauntAllowedForPlayer(ply, v) then continue end + + if func(k, v) then return end + end +end + +local function DoTaunt(ply, snd) + if !IsValid(ply) then return end + if !ply:CanTaunt() then return end + + local ats = AllowedTauntSounds[snd] + if !ats then return end + + local t + ForEachTaunt(ply, ats, function(k, v) + t = v + return true + end) + + if !t then + return + end + + ply:EmitTaunt(snd, t.soundDurationOverride) +end + +local function DoRandomTaunt(ply) + if !IsValid(ply) then return end + if !ply:CanTaunt() then return end + + local potential = {} + ForEachTaunt(ply, Taunts, function(k, v) + table.insert(potential, v) + end) + + if #potential == 0 then return end + + local t = potential[math.random(#potential)] + local snd = t.sound[math.random(#t.sound)] + + ply:EmitTaunt(snd, t.soundDurationOverride) +end + +concommand.Add("ph_taunt", function(ply, com, args, full) + DoTaunt(ply, args[1] || "") +end) + +concommand.Add("ph_taunt_random", function(ply, com, args, full) + DoRandomTaunt(ply) +end) + +util.AddNetworkString("ph_set_taunt_menu_phrase") +function GM:SetTauntMenuPhrase(phrase, ply) + net.Start("ph_set_taunt_menu_phrase") + net.WriteString(phrase) + + if ply then + net.Send(ply) + else + net.Broadcast() + end +end + +cvars.AddChangeCallback("ph_taunt_menu_phrase", function(convar_name, value_old, value_new) + (GM || GAMEMODE):SetTauntMenuPhrase(value_new) +end) + +function GM:AutoTauntCheck() + if self.GameState != ROUND_SEEK then return end + + local propsOnly = self.AutoTauntPropsOnly:GetBool() + local minDeadline = self.AutoTauntMin:GetInt() + local maxDeadline = self.AutoTauntMax:GetInt() + local badMinMax = minDeadline <= 0 || maxDeadline <= 0 || minDeadline > maxDeadline + + for i, ply in ipairs(player.GetAll()) do + if propsOnly && !ply:IsProp() then + ply.AutoTauntDeadline = nil + continue + end + + local begin + if ply.AutoTauntDeadline then + local secsLeft = ply.AutoTauntDeadline - CurTime() + if secsLeft > 0 then + continue + end + + if !ply.TauntEnd || CurTime() > ply.AutoTauntDeadline then + DoRandomTaunt(ply) + begin = ply.TauntEnd + end + end + if !begin then begin = CurTime() end + + if badMinMax then continue end + + local delta = math.random(minDeadline, maxDeadline) + ply.AutoTauntDeadline = begin + delta + end +end + +function GM:StartAutoTauntTimer() + timer.Remove("AutoTauntCheck") + local start = self.AutoTauntEnabled:GetBool() + + if start then + timer.Create("AutoTauntCheck", 5, 0, function() + self:AutoTauntCheck() + end) + end +end + +cvars.AddChangeCallback("ph_auto_taunt", function(convar_name, value_old, value_new) + (GM || GAMEMODE):StartAutoTauntTimer() +end) diff --git a/gamemodes/ultimateph/gamemode/sv_teams.lua b/gamemodes/ultimateph/gamemode/sv_teams.lua index 4254a50..477cbcb 100644 --- a/gamemodes/ultimateph/gamemode/sv_teams.lua +++ b/gamemodes/ultimateph/gamemode/sv_teams.lua @@ -1,87 +1,87 @@ -concommand.Add("ph_jointeam", function(ply, com, args) - local newTeam = tonumber(args[1]) || TEAM_SPEC -- Default to spectators if there's a problem - if ply:Team() == newTeam then return end - - -- Always able to join spectator team - -- Can join team hunter or team prop if team sizes are equal - -- Otherwise, can only join the smaller team - if newTeam == TEAM_SPEC || team.NumPlayers(TEAM_HUNTER) - team.NumPlayers(TEAM_PROP) == 0 || newTeam == team.BestAutoJoinTeam() then - if ply:Alive() then ply:Kill() end - ply:SetTeam(newTeam) - GlobalChatMsg(ply:Nick(), " changed team to ", team.GetColor(newTeam), team.GetName(newTeam)) - else - ply:PlayerChatMsg("Team full, you cannot join") - end -end) - -function GM:BalanceTeams() - if !self.AutoTeamBalance:GetBool() && self.NumberHunter:GetInt() < (player.GetCount() + 1) then - local tabProps = team.GetPlayers(TEAM_PROP) - - local nbHunters = self.NumberHunter:GetInt() - - while team.NumPlayers(TEAM_HUNTER) > nbHunters do - local players = team.GetPlayers(TEAM_HUNTER) - local ply = players[math.random(#players)] - ply:SetTeam(TEAM_PROP) - end - - for k, ply in pairs(tabProps) do - if ply:Team() == TEAM_HUNTER then - ply:SetTeam(TEAM_PROP) - end - end - elseif !self.AutoTeamBalance:GetBool() && (self.NumberHunter:GetInt() > (player.GetCount() + 1)) then - GlobalChatMsg("There is not enough players to have ", self.NumberHunter:GetInt(), " hunters. Now using Auto Team Balance until there is enough players") - - local teamDiff = team.NumPlayers(TEAM_HUNTER) - team.NumPlayers(TEAM_PROP) - if math.abs(teamDiff) <= 1 then return end -- Only balance if teams are off by 2 or more players - - local biggerTeam, smallerTeam = TEAM_PROP, TEAM_HUNTER -- Assume props had more players - if teamDiff > 1 then -- teamDiff > 1 means hunters had more players - biggerTeam = TEAM_HUNTER - smallerTeam = TEAM_PROP - end - - -- Continuously swap random players from biggerTeam to smallerTeam until sizes are balanced - teamDiff = math.abs(teamDiff) - while teamDiff > 1 do - local players = team.GetPlayers(biggerTeam) - local ply = players[math.random(#players)] - ply:SetTeam(smallerTeam) - GlobalChatMsg(ply:Nick(), " team balanced to ", team.GetColor(smallerTeam), team.GetName(smallerTeam)) - teamDiff = teamDiff - 2 - end - else - local teamDiff = team.NumPlayers(TEAM_HUNTER) - team.NumPlayers(TEAM_PROP) - if math.abs(teamDiff) <= 1 then return end -- Only balance if teams are off by 2 or more players - - local biggerTeam, smallerTeam = TEAM_PROP, TEAM_HUNTER -- Assume props had more players - if teamDiff > 1 then -- teamDiff > 1 means hunters had more players - biggerTeam = TEAM_HUNTER - smallerTeam = TEAM_PROP - end - - -- Continuously swap random players from biggerTeam to smallerTeam until sizes are balanced - teamDiff = math.abs(teamDiff) - while teamDiff > 1 do - local players = team.GetPlayers(biggerTeam) - local ply = players[math.random(#players)] - ply:SetTeam(smallerTeam) - GlobalChatMsg(ply:Nick(), " team balanced to ", team.GetColor(smallerTeam), team.GetName(smallerTeam)) - teamDiff = teamDiff - 2 - end - end -end - -function GM:SwapTeams() - for _, ply in pairs(player.GetAll()) do - if ply:IsHunter() then - ply:SetTeam(TEAM_PROP) - elseif ply:IsProp() then - ply:SetTeam(TEAM_HUNTER) - end - end - - GlobalChatMsg(Color(50, 220, 150), "Teams have been swapped") -end +concommand.Add("ph_jointeam", function(ply, com, args) + local newTeam = tonumber(args[1]) || TEAM_SPEC -- Default to spectators if there's a problem + if ply:Team() == newTeam then return end + + -- Always able to join spectator team + -- Can join team hunter or team prop if team sizes are equal + -- Otherwise, can only join the smaller team + if newTeam == TEAM_SPEC || team.NumPlayers(TEAM_HUNTER) - team.NumPlayers(TEAM_PROP) == 0 || newTeam == team.BestAutoJoinTeam() then + if ply:Alive() then ply:Kill() end + ply:SetTeam(newTeam) + GlobalChatMsg(ply:Nick(), " changed team to ", team.GetColor(newTeam), team.GetName(newTeam)) + else + ply:PlayerChatMsg("Team full, you cannot join") + end +end) + +function GM:BalanceTeams() + if !self.AutoTeamBalance:GetBool() && self.NumberHunter:GetInt() < (player.GetCount() + 1) then + local tabProps = team.GetPlayers(TEAM_PROP) + + local nbHunters = self.NumberHunter:GetInt() + + while team.NumPlayers(TEAM_HUNTER) > nbHunters do + local players = team.GetPlayers(TEAM_HUNTER) + local ply = players[math.random(#players)] + ply:SetTeam(TEAM_PROP) + end + + for k, ply in pairs(tabProps) do + if ply:Team() == TEAM_HUNTER then + ply:SetTeam(TEAM_PROP) + end + end + elseif !self.AutoTeamBalance:GetBool() && (self.NumberHunter:GetInt() > (player.GetCount() + 1)) then + GlobalChatMsg("There is not enough players to have ", self.NumberHunter:GetInt(), " hunters. Now using Auto Team Balance until there is enough players") + + local teamDiff = team.NumPlayers(TEAM_HUNTER) - team.NumPlayers(TEAM_PROP) + if math.abs(teamDiff) <= 1 then return end -- Only balance if teams are off by 2 or more players + + local biggerTeam, smallerTeam = TEAM_PROP, TEAM_HUNTER -- Assume props had more players + if teamDiff > 1 then -- teamDiff > 1 means hunters had more players + biggerTeam = TEAM_HUNTER + smallerTeam = TEAM_PROP + end + + -- Continuously swap random players from biggerTeam to smallerTeam until sizes are balanced + teamDiff = math.abs(teamDiff) + while teamDiff > 1 do + local players = team.GetPlayers(biggerTeam) + local ply = players[math.random(#players)] + ply:SetTeam(smallerTeam) + GlobalChatMsg(ply:Nick(), " team balanced to ", team.GetColor(smallerTeam), team.GetName(smallerTeam)) + teamDiff = teamDiff - 2 + end + else + local teamDiff = team.NumPlayers(TEAM_HUNTER) - team.NumPlayers(TEAM_PROP) + if math.abs(teamDiff) <= 1 then return end -- Only balance if teams are off by 2 or more players + + local biggerTeam, smallerTeam = TEAM_PROP, TEAM_HUNTER -- Assume props had more players + if teamDiff > 1 then -- teamDiff > 1 means hunters had more players + biggerTeam = TEAM_HUNTER + smallerTeam = TEAM_PROP + end + + -- Continuously swap random players from biggerTeam to smallerTeam until sizes are balanced + teamDiff = math.abs(teamDiff) + while teamDiff > 1 do + local players = team.GetPlayers(biggerTeam) + local ply = players[math.random(#players)] + ply:SetTeam(smallerTeam) + GlobalChatMsg(ply:Nick(), " team balanced to ", team.GetColor(smallerTeam), team.GetName(smallerTeam)) + teamDiff = teamDiff - 2 + end + end +end + +function GM:SwapTeams() + for _, ply in pairs(player.GetAll()) do + if ply:IsHunter() then + ply:SetTeam(TEAM_PROP) + elseif ply:IsProp() then + ply:SetTeam(TEAM_HUNTER) + end + end + + GlobalChatMsg(Color(50, 220, 150), "Teams have been swapped") +end diff --git a/gamemodes/ultimateph/gamemode/sv_version.lua b/gamemodes/ultimateph/gamemode/sv_version.lua index 977ac12..217df72 100644 --- a/gamemodes/ultimateph/gamemode/sv_version.lua +++ b/gamemodes/ultimateph/gamemode/sv_version.lua @@ -1,53 +1,53 @@ -local url = "https://raw.githubusercontent.com/datanext27/ultimateph/master/gamemodes/ultimateph/ultimateph.txt" -local downloadlinks = "https://steamcommunity.com/sharedfiles/filedetails/?id=3028430983" - -function GM:CheckForNewVersion(ply) - local req = {} - req.url = url - req.failed = function(reason) - print("Couldn't get version file.", reason) - end - - req.success = function(code, body, headers) - local tab = util.KeyValuesToTable(body) - if !tab || !tab.version then - print("Couldn't parse version file.") - return - end - - local msg = {} - if tab.version != GAMEMODE.Version then - msg = {Color(215, 20, 20), "Out of date!\n", - Color(255, 222, 102), "You're on version ", - Color(0, 255, 0), GAMEMODE.Version || "error", - Color(255, 222, 102), " but the latest is ", - Color(0, 255, 0), tab.version, "\n", - Color(255, 222, 102), "Download the latest version from: ", - Color(11, 191, 227), downloadlinks} - else - msg = {Color(0, 255, 0), "Up to date!"} - end - - if IsValid(ply) then - ply:PlayerChatMsg(unpack(msg)) - else - MsgC(unpack(msg)) - MsgC("\n") - end - end - - HTTP(req) -end - -concommand.Add("ph_version", function(ply) - local color = Color(255, 149, 129) - local msg = (GAMEMODE.Name || "") .. " " .. tostring(GAMEMODE.Version || "error") - - if IsValid(ply) then - ply:PlayerChatMsg(color, msg) - else - MsgC(color, msg, "\n") -- Print to the server console - end - - GAMEMODE:CheckForNewVersion(ply) -end) +local url = "https://raw.githubusercontent.com/datanext27/ultimateph/master/gamemodes/ultimateph/ultimateph.txt" +local downloadlinks = "https://steamcommunity.com/sharedfiles/filedetails/?id=3028430983" + +function GM:CheckForNewVersion(ply) + local req = {} + req.url = url + req.failed = function(reason) + print("Couldn't get version file.", reason) + end + + req.success = function(code, body, headers) + local tab = util.KeyValuesToTable(body) + if !tab || !tab.version then + print("Couldn't parse version file.") + return + end + + local msg = {} + if tab.version != GAMEMODE.Version then + msg = {Color(215, 20, 20), "Out of date!\n", + Color(255, 222, 102), "You're on version ", + Color(0, 255, 0), GAMEMODE.Version || "error", + Color(255, 222, 102), " but the latest is ", + Color(0, 255, 0), tab.version, "\n", + Color(255, 222, 102), "Download the latest version from: ", + Color(11, 191, 227), downloadlinks} + else + msg = {Color(0, 255, 0), "Up to date!"} + end + + if IsValid(ply) then + ply:PlayerChatMsg(unpack(msg)) + else + MsgC(unpack(msg)) + MsgC("\n") + end + end + + HTTP(req) +end + +concommand.Add("ph_version", function(ply) + local color = Color(255, 149, 129) + local msg = (GAMEMODE.Name || "") .. " " .. tostring(GAMEMODE.Version || "error") + + if IsValid(ply) then + ply:PlayerChatMsg(color, msg) + else + MsgC(color, msg, "\n") -- Print to the server console + end + + GAMEMODE:CheckForNewVersion(ply) +end) From 5030e526c7bd11b1ceca6d3cf2994b9977074e34 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 26 Sep 2025 00:21:26 +1000 Subject: [PATCH 03/25] merge all the small files to cl_utils.lua and sv_utils.lua, respectively. merge all duplicate sv_ and cl_ files into sh_ files --- gamemodes/ultimateph/gamemode/cl_aimlaser.lua | 43 -- .../ultimateph/gamemode/cl_bannedmodels.lua | 173 -------- gamemodes/ultimateph/gamemode/cl_chatmsg.lua | 10 - gamemodes/ultimateph/gamemode/cl_disguise.lua | 69 --- .../ultimateph/gamemode/cl_fixplayercolor.lua | 38 -- gamemodes/ultimateph/gamemode/cl_health.lua | 5 - gamemodes/ultimateph/gamemode/cl_init.lua | 20 +- gamemodes/ultimateph/gamemode/cl_mapvote.lua | 56 --- gamemodes/ultimateph/gamemode/cl_ragdoll.lua | 28 -- gamemodes/ultimateph/gamemode/cl_rounds.lua | 56 --- gamemodes/ultimateph/gamemode/cl_spectate.lua | 22 - gamemodes/ultimateph/gamemode/cl_taunt.lua | 254 ----------- .../{cl_killfeed.lua => cl_utils.lua} | 145 ++++++ gamemodes/ultimateph/gamemode/init.lua | 23 +- .../ultimateph/gamemode/sh_bannedmodels.lua | 261 +++++++++++ gamemodes/ultimateph/gamemode/sh_disguise.lua | 238 ++++++++++ gamemodes/ultimateph/gamemode/sh_init.lua | 96 +++- gamemodes/ultimateph/gamemode/sh_mapvote.lua | 314 +++++++++++++ gamemodes/ultimateph/gamemode/sh_rounds.lua | 393 +++++++++++++++++ gamemodes/ultimateph/gamemode/sh_taunt.lua | 412 ++++++++++++++++++ gamemodes/ultimateph/gamemode/shared.lua | 93 ---- .../ultimateph/gamemode/sv_bannedmodels.lua | 88 ---- gamemodes/ultimateph/gamemode/sv_chatmsg.lua | 20 - gamemodes/ultimateph/gamemode/sv_disguise.lua | 165 ------- gamemodes/ultimateph/gamemode/sv_health.lua | 11 - gamemodes/ultimateph/gamemode/sv_killfeed.lua | 78 ---- .../ultimateph/gamemode/sv_playercolor.lua | 10 - gamemodes/ultimateph/gamemode/sv_ragdoll.lua | 161 ------- gamemodes/ultimateph/gamemode/sv_realism.lua | 28 -- gamemodes/ultimateph/gamemode/sv_respawn.lua | 15 - gamemodes/ultimateph/gamemode/sv_rounds.lua | 330 -------------- gamemodes/ultimateph/gamemode/sv_spectate.lua | 133 ------ gamemodes/ultimateph/gamemode/sv_taunt.lua | 153 ------- gamemodes/ultimateph/gamemode/sv_teams.lua | 135 ++++++ gamemodes/ultimateph/gamemode/sv_utils.lua | 387 ++++++++++++++++ gamemodes/ultimateph/gamemode/sv_version.lua | 53 --- 36 files changed, 2394 insertions(+), 2122 deletions(-) delete mode 100644 gamemodes/ultimateph/gamemode/cl_aimlaser.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_bannedmodels.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_chatmsg.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_disguise.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_health.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_mapvote.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_ragdoll.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_rounds.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_spectate.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_taunt.lua rename gamemodes/ultimateph/gamemode/{cl_killfeed.lua => cl_utils.lua} (53%) create mode 100644 gamemodes/ultimateph/gamemode/sh_bannedmodels.lua create mode 100644 gamemodes/ultimateph/gamemode/sh_mapvote.lua create mode 100644 gamemodes/ultimateph/gamemode/sh_rounds.lua delete mode 100644 gamemodes/ultimateph/gamemode/shared.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_bannedmodels.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_chatmsg.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_disguise.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_health.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_killfeed.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_playercolor.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_ragdoll.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_realism.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_respawn.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_rounds.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_spectate.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_taunt.lua create mode 100644 gamemodes/ultimateph/gamemode/sv_utils.lua delete mode 100644 gamemodes/ultimateph/gamemode/sv_version.lua diff --git a/gamemodes/ultimateph/gamemode/cl_aimlaser.lua b/gamemodes/ultimateph/gamemode/cl_aimlaser.lua deleted file mode 100644 index 3c83d4d..0000000 --- a/gamemodes/ultimateph/gamemode/cl_aimlaser.lua +++ /dev/null @@ -1,43 +0,0 @@ -local beamMat = Material("ultimateph/aim_laser") -local ray_color = Color(50,170,46) -local ray_width = 6 - -local dotMat = Material("ultimateph/aim_dot") -local dot_color = Color(255,255,255) - -local hunters_aim = {} - -function GM:PostDrawTranslucentRenderables() - hunters_aim = {} - self:GetHuntersAim() - - -- When "ph_hunter_aim_laser" set to 0, nobody can see the beam - -- set to 1 == spectator only, set to 2 == props and spectator - -- Also, we don't want the hunters to see the ray, to prevent eventual visual bugs - local visibility = GetConVar("ph_hunter_aim_laser"):GetInt() - - if visibility == 0 then return end - - if visibility == 1 and not LocalPlayer():IsSpectator() then return end - if visibility == 2 and not (LocalPlayer():IsSpectator() or LocalPlayer():IsProp()) then return end - - for _, h_aim in pairs(hunters_aim) do - render.SetMaterial(beamMat) - render.DrawBeam(h_aim.eye_pos, h_aim.looking.HitPos, ray_width, 0, 1, ray_color) - - render.SetMaterial(dotMat) - local size = math.random(8, 16) - render.DrawQuadEasy(h_aim.looking.HitPos + h_aim.looking.HitNormal, h_aim.looking.HitNormal, size, size, dot_color, 0) - end -end - -function GM:GetHuntersAim() - for _, ply in pairs(player.GetAll()) do - if ply:IsHunter() then - local t = {} - t.eye_pos = ply:EyePos() - t.looking = ply:GetEyeTrace() - table.insert(hunters_aim, t) - end - end -end diff --git a/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua b/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua deleted file mode 100644 index 374bcaf..0000000 --- a/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua +++ /dev/null @@ -1,173 +0,0 @@ --- This file provides the functionality for accessing the banned models --- menu. It is used locally to determine if a model is viable (aka --- whether or not it gets an outline when moused over). - -GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. -local menu - -function GM:IsModelBanned(model) - return self.BannedModels[model] == true -end - -function GM:AddBannedModel(model) - if self.BannedModels[model] == true then return end - - self.BannedModels[model] = true - menu.AddModel(model) -end - -function GM:RemoveBannedModel(model) - if self.BannedModels[model] != true then return end - - self.BannedModels[model] = nil - menu.RemoveModel(model) -end - -net.Receive("ph_bannedmodels_getall", function(len) - GAMEMODE.BannedModels = {} - - local model = net.ReadString() - while model != "" do - GAMEMODE:AddBannedModel(model) - model = net.ReadString() - end -end) - -net.Receive("ph_bannedmodels_add", function(len) - local model = net.ReadString() - GAMEMODE:AddBannedModel(model) -end) - -net.Receive("ph_bannedmodels_remove", function(len) - local model = net.ReadString() - GAMEMODE:RemoveBannedModel(model) -end) - -concommand.Add("ph_bannedmodels_menu", function(client) - menu:SetVisible(true) -end) - --- This is all the code to create the banned models menu. -function GM:CreateBannedModelsMenu() - -- The main window that will contain all of the functionality for display, adding, and - -- removing banned models. - menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) - menu:SetTitle("Banned Models") - menu:Center() - menu:SetDraggable(true) - menu:ShowCloseButton(true) - menu:MakePopup() - menu:SetDeleteOnClose(false) - menu:SetVisible(false) - - -- Create a text box at the top of the window so the player can input new banned models. - local entry = vgui.Create("DTextEntry", menu) - entry:Dock(TOP) - if LocalPlayer():IsAdmin() then - entry:SetPlaceholderText("Hover for usage information.") - entry:SetTooltip([[ - To ban a model from usage put the path - to the model into this text box and - press Enter. - EXAMPLE INPUTS - models/props/cs_assault/money.mdl - models/props_borealis/bluebarrel001.mdl - models/props/cs_office/projector_remote.mdl]]) - else - entry:SetPlaceholderText("You must be an admin to ban models.") - entry:SetTooltip("You must be an admin to ban models.") - entry:SetEnabled(false) - end - - function entry:OnEnter() - -- This client side check is for informational purposes. - if !LocalPlayer():IsAdmin() then - chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") - return - end - - local modelToBan = entry:GetText() - -- This client side check is for informational purposes. - if modelToBan == "" then - chat.AddText(Color(255, 50, 50), "Error when attempting to ban model: no input text was given.") - return - end - -- This client side check is for informational purposes. - if GAMEMODE:IsModelBanned(modelToBan) then - chat.AddText(Color(255, 50, 50), "That model is already banned.") - return - end - - net.Start("ph_bannedmodels_add") - net.WriteString(modelToBan) - net.SendToServer() - entry:SetText("") - end - - -- Makes a scroll bar on the right side in the event that there are a LOT of - -- banned models and we need to be able to scroll. Will not appear unless - -- there are enough items in the list to require it. - local scrollPanel = vgui.Create("DScrollPanel", menu) - scrollPanel:Dock(FILL) - - local modelIconWidth = 128 - local modelIconHeight = 128 - local usableWidthForModelIcons = menu:GetWide() - scrollPanel:GetVBar():GetWide() -- Don't want icons to overlap the scroll bar. - local numCols = math.floor(usableWidthForModelIcons / modelIconWidth) - -- This offset is almost right, but there's 10 pixels more on the left than on the right and I can't figure out why. :( - local leftOffset = (usableWidthForModelIcons - (modelIconWidth * numCols)) / 2 - - -- This is the grid that will give the icons a nice layout. - local grid = vgui.Create("DGrid", scrollPanel) - menu.grid = grid - grid:SetCols(numCols) - grid:SetPos(leftOffset, 10) - grid:SetColWide(modelIconWidth) - grid:SetRowHeight(modelIconHeight) - - -- Allows functions outside of the menu to update the icons. Useful for updating the menu - -- live if it's open when a net message is received to add a model. - menu.AddModel = function(model) - local modelIcon = vgui.Create("SpawnIcon") - modelIcon:SetPos(75, 75) - modelIcon:SetSize(modelIconWidth, modelIconHeight) - modelIcon:SetEnabled(false) - modelIcon:SetCursor("arrow") - modelIcon:SetModel(model) - grid:AddItem(modelIcon) - - local unbanButton = vgui.Create("DButton", modelIcon) - unbanButton:SetSize(modelIconWidth, modelIconHeight / 4) - unbanButton:SetText("Unban Model") - unbanButton:SetPos(0, modelIconHeight - unbanButton:GetTall()) - unbanButton:SetVisible(false) - - function unbanButton:DoClick() - -- This client side check is for informational purposes. - if !LocalPlayer():IsAdmin() then - chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") - return - end - - net.Start("ph_bannedmodels_remove") - net.WriteString(self:GetParent():GetModelName()) - net.SendToServer() - end - - -- Logic for showing/hiding the unban button. - function modelIcon:Think() - self:GetChild(1):SetVisible(LocalPlayer():IsAdmin() && (self:IsHovered() || self:IsChildHovered())) - end - end - - -- Same as menu.AddModel but for removing models from the grid. - menu.RemoveModel = function(model) - for _, value in pairs(menu.grid:GetItems()) do - if value:GetModelName() == model then - menu.grid:RemoveItem(value) - break - end - end - end -end diff --git a/gamemodes/ultimateph/gamemode/cl_chatmsg.lua b/gamemodes/ultimateph/gamemode/cl_chatmsg.lua deleted file mode 100644 index 8b59c46..0000000 --- a/gamemodes/ultimateph/gamemode/cl_chatmsg.lua +++ /dev/null @@ -1,10 +0,0 @@ --- This file provides the functionality for printing chat messages to the client --- that are sent by the server. This is used instead of something like MsgC or --- PrintMessage because we want the messages to be colored (something PrintMessage --- can't do) and we want to be able to send messages from the server (MsgC can't do --- this). - -net.Receive("ph_chatmsg", function(len) - local tbl = net.ReadTable() - chat.AddText(unpack(tbl)) -end) diff --git a/gamemodes/ultimateph/gamemode/cl_disguise.lua b/gamemodes/ultimateph/gamemode/cl_disguise.lua deleted file mode 100644 index 2941661..0000000 --- a/gamemodes/ultimateph/gamemode/cl_disguise.lua +++ /dev/null @@ -1,69 +0,0 @@ -include("sh_disguise.lua") - -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:IsDisguised() - return self:GetNWBool("disguised", false) -end - -local function renderDis(self) - for k, ply in pairs(player.GetAll()) do - if ply:Alive() && ply:IsDisguised() then - local model = ply:GetNWString("disguiseModel") - if model && model != "" then - local ent = ply:GetNWEntity("disguiseEntity") - if IsValid(ent) then - local mins = ply:GetNWVector("disguiseMins") - local maxs = ply:GetNWVector("disguiseMaxs") - local ang = ply:EyeAngles() - ang.p = 0 - ang.r = 0 - if ply:DisguiseRotationLocked() then - ang.y = ply:GetNWFloat("disguiseRotationLockYaw") - end - local pos = ply:GetPos() + Vector(0, 0, -mins.z) - local center = (maxs + mins) / 2 - center.z = 0 - center:Rotate(ang) - ent:SetPos(pos - center) - ent:SetAngles(ang) - ent:SetSkin(ply:GetNWInt("disguiseSkin", 1)) - end - end - end - end -end - -function GM:RenderDisguises() - cam.Start3D(EyePos(), EyeAngles()) - local b, err = pcall(renderDis, self) - cam.End3D() - if !b then - MsgC(Color(255, 0, 0), err .. "\n") - end -end - -function GM:RenderDisguiseHalo() - local client = LocalPlayer() - if client:IsProp() then - local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(client) - if canDisguise then - local col = Color(50, 220, 50) - local hullxy, hullz = target:GetPropSize() - if !client:CanFitHull(hullxy, hullxy, hullz) then - col = Color(220, 50, 50) - end - halo.Add({target}, col, 2, 2, 2, true, true) - end - - local tab = {} - for k, ply in pairs(player.GetAll()) do - if ply != client && ply:IsProp() && ply:IsDisguised() then - if IsValid(ply.PropMod) then - table.insert(tab, ply.PropMod) - end - end - end - halo.Add(tab, team.GetColor(TEAM_PROP), 2, 2, 2, true, false) - end -end diff --git a/gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua b/gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua deleted file mode 100644 index d49163d..0000000 --- a/gamemodes/ultimateph/gamemode/cl_fixplayercolor.lua +++ /dev/null @@ -1,38 +0,0 @@ -local EntityMeta = FindMetaTable("Entity") - -function EntityMeta:GetPlayerColor() - return self:GetNWVector("playerColor") || Vector() -end - --- Proxies --- { --- PlayerColor --- { --- resultVar $color2 --- } --- } - -matproxy.Add({ - name = "PlayerColor", - init = function(self, mat, values) - -- Store the name of the variable we want to set - self.ResultTo = values.resultvar - end, - bind = function(self, mat, ent) - if !IsValid(ent) then return end - - if ent.GetPlayerColorOverride then -- clientside entities can't override functions, so we need an additional one for it - local col = ent:GetPlayerColorOverride() - if isvector(col) then - mat:SetVector(self.ResultTo, col) - end - elseif ent.GetPlayerColor then - local col = ent:GetPlayerColor() - if isvector(col) then - mat:SetVector(self.ResultTo, col) - end - else - mat:SetVector(self.ResultTo, Vector(62.0 / 255.0, 88.0 / 255.0, 106.0 / 255.0)) - end - end -}) diff --git a/gamemodes/ultimateph/gamemode/cl_health.lua b/gamemodes/ultimateph/gamemode/cl_health.lua deleted file mode 100644 index 8ff7e90..0000000 --- a/gamemodes/ultimateph/gamemode/cl_health.lua +++ /dev/null @@ -1,5 +0,0 @@ -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:GetHMaxHealth() - return self:GetNWFloat("HMaxHealth", 100) || 100 -end diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index 9886a5a..6d64380 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -1,21 +1,15 @@ -include("shared.lua") -include("cl_fixplayercolor.lua") -include("cl_ragdoll.lua") -include("cl_chatmsg.lua") -include("cl_rounds.lua") include("cl_hud.lua") include("cl_scoreboard.lua") -include("cl_spectate.lua") -include("cl_health.lua") -include("cl_killfeed.lua") include("cl_voicepanels.lua") include("cl_helpscreen.lua") -include("cl_disguise.lua") -include("cl_taunt.lua") include("cl_endroundboard.lua") -include("cl_mapvote.lua") -include("cl_bannedmodels.lua") -include("cl_aimlaser.lua") +include("cl_colors.lua") +include("cl_utils.lua") +include("sh_rounds.lua") +include("sh_disguise.lua") +include("sh_taunt.lua") +include("sh_bannedmodels.lua") +include("sh_mapvote.lua") include("sh_init.lua") function GM:InitPostEntity() diff --git a/gamemodes/ultimateph/gamemode/cl_mapvote.lua b/gamemodes/ultimateph/gamemode/cl_mapvote.lua deleted file mode 100644 index 2fc034f..0000000 --- a/gamemodes/ultimateph/gamemode/cl_mapvote.lua +++ /dev/null @@ -1,56 +0,0 @@ -GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 -GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() - -net.Receive("ph_mapvote", function(len) - GAMEMODE.MapVoteStart = net.ReadFloat() - GAMEMODE.MapVoteTime = net.ReadFloat() - - local mapList = {} - while true do - local k = net.ReadUInt(16) - if k <= 0 then break end - local map = net.ReadString() - table.insert(mapList, map) - end - - GAMEMODE.SelfMapVote = nil - GAMEMODE.MapVotes = {} - GAMEMODE.MapVotesByMap = {} - GAMEMODE.MapList = mapList - - GAMEMODE:EndRoundMapVote() -end) - -net.Receive("ph_mapvotevotes", function(len) - local mapVotes = {} - while true do - local k = net.ReadUInt(8) - if k <= 0 then break end - local ply = net.ReadEntity() - local map = net.ReadString() - mapVotes[ply] = map - end - - GAMEMODE.SelfMapVote = nil - - local byMap = {} - for ply, map in pairs(mapVotes) do - byMap[map] = byMap[map] || {} - table.insert(byMap[map], ply) - - if ply == LocalPlayer() then - GAMEMODE.SelfMapVote = map - end - end - - GAMEMODE.MapVotes = mapVotes - GAMEMODE.MapVotesByMap = byMap -end) - -function GM:GetMapVoteStart() - return self.MapVoteStart -end - -function GM:GetMapVoteRunningTime() - return CurTime() - self.MapVoteStart -end diff --git a/gamemodes/ultimateph/gamemode/cl_ragdoll.lua b/gamemodes/ultimateph/gamemode/cl_ragdoll.lua deleted file mode 100644 index ea81d27..0000000 --- a/gamemodes/ultimateph/gamemode/cl_ragdoll.lua +++ /dev/null @@ -1,28 +0,0 @@ -local PlayerMeta = FindMetaTable("Player") -local EntityMeta = FindMetaTable("Entity") - -if !PlayerMeta.GetRagdollEntityOld then - PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity -end - -function PlayerMeta:GetRagdollEntity() - local ent = self:GetNWEntity("DeathRagdoll") - if IsValid(ent) then - return ent - end - - return self:GetRagdollEntityOld() -end - -if !EntityMeta.GetRagdollOwnerOld then - EntityMeta.GetRagdollOwnerOld = EntityMeta.GetRagdollOwner -end - -function EntityMeta:GetRagdollOwner() - local ent = self:GetNWEntity("RagdollOwner") - if IsValid(ent) then - return ent - end - - return self:GetRagdollOwnerOld() -end diff --git a/gamemodes/ultimateph/gamemode/cl_rounds.lua b/gamemodes/ultimateph/gamemode/cl_rounds.lua deleted file mode 100644 index 5047077..0000000 --- a/gamemodes/ultimateph/gamemode/cl_rounds.lua +++ /dev/null @@ -1,56 +0,0 @@ -GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT -GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() - -function GM:GetGameState() - return self.GameState -end - -function GM:GetStateStart() - return self.StateStart -end - -function GM:GetStateRunningTime() - return CurTime() - self.StateStart -end - -net.Receive("gamestate", function(len) - GAMEMODE.GameState = net.ReadUInt(32) - GAMEMODE.StateStart = net.ReadDouble() - - if GAMEMODE.GameState == ROUND_HIDE then - GAMEMODE.UpgradesNotif = {} - GAMEMODE.ClearKillFeed() - end - - if GAMEMODE.GameState != ROUND_SEEK then - GAMEMODE:CloseEndRoundMenu() - end -end) - -net.Receive("round_victor", function(len) - local tab = {} - tab.winningTeam = net.ReadUInt(8) - tab.playerAwards = net.ReadTable() - - -- open the results panel - timer.Create("ph_timer_show_results_delay", 2, 1, function() - GAMEMODE:EndRoundMenuResults(tab) - end) -end) - -net.Receive("gamerules", function() - local settings = {} - while net.ReadUInt(8) != 0 do - local k = net.ReadString() - local t = net.ReadUInt(8) - local v = net.ReadType(t) - settings[k] = v - end - - GAMEMODE.RoundSettings = settings -end) - -function GM:GetRoundSettings() - self.RoundSettings = self.RoundSettings || {} - return self.RoundSettings -end diff --git a/gamemodes/ultimateph/gamemode/cl_spectate.lua b/gamemodes/ultimateph/gamemode/cl_spectate.lua deleted file mode 100644 index 2430c26..0000000 --- a/gamemodes/ultimateph/gamemode/cl_spectate.lua +++ /dev/null @@ -1,22 +0,0 @@ -net.Receive("spectating_status", function(length) - GAMEMODE.SpectateMode = net.ReadInt(8) - GAMEMODE.Spectating = false - GAMEMODE.Spectatee = nil - if GAMEMODE.SpectateMode >= 0 then - GAMEMODE.Spectating = true - GAMEMODE.Spectatee = net.ReadEntity() - end - -end) - -function GM:IsCSpectating() - return self.Spectating -end - -function GM:GetCSpectatee() - return self.Spectatee -end - -function GM:GetCSpectateMode() - return self.SpectateMode -end diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua deleted file mode 100644 index 9b917ba..0000000 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ /dev/null @@ -1,254 +0,0 @@ -include("sh_taunt.lua") -include("cl_colors.lua") - -local menu -local lastCursorX -local lastCursorY - -local function saveCursor() - lastCursorX, lastCursorY = input.GetCursorPos() -end - -local function restoreCursor() - if !lastCursorX then return end - - input.SetCursorPos(lastCursorX, lastCursorY) - lastCursorX = nil - lastCursorY = nil -end - -local function fillList(mlist, taunts, cat) - menu.CurrentTaunts = taunts - menu.CurrentTauntCat = cat - for k, v in pairs(menu.CatList:GetCanvas():GetChildren()) do - v.Selected = false - if v.CatName == cat then - v.Selected = true - end - end - - mlist:Clear() - for k, t in pairs(taunts) do - if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end - - local but = vgui.Create("DButton") - but:SetTall(draw.GetFontHeight("RobotoHUD-L15") * 1.0) - but:SetText("") - - function but:Paint(w, h) - if self:IsDown() then - col = PHLesserWhite - elseif self:IsHovered() then - col = PHLesserWhite - else - col = PHWhite - end - draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, 0, 1) - draw.ShadowText(math.Round(t.soundDuration * 10) / 10 .. "s", "RobotoHUD-L10", w, h / 2, col, 2, 1) - end - - function but:DoClick() - RunConsoleCommand("ph_taunt", t.sound[math.random(#t.sound)]) - saveCursor() - menu:Close() - end - - mlist:AddItem(but) - end -end - -local function addCat(clist, name, taunts, mlist) - local dname = name:lower():gsub("[_]", " ") - dname = dname:sub(1, 1):upper() .. dname:sub(2) - - local but = vgui.Create("DButton") - but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - but:SetText("") - but.Selected = false - but.CatName = name - - function but:Paint(w, h) - local colt = PHWhite - if !self.Selected then - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - if self:IsDown() then - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - elseif self:IsHovered() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - end - else - col = Color(PHDark.r, PHDark.g, PHDark.b) - end - - draw.RoundedBoxEx(4, 0, 0, w, h, col, true, false, true, false) - draw.ShadowText(dname, "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) - end - - function but:DoClick() - fillList(mlist, taunts, name) - self.Selected = true - end - - clist:AddItem(but) - return but -end - -local function fillCats(clist, mlist) - clist:Clear() - local all = addCat(clist, "all", Taunts, mlist) - for k, taunts in pairs(TauntCategories) do - local c = 0 - for a, t in pairs(taunts) do - if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end - - c = c + 1 - end - - if c > 0 then - addCat(clist, k, taunts, mlist) - end - end - - all.Selected = true -end - -local function openTauntMenu() - restoreCursor() - if IsValid(menu) then - fillCats(menu.CatList, menu.TauntList) - fillList(menu.TauntList, menu.CurrentTaunts, menu.CurrentTauntCat) - if menu:IsVisible() then - saveCursor() - end - - menu:SetVisible(!menu:IsVisible()) - return - end - - menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) - menu:Center() - menu:SetTitle("") - menu:MakePopup() - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - menu:SetDraggable(false) - menu:ShowCloseButton(false) - menu:DockPadding(8, 8 + draw.GetFontHeight("RobotoHUD-25"), 8, 8) - - local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('marlett') - closeButton:SetText('r') - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - closeButton.OnCursorEntered = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) - end - end - closeButton.OnCursorExited = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - end - closeButton:SetColor(PHWhite) - closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) - closeButton:SetPos(menu:GetWide() / 1.05, 0) - closeButton.DoClick = function() - menu:Close() - end - - function menu:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawRect(0, 0, w, h) - surface.SetFont("RobotoHUD-25") - local t = "Taunts" - local tw, th = surface.GetTextSize(t) - draw.SimpleText(t, "RobotoHUD-25", 8, 2, PHBlue, 0) - draw.SimpleText(TauntMenuPhrase, "RobotoHUD-L15", 8 + tw + 16, 2 + th * 0.90, PHWhite, 0, 4) - end - - local leftpnl = vgui.Create("DPanel", menu) - leftpnl:Dock(LEFT) - leftpnl:DockMargin(0, 0, 0, 0) - leftpnl:SetWide(menu:GetWide() * 0.3) - function leftpnl:Paint(w, h) - end - - local but = vgui.Create("DButton", leftpnl) - but:Dock(BOTTOM) - but:DockMargin(0, 4, 4, 0) - but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - but:SetText("") - - function but:Paint(w, h) - local colt = PHWhite - if self:IsDown() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - elseif self:IsHovered() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - else - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - end - - draw.RoundedBoxEx(4, 0, 0, w, h, col, true, true, true, true) - draw.ShadowText("Random", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) - end - - function but:DoClick() - RunConsoleCommand("ph_taunt_random") - saveCursor() - menu:Close() - end - - local clist = vgui.Create("DScrollPanel", leftpnl) - menu.CatList = clist - clist:Dock(FILL) - clist:DockMargin(0, 0, 0, 0) - - function clist:Paint(w, h) - end - - local canvas = clist:GetCanvas() - canvas:DockPadding(0, 0, 0, 0) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) - end - - local mlist = vgui.Create("DScrollPanel", menu) - menu.TauntList = mlist - mlist:Dock(FILL) - - function mlist:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) - surface.DrawRect(1, 1, w - 2, h - 2) - end - - -- child positioning - local canvas = mlist:GetCanvas() - canvas:DockPadding(8, 8, 8, 8) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) - end - - fillList(mlist, Taunts) - fillCats(clist, mlist, "all") -end - -concommand.Add("ph_menu_taunt", openTauntMenu) -net.Receive("open_taunt_menu", openTauntMenu) - -net.Receive("ph_set_taunt_menu_phrase", function() - value_new = net.ReadString() - - -- Prevent this from being done more than once. GMod is weird. - if TauntMenuPhrase == value_new then return end - TauntMenuPhrase = value_new -end) diff --git a/gamemodes/ultimateph/gamemode/cl_killfeed.lua b/gamemodes/ultimateph/gamemode/cl_utils.lua similarity index 53% rename from gamemodes/ultimateph/gamemode/cl_killfeed.lua rename to gamemodes/ultimateph/gamemode/cl_utils.lua index b2c39ab..ce2ea02 100644 --- a/gamemodes/ultimateph/gamemode/cl_killfeed.lua +++ b/gamemodes/ultimateph/gamemode/cl_utils.lua @@ -1,3 +1,148 @@ +-- former cl_aimlaser.lua +local beamMat = Material("ultimateph/aim_laser") +local ray_color = Color(50,170,46) +local ray_width = 6 + +local dotMat = Material("ultimateph/aim_dot") +local dot_color = Color(255,255,255) + +local hunters_aim = {} + +function GM:PostDrawTranslucentRenderables() + hunters_aim = {} + self:GetHuntersAim() + + -- When "ph_hunter_aim_laser" set to 0, nobody can see the beam + -- set to 1 == spectator only, set to 2 == props and spectator + -- Also, we don't want the hunters to see the ray, to prevent eventual visual bugs + local visibility = GetConVar("ph_hunter_aim_laser"):GetInt() + + if visibility == 0 then return end + + if visibility == 1 and not LocalPlayer():IsSpectator() then return end + if visibility == 2 and not (LocalPlayer():IsSpectator() or LocalPlayer():IsProp()) then return end + + for _, h_aim in pairs(hunters_aim) do + render.SetMaterial(beamMat) + render.DrawBeam(h_aim.eye_pos, h_aim.looking.HitPos, ray_width, 0, 1, ray_color) + + render.SetMaterial(dotMat) + local size = math.random(8, 16) + render.DrawQuadEasy(h_aim.looking.HitPos + h_aim.looking.HitNormal, h_aim.looking.HitNormal, size, size, dot_color, 0) + end +end + +function GM:GetHuntersAim() + for _, ply in pairs(player.GetAll()) do + if ply:IsHunter() then + local t = {} + t.eye_pos = ply:EyePos() + t.looking = ply:GetEyeTrace() + table.insert(hunters_aim, t) + end + end +end + +-- former cl_chatmsg.lua +net.Receive("ph_chatmsg", function(len) + local tbl = net.ReadTable() + chat.AddText(unpack(tbl)) +end) + +-- former cl_fixplayercolor +local EntityMeta = FindMetaTable("Entity") + +function EntityMeta:GetPlayerColor() + return self:GetNWVector("playerColor") || Vector() +end + +matproxy.Add({ + name = "PlayerColor", + init = function(self, mat, values) + -- Store the name of the variable we want to set + self.ResultTo = values.resultvar + end, + bind = function(self, mat, ent) + if !IsValid(ent) then return end + + if ent.GetPlayerColorOverride then -- clientside entities can't override functions, so we need an additional one for it + local col = ent:GetPlayerColorOverride() + if isvector(col) then + mat:SetVector(self.ResultTo, col) + end + elseif ent.GetPlayerColor then + local col = ent:GetPlayerColor() + if isvector(col) then + mat:SetVector(self.ResultTo, col) + end + else + mat:SetVector(self.ResultTo, Vector(62.0 / 255.0, 88.0 / 255.0, 106.0 / 255.0)) + end + end +}) + +-- former cl_health +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:GetHMaxHealth() + return self:GetNWFloat("HMaxHealth", 100) || 100 +end + +-- former cl_spectate.lua +net.Receive("spectating_status", function(length) + GAMEMODE.SpectateMode = net.ReadInt(8) + GAMEMODE.Spectating = false + GAMEMODE.Spectatee = nil + if GAMEMODE.SpectateMode >= 0 then + GAMEMODE.Spectating = true + GAMEMODE.Spectatee = net.ReadEntity() + end + +end) + +function GM:IsCSpectating() + return self.Spectating +end + +function GM:GetCSpectatee() + return self.Spectatee +end + +function GM:GetCSpectateMode() + return self.SpectateMode +end + +-- formar cl_ragdoll.lua +local PlayerMeta = FindMetaTable("Player") +local EntityMeta = FindMetaTable("Entity") + +if !PlayerMeta.GetRagdollEntityOld then + PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity +end + +function PlayerMeta:GetRagdollEntity() + local ent = self:GetNWEntity("DeathRagdoll") + if IsValid(ent) then + return ent + end + + return self:GetRagdollEntityOld() +end + +if !EntityMeta.GetRagdollOwnerOld then + EntityMeta.GetRagdollOwnerOld = EntityMeta.GetRagdollOwner +end + +function EntityMeta:GetRagdollOwner() + local ent = self:GetNWEntity("RagdollOwner") + if IsValid(ent) then + return ent + end + + return self:GetRagdollOwnerOld() +end + +--former cl_killfeed.lua local killFeedEvents = {} local KILL_FEED_MESSAGE_TIMEOUT = 10 -- How long a message stays in the kill feed before starting to fade away. diff --git a/gamemodes/ultimateph/gamemode/init.lua b/gamemodes/ultimateph/gamemode/init.lua index 7194290..c901907 100644 --- a/gamemodes/ultimateph/gamemode/init.lua +++ b/gamemodes/ultimateph/gamemode/init.lua @@ -1,4 +1,4 @@ -AddCSLuaFile("shared.lua") +AddCSLuaFile("sh_init.lua") local rootFolder = (GM || GAMEMODE).Folder:sub(11) .. "/gamemode/" @@ -14,23 +14,14 @@ util.AddNetworkString("clientIPE") util.AddNetworkString("ph_openhelpmenu") util.AddNetworkString("player_model_sex") -include("sv_chatmsg.lua") -include("shared.lua") -include("sv_ragdoll.lua") -include("sv_playercolor.lua") include("sv_player.lua") -include("sv_realism.lua") -include("sv_rounds.lua") -include("sv_spectate.lua") -include("sv_respawn.lua") -include("sv_health.lua") -include("sv_killfeed.lua") -include("sv_disguise.lua") include("sv_teams.lua") -include("sv_taunt.lua") -include("sv_mapvote.lua") -include("sv_bannedmodels.lua") -include("sv_version.lua") +include("sv_utils.lua") +include("sh_rounds.lua") +include("sh_disguise.lua") +include("sh_taunt.lua") +include("sh_bannedmodels.lua") +include("sh_mapvote.lua") include("sh_init.lua") resource.AddFile("materials/husklesph/skull.png") diff --git a/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua b/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua new file mode 100644 index 0000000..b94cd94 --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua @@ -0,0 +1,261 @@ +if SERVER then +-- former sv_bannedmodels.lua + GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. + + util.AddNetworkString("ph_bannedmodels_getall") + util.AddNetworkString("ph_bannedmodels_add") + util.AddNetworkString("ph_bannedmodels_remove") + + function GM:IsModelBanned(model) + return self.BannedModels[model] == true + end + + function GM:AddBannedModel(model) + if self.BannedModels[model] == true then return end + + self.BannedModels[model] = true + self:SaveBannedModels() + end + + function GM:RemoveBannedModel(model) + if self.BannedModels[model] != true then return end + + self.BannedModels[model] = nil + self:SaveBannedModels() + end + + function GM:SaveBannedModels() + -- ensure the folders are there + if !file.Exists("ultimateph/", "DATA") then + file.CreateDir("ultimateph") + end + + local txt = "" + for key, value in pairs(self.BannedModels) do + if value then + txt = txt .. key .. "\r\n" + end + end + + file.Write("ultimateph/bannedmodels.txt", txt) + end + + function GM:LoadBannedModels() + local bannedModels = file.Read("ultimateph/bannedmodels.txt", "DATA") + if bannedModels then + for match in bannedModels:gmatch("[^\r\n]+") do + self:AddBannedModel(match) + end + end + end + + net.Receive("ph_bannedmodels_getall", function(len, ply) + net.Start("ph_bannedmodels_getall") + + for key, value in pairs(GAMEMODE.BannedModels) do + if value then + net.WriteString(key) + end + end + + net.WriteString("") + net.Send(ply) + end) + + net.Receive("ph_bannedmodels_add", function(len, ply) + if !ply:IsAdmin() then return end + + local model = net.ReadString() + if model == "" then return end + + GAMEMODE:AddBannedModel(model) + net.Start("ph_bannedmodels_add") + net.WriteString(model) + net.Broadcast() + end) + + net.Receive("ph_bannedmodels_remove", function(len, ply) + if !ply:IsAdmin() then return end + + local model = net.ReadString() + if model == "" then return end + + GAMEMODE:RemoveBannedModel(model) + net.Start("ph_bannedmodels_remove") + net.WriteString(model) + net.Broadcast() + end) +end + +if CLIENT then +-- former cl_bannedmodels.lua + GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. + local menu + + function GM:IsModelBanned(model) + return self.BannedModels[model] == true + end + + function GM:AddBannedModel(model) + if self.BannedModels[model] == true then return end + + self.BannedModels[model] = true + menu.AddModel(model) + end + + function GM:RemoveBannedModel(model) + if self.BannedModels[model] != true then return end + + self.BannedModels[model] = nil + menu.RemoveModel(model) + end + + net.Receive("ph_bannedmodels_getall", function(len) + GAMEMODE.BannedModels = {} + + local model = net.ReadString() + while model != "" do + GAMEMODE:AddBannedModel(model) + model = net.ReadString() + end + end) + + net.Receive("ph_bannedmodels_add", function(len) + local model = net.ReadString() + GAMEMODE:AddBannedModel(model) + end) + + net.Receive("ph_bannedmodels_remove", function(len) + local model = net.ReadString() + GAMEMODE:RemoveBannedModel(model) + end) + + concommand.Add("ph_bannedmodels_menu", function(client) + menu:SetVisible(true) + end) + + -- This is all the code to create the banned models menu. + function GM:CreateBannedModelsMenu() + -- The main window that will contain all of the functionality for display, adding, and + -- removing banned models. + menu = vgui.Create("DFrame") + menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) + menu:SetTitle("Banned Models") + menu:Center() + menu:SetDraggable(true) + menu:ShowCloseButton(true) + menu:MakePopup() + menu:SetDeleteOnClose(false) + menu:SetVisible(false) + + -- Create a text box at the top of the window so the player can input new banned models. + local entry = vgui.Create("DTextEntry", menu) + entry:Dock(TOP) + if LocalPlayer():IsAdmin() then + entry:SetPlaceholderText("Hover for usage information.") + entry:SetTooltip([[ + To ban a model from usage put the path + to the model into this text box and + press Enter. + EXAMPLE INPUTS + models/props/cs_assault/money.mdl + models/props_borealis/bluebarrel001.mdl + models/props/cs_office/projector_remote.mdl]]) + else + entry:SetPlaceholderText("You must be an admin to ban models.") + entry:SetTooltip("You must be an admin to ban models.") + entry:SetEnabled(false) + end + + function entry:OnEnter() + -- This client side check is for informational purposes. + if !LocalPlayer():IsAdmin() then + chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") + return + end + + local modelToBan = entry:GetText() + -- This client side check is for informational purposes. + if modelToBan == "" then + chat.AddText(Color(255, 50, 50), "Error when attempting to ban model: no input text was given.") + return + end + -- This client side check is for informational purposes. + if GAMEMODE:IsModelBanned(modelToBan) then + chat.AddText(Color(255, 50, 50), "That model is already banned.") + return + end + + net.Start("ph_bannedmodels_add") + net.WriteString(modelToBan) + net.SendToServer() + entry:SetText("") + end + + -- Makes a scroll bar on the right side in the event that there are a LOT of + -- banned models and we need to be able to scroll. Will not appear unless + -- there are enough items in the list to require it. + local scrollPanel = vgui.Create("DScrollPanel", menu) + scrollPanel:Dock(FILL) + + local modelIconWidth = 128 + local modelIconHeight = 128 + local usableWidthForModelIcons = menu:GetWide() - scrollPanel:GetVBar():GetWide() -- Don't want icons to overlap the scroll bar. + local numCols = math.floor(usableWidthForModelIcons / modelIconWidth) + -- This offset is almost right, but there's 10 pixels more on the left than on the right and I can't figure out why. :( + local leftOffset = (usableWidthForModelIcons - (modelIconWidth * numCols)) / 2 + + -- This is the grid that will give the icons a nice layout. + local grid = vgui.Create("DGrid", scrollPanel) + menu.grid = grid + grid:SetCols(numCols) + grid:SetPos(leftOffset, 10) + grid:SetColWide(modelIconWidth) + grid:SetRowHeight(modelIconHeight) + + -- Allows functions outside of the menu to update the icons. Useful for updating the menu + -- live if it's open when a net message is received to add a model. + menu.AddModel = function(model) + local modelIcon = vgui.Create("SpawnIcon") + modelIcon:SetPos(75, 75) + modelIcon:SetSize(modelIconWidth, modelIconHeight) + modelIcon:SetEnabled(false) + modelIcon:SetCursor("arrow") + modelIcon:SetModel(model) + grid:AddItem(modelIcon) + + local unbanButton = vgui.Create("DButton", modelIcon) + unbanButton:SetSize(modelIconWidth, modelIconHeight / 4) + unbanButton:SetText("Unban Model") + unbanButton:SetPos(0, modelIconHeight - unbanButton:GetTall()) + unbanButton:SetVisible(false) + + function unbanButton:DoClick() + -- This client side check is for informational purposes. + if !LocalPlayer():IsAdmin() then + chat.AddText(Color(255, 50, 50), "You must be an admin to edit the banned models list.") + return + end + + net.Start("ph_bannedmodels_remove") + net.WriteString(self:GetParent():GetModelName()) + net.SendToServer() + end + + -- Logic for showing/hiding the unban button. + function modelIcon:Think() + self:GetChild(1):SetVisible(LocalPlayer():IsAdmin() && (self:IsHovered() || self:IsChildHovered())) + end + end + + -- Same as menu.AddModel but for removing models from the grid. + menu.RemoveModel = function(model) + for _, value in pairs(menu.grid:GetItems()) do + if value:GetModelName() == model then + menu.grid:RemoveItem(value) + break + end + end + end + end +end diff --git a/gamemodes/ultimateph/gamemode/sh_disguise.lua b/gamemodes/ultimateph/gamemode/sh_disguise.lua index 56c40e4..58dc10d 100644 --- a/gamemodes/ultimateph/gamemode/sh_disguise.lua +++ b/gamemodes/ultimateph/gamemode/sh_disguise.lua @@ -159,3 +159,241 @@ hook.Add('UpdateAnimation', 'PropUndisguiseLock', function(ply) ply:SetRenderAngles(ang) end) + +if CLIENT then +-- former cl_disguise.lua + local PlayerMeta = FindMetaTable("Player") + + function PlayerMeta:IsDisguised() + return self:GetNWBool("disguised", false) + end + + local function renderDis(self) + for k, ply in pairs(player.GetAll()) do + if ply:Alive() && ply:IsDisguised() then + local model = ply:GetNWString("disguiseModel") + if model && model != "" then + local ent = ply:GetNWEntity("disguiseEntity") + if IsValid(ent) then + local mins = ply:GetNWVector("disguiseMins") + local maxs = ply:GetNWVector("disguiseMaxs") + local ang = ply:EyeAngles() + ang.p = 0 + ang.r = 0 + if ply:DisguiseRotationLocked() then + ang.y = ply:GetNWFloat("disguiseRotationLockYaw") + end + local pos = ply:GetPos() + Vector(0, 0, -mins.z) + local center = (maxs + mins) / 2 + center.z = 0 + center:Rotate(ang) + ent:SetPos(pos - center) + ent:SetAngles(ang) + ent:SetSkin(ply:GetNWInt("disguiseSkin", 1)) + end + end + end + end + end + + function GM:RenderDisguises() + cam.Start3D(EyePos(), EyeAngles()) + local b, err = pcall(renderDis, self) + cam.End3D() + if !b then + MsgC(Color(255, 0, 0), err .. "\n") + end + end + + function GM:RenderDisguiseHalo() + local client = LocalPlayer() + if client:IsProp() then + local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(client) + if canDisguise then + local col = Color(50, 220, 50) + local hullxy, hullz = target:GetPropSize() + if !client:CanFitHull(hullxy, hullxy, hullz) then + col = Color(220, 50, 50) + end + halo.Add({target}, col, 2, 2, 2, true, true) + end + + local tab = {} + for k, ply in pairs(player.GetAll()) do + if ply != client && ply:IsProp() && ply:IsDisguised() then + if IsValid(ply.PropMod) then + table.insert(tab, ply.PropMod) + end + end + end + halo.Add(tab, team.GetColor(TEAM_PROP), 2, 2, 2, true, false) + end + end +end + +if SERVER then +-- former sv_disguise.lua + local PlayerMeta = FindMetaTable("Player") + + function GM:PlayerDisguise(ply) + local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(ply) + if canDisguise then + if ply.LastDisguise && ply.LastDisguise + 1 > CurTime() then + return + end + + ply:DisguiseAsProp(target) + end + end + + function PlayerMeta:DisguiseAsProp(ent) + local hullxy, hullz = ent:GetPropSize() + if !self:CanFitHull(hullxy, hullxy, hullz) then + self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to change") + return + end + + if !self:IsDisguised() then + self.OldPlayerModel = self:GetModel() + end + + self:Flashlight(false) + + -- create an entity for the disguise + -- we can't use a clientside entity as it needs a shadow + local dent = self:GetNWEntity("disguiseEntity") + if !IsValid(dent) then + dent = ents.Create("ph_disguise") + self:SetNWEntity("disguiseEntity", dent) + dent.PropOwner = self + dent:SetPos(self:GetPos()) + dent:Spawn() + end + dent:SetModel(ent:GetModel()) + + self:SetNWBool("disguised", true) + self:SetNWString("disguiseModel", ent:GetModel()) + self:SetNWVector("disguiseMins", ent:OBBMins()) + self:SetNWVector("disguiseMaxs", ent:OBBMaxs()) + self:SetNWInt("disguiseSkin", ent:GetSkin()) + self:SetNWBool("disguiseRotationLock", false) + self:SetColor(Color(255, 0, 0, 0)) + self:SetRenderMode(RENDERMODE_NONE) + self:SetModel(ent:GetModel()) + self:SetNoDraw(false) + self:DrawShadow(false) + GAMEMODE:PlayerSetNewHull(self, hullxy, hullz, hullz) + + local maxHealth = 1 + local volume = 1 + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + maxHealth = math.Clamp(math.Round(phys:GetVolume() / 230), 1, 200) + volume = phys:GetVolume() + end + + self.PercentageHealth = math.min(self:Health() / self:GetHMaxHealth(), self.PercentageHealth || 1) + local per = math.Clamp(self.PercentageHealth * maxHealth, 1, 200) + self:SetHealth(per) + self:SetHMaxHealth(maxHealth) + self:SetNWFloat("disguiseVolume", volume) + + self:CalculateSpeed() + + local offset = Vector(0, 0, ent:OBBMaxs().z - self:OBBMins().z + 10) + self:SetViewOffset(offset) + self:SetViewOffsetDucked(offset) + + self:EmitSound("weapons/bugbait/bugbait_squeeze" .. math.random(1, 3) .. ".wav") + self.LastDisguise = CurTime() + + local eff = EffectData() + eff:SetOrigin(self:GetPos() + Vector(0, 0, 1)) + eff:SetScale(hullxy) + eff:SetMagnitude(hullz) + util.Effect("ph_disguise", eff, true, true) + end + + function PlayerMeta:IsDisguised() + return self:GetNWBool("disguised", false) + end + + function PlayerMeta:UnDisguise() + local dent = self:GetNWEntity("disguiseEntity") + if IsValid(dent) then + dent:Remove() + end + + self.PercentageHealth = nil + self:SetNWBool("disguised", false) + self:SetNWBool("undisguiseRotationLock", false) + self:SetColor(Color(255, 255, 255, 255)) + self:SetNoDraw(false) + self:DrawShadow(true) + self:SetRenderMode(RENDERMODE_NORMAL) + GAMEMODE:PlayerSetNewHull(self) + if self.OldPlayerModel then + self:SetModel(self.OldPlayerModel) + self.OldPlayerModel = nil + end + + self:SetViewOffset(Vector(0, 0, 64)) + self:SetViewOffsetDucked(Vector(0, 0, 28)) + + self:CalculateSpeed() + end + + function PlayerMeta:DisguiseLockRotation() + if !self:IsDisguised() then + local ang = self:GetRenderAngles() + self:SetNWAngle("undisguiseRotationLockAng", ang) + self:SetNWBool("undisguiseRotationLock", true) + return + end + + local mins, maxs = self:CalculateRotatedDisguiseMinsMaxs() + local hullx = math.Round((maxs.x - mins.x) / 2) + local hully = math.Round((maxs.y - mins.y) / 2) + local hullz = math.Round(maxs.z - mins.z) + if !self:CanFitHull(hullx, hully, hullz) then + self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to lock rotation, move into a more open area") + return + end + + local ang = self:EyeAngles() + self:SetNWBool("disguiseRotationLock", true) + self:SetNWFloat("disguiseRotationLockYaw", ang.y) + GAMEMODE:PlayerSetHull(self, hullx, hully, hullz, hullz) + end + + function PlayerMeta:DisguiseUnlockRotation() + if !self:IsDisguised() then + self:SetNWBool("undisguiseRotationLock", false) + return + end + + local maxs = self:GetNWVector("disguiseMaxs") + local mins = self:GetNWVector("disguiseMins") + local hullxy = math.Round(math.Max(maxs.x - mins.x, maxs.y - mins.y) / 2) + local hullz = math.Round(maxs.z - mins.z) + if !self:CanFitHull(hullxy, hullxy, hullz) then + self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to unlock rotation, move into a more open area") + return + end + + self:SetNWBool("disguiseRotationLock", false) + GAMEMODE:PlayerSetHull(self, hullxy, hullxy, hullz, hullz) + end + + concommand.Add("ph_lockrotation", function(ply, com, args) + if !IsValid(ply) or ply:Team() ~= TEAM_PROP then + return + end + + if ply:DisguiseRotationLocked() then + ply:DisguiseUnlockRotation() + else + ply:DisguiseLockRotation() + end + end) +end diff --git a/gamemodes/ultimateph/gamemode/sh_init.lua b/gamemodes/ultimateph/gamemode/sh_init.lua index e247c15..9bbca12 100644 --- a/gamemodes/ultimateph/gamemode/sh_init.lua +++ b/gamemodes/ultimateph/gamemode/sh_init.lua @@ -1,3 +1,98 @@ +-- former shared.lua +include("cl_colors.lua") +local PlayerMeta = FindMetaTable("Player") +local tabFile = file.Read(GM.Folder .. "/ultimateph.txt", "GAME") || "" +local tab = util.KeyValuesToTable(tabFile) + +GM.Name = tab["title"] || "Prop Hunters - Utlimate Edition" +GM.Author = "DataNext, Zikaeroh, MechanicalMind" +-- Credits to waddlesworth for the logo and icon +GM.Email = "N/A" +GM.Website = "N/A" +GM.Version = tab["version"] || "unknown" + +ROUND_WAIT = 1 +ROUND_HIDE = 2 +ROUND_SEEK = 3 +ROUND_POST = 4 +ROUND_MAPVOTE = 5 + +TEAM_SPEC = 1 +TEAM_HUNTER = 2 +TEAM_PROP = 3 + +WIN_NONE = TEAM_SPEC +WIN_HUNTER = TEAM_HUNTER +WIN_PROP = TEAM_PROP + +function PlayerMeta:IsSpectator() return self:Team() == TEAM_SPEC end +function PlayerMeta:IsHunter() return self:Team() == TEAM_HUNTER end +function PlayerMeta:IsProp() return self:Team() == TEAM_PROP end + +GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT + +team.SetUp(TEAM_SPEC, "Spectators", PHWhite, false) -- Setting Joinable to false allows us to use team.BestAutoJoinTeam and have it only include the Hunters/Props teams. +team.SetUp(TEAM_HUNTER, "Hunters", PHBlue) +team.SetUp(TEAM_PROP, "Props", PHRed) + +function GM:GetGameState() + return self.GameState +end + +function GM:PlayerSetNewHull(ply, s, hullz, duckz) + self:PlayerSetHull(ply, s, s, hullz, duckz) +end + +function GM:PlayerSetHull(ply, hullx, hully, hullz, duckz) + hullx = hullx || 16 + hully = hully || 16 + hullz = hullz || 72 + duckz = duckz || hullz / 2 + ply:SetHull(Vector(-hullx, -hully, 0), Vector(hullx, hully, hullz)) + ply:SetHullDuck(Vector(-hullx, -hully, 0), Vector(hullx, hully, duckz)) + + if SERVER then + net.Start("hull_set") + net.WriteEntity(ply) + net.WriteFloat(hullx) + net.WriteFloat(hully) + net.WriteFloat(hullz) + net.WriteFloat(duckz) + net.Broadcast() + -- TODO send on player spawn + end +end + +function GM:EntityEmitSound(t) + if not GetConVar("ph_hunter_deaf_onhiding"):GetBool() or self:GetGameState() ~= ROUND_HIDE then + return + end + + for _, ply in ipairs(player.GetHumans()) do -- ipairs is preferred when working with tables. player.GetHumans() to ignore bots + if not ply:IsHunter() then + continue -- if the player is not a hunter, ignore them + end + + return false + end +end + +function GM:PlayerFootstep( ply, pos, foot, sound, volume, filter ) + if not GetConVar("ph_props_silent_footsteps"):GetBool() or not ply:IsProp() then + return + end + + return true +end + +hook.Add('CalcMainActivity', 'PropTpose', function(ply) + if not GetConVar("ph_props_tpose"):GetBool() or not ply:IsProp() then + return + end + + return ACT_INVALID +end) + -- NOTE: Make sure to sync default changes over to ULX. GM.VoiceHearTeam = CreateConVar("ph_voice_hearotherteam", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Can we hear the voices of opposing teams") GM.VoiceHearDead = CreateConVar("ph_voice_heardead", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Can we hear the voices of dead players and spectators") @@ -41,4 +136,3 @@ GM.RunSpeed = CreateConVar("ph_run_speed", 150, bit.bor(FCVAR_ARCHIVE, FCVAR_NOT GM.JumpPower = CreateConVar("ph_jump_power", 200, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Jump Power") GM.FallDMGMult = CreateConVar("ph_falldmg_mult", 50, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Adjust fall damage") GM.FallDMGNonLethal = CreateConVar("ph_falldmg_nonlethal", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Should fall damage kill") - diff --git a/gamemodes/ultimateph/gamemode/sh_mapvote.lua b/gamemodes/ultimateph/gamemode/sh_mapvote.lua new file mode 100644 index 0000000..578854b --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_mapvote.lua @@ -0,0 +1,314 @@ +if SERVER then +-- former sv_mapvote.lua + + util.AddNetworkString("ph_mapvote") + util.AddNetworkString("ph_mapvotevotes") + + GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 + GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() + + function GM:IsMapVoting() + return self.MapVoting + end + + function GM:GetMapVoteStart() + return self.MapVoteStart + end + + function GM:GetMapVoteRunningTime() + return CurTime() - self.MapVoteStart + end + + function GM:RotateMap() + local map = game.GetMap() + local index + for k, map2 in pairs(self.MapList) do + if map == map2 then + index = k + end + end + + if !index then index = 1 end + index = index + 1 + + if index > #self.MapList then + index = 1 + end + + local nextMap = self.MapList[index] + self:ChangeMapTo(nextMap) + end + + function GM:ChangeMapTo(map) + if map == game.GetMap() then + self.Rounds = 0 + self:SetGameState(ROUND_WAIT) + return + end + + print("[ultimateph] Rotate changing map to " .. map) + GlobalChatMsg("Changing map to ", map) + hook.Call("OnChangeMap", GAMEMODE) + timer.Simple(5, function() + RunConsoleCommand("changelevel", map) + end) + end + + GM.MapList = {} + + local defaultMapList = { + "cs_italy", + "cs_office", + "cs_compound", + "cs_assault" + } + + function GM:SaveMapList() + -- ensure the folders are there + if !file.Exists("ultimateph/", "DATA") then + file.CreateDir("ultimateph") + end + + local txt = "" + for k, map in pairs(self.MapList) do + txt = txt .. map .. "\r\n" + end + + file.Write("ultimateph/maplist.txt", txt) + end + + function GM:LoadMapList() + local jason = file.ReadDataAndContent("ultimateph/maplist.txt") + if jason then + local tbl = {} + for map in jason:gmatch("[^\r\n]+") do + table.insert(tbl, map) + end + + self.MapList = tbl + else + local tbl = {} + + for k, map in pairs(defaultMapList) do + if file.Exists("maps/" .. map .. ".bsp", "GAME") then + table.insert(tbl, map) + end + end + + local files = file.Find("maps/*", "GAME") + for k, v in pairs(files) do + local name = v:match("([^%.]+)%.bsp$") + if name then + if name:sub(1, 3) == "ph_" then + table.insert(tbl, name) + end + end + end + + self.MapList = tbl + self:SaveMapList() + end + + for k, map in pairs(self.MapList) do + local path = "maps/" .. map .. ".png" + if file.Exists(path, "GAME") then + resource.AddSingleFile(path) + else + local path = "maps/thumb/" .. map .. ".png" + if file.Exists(path, "GAME") then + resource.AddSingleFile(path) + end + end + end + end + + function GM:StartMapVote() + -- Check if we're using the MapVote addon. If so, ignore the builtin mapvote logic. + -- MapVote Workshop Link: https://steamcommunity.com/sharedfiles/filedetails/?id=151583504 + local initHookTbl = hook.GetTable().Initialize + if initHookTbl && initHookTbl.MapVoteConfigSetup then + self:SetGameState(ROUND_MAPVOTE) + MapVote.Start() + return + end + + -- allow developers to override builtin mapvote + if hook.GetTable().PHStartMapVote then + self:SetGameState(ROUND_MAPVOTE) + hook.Run("PHStartMapVote") + return + end + + self.MapVoteStart = CurTime() + self.MapVoteTime = 30 + self.MapVoting = true + self.MapVotes = {} + + -- randomise the order of maps so people choose different ones + local maps = {} + for k, v in pairs(self.MapList) do + table.insert(maps, math.random(#maps) + 1, v) + end + + self.MapList = maps + self:SetGameState(ROUND_MAPVOTE) + self:NetworkMapVoteStart() + end + + function GM:MapVoteThink() + if self.MapVoting then + if self:GetMapVoteRunningTime() >= self.MapVoteTime then + self.MapVoting = false + local votes = {} + for ply, map in pairs(self.MapVotes) do + if IsValid(ply) && ply:IsPlayer() then + votes[map] = (votes[map] || 0) + 1 + end + end + + local maxvotes = 0 + for k, v in pairs(votes) do + if v > maxvotes then + maxvotes = v + end + end + + local maps = {} + for k, v in pairs(votes) do + if v == maxvotes then + table.insert(maps, k) + end + end + + if #maps > 0 then + self:ChangeMapTo(table.Random(maps)) + else + GlobalChatMsg("Map change failed, not enough votes") + print("Map change failed, not enough votes") + self:SetGameState(ROUND_WAIT) + end + end + end + end + + function GM:NetworkMapVoteStart(ply) + net.Start("ph_mapvote") + net.WriteFloat(self.MapVoteStart) + net.WriteFloat(self.MapVoteTime) + + for k, map in pairs(self.MapList) do + net.WriteUInt(k, 16) + net.WriteString(map) + end + net.WriteUInt(0, 16) + + if ply then + net.Send(ply) + else + net.Broadcast() + end + + self:NetworkMapVotes() + end + + function GM:NetworkMapVotes(ply) + net.Start("ph_mapvotevotes") + + for k, map in pairs(self.MapVotes) do + net.WriteUInt(1, 8) + net.WriteEntity(k) + net.WriteString(map) + end + net.WriteUInt(0, 8) + + if ply then + net.Send(ply) + else + net.Broadcast() + end + end + + concommand.Add("ph_votemap", function(ply, com, args) + if GAMEMODE.MapVoting then + if #args < 1 then + return + end + + local found + for k, v in pairs(GAMEMODE.MapList) do + if v:lower() == args[1]:lower() then + found = v + break + end + end + + if !found then + ply:ChatPrint("Invalid map " .. args[1]) + return + end + + GAMEMODE.MapVotes[ply] = found + GAMEMODE:NetworkMapVotes() + end + end) +end + +if CLIENT then +-- former cl_mapvote.lua + GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 + GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() + + net.Receive("ph_mapvote", function(len) + GAMEMODE.MapVoteStart = net.ReadFloat() + GAMEMODE.MapVoteTime = net.ReadFloat() + + local mapList = {} + while true do + local k = net.ReadUInt(16) + if k <= 0 then break end + local map = net.ReadString() + table.insert(mapList, map) + end + + GAMEMODE.SelfMapVote = nil + GAMEMODE.MapVotes = {} + GAMEMODE.MapVotesByMap = {} + GAMEMODE.MapList = mapList + + GAMEMODE:EndRoundMapVote() + end) + + net.Receive("ph_mapvotevotes", function(len) + local mapVotes = {} + while true do + local k = net.ReadUInt(8) + if k <= 0 then break end + local ply = net.ReadEntity() + local map = net.ReadString() + mapVotes[ply] = map + end + + GAMEMODE.SelfMapVote = nil + + local byMap = {} + for ply, map in pairs(mapVotes) do + byMap[map] = byMap[map] || {} + table.insert(byMap[map], ply) + + if ply == LocalPlayer() then + GAMEMODE.SelfMapVote = map + end + end + + GAMEMODE.MapVotes = mapVotes + GAMEMODE.MapVotesByMap = byMap + end) + + function GM:GetMapVoteStart() + return self.MapVoteStart + end + + function GM:GetMapVoteRunningTime() + return CurTime() - self.MapVoteStart + end +end diff --git a/gamemodes/ultimateph/gamemode/sh_rounds.lua b/gamemodes/ultimateph/gamemode/sh_rounds.lua new file mode 100644 index 0000000..a67a76f --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_rounds.lua @@ -0,0 +1,393 @@ +if SERVER then +-- former sv_rounds.lua + include("sv_awards.lua") + + util.AddNetworkString("gamestate") + util.AddNetworkString("round_victor") + util.AddNetworkString("gamerules") + util.AddNetworkString("rounds") + + GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT + GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() + GM.Rounds = GAMEMODE && GAMEMODE.Rounds || 0 + + local function mapTimeLimitTimerResult() + if GAMEMODE.Rounds < GAMEMODE.RoundLimit:GetInt() then -- Only change if we haven't already hit the round limit + GAMEMODE.Rounds = GAMEMODE.RoundLimit:GetInt() - 1 -- Allows for 1 extra round before hitting the limit + end + end + + local function changeMapTimeLimitTimer(oldValueMinutes, newValueMinutes) + local timerName = "ph_timer_map_time_limit" + if newValueMinutes == -1 then -- Timer should be disabled + timer.Remove(timerName) + else + local newValueSeconds = math.floor(newValueMinutes) * 60 + + -- If a timer exists then take its elapsed time into account when calculating our new time limit. + if timer.Exists(timerName) then + local oldValueSeconds = math.floor(oldValueMinutes) * 60 + newValueSeconds = newValueSeconds - (oldValueSeconds - timer.TimeLeft(timerName)) + end + + -- Timers don't execute their callback if given a negative time. + -- newValueSeconds will be negative if oldValueMinutes was greater than newValueMinutes. + if newValueSeconds < 0 then + mapTimeLimitTimerResult() + else + -- This will create or update the timer with the new value. + timer.Create(timerName, newValueSeconds, 1, mapTimeLimitTimerResult) + end + end + end + + cvars.AddChangeCallback("ph_map_time_limit", function(convar, oldValue, newValue) + changeMapTimeLimitTimer(tonumber(oldValue), tonumber(newValue)) + end) + + function GM:GetGameState() + return self.GameState + end + + function GM:GetStateStart() + return self.StateStart + end + + function GM:GetStateRunningTime() + return CurTime() - self.StateStart + end + + function GM:GetPlayingPlayers() + local players = {} + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() && ply:GetNWBool("RoundInGame") then + table.insert(players, ply) + end + end + + return players + end + + function GM:SetGameState(state) + self.GameState = state + self.StateStart = CurTime() + self:NetworkGameState() + end + + function GM:NetworkGameState(ply) + net.Start("gamestate") + net.WriteUInt(self.GameState || ROUND_WAIT, 32) + net.WriteDouble(self.StateStart || 0) + net.Broadcast() + end + + function GM:GetRoundSettings() + self.RoundSettings = self.RoundSettings || {} + return self.RoundSettings + end + + function GM:NetworkGameSettings(ply) + net.Start("gamerules") + + if self.RoundSettings then + for k, v in pairs(self.RoundSettings) do + net.WriteUInt(1, 8) + net.WriteString(k) + net.WriteType(v) + end + end + net.WriteUInt(0, 8) + + if ply == nil then + net.Broadcast() + else + net.Send(ply) + end + end + + function GM:SetupRound() + local c = 0 + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() then -- ignore spectators + c = c + 1 + end + end + + if c < 2 then + GlobalChatMsg("Not enough players to start round") + self:SetGameState(ROUND_WAIT) + return + end + + self:BalanceTeams() + + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() then -- ignore spectators + ply:SetNWBool("RoundInGame", true) + ply:KillSilent() + ply:UnCSpectate() + ply:Spawn() + + local col = team.GetColor(ply:Team()) + ply:SetPlayerColor(Vector(col.r / 255, col.g / 255, col.b / 255)) + + if ply:IsHunter() then + ply:Freeze(true) + end + + ply.PropDmgPenalty = 0 + ply.PropMovement = 0 + ply.HunterKills = 0 + ply.TauntAmount = 0 + ply.TauntsUsed = {} + ply.TauntEnd = nil + ply.AutoTauntDeadline = nil + else + ply:SetNWBool("RoundInGame", false) + end + end + + self:CleanupMap() + self.Rounds = self.Rounds + 1 + net.Start("rounds") + net.WriteInt(self.Rounds, 5) + net.Broadcast() + + if self.Rounds == self.RoundLimit:GetInt() then + GlobalChatMsg(Color(255, 0, 0), "This is the LAST ROUND!") + + if self.Secrets:GetBool() then + BroadcastLua("surface.PlaySound('husklesph/hphaaaaa2.mp3')") + end + end + + hook.Run("OnSetupRound") + self:SetGameState(ROUND_HIDE) + end + + function GM:StartRound() + self.LastPropDeath = nil + self.FirstHunterKill = nil + + local hunters, props = 0, 0 + for k, ply in pairs(self:GetPlayingPlayers()) do + ply:Freeze(false) + ply.PropDmgPenalty = 0 + ply.PropMovement = 0 + ply.HunterKills = 0 + ply.TauntAmount = 0 + if ply:IsHunter() then + hunters = hunters + 1 + elseif ply:IsProp() then + props = props + 1 + end + end + + local c = 0 + for k, ent in pairs(ents.GetAll()) do + if ent.IsDisguisableAs && ent:IsDisguisableAs() then + c = c + 1 + end + end + + self.RoundSettings = {} + if self.RoundTime:GetInt() > 0 then + self.RoundSettings.RoundTime = self.RoundTime:GetInt() + else + self.RoundSettings.RoundTime = math.Round((c * 0.5 / hunters + 60 * 4) * math.sqrt(props / hunters)) + end + self.RoundSettings.PropsCamDistanceMult = self.PropsCamDistanceMult:GetFloat() + print("Round time is " .. (self.RoundSettings.RoundTime / 60) .. " (" .. c .. " props)") + self:NetworkGameSettings() + self:SetGameState(ROUND_SEEK) + GlobalChatMsg("Round has started") + end + + function GM:EndRound(winningTeam) + if winningTeam == WIN_NONE then + GlobalChatMsg("Tie everybody loses") + else + GlobalChatMsg(team.GetColor(winningTeam), team.GetName(winningTeam), " win") + end + + self.LastRoundResult = winningTeam + + hook.Run("PH".. winningTeam, ply) + hook.Run("PHEndRound", ply) + + local awards = {} + for awardKey, award in pairs(PlayerAwards) do -- PlayerAwards comes from sv_awards.lua + local result = award.getWinner() + + -- nil values cannot exist in awards otherwise the net.WriteTable below will break + if !result then + continue + elseif type(result) == "Player" && IsValid(result) then + awards[awardKey] = { + name = award.name, + desc = award.desc, + winnerName = result:Nick(), + winnerTeam = result:Team() + } + hook.Run("PHAward", result, award) + else + ErrorNoHalt("ULTIMATEPH WARNING: EndRound Player Award gave non Player object: " .. type(result)) + end + end + + net.Start("round_victor") + net.WriteUInt(winningTeam, 8) + net.WriteTable(awards) + net.Broadcast() + + self.RoundSettings.NextRoundTime = self.PostRoundTime:GetInt() + self:NetworkGameSettings() + self:SetGameState(ROUND_POST) + end + + function GM:RoundsSetupPlayer(ply) + -- start off not participating + ply:SetNWBool("RoundInGame", false) + + -- send game state + self:NetworkGameState(ply) + end + + function GM:CheckForVictory() + -- Check if time limit expired + local settings = self:GetRoundSettings() + local roundTime = settings.RoundTime || 5 * 60 + if self:GetStateRunningTime() > roundTime then + self:EndRound(WIN_PROP) + return + end + + -- Check if there are still living players on either team + local huntersAlive, propsAlive = false, false + for _, ply in pairs(self:GetPlayingPlayers()) do + if !ply:Alive() then continue end + + huntersAlive = huntersAlive || ply:IsHunter() + propsAlive = propsAlive || ply:IsProp() + end + + if !huntersAlive && !propsAlive then + self:EndRound(WIN_NONE) + elseif !huntersAlive then + self:EndRound(WIN_PROP) + elseif !propsAlive then + self:EndRound(WIN_HUNTER) + end + end + + function GM:RoundsThink() + if self:GetGameState() == ROUND_WAIT then + local c = 0 + for k, ply in pairs(player.GetAll()) do + if !ply:IsSpectator() then -- ignore spectators + c = c + 1 + end + end + + if c >= 2 && self.RoundWaitForPlayers + self.StartWaitTime:GetFloat() < CurTime() then + self:SetupRound() + end + elseif self:GetGameState() == ROUND_HIDE then + if self:GetStateRunningTime() > self.HidingTime:GetInt() then + self:StartRound() + end + elseif self:GetGameState() == ROUND_SEEK then + self:CheckForVictory() + for k, ply in pairs(self:GetPlayingPlayers()) do + if ply:IsProp() && ply:Alive() then + ply.PropMovement = (ply.PropMovement || 0) + ply:GetVelocity():Length() + end + end + elseif self:GetGameState() == ROUND_POST then + if self:GetStateRunningTime() > (self.RoundSettings.NextRoundTime || 30) then + if self.RoundLimit:GetInt() > 0 && self.Rounds >= self.RoundLimit:GetInt() then + self:StartMapVote() + else + if self.LastRoundResult != WIN_PROP || !self.PropsWinStayProps:GetBool() then + self:SwapTeams() + end + + self:SetupRound() + end + end + elseif self:GetGameState() == ROUND_MAPVOTE then + self:MapVoteThink() + end + end + + local function ForceEndRound(ply, command, args) + -- ply is nil on dedicated server console + if !IsValid(ply) || ply:IsAdmin() || ply:IsSuperAdmin() || cvars.Bool("sv_cheats", 0) then + GAMEMODE.RoundSettings = GAMEMODE.RoundSettings || {} + GAMEMODE:EndRound(WIN_NONE) + else + ply:PrintMessage(HUD_PRINTCONSOLE, "You must be a GMod Admin or SuperAdmin on the server to use this command, or sv_cheats must be enabled.") + end + end + concommand.Add("ph_endround", ForceEndRound) +end + +if CLIENT then +-- former cl_rounds.lua + GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT + GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() + + function GM:GetGameState() + return self.GameState + end + + function GM:GetStateStart() + return self.StateStart + end + + function GM:GetStateRunningTime() + return CurTime() - self.StateStart + end + + net.Receive("gamestate", function(len) + GAMEMODE.GameState = net.ReadUInt(32) + GAMEMODE.StateStart = net.ReadDouble() + + if GAMEMODE.GameState == ROUND_HIDE then + GAMEMODE.UpgradesNotif = {} + GAMEMODE.ClearKillFeed() + end + + if GAMEMODE.GameState != ROUND_SEEK then + GAMEMODE:CloseEndRoundMenu() + end + end) + + net.Receive("round_victor", function(len) + local tab = {} + tab.winningTeam = net.ReadUInt(8) + tab.playerAwards = net.ReadTable() + + -- open the results panel + timer.Create("ph_timer_show_results_delay", 2, 1, function() + GAMEMODE:EndRoundMenuResults(tab) + end) + end) + + net.Receive("gamerules", function() + local settings = {} + while net.ReadUInt(8) != 0 do + local k = net.ReadString() + local t = net.ReadUInt(8) + local v = net.ReadType(t) + settings[k] = v + end + + GAMEMODE.RoundSettings = settings + end) + + function GM:GetRoundSettings() + self.RoundSettings = self.RoundSettings || {} + return self.RoundSettings + end +end diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index db76539..97830a7 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -164,3 +164,415 @@ function GM:LoadTaunts() end GM:LoadTaunts() + +if SERVER then +-- former sv_taunt.lua + util.AddNetworkString("open_taunt_menu") + + local PlayerMeta = FindMetaTable("Player") + + function PlayerMeta:CanTaunt() + if !self:Alive() then + return false + end + + if self.TauntEnd && self.TauntEnd > CurTime() then + return false + end + + return true + end + + function PlayerMeta:EmitTaunt(filename, durationOverride) + local duration = SoundDuration(filename) + if filename:match("%.mp3$") then + duration = durationOverride || 1 + end + + local sndName = FilenameToSoundname(filename) + + self:EmitSound(sndName) + self.TauntEnd = CurTime() + duration + 0.1 + self.TauntAmount = (self.TauntAmount || 0) + 1 + self.AutoTauntDeadline = nil + + if !self.TauntsUsed then self.TauntsUsed = {} end + self.TauntsUsed[sndName] = true + end + + local function ForEachTaunt(ply, taunts, func) + for k, v in pairs(taunts) do + if !TauntAllowedForPlayer(ply, v) then continue end + + if func(k, v) then return end + end + end + + local function DoTaunt(ply, snd) + if !IsValid(ply) then return end + if !ply:CanTaunt() then return end + + local ats = AllowedTauntSounds[snd] + if !ats then return end + + local t + ForEachTaunt(ply, ats, function(k, v) + t = v + return true + end) + + if !t then + return + end + + ply:EmitTaunt(snd, t.soundDurationOverride) + end + + local function DoRandomTaunt(ply) + if !IsValid(ply) then return end + if !ply:CanTaunt() then return end + + local potential = {} + ForEachTaunt(ply, Taunts, function(k, v) + table.insert(potential, v) + end) + + if #potential == 0 then return end + + local t = potential[math.random(#potential)] + local snd = t.sound[math.random(#t.sound)] + + ply:EmitTaunt(snd, t.soundDurationOverride) + end + + concommand.Add("ph_taunt", function(ply, com, args, full) + DoTaunt(ply, args[1] || "") + end) + + concommand.Add("ph_taunt_random", function(ply, com, args, full) + DoRandomTaunt(ply) + end) + + util.AddNetworkString("ph_set_taunt_menu_phrase") + function GM:SetTauntMenuPhrase(phrase, ply) + net.Start("ph_set_taunt_menu_phrase") + net.WriteString(phrase) + + if ply then + net.Send(ply) + else + net.Broadcast() + end + end + + cvars.AddChangeCallback("ph_taunt_menu_phrase", function(convar_name, value_old, value_new) + (GM || GAMEMODE):SetTauntMenuPhrase(value_new) + end) + + function GM:AutoTauntCheck() + if self.GameState != ROUND_SEEK then return end + + local propsOnly = self.AutoTauntPropsOnly:GetBool() + local minDeadline = self.AutoTauntMin:GetInt() + local maxDeadline = self.AutoTauntMax:GetInt() + local badMinMax = minDeadline <= 0 || maxDeadline <= 0 || minDeadline > maxDeadline + + for i, ply in ipairs(player.GetAll()) do + if propsOnly && !ply:IsProp() then + ply.AutoTauntDeadline = nil + continue + end + + local begin + if ply.AutoTauntDeadline then + local secsLeft = ply.AutoTauntDeadline - CurTime() + if secsLeft > 0 then + continue + end + + if !ply.TauntEnd || CurTime() > ply.AutoTauntDeadline then + DoRandomTaunt(ply) + begin = ply.TauntEnd + end + end + if !begin then begin = CurTime() end + + if badMinMax then continue end + + local delta = math.random(minDeadline, maxDeadline) + ply.AutoTauntDeadline = begin + delta + end + end + + function GM:StartAutoTauntTimer() + timer.Remove("AutoTauntCheck") + local start = self.AutoTauntEnabled:GetBool() + + if start then + timer.Create("AutoTauntCheck", 5, 0, function() + self:AutoTauntCheck() + end) + end + end + + cvars.AddChangeCallback("ph_auto_taunt", function(convar_name, value_old, value_new) + (GM || GAMEMODE):StartAutoTauntTimer() + end) +end + +if CLIENT then +-- former cl_taunt.lua + include("cl_colors.lua") + + local menu + local lastCursorX + local lastCursorY + + local function saveCursor() + lastCursorX, lastCursorY = input.GetCursorPos() + end + + local function restoreCursor() + if !lastCursorX then return end + + input.SetCursorPos(lastCursorX, lastCursorY) + lastCursorX = nil + lastCursorY = nil + end + + local function fillList(mlist, taunts, cat) + menu.CurrentTaunts = taunts + menu.CurrentTauntCat = cat + for k, v in pairs(menu.CatList:GetCanvas():GetChildren()) do + v.Selected = false + if v.CatName == cat then + v.Selected = true + end + end + + mlist:Clear() + for k, t in pairs(taunts) do + if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end + + local but = vgui.Create("DButton") + but:SetTall(draw.GetFontHeight("RobotoHUD-L15") * 1.0) + but:SetText("") + + function but:Paint(w, h) + if self:IsDown() then + col = PHLesserWhite + elseif self:IsHovered() then + col = PHLesserWhite + else + col = PHWhite + end + draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, 0, 1) + draw.ShadowText(math.Round(t.soundDuration * 10) / 10 .. "s", "RobotoHUD-L10", w, h / 2, col, 2, 1) + end + + function but:DoClick() + RunConsoleCommand("ph_taunt", t.sound[math.random(#t.sound)]) + saveCursor() + menu:Close() + end + + mlist:AddItem(but) + end + end + + local function addCat(clist, name, taunts, mlist) + local dname = name:lower():gsub("[_]", " ") + dname = dname:sub(1, 1):upper() .. dname:sub(2) + + local but = vgui.Create("DButton") + but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) + but:SetText("") + but.Selected = false + but.CatName = name + + function but:Paint(w, h) + local colt = PHWhite + if !self.Selected then + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) + if self:IsDown() then + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) + elseif self:IsHovered() then + col = Color(PHDark.r, PHDark.g, PHDark.b) + end + else + col = Color(PHDark.r, PHDark.g, PHDark.b) + end + + draw.RoundedBoxEx(4, 0, 0, w, h, col, true, false, true, false) + draw.ShadowText(dname, "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) + end + + function but:DoClick() + fillList(mlist, taunts, name) + self.Selected = true + end + + clist:AddItem(but) + return but + end + + local function fillCats(clist, mlist) + clist:Clear() + local all = addCat(clist, "all", Taunts, mlist) + for k, taunts in pairs(TauntCategories) do + local c = 0 + for a, t in pairs(taunts) do + if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end + + c = c + 1 + end + + if c > 0 then + addCat(clist, k, taunts, mlist) + end + end + + all.Selected = true + end + + local function openTauntMenu() + restoreCursor() + if IsValid(menu) then + fillCats(menu.CatList, menu.TauntList) + fillList(menu.TauntList, menu.CurrentTaunts, menu.CurrentTauntCat) + if menu:IsVisible() then + saveCursor() + end + + menu:SetVisible(!menu:IsVisible()) + return + end + + menu = vgui.Create("DFrame") + menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) + menu:Center() + menu:SetTitle("") + menu:MakePopup() + menu:SetKeyboardInputEnabled(false) + menu:SetDeleteOnClose(false) + menu:SetDraggable(false) + menu:ShowCloseButton(false) + menu:DockPadding(8, 8 + draw.GetFontHeight("RobotoHUD-25"), 8, 8) + + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('marlett') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + closeButton.OnCursorEntered = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) + end + end + closeButton.OnCursorExited = function() + closeButton.Paint = function(s,w,h) + draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) + closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton.DoClick = function() + menu:Close() + end + + function menu:Paint(w, h) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + surface.DrawRect(0, 0, w, h) + surface.SetFont("RobotoHUD-25") + local t = "Taunts" + local tw, th = surface.GetTextSize(t) + draw.SimpleText(t, "RobotoHUD-25", 8, 2, PHBlue, 0) + draw.SimpleText(TauntMenuPhrase, "RobotoHUD-L15", 8 + tw + 16, 2 + th * 0.90, PHWhite, 0, 4) + end + + local leftpnl = vgui.Create("DPanel", menu) + leftpnl:Dock(LEFT) + leftpnl:DockMargin(0, 0, 0, 0) + leftpnl:SetWide(menu:GetWide() * 0.3) + function leftpnl:Paint(w, h) + end + + local but = vgui.Create("DButton", leftpnl) + but:Dock(BOTTOM) + but:DockMargin(0, 4, 4, 0) + but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) + but:SetText("") + + function but:Paint(w, h) + local colt = PHWhite + if self:IsDown() then + col = Color(PHDark.r, PHDark.g, PHDark.b) + elseif self:IsHovered() then + col = Color(PHDark.r, PHDark.g, PHDark.b) + else + col = Color(PHDarker.r, PHDarker.g, PHDarker.b) + end + + draw.RoundedBoxEx(4, 0, 0, w, h, col, true, true, true, true) + draw.ShadowText("Random", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) + end + + function but:DoClick() + RunConsoleCommand("ph_taunt_random") + saveCursor() + menu:Close() + end + + local clist = vgui.Create("DScrollPanel", leftpnl) + menu.CatList = clist + clist:Dock(FILL) + clist:DockMargin(0, 0, 0, 0) + + function clist:Paint(w, h) + end + + local canvas = clist:GetCanvas() + canvas:DockPadding(0, 0, 0, 0) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, 4) + end + + local mlist = vgui.Create("DScrollPanel", menu) + menu.TauntList = mlist + mlist:Dock(FILL) + + function mlist:Paint(w, h) + surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) + surface.DrawOutlinedRect(0, 0, w, h) + surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) + surface.DrawRect(1, 1, w - 2, h - 2) + end + + -- child positioning + local canvas = mlist:GetCanvas() + canvas:DockPadding(8, 8, 8, 8) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, 4) + end + + fillList(mlist, Taunts) + fillCats(clist, mlist, "all") + end + + concommand.Add("ph_menu_taunt", openTauntMenu) + net.Receive("open_taunt_menu", openTauntMenu) + + net.Receive("ph_set_taunt_menu_phrase", function() + value_new = net.ReadString() + + -- Prevent this from being done more than once. GMod is weird. + if TauntMenuPhrase == value_new then return end + TauntMenuPhrase = value_new + end) +end diff --git a/gamemodes/ultimateph/gamemode/shared.lua b/gamemodes/ultimateph/gamemode/shared.lua deleted file mode 100644 index f92d3de..0000000 --- a/gamemodes/ultimateph/gamemode/shared.lua +++ /dev/null @@ -1,93 +0,0 @@ -include("cl_colors.lua") -local PlayerMeta = FindMetaTable("Player") -local tabFile = file.Read(GM.Folder .. "/ultimateph.txt", "GAME") || "" -local tab = util.KeyValuesToTable(tabFile) - -GM.Name = tab["title"] || "Prop Hunters - Utlimate Edition" -GM.Author = "DataNext, Zikaeroh, MechanicalMind" --- Credits to waddlesworth for the logo and icon -GM.Email = "N/A" -GM.Website = "N/A" -GM.Version = tab["version"] || "unknown" - -ROUND_WAIT = 1 -ROUND_HIDE = 2 -ROUND_SEEK = 3 -ROUND_POST = 4 -ROUND_MAPVOTE = 5 - -TEAM_SPEC = 1 -TEAM_HUNTER = 2 -TEAM_PROP = 3 - -WIN_NONE = TEAM_SPEC -WIN_HUNTER = TEAM_HUNTER -WIN_PROP = TEAM_PROP - -function PlayerMeta:IsSpectator() return self:Team() == TEAM_SPEC end -function PlayerMeta:IsHunter() return self:Team() == TEAM_HUNTER end -function PlayerMeta:IsProp() return self:Team() == TEAM_PROP end - -GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT - -team.SetUp(TEAM_SPEC, "Spectators", PHWhite, false) -- Setting Joinable to false allows us to use team.BestAutoJoinTeam and have it only include the Hunters/Props teams. -team.SetUp(TEAM_HUNTER, "Hunters", PHBlue) -team.SetUp(TEAM_PROP, "Props", PHRed) - -function GM:GetGameState() - return self.GameState -end - -function GM:PlayerSetNewHull(ply, s, hullz, duckz) - self:PlayerSetHull(ply, s, s, hullz, duckz) -end - -function GM:PlayerSetHull(ply, hullx, hully, hullz, duckz) - hullx = hullx || 16 - hully = hully || 16 - hullz = hullz || 72 - duckz = duckz || hullz / 2 - ply:SetHull(Vector(-hullx, -hully, 0), Vector(hullx, hully, hullz)) - ply:SetHullDuck(Vector(-hullx, -hully, 0), Vector(hullx, hully, duckz)) - - if SERVER then - net.Start("hull_set") - net.WriteEntity(ply) - net.WriteFloat(hullx) - net.WriteFloat(hully) - net.WriteFloat(hullz) - net.WriteFloat(duckz) - net.Broadcast() - -- TODO send on player spawn - end -end - -function GM:EntityEmitSound(t) - if not GetConVar("ph_hunter_deaf_onhiding"):GetBool() or self:GetGameState() ~= ROUND_HIDE then - return - end - - for _, ply in ipairs(player.GetHumans()) do -- ipairs is preferred when working with tables. player.GetHumans() to ignore bots - if not ply:IsHunter() then - continue -- if the player is not a hunter, ignore them - end - - return false - end -end - -function GM:PlayerFootstep( ply, pos, foot, sound, volume, filter ) - if not GetConVar("ph_props_silent_footsteps"):GetBool() or not ply:IsProp() then - return - end - - return true -end - -hook.Add('CalcMainActivity', 'PropTpose', function(ply) - if not GetConVar("ph_props_tpose"):GetBool() or not ply:IsProp() then - return - end - - return ACT_INVALID -end) diff --git a/gamemodes/ultimateph/gamemode/sv_bannedmodels.lua b/gamemodes/ultimateph/gamemode/sv_bannedmodels.lua deleted file mode 100644 index 28b7336..0000000 --- a/gamemodes/ultimateph/gamemode/sv_bannedmodels.lua +++ /dev/null @@ -1,88 +0,0 @@ --- This file is what controls what models are banned. Models that are banned --- cannot be chosen as a disguise. - -GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. - -util.AddNetworkString("ph_bannedmodels_getall") -util.AddNetworkString("ph_bannedmodels_add") -util.AddNetworkString("ph_bannedmodels_remove") - -function GM:IsModelBanned(model) - return self.BannedModels[model] == true -end - -function GM:AddBannedModel(model) - if self.BannedModels[model] == true then return end - - self.BannedModels[model] = true - self:SaveBannedModels() -end - -function GM:RemoveBannedModel(model) - if self.BannedModels[model] != true then return end - - self.BannedModels[model] = nil - self:SaveBannedModels() -end - -function GM:SaveBannedModels() - -- ensure the folders are there - if !file.Exists("ultimateph/", "DATA") then - file.CreateDir("ultimateph") - end - - local txt = "" - for key, value in pairs(self.BannedModels) do - if value then - txt = txt .. key .. "\r\n" - end - end - - file.Write("ultimateph/bannedmodels.txt", txt) -end - -function GM:LoadBannedModels() - local bannedModels = file.Read("ultimateph/bannedmodels.txt", "DATA") - if bannedModels then - for match in bannedModels:gmatch("[^\r\n]+") do - self:AddBannedModel(match) - end - end -end - -net.Receive("ph_bannedmodels_getall", function(len, ply) - net.Start("ph_bannedmodels_getall") - - for key, value in pairs(GAMEMODE.BannedModels) do - if value then - net.WriteString(key) - end - end - - net.WriteString("") - net.Send(ply) -end) - -net.Receive("ph_bannedmodels_add", function(len, ply) - if !ply:IsAdmin() then return end - - local model = net.ReadString() - if model == "" then return end - - GAMEMODE:AddBannedModel(model) - net.Start("ph_bannedmodels_add") - net.WriteString(model) - net.Broadcast() -end) - -net.Receive("ph_bannedmodels_remove", function(len, ply) - if !ply:IsAdmin() then return end - - local model = net.ReadString() - if model == "" then return end - - GAMEMODE:RemoveBannedModel(model) - net.Start("ph_bannedmodels_remove") - net.WriteString(model) - net.Broadcast() -end) diff --git a/gamemodes/ultimateph/gamemode/sv_chatmsg.lua b/gamemodes/ultimateph/gamemode/sv_chatmsg.lua deleted file mode 100644 index 158e0b7..0000000 --- a/gamemodes/ultimateph/gamemode/sv_chatmsg.lua +++ /dev/null @@ -1,20 +0,0 @@ --- The file provides the functionality for sending chat messages to players from --- the server. This is used instead of PrintMessage because we want to have --- colored messages (PrintMessage can't do this). - -util.AddNetworkString("ph_chatmsg") -local PlayerMeta = FindMetaTable("Player") - --- Sends a message to an individual player. -function PlayerMeta:PlayerChatMsg(...) - net.Start("ph_chatmsg") - net.WriteTable({...}) - net.Send(self) -end - --- Sends a message to every player. -function GlobalChatMsg(...) - net.Start("ph_chatmsg") - net.WriteTable({...}) - net.Broadcast() -end diff --git a/gamemodes/ultimateph/gamemode/sv_disguise.lua b/gamemodes/ultimateph/gamemode/sv_disguise.lua deleted file mode 100644 index 4588478..0000000 --- a/gamemodes/ultimateph/gamemode/sv_disguise.lua +++ /dev/null @@ -1,165 +0,0 @@ -include("sh_disguise.lua") - -local PlayerMeta = FindMetaTable("Player") - -function GM:PlayerDisguise(ply) - local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(ply) - if canDisguise then - if ply.LastDisguise && ply.LastDisguise + 1 > CurTime() then - return - end - - ply:DisguiseAsProp(target) - end -end - -function PlayerMeta:DisguiseAsProp(ent) - local hullxy, hullz = ent:GetPropSize() - if !self:CanFitHull(hullxy, hullxy, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to change") - return - end - - if !self:IsDisguised() then - self.OldPlayerModel = self:GetModel() - end - - self:Flashlight(false) - - -- create an entity for the disguise - -- we can't use a clientside entity as it needs a shadow - local dent = self:GetNWEntity("disguiseEntity") - if !IsValid(dent) then - dent = ents.Create("ph_disguise") - self:SetNWEntity("disguiseEntity", dent) - dent.PropOwner = self - dent:SetPos(self:GetPos()) - dent:Spawn() - end - dent:SetModel(ent:GetModel()) - - self:SetNWBool("disguised", true) - self:SetNWString("disguiseModel", ent:GetModel()) - self:SetNWVector("disguiseMins", ent:OBBMins()) - self:SetNWVector("disguiseMaxs", ent:OBBMaxs()) - self:SetNWInt("disguiseSkin", ent:GetSkin()) - self:SetNWBool("disguiseRotationLock", false) - self:SetColor(Color(255, 0, 0, 0)) - self:SetRenderMode(RENDERMODE_NONE) - self:SetModel(ent:GetModel()) - self:SetNoDraw(false) - self:DrawShadow(false) - GAMEMODE:PlayerSetNewHull(self, hullxy, hullz, hullz) - - local maxHealth = 1 - local volume = 1 - local phys = ent:GetPhysicsObject() - if IsValid(phys) then - maxHealth = math.Clamp(math.Round(phys:GetVolume() / 230), 1, 200) - volume = phys:GetVolume() - end - - self.PercentageHealth = math.min(self:Health() / self:GetHMaxHealth(), self.PercentageHealth || 1) - local per = math.Clamp(self.PercentageHealth * maxHealth, 1, 200) - self:SetHealth(per) - self:SetHMaxHealth(maxHealth) - self:SetNWFloat("disguiseVolume", volume) - - self:CalculateSpeed() - - local offset = Vector(0, 0, ent:OBBMaxs().z - self:OBBMins().z + 10) - self:SetViewOffset(offset) - self:SetViewOffsetDucked(offset) - - self:EmitSound("weapons/bugbait/bugbait_squeeze" .. math.random(1, 3) .. ".wav") - self.LastDisguise = CurTime() - - local eff = EffectData() - eff:SetOrigin(self:GetPos() + Vector(0, 0, 1)) - eff:SetScale(hullxy) - eff:SetMagnitude(hullz) - util.Effect("ph_disguise", eff, true, true) -end - -function PlayerMeta:IsDisguised() - return self:GetNWBool("disguised", false) -end - -function PlayerMeta:UnDisguise() - local dent = self:GetNWEntity("disguiseEntity") - if IsValid(dent) then - dent:Remove() - end - - self.PercentageHealth = nil - self:SetNWBool("disguised", false) - self:SetNWBool("undisguiseRotationLock", false) - self:SetColor(Color(255, 255, 255, 255)) - self:SetNoDraw(false) - self:DrawShadow(true) - self:SetRenderMode(RENDERMODE_NORMAL) - GAMEMODE:PlayerSetNewHull(self) - if self.OldPlayerModel then - self:SetModel(self.OldPlayerModel) - self.OldPlayerModel = nil - end - - self:SetViewOffset(Vector(0, 0, 64)) - self:SetViewOffsetDucked(Vector(0, 0, 28)) - - self:CalculateSpeed() -end - -function PlayerMeta:DisguiseLockRotation() - if !self:IsDisguised() then - local ang = self:GetRenderAngles() - self:SetNWAngle("undisguiseRotationLockAng", ang) - self:SetNWBool("undisguiseRotationLock", true) - return - end - - local mins, maxs = self:CalculateRotatedDisguiseMinsMaxs() - local hullx = math.Round((maxs.x - mins.x) / 2) - local hully = math.Round((maxs.y - mins.y) / 2) - local hullz = math.Round(maxs.z - mins.z) - if !self:CanFitHull(hullx, hully, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to lock rotation, move into a more open area") - return - end - - local ang = self:EyeAngles() - self:SetNWBool("disguiseRotationLock", true) - self:SetNWFloat("disguiseRotationLockYaw", ang.y) - GAMEMODE:PlayerSetHull(self, hullx, hully, hullz, hullz) -end - -function PlayerMeta:DisguiseUnlockRotation() - if !self:IsDisguised() then - self:SetNWBool("undisguiseRotationLock", false) - return - end - - local maxs = self:GetNWVector("disguiseMaxs") - local mins = self:GetNWVector("disguiseMins") - local hullxy = math.Round(math.Max(maxs.x - mins.x, maxs.y - mins.y) / 2) - local hullz = math.Round(maxs.z - mins.z) - if !self:CanFitHull(hullxy, hullxy, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to unlock rotation, move into a more open area") - return - end - - self:SetNWBool("disguiseRotationLock", false) - GAMEMODE:PlayerSetHull(self, hullxy, hullxy, hullz, hullz) -end - -concommand.Add("ph_lockrotation", function(ply, com, args) - if !IsValid(ply) or ply:Team() ~= TEAM_PROP then - return - end - - if ply:DisguiseRotationLocked() then - ply:DisguiseUnlockRotation() - else - ply:DisguiseLockRotation() - end -end) diff --git a/gamemodes/ultimateph/gamemode/sv_health.lua b/gamemodes/ultimateph/gamemode/sv_health.lua deleted file mode 100644 index 4096a8b..0000000 --- a/gamemodes/ultimateph/gamemode/sv_health.lua +++ /dev/null @@ -1,11 +0,0 @@ -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:SetHMaxHealth(amo) - self.HMaxHealth = amo - self:SetNWFloat("HMaxHealth", amo) - self:SetMaxHealth(amo) -end - -function PlayerMeta:GetHMaxHealth() - return self.HMaxHealth || 100 -end diff --git a/gamemodes/ultimateph/gamemode/sv_killfeed.lua b/gamemodes/ultimateph/gamemode/sv_killfeed.lua deleted file mode 100644 index 8bb057d..0000000 --- a/gamemodes/ultimateph/gamemode/sv_killfeed.lua +++ /dev/null @@ -1,78 +0,0 @@ -util.AddNetworkString("ph_kill_feed_add") - -local DMG_CLUB_GENERIC = bit.bor(DMG_CLUB, DMG_GENERIC) -local DMG_SLOWBURN_BURN = bit.bor(DMG_SLOWBURN, DMG_BURN) -local DMG_BLAST_SURFACE_BLAST = bit.bor(DMG_BLAST_SURFACE, DMG_BLAST) -local DMG_SONIC_SHOCK = bit.bor(DMG_SONIC, DMG_SHOCK) -local DMG_PLASMA_ENERGYBEAM = bit.bor(DMG_PLASMA, DMG_ENERGYBEAM) -local DMG_NERVEGAS_POISON = bit.bor(DMG_NERVEGAS, DMG_POISON) -local DMG_DISSOLVE_ACID = bit.bor(DMG_DISSOLVE, DMG_ACID) - -local attackedMessages = {} -attackedMessages[DMG_CLUB_GENERIC] = {"killed", "destroyed"} -attackedMessages[DMG_CRUSH] = {"threw a prop at", "crushed"} -attackedMessages[DMG_BULLET] = {"shot", "fed lead to"} -attackedMessages[DMG_SLASH] = {"cut", "sliced"} -attackedMessages[DMG_SLOWBURN_BURN] = {"incinerated", "cooked"} -attackedMessages[DMG_VEHICLE] = {"ran over", "flattened"} -attackedMessages[DMG_FALL] = {"pushed", "tripped"} -attackedMessages[DMG_BLAST_SURFACE_BLAST] = {"blew up", "blasted"} -attackedMessages[DMG_SONIC_SHOCK] = {"electrocuted", "zapped"} -attackedMessages[DMG_PLASMA_ENERGYBEAM] = {"atomized", "disintegrated"} -attackedMessages[DMG_DROWN] = {"drowned"} -attackedMessages[DMG_NERVEGAS_POISON] = {"poisoned"} -attackedMessages[DMG_RADIATION] = {"irradiated"} -attackedMessages[DMG_DISSOLVE_ACID] = {"dissolved"} -attackedMessages[DMG_DIRECT] = {"mysteriously killed"} -attackedMessages[DMG_BUCKSHOT] = {"swiss cheesed", "shotgunned"} -attackedMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props - -local suicideMessages = {} -suicideMessages[DMG_CLUB_GENERIC] = {"couldn't take it anymore", "killed themself"} -suicideMessages[DMG_CRUSH] = {"was crushed to death"} -suicideMessages[DMG_BULLET] = {"shot themself"} -suicideMessages[DMG_SLASH] = {"got a paper cut"} -suicideMessages[DMG_SLOWBURN_BURN] = {"burned to death"} -suicideMessages[DMG_VEHICLE] = {"ran themself over"} -suicideMessages[DMG_FALL] = {"fell over"} -suicideMessages[DMG_BLAST_SURFACE_BLAST] = {"blew themself up"} -suicideMessages[DMG_SONIC_SHOCK] = {"electrocuted themself"} -suicideMessages[DMG_PLASMA_ENERGYBEAM] = {"looked into a laser"} -suicideMessages[DMG_DROWN] = {"drowned", "couldn't swim"} -suicideMessages[DMG_NERVEGAS_POISON] = {"ate some hemlock", "couldn't find an antidote"} -suicideMessages[DMG_RADIATION] = {"handled too much uranium"} -suicideMessages[DMG_DISSOLVE_ACID] = {"spilled acid on themself"} -suicideMessages[DMG_DIRECT] = {"mysteriously died"} -suicideMessages[DMG_BUCKSHOT] = {"shotgunned themself"} -suicideMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props - -local function getKillMessage(dmgInfo, tblToUse) - local message - for dmgType, messages in pairs(tblToUse) do - if dmgInfo:IsDamageType(dmgType) then - message = table.Random(messages) - end - end - - return message -end - -function GM:AddKillFeed(ply, attacker, dmgInfo) - local killData = { - victimName = ply:Nick(), - victimColor = team.GetColor(ply:Team()), - messageColor = Color(255, 255, 255, 255) - } - - if IsValid(attacker) && attacker:IsPlayer() && ply != attacker then - killData.attackerName = attacker:Nick() - killData.attackerColor = team.GetColor(attacker:Team()) - killData.message = getKillMessage(dmgInfo, attackedMessages) || "killed" - else - killData.message = getKillMessage(dmgInfo, suicideMessages) || "died" - end - - net.Start("ph_kill_feed_add") - net.WriteTable(killData) - net.Broadcast() -end diff --git a/gamemodes/ultimateph/gamemode/sv_playercolor.lua b/gamemodes/ultimateph/gamemode/sv_playercolor.lua deleted file mode 100644 index 2788cbc..0000000 --- a/gamemodes/ultimateph/gamemode/sv_playercolor.lua +++ /dev/null @@ -1,10 +0,0 @@ -local EntityMeta = FindMetaTable("Entity") - -function EntityMeta:GetPlayerColor() - return self.playerColor || Vector() -end - -function EntityMeta:SetPlayerColor(vec) - self.playerColor = vec - self:SetNWVector("playerColor", vec) -end diff --git a/gamemodes/ultimateph/gamemode/sv_ragdoll.lua b/gamemodes/ultimateph/gamemode/sv_ragdoll.lua deleted file mode 100644 index 3c7940a..0000000 --- a/gamemodes/ultimateph/gamemode/sv_ragdoll.lua +++ /dev/null @@ -1,161 +0,0 @@ -local PlayerMeta = FindMetaTable("Player") -local EntityMeta = FindMetaTable("Entity") - -local dtypes = {} -dtypes[DMG_GENERIC] = "" -dtypes[DMG_CRUSH] = "Blunt Force" -dtypes[DMG_BULLET] = "Bullet" -dtypes[DMG_SLASH] = "Laceration" -dtypes[DMG_BURN] = "Fire" -dtypes[DMG_VEHICLE] = "Blunt Force" -dtypes[DMG_FALL] = "Fall force" -dtypes[DMG_BLAST] = "Explosion" -dtypes[DMG_CLUB] = "Blunt Force" -dtypes[DMG_SHOCK] = "Shock" -dtypes[DMG_SONIC] = "Sonic" -dtypes[DMG_ENERGYBEAM] = "Enery" -dtypes[DMG_DROWN] = "Hydration" -dtypes[DMG_PARALYZE] = "Paralyzation" -dtypes[DMG_NERVEGAS] = "Nervegas" -dtypes[DMG_POISON] = "Poison" -dtypes[DMG_RADIATION] = "Radiation" -dtypes[DMG_DROWNRECOVER] = "" -dtypes[DMG_ACID] = "Acid" -dtypes[DMG_PLASMA] = "Plasma" -dtypes[DMG_AIRBOAT] = "Energy" -dtypes[DMG_DISSOLVE] = "Energy" -dtypes[DMG_BLAST_SURFACE] = "" -dtypes[DMG_DIRECT] = "Fire" -dtypes[DMG_BUCKSHOT] = "Bullet" - -local DeathRagdollsPerPlayer = 3 -local DeathRagdollsPerServer = 22 - -if !PlayerMeta.CreateRagdollOld then - PlayerMeta.CreateRagdollOld = PlayerMeta.CreateRagdoll -end - -function PlayerMeta:CreateRagdoll(attacker, dmginfo) - local ent = self:GetNWEntity("DeathRagdoll") - - -- remove old player ragdolls - if !self.DeathRagdolls then self.DeathRagdolls = {} end - - local countPlayerRagdolls = 1 - for k, rag in pairs(self.DeathRagdolls) do - if IsValid(rag) then - countPlayerRagdolls = countPlayerRagdolls + 1 - else - self.DeathRagdolls[k] = nil - end - end - - if DeathRagdollsPerPlayer >= 0 && countPlayerRagdolls > DeathRagdollsPerPlayer then - for i = 0, countPlayerRagdolls do - if countPlayerRagdolls > DeathRagdollsPerPlayer then - self.DeathRagdolls[1]:Remove() - table.remove(self.DeathRagdolls, 1) - countPlayerRagdolls = countPlayerRagdolls - 1 - else - break - end - end - end - - -- remove old server ragdolls - local c2 = 1 - for k, rag in pairs(GAMEMODE.DeathRagdolls) do - if IsValid(rag) then - c2 = c2 + 1 - else - GAMEMODE.DeathRagdolls[k] = nil - end - end - - if DeathRagdollsPerServer >= 0 && c2 > DeathRagdollsPerServer then - for i = 0, c2 do - if c2 > DeathRagdollsPerServer then - GAMEMODE.DeathRagdolls[1]:Remove() - table.remove(GAMEMODE.DeathRagdolls, 1) - c2 = c2 - 1 - else - break - end - end - end - - local Data = duplicator.CopyEntTable(self) - if !util.IsValidRagdoll(Data.Model) then - return - end - - local ent = ents.Create("prop_ragdoll") - duplicator.DoGeneric(ent, Data) - ent:Spawn() - ent:SetCollisionGroup(COLLISION_GROUP_WEAPON) - ent:Fire("kill", "", 60 * 8) - if ent.SetPlayerColor then - ent:SetPlayerColor(self:GetPlayerColor()) - end - - ent:SetNWEntity("RagdollOwner", self) - - ent.Corpse = {} - ent.Corpse.Name = self:Nick() - ent.Corpse.CauseDeath = "" - ent.Corpse.Attacker = "" - if IsValid(attacker) && attacker:IsPlayer() then - if attacker == self then - if ent.Corpse.CauseDeath == "" then - ent.Corpse.CauseDeath = "Suicide" - end - else - ent.Corpse.Attacker = attacker:Nick() - end - -- inflicter doesn't work, do on GM:PlayerDeath - end - - -- set velocities - local Vel = self:GetVelocity() - local iNumPhysObjects = ent:GetPhysicsObjectCount() - for Bone = 0, iNumPhysObjects-1 do - local PhysObj = ent:GetPhysicsObjectNum(Bone) - if IsValid(PhysObj) then - local Pos, Ang = self:GetBonePosition(ent:TranslatePhysBoneToBone(Bone)) - PhysObj:SetPos(Pos) - PhysObj:SetAngles(Ang) - PhysObj:AddVelocity(Vel) - end - end - - -- finish up - self:SetNWEntity("DeathRagdoll", ent) - table.insert(self.DeathRagdolls, ent) - table.insert(GAMEMODE.DeathRagdolls, ent) -end - -if !PlayerMeta.GetRagdollEntityOld then - PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity -end - -function PlayerMeta:GetRagdollEntity() - local ent = self:GetNWEntity("DeathRagdoll") - if IsValid(ent) then - return ent - end - - return self:GetRagdollEntityOld() -end - -if !PlayerMeta.GetRagdollOwnerOld then - PlayerMeta.GetRagdollOwnerOld = PlayerMeta.GetRagdollOwner -end - -function EntityMeta:GetRagdollOwner() - local ent = self:GetNWEntity("RagdollOwner") - if IsValid(ent) then - return ent - end - - return self:GetRagdollOwnerOld() -end diff --git a/gamemodes/ultimateph/gamemode/sv_realism.lua b/gamemodes/ultimateph/gamemode/sv_realism.lua deleted file mode 100644 index 676a05c..0000000 --- a/gamemodes/ultimateph/gamemode/sv_realism.lua +++ /dev/null @@ -1,28 +0,0 @@ -function GM:RealismThink() - for k, ply in pairs(player.GetAll()) do - if ply:Alive() then - -- don't increase velocity when jumping off ground - if ply:KeyPressed(IN_JUMP) && ply.PrevOnGround then - ply.LastJump = CurTime() - - local curVel = ply:GetVelocity() - local newVel = ply.PrevSpeed * 1 - newVel.z = curVel.z - ply:SetLocalVelocity(newVel) - end - - ply.PrevSpeed = ply:GetVelocity() - ply.PrevOnGround = ply:OnGround() - end - end -end - --- minimum velocity to trigger function is 530 -function GM:GetFallDamage(ply, vel) - if vel > 530 then - local minvel = vel - 530 - local dmg = math.ceil(minvel / 278 * GAMEMODE.FallDMGMult:GetInt()) - if GAMEMODE.FallDMGNonLethal:GetBool() && ply:Health() <= dmg then dmg = ply:Health() - 1 end - return dmg - end -end diff --git a/gamemodes/ultimateph/gamemode/sv_respawn.lua b/gamemodes/ultimateph/gamemode/sv_respawn.lua deleted file mode 100644 index 919ace1..0000000 --- a/gamemodes/ultimateph/gamemode/sv_respawn.lua +++ /dev/null @@ -1,15 +0,0 @@ -function GM:CanRespawn(ply) - if ply:IsSpectator() then - return false - end - - if self:GetGameState() == ROUND_WAIT or self:GetGameState() == ROUND_HIDE then - if ply.NextSpawnTime && ply.NextSpawnTime > CurTime() then return end - - if ply:KeyPressed(IN_JUMP) || ply:KeyPressed(IN_ATTACK) then - return true - end - end - - return false -end diff --git a/gamemodes/ultimateph/gamemode/sv_rounds.lua b/gamemodes/ultimateph/gamemode/sv_rounds.lua deleted file mode 100644 index 5d78c87..0000000 --- a/gamemodes/ultimateph/gamemode/sv_rounds.lua +++ /dev/null @@ -1,330 +0,0 @@ -include("sv_awards.lua") - -util.AddNetworkString("gamestate") -util.AddNetworkString("round_victor") -util.AddNetworkString("gamerules") -util.AddNetworkString("rounds") - -GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT -GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() -GM.Rounds = GAMEMODE && GAMEMODE.Rounds || 0 - -local function mapTimeLimitTimerResult() - if GAMEMODE.Rounds < GAMEMODE.RoundLimit:GetInt() then -- Only change if we haven't already hit the round limit - GAMEMODE.Rounds = GAMEMODE.RoundLimit:GetInt() - 1 -- Allows for 1 extra round before hitting the limit - end -end - -local function changeMapTimeLimitTimer(oldValueMinutes, newValueMinutes) - local timerName = "ph_timer_map_time_limit" - if newValueMinutes == -1 then -- Timer should be disabled - timer.Remove(timerName) - else - local newValueSeconds = math.floor(newValueMinutes) * 60 - - -- If a timer exists then take its elapsed time into account when calculating our new time limit. - if timer.Exists(timerName) then - local oldValueSeconds = math.floor(oldValueMinutes) * 60 - newValueSeconds = newValueSeconds - (oldValueSeconds - timer.TimeLeft(timerName)) - end - - -- Timers don't execute their callback if given a negative time. - -- newValueSeconds will be negative if oldValueMinutes was greater than newValueMinutes. - if newValueSeconds < 0 then - mapTimeLimitTimerResult() - else - -- This will create or update the timer with the new value. - timer.Create(timerName, newValueSeconds, 1, mapTimeLimitTimerResult) - end - end -end - -cvars.AddChangeCallback("ph_map_time_limit", function(convar, oldValue, newValue) - changeMapTimeLimitTimer(tonumber(oldValue), tonumber(newValue)) -end) - -function GM:GetGameState() - return self.GameState -end - -function GM:GetStateStart() - return self.StateStart -end - -function GM:GetStateRunningTime() - return CurTime() - self.StateStart -end - -function GM:GetPlayingPlayers() - local players = {} - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() && ply:GetNWBool("RoundInGame") then - table.insert(players, ply) - end - end - - return players -end - -function GM:SetGameState(state) - self.GameState = state - self.StateStart = CurTime() - self:NetworkGameState() -end - -function GM:NetworkGameState(ply) - net.Start("gamestate") - net.WriteUInt(self.GameState || ROUND_WAIT, 32) - net.WriteDouble(self.StateStart || 0) - net.Broadcast() -end - -function GM:GetRoundSettings() - self.RoundSettings = self.RoundSettings || {} - return self.RoundSettings -end - -function GM:NetworkGameSettings(ply) - net.Start("gamerules") - - if self.RoundSettings then - for k, v in pairs(self.RoundSettings) do - net.WriteUInt(1, 8) - net.WriteString(k) - net.WriteType(v) - end - end - net.WriteUInt(0, 8) - - if ply == nil then - net.Broadcast() - else - net.Send(ply) - end -end - -function GM:SetupRound() - local c = 0 - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() then -- ignore spectators - c = c + 1 - end - end - - if c < 2 then - GlobalChatMsg("Not enough players to start round") - self:SetGameState(ROUND_WAIT) - return - end - - self:BalanceTeams() - - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() then -- ignore spectators - ply:SetNWBool("RoundInGame", true) - ply:KillSilent() - ply:UnCSpectate() - ply:Spawn() - - local col = team.GetColor(ply:Team()) - ply:SetPlayerColor(Vector(col.r / 255, col.g / 255, col.b / 255)) - - if ply:IsHunter() then - ply:Freeze(true) - end - - ply.PropDmgPenalty = 0 - ply.PropMovement = 0 - ply.HunterKills = 0 - ply.TauntAmount = 0 - ply.TauntsUsed = {} - ply.TauntEnd = nil - ply.AutoTauntDeadline = nil - else - ply:SetNWBool("RoundInGame", false) - end - end - - self:CleanupMap() - self.Rounds = self.Rounds + 1 - net.Start("rounds") - net.WriteInt(self.Rounds, 5) - net.Broadcast() - - if self.Rounds == self.RoundLimit:GetInt() then - GlobalChatMsg(Color(255, 0, 0), "This is the LAST ROUND!") - - if self.Secrets:GetBool() then - BroadcastLua("surface.PlaySound('husklesph/hphaaaaa2.mp3')") - end - end - - hook.Run("OnSetupRound") - self:SetGameState(ROUND_HIDE) -end - -function GM:StartRound() - self.LastPropDeath = nil - self.FirstHunterKill = nil - - local hunters, props = 0, 0 - for k, ply in pairs(self:GetPlayingPlayers()) do - ply:Freeze(false) - ply.PropDmgPenalty = 0 - ply.PropMovement = 0 - ply.HunterKills = 0 - ply.TauntAmount = 0 - if ply:IsHunter() then - hunters = hunters + 1 - elseif ply:IsProp() then - props = props + 1 - end - end - - local c = 0 - for k, ent in pairs(ents.GetAll()) do - if ent.IsDisguisableAs && ent:IsDisguisableAs() then - c = c + 1 - end - end - - self.RoundSettings = {} - if self.RoundTime:GetInt() > 0 then - self.RoundSettings.RoundTime = self.RoundTime:GetInt() - else - self.RoundSettings.RoundTime = math.Round((c * 0.5 / hunters + 60 * 4) * math.sqrt(props / hunters)) - end - self.RoundSettings.PropsCamDistanceMult = self.PropsCamDistanceMult:GetFloat() - print("Round time is " .. (self.RoundSettings.RoundTime / 60) .. " (" .. c .. " props)") - self:NetworkGameSettings() - self:SetGameState(ROUND_SEEK) - GlobalChatMsg("Round has started") -end - -function GM:EndRound(winningTeam) - if winningTeam == WIN_NONE then - GlobalChatMsg("Tie everybody loses") - else - GlobalChatMsg(team.GetColor(winningTeam), team.GetName(winningTeam), " win") - end - - self.LastRoundResult = winningTeam - - hook.Run("PH".. winningTeam, ply) - hook.Run("PHEndRound", ply) - - local awards = {} - for awardKey, award in pairs(PlayerAwards) do -- PlayerAwards comes from sv_awards.lua - local result = award.getWinner() - - -- nil values cannot exist in awards otherwise the net.WriteTable below will break - if !result then - continue - elseif type(result) == "Player" && IsValid(result) then - awards[awardKey] = { - name = award.name, - desc = award.desc, - winnerName = result:Nick(), - winnerTeam = result:Team() - } - hook.Run("PHAward", result, award) - else - ErrorNoHalt("ULTIMATEPH WARNING: EndRound Player Award gave non Player object: " .. type(result)) - end - end - - net.Start("round_victor") - net.WriteUInt(winningTeam, 8) - net.WriteTable(awards) - net.Broadcast() - - self.RoundSettings.NextRoundTime = self.PostRoundTime:GetInt() - self:NetworkGameSettings() - self:SetGameState(ROUND_POST) -end - -function GM:RoundsSetupPlayer(ply) - -- start off not participating - ply:SetNWBool("RoundInGame", false) - - -- send game state - self:NetworkGameState(ply) -end - -function GM:CheckForVictory() - -- Check if time limit expired - local settings = self:GetRoundSettings() - local roundTime = settings.RoundTime || 5 * 60 - if self:GetStateRunningTime() > roundTime then - self:EndRound(WIN_PROP) - return - end - - -- Check if there are still living players on either team - local huntersAlive, propsAlive = false, false - for _, ply in pairs(self:GetPlayingPlayers()) do - if !ply:Alive() then continue end - - huntersAlive = huntersAlive || ply:IsHunter() - propsAlive = propsAlive || ply:IsProp() - end - - if !huntersAlive && !propsAlive then - self:EndRound(WIN_NONE) - elseif !huntersAlive then - self:EndRound(WIN_PROP) - elseif !propsAlive then - self:EndRound(WIN_HUNTER) - end -end - -function GM:RoundsThink() - if self:GetGameState() == ROUND_WAIT then - local c = 0 - for k, ply in pairs(player.GetAll()) do - if !ply:IsSpectator() then -- ignore spectators - c = c + 1 - end - end - - if c >= 2 && self.RoundWaitForPlayers + self.StartWaitTime:GetFloat() < CurTime() then - self:SetupRound() - end - elseif self:GetGameState() == ROUND_HIDE then - if self:GetStateRunningTime() > self.HidingTime:GetInt() then - self:StartRound() - end - elseif self:GetGameState() == ROUND_SEEK then - self:CheckForVictory() - for k, ply in pairs(self:GetPlayingPlayers()) do - if ply:IsProp() && ply:Alive() then - ply.PropMovement = (ply.PropMovement || 0) + ply:GetVelocity():Length() - end - end - elseif self:GetGameState() == ROUND_POST then - if self:GetStateRunningTime() > (self.RoundSettings.NextRoundTime || 30) then - if self.RoundLimit:GetInt() > 0 && self.Rounds >= self.RoundLimit:GetInt() then - self:StartMapVote() - else - if self.LastRoundResult != WIN_PROP || !self.PropsWinStayProps:GetBool() then - self:SwapTeams() - end - - self:SetupRound() - end - end - elseif self:GetGameState() == ROUND_MAPVOTE then - self:MapVoteThink() - end -end - -local function ForceEndRound(ply, command, args) - -- ply is nil on dedicated server console - if !IsValid(ply) || ply:IsAdmin() || ply:IsSuperAdmin() || cvars.Bool("sv_cheats", 0) then - GAMEMODE.RoundSettings = GAMEMODE.RoundSettings || {} - GAMEMODE:EndRound(WIN_NONE) - else - ply:PrintMessage(HUD_PRINTCONSOLE, "You must be a GMod Admin or SuperAdmin on the server to use this command, or sv_cheats must be enabled.") - end -end -concommand.Add("ph_endround", ForceEndRound) diff --git a/gamemodes/ultimateph/gamemode/sv_spectate.lua b/gamemodes/ultimateph/gamemode/sv_spectate.lua deleted file mode 100644 index d54988e..0000000 --- a/gamemodes/ultimateph/gamemode/sv_spectate.lua +++ /dev/null @@ -1,133 +0,0 @@ -util.AddNetworkString("spectating_status") - -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:CSpectate(mode, spectatee) - mode = mode || OBS_MODE_IN_EYE - self:Spectate(mode) - if IsValid(spectatee) then - self:SpectateEntity(spectatee) - self.Spectatee = spectatee - else - self:SpectateEntity(Entity(-1)) - self.Spectatee = nil - end - - self.SpectateMode = mode - self.Spectating = true - net.Start("spectating_status") - net.WriteInt(self.SpectateMode || -1, 8) - net.WriteEntity(self.Spectatee || Entity(-1)) - net.Send(self) -end - -function PlayerMeta:UnCSpectate(mode, spectatee) - self:UnSpectate() - self.SpectateMode = nil - self.Spectatee = nil - self.Spectating = false - net.Start("spectating_status") - net.WriteInt(-1, 8) - net.WriteEntity(Entity(-1)) - net.Send(self) -end - -function PlayerMeta:IsCSpectating() - return self.Spectating -end - -function PlayerMeta:GetCSpectatee() - return self.Spectatee -end - -function PlayerMeta:GetCSpectateMode() - return self.SpectateMode -end - -function GM:SpectateThink() - for k, ply in pairs(player.GetAll()) do - if ply:IsCSpectating() && IsValid(ply:GetCSpectatee()) && (!ply.LastSpectatePosSet || ply.LastSpectatePosSet < CurTime()) then - ply.LastSpectatePosSet = CurTime() + 0.25 - ply:SetPos(ply:GetCSpectatee():GetPos()) - end - end -end - -function GM:SpectateNext(ply, direction) - direction = direction || 1 - local players = {} - local index = 1 - for k, v in pairs(player.GetAll()) do - if v != ply then - -- can only spectate same team and alive - if v:Alive() && (v:Team() == ply:Team() || ply:IsSpectator()) then - table.insert(players, v) - if v == ply:GetCSpectatee() then - index = #players - end - end - end - end - - if #players > 0 then - index = index + direction - if index > #players then - index = 1 - end - - if index < 1 then - index = #players - end - - local ent = players[index] - if IsValid(ent) then - if ent:IsPlayer() && ent:IsHunter() then - ply:CSpectate(OBS_MODE_IN_EYE, ent) - else - ply:CSpectate(OBS_MODE_CHASE, ent) - end - else - if IsValid(ply:GetRagdollEntity()) then - if ply:GetCSpectatee() != ply:GetRagdollEntity() then - ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) - end - else - ply:CSpectate(OBS_MODE_ROAMING) - end - end - else - if IsValid(ply:GetRagdollEntity()) then - if ply:GetCSpectatee() != ply:GetRagdollEntity() then - ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) - end - else - ply:CSpectate(OBS_MODE_ROAMING) - end - end -end - -function GM:ChooseSpectatee(ply) - if !ply.SpectateTime || ply.SpectateTime < CurTime() then - if ply:KeyPressed(IN_JUMP) then - if ply:GetCSpectateMode() != OBS_MODE_ROAMING && (ply:IsSpectator() || self.DeadSpectateRoam:GetBool()) then - ply:CSpectate(OBS_MODE_ROAMING) - end - else - local direction - if ply:KeyPressed(IN_ATTACK) then - direction = 1 - elseif ply:KeyPressed(IN_ATTACK2) then - direction = -1 - end - - if direction then - self:SpectateNext(ply, direction) - end - end - end - - -- if invalid or dead - if !IsValid(ply:GetCSpectatee()) && ply:GetCSpectateMode() != OBS_MODE_ROAMING then - self:SpectateNext(ply) - end -end diff --git a/gamemodes/ultimateph/gamemode/sv_taunt.lua b/gamemodes/ultimateph/gamemode/sv_taunt.lua deleted file mode 100644 index 86c3d06..0000000 --- a/gamemodes/ultimateph/gamemode/sv_taunt.lua +++ /dev/null @@ -1,153 +0,0 @@ -include("sh_taunt.lua") - -util.AddNetworkString("open_taunt_menu") - -local PlayerMeta = FindMetaTable("Player") - -function PlayerMeta:CanTaunt() - if !self:Alive() then - return false - end - - if self.TauntEnd && self.TauntEnd > CurTime() then - return false - end - - return true -end - -function PlayerMeta:EmitTaunt(filename, durationOverride) - local duration = SoundDuration(filename) - if filename:match("%.mp3$") then - duration = durationOverride || 1 - end - - local sndName = FilenameToSoundname(filename) - - self:EmitSound(sndName) - self.TauntEnd = CurTime() + duration + 0.1 - self.TauntAmount = (self.TauntAmount || 0) + 1 - self.AutoTauntDeadline = nil - - if !self.TauntsUsed then self.TauntsUsed = {} end - self.TauntsUsed[sndName] = true -end - -local function ForEachTaunt(ply, taunts, func) - for k, v in pairs(taunts) do - if !TauntAllowedForPlayer(ply, v) then continue end - - if func(k, v) then return end - end -end - -local function DoTaunt(ply, snd) - if !IsValid(ply) then return end - if !ply:CanTaunt() then return end - - local ats = AllowedTauntSounds[snd] - if !ats then return end - - local t - ForEachTaunt(ply, ats, function(k, v) - t = v - return true - end) - - if !t then - return - end - - ply:EmitTaunt(snd, t.soundDurationOverride) -end - -local function DoRandomTaunt(ply) - if !IsValid(ply) then return end - if !ply:CanTaunt() then return end - - local potential = {} - ForEachTaunt(ply, Taunts, function(k, v) - table.insert(potential, v) - end) - - if #potential == 0 then return end - - local t = potential[math.random(#potential)] - local snd = t.sound[math.random(#t.sound)] - - ply:EmitTaunt(snd, t.soundDurationOverride) -end - -concommand.Add("ph_taunt", function(ply, com, args, full) - DoTaunt(ply, args[1] || "") -end) - -concommand.Add("ph_taunt_random", function(ply, com, args, full) - DoRandomTaunt(ply) -end) - -util.AddNetworkString("ph_set_taunt_menu_phrase") -function GM:SetTauntMenuPhrase(phrase, ply) - net.Start("ph_set_taunt_menu_phrase") - net.WriteString(phrase) - - if ply then - net.Send(ply) - else - net.Broadcast() - end -end - -cvars.AddChangeCallback("ph_taunt_menu_phrase", function(convar_name, value_old, value_new) - (GM || GAMEMODE):SetTauntMenuPhrase(value_new) -end) - -function GM:AutoTauntCheck() - if self.GameState != ROUND_SEEK then return end - - local propsOnly = self.AutoTauntPropsOnly:GetBool() - local minDeadline = self.AutoTauntMin:GetInt() - local maxDeadline = self.AutoTauntMax:GetInt() - local badMinMax = minDeadline <= 0 || maxDeadline <= 0 || minDeadline > maxDeadline - - for i, ply in ipairs(player.GetAll()) do - if propsOnly && !ply:IsProp() then - ply.AutoTauntDeadline = nil - continue - end - - local begin - if ply.AutoTauntDeadline then - local secsLeft = ply.AutoTauntDeadline - CurTime() - if secsLeft > 0 then - continue - end - - if !ply.TauntEnd || CurTime() > ply.AutoTauntDeadline then - DoRandomTaunt(ply) - begin = ply.TauntEnd - end - end - if !begin then begin = CurTime() end - - if badMinMax then continue end - - local delta = math.random(minDeadline, maxDeadline) - ply.AutoTauntDeadline = begin + delta - end -end - -function GM:StartAutoTauntTimer() - timer.Remove("AutoTauntCheck") - local start = self.AutoTauntEnabled:GetBool() - - if start then - timer.Create("AutoTauntCheck", 5, 0, function() - self:AutoTauntCheck() - end) - end -end - -cvars.AddChangeCallback("ph_auto_taunt", function(convar_name, value_old, value_new) - (GM || GAMEMODE):StartAutoTauntTimer() -end) diff --git a/gamemodes/ultimateph/gamemode/sv_teams.lua b/gamemodes/ultimateph/gamemode/sv_teams.lua index 477cbcb..bd3206e 100644 --- a/gamemodes/ultimateph/gamemode/sv_teams.lua +++ b/gamemodes/ultimateph/gamemode/sv_teams.lua @@ -85,3 +85,138 @@ function GM:SwapTeams() GlobalChatMsg(Color(50, 220, 150), "Teams have been swapped") end + +-- former sv_spectate.lua +util.AddNetworkString("spectating_status") + +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:CSpectate(mode, spectatee) + mode = mode || OBS_MODE_IN_EYE + self:Spectate(mode) + if IsValid(spectatee) then + self:SpectateEntity(spectatee) + self.Spectatee = spectatee + else + self:SpectateEntity(Entity(-1)) + self.Spectatee = nil + end + + self.SpectateMode = mode + self.Spectating = true + net.Start("spectating_status") + net.WriteInt(self.SpectateMode || -1, 8) + net.WriteEntity(self.Spectatee || Entity(-1)) + net.Send(self) +end + +function PlayerMeta:UnCSpectate(mode, spectatee) + self:UnSpectate() + self.SpectateMode = nil + self.Spectatee = nil + self.Spectating = false + net.Start("spectating_status") + net.WriteInt(-1, 8) + net.WriteEntity(Entity(-1)) + net.Send(self) +end + +function PlayerMeta:IsCSpectating() + return self.Spectating +end + +function PlayerMeta:GetCSpectatee() + return self.Spectatee +end + +function PlayerMeta:GetCSpectateMode() + return self.SpectateMode +end + +function GM:SpectateThink() + for k, ply in pairs(player.GetAll()) do + if ply:IsCSpectating() && IsValid(ply:GetCSpectatee()) && (!ply.LastSpectatePosSet || ply.LastSpectatePosSet < CurTime()) then + ply.LastSpectatePosSet = CurTime() + 0.25 + ply:SetPos(ply:GetCSpectatee():GetPos()) + end + end +end + +function GM:SpectateNext(ply, direction) + direction = direction || 1 + local players = {} + local index = 1 + for k, v in pairs(player.GetAll()) do + if v != ply then + -- can only spectate same team and alive + if v:Alive() && (v:Team() == ply:Team() || ply:IsSpectator()) then + table.insert(players, v) + if v == ply:GetCSpectatee() then + index = #players + end + end + end + end + + if #players > 0 then + index = index + direction + if index > #players then + index = 1 + end + + if index < 1 then + index = #players + end + + local ent = players[index] + if IsValid(ent) then + if ent:IsPlayer() && ent:IsHunter() then + ply:CSpectate(OBS_MODE_IN_EYE, ent) + else + ply:CSpectate(OBS_MODE_CHASE, ent) + end + else + if IsValid(ply:GetRagdollEntity()) then + if ply:GetCSpectatee() != ply:GetRagdollEntity() then + ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) + end + else + ply:CSpectate(OBS_MODE_ROAMING) + end + end + else + if IsValid(ply:GetRagdollEntity()) then + if ply:GetCSpectatee() != ply:GetRagdollEntity() then + ply:CSpectate(OBS_MODE_CHASE, ply:GetRagdollEntity()) + end + else + ply:CSpectate(OBS_MODE_ROAMING) + end + end +end + +function GM:ChooseSpectatee(ply) + if !ply.SpectateTime || ply.SpectateTime < CurTime() then + if ply:KeyPressed(IN_JUMP) then + if ply:GetCSpectateMode() != OBS_MODE_ROAMING && (ply:IsSpectator() || self.DeadSpectateRoam:GetBool()) then + ply:CSpectate(OBS_MODE_ROAMING) + end + else + local direction + if ply:KeyPressed(IN_ATTACK) then + direction = 1 + elseif ply:KeyPressed(IN_ATTACK2) then + direction = -1 + end + + if direction then + self:SpectateNext(ply, direction) + end + end + end + + -- if invalid or dead + if !IsValid(ply:GetCSpectatee()) && ply:GetCSpectateMode() != OBS_MODE_ROAMING then + self:SpectateNext(ply) + end +end diff --git a/gamemodes/ultimateph/gamemode/sv_utils.lua b/gamemodes/ultimateph/gamemode/sv_utils.lua new file mode 100644 index 0000000..43442ce --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sv_utils.lua @@ -0,0 +1,387 @@ +-- former sv_chatmsg.lua +util.AddNetworkString("ph_chatmsg") +local PlayerMeta = FindMetaTable("Player") + +-- Sends a message to an individual player. +function PlayerMeta:PlayerChatMsg(...) + net.Start("ph_chatmsg") + net.WriteTable({...}) + net.Send(self) +end + +-- Sends a message to every player. +function GlobalChatMsg(...) + net.Start("ph_chatmsg") + net.WriteTable({...}) + net.Broadcast() +end + +-- former sv_health.lua +local PlayerMeta = FindMetaTable("Player") + +function PlayerMeta:SetHMaxHealth(amo) + self.HMaxHealth = amo + self:SetNWFloat("HMaxHealth", amo) + self:SetMaxHealth(amo) +end + +function PlayerMeta:GetHMaxHealth() + return self.HMaxHealth || 100 +end + +-- former sv_playercolor.lua +local EntityMeta = FindMetaTable("Entity") + +function EntityMeta:GetPlayerColor() + return self.playerColor || Vector() +end + +function EntityMeta:SetPlayerColor(vec) + self.playerColor = vec + self:SetNWVector("playerColor", vec) +end + +-- former sv_realism.lua +function GM:RealismThink() + for k, ply in pairs(player.GetAll()) do + if ply:Alive() then + -- don't increase velocity when jumping off ground + if ply:KeyPressed(IN_JUMP) && ply.PrevOnGround then + ply.LastJump = CurTime() + + local curVel = ply:GetVelocity() + local newVel = ply.PrevSpeed * 1 + newVel.z = curVel.z + ply:SetLocalVelocity(newVel) + end + + ply.PrevSpeed = ply:GetVelocity() + ply.PrevOnGround = ply:OnGround() + end + end +end + +-- minimum velocity to trigger function is 530 +function GM:GetFallDamage(ply, vel) + if vel > 530 then + local minvel = vel - 530 + local dmg = math.ceil(minvel / 278 * GAMEMODE.FallDMGMult:GetInt()) + if GAMEMODE.FallDMGNonLethal:GetBool() && ply:Health() <= dmg then dmg = ply:Health() - 1 end + return dmg + end +end + +-- former sv_respawn.lua +function GM:CanRespawn(ply) + if ply:IsSpectator() then + return false + end + + if self:GetGameState() == ROUND_WAIT or self:GetGameState() == ROUND_HIDE then + if ply.NextSpawnTime && ply.NextSpawnTime > CurTime() then return end + + if ply:KeyPressed(IN_JUMP) || ply:KeyPressed(IN_ATTACK) then + return true + end + end + + return false +end + +-- former sv_ragdoll.lua +local PlayerMeta = FindMetaTable("Player") +local EntityMeta = FindMetaTable("Entity") + +local dtypes = {} +dtypes[DMG_GENERIC] = "" +dtypes[DMG_CRUSH] = "Blunt Force" +dtypes[DMG_BULLET] = "Bullet" +dtypes[DMG_SLASH] = "Laceration" +dtypes[DMG_BURN] = "Fire" +dtypes[DMG_VEHICLE] = "Blunt Force" +dtypes[DMG_FALL] = "Fall force" +dtypes[DMG_BLAST] = "Explosion" +dtypes[DMG_CLUB] = "Blunt Force" +dtypes[DMG_SHOCK] = "Shock" +dtypes[DMG_SONIC] = "Sonic" +dtypes[DMG_ENERGYBEAM] = "Enery" +dtypes[DMG_DROWN] = "Hydration" +dtypes[DMG_PARALYZE] = "Paralyzation" +dtypes[DMG_NERVEGAS] = "Nervegas" +dtypes[DMG_POISON] = "Poison" +dtypes[DMG_RADIATION] = "Radiation" +dtypes[DMG_DROWNRECOVER] = "" +dtypes[DMG_ACID] = "Acid" +dtypes[DMG_PLASMA] = "Plasma" +dtypes[DMG_AIRBOAT] = "Energy" +dtypes[DMG_DISSOLVE] = "Energy" +dtypes[DMG_BLAST_SURFACE] = "" +dtypes[DMG_DIRECT] = "Fire" +dtypes[DMG_BUCKSHOT] = "Bullet" + +local DeathRagdollsPerPlayer = 3 +local DeathRagdollsPerServer = 22 + +if !PlayerMeta.CreateRagdollOld then + PlayerMeta.CreateRagdollOld = PlayerMeta.CreateRagdoll +end + +function PlayerMeta:CreateRagdoll(attacker, dmginfo) + local ent = self:GetNWEntity("DeathRagdoll") + + -- remove old player ragdolls + if !self.DeathRagdolls then self.DeathRagdolls = {} end + + local countPlayerRagdolls = 1 + for k, rag in pairs(self.DeathRagdolls) do + if IsValid(rag) then + countPlayerRagdolls = countPlayerRagdolls + 1 + else + self.DeathRagdolls[k] = nil + end + end + + if DeathRagdollsPerPlayer >= 0 && countPlayerRagdolls > DeathRagdollsPerPlayer then + for i = 0, countPlayerRagdolls do + if countPlayerRagdolls > DeathRagdollsPerPlayer then + self.DeathRagdolls[1]:Remove() + table.remove(self.DeathRagdolls, 1) + countPlayerRagdolls = countPlayerRagdolls - 1 + else + break + end + end + end + + -- remove old server ragdolls + local c2 = 1 + for k, rag in pairs(GAMEMODE.DeathRagdolls) do + if IsValid(rag) then + c2 = c2 + 1 + else + GAMEMODE.DeathRagdolls[k] = nil + end + end + + if DeathRagdollsPerServer >= 0 && c2 > DeathRagdollsPerServer then + for i = 0, c2 do + if c2 > DeathRagdollsPerServer then + GAMEMODE.DeathRagdolls[1]:Remove() + table.remove(GAMEMODE.DeathRagdolls, 1) + c2 = c2 - 1 + else + break + end + end + end + + local Data = duplicator.CopyEntTable(self) + if !util.IsValidRagdoll(Data.Model) then + return + end + + local ent = ents.Create("prop_ragdoll") + duplicator.DoGeneric(ent, Data) + ent:Spawn() + ent:SetCollisionGroup(COLLISION_GROUP_WEAPON) + ent:Fire("kill", "", 60 * 8) + if ent.SetPlayerColor then + ent:SetPlayerColor(self:GetPlayerColor()) + end + + ent:SetNWEntity("RagdollOwner", self) + + ent.Corpse = {} + ent.Corpse.Name = self:Nick() + ent.Corpse.CauseDeath = "" + ent.Corpse.Attacker = "" + if IsValid(attacker) && attacker:IsPlayer() then + if attacker == self then + if ent.Corpse.CauseDeath == "" then + ent.Corpse.CauseDeath = "Suicide" + end + else + ent.Corpse.Attacker = attacker:Nick() + end + -- inflicter doesn't work, do on GM:PlayerDeath + end + + -- set velocities + local Vel = self:GetVelocity() + local iNumPhysObjects = ent:GetPhysicsObjectCount() + for Bone = 0, iNumPhysObjects-1 do + local PhysObj = ent:GetPhysicsObjectNum(Bone) + if IsValid(PhysObj) then + local Pos, Ang = self:GetBonePosition(ent:TranslatePhysBoneToBone(Bone)) + PhysObj:SetPos(Pos) + PhysObj:SetAngles(Ang) + PhysObj:AddVelocity(Vel) + end + end + + -- finish up + self:SetNWEntity("DeathRagdoll", ent) + table.insert(self.DeathRagdolls, ent) + table.insert(GAMEMODE.DeathRagdolls, ent) +end + +if !PlayerMeta.GetRagdollEntityOld then + PlayerMeta.GetRagdollEntityOld = PlayerMeta.GetRagdollEntity +end + +function PlayerMeta:GetRagdollEntity() + local ent = self:GetNWEntity("DeathRagdoll") + if IsValid(ent) then + return ent + end + + return self:GetRagdollEntityOld() +end + +if !PlayerMeta.GetRagdollOwnerOld then + PlayerMeta.GetRagdollOwnerOld = PlayerMeta.GetRagdollOwner +end + +function EntityMeta:GetRagdollOwner() + local ent = self:GetNWEntity("RagdollOwner") + if IsValid(ent) then + return ent + end + + return self:GetRagdollOwnerOld() +end + +-- former sv_killfeed.lua +util.AddNetworkString("ph_kill_feed_add") + +local DMG_CLUB_GENERIC = bit.bor(DMG_CLUB, DMG_GENERIC) +local DMG_SLOWBURN_BURN = bit.bor(DMG_SLOWBURN, DMG_BURN) +local DMG_BLAST_SURFACE_BLAST = bit.bor(DMG_BLAST_SURFACE, DMG_BLAST) +local DMG_SONIC_SHOCK = bit.bor(DMG_SONIC, DMG_SHOCK) +local DMG_PLASMA_ENERGYBEAM = bit.bor(DMG_PLASMA, DMG_ENERGYBEAM) +local DMG_NERVEGAS_POISON = bit.bor(DMG_NERVEGAS, DMG_POISON) +local DMG_DISSOLVE_ACID = bit.bor(DMG_DISSOLVE, DMG_ACID) + +local attackedMessages = {} +attackedMessages[DMG_CLUB_GENERIC] = {"killed", "destroyed"} +attackedMessages[DMG_CRUSH] = {"threw a prop at", "crushed"} +attackedMessages[DMG_BULLET] = {"shot", "fed lead to"} +attackedMessages[DMG_SLASH] = {"cut", "sliced"} +attackedMessages[DMG_SLOWBURN_BURN] = {"incinerated", "cooked"} +attackedMessages[DMG_VEHICLE] = {"ran over", "flattened"} +attackedMessages[DMG_FALL] = {"pushed", "tripped"} +attackedMessages[DMG_BLAST_SURFACE_BLAST] = {"blew up", "blasted"} +attackedMessages[DMG_SONIC_SHOCK] = {"electrocuted", "zapped"} +attackedMessages[DMG_PLASMA_ENERGYBEAM] = {"atomized", "disintegrated"} +attackedMessages[DMG_DROWN] = {"drowned"} +attackedMessages[DMG_NERVEGAS_POISON] = {"poisoned"} +attackedMessages[DMG_RADIATION] = {"irradiated"} +attackedMessages[DMG_DISSOLVE_ACID] = {"dissolved"} +attackedMessages[DMG_DIRECT] = {"mysteriously killed"} +attackedMessages[DMG_BUCKSHOT] = {"swiss cheesed", "shotgunned"} +attackedMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props + +local suicideMessages = {} +suicideMessages[DMG_CLUB_GENERIC] = {"couldn't take it anymore", "killed themself"} +suicideMessages[DMG_CRUSH] = {"was crushed to death"} +suicideMessages[DMG_BULLET] = {"shot themself"} +suicideMessages[DMG_SLASH] = {"got a paper cut"} +suicideMessages[DMG_SLOWBURN_BURN] = {"burned to death"} +suicideMessages[DMG_VEHICLE] = {"ran themself over"} +suicideMessages[DMG_FALL] = {"fell over"} +suicideMessages[DMG_BLAST_SURFACE_BLAST] = {"blew themself up"} +suicideMessages[DMG_SONIC_SHOCK] = {"electrocuted themself"} +suicideMessages[DMG_PLASMA_ENERGYBEAM] = {"looked into a laser"} +suicideMessages[DMG_DROWN] = {"drowned", "couldn't swim"} +suicideMessages[DMG_NERVEGAS_POISON] = {"ate some hemlock", "couldn't find an antidote"} +suicideMessages[DMG_RADIATION] = {"handled too much uranium"} +suicideMessages[DMG_DISSOLVE_ACID] = {"spilled acid on themself"} +suicideMessages[DMG_DIRECT] = {"mysteriously died"} +suicideMessages[DMG_BUCKSHOT] = {"shotgunned themself"} +suicideMessages[DMG_AIRBOAT] = {"shot too many props"} -- Used for indicating a hunter shot too many props + +local function getKillMessage(dmgInfo, tblToUse) + local message + for dmgType, messages in pairs(tblToUse) do + if dmgInfo:IsDamageType(dmgType) then + message = table.Random(messages) + end + end + + return message +end + +function GM:AddKillFeed(ply, attacker, dmgInfo) + local killData = { + victimName = ply:Nick(), + victimColor = team.GetColor(ply:Team()), + messageColor = Color(255, 255, 255, 255) + } + + if IsValid(attacker) && attacker:IsPlayer() && ply != attacker then + killData.attackerName = attacker:Nick() + killData.attackerColor = team.GetColor(attacker:Team()) + killData.message = getKillMessage(dmgInfo, attackedMessages) || "killed" + else + killData.message = getKillMessage(dmgInfo, suicideMessages) || "died" + end + + net.Start("ph_kill_feed_add") + net.WriteTable(killData) + net.Broadcast() +end + +-- former sv_version.lua +local url = "https://raw.githubusercontent.com/datanext27/ultimateph/master/gamemodes/ultimateph/ultimateph.txt" +local downloadlinks = "https://steamcommunity.com/sharedfiles/filedetails/?id=3028430983" + +function GM:CheckForNewVersion(ply) + local req = {} + req.url = url + req.failed = function(reason) + print("Couldn't get version file.", reason) + end + + req.success = function(code, body, headers) + local tab = util.KeyValuesToTable(body) + if !tab || !tab.version then + print("Couldn't parse version file.") + return + end + + local msg = {} + if tab.version != GAMEMODE.Version then + msg = {Color(215, 20, 20), "Out of date!\n", + Color(255, 222, 102), "You're on version ", + Color(0, 255, 0), GAMEMODE.Version || "error", + Color(255, 222, 102), " but the latest is ", + Color(0, 255, 0), tab.version, "\n", + Color(255, 222, 102), "Download the latest version from: ", + Color(11, 191, 227), downloadlinks} + else + msg = {Color(0, 255, 0), "Up to date!"} + end + + if IsValid(ply) then + ply:PlayerChatMsg(unpack(msg)) + else + MsgC(unpack(msg)) + MsgC("\n") + end + end + + HTTP(req) +end + +concommand.Add("ph_version", function(ply) + local color = Color(255, 149, 129) + local msg = (GAMEMODE.Name || "") .. " " .. tostring(GAMEMODE.Version || "error") + + if IsValid(ply) then + ply:PlayerChatMsg(color, msg) + else + MsgC(color, msg, "\n") -- Print to the server console + end + + GAMEMODE:CheckForNewVersion(ply) +end) diff --git a/gamemodes/ultimateph/gamemode/sv_version.lua b/gamemodes/ultimateph/gamemode/sv_version.lua deleted file mode 100644 index 217df72..0000000 --- a/gamemodes/ultimateph/gamemode/sv_version.lua +++ /dev/null @@ -1,53 +0,0 @@ -local url = "https://raw.githubusercontent.com/datanext27/ultimateph/master/gamemodes/ultimateph/ultimateph.txt" -local downloadlinks = "https://steamcommunity.com/sharedfiles/filedetails/?id=3028430983" - -function GM:CheckForNewVersion(ply) - local req = {} - req.url = url - req.failed = function(reason) - print("Couldn't get version file.", reason) - end - - req.success = function(code, body, headers) - local tab = util.KeyValuesToTable(body) - if !tab || !tab.version then - print("Couldn't parse version file.") - return - end - - local msg = {} - if tab.version != GAMEMODE.Version then - msg = {Color(215, 20, 20), "Out of date!\n", - Color(255, 222, 102), "You're on version ", - Color(0, 255, 0), GAMEMODE.Version || "error", - Color(255, 222, 102), " but the latest is ", - Color(0, 255, 0), tab.version, "\n", - Color(255, 222, 102), "Download the latest version from: ", - Color(11, 191, 227), downloadlinks} - else - msg = {Color(0, 255, 0), "Up to date!"} - end - - if IsValid(ply) then - ply:PlayerChatMsg(unpack(msg)) - else - MsgC(unpack(msg)) - MsgC("\n") - end - end - - HTTP(req) -end - -concommand.Add("ph_version", function(ply) - local color = Color(255, 149, 129) - local msg = (GAMEMODE.Name || "") .. " " .. tostring(GAMEMODE.Version || "error") - - if IsValid(ply) then - ply:PlayerChatMsg(color, msg) - else - MsgC(color, msg, "\n") -- Print to the server console - end - - GAMEMODE:CheckForNewVersion(ply) -end) From 721140ec21ba00b07ff83b6a0abf9d8bcb8b3fbb Mon Sep 17 00:00:00 2001 From: queeek180 Date: Sun, 28 Sep 2025 16:57:08 +1000 Subject: [PATCH 04/25] tpose rotation lock, reduced clutter, some hook & function rewrites, reworked all the UI rendering code, cleanups to endround board, group color & tag support, limited sh_config file, act command whitelist --- gamemodes/ultimateph/gamemode/cl_colors.lua | 17 - .../ultimateph/gamemode/cl_endroundboard.lua | 128 +++-- .../ultimateph/gamemode/cl_helpscreen.lua | 140 +++--- gamemodes/ultimateph/gamemode/cl_hud.lua | 453 +++++++++++++----- gamemodes/ultimateph/gamemode/cl_init.lua | 50 +- .../ultimateph/gamemode/cl_scoreboard.lua | 240 +++++----- gamemodes/ultimateph/gamemode/cl_taunt.lua | 268 +++++++++++ gamemodes/ultimateph/gamemode/cl_utils.lua | 168 +------ .../ultimateph/gamemode/cl_voicepanels.lua | 114 ----- gamemodes/ultimateph/gamemode/sh_config.lua | 62 +++ gamemodes/ultimateph/gamemode/sh_disguise.lua | 14 +- gamemodes/ultimateph/gamemode/sh_init.lua | 7 +- gamemodes/ultimateph/gamemode/sh_rounds.lua | 11 +- gamemodes/ultimateph/gamemode/sh_taunt.lua | 257 ---------- gamemodes/ultimateph/gamemode/sv_player.lua | 22 +- 15 files changed, 964 insertions(+), 987 deletions(-) delete mode 100644 gamemodes/ultimateph/gamemode/cl_colors.lua create mode 100644 gamemodes/ultimateph/gamemode/cl_taunt.lua delete mode 100644 gamemodes/ultimateph/gamemode/cl_voicepanels.lua create mode 100644 gamemodes/ultimateph/gamemode/sh_config.lua diff --git a/gamemodes/ultimateph/gamemode/cl_colors.lua b/gamemodes/ultimateph/gamemode/cl_colors.lua deleted file mode 100644 index e14d24f..0000000 --- a/gamemodes/ultimateph/gamemode/cl_colors.lua +++ /dev/null @@ -1,17 +0,0 @@ -PHDark = Color(73, 77, 100) -PHDarker = Color(54, 58, 79) -PHDarkest = Color(36, 39, 58) -PHWhite = Color(202, 211, 245) -PHLessWhite = Color(184, 192, 224) -PHLesserWhite = Color(165, 173, 203) -PHTeal = Color(166, 218, 149) -PHPink = Color(245, 189, 230) -PHMauve = Color(198, 160, 246) -PHRed = Color(237, 135, 150) -PHMaroon = Color(238, 153, 160) -PHPeach = Color(245, 169, 127) -PHYellow = Color(238, 212, 159) -PHGreen = Color(139, 213, 202) -PHSky = Color(145, 215, 227) -PHSapphire = Color(125, 196, 228) -PHBlue = Color(138, 173, 244) diff --git a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua index 89dcebc..2920ff0 100644 --- a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua @@ -1,36 +1,31 @@ local menu -include("cl_colors.lua") local function createEndRoundMenu() menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) + menu:SetSize(ScrH() * 0.75, ScrH() * 0.75) menu:Center() menu:MakePopup() + menu:SetTitle("") menu:ShowCloseButton(false) menu:SetMouseInputEnabled(true) menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) menu:SetDraggable(false) + menu:SetDeleteOnClose(false) + menu:DockPadding(math.Clamp(ScreenScaleH(2), 2, 4), ScreenScaleH(12) + math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4)) local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('marlett') + closeButton:SetFont('PHIcons') closeButton:SetText('r') closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - closeButton.OnCursorEntered = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) - end - end - closeButton.OnCursorExited = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) + if not closeButton:IsHovered() then + draw.RoundedBox(0, 0, 0, w, h, Color(0,0,0,0)) + else + draw.RoundedBox(0, 0, 0, w, h, PHEndDarker) end end closeButton:SetColor(PHWhite) - closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 40) - closeButton:SetPos(menu:GetWide() / 1.05, 0) + closeButton:SetSize(ScreenScaleH(16), ScreenScaleH(12)) + closeButton:SetPos(math.Round(menu:GetWide() - closeButton:GetWide() - math.Clamp(ScreenScaleH(2), 2, 4)), math.Clamp(ScreenScaleH(2), 2, 4)) closeButton.DoClick = function() menu:Close() end @@ -52,25 +47,20 @@ local function createEndRoundMenu() surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) end - -- 2 pixel thick black border on the OUTSIDE of the panel - -- Temporarily disable clipping so we can draw outside the bounds of menu - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - DisableClipping(true) - surface.DrawOutlinedRect(-1, -1, w + 2, h + 2) - surface.DrawOutlinedRect(-2, -2, w + 4, h + 4) - DisableClipping(false) + -- draw a scaling 2px outline, clamped to a maximum size of 4 pixels. also don't disable clipping and instead position the content inside the outline + surface.SetDrawColor(PHEndBlack) + surface.DrawOutlinedRect(0, 0, w, h, math.Clamp(ScreenScaleH(2), 2, 4)) - -- Title bar rectangle (the title bar is always 22 pixels in height) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawRect(0, 0, w, 22) + -- Title bar rectangle (the title bar is a scaling 12px in height) + surface.SetDrawColor(PHEndDarkest) + surface.DrawRect(math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4), w - math.Clamp(ScreenScaleH(4), 4, 8), ScreenScaleH(12)) -- Light grey background on lower area - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) - surface.DrawRect(0, 22, w, h) - end + surface.SetDrawColor(PHEndDarker) + surface.DrawRect(math.Clamp(ScreenScaleH(2), 2, 4), ScreenScaleH(12) + math.Clamp(ScreenScaleH(2), 2, 4), w - math.Clamp(ScreenScaleH(4), 4, 8), h - ScreenScaleH(12) - math.Clamp(ScreenScaleH(4), 4, 8)) - menu.changeTitle = function(newTitle) - menu:SetTitle(newTitle) + -- title text + draw.SimpleText("Round Over!", 'RobotoHUD-12', math.Round((ScreenScaleH(12) / 8) + math.Clamp(ScreenScaleH(2), 2, 4)), (ScreenScaleH(12) + math.Clamp(ScreenScaleH(2), 2, 4)) / 2, PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end -- Results section (this is just a container for winner, awards, and resultsTimeLeft) @@ -93,7 +83,7 @@ local function createEndRoundMenu() menu.setWinningTeamText = function(winState) if winState == WIN_NONE then winner:SetText("Round tied") - winner:SetColor(PHWhite) + winner:SetColor(PHLessWhite) else winner:SetText(team.GetName(winState) .. " win!") winner:SetColor(team.GetColor(winState)) @@ -106,7 +96,7 @@ local function createEndRoundMenu() function awards:Paint(w, h) -- Add a dark rectangle over the area for the awards to visually separate it from rest of the menu - surface.SetDrawColor(PHDark.r, PHDark.g, PHDark.b) + surface.SetDrawColor(PHEndDark) surface.DrawRect(0, 0, w, h) end @@ -116,7 +106,7 @@ local function createEndRoundMenu() function canvas:OnChildAdded(child) -- Awards fill from top to bottom child:Dock(TOP) - child:DockMargin(10, 10, 10, 0) + child:DockMargin(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), 0) end menu.setPlayerAwards = function(allAwards) @@ -127,9 +117,8 @@ local function createEndRoundMenu() containerPanel:SetTall(draw.GetFontHeight("RobotoHUD-20")) function containerPanel:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) draw.DrawText(award.name, "RobotoHUD-10", 0, 0, PHWhite, 0) - draw.DrawText(award.desc, "RobotoHUD-10", 0, draw.GetFontHeight("RobotoHUD-10"), PHLesserWhite, 0) + draw.DrawText(award.desc, "RobotoHUD-10", 0, draw.GetFontHeight("RobotoHUD-10"), PHLessWhite, 0) draw.DrawText(award.winnerName, "RobotoHUD-15", w, (h / 2) - (draw.GetFontHeight("RobotoHUD-20") / 2), team.GetColor(award.winnerTeam), 2) end @@ -137,22 +126,25 @@ local function createEndRoundMenu() end end - -- Timer at bottom right showing how long until next round + -- Timer at bottom right showing how long until next round/mapvote local resultsTimeLeft = vgui.Create("DPanel", resultsPanel) resultsTimeLeft:Dock(BOTTOM) resultsTimeLeft:SetTall(draw.GetFontHeight("RobotoHUD-15")) function resultsTimeLeft:Paint(w, h) -- "Extend" the dark rectangle from awards:Paint to make a larger seamless rectangle - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) + surface.SetDrawColor(PHEndDark) surface.DrawRect(0, 0, w, h) if GAMEMODE:GetGameState() == ROUND_POST then local settings = GAMEMODE:GetRoundSettings() - local roundTime = settings.NextRoundTime || 30 + local roundTime = settings.NextRoundTime or 30 local time = math.max(0, roundTime - GAMEMODE:GetStateRunningTime()) - -- TODO: Say "Mapvote in..." if last round - draw.DrawText("Next round in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, PHWhite, 2) + if GAMEMODE.CurrentRound >= GAMEMODE.RoundLimit:GetInt() then + draw.DrawText("Map vote in " .. math.ceil(time), "RobotoHUD-15", w - ScreenScaleH(2), 0, PHLessWhite, 2) + else + draw.DrawText("Next round in " .. math.ceil(time), "RobotoHUD-15", w - ScreenScaleH(2), 0, PHLessWhite, 2) + end end end @@ -171,18 +163,20 @@ local function createEndRoundMenu() menu.MapVoteList = mapList mapList:Dock(FILL) mapList:DockMargin(0, 0, 0, 0) + local vbar = mapList:GetVBar() + vbar:SetWide(0) function mapList:Paint(w, h) - surface.SetDrawColor(20, 20, 20, 150) + surface.SetDrawColor(PHEndDark) surface.DrawRect(0, 0, w, h) end local canvas = mapList:GetCanvas() - canvas:DockPadding(20, 0, 20, 0) + canvas:DockPadding(ScreenScaleH(8), 0, ScreenScaleH(8), 0) function canvas:OnChildAdded(child) child:Dock(TOP) - child:DockMargin(0, 15, 0, 0) + child:DockMargin(0, ScreenScaleH(8), 0, 0) end -- Text showing time until map vote ends @@ -191,13 +185,13 @@ local function createEndRoundMenu() mapVoteTimeLeft:SetTall(draw.GetFontHeight("RobotoHUD-15")) function mapVoteTimeLeft:Paint(w, h) - surface.SetDrawColor(20, 20, 20, 150) + surface.SetDrawColor(PHEndDark) surface.DrawRect(0, 0, w, h) if GAMEMODE:GetGameState() == ROUND_MAPVOTE then - local voteTime = GAMEMODE.MapVoteTime || 30 + local voteTime = GAMEMODE.MapVoteTime or 30 local time = math.max(0, voteTime - GAMEMODE:GetMapVoteRunningTime()) - draw.SimpleText("Voting ends in " .. math.ceil(time), "RobotoHUD-15", w - 4, 0, Color(150, 150, 150), 2) + draw.SimpleText("Voting ends in " .. math.ceil(time), "RobotoHUD-15", w - ScreenScaleH(2), 0, PHLessWhite, TEXT_ALIGN_RIGHT) end end end @@ -205,7 +199,6 @@ end function GM:EndRoundMenuResults(res) self:OpenEndRoundMenu() - menu.changeTitle("Round Results") menu.setResultsPanelVisibility(true) menu.setVotemapPanelVisibility(false) menu.Results = res @@ -216,7 +209,6 @@ end function GM:EndRoundMapVote() self:OpenEndRoundMenu() - menu.changeTitle("Map Vote") menu.setResultsPanelVisibility(false) menu.setVotemapPanelVisibility(true) menu.MapVoteList:Clear() @@ -224,7 +216,7 @@ function GM:EndRoundMapVote() for k, map in pairs(self.MapList) do local but = vgui.Create("DButton") but:SetText("") - but:SetTall(128) + but:SetTall(math.Clamp(ScreenScaleH(32), 32, 64)) local png local path = "maps/" .. map .. ".png" @@ -246,52 +238,48 @@ function GM:EndRoundMapVote() dname = dname:gsub("[_]", " "):gsub("([%a])([%a]+)", function(a, b) return a:upper() .. b end) local z = tonumber(util.CRC(dname):sub(1, 8)) local mcol = Color(z % 255, z / 255 % 255, z / 255 / 255 % 255, 50) - local gray = Color(150, 150, 150) + local gray = PHLessWhite but.VotesScroll = 0 but.VotesScrollDir = 1 function but:Paint(w, h) if self.Hovered then - surface.SetDrawColor(50, 50, 50, 50) + surface.SetDrawColor(PHEndGray) surface.DrawRect(0, 0, w, h) end - draw.SimpleText(dname, "RobotoHUD-15", 128 + 20, 20, color_white, 0) + draw.SimpleText(dname, "RobotoHUD-15", but:GetTall() / 0.8, but:GetTall() / 16, PHWhite, 0) local fg = draw.GetFontHeight("RobotoHUD-15") - draw.SimpleText(map, "RobotoHUD-L10", 128 + 20, 20 + fg, gray, 0) + draw.SimpleText(map, "RobotoHUD-L10", but:GetTall() / 0.8, (but:GetTall() / 16) + fg, PHLessWhite, 0) if png then surface.SetMaterial(png) - surface.SetDrawColor(255, 255, 255, 255) - surface.DrawTexturedRect(0, 0, 128, 128) + surface.SetDrawColor(PHWhite) + surface.DrawTexturedRect(0, 0, but:GetTall(), but:GetTall()) else - surface.SetDrawColor(50, 50, 50, 255) - surface.DrawRect(0, 0, 128, 128) + surface.SetDrawColor(PHEndDarkest) + surface.DrawRect(0, 0, but:GetTall(), but:GetTall()) surface.SetDrawColor(mcol) - surface.DrawRect(20, 20, 128 - 40, 128 - 40) + surface.DrawRect(but:GetTall() / 4, but:GetTall() / 4, but:GetTall() / 2, but:GetTall() / 2) end local votes = 0 if GAMEMODE.MapVotesByMap[map] then votes = #GAMEMODE.MapVotesByMap[map] end - + local fg2 = draw.GetFontHeight("RobotoHUD-L10") - if votes > 0 then - draw.SimpleText(votes .. (votes > 1 && " votes" || " vote"), "RobotoHUD-L10", 128 + 20, 20 + fg + 20 + fg2, color_white, 0) - end - local i = 0 for ply, map2 in pairs(GAMEMODE.MapVotes) do - if IsValid(ply) && map2 == map then - draw.SimpleText(ply:Nick(), "RobotoHUD-L10", w, i * fg2 - self.VotesScroll, gray, 2) + if IsValid(ply) and map2 == map then + draw.SimpleText(ply:Nick(), "RobotoHUD-L10", w, i * fg2 - self.VotesScroll, PHLessWhite, 2) i = i + 1 end end - if i * fg2 > 128 then + if i * fg2 > but:GetTall() then self.VotesScroll = self.VotesScroll + FrameTime() * 14 * self.VotesScrollDir - if self.VotesScroll > i * fg2 - 128 then + if self.VotesScroll > i * fg2 - but:GetTall() then self.VotesScrollDir = -1 elseif self.VotesScroll < 0 then self.VotesScrollDir = 1 @@ -308,7 +296,7 @@ function GM:EndRoundMapVote() end function GM:OpenEndRoundMenu() - if !IsValid(menu) then + if not IsValid(menu) then createEndRoundMenu() end @@ -322,7 +310,7 @@ function GM:CloseEndRoundMenu() end function GM:ToggleEndRoundMenuVisibility() - if IsValid(menu) && menu:IsVisible() then + if IsValid(menu) and menu:IsVisible() then GAMEMODE:CloseEndRoundMenu() else GAMEMODE:OpenEndRoundMenu() diff --git a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua index 04da46e..6ca7a37 100644 --- a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua +++ b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua @@ -1,82 +1,58 @@ -local helpText = [[ -== CONTROLS == -LEFT CLICK - Disguises as the prop you are looking at -C - Locks your prop's rotation when disguised -F3 - Taunt menu - - -== OBJECTIVES == -The aim of the hunters is to find and kill all the props. -Don't shoot too many actual props, as guessing incorrectly costs health! - -The aim of the props is to hide from the hunters and not get killed. - - -== COMMANDS == -"ph_taunt_random" plays a random taunt. -"ph_taunt " plays a taunt given a filename. -]] - -local menu -include("cl_colors.lua") - -local function createHelpMenu() - menu = vgui.Create("DFrame") - GAMEMODE.HelpMenu = menu - menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) - menu:Center() - menu:MakePopup() - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - menu:ShowCloseButton(false) - menu:SetTitle("") - menu:SetVisible(false) - - local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('marlett') - closeButton:SetText('r') - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) - end - closeButton.OnCursorEntered = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b, 245)) - end - end - closeButton.OnCursorExited = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b, 0)) - end - end - closeButton:SetColor(PHWhite) - closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) - closeButton:SetPos(menu:GetWide() / 1.05, 0) - closeButton.DoClick = function() - menu:Close() - end - - function menu:Paint(w, h) - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b, 245) - surface.DrawRect(0, 0, w, h) - surface.SetFont("RobotoHUD-25") - draw.ShadowText("Help", "RobotoHUD-25", 8, 2, Color(132, 199, 29), 0) - end - - local text = vgui.Create("DLabel", menu) - text:SetText(helpText) - text:SetWrap(true) - text:SetFont("RobotoHUD-10") - text:Dock(FILL) - - function text:Paint(w, h) end -end - -local function toggleHelpMenu() - if !IsValid(menu) then - createHelpMenu() - end - - menu:SetVisible(!menu:IsVisible()) -end - -net.Receive("ph_openhelpmenu", toggleHelpMenu) +local helpText = [[ +== CONTROLS == +LEFT CLICK - Disguises as the prop you are looking at +C - Locks your prop's rotation when disguised +F3 - Taunt menu + + +== OBJECTIVES == +The aim of the hunters is to find and kill all the props. +Don't shoot too many actual props, as guessing incorrectly costs health! + +The aim of the props is to hide from the hunters and not get killed. + + +== COMMANDS == +"ph_taunt_random" plays a random taunt. +"ph_taunt " plays a taunt given a filename. +]] + +local menu + +local function createHelpMenu() + menu = vgui.Create("DFrame") + GAMEMODE.HelpMenu = menu + menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) + menu:Center() + menu:MakePopup() + menu:SetKeyboardInputEnabled(false) + menu:SetDeleteOnClose(false) + menu:ShowCloseButton(true) + menu:SetTitle("") + menu:SetVisible(false) + + function menu:Paint(w, h) + surface.SetDrawColor(40, 40, 40, 230) + surface.DrawRect(0, 0, w, h) + surface.SetFont("RobotoHUD-25") + draw.ShadowText("Help", "RobotoHUD-25", 8, 2, Color(132, 199, 29), 0) + end + + local text = vgui.Create("DLabel", menu) + text:SetText(helpText) + text:SetWrap(true) + text:SetFont("RobotoHUD-10") + text:Dock(FILL) + + function text:Paint(w, h) end +end + +local function toggleHelpMenu() + if !IsValid(menu) then + createHelpMenu() + end + + menu:SetVisible(!menu:IsVisible()) +end + +net.Receive("ph_openhelpmenu", toggleHelpMenu) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index 4c87718..153e4af 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -1,172 +1,385 @@ -include("cl_colors.lua") -local function createRoboto(s) - surface.CreateFont("RobotoHUD-" .. s , { - font = "Roboto-Bold", - size = math.Round(ScrW() / 1000 * s), - weight = 700, - antialias = true, - italic = false - }) - - surface.CreateFont("RobotoHUD-L" .. s , { - font = "Roboto", - size = math.Round(ScrW() / 1000 * s), - weight = 500, - antialias = true, - italic = false - }) -end - -for i = 5, 50, 5 do - createRoboto(i) -end -createRoboto(8) -createRoboto(12) - -function draw.ShadowText(n, f, x, y, c, px, py, shadowColor) - draw.SimpleText(n, f, x + 1, y + 1, shadowColor || color_black, px, py) - draw.SimpleText(n, f, x, y, c, px, py) -end - function GM:HUDPaint() self:DrawGameHUD() self:DrawRoundTimer() end -local helpKeysProps = { - {"attack", "Disguise as prop"}, - {"menu_context", "Lock prop rotation"}, - {"gm_showspare1", "Taunt"} -} +function GM:HUDShouldDraw(name) + if HudBlacklist[name] then + return + end -local function keyName(str) - str = input.LookupBinding(str) - return str:upper() + return true end function GM:DrawGameHUD() local ply = LocalPlayer() - if self:IsCSpectating() && IsValid(self:GetCSpectatee()) && self:GetCSpectatee():IsPlayer() then + + if self:IsCSpectating() and IsValid(self:GetCSpectatee()) and self:GetCSpectatee():IsPlayer() then ply = self:GetCSpectatee() end --- self:DrawHealth(ply) - - if ply != LocalPlayer() then + if ply ~= LocalPlayer() then local col = team.GetColor(ply:Team()) - draw.ShadowText(ply:Nick(), "RobotoHUD-30", ScrW() / 2, ScrH() - 4, col, 1, 4) + draw.ShadowText(ply:Nick(), "RobotoHUD-30", ScrW() / 2, ScrH() - ScreenScaleH(2), col, 1, 4) end - local tr = ply:GetEyeTraceNoCursor() - local shouldDraw = hook.Run("HUDShouldDraw", "PropHuntersPlayerNames") - if shouldDraw != false then + local eyeTrace = ply:GetEyeTraceNoCursor() + if GAMEMODE:HUDShouldDraw("PropHuntersPlayerNames") then -- draw names - if IsValid(tr.Entity) && tr.Entity:IsPlayer() && tr.HitPos:Distance(tr.StartPos) < 500 then + if IsValid(eyeTrace.Entity) and eyeTrace.Entity:IsPlayer() and eyeTrace.HitPos:Distance(eyeTrace.StartPos) < NameDistance then -- hunters can only see their teams names - if !ply:IsHunter() || ply:Team() == tr.Entity:Team() then - self.LastLooked = tr.Entity + if not ply:IsHunter() or ply:Team() == eyeTrace.Entity:Team() then + self.LastLooked = eyeTrace.Entity self.LookedFade = CurTime() end end - if IsValid(self.LastLooked) && self.LookedFade + 2 > CurTime() then - local name = self.LastLooked:Nick() || "error" + if IsValid(self.LastLooked) and self.LookedFade + 2 > CurTime() then + local name = self.LastLooked:Nick() or "error" local col = table.Copy(team.GetColor(self.LastLooked:Team())) col.a = (1 - (CurTime() - self.LookedFade) / 2) * 255 - draw.ShadowText(name, "RobotoHUD-20", ScrW() / 2, ScrH() / 2 + 80, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, col.a)) + draw.ShadowText(name, "RobotoHUD-20", ScrW() / 2, ScrH() / 1.85, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, col.a)) end end - local help - if LocalPlayer():Alive() then - if LocalPlayer():IsProp() then - if self:GetGameState() == ROUND_HIDE || (self:GetGameState() == ROUND_SEEK && !LocalPlayer():IsDisguised()) then - help = helpKeysProps - end - end + if not LocalPlayer():Alive() or not LocalPlayer():IsProp() or (not self:GetGameState() == ROUND_HIDE and LocalPlayer():IsDisguised()) then + return -- skip over the help text if not above conditions end - if help then - local fh = draw.GetFontHeight("RobotoHUD-L15") - local h = #help * fh - local x = 20 - local y = ScrH() / 2 - h / 2 - local i = 0 - local tw = 0 - for k, t in pairs(help) do - surface.SetFont("RobotoHUD-15") - local name = keyName(t[1]) - local w = surface.GetTextSize(name) - tw = math.max(tw, w) - end + -- prop help text + surface.SetFont("RobotoHUD-15") -- set font for surface.GetTextSize later + local fontSize = draw.GetFontHeight("RobotoHUD-15") + local disguise = input.LookupBinding("attack") + local rotationLock = input.LookupBinding("menu_context") + local tauntMenu = input.LookupBinding("gm_showspare1") - for k, t in pairs(help) do - surface.SetFont("RobotoHUD-15") - local name = keyName(t[1]) - draw.ShadowText(name, "RobotoHUD-15", x + tw / 2, y + i * fh, color_white, 1, 0) - draw.ShadowText(t[2], "RobotoHUD-L15", x + tw + 10, y + i * fh, color_white, 0, 0) - i = i + 1 - end + if not disguise or not rotationLock or not tauntMenu then + draw.ShadowText("UNBOUND!", "RobotoHUD-15", ScreenScale(4), ScrH() / 2, PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.ShadowText(" Please ensure +attack, +menu_context, and gm_showspare1 are bound!", "RobotoHUD-L15", ScreenScale(4) + surface.GetTextSize("UNBOUND!"), ScrH() / 2, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.ShadowText("* MOUSE1, C, and F3 by default.", "RobotoHUD-L12", ScreenScale(4), ScrH() / 2 + fontSize, PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + return end -end - -local polyTex = surface.GetTextureID("VGUI/white.vmt") - -function GM:HUDShouldDraw(name) - if name == "CHudHealth" then return true end - if name == "CHudVoiceStatus" then return false end - if name == "CHudVoiceSelfStatus" then return false end - - return true + + local totalWidth = math.max(surface.GetTextSize(disguise), surface.GetTextSize(rotationLock), surface.GetTextSize(tauntMenu)) + + draw.ShadowText(disguise, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 - fontSize, PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.ShadowText(" Disguise", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 - fontSize, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + + draw.ShadowText(rotationLock, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2, PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.ShadowText(" Lock Rotation", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + + draw.ShadowText(tauntMenu, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 + fontSize, PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.ShadowText(" Taunt Menu", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 + fontSize, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) end function GM:DrawRoundTimer() if self:GetGameState() == ROUND_WAIT then local time = math.ceil(self.StartWaitTime:GetFloat() - self:GetStateRunningTime()) if time > 0 then - draw.ShadowText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScrH() / 10 - draw.GetFontHeight("RobotoHUD-40") / 4, color_white, 1, 4) - draw.ShadowText("Game starts in " .. tostring(time) .. " second" .. (time > 1 && "s" || ""), "RobotoHUD-15", ScrW() / 2, ScrH() / 10, color_white, 1, 1) + draw.ShadowText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Game starts in " .. tostring(time) .. " second" .. (time > 1 and "s" or ""), "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) else - draw.ShadowText("Not enough players to start game", "RobotoHUD-25", ScrW() / 2, ScrH() / 10 - draw.GetFontHeight("RobotoHUD-40") / 4, color_white, 1, 4) - draw.ShadowText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScrH() / 10, color_white, 1, 1) + draw.ShadowText("Not enough players to start", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end - elseif self:GetGameState() == ROUND_HIDE then + return + end + + if self:GetGameState() == ROUND_HIDE then local time = math.ceil(self.HidingTime:GetInt() - self:GetStateRunningTime()) if time > 0 then - draw.ShadowText("Hunters will be released in", "RobotoHUD-15", ScrW() / 2, ScrH() / 3 - draw.GetFontHeight("RobotoHUD-40") / 2, color_white, 1, 4) - draw.ShadowText(time, "RobotoHUD-40", ScrW() / 2, ScrH() / 3, color_white, 1, 1) + draw.ShadowText("Hunters will be released in", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText(time, "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end - elseif self:GetGameState() == ROUND_SEEK then - if self:GetStateRunningTime() < 2 then - draw.ShadowText("GO!", "RobotoHUD-50", ScrW() / 2, ScrH() / 3, color_white, 1, 1) + return + end + + if self:GetGameState() ~= ROUND_SEEK then + return + end + + if self:GetStateRunningTime() < 2 then + draw.ShadowText("GO!", "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + return + end + + local settings = self:GetRoundSettings() + local roundTime = settings.RoundTime or 5 * 60 + local time = tostring(math.max(0, roundTime - self:GetStateRunningTime())) + draw.ShadowText("Round "..GAMEMODE.CurrentRound.."/"..GAMEMODE.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText(string.ToMinutesSeconds(time), "RobotoHUD-20", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-L15"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) +end + +function GM:PreDrawHUD() + if self:GetGameState() ~= ROUND_HIDE or not LocalPlayer():IsHunter() then + return + end + + cam.Start2D() + surface.SetDrawColor(25, 25, 25, 255) + surface.DrawRect(0, 0, ScrW(), ScrH()) + cam.End2D() +end + +-- former cl_voicepanels.lua +local PANEL = {} +local PlayerVoicePanels = {} + +function PANEL:Init() + self.Avatar = vgui.Create("AvatarImage", self) + self.Avatar:Dock(LEFT) + self.Avatar:SetSize(math.Clamp(ScreenScaleH(16), 16, 32), math.Clamp(ScreenScaleH(16), 16, 32)) + self.Color = color_transparent + self:SetSize(math.Clamp(ScreenScaleH(16), 16, 32), math.Clamp(ScreenScaleH(16), 16, 32)) + self:DockPadding(ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2)) + self:DockMargin(ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2)) + self:Dock(BOTTOM) +end + +function PANEL:Setup(ply) + self.ply = ply + self.Avatar:SetPlayer(ply, 64) + self.Color = team.GetColor(ply:Team()) + self:InvalidateLayout() +end + +function PANEL:Paint(w, h) + if not IsValid(self.ply) then return end + + local volume = self.ply:VoiceVolume() + col = team.GetColor(self.ply:Team()) + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(col.r, col.g, col.b, 120) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawOutlinedRect(0, 0, w, h) + surface.SetDrawColor(col.r, col.g, col.b, 120) + surface.DrawRect(0, 0, math.floor(w * volume), h) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawOutlinedRect(0, 0, w * volume, h) + + draw.ShadowText(self.ply:Nick(), "RobotoHUD-12", self.Avatar:GetWide() + ScreenScale(4), h / 2, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +function PANEL:Think() + if self.fadeAnim then + self.fadeAnim:Run() + end +end + +function PANEL:FadeOut(anim, delta, data) + if anim.Finished then + if IsValid(PlayerVoicePanels[self.ply]) then + PlayerVoicePanels[self.ply]:Remove() + PlayerVoicePanels[self.ply] = nil + return end - local settings = self:GetRoundSettings() - local roundTime = settings.RoundTime || 5 * 60 - local time = math.max(0, roundTime - self:GetStateRunningTime()) - net.Receive("rounds", function(len) - self.rounds = net.ReadInt(5) - end) - if self.rounds == nil then - self.rounds = 1 + return + end + + self:SetAlpha(255 - (255 * delta)) +end + +derma.DefineControl("VoiceNotify", "", PANEL, "DPanel") + +function GM:PlayerStartVoice(ply) + if not IsValid(g_VoicePanelList) then return end + + -- There'd be an exta one if voice_loopback is on, so remove it. + GAMEMODE:PlayerEndVoice(ply) + + if IsValid(PlayerVoicePanels[ply]) then + if PlayerVoicePanels[ply].fadeAnim then + PlayerVoicePanels[ply].fadeAnim:Stop() + PlayerVoicePanels[ply].fadeAnim = nil end - local m = math.floor(time / 60) - local s = math.floor(time % 60) - m = tostring(m) - s = s < 10 && "0" .. s || tostring(s) - local fh = draw.GetFontHeight("RobotoHUD-L15") * 1 - draw.ShadowText("Round "..self.rounds.." / "..self.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, 20, color_white, 1, 3) - draw.ShadowText(m .. ":" .. s, "RobotoHUD-20", ScrW() / 2, fh + 20, color_white, 1, 3) + + PlayerVoicePanels[ply]:SetAlpha(255) + return end + + if not IsValid(ply) then return end + + local pnl = g_VoicePanelList:Add("VoiceNotify") + pnl:Setup(ply) + PlayerVoicePanels[ply] = pnl end -function GM:PreDrawHUD() - local client = LocalPlayer() - if self:GetGameState() == ROUND_HIDE then - if client:IsHunter() then - surface.SetDrawColor(25, 25, 25, 255) - surface.DrawRect(-10, -10, ScrW() + 20, ScrH() + 20) +local function VoiceClean() + for k, v in pairs(PlayerVoicePanels) do + if not IsValid(k) or not k:IsPlayer() then + GAMEMODE:PlayerEndVoice(k) end end end +timer.Create("VoiceClean", 10, 0, VoiceClean) + +function GM:PlayerEndVoice(ply) + if IsValid(PlayerVoicePanels[ply]) then + if PlayerVoicePanels[ply].fadeAnim then return end + + PlayerVoicePanels[ply].fadeAnim = Derma_Anim("FadeOut", PlayerVoicePanels[ply], PlayerVoicePanels[ply].FadeOut) + PlayerVoicePanels[ply].fadeAnim:Start(2) + end +end + +local function CreateVoiceVGUI() + g_VoicePanelList = vgui.Create("DPanel") + g_VoicePanelList:ParentToHUD() + g_VoicePanelList:DockMargin(0, 0, ScreenScaleH(16), ScreenScaleH(48)) -- align with ammo hud ig + g_VoicePanelList:Dock(RIGHT) + g_VoicePanelList:SetSize(ScreenScaleH(136), ScreenScaleH(96)) + g_VoicePanelList:SetPaintBackground(false) +end + +hook.Add("InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI) + +--former cl_killfeed.lua +local killFeedEvents = {} + +local KILL_FEED_MESSAGE_TIMEOUT = 10 -- How long a message stays in the kill feed before starting to fade away. +local VISUALS_UPDATE_INTERVAL = 0.05 -- Tick speed of the visual update timer. Smaller number = smoother visuals. +local FADE_TIME = 1.5 -- How long it takes for an expiring kill feed message to fade away. + +local NUM_FADE_TICKS = math.floor(FADE_TIME / VISUALS_UPDATE_INTERVAL) +local FADE_AMOUNT = math.floor(255 / NUM_FADE_TICKS) + +local FONT = "RobotoHUD-15" -- Font used for kill feed messages. + +function GM:ClearKillFeed() + killFeedEvents = {} +end + +-- This timer handles the visual updates for kill feed messages. +-- New messages are given a "slide in" effect wherein they slide in from off-screen. +-- Expiring messages are given a fade out effect. +timer.Create("ph_timer_kill_feed_visuals", VISUALS_UPDATE_INTERVAL, 0, function() + for i, event in ipairs(killFeedEvents) do + local finalPos = ScrW() - ScreenScaleH(4) - event.eventWidth + -- Handle slide in effect. + if event.new then + -- The distance to slide in a single "frame" is going to be proportional to how much distance is + -- left to the final position. This will be scaled up by 50% to make things go faster. + local subtractAmt = math.floor((event.x - finalPos) * 0.5) + -- Always move by at least one pixel. + if subtractAmt < 1 then + subtractAmt = 1 + end + + event.x = event.x - subtractAmt + if event.x < finalPos then + event.x = finalPos + event.new = false + end + -- Handle the fade out effect. + elseif event.entryTime + KILL_FEED_MESSAGE_TIMEOUT < CurTime() then + if event.attackerName then + event.attackerColor.a = event.attackerColor.a - FADE_AMOUNT + end + event.victimColor.a = event.victimColor.a - FADE_AMOUNT + event.messageColor.a = event.messageColor.a - FADE_AMOUNT + -- Remove message from the kill feed once it is completely transparent. + if event.messageColor.a < 0 then + table.remove(killFeedEvents, i) + end + end + end +end) + +net.Receive("ph_kill_feed_add", function(len) + local event = net.ReadTable() + event.entryTime = CurTime() + event.new = true -- New events are events that are in the process of "sliding in" to view. + + -- Starting x position. Individual draw statements will be relative to this value. + event.x = ScrW() - ScreenScaleH(4) -- This will be offscreen. + + -- Calculate the final width of the entire kill feed event. + surface.SetFont(FONT) + event.eventWidth = surface.GetTextSize(event.victimName) + surface.GetTextSize(" ") + surface.GetTextSize(event.message) + if event.attackerName then + event.eventWidth = event.eventWidth + surface.GetTextSize(event.attackerName) + surface.GetTextSize(" ") + end + + -- New events will start at the top. + event.y = ScreenScaleH(4) + -- Shift existing events downward. + for _, kfEvent in ipairs(killFeedEvents) do + kfEvent.y = kfEvent.y + draw.GetFontHeight(FONT) + end + + table.insert(killFeedEvents, event) +end) + +local function drawKillFeedHUD() + for _, event in ipairs(killFeedEvents) do + local widthOffset = event.x + + -- If Murder: [Attacker] [Message] [Victim] + -- If Suicide: [Victim] [Message] + + if event.attackerName then + widthOffset = widthOffset + draw.ShadowText(event.attackerName .. " ", FONT, widthOffset, event.y, event.attackerColor) + else + widthOffset = widthOffset + draw.ShadowText(event.victimName .. " ", FONT, widthOffset, event.y, event.victimColor) + end + + widthOffset = widthOffset + draw.ShadowText(event.message .. (event.attackerName and " " or ""), FONT, widthOffset, event.y, event.messageColor) + + if event.attackerName then + draw.ShadowText(event.victimName, FONT, widthOffset, event.y, event.victimColor) + end + end +end + +hook.Add("HUDPaint", "ph_kill_feed_hud_paint", drawKillFeedHUD) + +-- former cl_aimlaser.lua +local beamMat = Material("ultimateph/aim_laser") +local ray_color = PHGreen +local ray_width = 6 + +local dotMat = Material("ultimateph/aim_dot") +local dot_color = PHWhite + +local hunters_aim = {} + +function GM:PostDrawTranslucentRenderables() + hunters_aim = {} + self:GetHuntersAim() + + -- When "ph_hunter_aim_laser" set to 0, nobody can see the beam + -- set to 1 == spectator only, set to 2 == props and spectator + -- Also, we don't want the hunters to see the ray, to prevent eventual visual bugs + local visibility = GetConVar("ph_hunter_aim_laser"):GetInt() + + if visibility == 0 or (visibility == 1 and not LocalPlayer():IsSpectator()) or (visibility == 2 and not (LocalPlayer():IsSpectator() or LocalPlayer():IsProp())) then + return + end + + for _, h_aim in pairs(hunters_aim) do + render.SetMaterial(beamMat) + render.DrawBeam(h_aim.eye_pos, h_aim.looking.HitPos, ray_width, 0, 1, ray_color) + + render.SetMaterial(dotMat) + local size = math.random(8, 16) + render.DrawQuadEasy(h_aim.looking.HitPos + h_aim.looking.HitNormal, h_aim.looking.HitNormal, size, size, dot_color, 0) + end +end + +function GM:GetHuntersAim() + for _, ply in ipairs(player.GetAll()) do + if not ply:IsHunter() then + continue + end + + local t = {} + t.eye_pos = ply:EyePos() + t.looking = ply:GetEyeTrace() + table.insert(hunters_aim, t) + end +end diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index 6d64380..5ace97b 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -1,16 +1,50 @@ +include("sh_taunt.lua") +include("sh_init.lua") +include("sh_config.lua") +include("sh_rounds.lua") +include("sh_disguise.lua") +include("sh_bannedmodels.lua") +include("sh_mapvote.lua") include("cl_hud.lua") include("cl_scoreboard.lua") -include("cl_voicepanels.lua") include("cl_helpscreen.lua") include("cl_endroundboard.lua") -include("cl_colors.lua") +include("cl_taunt.lua") include("cl_utils.lua") -include("sh_rounds.lua") -include("sh_disguise.lua") -include("sh_taunt.lua") -include("sh_bannedmodels.lua") -include("sh_mapvote.lua") -include("sh_init.lua") + +surface.CreateFont( "PHIcons", { + font = "marlett", + size = math.Clamp( ScreenScaleH(12), 12, 16 ), weight = 700, antialias = true, symbol = true, +}) + +local function createRoboto(s) + surface.CreateFont("RobotoHUD-" .. s , { + font = "Roboto-Bold", + size = math.Clamp( ScreenScaleH(s), s, (s * 2) ), + weight = 700, + antialias = true, + italic = false + }) + + surface.CreateFont("RobotoHUD-L" .. s , { + font = "Roboto", + size = math.Clamp( ScreenScaleH(s), s, (s * 2) ), + weight = 500, + antialias = true, + italic = false + }) +end + +for i = 5, 50, 5 do + createRoboto(i) +end +createRoboto(8) +createRoboto(12) + +function draw.ShadowText(text, font, x, y, color, xalign, yalign, shadowColor) + draw.SimpleText(text, font, x + ScreenScaleH(1), y + ScreenScaleH(1), shadowColor or color_black, xalign, yalign) + return draw.SimpleText(text, font, x, y, color, xalign, yalign) +end function GM:InitPostEntity() net.Start("clientIPE") diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index d8f9e65..0bb335d 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -1,35 +1,12 @@ -if GAMEMODE && IsValid(GAMEMODE.ScoreboardPanel) then +if GAMEMODE and IsValid(GAMEMODE.ScoreboardPanel) then GAMEMODE.ScoreboardPanel:Remove() end local menu -include("cl_colors.lua") - -GroupColors = {} -GroupColors["owner"] = PHGreen -GroupColors["superadmin"] = PHPink -GroupColors["headadmin"] = PHMauve -GroupColors["admin"] = PHYellow -GroupColors["moderator"] = PHBlue -GroupColors["operator"] = PHPink -GroupColors["superowner"] = PHRed -GroupColors["trusted"] = PHPeach -GroupColors["user"] = PHTeal - -GroupNames = {} -GroupNames["owner"] = "Owner" -GroupNames["superadmin"] = "Super Admin" -GroupNames["headadmin"] = "Head Admin" -GroupNames["admin"] = "Admin" -GroupNames["moderator"] = "Moderator" -GroupNames["operator"] = "Operator" -GroupNames["superowner"] = "Super Owner" -GroupNames["trusted"] = "Trusted" -GroupNames["user"] = "User" surface.CreateFont("ScoreboardPlayer" , { font = "coolvetica", - size = 32, + size = math.Clamp( ScreenScaleH(16), 16, 32 ), weight = 500, antialias = true, italic = false @@ -42,43 +19,46 @@ local function addPlayerItem(self, mlist, ply, pteam) local but = vgui.Create("DButton") but.player = ply but.ctime = CurTime() - but:SetTall(draw.GetFontHeight("RobotoHUD-20")) + but:SetTall(draw.GetFontHeight("RobotoHUD-20") + ScreenScaleH(4)) but:SetText("") function but:Paint(w, h) - if GroupColors[ply:GetUserGroup()] ~= nil then - surface.SetDrawColor(Color(GroupColors[ply:GetUserGroup()].r, GroupColors[ply:GetUserGroup()].g, GroupColors[ply:GetUserGroup()].b)) - else - surface.SetDrawColor(PHGreen) - end - self:DrawFilledRect() - - if IsValid(ply) && ply:IsPlayer() then - local s = 4 - if !ply:Alive() then + surface.SetDrawColor(color_black) + + if IsValid(ply) and ply:IsPlayer() then + local s = ScreenScaleH(2) + if not ply:Alive() then surface.SetMaterial(skull) surface.SetDrawColor(220, 220, 220, 255) - surface.DrawTexturedRect(s, h / 2 - 16, 32, 32) - s = s + 32 + 4 + surface.DrawTexturedRect(s, 0, ScreenScaleH(16), ScreenScaleH(16)) + s = s + ScreenScaleH(16) end - + if ply:IsMuted() then surface.SetMaterial(muted) -- draw mute icon surface.SetDrawColor(150, 150, 150, 255) - surface.DrawTexturedRect(s, h / 2 - 16, 32, 32) - s = s + 32 + 4 + surface.DrawTexturedRect(s, 0, ScreenScaleH(16), ScreenScaleH(16)) + s = s + ScreenScaleH(16) end - local col = color_white - draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - 4, 0, col, 2) - if GroupNames[ply:GetUserGroup()] then - draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHDarkest, 0) - s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + 4 - draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) + -- group tags :D + if tobool(GroupTags) then + if GroupColors[ply:GetUserGroup()] ~= nil then + surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) + self:DrawFilledRect() + end + + if GroupNames[ply:GetUserGroup()] then + draw.ShadowText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHScobDarkest, 0) + s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + ScreenScaleH(2) + draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, 0, PHWhite, 0) + draw.ShadowText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), 0, PHWhite, 2) + end else - draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) + draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, 0, PHWHite, 0) + draw.ShadowText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), 0, PHWhite, 2) end end end @@ -102,14 +82,14 @@ local function doPlayerItems(self, mlist, pteam) end end - if !found then + if not found then addPlayerItem(self, mlist, ply, pteam) end end local del = false for t, v in pairs(mlist:GetCanvas():GetChildren()) do - if !v.perm && v.ctime != CurTime() then + if not v.perm and v.ctime ~= CurTime() then v:Remove() del = true end @@ -128,30 +108,26 @@ local function makeTeamList(parent, pteam) local hs = math.Round(draw.GetFontHeight("RobotoHUD-25") * 1.1) function pnl:Paint(w, h) - surface.SetDrawColor(PHDarkest) - surface.DrawLine(0, hs, 0, h - 1) - surface.DrawLine(w - 1, hs, w - 1, h - 1) - surface.DrawLine(0, h - 1, w, h - 1) - surface.SetDrawColor(PHDarker) - surface.DrawRect(1, hs, w - 2, h - hs) + surface.SetDrawColor(PHScobDark) + surface.DrawRect(0, hs, w, h - hs ) end function pnl:Think() - if !self.RefreshWait || self.RefreshWait < CurTime() then + if not self.RefreshWait or self.RefreshWait < CurTime() then self.RefreshWait = CurTime() + 0.1 doPlayerItems(self, mlist, pteam) end end local headp = vgui.Create("DPanel", pnl) - headp:DockMargin(0, 0, 0, 4) + headp:DockMargin(0, 0, 0, ScreenScaleH(2)) headp:Dock(TOP) headp:SetTall(hs) function headp:Paint(w, h) - surface.SetDrawColor(68, 68, 68, 255) - draw.RoundedBoxEx(4, 0, 0, w, h, PHDarkest, true, true, false, false) - draw.SimpleText(team.GetName(pteam), "RobotoHUD-25", 6, 0, team.GetColor(pteam), 0) + surface.SetDrawColor(PHScobDarker) + draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker, true, true, false, false) + draw.ShadowText(team.GetName(pteam), "RobotoHUD-25", ScreenScaleH(6), 0, team.GetColor(pteam), 0) end local but = vgui.Create("DButton", headp) @@ -159,7 +135,7 @@ local function makeTeamList(parent, pteam) but:SetText("") surface.SetFont("RobotoHUD-20") local tw, th = surface.GetTextSize("Join team") - but:SetWide(tw + 6) + but:SetWide(tw + ScreenScaleH(6)) function but:DoClick() RunConsoleCommand("ph_jointeam", pteam) @@ -167,22 +143,15 @@ local function makeTeamList(parent, pteam) function but:Paint(w, h) surface.SetDrawColor(team.GetColor(pteam)) - surface.SetDrawColor(color_black) local col = table.Copy(team.GetColor(pteam)) - if self:IsDown() then - surface.SetDrawColor(12, 50, 50, 120) - col.r = col.r * 0.8 - col.g = col.g * 0.8 - col.b = col.b * 0.8 - elseif self:IsHovered() then - surface.SetDrawColor(255, 255, 255, 30) - col.r = col.r * 1.2 - col.g = col.g * 1.2 - col.b = col.b * 1.2 - end - - draw.SimpleText("Join team", "RobotoHUD-20", 2, h / 2 - th / 2, col, 0) + if self:IsDown() then + colMul(col, 0.8) + elseif self:IsHovered() then + colMul(col, 1.2) + end + + draw.ShadowText("Join team", "RobotoHUD-20", ScreenScaleH(2), h / 2 - th / 2, col, 0) end mlist = vgui.Create("DScrollPanel", pnl) @@ -193,21 +162,20 @@ local function makeTeamList(parent, pteam) -- child positioning local canvas = mlist:GetCanvas() - canvas:DockPadding(8, 8, 8, 8) + canvas:DockPadding(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4)) function canvas:OnChildAdded(child) child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) + child:DockMargin(0, 0, 0, ScreenScaleH(2)) end local head = vgui.Create("DPanel") head:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.05) head.perm = true - local col = Color(190, 190, 190) function head:Paint(w, h) - draw.SimpleText("Name", "RobotoHUD-15", 4, 0, col, 0) - draw.SimpleText("Ping", "RobotoHUD-15", w - 4, 0, col, 2) + draw.ShadowText("Name", "RobotoHUD-15", ScreenScaleH(2), 0, PHLessWhite, 0) + draw.ShadowText("Ping", "RobotoHUD-15", w - ScreenScaleH(2), 0, PHLessWhite, 2) end mlist:AddItem(head) @@ -223,7 +191,7 @@ end local function createScoreboardPanel() menu = vgui.Create("DFrame") GAMEMODE.ScoreboardPanel = menu - menu:SetSize(ScrW() * 0.8, ScrH() * 0.8) + menu:SetSize(ScreenScale(512), ScreenScaleH(384)) menu:Center() menu:MakePopup() menu:SetKeyboardInputEnabled(false) @@ -231,29 +199,56 @@ local function createScoreboardPanel() menu:SetDraggable(false) menu:ShowCloseButton(false) menu:SetTitle("") - menu:DockPadding(8, 8, 8, 8) + menu:DockPadding(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4)) function menu:PerformLayout() if IsValid(menu.HuntersList) then - menu.HuntersList:SetWidth((self:GetWide() - 16) * 0.5) + menu.HuntersList:SetWidth((self:GetWide() - ScreenScaleH(8)) * 0.5) end end function menu:Paint(w, h) - surface.SetDrawColor(0, 0, 0, 0) - surface.DrawRect(0, 0, w, h) + if not tobool(ScobBackground) then + return + end + surface.SetDrawColor(PHScobBlack) + surface.DrawOutlinedRect(0, 0, w, h, math.Clamp(ScreenScaleH(2), 2, 4)) + surface.SetDrawColor(PHScobDarkest) + surface.DrawRect(math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4), w - math.Clamp(ScreenScaleH(4), 4, 8), h - math.Clamp(ScreenScaleH(4), 4, 8)) + end + + menu.Credits = vgui.Create("DPanel", menu) + menu.Credits:Dock(TOP) + menu.Credits:DockMargin(0, 0, 0, ScreenScaleH(2)) + + function menu.Credits:Paint(w, h) + if not tobool(ScobBackground) then + return + end + + surface.SetFont("RobotoHUD-25") + local t = GAMEMODE.Name or "" + local tw = surface.GetTextSize(t) + draw.ShadowText(t, "RobotoHUD-25", ScreenScaleH(2), draw.GetFontHeight("RobotoHUD-25"), PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.ShadowText("- "..tostring(GAMEMODE.Version or "error") .. ", maintained by DataNext, code by many cool people :)", "RobotoHUD-L12", tw + ScreenScaleH(8), draw.GetFontHeight("RobotoHUD-L12") * 2, PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + end + + function menu.Credits:PerformLayout() + surface.SetFont("RobotoHUD-25") + local _, h = surface.GetTextSize(GAMEMODE.Name || "") + self:SetTall(h) end - - local bottom = vgui.Create("DPanel", menu) + + local bottom = vgui.Create("DPanel", menu) bottom:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) bottom:Dock(BOTTOM) - bottom:DockMargin(0, 8, 0, 0) + bottom:DockMargin(0, ScreenScaleH(4), 0, 0) surface.SetFont("RobotoHUD-15") local tw = surface.GetTextSize("Spectate") function bottom:Paint(w, h) - draw.RoundedBox(0, 0, 0, w, h, PHDarker) + draw.RoundedBox(0, 0, 0, w, h, PHScobDarker) local c for k, ply in pairs(team.GetPlayers(TEAM_SPEC)) do if c then @@ -264,26 +259,26 @@ local function createScoreboardPanel() end if c then - draw.ShadowText(c, "RobotoHUD-10", tw + 8 + 4, h / 2, color_white, 0, 1) + draw.ShadowText(c, "RobotoHUD-10", tw + ScreenScaleH(4) + ScreenScaleH(2), h / 2, PHWhite, 0, 1) end end local but = vgui.Create("DButton", bottom) but:Dock(LEFT) but:SetText("") - but:DockMargin(0, 0, 4, 0) - but:SetWide(tw + 8) - - function but:Paint(w, h) - local col = PHWhite - if self:IsDown() then - col = PHLessWhite - elseif self:IsHovered() then - col = PHLessWhite - end + but:DockMargin(0, 0, ScreenScaleH(2), 0) + but:SetWide(tw + ScreenScaleH(4)) + + function but:Paint(w, h) + local colt = table.Copy(PHLessWhite) + if self:IsDown() then + colMul(col, 0.8) + elseif self:IsHovered() then + colMul(col, 1.2) + end - draw.RoundedBox(0, 0, 0, w, h, PHDarkest) - draw.SimpleText("Spectate", "RobotoHUD-15", w / 2, h / 2, col, 1, 1) + draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker) + draw.ShadowText("Spectate", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) end function but:DoClick() @@ -294,18 +289,18 @@ local function createScoreboardPanel() main:Dock(FILL) function main:Paint(w, h) - surface.SetDrawColor(40, 40, 40, 0) + surface.SetDrawColor(PHScobDarkest) end menu.HuntersList = makeTeamList(main, TEAM_HUNTER) menu.HuntersList:Dock(LEFT) - menu.HuntersList:DockMargin(0, 0, 8, 0) + menu.HuntersList:DockMargin(0, 0, ScreenScaleH(4), 0) menu.PropsList = makeTeamList(main, TEAM_PROP) menu.PropsList:Dock(FILL) end function GM:ScoreboardShow() - if !IsValid(menu) then + if not IsValid(menu) then createScoreboardPanel() end @@ -315,14 +310,18 @@ end function GM:ScoreboardHide() if IsValid(menu) then menu:Close() - DermaMenu() end end function GM:DoScoreboardActionPopup(ply) local actions = DermaMenu() - if ply != LocalPlayer() then + if ply:IsAdmin() then + local admin = actions:AddOption("Is an Admin") + admin:SetIcon("icon16/shield.png") + end + + if ply ~= LocalPlayer() then local t = "Mute" if ply:IsMuted() then t = "Unmute" @@ -333,30 +332,7 @@ function GM:DoScoreboardActionPopup(ply) function mute:DoClick() if IsValid(ply) then - ply:SetMuted(!ply:IsMuted()) - end - end - end - - local t = "View Steam Profile" - local steamprofile = actions:AddOption(t) - steamprofile:SetIcon("icon16/application.png") - - function steamprofile:DoClick() - if IsValid(ply) then - gui.OpenURL("https://steamcommunity.com/profiles/".. ply:SteamID64()) - end - end - --- this requires ulx - if LocalPlayer():IsAdmin() then - local t = "Force Team Switch" - local switchteams = actions:AddOption(t) - switchteams:SetIcon("icon16/shield.png") - - function switchteams:DoClick() - if IsValid(ply) then - LocalPlayer():ConCommand("ulx teamswitch ".. ply:Nick()) + ply:SetMuted(not ply:IsMuted()) end end end diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua new file mode 100644 index 0000000..c4b987a --- /dev/null +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -0,0 +1,268 @@ +local menu +local lastCursorX +local lastCursorY + +local function saveCursor() + lastCursorX, lastCursorY = input.GetCursorPos() +end + +local function restoreCursor() + if not lastCursorX then + return + end + + input.SetCursorPos(lastCursorX, lastCursorY) + lastCursorX = nil + lastCursorY = nil +end + +local function fillList(mlist, taunts, cat) + menu.CurrentTaunts = taunts + menu.CurrentTauntCat = cat + for k, v in pairs(menu.CatList:GetCanvas():GetChildren()) do + v.Selected = false + if v.CatName == cat then + v.Selected = true + end + end + + mlist:Clear() + for k, t in pairs(taunts) do + if not TauntAllowedForPlayer(LocalPlayer(), t) then + continue + end + + local but = vgui.Create("DButton") + but:SetTall(draw.GetFontHeight("RobotoHUD-L15") * 1.0) + but:SetText("") + + function but:Paint(w, h) + local col = table.Copy(PHWhite) + if self:IsDown() then + colMul(col, 0.5) + elseif self:IsHovered() then + colMul(col, 0.8) + end + draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.ShadowText(math.Round(t.soundDuration % 60, 2) .. "s", "RobotoHUD-L10", w - ScreenScaleH(2), h / 2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + function but:DoClick() + RunConsoleCommand("ph_taunt", t.sound[math.random(#t.sound)]) + saveCursor() + menu:Close() + end + + mlist:AddItem(but) + end +end + +local function addCat(clist, name, taunts, mlist) + local dname = name:lower():gsub("[_]", " ") + dname = dname:sub(1, 1):upper() .. dname:sub(2) + + local but = vgui.Create("DButton") + but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.25) + but:SetText("") + but.Selected = false + but.CatName = name + + function but:Paint(w, h) + local col = table.Copy(PHTauntDark) + local colt = table.Copy(PHLessWhite) + if not self.Selected then + colMul(col, 0.7) + if self:IsDown() then + colMul(colt, 0.5) + elseif self:IsHovered() then + colMul(colt, 1.2) + end + else + colMul(colt, 1.2) + end + + draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, col, true, false, true, false) + draw.ShadowText(dname, "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + function but:DoClick() + fillList(mlist, taunts, name) + self.Selected = true + end + + clist:AddItem(but) + return but +end + +local function fillCats(clist, mlist) + clist:Clear() + local all = addCat(clist, "all", Taunts, mlist) + for k, taunts in pairs(TauntCategories) do + local c = 0 + for a, t in pairs(taunts) do + if not TauntAllowedForPlayer(LocalPlayer(), t) then continue end + + c = c + 1 + end + + if c > 0 then + addCat(clist, k, taunts, mlist) + end + end + + all.Selected = true +end + +local function openTauntMenu() + restoreCursor() + if IsValid(menu) then + fillCats(menu.CatList, menu.TauntList) + fillList(menu.TauntList, menu.CurrentTaunts, menu.CurrentTauntCat) + if menu:IsVisible() then + saveCursor() + end + + menu:SetVisible(not menu:IsVisible()) + return + end + + menu = vgui.Create("DFrame") + menu:SetSize(ScrH() * 0.75, ScrH() * 0.75) + menu:Center() + menu:SetTitle("") + menu:MakePopup() + menu:SetKeyboardInputEnabled(false) + menu:SetDeleteOnClose(false) + menu:SetDraggable(false) + menu:ShowCloseButton(false) + menu:DockPadding(ScreenScaleH(4), ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), ScreenScaleH(4), ScreenScaleH(4)) + + local closeButton = vgui.Create('DButton', menu) + closeButton:SetFont('PHIcons') + closeButton:SetText('r') + closeButton.Paint = function(s,w,h) + if not closeButton:IsHovered() then + draw.RoundedBox(0, 0, 0, w, h, Color(0,0,0,0)) + else + draw.RoundedBox(0, 0, 0, w, h, PHTauntDarker) + end + end + closeButton:SetColor(PHWhite) + closeButton:SetSize(ScreenScaleH(16), ScreenScaleH(12)) + closeButton:SetPos(math.Round(menu:GetWide() - closeButton:GetWide() - math.Clamp(ScreenScaleH(2), 2, 4)), math.Clamp(ScreenScaleH(2), 2, 4)) + closeButton.DoClick = function() + menu:Close() + end + + local matBlurScreen = Material("pp/blurscreen") + + function menu:Paint(w, h) + -- copy the blur from endround panel for more consistent UI + local x, y = self:LocalToScreen(0, 0) + local Fraction = 0.4 + + surface.SetMaterial(matBlurScreen) + surface.SetDrawColor(255, 255, 255, 255) + + for i = 0.33, 1, 0.33 do + matBlurScreen:SetFloat("$blur", Fraction * 5 * i) + matBlurScreen:Recompute() + if render then render.UpdateScreenEffectTexture() end + surface.DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) + end + + surface.SetDrawColor(PHTauntBlack) + surface.DrawOutlinedRect(0, 0, w, h, math.Clamp(ScreenScaleH(2), 2, 4)) + surface.SetDrawColor(PHTauntDarker) + surface.DrawRect(math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4), w - math.Clamp(ScreenScaleH(4), 4, 8), h - math.Clamp(ScreenScaleH(4), 4, 8)) + surface.SetFont("RobotoHUD-25") + local t = "Taunts" + local tw = surface.GetTextSize(t) + draw.ShadowText(t, "RobotoHUD-25", ScreenScaleH(4), draw.GetFontHeight("RobotoHUD-25"), PHBlue, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.ShadowText("- ".. TauntMenuPhrase, "RobotoHUD-L15", ScreenScaleH(8) + tw, draw.GetFontHeight("RobotoHUD-L15"), PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local leftpnl = vgui.Create("DPanel", menu) + leftpnl:Dock(LEFT) + leftpnl:DockMargin(0, 0, 0, 0) + leftpnl:SetWide(menu:GetWide() / 4) + function leftpnl:Paint(w, h) + end + + local but = vgui.Create("DButton", leftpnl) + but:Dock(BOTTOM) + but:DockMargin(0, ScreenScaleH(2), ScreenScaleH(2), 0) + but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.25) + but:SetText("") + + function but:Paint(w, h) + local col = table.Copy(PHTauntDark) + local colt = table.Copy(PHLessWhite) + if self:IsDown() then + colMul(colt, 0.5) + elseif self:IsHovered() then + colMul(colt, 1.2) + end + + draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, col, true, true, true, true) + draw.ShadowText("Random", "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + function but:DoClick() + RunConsoleCommand("ph_taunt_random") + saveCursor() + menu:Close() + end + + local clist = vgui.Create("DScrollPanel", leftpnl) + menu.CatList = clist + clist:Dock(FILL) + clist:DockMargin(0, 0, 0, 0) + + function clist:Paint(w, h) + end + + local canvas = clist:GetCanvas() + canvas:DockPadding(0, 0, 0, 0) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, ScreenScaleH(2)) + end + + local mlist = vgui.Create("DScrollPanel", menu) + menu.TauntList = mlist + mlist:Dock(FILL) + + local vbar = mlist:GetVBar() + vbar:SetWide(0) + + function mlist:Paint(w, h) + surface.SetDrawColor(PHTauntDark) + surface.DrawOutlinedRect(0, 0, w, h, math.Clamp(ScreenScaleH(2), 2, 4)) + surface.SetDrawColor(PHTauntDarkest) + end + + -- child positioning + local canvas = mlist:GetCanvas() + canvas:DockPadding(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4)) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, 0, 0, 0) + end + + fillList(mlist, Taunts) + fillCats(clist, mlist, "all") +end + +concommand.Add("ph_menu_taunt", openTauntMenu) +net.Receive("open_taunt_menu", openTauntMenu) + +net.Receive("ph_set_taunt_menu_phrase", function() + value_new = net.ReadString() + + -- Prevent this from being done more than once. GMod is weird. + if TauntMenuPhrase == value_new then return end + TauntMenuPhrase = value_new +end) diff --git a/gamemodes/ultimateph/gamemode/cl_utils.lua b/gamemodes/ultimateph/gamemode/cl_utils.lua index ce2ea02..8a85112 100644 --- a/gamemodes/ultimateph/gamemode/cl_utils.lua +++ b/gamemodes/ultimateph/gamemode/cl_utils.lua @@ -1,46 +1,8 @@ --- former cl_aimlaser.lua -local beamMat = Material("ultimateph/aim_laser") -local ray_color = Color(50,170,46) -local ray_width = 6 - -local dotMat = Material("ultimateph/aim_dot") -local dot_color = Color(255,255,255) - -local hunters_aim = {} - -function GM:PostDrawTranslucentRenderables() - hunters_aim = {} - self:GetHuntersAim() - - -- When "ph_hunter_aim_laser" set to 0, nobody can see the beam - -- set to 1 == spectator only, set to 2 == props and spectator - -- Also, we don't want the hunters to see the ray, to prevent eventual visual bugs - local visibility = GetConVar("ph_hunter_aim_laser"):GetInt() - - if visibility == 0 then return end - - if visibility == 1 and not LocalPlayer():IsSpectator() then return end - if visibility == 2 and not (LocalPlayer():IsSpectator() or LocalPlayer():IsProp()) then return end - - for _, h_aim in pairs(hunters_aim) do - render.SetMaterial(beamMat) - render.DrawBeam(h_aim.eye_pos, h_aim.looking.HitPos, ray_width, 0, 1, ray_color) - - render.SetMaterial(dotMat) - local size = math.random(8, 16) - render.DrawQuadEasy(h_aim.looking.HitPos + h_aim.looking.HitNormal, h_aim.looking.HitNormal, size, size, dot_color, 0) - end -end - -function GM:GetHuntersAim() - for _, ply in pairs(player.GetAll()) do - if ply:IsHunter() then - local t = {} - t.eye_pos = ply:EyePos() - t.looking = ply:GetEyeTrace() - table.insert(hunters_aim, t) - end - end +-- new colmult +function colMul(color, mul) + color.r = math.Clamp(math.Round(color.r * mul), 0, 255) + color.g = math.Clamp(math.Round(color.g * mul), 0, 255) + color.b = math.Clamp(math.Round(color.b * mul), 0, 255) end -- former cl_chatmsg.lua @@ -112,7 +74,7 @@ function GM:GetCSpectateMode() return self.SpectateMode end --- formar cl_ragdoll.lua +-- former cl_ragdoll.lua local PlayerMeta = FindMetaTable("Player") local EntityMeta = FindMetaTable("Entity") @@ -141,121 +103,3 @@ function EntityMeta:GetRagdollOwner() return self:GetRagdollOwnerOld() end - ---former cl_killfeed.lua -local killFeedEvents = {} - -local KILL_FEED_MESSAGE_TIMEOUT = 10 -- How long a message stays in the kill feed before starting to fade away. -local VISUALS_UPDATE_INTERVAL = 0.05 -- Tick speed of the visual update timer. Smaller number = smoother visuals. -local FADE_TIME = 1.5 -- How long it takes for an expiring kill feed message to fade away. - -local NUM_FADE_TICKS = math.floor(FADE_TIME / VISUALS_UPDATE_INTERVAL) -local FADE_AMOUNT = math.floor(255 / NUM_FADE_TICKS) - -local FONT = "RobotoHUD-15" -- Font used for kill feed messages. - -function GM:ClearKillFeed() - killFeedEvents = {} -end - --- This timer handles the visual updates for kill feed messages. --- New messages are given a "slide in" effect wherein they slide in from off-screen. --- Expiring messages are given a fade out effect. -timer.Create("ph_timer_kill_feed_visuals", VISUALS_UPDATE_INTERVAL, 0, function() - for i, event in ipairs(killFeedEvents) do - local finalPos = ScrW() - 10 - event.eventWidth - -- Handle slide in effect. - if event.new then - -- The distance to slide in a single "frame" is going to be proportional to how much distance is - -- left to the final position. This will be scaled up by 50% to make things go faster. - local subtractAmt = math.floor((event.x - finalPos) * 0.5) - -- Always move by at least one pixel. - if subtractAmt < 1 then - subtractAmt = 1 - end - - event.x = event.x - subtractAmt - if event.x < finalPos then - event.x = finalPos - event.new = false - end - -- Handle the fade out effect. - elseif event.entryTime + KILL_FEED_MESSAGE_TIMEOUT < CurTime() then - if event.attackerName then - event.attackerColor.a = event.attackerColor.a - FADE_AMOUNT - end - event.victimColor.a = event.victimColor.a - FADE_AMOUNT - event.messageColor.a = event.messageColor.a - FADE_AMOUNT - -- Remove message from the kill feed once it is completely transparent. - if event.messageColor.a < 0 then - table.remove(killFeedEvents, i) - end - end - end -end) - -net.Receive("ph_kill_feed_add", function(len) - local event = net.ReadTable() - event.entryTime = CurTime() - event.new = true -- New events are events that are in the process of "sliding in" to view. - - -- Starting x position. Individual draw statements will be relative to this value. - event.x = ScrW() -- This will be offscreen. - - -- Calculate the final width of the entire kill feed event. - surface.SetFont(FONT) - event.eventWidth = surface.GetTextSize(event.victimName) + surface.GetTextSize(" ") + surface.GetTextSize(event.message) - if event.attackerName then - event.eventWidth = event.eventWidth + surface.GetTextSize(event.attackerName) + surface.GetTextSize(" ") - end - - -- New events will start at the top. - event.y = 10 - - -- Shift existing events downward. - for _, kfEvent in ipairs(killFeedEvents) do - kfEvent.y = kfEvent.y + draw.GetFontHeight(FONT) - end - - table.insert(killFeedEvents, event) -end) - --- The shadow transparency will be the same as the provided color transparency. --- Returns the width and height of the NON SHADOW text. -local function drawShadowText(text, font, x, y, color) - draw.SimpleText(text, font, x + 1, y + 1, Color(0, 0, 0, color.a)) - return draw.SimpleText(text, font, x, y, color) -end - -local function drawKillFeedHUD() - for _, event in ipairs(killFeedEvents) do - local widthOffset = event.x - - -- New events need to have clipping disabled so they can be drawn while partially off-screen. - local oldClippingValue - if event.new then - oldClippingValue = DisableClipping(true) - end - - -- If Murder: [Attacker] [Message] [Victim] - -- If Suicide: [Victim] [Message] - - if event.attackerName then - widthOffset = widthOffset + drawShadowText(event.attackerName .. " ", FONT, widthOffset, event.y, event.attackerColor) - else - widthOffset = widthOffset + drawShadowText(event.victimName .. " ", FONT, widthOffset, event.y, event.victimColor) - end - - widthOffset = widthOffset + drawShadowText(event.message .. (event.attackerName && " " || ""), FONT, widthOffset, event.y, event.messageColor) - - if event.attackerName then - drawShadowText(event.victimName, FONT, widthOffset, event.y, event.victimColor) - end - - if event.new then - DisableClipping(oldClippingValue) - end - end -end - -hook.Add("HUDPaint", "ph_kill_feed_hud_paint", drawKillFeedHUD) diff --git a/gamemodes/ultimateph/gamemode/cl_voicepanels.lua b/gamemodes/ultimateph/gamemode/cl_voicepanels.lua deleted file mode 100644 index ac2d9ea..0000000 --- a/gamemodes/ultimateph/gamemode/cl_voicepanels.lua +++ /dev/null @@ -1,114 +0,0 @@ -local PANEL = {} -local PlayerVoicePanels = {} - -function PANEL:Init() - self.Avatar = vgui.Create("AvatarImage", self) - self.Avatar:Dock(LEFT) - self.Avatar:SetSize(32, 32) - self.Color = color_transparent - self:SetSize(250, 32 + 8) - self:DockPadding(4, 4, 4, 4) - self:DockMargin(2, 2, 2, 2) - self:Dock(BOTTOM) -end - -function PANEL:Setup(ply) - self.ply = ply - self.Avatar:SetPlayer(ply) - self.Color = team.GetColor(ply:Team()) - self:InvalidateLayout() -end - -function PANEL:Paint(w, h) - if !IsValid(self.ply) then return end - - local volume = self.ply:VoiceVolume() - col = team.GetColor(self.ply:Team()) - - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawRect(0, 0, w, h) - surface.SetDrawColor(col.r, col.g, col.b, 120) - surface.DrawRect(0, 0, w, h) - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(col.r, col.g, col.b, 120) - surface.DrawRect(0, 0, math.floor(w * volume), h) - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawOutlinedRect(0, 0, w * volume, h) - - draw.ShadowText(self.ply:Nick(), "GModNotify", 4 + 32 + 4, h / 2, color_white, 0, 1) -end - -function PANEL:Think() - if self.fadeAnim then - self.fadeAnim:Run() - end -end - -function PANEL:FadeOut(anim, delta, data) - if anim.Finished then - if IsValid(PlayerVoicePanels[self.ply]) then - PlayerVoicePanels[self.ply]:Remove() - PlayerVoicePanels[self.ply] = nil - return - end - - return - end - - self:SetAlpha(255 - (255 * delta)) -end - -derma.DefineControl("VoiceNotify", "", PANEL, "DPanel") - -function GM:PlayerStartVoice(ply) - if !IsValid(g_VoicePanelList) then return end - - -- There'd be an exta one if voice_loopback is on, so remove it. - GAMEMODE:PlayerEndVoice(ply) - - if IsValid(PlayerVoicePanels[ply]) then - if PlayerVoicePanels[ply].fadeAnim then - PlayerVoicePanels[ply].fadeAnim:Stop() - PlayerVoicePanels[ply].fadeAnim = nil - end - - PlayerVoicePanels[ply]:SetAlpha(255) - return - end - - if !IsValid(ply) then return end - - local pnl = g_VoicePanelList:Add("VoiceNotify") - pnl:Setup(ply) - PlayerVoicePanels[ply] = pnl -end - -local function VoiceClean() - for k, v in pairs(PlayerVoicePanels) do - if !IsValid(k) || !k:IsPlayer() then - GAMEMODE:PlayerEndVoice(k) - end - end -end -timer.Create("VoiceClean", 10, 0, VoiceClean) - -function GM:PlayerEndVoice(ply) - if IsValid(PlayerVoicePanels[ply]) then - if PlayerVoicePanels[ply].fadeAnim then return end - - PlayerVoicePanels[ply].fadeAnim = Derma_Anim("FadeOut", PlayerVoicePanels[ply], PlayerVoicePanels[ply].FadeOut) - PlayerVoicePanels[ply].fadeAnim:Start(2) - end -end - -local function CreateVoiceVGUI() - g_VoicePanelList = vgui.Create("DPanel") - g_VoicePanelList:ParentToHUD() - g_VoicePanelList:SetPos(ScrW() - 300, 100) - g_VoicePanelList:SetSize(250, ScrH() - 200) - g_VoicePanelList:SetPaintBackground(false) - -end - -hook.Add("InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI) diff --git a/gamemodes/ultimateph/gamemode/sh_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua new file mode 100644 index 0000000..be61d43 --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -0,0 +1,62 @@ +-- color +PHScobDark = Color(55, 55, 55, 120) -- scoreboard playerlist +PHScobDarker = Color(68, 68, 68, 120) -- scoreboard header +PHScobDarkest = Color(40, 40, 40, 230) -- scoreboard background +PHScobBlack = Color(0, 0, 0, 230) -- scoreboard boarder + +PHEndDark = Color(20, 20, 20, 150) -- endround playerlist +PHEndDarker = Color(60, 60, 60, 230) -- end round board background, close button hover +PHEndDarkest = Color(40, 40, 40, 230) -- endboard header +PHEndBlack = Color(0, 0, 0, 230) -- endround boarder +PHEndGray = Color(50, 50, 50, 50) -- mapvote hover + +PHTauntDark = Color(68, 68, 68, 160) -- taunt buttons +PHTauntDarker = Color(40, 40, 40, 230) -- side panel & title bar +PHTauntDarkest = Color(55, 55, 55, 120) -- taunt list background +PHTauntBlack = Color(0, 0, 0, 230) -- taunt outline + +PHWhite = Color(255, 255, 255, 255) -- text +PHLessWhite = Color(190, 190, 190) -- subtext + +PHRed = Color(199, 49, 29) -- error, disguise outline, scoreboard text +PHOrange = Color(255, 150, 50) -- hunters +PHGreen = Color(50, 170, 46) -- aim lazer +PHBlue = Color(50, 150, 255) -- props + +-- misc. +CornerRadius = "4" -- set to 0 to disable rounded corners. number is in pixels relative to 480p. +ScobBackground = true -- set to false to disable scoreboard background & credits +GroupTags = false -- set to true to enable group tags. requires ULX +ActEnableAll = false -- set to true to enable all default gmod animations +NameDistance = 500 -- adjusts how close you need to be to see a player's name + +GroupColors = {} +GroupColors["example"] = PHExample + +GroupNames = {} +GroupNames["example"] = "Example" + +-- it is ideal to delete undesired options rather than setting to false. see https://wiki.facepunch.com/gmod/Enums/ACT +ActWhitelist = {} +ActWhitelist[ACT_GMOD_GESTURE_BOW] = true +ActWhitelist[ACT_GMOD_GESTURE_WAVE] = true +ActWhitelist[ACT_GMOD_GESTURE_AGREE] = true +ActWhitelist[ACT_GMOD_GESTURE_BECON] = true +ActWhitelist[ACT_GMOD_GESTURE_DISAGREE] = true +ActWhitelist[ACT_GMOD_GESTURE_TAUNT_ZOMBIE] = true +ActWhitelist[ACT_GMOD_TAUNT_LAUGH] = true +ActWhitelist[ACT_GMOD_TAUNT_CHEER] = true +ActWhitelist[ACT_GMOD_TAUNT_DANCE] = true +ActWhitelist[ACT_GMOD_TAUNT_ROBOT] = true +ActWhitelist[ACT_GMOD_TAUNT_SALUTE] = true +ActWhitelist[ACT_GMOD_TAUNT_MUSCLE] = true +ActWhitelist[ACT_GMOD_TAUNT_PERSISTENCE] = true +ActWhitelist[ACT_SIGNAL_HALT] = true +ActWhitelist[ACT_SIGNAL_GROUP] = true +ActWhitelist[ACT_SIGNAL_FORWARD] = true + +-- set which HUD elements to hide. see https://wiki.facepunch.com/gmod/HUD_Element_List +-- gamemode-added elements include "PropHuntersPlayerNames", +HudBlacklist = {} +HudBlacklist["CHudVoiceSelfStatus"] = true -- you should probably leave this one alone. disabling the custom voice panel is no currently supported +HudBlacklist["CHudVoiceStatus"] = true -- same deal here diff --git a/gamemodes/ultimateph/gamemode/sh_disguise.lua b/gamemodes/ultimateph/gamemode/sh_disguise.lua index 58dc10d..1f8512b 100644 --- a/gamemodes/ultimateph/gamemode/sh_disguise.lua +++ b/gamemodes/ultimateph/gamemode/sh_disguise.lua @@ -147,7 +147,7 @@ function GM:PlayerCanDisguiseCurrentTarget(ply) end hook.Add('UpdateAnimation', 'PropUndisguiseLock', function(ply) - if ply:IsDisguised() or !GAMEMODE.PropTpose:GetBool() or !ply:GetNWBool("undisguiseRotationLock") then + if ply:IsDisguised() or not GAMEMODE.PropTpose:GetBool() or not ply:GetNWBool("undisguiseRotationLock") then return end @@ -201,7 +201,7 @@ if CLIENT then local b, err = pcall(renderDis, self) cam.End3D() if !b then - MsgC(Color(255, 0, 0), err .. "\n") + MsgC(PHRed, err .. "\n") end end @@ -210,10 +210,10 @@ if CLIENT then if client:IsProp() then local canDisguise, target = self:PlayerCanDisguiseCurrentTarget(client) if canDisguise then - local col = Color(50, 220, 50) + local col = PHGreen local hullxy, hullz = target:GetPropSize() if !client:CanFitHull(hullxy, hullxy, hullz) then - col = Color(220, 50, 50) + col = PHRed end halo.Add({target}, col, 2, 2, 2, true, true) end @@ -249,7 +249,7 @@ if SERVER then function PlayerMeta:DisguiseAsProp(ent) local hullxy, hullz = ent:GetPropSize() if !self:CanFitHull(hullxy, hullxy, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to change") + self:PlayerChatMsg(PHRed, "Not enough room to change") return end @@ -344,7 +344,7 @@ if SERVER then end function PlayerMeta:DisguiseLockRotation() - if !self:IsDisguised() then + if not self:IsDisguised() then local ang = self:GetRenderAngles() self:SetNWAngle("undisguiseRotationLockAng", ang) self:SetNWBool("undisguiseRotationLock", true) @@ -377,7 +377,7 @@ if SERVER then local hullxy = math.Round(math.Max(maxs.x - mins.x, maxs.y - mins.y) / 2) local hullz = math.Round(maxs.z - mins.z) if !self:CanFitHull(hullxy, hullxy, hullz) then - self:PlayerChatMsg(Color(255, 50, 50), "Not enough room to unlock rotation, move into a more open area") + self:PlayerChatMsg(PHRed, "Not enough room to unlock rotation, move into a more open area") return end diff --git a/gamemodes/ultimateph/gamemode/sh_init.lua b/gamemodes/ultimateph/gamemode/sh_init.lua index 9bbca12..7ae0758 100644 --- a/gamemodes/ultimateph/gamemode/sh_init.lua +++ b/gamemodes/ultimateph/gamemode/sh_init.lua @@ -1,5 +1,6 @@ -- former shared.lua -include("cl_colors.lua") +include("sh_config.lua") + local PlayerMeta = FindMetaTable("Player") local tabFile = file.Read(GM.Folder .. "/ultimateph.txt", "GAME") || "" local tab = util.KeyValuesToTable(tabFile) @@ -32,8 +33,8 @@ function PlayerMeta:IsProp() return self:Team() == TEAM_PROP end GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT team.SetUp(TEAM_SPEC, "Spectators", PHWhite, false) -- Setting Joinable to false allows us to use team.BestAutoJoinTeam and have it only include the Hunters/Props teams. -team.SetUp(TEAM_HUNTER, "Hunters", PHBlue) -team.SetUp(TEAM_PROP, "Props", PHRed) +team.SetUp(TEAM_HUNTER, "Hunters", PHOrange) +team.SetUp(TEAM_PROP, "Props", PHBlue) function GM:GetGameState() return self.GameState diff --git a/gamemodes/ultimateph/gamemode/sh_rounds.lua b/gamemodes/ultimateph/gamemode/sh_rounds.lua index a67a76f..67c737d 100644 --- a/gamemodes/ultimateph/gamemode/sh_rounds.lua +++ b/gamemodes/ultimateph/gamemode/sh_rounds.lua @@ -5,7 +5,6 @@ if SERVER then util.AddNetworkString("gamestate") util.AddNetworkString("round_victor") util.AddNetworkString("gamerules") - util.AddNetworkString("rounds") GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() @@ -70,6 +69,7 @@ if SERVER then function GM:SetGameState(state) self.GameState = state + self.CurrentRound = self.Rounds self.StateStart = CurTime() self:NetworkGameState() end @@ -77,10 +77,11 @@ if SERVER then function GM:NetworkGameState(ply) net.Start("gamestate") net.WriteUInt(self.GameState || ROUND_WAIT, 32) + net.WriteUInt(self.CurrentRound || 0, 7) net.WriteDouble(self.StateStart || 0) net.Broadcast() end - + function GM:GetRoundSettings() self.RoundSettings = self.RoundSettings || {} return self.RoundSettings @@ -149,12 +150,9 @@ if SERVER then self:CleanupMap() self.Rounds = self.Rounds + 1 - net.Start("rounds") - net.WriteInt(self.Rounds, 5) - net.Broadcast() if self.Rounds == self.RoundLimit:GetInt() then - GlobalChatMsg(Color(255, 0, 0), "This is the LAST ROUND!") + GlobalChatMsg(PHRed, "LAST ROUND!") if self.Secrets:GetBool() then BroadcastLua("surface.PlaySound('husklesph/hphaaaaa2.mp3')") @@ -351,6 +349,7 @@ if CLIENT then net.Receive("gamestate", function(len) GAMEMODE.GameState = net.ReadUInt(32) + GAMEMODE.CurrentRound = net.ReadUInt(7) GAMEMODE.StateStart = net.ReadDouble() if GAMEMODE.GameState == ROUND_HIDE then diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index 97830a7..301e50e 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -319,260 +319,3 @@ if SERVER then (GM || GAMEMODE):StartAutoTauntTimer() end) end - -if CLIENT then --- former cl_taunt.lua - include("cl_colors.lua") - - local menu - local lastCursorX - local lastCursorY - - local function saveCursor() - lastCursorX, lastCursorY = input.GetCursorPos() - end - - local function restoreCursor() - if !lastCursorX then return end - - input.SetCursorPos(lastCursorX, lastCursorY) - lastCursorX = nil - lastCursorY = nil - end - - local function fillList(mlist, taunts, cat) - menu.CurrentTaunts = taunts - menu.CurrentTauntCat = cat - for k, v in pairs(menu.CatList:GetCanvas():GetChildren()) do - v.Selected = false - if v.CatName == cat then - v.Selected = true - end - end - - mlist:Clear() - for k, t in pairs(taunts) do - if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end - - local but = vgui.Create("DButton") - but:SetTall(draw.GetFontHeight("RobotoHUD-L15") * 1.0) - but:SetText("") - - function but:Paint(w, h) - if self:IsDown() then - col = PHLesserWhite - elseif self:IsHovered() then - col = PHLesserWhite - else - col = PHWhite - end - draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, 0, 1) - draw.ShadowText(math.Round(t.soundDuration * 10) / 10 .. "s", "RobotoHUD-L10", w, h / 2, col, 2, 1) - end - - function but:DoClick() - RunConsoleCommand("ph_taunt", t.sound[math.random(#t.sound)]) - saveCursor() - menu:Close() - end - - mlist:AddItem(but) - end - end - - local function addCat(clist, name, taunts, mlist) - local dname = name:lower():gsub("[_]", " ") - dname = dname:sub(1, 1):upper() .. dname:sub(2) - - local but = vgui.Create("DButton") - but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - but:SetText("") - but.Selected = false - but.CatName = name - - function but:Paint(w, h) - local colt = PHWhite - if !self.Selected then - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - if self:IsDown() then - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - elseif self:IsHovered() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - end - else - col = Color(PHDark.r, PHDark.g, PHDark.b) - end - - draw.RoundedBoxEx(4, 0, 0, w, h, col, true, false, true, false) - draw.ShadowText(dname, "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) - end - - function but:DoClick() - fillList(mlist, taunts, name) - self.Selected = true - end - - clist:AddItem(but) - return but - end - - local function fillCats(clist, mlist) - clist:Clear() - local all = addCat(clist, "all", Taunts, mlist) - for k, taunts in pairs(TauntCategories) do - local c = 0 - for a, t in pairs(taunts) do - if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end - - c = c + 1 - end - - if c > 0 then - addCat(clist, k, taunts, mlist) - end - end - - all.Selected = true - end - - local function openTauntMenu() - restoreCursor() - if IsValid(menu) then - fillCats(menu.CatList, menu.TauntList) - fillList(menu.TauntList, menu.CurrentTaunts, menu.CurrentTauntCat) - if menu:IsVisible() then - saveCursor() - end - - menu:SetVisible(!menu:IsVisible()) - return - end - - menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.8) - menu:Center() - menu:SetTitle("") - menu:MakePopup() - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - menu:SetDraggable(false) - menu:ShowCloseButton(false) - menu:DockPadding(8, 8 + draw.GetFontHeight("RobotoHUD-25"), 8, 8) - - local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('marlett') - closeButton:SetText('r') - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - closeButton.OnCursorEntered = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarker.r, PHDarker.g, PHDarker.b)) - end - end - closeButton.OnCursorExited = function() - closeButton.Paint = function(s,w,h) - draw.RoundedBox(0,0,0,w,h,Color(PHDarkest.r, PHDarkest.g, PHDarkest.b)) - end - end - closeButton:SetColor(PHWhite) - closeButton:SetSize(menu:GetWide() / 20, menu:GetTall() / 30) - closeButton:SetPos(menu:GetWide() / 1.05, 0) - closeButton.DoClick = function() - menu:Close() - end - - function menu:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawRect(0, 0, w, h) - surface.SetFont("RobotoHUD-25") - local t = "Taunts" - local tw, th = surface.GetTextSize(t) - draw.SimpleText(t, "RobotoHUD-25", 8, 2, PHBlue, 0) - draw.SimpleText(TauntMenuPhrase, "RobotoHUD-L15", 8 + tw + 16, 2 + th * 0.90, PHWhite, 0, 4) - end - - local leftpnl = vgui.Create("DPanel", menu) - leftpnl:Dock(LEFT) - leftpnl:DockMargin(0, 0, 0, 0) - leftpnl:SetWide(menu:GetWide() * 0.3) - function leftpnl:Paint(w, h) - end - - local but = vgui.Create("DButton", leftpnl) - but:Dock(BOTTOM) - but:DockMargin(0, 4, 4, 0) - but:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - but:SetText("") - - function but:Paint(w, h) - local colt = PHWhite - if self:IsDown() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - elseif self:IsHovered() then - col = Color(PHDark.r, PHDark.g, PHDark.b) - else - col = Color(PHDarker.r, PHDarker.g, PHDarker.b) - end - - draw.RoundedBoxEx(4, 0, 0, w, h, col, true, true, true, true) - draw.ShadowText("Random", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) - end - - function but:DoClick() - RunConsoleCommand("ph_taunt_random") - saveCursor() - menu:Close() - end - - local clist = vgui.Create("DScrollPanel", leftpnl) - menu.CatList = clist - clist:Dock(FILL) - clist:DockMargin(0, 0, 0, 0) - - function clist:Paint(w, h) - end - - local canvas = clist:GetCanvas() - canvas:DockPadding(0, 0, 0, 0) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) - end - - local mlist = vgui.Create("DScrollPanel", menu) - menu.TauntList = mlist - mlist:Dock(FILL) - - function mlist:Paint(w, h) - surface.SetDrawColor(PHDarkest.r, PHDarkest.g, PHDarkest.b) - surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(PHDarker.r, PHDarker.g, PHDarker.b) - surface.DrawRect(1, 1, w - 2, h - 2) - end - - -- child positioning - local canvas = mlist:GetCanvas() - canvas:DockPadding(8, 8, 8, 8) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, 4) - end - - fillList(mlist, Taunts) - fillCats(clist, mlist, "all") - end - - concommand.Add("ph_menu_taunt", openTauntMenu) - net.Receive("open_taunt_menu", openTauntMenu) - - net.Receive("ph_set_taunt_menu_phrase", function() - value_new = net.ReadString() - - -- Prevent this from being done more than once. GMod is weird. - if TauntMenuPhrase == value_new then return end - TauntMenuPhrase = value_new - end) -end diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index 21c0b7f..57e255d 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -591,11 +591,11 @@ function GM:PlayerDeath(ply, inflictor, attacker) end function GM:KeyPress(ply, key) - if ply:Alive() then - if key == IN_ATTACK then - self:PlayerDisguise(ply) - end + if not ply:Alive() or key ~= IN_ATTACK then + return end + + self:PlayerDisguise(ply) end function GM:PlayerSwitchFlashlight(ply) @@ -606,10 +606,6 @@ function GM:PlayerSwitchFlashlight(ply) return true end -function GM:PlayerShouldTaunt(ply, actid) - return false -end - function GM:PlayerCanSeePlayersChat(text, teamOnly, listener, speaker) if !IsValid(speaker) then return false end local canhear = self:PlayerCanHearChatVoice(listener, speaker, "chat", teamOnly) @@ -624,6 +620,14 @@ function GM:StartCommand(ply, cmd) end end +function GM:PlayerShouldTaunt(ply, actid) + if not ActWhitelist[actid] and not ActEnableAll then + return false + end + + return true +end + local sv_alltalk = GetConVar("sv_alltalk") function GM:PlayerCanHearPlayersVoice(listener, talker) if !IsValid(talker) then return false end @@ -637,7 +641,7 @@ function GM:PlayerCanHearChatVoice(listener, talker, typ, teamOnly) end end - if sv_alltalk:GetInt() == 3 then -- sv_alltalk is not a bool. 0 = team only with poximity, 1 = team only without proximity, 2 = everybody with proxitiy, 3 = everybody without proximity + if sv_alltalk:GetInt() >= 2 then -- sv_alltalk is not a bool. 0 = team only with poximity, 1 = team only without proximity, 2 = everybody with proxitiy, 3 = everybody without proximity return true end From 1da5ae4dce59dc0eacaad3b31c471b821bdc7f9a Mon Sep 17 00:00:00 2001 From: queeek180 Date: Mon, 29 Sep 2025 07:32:24 +0000 Subject: [PATCH 05/25] Update cl_taunt.lua taunt fix --- gamemodes/ultimateph/gamemode/cl_taunt.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index c4b987a..2fe1d2f 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -28,7 +28,7 @@ local function fillList(mlist, taunts, cat) mlist:Clear() for k, t in pairs(taunts) do - if not TauntAllowedForPlayer(LocalPlayer(), t) then + if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end @@ -100,7 +100,7 @@ local function fillCats(clist, mlist) for k, taunts in pairs(TauntCategories) do local c = 0 for a, t in pairs(taunts) do - if not TauntAllowedForPlayer(LocalPlayer(), t) then continue end + if !TauntAllowedForPlayer(LocalPlayer(), t) then continue end c = c + 1 end From 01b4550fc76e6116006428bd046ef94f42a03809 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Mon, 29 Sep 2025 20:32:50 +1000 Subject: [PATCH 06/25] fix taunts (real) --- gamemodes/ultimateph/gamemode/cl_hud.lua | 38 ++-- gamemodes/ultimateph/gamemode/cl_init.lua | 2 +- .../ultimateph/gamemode/cl_scoreboard.lua | 12 +- gamemodes/ultimateph/gamemode/cl_taunt.lua | 2 +- gamemodes/ultimateph/gamemode/init.lua | 4 +- gamemodes/ultimateph/gamemode/sh_init.lua | 6 +- gamemodes/ultimateph/gamemode/sh_taunt.lua | 2 +- gamemodes/ultimateph/gamemode/sv_player.lua | 4 - gamemodes/ultimateph/ultimateph.txt | 213 ++++++++++++++++++ 9 files changed, 246 insertions(+), 37 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index 153e4af..77eb2bc 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -55,32 +55,32 @@ function GM:DrawGameHUD() if not disguise or not rotationLock or not tauntMenu then draw.ShadowText("UNBOUND!", "RobotoHUD-15", ScreenScale(4), ScrH() / 2, PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText(" Please ensure +attack, +menu_context, and gm_showspare1 are bound!", "RobotoHUD-L15", ScreenScale(4) + surface.GetTextSize("UNBOUND!"), ScrH() / 2, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText("* MOUSE1, C, and F3 by default.", "RobotoHUD-L12", ScreenScale(4), ScrH() / 2 + fontSize, PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.ShadowText(" Please ensure +attack, +menu_context, and gm_showspare1 are bound!", "RobotoHUD-L15", ScreenScale(4) + surface.GetTextSize("UNBOUND!"), ScrH() / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.ShadowText("* MOUSE1, C, and F3 by default.", "RobotoHUD-L12", ScreenScale(4), ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) return end local totalWidth = math.max(surface.GetTextSize(disguise), surface.GetTextSize(rotationLock), surface.GetTextSize(tauntMenu)) - draw.ShadowText(disguise, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 - fontSize, PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) - draw.ShadowText(" Disguise", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 - fontSize, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.ShadowText(disguise, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 - fontSize, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.ShadowText(" Disguise", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 - fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText(rotationLock, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2, PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) - draw.ShadowText(" Lock Rotation", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.ShadowText(rotationLock, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.ShadowText(" Lock Rotation", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText(tauntMenu, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 + fontSize, PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) - draw.ShadowText(" Taunt Menu", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 + fontSize, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.ShadowText(tauntMenu, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.ShadowText(" Taunt Menu", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) end function GM:DrawRoundTimer() if self:GetGameState() == ROUND_WAIT then local time = math.ceil(self.StartWaitTime:GetFloat() - self:GetStateRunningTime()) if time > 0 then - draw.ShadowText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText("Game starts in " .. tostring(time) .. " second" .. (time > 1 and "s" or ""), "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Game starts in " .. tostring(time) .. " second" .. (time > 1 and "s" or ""), "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) else - draw.ShadowText("Not enough players to start", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Not enough players to start", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end return end @@ -88,8 +88,8 @@ function GM:DrawRoundTimer() if self:GetGameState() == ROUND_HIDE then local time = math.ceil(self.HidingTime:GetInt() - self:GetStateRunningTime()) if time > 0 then - draw.ShadowText("Hunters will be released in", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText(time, "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Hunters will be released in", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText(time, "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end return end @@ -99,15 +99,15 @@ function GM:DrawRoundTimer() end if self:GetStateRunningTime() < 2 then - draw.ShadowText("GO!", "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("GO!", "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) return end local settings = self:GetRoundSettings() local roundTime = settings.RoundTime or 5 * 60 local time = tostring(math.max(0, roundTime - self:GetStateRunningTime())) - draw.ShadowText("Round "..GAMEMODE.CurrentRound.."/"..GAMEMODE.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, ScreenScaleH(4), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText(string.ToMinutesSeconds(time), "RobotoHUD-20", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-L15"), PHWhite, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText("Round "..GAMEMODE.CurrentRound.."/"..GAMEMODE.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.ShadowText(string.ToMinutesSeconds(time), "RobotoHUD-20", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-L15"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end function GM:PreDrawHUD() @@ -160,7 +160,7 @@ function PANEL:Paint(w, h) surface.SetDrawColor(0, 0, 0, 255) surface.DrawOutlinedRect(0, 0, w * volume, h) - draw.ShadowText(self.ply:Nick(), "RobotoHUD-12", self.Avatar:GetWide() + ScreenScale(4), h / 2, PHWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.ShadowText(self.ply:Nick(), "RobotoHUD-12", self.Avatar:GetWide() + ScreenScale(4), h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end function PANEL:Think() @@ -344,7 +344,7 @@ local ray_color = PHGreen local ray_width = 6 local dotMat = Material("ultimateph/aim_dot") -local dot_color = PHWhite +local dot_color = color_white local hunters_aim = {} diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index 5ace97b..fa8a7d3 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -1,10 +1,10 @@ -include("sh_taunt.lua") include("sh_init.lua") include("sh_config.lua") include("sh_rounds.lua") include("sh_disguise.lua") include("sh_bannedmodels.lua") include("sh_mapvote.lua") +include("sh_taunt.lua") include("cl_hud.lua") include("cl_scoreboard.lua") include("cl_helpscreen.lua") diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index 0bb335d..a3cf939 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -51,10 +51,10 @@ local function addPlayerItem(self, mlist, ply, pteam) end if GroupNames[ply:GetUserGroup()] then - draw.ShadowText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHScobDarkest, 0) + draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHScobDarkest, 0) s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + ScreenScaleH(2) - draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, 0, PHWhite, 0) - draw.ShadowText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), 0, PHWhite, 2) + draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, color_white, 0) + draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), 0, PHHudWhite, 2) end else draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, 0, PHWHite, 0) @@ -248,7 +248,7 @@ local function createScoreboardPanel() local tw = surface.GetTextSize("Spectate") function bottom:Paint(w, h) - draw.RoundedBox(0, 0, 0, w, h, PHScobDarker) + draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker) local c for k, ply in pairs(team.GetPlayers(TEAM_SPEC)) do if c then @@ -272,9 +272,9 @@ local function createScoreboardPanel() function but:Paint(w, h) local colt = table.Copy(PHLessWhite) if self:IsDown() then - colMul(col, 0.8) + colMul(colt, 0.8) elseif self:IsHovered() then - colMul(col, 1.2) + colMul(colt, 1.2) end draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker) diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index c4b987a..2d98d7d 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -179,7 +179,7 @@ local function openTauntMenu() local t = "Taunts" local tw = surface.GetTextSize(t) draw.ShadowText(t, "RobotoHUD-25", ScreenScaleH(4), draw.GetFontHeight("RobotoHUD-25"), PHBlue, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - draw.ShadowText("- ".. TauntMenuPhrase, "RobotoHUD-L15", ScreenScaleH(8) + tw, draw.GetFontHeight("RobotoHUD-L15"), PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.ShadowText("- ".. GetConVar("ph_taunt_menu_phrase"):GetString(), "RobotoHUD-L15", ScreenScaleH(8) + tw, draw.GetFontHeight("RobotoHUD-L15"), PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end local leftpnl = vgui.Create("DPanel", menu) diff --git a/gamemodes/ultimateph/gamemode/init.lua b/gamemodes/ultimateph/gamemode/init.lua index c901907..77d4e69 100644 --- a/gamemodes/ultimateph/gamemode/init.lua +++ b/gamemodes/ultimateph/gamemode/init.lua @@ -17,12 +17,12 @@ util.AddNetworkString("player_model_sex") include("sv_player.lua") include("sv_teams.lua") include("sv_utils.lua") +include("sh_init.lua") include("sh_rounds.lua") include("sh_disguise.lua") -include("sh_taunt.lua") include("sh_bannedmodels.lua") +include("sh_taunt.lua") include("sh_mapvote.lua") -include("sh_init.lua") resource.AddFile("materials/husklesph/skull.png") resource.AddFile("sound/husklesph/hphaaaaa2.mp3") diff --git a/gamemodes/ultimateph/gamemode/sh_init.lua b/gamemodes/ultimateph/gamemode/sh_init.lua index 7ae0758..238ace2 100644 --- a/gamemodes/ultimateph/gamemode/sh_init.lua +++ b/gamemodes/ultimateph/gamemode/sh_init.lua @@ -33,8 +33,8 @@ function PlayerMeta:IsProp() return self:Team() == TEAM_PROP end GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT team.SetUp(TEAM_SPEC, "Spectators", PHWhite, false) -- Setting Joinable to false allows us to use team.BestAutoJoinTeam and have it only include the Hunters/Props teams. -team.SetUp(TEAM_HUNTER, "Hunters", PHOrange) -team.SetUp(TEAM_PROP, "Props", PHBlue) +team.SetUp(TEAM_HUNTER, "Hunters", PHBlue) +team.SetUp(TEAM_PROP, "Props", PHRed) function GM:GetGameState() return self.GameState @@ -124,7 +124,7 @@ GM.PropUndisguisedThirdperson = CreateConVar("ph_props_undisguised_thirdperson", GM.AutoTeamBalance = CreateConVar("ph_auto_team_balance", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Automatically balance teams") GM.NumberHunter = CreateConVar("ph_nb_hunter", 2, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Set the maximum number of hunters, only works if auto team balance is disable") -GM.TauntMenuPhrase = CreateConVar("ph_taunt_menu_phrase", TauntMenuPhrase, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Phrase shown at the top of the taunt menu") +GM.TauntMenuPhrase = CreateConVar("ph_taunt_menu_phrase", "make annoying fart sounds", bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Phrase shown at the top of the taunt menu") GM.AutoTauntEnabled = CreateConVar("ph_auto_taunt", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "1 if auto taunts should be enabled") GM.AutoTauntMin = CreateConVar("ph_auto_taunt_delay_min", 60, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Mininum time to go without taunting") GM.AutoTauntMax = CreateConVar("ph_auto_taunt_delay_max", 120, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Maximum time to go without taunting") diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index 301e50e..26992d9 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -1,7 +1,6 @@ Taunts = {} TauntCategories = {} AllowedTauntSounds = {} -TauntMenuPhrase = "make annoying fart sounds" function FilenameToSoundname(filename) local sndName = string.Trim(filename) @@ -319,3 +318,4 @@ if SERVER then (GM || GAMEMODE):StartAutoTauntTimer() end) end + diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index 57e255d..f92b4a7 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -20,10 +20,6 @@ function GM:PlayerInitialSpawn(ply) end end -function GM:PlayerLoadedLocalPlayer(ply) - self:SetTauntMenuPhrase(self.TauntMenuPhrase:GetString(), ply) -end - net.Receive("clientIPE", function(len, ply) if !ply.ClientIPE then ply.ClientIPE = true diff --git a/gamemodes/ultimateph/ultimateph.txt b/gamemodes/ultimateph/ultimateph.txt index 6db82a1..22fa2cf 100644 --- a/gamemodes/ultimateph/ultimateph.txt +++ b/gamemodes/ultimateph/ultimateph.txt @@ -213,3 +213,216 @@ } +"ultimateph" +{ + "base" "base" + "title" "Prop Hunters - Ultimate Edition" + "maps" "^ph_" + "menusystem" "1" + "workshopid" "3028430983" + "version" "v1.3.1" + "settings" + { + 1 + { + "name" "ph_auto_team_balance" + "text" "Auto balance teams" + "help" "Automatically balance teams" + "type" "CheckBox" + "default" "1" + } + 2 + { + "name" "ph_nb_hunter" + "text" "Number of hunters" + "help" "Set the number of hunters, works only if auto balance is disable" + "type" "Numeric" + "default" "2" + } + 3 + { + "name" "ph_roundlimit" + "text" "Number of rounds" + "help" "Set the number of rounds before mapvote" + "type" "Numeric" + "default" "10" + } + 4 + { + "name" "ph_roundtime" + "text" "Rounds time limit" + "help" "Time limit before ending the round (0 for automatic time)" + "type" "Numeric" + "default" "0" + } + 5 + { + "name" "ph_hidingtime" + "text" "Hiding phase time" + "help" "Time before hunters are released" + "type" "Numeric" + "default" "30" + } + 6 + { + "name" "ph_postroundtime" + "text" "Post-round time" + "help" "Time before next round after end of the round" + "type" "Numeric" + "default" "15" + + } + 7 + { + "name" "ph_hunter_dmgpenalty" + "text" "Damage taken by hunters" + "help" "Amount of damage a hunter should take for shooting an incorrect prop" + "type" "Numeric" + "default" "3" + } + 8 + { + "name" "ph_hunter_smggrenades" + "text" "Number of SMG grenades" + "help" "Amount of SMG grenades hunters should spawn with" + "type" "Numeric" + "default" "1" + } + 9 + { + "name" "ph_dead_canroam" + "text" "Use roam spectate mode" + "help" "Can dead players use the roam spectate mode" + "type" "CheckBox" + "default" "0" + } + 10 + { + "name" "ph_props_onwinstayprops" + "text" "Props stay props on win" + "help" "If the props win, they stay on the props team" + "type" "CheckBox" + "default" "0" + } + 11 + { + "name" "ph_auto_taunt" + "text" "Auto taunts enabled" + "help" "Sets if auto taunts are enabled." + "type" "CheckBox" + "default" "0" + } + 12 + { + "name" "ph_auto_taunt_delay_min" + "text" "Min time without taunting" + "help" "Mininum time to go without taunting (sec)." + "type" "Numeric" + "default" "60" + } + 13 + { + "name" "ph_auto_taunt_delay_max" + "text" "Max time without taunting" + "help" "Maximum time to go without taunting (sec)." + "type" "Numeric" + "default" "120" + } + 14 + { + "name" "ph_hunter_deaf_onhiding" + "text" "Deaf hunter on hinding" + "help" "Deaf hunters while hiding duration (black screen)" + "type" "CheckBox" + "default" "1" + } + 15 + { + "name" "ph_props_silent_footsteps" + "text" "Props silent footsteps" + "help" "Does props emit footsteps sounds while moving" + "type" "CheckBox" + "default" "0" + } + 16 + { + "name" "ph_props_tpose" + "text" "Props Tpose" + "help" "Should props be in T pose" + "type" "CheckBox" + "default" "0" + } + 17 + { + "name" "ph_props_undisguised_thirdperson" + "text" "Props Undisguised Thirdperson" + "help" "Should props be in thirdperson when undisguised" + "type" "CheckBox" + "default" "0" + } + 18 + { + "name" "ph_walk_speed" + "text" "Walk Speed" + "help" "Default Walk Speed for hunters and props." + "type" "Numeric" + "default" "200" + } + 19 + { + "name" "ph_run_speed" + "text" "Run Speed" + "help" "Default Run Speed for hunters and props." + "type" "Numeric" + "default" "150" + } + 20 + { + "name" "ph_jump_power" + "text" "Jump Power" + "help" "Default Jump Power for hunters and props." + "type" "Numeric" + "default" "200" + } + 21 + { + "name" "ph_falldmg_mult" + "text" "Fall Damage multiplier" + "help" "Default Fall Damage multiplier for hunters and props." + "type" "Numeric" + "default" "50" + } + 22 + { + "name" "ph_falldmg_nonlethal" + "text" "Non-Lethal Fall Damage" + "help" "If set, fall damage will not take players below one health." + "type" "CheckBox" + "default" "0" + } + 23 + { + "name" "ph_props_camdistance_mult" + "text" "Prop Camera Distance Multiplier" + "help" "The camera distance multiplier for props when disguised" + "type" "Numeric" + "default" "1" + } + 24 + { + "name" "ph_props_camdistance_min" + "text" "Props Minimum Camera Distance" + "help" "The minimum camera distance for props when disguised" + "type" "Numeric" + "default" "60" + } + 25 + { + "name" "ph_props_camdistance_max" + "text" "Props Maximum Camera Distance" + "help" "The maximum camera distance for props when disguised" + "type" "Numeric" + "default" "160" + } + } +} From 5ef63c9c0ac70286fb1443e1289aa345b459bc6d Mon Sep 17 00:00:00 2001 From: queeek180 Date: Mon, 29 Sep 2025 20:33:30 +1000 Subject: [PATCH 07/25] fix taunts (real) --- gamemodes/ultimateph/gamemode/sh_config.lua | 124 ++++++++++---------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/sh_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua index be61d43..89a6c32 100644 --- a/gamemodes/ultimateph/gamemode/sh_config.lua +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -1,62 +1,62 @@ --- color -PHScobDark = Color(55, 55, 55, 120) -- scoreboard playerlist -PHScobDarker = Color(68, 68, 68, 120) -- scoreboard header -PHScobDarkest = Color(40, 40, 40, 230) -- scoreboard background -PHScobBlack = Color(0, 0, 0, 230) -- scoreboard boarder - -PHEndDark = Color(20, 20, 20, 150) -- endround playerlist -PHEndDarker = Color(60, 60, 60, 230) -- end round board background, close button hover -PHEndDarkest = Color(40, 40, 40, 230) -- endboard header -PHEndBlack = Color(0, 0, 0, 230) -- endround boarder -PHEndGray = Color(50, 50, 50, 50) -- mapvote hover - -PHTauntDark = Color(68, 68, 68, 160) -- taunt buttons -PHTauntDarker = Color(40, 40, 40, 230) -- side panel & title bar -PHTauntDarkest = Color(55, 55, 55, 120) -- taunt list background -PHTauntBlack = Color(0, 0, 0, 230) -- taunt outline - -PHWhite = Color(255, 255, 255, 255) -- text -PHLessWhite = Color(190, 190, 190) -- subtext - -PHRed = Color(199, 49, 29) -- error, disguise outline, scoreboard text -PHOrange = Color(255, 150, 50) -- hunters -PHGreen = Color(50, 170, 46) -- aim lazer -PHBlue = Color(50, 150, 255) -- props - --- misc. -CornerRadius = "4" -- set to 0 to disable rounded corners. number is in pixels relative to 480p. -ScobBackground = true -- set to false to disable scoreboard background & credits -GroupTags = false -- set to true to enable group tags. requires ULX -ActEnableAll = false -- set to true to enable all default gmod animations -NameDistance = 500 -- adjusts how close you need to be to see a player's name - -GroupColors = {} -GroupColors["example"] = PHExample - -GroupNames = {} -GroupNames["example"] = "Example" - --- it is ideal to delete undesired options rather than setting to false. see https://wiki.facepunch.com/gmod/Enums/ACT -ActWhitelist = {} -ActWhitelist[ACT_GMOD_GESTURE_BOW] = true -ActWhitelist[ACT_GMOD_GESTURE_WAVE] = true -ActWhitelist[ACT_GMOD_GESTURE_AGREE] = true -ActWhitelist[ACT_GMOD_GESTURE_BECON] = true -ActWhitelist[ACT_GMOD_GESTURE_DISAGREE] = true -ActWhitelist[ACT_GMOD_GESTURE_TAUNT_ZOMBIE] = true -ActWhitelist[ACT_GMOD_TAUNT_LAUGH] = true -ActWhitelist[ACT_GMOD_TAUNT_CHEER] = true -ActWhitelist[ACT_GMOD_TAUNT_DANCE] = true -ActWhitelist[ACT_GMOD_TAUNT_ROBOT] = true -ActWhitelist[ACT_GMOD_TAUNT_SALUTE] = true -ActWhitelist[ACT_GMOD_TAUNT_MUSCLE] = true -ActWhitelist[ACT_GMOD_TAUNT_PERSISTENCE] = true -ActWhitelist[ACT_SIGNAL_HALT] = true -ActWhitelist[ACT_SIGNAL_GROUP] = true -ActWhitelist[ACT_SIGNAL_FORWARD] = true - --- set which HUD elements to hide. see https://wiki.facepunch.com/gmod/HUD_Element_List --- gamemode-added elements include "PropHuntersPlayerNames", -HudBlacklist = {} -HudBlacklist["CHudVoiceSelfStatus"] = true -- you should probably leave this one alone. disabling the custom voice panel is no currently supported -HudBlacklist["CHudVoiceStatus"] = true -- same deal here +-- color +PHScobDark = Color(55, 55, 55, 120) -- scoreboard playerlist +PHScobDarker = Color(68, 68, 68, 120) -- scoreboard header +PHScobDarkest = Color(40, 40, 40, 230) -- scoreboard background +PHScobBlack = Color(0, 0, 0, 230) -- scoreboard boarder + +PHEndDark = Color(20, 20, 20, 150) -- endround playerlist +PHEndDarker = Color(60, 60, 60, 230) -- end round board background, close button hover +PHEndDarkest = Color(40, 40, 40, 230) -- endboard header +PHEndBlack = Color(0, 0, 0, 230) -- endround boarder +PHEndGray = Color(50, 50, 50, 50) -- mapvote hover + +PHTauntDark = Color(68, 68, 68, 160) -- taunt buttons +PHTauntDarker = Color(40, 40, 40, 230) -- side panel & title bar +PHTauntDarkest = Color(55, 55, 55, 120) -- taunt list background +PHTauntBlack = Color(0, 0, 0, 230) -- taunt outline + +PHWhite = Color(255, 255, 255, 255) -- text +PHLessWhite = Color(190, 190, 190) -- subtext + +PHRed = Color(199, 49, 29) -- error, disguise outline, scoreboard text +PHOrange = Color(255, 150, 50) -- hunters +PHGreen = Color(50, 170, 46) -- aim lazer +PHBlue = Color(50, 150, 255) -- props + +-- misc. +CornerRadius = "4" -- set to 0 to disable rounded corners. number is in pixels relative to 480p. +ScobBackground = true -- set to false to disable scoreboard background & credits +GroupTags = false -- set to true to enable group tags. requires ULX +ActEnableAll = false -- set to true to enable all default gmod animations +NameDistance = 500 -- adjusts how close you need to be to see a player's name + +GroupColors = {} +GroupColors["example"] = PHExample + +GroupNames = {} +GroupNames["example"] = "Example" + +-- it is ideal to delete undesired options rather than setting to false. see https://wiki.facepunch.com/gmod/Enums/ACT +ActWhitelist = {} +ActWhitelist[ACT_GMOD_GESTURE_BOW] = true +ActWhitelist[ACT_GMOD_GESTURE_WAVE] = true +ActWhitelist[ACT_GMOD_GESTURE_AGREE] = true +ActWhitelist[ACT_GMOD_GESTURE_BECON] = true +ActWhitelist[ACT_GMOD_GESTURE_DISAGREE] = true +ActWhitelist[ACT_GMOD_GESTURE_TAUNT_ZOMBIE] = true +ActWhitelist[ACT_GMOD_TAUNT_LAUGH] = true +ActWhitelist[ACT_GMOD_TAUNT_CHEER] = true +ActWhitelist[ACT_GMOD_TAUNT_DANCE] = true +ActWhitelist[ACT_GMOD_TAUNT_ROBOT] = true +ActWhitelist[ACT_GMOD_TAUNT_SALUTE] = true +ActWhitelist[ACT_GMOD_TAUNT_MUSCLE] = true +ActWhitelist[ACT_GMOD_TAUNT_PERSISTENCE] = true +ActWhitelist[ACT_SIGNAL_HALT] = true +ActWhitelist[ACT_SIGNAL_GROUP] = true +ActWhitelist[ACT_SIGNAL_FORWARD] = true + +-- set which HUD elements to hide. see https://wiki.facepunch.com/gmod/HUD_Element_List +-- gamemode-added elements include "PropHuntersPlayerNames", +HudBlacklist = {} +HudBlacklist["CHudVoiceSelfStatus"] = true -- you should probably leave this one alone. disabling the custom voice panel is no currently supported +HudBlacklist["CHudVoiceStatus"] = true -- same deal here From aa35429e97783b4e0f3bb837e74dfacba6b3c9e4 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Mon, 29 Sep 2025 21:38:34 +1000 Subject: [PATCH 08/25] fix scoreboard icons and better centering --- .../ultimateph/gamemode/cl_scoreboard.lua | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index a3cf939..0914a88 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -24,14 +24,19 @@ local function addPlayerItem(self, mlist, ply, pteam) function but:Paint(w, h) surface.SetDrawColor(color_black) + + if tobool(GroupTags) and GroupColors[ply:GetUserGroup()] ~= nil then + surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) + self:DrawFilledRect() + end if IsValid(ply) and ply:IsPlayer() then local s = ScreenScaleH(2) if not ply:Alive() then surface.SetMaterial(skull) surface.SetDrawColor(220, 220, 220, 255) - surface.DrawTexturedRect(s, 0, ScreenScaleH(16), ScreenScaleH(16)) - s = s + ScreenScaleH(16) + surface.DrawTexturedRect(s, ScreenScaleH(4), ScreenScaleH(8), ScreenScaleH(8)) + s = s + ScreenScaleH(8) end if ply:IsMuted() then @@ -39,26 +44,19 @@ local function addPlayerItem(self, mlist, ply, pteam) -- draw mute icon surface.SetDrawColor(150, 150, 150, 255) - surface.DrawTexturedRect(s, 0, ScreenScaleH(16), ScreenScaleH(16)) - s = s + ScreenScaleH(16) + surface.DrawTexturedRect(s, ScreenScaleH(4), ScreenScaleH(8), ScreenScaleH(8)) + s = s + ScreenScaleH(8) end -- group tags :D - if tobool(GroupTags) then - if GroupColors[ply:GetUserGroup()] ~= nil then - surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) - self:DrawFilledRect() - end - - if GroupNames[ply:GetUserGroup()] then - draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, 0, PHScobDarkest, 0) - s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + ScreenScaleH(2) - draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, 0, color_white, 0) - draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), 0, PHHudWhite, 2) - end + if tobool(GroupTags) and GroupNames[ply:GetUserGroup()] then + draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, h / 2, PHScobDarkest, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + ScreenScaleH(2) + draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), h / 2, PHHudWhite, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) else - draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, 0, PHWHite, 0) - draw.ShadowText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), 0, PHWhite, 2) + draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, h / 2, PHWHite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.ShadowText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), h / 2, PHWhite, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end end end From 8923678ca704521ac02b68b5c68154bb10dc0515 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Mon, 29 Sep 2025 21:58:43 +1000 Subject: [PATCH 09/25] unsquish voice --- gamemodes/ultimateph/gamemode/cl_hud.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index 77eb2bc..697be27 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -128,9 +128,9 @@ local PlayerVoicePanels = {} function PANEL:Init() self.Avatar = vgui.Create("AvatarImage", self) self.Avatar:Dock(LEFT) - self.Avatar:SetSize(math.Clamp(ScreenScaleH(16), 16, 32), math.Clamp(ScreenScaleH(16), 16, 32)) + self.Avatar:SetSize(ScreenScaleH(16), ScreenScaleH(16)) self.Color = color_transparent - self:SetSize(math.Clamp(ScreenScaleH(16), 16, 32), math.Clamp(ScreenScaleH(16), 16, 32)) + self:SetSize(ScreenScaleH(16), ScreenScaleH(16)) self:DockPadding(ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2)) self:DockMargin(ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2)) self:Dock(BOTTOM) From 6b55add57752e57b4f92bdd79eb6a868a2d21085 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Tue, 30 Sep 2025 13:58:18 +1000 Subject: [PATCH 10/25] scob rewrite --- .../ultimateph/gamemode/cl_scoreboard.lua | 418 ++++++------------ gamemodes/ultimateph/gamemode/sv_teams.lua | 8 + 2 files changed, 141 insertions(+), 285 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index 0914a88..d4d2d70 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -1,339 +1,187 @@ -if GAMEMODE and IsValid(GAMEMODE.ScoreboardPanel) then - GAMEMODE.ScoreboardPanel:Remove() -end - -local menu - -surface.CreateFont("ScoreboardPlayer" , { - font = "coolvetica", - size = math.Clamp( ScreenScaleH(16), 16, 32 ), - weight = 500, - antialias = true, - italic = false -}) - -local muted = Material("icon32/muted.png", "noclamp") -local skull = Material("husklesph/skull.png", "noclamp") - -local function addPlayerItem(self, mlist, ply, pteam) - local but = vgui.Create("DButton") - but.player = ply - but.ctime = CurTime() - but:SetTall(draw.GetFontHeight("RobotoHUD-20") + ScreenScaleH(4)) - but:SetText("") - - function but:Paint(w, h) - surface.SetDrawColor(color_black) - - if tobool(GroupTags) and GroupColors[ply:GetUserGroup()] ~= nil then - surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) - self:DrawFilledRect() - end - - if IsValid(ply) and ply:IsPlayer() then - local s = ScreenScaleH(2) - if not ply:Alive() then - surface.SetMaterial(skull) - surface.SetDrawColor(220, 220, 220, 255) - surface.DrawTexturedRect(s, ScreenScaleH(4), ScreenScaleH(8), ScreenScaleH(8)) - s = s + ScreenScaleH(8) - end - - if ply:IsMuted() then - surface.SetMaterial(muted) - - -- draw mute icon - surface.SetDrawColor(150, 150, 150, 255) - surface.DrawTexturedRect(s, ScreenScaleH(4), ScreenScaleH(8), ScreenScaleH(8)) - s = s + ScreenScaleH(8) - end - - -- group tags :D - if tobool(GroupTags) and GroupNames[ply:GetUserGroup()] then - draw.SimpleText("["..GroupNames[ply:GetUserGroup()].."]", "RobotoHUD-L20", s, h / 2, PHScobDarkest, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - s = s + surface.GetTextSize("["..GroupNames[ply:GetUserGroup()].."]") + ScreenScaleH(2) - draw.SimpleText(ply:Nick(), "RobotoHUD-L20", s, h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - draw.SimpleText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), h / 2, PHHudWhite, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) - else - draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, h / 2, PHWHite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - draw.ShadowText(ply:Ping(), "RobotoHUD-L20", w - ScreenScaleH(2), h / 2, PHWhite, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) - end - end - end - - function but:DoClick() - if IsValid(ply) then - GAMEMODE:DoScoreboardActionPopup(ply) - end - end - - mlist:AddItem(but) -end - -local function doPlayerItems(self, mlist, pteam) - for k, ply in pairs(team.GetPlayers(pteam)) do - local found = false - for t, v in pairs(mlist:GetCanvas():GetChildren()) do - if v.player == ply then - found = true - v.ctime = CurTime() - end - end - - if not found then - addPlayerItem(self, mlist, ply, pteam) - end - end - - local del = false - for t, v in pairs(mlist:GetCanvas():GetChildren()) do - if not v.perm and v.ctime ~= CurTime() then - v:Remove() - del = true - end - end - - -- make sure the rest of the elements are moved up - if del then - timer.Simple(0, function() mlist:GetCanvas():InvalidateLayout() end) - end -end - -local function makeTeamList(parent, pteam) - local mlist - local pnl = vgui.Create("DPanel", parent) - pnl:DockPadding(0, 0, 0, 0) - local hs = math.Round(draw.GetFontHeight("RobotoHUD-25") * 1.1) - - function pnl:Paint(w, h) - surface.SetDrawColor(PHScobDark) - surface.DrawRect(0, hs, w, h - hs ) - end - - function pnl:Think() - if not self.RefreshWait or self.RefreshWait < CurTime() then - self.RefreshWait = CurTime() + 0.1 - doPlayerItems(self, mlist, pteam) - end - end - - local headp = vgui.Create("DPanel", pnl) - headp:DockMargin(0, 0, 0, ScreenScaleH(2)) - headp:Dock(TOP) - headp:SetTall(hs) - - function headp:Paint(w, h) - surface.SetDrawColor(PHScobDarker) - draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker, true, true, false, false) - draw.ShadowText(team.GetName(pteam), "RobotoHUD-25", ScreenScaleH(6), 0, team.GetColor(pteam), 0) - end - - local but = vgui.Create("DButton", headp) - but:Dock(RIGHT) - but:SetText("") - surface.SetFont("RobotoHUD-20") - local tw, th = surface.GetTextSize("Join team") - but:SetWide(tw + ScreenScaleH(6)) - - function but:DoClick() - RunConsoleCommand("ph_jointeam", pteam) - end - - function but:Paint(w, h) - surface.SetDrawColor(team.GetColor(pteam)) - - local col = table.Copy(team.GetColor(pteam)) - if self:IsDown() then - colMul(col, 0.8) - elseif self:IsHovered() then - colMul(col, 1.2) - end - - draw.ShadowText("Join team", "RobotoHUD-20", ScreenScaleH(2), h / 2 - th / 2, col, 0) - end - - mlist = vgui.Create("DScrollPanel", pnl) - mlist:Dock(FILL) - - function mlist:Paint(w, h) - end - - -- child positioning - local canvas = mlist:GetCanvas() - canvas:DockPadding(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4)) - - function canvas:OnChildAdded(child) - child:Dock(TOP) - child:DockMargin(0, 0, 0, ScreenScaleH(2)) - end - - local head = vgui.Create("DPanel") - head:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.05) - head.perm = true - - function head:Paint(w, h) - draw.ShadowText("Name", "RobotoHUD-15", ScreenScaleH(2), 0, PHLessWhite, 0) - draw.ShadowText("Ping", "RobotoHUD-15", w - ScreenScaleH(2), 0, PHLessWhite, 2) - end - - mlist:AddItem(head) - return pnl -end - -function GM:ScoreboardRoundResults(results) - self:ScoreboardShow() - menu.ResultsPanel.Results = results - menu.ResultsPanel:InvalidateLayout() -end +scoreboard = scoreboard or {} -local function createScoreboardPanel() - menu = vgui.Create("DFrame") - GAMEMODE.ScoreboardPanel = menu +function scoreboard:show() + local menu = vgui.Create("DFrame") + PHScoreboard = menu menu:SetSize(ScreenScale(512), ScreenScaleH(384)) menu:Center() menu:MakePopup() menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) menu:SetDraggable(false) menu:ShowCloseButton(false) menu:SetTitle("") menu:DockPadding(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4)) - function menu:PerformLayout() - if IsValid(menu.HuntersList) then - menu.HuntersList:SetWidth((self:GetWide() - ScreenScaleH(8)) * 0.5) - end - end - function menu:Paint(w, h) if not tobool(ScobBackground) then return end + surface.SetDrawColor(PHScobBlack) surface.DrawOutlinedRect(0, 0, w, h, math.Clamp(ScreenScaleH(2), 2, 4)) surface.SetDrawColor(PHScobDarkest) surface.DrawRect(math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4), w - math.Clamp(ScreenScaleH(4), 4, 8), h - math.Clamp(ScreenScaleH(4), 4, 8)) end - menu.Credits = vgui.Create("DPanel", menu) - menu.Credits:Dock(TOP) - menu.Credits:DockMargin(0, 0, 0, ScreenScaleH(2)) + Header(menu, TOP) + SpectatorList(menu, TEAM_SPEC, BOTTOM) + HunterList = TeamList(menu, TEAM_HUNTER, LEFT) + PropList = TeamList(menu, TEAM_PROP, RIGHT) + + function scoreboard:hide() + menu:Close() + end +end - function menu.Credits:Paint(w, h) +function Header(parent, dock) + local Header = vgui.Create("DPanel", parent) + Header:Dock(dock) + Header:DockMargin(0, 0, 0, ScreenScaleH(2)) + + function Header:Paint(w, h) if not tobool(ScobBackground) then return end surface.SetFont("RobotoHUD-25") - local t = GAMEMODE.Name or "" - local tw = surface.GetTextSize(t) - draw.ShadowText(t, "RobotoHUD-25", ScreenScaleH(2), draw.GetFontHeight("RobotoHUD-25"), PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - draw.ShadowText("- "..tostring(GAMEMODE.Version or "error") .. ", maintained by DataNext, code by many cool people :)", "RobotoHUD-L12", tw + ScreenScaleH(8), draw.GetFontHeight("RobotoHUD-L12") * 2, PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - end - - function menu.Credits:PerformLayout() - surface.SetFont("RobotoHUD-25") - local _, h = surface.GetTextSize(GAMEMODE.Name || "") - self:SetTall(h) + self:SetTall(draw.GetFontHeight("RobotoHUD-25")) + draw.ShadowText(GAMEMODE.Name or "", "RobotoHUD-25", ScreenScaleH(2), draw.GetFontHeight("RobotoHUD-25"), PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) end +end - local bottom = vgui.Create("DPanel", menu) - bottom:SetTall(draw.GetFontHeight("RobotoHUD-15") * 1.3) - bottom:Dock(BOTTOM) - bottom:DockMargin(0, ScreenScaleH(4), 0, 0) - - surface.SetFont("RobotoHUD-15") - local tw = surface.GetTextSize("Spectate") - - function bottom:Paint(w, h) - draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker) - local c - for k, ply in pairs(team.GetPlayers(TEAM_SPEC)) do - if c then - c = c .. ", " .. ply:Nick() - else - c = ply:Nick() +function TeamList(parent, pteam, dock) + local PlayerListBG = vgui.Create("DPanel", parent) + PlayerListBG:Dock(dock) + PlayerListBG:DockMargin(0, 0, 0, ScreenScale(4)) + PlayerListBG:SetSize(parent:GetWide() / 2 - ScreenScaleH(8), parent:GetTall()) + + function PlayerListBG:Paint(w, h) + surface.SetDrawColor(0, 0, 0, 0) + end + + TeamHeader(PlayerListBG, pteam, TOP, "Join Team") + + local PlayerList = vgui.Create("DListView", PlayerListBG) + PlayerList:Dock(dock) + PlayerList:SetSize(PlayerListBG:GetWide(), PlayerListBG:GetTall()) + PlayerList:SetDataHeight(draw.GetFontHeight("RobotoHUD-L20")) + PlayerList:SetHeaderHeight(draw.GetFontHeight("RobotoHUD-L20")) + PlayerList:AddColumn("NAME", 1) + PlayerList:AddColumn("KILLS", 2) + PlayerList:AddColumn("DEATHS", 3) + PlayerList:AddColumn("PING", 4) + + function PlayerList:Paint(w, h) + surface.SetDrawColor(PHScobDarkest) + draw.RoundedBoxEx(0, 0, 0, w, h, PHScobDark, true, true, false, false) + end + + for _, ply in ipairs( team.GetPlayers(pteam) ) do + local line = PlayerList:AddLine(ply:Name(), (ply:Frags() + ply:Deaths()), ply:Deaths(), ply:Ping()) + function line:Paint( w, h ) + if tobool(GroupTags) and GroupColors[ply:GetUserGroup()] ~= nil then + surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) + self:DrawFilledRect() + end + + for _, cln in pairs( self.Columns ) do + cln:SetFont("RobotoHUD-L20") + cln:SetTextColor(PHWhite) + cln:SetContentAlignment(5) end end - - if c then - draw.ShadowText(c, "RobotoHUD-10", tw + ScreenScaleH(4) + ScreenScaleH(2), h / 2, PHWhite, 0, 1) + end + + for _, v in ipairs(PlayerList.Columns) do + function v.Header:Paint(w, h) + self:SetFont("RobotoHUD-L20") + self:SetTextColor(PHLessWhite) + v:SetTextAlign(5) end end +end - local but = vgui.Create("DButton", bottom) - but:Dock(LEFT) - but:SetText("") - but:DockMargin(0, 0, ScreenScaleH(2), 0) - but:SetWide(tw + ScreenScaleH(4)) +function TeamHeader(parent, pteam, dock, text) + local TeamHeader = vgui.Create("DPanel", parent) + TeamHeader:SetSize(parent:GetWide(), parent:GetTall() / 16) + TeamHeader:Dock(dock) - function but:Paint(w, h) - local colt = table.Copy(PHLessWhite) - if self:IsDown() then - colMul(colt, 0.8) - elseif self:IsHovered() then - colMul(colt, 1.2) - end + function TeamHeader:Paint(w, h) + surface.SetDrawColor(PHScobDarker) + draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker, true, true, false, false) + draw.ShadowText(team.GetName(pteam), "RobotoHUD-25", ScreenScaleH(2), h / 2, team.GetColor(pteam), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + JoinTeam(TeamHeader, pteam, RIGHT, text) +end +function SpectatorList(parent, pteam, dock) + local SpectatorListBG = vgui.Create("DPanel", parent) + SpectatorListBG:Dock(dock) + SpectatorListBG:SetSize(parent:GetWide(), parent:GetTall() / 24) + + function SpectatorListBG:Paint(w, h) + surface.SetFont("RobotoHUD-20") draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker) - draw.ShadowText("Spectate", "RobotoHUD-15", w / 2, h / 2, colt, 1, 1) end - function but:DoClick() - RunConsoleCommand("ph_jointeam", TEAM_SPEC) + JoinTeam(SpectatorListBG, TEAM_SPEC, LEFT, "Spectate") + + local SpectatorList = vgui.Create("DHorizontalScroller", SpectatorListBG) + SpectatorList:SetTall(draw.GetFontHeight("RobotoHUD-20")) + SpectatorList:Dock(dock) + + for _, ply in ipairs(team.GetPlayers(TEAM_SPEC)) do + local line = vgui.Create("DLabel", SpectatorList) + line:SetColor(PHLessWhite) + line:SetFont("RobotoHUD-L10") + line:SetText(ply:Nick()) + line:SetWide(line:GetTextSize() + ScreenScaleH(6)) + SpectatorList:AddPanel(line) end +end - local main = vgui.Create("DPanel", menu) - main:Dock(FILL) +function JoinTeam(parent, pteam, dock, text) + local JoinTeam = vgui.Create("DButton", parent) + JoinTeam:Dock(dock) + JoinTeam:SetText("") + surface.SetFont("RobotoHUD-20") + JoinTeam:SetWide(surface.GetTextSize(text) + ScreenScaleH(6)) - function main:Paint(w, h) - surface.SetDrawColor(PHScobDarkest) + function JoinTeam:Paint(w, h) + local col = table.Copy(team.GetColor(pteam)) + if self:IsDown() then + colMul(col, 0.8) + elseif self:IsHovered() then + colMul(col, 1.2) + end + + draw.ShadowText(text, "RobotoHUD-20", w - ScreenScaleH(2), h / 2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end - menu.HuntersList = makeTeamList(main, TEAM_HUNTER) - menu.HuntersList:Dock(LEFT) - menu.HuntersList:DockMargin(0, 0, ScreenScaleH(4), 0) - menu.PropsList = makeTeamList(main, TEAM_PROP) - menu.PropsList:Dock(FILL) + function JoinTeam:DoClick() + RunConsoleCommand("ph_jointeam", pteam) + end end function GM:ScoreboardShow() - if not IsValid(menu) then - createScoreboardPanel() + if IsValid(PHScoreboard) then + scoreboard:hide() end - - menu:SetVisible(true) + scoreboard:show() end function GM:ScoreboardHide() - if IsValid(menu) then - menu:Close() + if IsValid(PHScoreboard) then + scoreboard:hide() end end -function GM:DoScoreboardActionPopup(ply) - local actions = DermaMenu() - - if ply:IsAdmin() then - local admin = actions:AddOption("Is an Admin") - admin:SetIcon("icon16/shield.png") - end - - if ply ~= LocalPlayer() then - local t = "Mute" - if ply:IsMuted() then - t = "Unmute" +-- hack to update player list w/o constantly running through loops. todo: add a proper refresh function +net.Receive("TeamChanged", function(ply) + timer.Simple(0.1, function() + if IsValid(PHScoreboard) then + RunConsoleCommand("-showscores") + timer.Simple(0, function() RunConsoleCommand("+showscores") end) end - - local mute = actions:AddOption(t) - mute:SetIcon("icon16/sound_mute.png") - - function mute:DoClick() - if IsValid(ply) then - ply:SetMuted(not ply:IsMuted()) - end - end - end - - actions:Open() -end + end) +end) diff --git a/gamemodes/ultimateph/gamemode/sv_teams.lua b/gamemodes/ultimateph/gamemode/sv_teams.lua index bd3206e..6fd6722 100644 --- a/gamemodes/ultimateph/gamemode/sv_teams.lua +++ b/gamemodes/ultimateph/gamemode/sv_teams.lua @@ -1,3 +1,5 @@ +util.AddNetworkString("TeamChanged") + concommand.Add("ph_jointeam", function(ply, com, args) local newTeam = tonumber(args[1]) || TEAM_SPEC -- Default to spectators if there's a problem if ply:Team() == newTeam then return end @@ -220,3 +222,9 @@ function GM:ChooseSpectatee(ply) self:SpectateNext(ply) end end + +hook.Add("PlayerChangedTeam", "TeamChanged", function(ply) + net.Start("TeamChanged") + net.WriteEntity(ply) + net.Broadcast() +end) From 86b72271b55738d8657a2ca0a3f10a1e8077db7c Mon Sep 17 00:00:00 2001 From: queeek180 Date: Wed, 1 Oct 2025 06:04:02 +1000 Subject: [PATCH 11/25] more scob work + shadowtext removal --- .../ultimateph/gamemode/cl_helpscreen.lua | 2 +- gamemodes/ultimateph/gamemode/cl_hud.lua | 50 ++++----- gamemodes/ultimateph/gamemode/cl_init.lua | 11 +- .../ultimateph/gamemode/cl_scoreboard.lua | 104 +++++++++++------- gamemodes/ultimateph/gamemode/cl_taunt.lua | 12 +- gamemodes/ultimateph/gamemode/sh_config.lua | 10 +- 6 files changed, 111 insertions(+), 78 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua index 6ca7a37..0aa94cb 100644 --- a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua +++ b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua @@ -35,7 +35,7 @@ local function createHelpMenu() surface.SetDrawColor(40, 40, 40, 230) surface.DrawRect(0, 0, w, h) surface.SetFont("RobotoHUD-25") - draw.ShadowText("Help", "RobotoHUD-25", 8, 2, Color(132, 199, 29), 0) + draw.SimpleText("Help", "RobotoHUD-25", 8, 2, Color(132, 199, 29), 0) end local text = vgui.Create("DLabel", menu) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index 697be27..0014563 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -20,7 +20,7 @@ function GM:DrawGameHUD() if ply ~= LocalPlayer() then local col = team.GetColor(ply:Team()) - draw.ShadowText(ply:Nick(), "RobotoHUD-30", ScrW() / 2, ScrH() - ScreenScaleH(2), col, 1, 4) + draw.SimpleText(ply:Nick(), "RobotoHUD-30", ScrW() / 2, ScrH() - ScreenScaleH(2), col, 1, 4) end local eyeTrace = ply:GetEyeTraceNoCursor() @@ -38,7 +38,7 @@ function GM:DrawGameHUD() local name = self.LastLooked:Nick() or "error" local col = table.Copy(team.GetColor(self.LastLooked:Team())) col.a = (1 - (CurTime() - self.LookedFade) / 2) * 255 - draw.ShadowText(name, "RobotoHUD-20", ScrW() / 2, ScrH() / 1.85, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, col.a)) + draw.SimpleText(name, "RobotoHUD-20", ScrW() / 2, ScrH() / 1.85, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end end @@ -54,33 +54,33 @@ function GM:DrawGameHUD() local tauntMenu = input.LookupBinding("gm_showspare1") if not disguise or not rotationLock or not tauntMenu then - draw.ShadowText("UNBOUND!", "RobotoHUD-15", ScreenScale(4), ScrH() / 2, PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText(" Please ensure +attack, +menu_context, and gm_showspare1 are bound!", "RobotoHUD-L15", ScreenScale(4) + surface.GetTextSize("UNBOUND!"), ScrH() / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText("* MOUSE1, C, and F3 by default.", "RobotoHUD-L12", ScreenScale(4), ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.SimpleText("UNBOUND!", "RobotoHUD-15", ScreenScale(4), ScrH() / 2, PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.SimpleText(" Please ensure +attack, +menu_context, and gm_showspare1 are bound!", "RobotoHUD-L15", ScreenScale(4) + surface.GetTextSize("UNBOUND!"), ScrH() / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.SimpleText("* MOUSE1, C, and F3 by default.", "RobotoHUD-L12", ScreenScale(4), ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) return end local totalWidth = math.max(surface.GetTextSize(disguise), surface.GetTextSize(rotationLock), surface.GetTextSize(tauntMenu)) - draw.ShadowText(disguise, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 - fontSize, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) - draw.ShadowText(" Disguise", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 - fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.SimpleText(disguise, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 - fontSize, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.SimpleText(" Disguise", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 - fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText(rotationLock, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) - draw.ShadowText(" Lock Rotation", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.SimpleText(rotationLock, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.SimpleText(" Lock Rotation", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) - draw.ShadowText(tauntMenu, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) - draw.ShadowText(" Taunt Menu", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) + draw.SimpleText(tauntMenu, "RobotoHUD-15", ScreenScale(4) + totalWidth / 2, ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT) + draw.SimpleText(" Taunt Menu", "RobotoHUD-L15", ScreenScale(4) + totalWidth, ScrH() / 2 + fontSize, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_LEFT) end function GM:DrawRoundTimer() if self:GetGameState() == ROUND_WAIT then local time = math.ceil(self.StartWaitTime:GetFloat() - self:GetStateRunningTime()) if time > 0 then - draw.ShadowText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText("Game starts in " .. tostring(time) .. " second" .. (time > 1 and "s" or ""), "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("Waiting for players to join", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("Game starts in " .. tostring(time) .. " second" .. (time > 1 and "s" or ""), "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) else - draw.ShadowText("Not enough players to start", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("Not enough players to start", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("Waiting for more players to join", "RobotoHUD-15", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end return end @@ -88,8 +88,8 @@ function GM:DrawRoundTimer() if self:GetGameState() == ROUND_HIDE then local time = math.ceil(self.HidingTime:GetInt() - self:GetStateRunningTime()) if time > 0 then - draw.ShadowText("Hunters will be released in", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText(time, "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("Hunters will be released in", "RobotoHUD-25", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText(time, "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end return end @@ -99,15 +99,15 @@ function GM:DrawRoundTimer() end if self:GetStateRunningTime() < 2 then - draw.ShadowText("GO!", "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("GO!", "RobotoHUD-50", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) return end local settings = self:GetRoundSettings() local roundTime = settings.RoundTime or 5 * 60 local time = tostring(math.max(0, roundTime - self:GetStateRunningTime())) - draw.ShadowText("Round "..GAMEMODE.CurrentRound.."/"..GAMEMODE.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - draw.ShadowText(string.ToMinutesSeconds(time), "RobotoHUD-20", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-L15"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("Round "..GAMEMODE.CurrentRound.."/"..GAMEMODE.RoundLimit:GetInt(), "RobotoHUD-L15", ScrW() / 2, ScreenScaleH(4), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText(string.ToMinutesSeconds(time), "RobotoHUD-20", ScrW() / 2, ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-L15"), color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end function GM:PreDrawHUD() @@ -160,7 +160,7 @@ function PANEL:Paint(w, h) surface.SetDrawColor(0, 0, 0, 255) surface.DrawOutlinedRect(0, 0, w * volume, h) - draw.ShadowText(self.ply:Nick(), "RobotoHUD-12", self.Avatar:GetWide() + ScreenScale(4), h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(self.ply:Nick(), "RobotoHUD-12", self.Avatar:GetWide() + ScreenScale(4), h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end function PANEL:Think() @@ -323,15 +323,15 @@ local function drawKillFeedHUD() -- If Suicide: [Victim] [Message] if event.attackerName then - widthOffset = widthOffset + draw.ShadowText(event.attackerName .. " ", FONT, widthOffset, event.y, event.attackerColor) + widthOffset = widthOffset + draw.SimpleText(event.attackerName .. " ", FONT, widthOffset, event.y, event.attackerColor) else - widthOffset = widthOffset + draw.ShadowText(event.victimName .. " ", FONT, widthOffset, event.y, event.victimColor) + widthOffset = widthOffset + draw.SimpleText(event.victimName .. " ", FONT, widthOffset, event.y, event.victimColor) end - widthOffset = widthOffset + draw.ShadowText(event.message .. (event.attackerName and " " or ""), FONT, widthOffset, event.y, event.messageColor) + widthOffset = widthOffset + draw.SimpleText(event.message .. (event.attackerName and " " or ""), FONT, widthOffset, event.y, event.messageColor) if event.attackerName then - draw.ShadowText(event.victimName, FONT, widthOffset, event.y, event.victimColor) + draw.SimpleText(event.victimName, FONT, widthOffset, event.y, event.victimColor) end end end diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index fa8a7d3..bbc9c88 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -27,7 +27,7 @@ local function createRoboto(s) }) surface.CreateFont("RobotoHUD-L" .. s , { - font = "Roboto", + font = "Roboto-Regular", size = math.Clamp( ScreenScaleH(s), s, (s * 2) ), weight = 500, antialias = true, @@ -40,11 +40,10 @@ for i = 5, 50, 5 do end createRoboto(8) createRoboto(12) - -function draw.ShadowText(text, font, x, y, color, xalign, yalign, shadowColor) - draw.SimpleText(text, font, x + ScreenScaleH(1), y + ScreenScaleH(1), shadowColor or color_black, xalign, yalign) - return draw.SimpleText(text, font, x, y, color, xalign, yalign) -end +createRoboto(14) +createRoboto(16) +createRoboto(18) +createRoboto(24) function GM:InitPostEntity() net.Start("clientIPE") diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index d4d2d70..f3bd6f9 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -1,6 +1,6 @@ scoreboard = scoreboard or {} -function scoreboard:show() +function scoreboard:show() -- setup the main parent from which all elements will dock local menu = vgui.Create("DFrame") PHScoreboard = menu menu:SetSize(ScreenScale(512), ScreenScaleH(384)) @@ -23,10 +23,14 @@ function scoreboard:show() surface.DrawRect(math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4), w - math.Clamp(ScreenScaleH(4), 4, 8), h - math.Clamp(ScreenScaleH(4), 4, 8)) end - Header(menu, TOP) + -- all the scoreboard elements have been broken out into individual functions for the sake of reducing duplicate code and improving modularity. they are called here + if tobool(ScobBackground) then + Header(menu, TOP) + end + SpectatorList(menu, TEAM_SPEC, BOTTOM) - HunterList = TeamList(menu, TEAM_HUNTER, LEFT) - PropList = TeamList(menu, TEAM_PROP, RIGHT) + PlayerList(menu, TEAM_HUNTER, LEFT) + PlayerList(menu, TEAM_PROP, RIGHT) function scoreboard:hide() menu:Close() @@ -39,17 +43,13 @@ function Header(parent, dock) Header:DockMargin(0, 0, 0, ScreenScaleH(2)) function Header:Paint(w, h) - if not tobool(ScobBackground) then - return - end - - surface.SetFont("RobotoHUD-25") - self:SetTall(draw.GetFontHeight("RobotoHUD-25")) - draw.ShadowText(GAMEMODE.Name or "", "RobotoHUD-25", ScreenScaleH(2), draw.GetFontHeight("RobotoHUD-25"), PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + surface.SetFont("RobotoHUD-18") + self:SetTall(draw.GetFontHeight("RobotoHUD-18")) + draw.SimpleText(GAMEMODE.Name or "", "RobotoHUD-18", ScreenScaleH(2), draw.GetFontHeight("RobotoHUD-18"), PHRed, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) end end -function TeamList(parent, pteam, dock) +function PlayerList(parent, pteam, dock) local PlayerListBG = vgui.Create("DPanel", parent) PlayerListBG:Dock(dock) PlayerListBG:DockMargin(0, 0, 0, ScreenScale(4)) @@ -59,45 +59,75 @@ function TeamList(parent, pteam, dock) surface.SetDrawColor(0, 0, 0, 0) end - TeamHeader(PlayerListBG, pteam, TOP, "Join Team") + TeamHeader(PlayerListBG, pteam, TOP, "Join Team") -- call the header early so it docks properly local PlayerList = vgui.Create("DListView", PlayerListBG) - PlayerList:Dock(dock) + PlayerList:Dock(FILL) PlayerList:SetSize(PlayerListBG:GetWide(), PlayerListBG:GetTall()) - PlayerList:SetDataHeight(draw.GetFontHeight("RobotoHUD-L20")) - PlayerList:SetHeaderHeight(draw.GetFontHeight("RobotoHUD-L20")) - PlayerList:AddColumn("NAME", 1) - PlayerList:AddColumn("KILLS", 2) - PlayerList:AddColumn("DEATHS", 3) - PlayerList:AddColumn("PING", 4) + PlayerList:SetDataHeight(ScreenScaleH(16)) + PlayerList:SetHeaderHeight(draw.GetFontHeight("RobotoHUD-L18")) + surface.SetFont("RobotoHUD-L18") + PlayerList:AddColumn("", 1):SetFixedWidth(ScreenScaleH(16)) + PlayerList:AddColumn("", 2):SetFixedWidth(PlayerList:GetWide() - (ScreenScaleH(16) + surface.GetTextSize("KILLS") + surface.GetTextSize("DEATHS") + surface.GetTextSize("PING"))) + PlayerList:AddColumn("KILLS", 3):SetFixedWidth(surface.GetTextSize("KILLS")) + PlayerList:AddColumn("DEATHS", 4):SetFixedWidth(surface.GetTextSize("DEATHS")) + PlayerList:AddColumn("PING", 5):SetFixedWidth(surface.GetTextSize("PING")) function PlayerList:Paint(w, h) surface.SetDrawColor(PHScobDarkest) draw.RoundedBoxEx(0, 0, 0, w, h, PHScobDark, true, true, false, false) end - for _, ply in ipairs( team.GetPlayers(pteam) ) do - local line = PlayerList:AddLine(ply:Name(), (ply:Frags() + ply:Deaths()), ply:Deaths(), ply:Ping()) + for _, ply in ipairs( team.GetPlayers(pteam) ) do + local line = PlayerList:AddLine(nil, ply:Nick(), ply:Frags(), ply:Deaths(), ply:Ping()) -- leave column 1 empty to override with player's avatar later + function line:Paint( w, h ) if tobool(GroupTags) and GroupColors[ply:GetUserGroup()] ~= nil then surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) self:DrawFilledRect() end - - for _, cln in pairs( self.Columns ) do - cln:SetFont("RobotoHUD-L20") - cln:SetTextColor(PHWhite) - cln:SetContentAlignment(5) + end + + for id, cln in ipairs( line.Columns ) do + cln:SetFont("RobotoHUD-L18") + cln:SetTextColor(PHWhite) + + if id == 1 then -- override the content of column1 with avatar, group, mute and death icons + local Avatar = vgui.Create( "AvatarImage", cln ) + Avatar:SetPos( 0, 0 ) + Avatar:SetPlayer( ply, 64 ) + Avatar:Dock(FILL) + ply.Avatar = Avatar + + if tobool(GroupTags) and GroupIcons[ply:GetUserGroup()] ~= nil then + local mat = vgui.Create("Material", ply.Avatar) + mat:SetPos(0, 0) + mat:SetFGColor(PHWhite) + mat:SetMaterial(GroupIcons[ply:GetUserGroup()]) + end + end + + if id == 2 then -- adjust text alignment for player names (align left center) + cln:SetContentAlignment(4) + continue end + + cln:SetContentAlignment(5) -- set the rest to align center center end end - for _, v in ipairs(PlayerList.Columns) do + for id, v in ipairs(PlayerList.Columns) do function v.Header:Paint(w, h) - self:SetFont("RobotoHUD-L20") + self:SetFont("RobotoHUD-L14") self:SetTextColor(PHLessWhite) - v:SetTextAlign(5) end + + if id <= 2 then + v:SetTextAlign(4) + continue + end + + v:SetTextAlign(5) end end @@ -109,7 +139,7 @@ function TeamHeader(parent, pteam, dock, text) function TeamHeader:Paint(w, h) surface.SetDrawColor(PHScobDarker) draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker, true, true, false, false) - draw.ShadowText(team.GetName(pteam), "RobotoHUD-25", ScreenScaleH(2), h / 2, team.GetColor(pteam), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(team.GetName(pteam), "RobotoHUD-18", ScreenScaleH(2), h / 2, team.GetColor(pteam), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end JoinTeam(TeamHeader, pteam, RIGHT, text) end @@ -120,20 +150,20 @@ function SpectatorList(parent, pteam, dock) SpectatorListBG:SetSize(parent:GetWide(), parent:GetTall() / 24) function SpectatorListBG:Paint(w, h) - surface.SetFont("RobotoHUD-20") - draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker) + surface.SetFont("RobotoHUD-18") + draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDark) end JoinTeam(SpectatorListBG, TEAM_SPEC, LEFT, "Spectate") local SpectatorList = vgui.Create("DHorizontalScroller", SpectatorListBG) - SpectatorList:SetTall(draw.GetFontHeight("RobotoHUD-20")) + SpectatorList:SetTall(draw.GetFontHeight("RobotoHUD-18")) SpectatorList:Dock(dock) for _, ply in ipairs(team.GetPlayers(TEAM_SPEC)) do local line = vgui.Create("DLabel", SpectatorList) line:SetColor(PHLessWhite) - line:SetFont("RobotoHUD-L10") + line:SetFont("RobotoHUD-L14") line:SetText(ply:Nick()) line:SetWide(line:GetTextSize() + ScreenScaleH(6)) SpectatorList:AddPanel(line) @@ -144,7 +174,7 @@ function JoinTeam(parent, pteam, dock, text) local JoinTeam = vgui.Create("DButton", parent) JoinTeam:Dock(dock) JoinTeam:SetText("") - surface.SetFont("RobotoHUD-20") + surface.SetFont("RobotoHUD-18") JoinTeam:SetWide(surface.GetTextSize(text) + ScreenScaleH(6)) function JoinTeam:Paint(w, h) @@ -155,7 +185,7 @@ function JoinTeam(parent, pteam, dock, text) colMul(col, 1.2) end - draw.ShadowText(text, "RobotoHUD-20", w - ScreenScaleH(2), h / 2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + draw.SimpleText(text, "RobotoHUD-18", w - ScreenScaleH(2), h / 2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end function JoinTeam:DoClick() diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index f66faf1..255a08f 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -43,8 +43,8 @@ local function fillList(mlist, taunts, cat) elseif self:IsHovered() then colMul(col, 0.8) end - draw.ShadowText(t.name, "RobotoHUD-L15", 0, h / 2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) - draw.ShadowText(math.Round(t.soundDuration % 60, 2) .. "s", "RobotoHUD-L10", w - ScreenScaleH(2), h / 2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + draw.SimpleText(t.name, "RobotoHUD-L15", 0, h / 2, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(math.Round(t.soundDuration % 60, 2) .. "s", "RobotoHUD-L10", w - ScreenScaleH(2), h / 2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) end function but:DoClick() @@ -82,7 +82,7 @@ local function addCat(clist, name, taunts, mlist) end draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, col, true, false, true, false) - draw.ShadowText(dname, "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(dname, "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end function but:DoClick() @@ -178,8 +178,8 @@ local function openTauntMenu() surface.SetFont("RobotoHUD-25") local t = "Taunts" local tw = surface.GetTextSize(t) - draw.ShadowText(t, "RobotoHUD-25", ScreenScaleH(4), draw.GetFontHeight("RobotoHUD-25"), PHBlue, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - draw.ShadowText("- ".. GetConVar("ph_taunt_menu_phrase"):GetString(), "RobotoHUD-L15", ScreenScaleH(8) + tw, draw.GetFontHeight("RobotoHUD-L15"), PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(t, "RobotoHUD-25", ScreenScaleH(4), draw.GetFontHeight("RobotoHUD-25"), PHBlue, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText("- ".. GetConVar("ph_taunt_menu_phrase"):GetString(), "RobotoHUD-L15", ScreenScaleH(8) + tw, draw.GetFontHeight("RobotoHUD-L15"), PHLessWhite, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end local leftpnl = vgui.Create("DPanel", menu) @@ -205,7 +205,7 @@ local function openTauntMenu() end draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, col, true, true, true, true) - draw.ShadowText("Random", "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("Random", "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end function but:DoClick() diff --git a/gamemodes/ultimateph/gamemode/sh_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua index 89a6c32..73d305b 100644 --- a/gamemodes/ultimateph/gamemode/sh_config.lua +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -26,15 +26,19 @@ PHBlue = Color(50, 150, 255) -- props -- misc. CornerRadius = "4" -- set to 0 to disable rounded corners. number is in pixels relative to 480p. ScobBackground = true -- set to false to disable scoreboard background & credits -GroupTags = false -- set to true to enable group tags. requires ULX +GroupTags = true -- set to true to enable group tags. requires ULX ActEnableAll = false -- set to true to enable all default gmod animations NameDistance = 500 -- adjusts how close you need to be to see a player's name GroupColors = {} -GroupColors["example"] = PHExample +GroupColors["superadmin"] = PHRed +GroupColors["user"] = PHBlue GroupNames = {} -GroupNames["example"] = "Example" +GroupNames["superadmin"] = "Super Admin" + +GroupIcons = {} +--GroupIcons["superadmin"] = "icon16/award_star_gold_1.png" -- it is ideal to delete undesired options rather than setting to false. see https://wiki.facepunch.com/gmod/Enums/ACT ActWhitelist = {} From 437a9a46085fba9d0936015afdccf2b4c4b44eea Mon Sep 17 00:00:00 2001 From: queeek180 Date: Wed, 1 Oct 2025 22:25:57 +1000 Subject: [PATCH 12/25] more scob work + register all net message names in init.lua, remove two redundant networking --- .../ultimateph/gamemode/cl_helpscreen.lua | 2 +- gamemodes/ultimateph/gamemode/cl_init.lua | 20 ++++++++--- .../ultimateph/gamemode/cl_scoreboard.lua | 34 +++++++++++++++---- gamemodes/ultimateph/gamemode/cl_taunt.lua | 9 ----- gamemodes/ultimateph/gamemode/init.lua | 24 ++++++++++--- .../ultimateph/gamemode/sh_bannedmodels.lua | 4 --- gamemodes/ultimateph/gamemode/sh_config.lua | 2 +- gamemodes/ultimateph/gamemode/sh_mapvote.lua | 6 +--- gamemodes/ultimateph/gamemode/sh_rounds.lua | 9 +---- gamemodes/ultimateph/gamemode/sh_taunt.lua | 20 +---------- gamemodes/ultimateph/gamemode/sv_mapvote.lua | 4 --- gamemodes/ultimateph/gamemode/sv_player.lua | 7 ++-- gamemodes/ultimateph/gamemode/sv_teams.lua | 14 +++----- gamemodes/ultimateph/gamemode/sv_utils.lua | 3 -- 14 files changed, 77 insertions(+), 81 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua index 0aa94cb..7fe7f9c 100644 --- a/gamemodes/ultimateph/gamemode/cl_helpscreen.lua +++ b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua @@ -55,4 +55,4 @@ local function toggleHelpMenu() menu:SetVisible(!menu:IsVisible()) end -net.Receive("ph_openhelpmenu", toggleHelpMenu) +concommand.Add("ph_openhelpmenu", toggleHelpMenu) diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index bbc9c88..c741742 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -12,10 +12,16 @@ include("cl_endroundboard.lua") include("cl_taunt.lua") include("cl_utils.lua") -surface.CreateFont( "PHIcons", { - font = "marlett", - size = math.Clamp( ScreenScaleH(12), 12, 16 ), weight = 700, antialias = true, symbol = true, -}) +local function createIcons(s) + surface.CreateFont( "PHIcons-" .. s, { + font = "marlett", + size = math.Clamp( ScreenScaleH(s), s, s * 4 ), + weight = 700, + antialias = true, + italic = false, + symbol = true, + }) +end local function createRoboto(s) surface.CreateFont("RobotoHUD-" .. s , { @@ -38,6 +44,12 @@ end for i = 5, 50, 5 do createRoboto(i) end +-- todo: this better +createIcons(12) +createIcons(16) +createIcons(24) +createIcons(32) +createIcons(64) createRoboto(8) createRoboto(12) createRoboto(14) diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index f3bd6f9..33549aa 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -95,15 +95,27 @@ function PlayerList(parent, pteam, dock) if id == 1 then -- override the content of column1 with avatar, group, mute and death icons local Avatar = vgui.Create( "AvatarImage", cln ) Avatar:SetPos( 0, 0 ) + Avatar:SetSize(ScreenScaleH(16), ScreenScaleH(16)) Avatar:SetPlayer( ply, 64 ) Avatar:Dock(FILL) - ply.Avatar = Avatar + ply.PHAvatar = Avatar + + if not ply:Alive() then + local DeadMat = vgui.Create("DLabel", ply.PHAvatar) + DeadMat:Dock(FILL) + DeadMat:SetTextColor(PHRed) + DeadMat:SetFont("PHIcons-16") + DeadMat:SetText("r") + DeadMat:SetContentAlignment(5) + end if tobool(GroupTags) and GroupIcons[ply:GetUserGroup()] ~= nil then - local mat = vgui.Create("Material", ply.Avatar) - mat:SetPos(0, 0) - mat:SetFGColor(PHWhite) - mat:SetMaterial(GroupIcons[ply:GetUserGroup()]) + local GroupMat = vgui.Create("Material", ply.PHAvatar) + GroupMat:SetPos(0, 0) + GroupMat:SetSize(ply.PHAvatar:GetWide() / 3, ply.PHAvatar:GetTall() / 3) + GroupMat:SetFGColor(PHWhite) + GroupMat:SetMaterial(GroupIcons[ply:GetUserGroup()]) + GroupMat.AutoSize = false end end @@ -141,6 +153,7 @@ function TeamHeader(parent, pteam, dock, text) draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker, true, true, false, false) draw.SimpleText(team.GetName(pteam), "RobotoHUD-18", ScreenScaleH(2), h / 2, team.GetColor(pteam), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end + JoinTeam(TeamHeader, pteam, RIGHT, text) end @@ -197,6 +210,7 @@ function GM:ScoreboardShow() if IsValid(PHScoreboard) then scoreboard:hide() end + scoreboard:show() end @@ -207,11 +221,19 @@ function GM:ScoreboardHide() end -- hack to update player list w/o constantly running through loops. todo: add a proper refresh function -net.Receive("TeamChanged", function(ply) +function scoreboard:refresh() timer.Simple(0.1, function() if IsValid(PHScoreboard) then RunConsoleCommand("-showscores") timer.Simple(0, function() RunConsoleCommand("+showscores") end) end end) +end + +net.Receive("TeamChanged", function(ply) + scoreboard:refresh() +end) + +net.Receive("PlayerDeath", function(ply) + scoreboard:refresh() end) diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index 255a08f..ab64032 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -257,12 +257,3 @@ local function openTauntMenu() end concommand.Add("ph_menu_taunt", openTauntMenu) -net.Receive("open_taunt_menu", openTauntMenu) - -net.Receive("ph_set_taunt_menu_phrase", function() - value_new = net.ReadString() - - -- Prevent this from being done more than once. GMod is weird. - if TauntMenuPhrase == value_new then return end - TauntMenuPhrase = value_new -end) diff --git a/gamemodes/ultimateph/gamemode/init.lua b/gamemodes/ultimateph/gamemode/init.lua index 77d4e69..4fec38e 100644 --- a/gamemodes/ultimateph/gamemode/init.lua +++ b/gamemodes/ultimateph/gamemode/init.lua @@ -11,12 +11,27 @@ for k, v in pairs(files) do end util.AddNetworkString("clientIPE") -util.AddNetworkString("ph_openhelpmenu") util.AddNetworkString("player_model_sex") +util.AddNetworkString("hull_set") +util.AddNetworkString("ph_chatmsg") +util.AddNetworkString("ph_kill_feed_add") +util.AddNetworkString("gamestate") +util.AddNetworkString("round_victor") +util.AddNetworkString("gamerules") +util.AddNetworkString("ph_mapvote") +util.AddNetworkString("ph_mapvotevotes") +util.AddNetworkString("spectating_status") +util.AddNetworkString("TeamChanged") +util.AddNetworkString("PlayerDeath") +-- banned models +util.AddNetworkString("ph_bannedmodels_getall") +util.AddNetworkString("ph_bannedmodels_add") +util.AddNetworkString("ph_bannedmodels_remove") include("sv_player.lua") include("sv_teams.lua") include("sv_utils.lua") +include("sv_awards.lua") include("sh_init.lua") include("sh_rounds.lua") include("sh_disguise.lua") @@ -109,11 +124,10 @@ function GM:CleanupMap() end function GM:ShowHelp(ply) - net.Start("ph_openhelpmenu") - net.Send(ply) + ply:ConCommand("ph_openhelpmenu") end function GM:ShowSpare1(ply) - net.Start("open_taunt_menu") - net.Send(ply) + ply:ConCommand("ph_menu_taunt") end + diff --git a/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua b/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua index b94cd94..de23b0b 100644 --- a/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua +++ b/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua @@ -2,10 +2,6 @@ if SERVER then -- former sv_bannedmodels.lua GM.BannedModels = {} -- This is used as a hash table where the key is the model string and the value is true. - util.AddNetworkString("ph_bannedmodels_getall") - util.AddNetworkString("ph_bannedmodels_add") - util.AddNetworkString("ph_bannedmodels_remove") - function GM:IsModelBanned(model) return self.BannedModels[model] == true end diff --git a/gamemodes/ultimateph/gamemode/sh_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua index 73d305b..7d11e3f 100644 --- a/gamemodes/ultimateph/gamemode/sh_config.lua +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -38,7 +38,7 @@ GroupNames = {} GroupNames["superadmin"] = "Super Admin" GroupIcons = {} ---GroupIcons["superadmin"] = "icon16/award_star_gold_1.png" +GroupIcons["superadmin"] = "icon16/award_star_gold_1.png" -- it is ideal to delete undesired options rather than setting to false. see https://wiki.facepunch.com/gmod/Enums/ACT ActWhitelist = {} diff --git a/gamemodes/ultimateph/gamemode/sh_mapvote.lua b/gamemodes/ultimateph/gamemode/sh_mapvote.lua index 578854b..3a5a8fc 100644 --- a/gamemodes/ultimateph/gamemode/sh_mapvote.lua +++ b/gamemodes/ultimateph/gamemode/sh_mapvote.lua @@ -1,9 +1,5 @@ if SERVER then --- former sv_mapvote.lua - - util.AddNetworkString("ph_mapvote") - util.AddNetworkString("ph_mapvotevotes") - +-- former sv_mapvote.lua GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() diff --git a/gamemodes/ultimateph/gamemode/sh_rounds.lua b/gamemodes/ultimateph/gamemode/sh_rounds.lua index 67c737d..c61b56e 100644 --- a/gamemodes/ultimateph/gamemode/sh_rounds.lua +++ b/gamemodes/ultimateph/gamemode/sh_rounds.lua @@ -1,11 +1,4 @@ -if SERVER then --- former sv_rounds.lua - include("sv_awards.lua") - - util.AddNetworkString("gamestate") - util.AddNetworkString("round_victor") - util.AddNetworkString("gamerules") - +if SERVER then GM.GameState = GAMEMODE && GAMEMODE.GameState || ROUND_WAIT GM.StateStart = GAMEMODE && GAMEMODE.StateStart || CurTime() GM.Rounds = GAMEMODE && GAMEMODE.Rounds || 0 diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index 26992d9..de443e5 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -165,9 +165,7 @@ end GM:LoadTaunts() if SERVER then --- former sv_taunt.lua - util.AddNetworkString("open_taunt_menu") - +-- former sv_taunt.lua local PlayerMeta = FindMetaTable("Player") function PlayerMeta:CanTaunt() @@ -252,22 +250,6 @@ if SERVER then DoRandomTaunt(ply) end) - util.AddNetworkString("ph_set_taunt_menu_phrase") - function GM:SetTauntMenuPhrase(phrase, ply) - net.Start("ph_set_taunt_menu_phrase") - net.WriteString(phrase) - - if ply then - net.Send(ply) - else - net.Broadcast() - end - end - - cvars.AddChangeCallback("ph_taunt_menu_phrase", function(convar_name, value_old, value_new) - (GM || GAMEMODE):SetTauntMenuPhrase(value_new) - end) - function GM:AutoTauntCheck() if self.GameState != ROUND_SEEK then return end diff --git a/gamemodes/ultimateph/gamemode/sv_mapvote.lua b/gamemodes/ultimateph/gamemode/sv_mapvote.lua index 49b638b..e6ec53d 100644 --- a/gamemodes/ultimateph/gamemode/sv_mapvote.lua +++ b/gamemodes/ultimateph/gamemode/sv_mapvote.lua @@ -1,8 +1,4 @@ -- mapvote - -util.AddNetworkString("ph_mapvote") -util.AddNetworkString("ph_mapvotevotes") - GM.MapVoteTime = GAMEMODE && GAMEMODE.MapVoteTime || 30 GM.MapVoteStart = GAMEMODE && GAMEMODE.MapVoteStart || CurTime() diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index f92b4a7..41b2cd0 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -31,8 +31,6 @@ function GM:PlayerDisconnected(ply) ply:SetTeam(TEAM_HUNTER) end -util.AddNetworkString("hull_set") - function GM:PlayerSpawn(ply) ply:UnCSpectate() @@ -584,6 +582,11 @@ function GM:PlayerDeath(ply, inflictor, attacker) -- time until player can spectate another player ply.SpectateTime = CurTime() + 2 + + -- inform the client (currently only for scob updating) + net.Start("PlayerDeath") + net.WriteEntity(ply) + net.Broadcast() end function GM:KeyPress(ply, key) diff --git a/gamemodes/ultimateph/gamemode/sv_teams.lua b/gamemodes/ultimateph/gamemode/sv_teams.lua index 6fd6722..29f63f3 100644 --- a/gamemodes/ultimateph/gamemode/sv_teams.lua +++ b/gamemodes/ultimateph/gamemode/sv_teams.lua @@ -1,5 +1,3 @@ -util.AddNetworkString("TeamChanged") - concommand.Add("ph_jointeam", function(ply, com, args) local newTeam = tonumber(args[1]) || TEAM_SPEC -- Default to spectators if there's a problem if ply:Team() == newTeam then return end @@ -11,6 +9,10 @@ concommand.Add("ph_jointeam", function(ply, com, args) if ply:Alive() then ply:Kill() end ply:SetTeam(newTeam) GlobalChatMsg(ply:Nick(), " changed team to ", team.GetColor(newTeam), team.GetName(newTeam)) + -- inform the client, currently only for scob updates + net.Start("TeamChanged") + net.WriteEntity(ply) + net.Broadcast() else ply:PlayerChatMsg("Team full, you cannot join") end @@ -89,8 +91,6 @@ function GM:SwapTeams() end -- former sv_spectate.lua -util.AddNetworkString("spectating_status") - local PlayerMeta = FindMetaTable("Player") function PlayerMeta:CSpectate(mode, spectatee) @@ -222,9 +222,3 @@ function GM:ChooseSpectatee(ply) self:SpectateNext(ply) end end - -hook.Add("PlayerChangedTeam", "TeamChanged", function(ply) - net.Start("TeamChanged") - net.WriteEntity(ply) - net.Broadcast() -end) diff --git a/gamemodes/ultimateph/gamemode/sv_utils.lua b/gamemodes/ultimateph/gamemode/sv_utils.lua index 43442ce..fe800a4 100644 --- a/gamemodes/ultimateph/gamemode/sv_utils.lua +++ b/gamemodes/ultimateph/gamemode/sv_utils.lua @@ -1,5 +1,4 @@ -- former sv_chatmsg.lua -util.AddNetworkString("ph_chatmsg") local PlayerMeta = FindMetaTable("Player") -- Sends a message to an individual player. @@ -252,8 +251,6 @@ function EntityMeta:GetRagdollOwner() end -- former sv_killfeed.lua -util.AddNetworkString("ph_kill_feed_add") - local DMG_CLUB_GENERIC = bit.bor(DMG_CLUB, DMG_GENERIC) local DMG_SLOWBURN_BURN = bit.bor(DMG_SLOWBURN, DMG_BURN) local DMG_BLAST_SURFACE_BLAST = bit.bor(DMG_BLAST_SURFACE, DMG_BLAST) From 75341ec771f1bf227f20e0698425df7ad749e831 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Thu, 2 Oct 2025 00:17:48 +1000 Subject: [PATCH 13/25] more scob work + remove now-unused skull image + more icon fonts + slight end round board tweak --- .../ultimateph/gamemode/cl_endroundboard.lua | 4 +- gamemodes/ultimateph/gamemode/cl_init.lua | 1 + .../ultimateph/gamemode/cl_scoreboard.lua | 56 ++++++++++++++---- gamemodes/ultimateph/gamemode/cl_taunt.lua | 2 +- gamemodes/ultimateph/gamemode/init.lua | 16 +++-- gamemodes/ultimateph/gamemode/sv_player.lua | 5 ++ lua/ulx/modules/sh/ultimateph.lua | 15 +++++ materials/husklesph/skull.png | Bin 5152 -> 0 bytes 8 files changed, 80 insertions(+), 19 deletions(-) delete mode 100644 materials/husklesph/skull.png diff --git a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua index 2920ff0..47b4460 100644 --- a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua @@ -14,7 +14,7 @@ local function createEndRoundMenu() menu:DockPadding(math.Clamp(ScreenScaleH(2), 2, 4), ScreenScaleH(12) + math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4), math.Clamp(ScreenScaleH(2), 2, 4)) local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('PHIcons') + closeButton:SetFont('PHIcons-8') closeButton:SetText('r') closeButton.Paint = function(s,w,h) if not closeButton:IsHovered() then @@ -133,7 +133,7 @@ local function createEndRoundMenu() function resultsTimeLeft:Paint(w, h) -- "Extend" the dark rectangle from awards:Paint to make a larger seamless rectangle - surface.SetDrawColor(PHEndDark) + surface.SetDrawColor(PHEndDarker) surface.DrawRect(0, 0, w, h) if GAMEMODE:GetGameState() == ROUND_POST then diff --git a/gamemodes/ultimateph/gamemode/cl_init.lua b/gamemodes/ultimateph/gamemode/cl_init.lua index c741742..57d7134 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -45,6 +45,7 @@ for i = 5, 50, 5 do createRoboto(i) end -- todo: this better +createIcons(8) createIcons(12) createIcons(16) createIcons(24) diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index 33549aa..b2a8bf6 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -62,11 +62,11 @@ function PlayerList(parent, pteam, dock) TeamHeader(PlayerListBG, pteam, TOP, "Join Team") -- call the header early so it docks properly local PlayerList = vgui.Create("DListView", PlayerListBG) - PlayerList:Dock(FILL) + surface.SetFont("RobotoHUD-L18") PlayerList:SetSize(PlayerListBG:GetWide(), PlayerListBG:GetTall()) PlayerList:SetDataHeight(ScreenScaleH(16)) PlayerList:SetHeaderHeight(draw.GetFontHeight("RobotoHUD-L18")) - surface.SetFont("RobotoHUD-L18") + PlayerList:Dock(FILL) PlayerList:AddColumn("", 1):SetFixedWidth(ScreenScaleH(16)) PlayerList:AddColumn("", 2):SetFixedWidth(PlayerList:GetWide() - (ScreenScaleH(16) + surface.GetTextSize("KILLS") + surface.GetTextSize("DEATHS") + surface.GetTextSize("PING"))) PlayerList:AddColumn("KILLS", 3):SetFixedWidth(surface.GetTextSize("KILLS")) @@ -81,6 +81,29 @@ function PlayerList(parent, pteam, dock) for _, ply in ipairs( team.GetPlayers(pteam) ) do local line = PlayerList:AddLine(nil, ply:Nick(), ply:Frags(), ply:Deaths(), ply:Ping()) -- leave column 1 empty to override with player's avatar later + function PlayerList:OnRowSelected() + local PlayerActions = DermaMenu() + + if ply ~= LocalPlayer() then + PlayerActions:AddOption("Mute", function() + if not ply:IsValid() then + return + end + + ply:SetMuted(!ply:IsMuted()) + scoreboard:refresh() + end) + end + + if LocalPlayer():IsAdmin() then + PlayerActions:AddOption("Team Switch", function() + LocalPlayer():ConCommand("ulx teamswitch ".. ply:Nick()) + end) + end + + PlayerActions:Open() + end + function line:Paint( w, h ) if tobool(GroupTags) and GroupColors[ply:GetUserGroup()] ~= nil then surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) @@ -94,28 +117,27 @@ function PlayerList(parent, pteam, dock) if id == 1 then -- override the content of column1 with avatar, group, mute and death icons local Avatar = vgui.Create( "AvatarImage", cln ) - Avatar:SetPos( 0, 0 ) - Avatar:SetSize(ScreenScaleH(16), ScreenScaleH(16)) Avatar:SetPlayer( ply, 64 ) + Avatar:SetSize(ScreenScaleH(16), ScreenScaleH(16)) + Avatar:SetPos( 0, 0 ) Avatar:Dock(FILL) ply.PHAvatar = Avatar if not ply:Alive() then local DeadMat = vgui.Create("DLabel", ply.PHAvatar) DeadMat:Dock(FILL) - DeadMat:SetTextColor(PHRed) + DeadMat:SetTextColor(Color(PHRed.r, PHRed.g, PHRed.b, 220)) DeadMat:SetFont("PHIcons-16") DeadMat:SetText("r") DeadMat:SetContentAlignment(5) end if tobool(GroupTags) and GroupIcons[ply:GetUserGroup()] ~= nil then - local GroupMat = vgui.Create("Material", ply.PHAvatar) - GroupMat:SetPos(0, 0) - GroupMat:SetSize(ply.PHAvatar:GetWide() / 3, ply.PHAvatar:GetTall() / 3) - GroupMat:SetFGColor(PHWhite) - GroupMat:SetMaterial(GroupIcons[ply:GetUserGroup()]) - GroupMat.AutoSize = false + PlayerIcons(ply.PHAvatar, ply.PHAvatar:GetWide() / 3, ply.PHAvatar:GetTall() / 3, 0, 0, GroupIcons[ply:GetUserGroup()]) + end + + if ply:IsMuted() then + PlayerIcons(ply.PHAvatar, ply.PHAvatar:GetWide() / 3, ply.PHAvatar:GetTall() / 3, ply.PHAvatar:GetWide() - ply.PHAvatar:GetWide() / 3, ply.PHAvatar:GetTall() - ply.PHAvatar:GetTall() / 3, "icon16/sound_mute.png") end end @@ -143,6 +165,14 @@ function PlayerList(parent, pteam, dock) end end +function PlayerIcons(parent, sizex, sizez, posx, posz, material) + local PlayerIcons = vgui.Create("Material", parent) + PlayerIcons:SetSize(sizex, sizez) + PlayerIcons:SetPos(posx, posz) + PlayerIcons:SetMaterial(material) + PlayerIcons.AutoSize = false +end + function TeamHeader(parent, pteam, dock, text) local TeamHeader = vgui.Create("DPanel", parent) TeamHeader:SetSize(parent:GetWide(), parent:GetTall() / 16) @@ -237,3 +267,7 @@ end) net.Receive("PlayerDeath", function(ply) scoreboard:refresh() end) + +net.Receive("PlayerSpawn", function(ply) + scoreboard:refresh() +end) diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index ab64032..4f07b12 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -138,7 +138,7 @@ local function openTauntMenu() menu:DockPadding(ScreenScaleH(4), ScreenScaleH(4) + draw.GetFontHeight("RobotoHUD-25"), ScreenScaleH(4), ScreenScaleH(4)) local closeButton = vgui.Create('DButton', menu) - closeButton:SetFont('PHIcons') + closeButton:SetFont('PHIcons-8') closeButton:SetText('r') closeButton.Paint = function(s,w,h) if not closeButton:IsHovered() then diff --git a/gamemodes/ultimateph/gamemode/init.lua b/gamemodes/ultimateph/gamemode/init.lua index 4fec38e..9690edf 100644 --- a/gamemodes/ultimateph/gamemode/init.lua +++ b/gamemodes/ultimateph/gamemode/init.lua @@ -10,19 +10,25 @@ for k, v in pairs(files) do end end +-- what? util.AddNetworkString("clientIPE") -util.AddNetworkString("player_model_sex") -util.AddNetworkString("hull_set") +-- ui util.AddNetworkString("ph_chatmsg") util.AddNetworkString("ph_kill_feed_add") +util.AddNetworkString("TeamChanged") +util.AddNetworkString("PlayerDeath") +util.AddNetworkString("PlayerSpawn") +-- player meta +util.AddNetworkString("player_model_sex") +util.AddNetworkString("hull_set") +-- rounds util.AddNetworkString("gamestate") util.AddNetworkString("round_victor") util.AddNetworkString("gamerules") +util.AddNetworkString("spectating_status") +-- mapvote util.AddNetworkString("ph_mapvote") util.AddNetworkString("ph_mapvotevotes") -util.AddNetworkString("spectating_status") -util.AddNetworkString("TeamChanged") -util.AddNetworkString("PlayerDeath") -- banned models util.AddNetworkString("ph_bannedmodels_getall") util.AddNetworkString("ph_bannedmodels_add") diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index 41b2cd0..f49f0c3 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -54,6 +54,11 @@ function GM:PlayerSpawn(ply) ply:SetPlayerColor(vec) ply.LastSpawnTime = CurTime() + + -- inform the client (currently only for scob updating) + net.Start("PlayerSpawn") + net.WriteEntity(ply) + net.Broadcast() end function GM:PlayerSetupHands(ply) diff --git a/lua/ulx/modules/sh/ultimateph.lua b/lua/ulx/modules/sh/ultimateph.lua index 085f325..52b88e6 100644 --- a/lua/ulx/modules/sh/ultimateph.lua +++ b/lua/ulx/modules/sh/ultimateph.lua @@ -206,3 +206,18 @@ commandToUlx("ph_endround", function(c) c:help("Ends the round on a tie.") end) +-- force team switch for admins. modified from ULX CustomCommands +function ulx.ph_teamswitch(calling_ply, target_plys) + for _, v in pairs(target_plys) do + if v:Team() == TEAM_HUNTER then + v:ConCommand("ph_jointeam 3") + else + v:ConCommand("ph_jointeam 2") + end + end + ulx.fancyLogAdmin(calling_ply, "#A forced #T to switch teams", target_plys) +end +local ph_teamswitch = ulx.command("UltimatePH", "ulx ph_teamswitch", ulx.ph_teamswitch, {"!ph_teamswitch", "!ph_teamswitch"}) +ph_teamswitch:addParam{type = ULib.cmds.PlayersArg} +ph_teamswitch:defaultAccess(ULib.ACCESS_ADMIN) +ph_teamswitch:help("Force a player to switch teams.") diff --git a/materials/husklesph/skull.png b/materials/husklesph/skull.png deleted file mode 100644 index 796e28366fabc3074d7b95b71edd374f09be04a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5152 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 O0000 Date: Thu, 2 Oct 2025 01:05:41 +1000 Subject: [PATCH 14/25] more scob work + ulx command support for right click menu --- .../ultimateph/gamemode/cl_scoreboard.lua | 52 ++++++++++--------- gamemodes/ultimateph/gamemode/sh_config.lua | 6 ++- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index b2a8bf6..9ae4446 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -72,6 +72,32 @@ function PlayerList(parent, pteam, dock) PlayerList:AddColumn("KILLS", 3):SetFixedWidth(surface.GetTextSize("KILLS")) PlayerList:AddColumn("DEATHS", 4):SetFixedWidth(surface.GetTextSize("DEATHS")) PlayerList:AddColumn("PING", 5):SetFixedWidth(surface.GetTextSize("PING")) + + PlayerList.OnRowSelected = function(panel, rowIndex, row) + local PlayerActions = DermaMenu() + local ply = row:GetValue(1) + + if ply ~= LocalPlayer() then + PlayerActions:AddOption("Mute", function() + if not ply:IsValid() then + return + end + + ply:SetMuted(!ply:IsMuted()) + scoreboard:refresh() + end) + end + + if LocalPlayer():IsAdmin() then + for cmd, cmdname in pairs(ULXCommands) do + PlayerActions:AddOption(cmdname, function() + LocalPlayer():ConCommand(cmd.." "..ply:Nick()) + end) + end + end + + PlayerActions:Open() + end function PlayerList:Paint(w, h) surface.SetDrawColor(PHScobDarkest) @@ -79,30 +105,7 @@ function PlayerList(parent, pteam, dock) end for _, ply in ipairs( team.GetPlayers(pteam) ) do - local line = PlayerList:AddLine(nil, ply:Nick(), ply:Frags(), ply:Deaths(), ply:Ping()) -- leave column 1 empty to override with player's avatar later - - function PlayerList:OnRowSelected() - local PlayerActions = DermaMenu() - - if ply ~= LocalPlayer() then - PlayerActions:AddOption("Mute", function() - if not ply:IsValid() then - return - end - - ply:SetMuted(!ply:IsMuted()) - scoreboard:refresh() - end) - end - - if LocalPlayer():IsAdmin() then - PlayerActions:AddOption("Team Switch", function() - LocalPlayer():ConCommand("ulx teamswitch ".. ply:Nick()) - end) - end - - PlayerActions:Open() - end + local line = PlayerList:AddLine(ply, ply:Nick(), ply:Frags(), ply:Deaths(), ply:Ping()) -- an avatar is drawn over column 1, so use it to store playerdata. this makes the drop down menu much simpler function line:Paint( w, h ) if tobool(GroupTags) and GroupColors[ply:GetUserGroup()] ~= nil then @@ -247,6 +250,7 @@ end function GM:ScoreboardHide() if IsValid(PHScoreboard) then scoreboard:hide() + DermaMenu() end end diff --git a/gamemodes/ultimateph/gamemode/sh_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua index 7d11e3f..68c9049 100644 --- a/gamemodes/ultimateph/gamemode/sh_config.lua +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -26,7 +26,7 @@ PHBlue = Color(50, 150, 255) -- props -- misc. CornerRadius = "4" -- set to 0 to disable rounded corners. number is in pixels relative to 480p. ScobBackground = true -- set to false to disable scoreboard background & credits -GroupTags = true -- set to true to enable group tags. requires ULX +GroupTags = false -- set to true to enable group tags. requires ULX ActEnableAll = false -- set to true to enable all default gmod animations NameDistance = 500 -- adjusts how close you need to be to see a player's name @@ -40,6 +40,10 @@ GroupNames["superadmin"] = "Super Admin" GroupIcons = {} GroupIcons["superadmin"] = "icon16/award_star_gold_1.png" +-- ulx admin commands to add to the scoreboard click menu. the first bit is the console command, the second is the name to show +ULXCommands = {} +ULXCommands["ulx ph_teamswitch"] = "Team Switch" + -- it is ideal to delete undesired options rather than setting to false. see https://wiki.facepunch.com/gmod/Enums/ACT ActWhitelist = {} ActWhitelist[ACT_GMOD_GESTURE_BOW] = true From 35088079a586e7ae54420f3e3e148a3371a75ba6 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Thu, 2 Oct 2025 01:20:47 +1000 Subject: [PATCH 15/25] remove custom voice hud for now --- gamemodes/ultimateph/gamemode.zip | Bin 0 -> 53368 bytes gamemodes/ultimateph/gamemode/cl_hud.lua | 116 ----------------------- 2 files changed, 116 deletions(-) create mode 100644 gamemodes/ultimateph/gamemode.zip diff --git a/gamemodes/ultimateph/gamemode.zip b/gamemodes/ultimateph/gamemode.zip new file mode 100644 index 0000000000000000000000000000000000000000..b3c55f2eb343af062d69ecff36b040aaafc95ff8 GIT binary patch literal 53368 zcmaI71FR_PvMoGq+qSW$ZQHhO+qP}nwr$&Ide`yXyl|J{v|wVsQ!g|!o%wTr?3=F+El-8_eN+x+jd zM#aW%lL6sd+BYPP9$IN!y+joiDT(U+J%F)A<)wjX0&q1t1}RLj<>Ri)Nf#1OxdMqO z;_kM?EBDVv51!Kfpdf8p#jjZ!l>$V~g+cZdJYMH(=4O|i*e?h}R%0n!v;pyllgXyK z7fzJ~we>y?zq}_OS4U4xu-Q0}*%wgRI1pJkAOVrsrK^UF*ffMDdEPK++tdl}cuF^v z^nE-*1grb)mBRWCUg~o%B$U!L&f~<4ieaEe0z96F3XnLLO;=G6%$p^J?n-N(s?~-VSIwJ<0-*q98=PW~R%vn+FCQ?){1F-3PXtu)k$}%uy zF{o1fv*YhqbI8G9GPjivPF>euIVYjoBq&a(S2NU%zjw1^52gAox3RjfxNr-C2Tm06 zCy$hryjxG1LO`U>368*n!A#NmY2tG?URdHl7YylNk0u$48We*FgM%8$+F$Tk$j18v z*K76Dmj`1S98~K+7ykM6l zv`WQSHL@6piUdR9knp(uC$!p0bLI9~v&Rqnr1MkkB3#>x<>*E@&=Tu%b3lBab4uC! z4S4zL{JzffRPuBr^?zU7={fDA^_OTI2c<4p;x|vXvY4lESvYc?W@$yOa+<9vIvsVS zq)CN5-;^ITolr=sil<<&^F%pD$^SV6$+K8wPdqgZAl=rEt{Y97s@f+6$GU>BBwx;9 z+VzOSA6$fR*owhLh$1K!qP3g5cN%{e`Ppia$rg-4yj(FU!;r8@nV6kae9!v5Plu_J z4?AIX!DMh1upX#N`8a}1T=R0CAv(L0xDN{5Vj z;=^(H%NQI7YtR~gAhk?QH^$wXz?=o9W2XnCyXX%F3BQ-+ne}LlV(u*29_-9-OC}0i zTBPE{T2YgSLW0^y_G@P2j~iH)*1O%#BL~LI-8=$O1>f2cKY1x|2(G$SB!<60Isgd( z@c$rb-ze}8NMij5B+X5%?VXGqO-yY68!nY`cFg}}Hp>4Om)a!XLo9k&WLOJnGFB{P zEr6=36e<%zE2dAM8+5t`bO>rD!II8ObN;$-b*$Phkt$War zJ^d%L9CU9S#K3-Onu1cPn`#Sm&Ze$f^)L_0sTa`Dv{PNOybx0rUG43-FZx}l8j~p} zMu6^5tIa{owEzkjBbW-7^qcoUQU)wkVq})N5#T=?8-}L+?DV_=h zo?Rr0&5v`eOpj+IC|R(mLH#(fs|yG0mLibsS}^NAr;@dx*wIlmj~pG{xYJx-BrUvZ znN4*zm^LqD#|gilGA((s`f%!Ai#}R(eRtO5v6SeahF&Jf@lq9g3r{c1J zbeF`7z15@+@^Ni%`g&8h52R36u4Mmuc6#<>iqn_eFdDtWndO&Y`N8*t=kuf&g!_>+ImsV46n`%*kwGrEuxb z8sXrF5`*|N@AWDn*&!ss;iRnDv`PMc^YmJFagQZEfVtGHIs#QzMN-u< zlQ#-u`nZWDA&~#(gJJJwydKmYjkn|Tmh?~cruBkp&pf=5da)nfBC38dEW%jY{@oe3 zF9M?h=x*z`N`e$VL+@v6yyWu8?Dihh?FX3C8seBi;a8}-^LQCu@H-g~;No4%qhVd2 z?Olv_p>gq_`m?EvARDsf2skZAA2n+7KP2q?({e^KIP;*zOH&RHi0_A@P=tt6#BDtKw>swT@Am*NmwiJk_j1^(Vl=uhJrA;jevs#Dj+azp z^E5E1+|z6#fb0(iY6k>u>9+U3C+vaSEZ99PapeJ7C~G% z+><;4J4aARlT?g8@TU#VreD_lp=#7FtdvG>>MMh&(G{0&_ggl_$7(7>^Yv${ z_n^tWc?IWsJAmp&5=`x*)Bmh`_|ml4l|KgcUx8Z(U{;D@?F-!BWF@^(r6bmp@32w0 z(I+m?@Z&$e+YNpxfTHiqzQc8Pi#c%iaIoa=4iaKbK>1!xU>gWR?o>AgCz2?4Z`~;V zshilj*uOk{x1Imtrlfs*UrYLy(KX|L#7-#gl_Vg%@&-EqAOzt1ksz#bg3=fNy-$!% zr^e(z#(G3;{*uT#;!{m2eXIyG3X5Ykn8gOrgOIOqSAUYmeQabnM&4Q8d@i3_h z)If!^R49KU8Qj^>-#aP1E2URy)^$D^yy|n!JP95&msNu#UrX%_e)`8NKI|&7Y%rWO zA$@X#`7~;U$heAO{8xU9N|KL13cu@zHM^*g)dhKxLqnhs{){7{kz0=p}Er$39``hDt!8!dE zOe5nmRGZ70FFrKsq7@NvfCNIM{0a5B=BrZ(A}0gHu_82kM>x&z2ASRuazjz?+XUK@kW(9qS~Bos=e#q@ z>;kAO!~!iLn~!a5AQ92cwN$@dIP z+9%15pNV6eo*@~mbQZ~!=eUcYQH?VNqgTS^Da?jPX|vG9(;_;lxAr6ts?}Rgw zSC?95g!F9p8XemjLK<*{4BI`(oE~8FsxmR}wav2fR=pKg6w>&6>3bDutALVD;k%}i z3=y`7{w|O(S(-OpA}f#44MWoCA_W7b?RfL3?rpQR>-31DS^1MyJuL3}5`1f`q*W}7rzofow3|G%bBFq^G1IxDrdP??drRt+;q^#v)$tyFv)|ep>)jaZ zc?*_Y{YQoM4Q zM4zG;9;}w?&BUPbkYJ(`#Tueev95$A8!bTSvxwIBX|?rEz_zxIwyfk1iid`6LDWIS zSK_QT1^av?0Md?D91)`O?op2`pg|v#R-S?seIMt>%CwkuP-Yp_aV4?_^zCLx4!_z+ z=11WArvB^?k)1^16VR4rgJUBLoPmk=d)$-`{Bjc^+RE`ZfVdvJiV^SfNX(;)#+ zJ5#7c)p<0)s_&eUe_*`rxz;m__)vpo0^MB}{ND8=Y%Kj|1r;zBxksBWaF$~OBz9?E z<7V|X3LYy==YGy~(&Mhhmsi+PU*9c(hX2aU<&Iw5o?Ca$?Cfq_o8KjF_b1Ex;~Kwj z-$%d)jhIXC-90|tm+#e}D&uls002RV z;OfQBBM#>>&j2&r!`8nVV{|9XY=kG?^q%%9K>hRgRsS3v0w()82XQjDZ3lc6d- zdVL-KXF6%~BmUmKH+em7cfGCv2E8qQeD9uCw%@?HYKNx$ZKyHV>@L3dyMC;8-K_vO zhSt9*Tf$TIZ+nY>e+oW@phs!mk*-qVPj%=Qr5Re?d|YzOJTj83&eu>qmDhfSme>v{ zPTWPZQZfoCW`@YTNzUdTTyU`u#0@H9F7E02(>-jY-h}R`eIa~JJ!oezhNUn+6K<<1<0l9 zTfLSu7BXMtBEkrkN8IzRe^-%MR!*QQPtKoOk-x7(c;4$`kQC5+9ymfiyJ#*P&Q1%^ zKA>EA0k_oBbY*~a-e#_L6sS%21}DFv>Bt!$viMQgw)@uK!83DdPp)iiX9 zo_Y22KKT-}F!>SrS+IC5vi)R7!DrXqG*G#&^QsPj6tPM?nf{n)zhI7>k2D4Inp@nb zfbH0EmU9yy(f8v9ciu>VVuM}-in|Fw`_8xleLugmYw_6v(=5&leiQ+)Anr^1?Py>F z_6g`mfOVSqEqbv31VgKNo_uoyde8X__-8eWA0~&iS?jdl`2hJ*_`?q<2oJCqY)!AC z^0;0jUz7-KlPPmIcANi6^42Z+0(&MP=h%b}JtOCRgTY_W>gSg>;TK1U_LGJuZn$&~ zKSw5h;rHK${)WKsrh#ye8R}#3jT}N!EPk+*#UmglrG(HK#1Gdu=m@s+or7@?c?a%q`5QAn1uX3$jQHCr>BPA#+#v|UHc3~Yiozb z+tJgTSzArOHmQIT2D7A2&Pi5KWs>Y$?$AtBDf!ljB|uWHZa zqa)Mr{;SLoxs3C9uWj*hT~;Z^l)N$lBn>B*t8G6sP_9oVRx}S6av%|0L%u)E7D59- zn{u5picJ9=Sb=02$&ko^`dPbO&FoLq+srZ1D^v;^bPq7rraSJ!fOg%(!-S#dl;Gob zUR77q#?#6gmvik?K3rt2Uo=FycQR_UtyZmN|7@METRNmoM~#zxzenTnCWZ>UVzlf4 zgWoti$=x`kGUSxEIwuzU1qT-z&7>D`A!Src^xc(V6Ohma09(ZhnYIBQN1vnl62#^B zcTer5Qt@D(LPB>u4wXEiO)hddF=kNMQ0)^}>sVOdm^Jt*n+O0ReDIU>h)a;X)+&>v zjb2u(#T;4;ByD-D(iCQqoQ}E)JFE};ycuT|3=mT#U7Z>8$8Qacz_v-7OGDonZewRm zz9s||%0ji^#)P=7aWG?K53ypQm}iLln~u;2|T8L6P0ciLmBgO|mm#gF5ota1yLFtUbyvYwMJQs)Pk0ejXOB+N>{ zWc)mc-Y!|8XPQ0(w5$%NQtWqhCIdsMv-y^7FtQ+!&p`9@^*swkCb!`22DzgRzutIE zASz8;<{ph#Ti0`p zd$tDi6Fd}3i8s7AERJGd0W-eSN-{`{f&GNC2>G6RM&mYfRWJ$_Ox54;Th(?tUseHb_;SyLnMm&- zPnz-UX+dBxLvr(Rt4u}{<72tXr!XH#(_CsLbvx4WE1dp;h zq2sN=RpxZy1O?uzU7#xqa0#I#i$gQg-k?qPtw6Zk9O_efiz07)^56`sZP}1->rdYi z8yTY#Nn$;-J|W>ytQ=>AvnJ6)o!wW>g?8nMB%?SIic1OQD*2N-x*c`^3}qkK_CB#C zbx?zRh3)!{@A(pj6fSz>;+A2$Lgz?HLqxG5fuG!kcxx15zSjZj2OY zi;0AzKoNknY44TioJtD%a%jT}admCZK`AgPDx!*W3YIw&!e9qwNwXeadIViYR+B0+ z>ts^{ndzpi0!>F%l^8UoE!US*^*W|=iLVhFBB~cK8i=zNbL=8_D->4pYa>!c%_rq~ zTDH~MkVPH@kI{J6mGmzRIyikVogm8DG^K0jg+&YGWqDy&;kii-kzn#3NryITU+QV| zn{?nz(huHBfftw5h}uhvtvl6kpF%IvGx3+wDl0>d&Mgg|2R|ykJnmwALiJI`j*bjG zVtKnK>u$FUE@g3au$S-l+H2rg-ztMF1^yI-&51E)grJ zt+rHh0(U39yb=`8M}587)6PToL9KRryWg}f^6idHrxL!wwspwGYiMYPAN zdn3-|`SVuB+#twv`KEsMPCR2G(#dL|JB97gTRK8-cIQdmbt+_Rbr7m2V?cKPL`GSK zoBD&8nG7Vr%HFc0w=A>OF7kLm=z%%cSOb5)(-j8nbn%|kK@p%9FlvmCVhyD;!2087 zF^K&5TRe?2qEjNb{feI2-eU7$awnz5l#^U}1%p;p^EFtFo+x2dEVZ=tysNP!Z#HgZotR9C1xv6C^R(G+xKECHdfRA) z*oHGnmwMc_RTAsJ8fgW(Q5WrTVOFXDYH=+0Rfn&eJ-8{3bVHWOEVNT10ljGoPjF)) zgZ7d1X%kgaq12rJQ!V&`=BEV{Pz-Rzdton%{o*imcwWZGk0JH7CjD)tgPb9p6nRf_pQz6M*3 ztyhIE-3YJ6cZj5ILfHGdNZ=fxYWEuZ&6|t@E;m@0Jh^Y zhT^c`OBTKb>@H7@)sOc*^()lhsgSsYw;%e(wHx7)%Rx{t9FGt;lp80O6s;NRG@kQ* zj0+JBF3MM27Cp(R1GHMq-EmwP#z*0$*l3wriWI@S^?q5DkQ>6vENLG#KPlr(MO}|# z;h*S#Q&mkx6;B~30DwWlf2XPh4F7BtA^(ToZSL}4Rqn69y0-ta8EgwVY>DT;RBlbF zss{2>VCisE1*Sw`uxfq%1_{+jQPl&wt(M!N%i2+QHLgCNJuj6N9UlzA#FcCC{^j47 zJ8CIr8oyMSV%Oj9Uk`5|L#M=BxVpG1AWX-~AocS8$oC6dT(iy+`Oyd^^w)=f?(Aq_ zPMaf|1;j~hAzp{|IF8aW{Rs7!P4SmrLc{&cE(;1qZS9c zPXf0>v0n3Z4bQOw?jaa*Ia3kxh|7smGpcA$nD5>);JsPL`yK{7ZZ+>~Q*;T2yNcDB zX9CJU0}SHDubHvYwmt%2wV%g9g`)YB8DN0}h(h9dLXg277Q=SMjxhjX3~=I8yofSs zgBsG{q0Z<|Tr}=nG_Kls<5yv%H@-~yR!xa69W!U&XW}lNLBDo~;RlC{+Gn<#;}6oY zH%-BHUdzN*j*3_hvmYn{LL3nK%k4#LkC1C5jt=V)Ty?Wu!`I1upcb^*Cc3*Mh)9xz z>ZlPkG^ZqWw~fVPg<8fB{s2r)4&~lF5tFWnMinxhOBVd{AzYEk#>)$g{B2GJrB6J9 zoPAmE2IJ5`O(Ph-AV@r2%5CTX0we`GGZj3Xc)Cv1sZbrx7NoocnLZ3j4KljhS7%^j zdYxb_ze_5&VuC{-GH1z^t{QkVX&DVhXs*>^(-YR&ocIw-)4{gxu2z;DJ!pwnq6P{i*)4ZM`SrXa zdOUg#ghe*ZapVv@VIM^+Tp#rEqdZ+8ZEpx?9_I~7$I`MQAcva2b{znYPs-GoTQFw? z0Uak74<~@}$Uv|Gb9$gIc;ks5LDG=qhg2Ovm~^#nUlPkl?lcY>@jfK}j|gn{&M~&7 zK~l7829e6RDn5xkz;5?QN?bv|~63QL=d+Y=z^PnxQPUjU}gnp@kRGY&48Pj5#% zs;`6|{fF7nCchGlMMD<9eZQ740gpXIW|q$L{*y@3+^flTe8Ub(Q| z(P`bmLdL=ogu1(h!aIiV(ZdpDo)gl^R+4=uI|e#*C`mu_tK?sILF8maR(@e%$D>sK+XzD2618*TpSC)lk~HB=Pd*e{)POMRti5G z7c3T6?B{%Fxieb+Z?*)AK;>VGo}eB<;fL0}_HVMqKbX;OPXy6~!0w8e-`uW>GRI)M zXRtAiH+2ln&==Sr0`fuUVH@ccX&ahgfL-V`h{+jm*K!Sv!)?x&3PPw)KV;K<)eK@^ z-(!v@%k@ok)X#9AU#-WVpPh*pykiM=gssult3u@s^XH0XQ^JhK#-mZJ>V`tlv@1=z zcy{wgxB@$$u6}LR2OU?_$O~V!z*iDCwou*9L{=WTHt-(uv*bjJ=3cs2EnH$jX-vsf zY~`osU%+qLA{t9~F-HZ_Or(p5ps~thiDp@l^i04cXV~(!B}i-${nmrc0(s0354KvK zNFKmd1NsCTSr77N=wY4E+P^^Wy?53*xGx(I&&D-GG2_yylq-CuII{Fyem(jgBU=|b zz&AaEG!nDIlEF7*N210N@Olc2y0vWXKGwdc<+uHO$Ketf@*W&wYgUs(`!2*D36T6b zZC&%)=SQLO_U9bpk^ph6E)fGK%riO(fS$UFlOw#b&5B;a1$UKiP*DsEYRicX><`}& z&kp+2LRKCurmP`0>Aa~2lVIO9JI{ zY$>Ar6cTDN(n0r02-jGc7AYRz=v!!@JW+b<*Skv8J@VRYa>Hlj0rn&cvF;@^PVV9K zb=Ert2lvtkyNUd&ZAKNJaQ|cGt^kq+%4n@CRKDbW_sMSTln&BJ7_XkeqCG92HnE}; z|0_A`yi9MUQ!Wa9^|V!Jr?~4VBsx_#`QCIJy+=HB^(YN2jQ46{mb9aceU9CM$RW8c$*8ASv~?e{8|F1S<9Je+1#;pU>m_V zT0eHzynTTp4TD7&V!cr5MN6IR09Ext7 z_sFDnr)cTSl$2vl*&1pq-^G9$BIkLfajaLb-YO6~Xlm-98nZ3NbRqv$Y6osZq@a{s z{yQ<(u%_3B3Hi-&VM{@tzeg~n-{{8XsbRn3E5YHi0oc+)Il z%p7};{2#9NzKphqPZq;z8*8gu=2~J{e5OjKHi! z{ASs6Kkb$aTF|+3{MFILaTD{A1Xv1?=c$f$&ptWm6`Yn>%PjhB1|Zo6@sh6id{@g3U$2Py)4P}h3Y_Q1zKf8bTe~TtecC4W~7Yu1Fu9bT#a76sIQP4 z3GO)3AWKu>vLi8$=zbcS@+I2sy5Hkjne(%s*RZhf^zo&-e@MfYV5`F|WZh{W6ol&x zrU=6zFl!5KM}zK{zq*c!oexJ+9u(h1=w@MEuKmj$)~@mi!6P#C7g=p&drr||@~#j| z++BxlZT<+qN4&nI0jj7+Gh6@~W>(#84a^!VFtUqIYM1*1%}G33QXuuONQ-FFCZvH- zjvV^B;j+n$yX>hX+^=EjG-lMkd6UKmhFLP5{cGX^?(#EG-bFz@6?e+p585$kP@>7L z%T0KJ5rM1@zR{ry;o5TH3ya(;$JgW(Og~y@0{%Qp-bzq)#BaMqfO{mRd|YDIH-|_3 z^NE3a{9<*l&QI)(>HUS@FhS#K@QY*j0`N3S)*xZdkxq5PsPlC>(Bp>z;2HO>xJw(i zf-jT3;6!1tIKfMh@TD635hAy(tIlh7jqvOOWVN#ALRWS(40z}`vplS&WUzAD+Mr@h zo9~C=Ev+&s&a&u~TQeX(bm?0YC z5R`|Ag0Ph_fTv?u<}=89O9pO>CQ}+eRzP(cqGzai*mZvqf(SI5P9{mvWMP#h(qhh0 zb!t*1pw^5ON^6)=dT|tS>^cFP5URrIS~%b!Ol;E#`b4AW5;KxVt`DYVPQX0ggut)d zIy@6csDvN(JW8~?Ti&zZQ|awLn0TBvBVY`V{eawP53N_WY}fa!JEiLfy{zsWU?Cwr z``+ZY>#KveZ8)~X(7mVT`Vc)=`XgPjXNXR~(q|q4JY$J=$07?z6i{j(Dljw}QVLK| zw-b}o%GU)-9bA9Gm>nZSsdE<^502x(D}2t86n?XY1|WF9Z{?8XI()v)JA(x%c|11Iu{V0+HwD$1N`=}gO=eB4*o^C?wNrR zDS+t;U@y7E6C1xj-6xvB+AQuN4jMp(Z={{t&1E%bjDS;qb$C%;zWPyu>-94iq`GlB zbHE>+PrnrNHnBd&{jA2{yjf*$JxS6hJutS2m$yFf9q7Fp z^l=w$$}(z`#8NoD@^yCH(`>DI>k-&ldvn0^r+08S$Aks~j&oY2u}taq@$s$ty$RG+ zjB&DzQCQD_c6$)n64?cvKliyNhWsrrVJ}u>qBrY5|)_ zRQWkb%ym_S6(Q-%!4>>~{;ep8s;cVzh6eyZVf%MQVHEJ6&9DEQ26r}av334$lXJiR znw=EE35PCzU6g zd`hhI@SVRO~9+Wik3*@!ju}AfR^QITAAIQ z$bkd@3Zi?|m25%$4Vtd^P$&42X3qVBCUZjTzmt zFZ#N>VPP9B$s=C^zD2%&!jZNZ$FirduH#=FA&!?i`eM7>!({nzaee=Az7D+(e{IfC z%@k>t52R49?1}nf$}JJo2f2Y%k;9abcs$@m31-yteK|*_ml}a64D>P^teq|0#M}aC zmb@A=HBqJgevjICu%BKI>TWW{s30d8<<7RmH5JTz@pN9;HepE%dSM4f$1c~Rp_(>)= zRwa{?a@4ZrFu%G@cUQqU5KHzq-#HzY>&oQMEG?K{qQB?&B_coJxeD25BoG?(-I2TH z@FX}bP*h^=b^j3tz?m2;n>s!!X5g{@C1EN%sWQ?GWFeR{yT|y0*WSm`tz0wpkUjnQ z`3Fc_oS-X8_VE>qV?KJT7BS4F4}UXXs3b&8EVVaiuzdmXpMA>=xAV~aHE8uMNES&d zGi|=ra_OXNMPrQ!Ni{|UQ>_)K=oP}}x*i4VZ*mM^bd1_SBLie7@`xx_M{ohT;j+uO z&y6|*i)v@8N7%R|ZfJ^cDiIa5Mnb-mduXmz3t}LOO3xcg0~}W3jDxpaR4XHtbYF&At;O zE)f+izYZ|SD^#8@HK4~_ZQ1FXPhD)qC7fTSM9O;kS==9bwtl|sFdHLnpD?vdM9 z?vZND4-rv{@Y((zQSGwU#}-!>KX?Ao(OE^C@PX8n_tT6>JTnASiFf%qJ-eWI7Bm8N zy;=d{ask@f)xp-kW@~D&;Mnb1?)yW*PXZIs?joUH!J8&f>{D=ixy+U1Pr2t~asW|( zRr|xi`J&klnv~$Fj5w#ni0;0GJstC^A_Y2PEaFGD7N&L&s#6vUuI2A^XYd$q?Xk8- ziSKNFo}ZPX$BGJMA(A&p3Q7D1?EOw0hUgi}G^xBAW={2PdDgliK_R<$ZwHszk*UQ} z!9{({ipd+Piy>v-S3cc@0&k^;AkMLetT}z3;kh<$4kD;V;Z0P#bAUT2oUh66!+dB6 zQKg>V#Hs-rAwZ3nF5s>vFf-JgBWMCV-6*rr5XlvKMs?0#4Z0lA))*PT*usNsj9vkb zoZY7_t*2Ro#)HI(o5EhMMO%&l_=AlNUt}~c-U83X5l{*_@pD0LZXoDsFH%jGE>DV@ zjD45qZdYDb;>lNU^qK}@Q;LmPfoe>%6;2#V5HlvYRcMw6p0~=lJ>r;6k0xJ$klitQ z@8G25K-wL*6)n*EJMUnBh`71z3yf0lGk?5?RMxX=`cU76%HvVJ1y@n~Oq8sGt2}bV zr|@aysFk8NSM02t$}8^Z=DIZs=4CJR8dt{2E1rc;sG=odWh#7R`C?=8#s-}Y<63qQ zJj;_3I-}Nhb(wPm(Y2Ab=edHp339l=-NTu;CwH!{uCw_bE(4qyv$sg$!pxjuD&Y|> zX>iB0!UGH!z_e4VsDT#@*T`GukDEHt%qh#$g$wwlUaXMU?9Ylf`N&|k(uSqBX?k9F zqctZ}H3t_^rdNO0xl)Mo+(=zojLiD=cG;!@=qpI9>DXV3-TmAeiwE;9}=Jcfd9=pI7;&S zkRbp7l(GMvbyzq2vyXxP?|lp>b3GdadsjPWlmEs$tg&{?|79Z@HcnftfAbC~9H*NE z^srWX8SG$YEQNoL@Y*^nZrz)ZAch*Fsd z5ZuBRGD>JzF|(S}#IEpi@nnrWf{Q=>T!{_h}-AoUop{ivb{&xxp zO6vd&;V2!??S8+W zejbH2O<|*0FyFo=o-nHQx@nP6$p#Q^Z6hY>Gzur>Yd51xBE$s~Hni%H7z!XWXzEP0 z+{tVfi;*6PhTE_gNE~|Ed7O|`r)fb%@Uu(}d1RYH{?WrpU}ESD1e0$;9w-rC7cQne zNw~9EQXh1ssZa@!aNHaAb_LjvaL_o0VFnjZ5vEBC&OFQBSkxJTgf4sOC-JG9 z9c6qy%gNix_Ha35xgas)x5qo9Ye9FORNg5{%3WLc$=mY|1?7_&{5|zS`4iz4>ufU| z#vO{~Gz0noA{^2Wn>u9X*P#cA)xpTr`YlLG6Bm)nWAux&EHd{u#9|#r>II&NYa7Nh zljQ1`h#_?Ut6k1oaS_Y7rfvSapXl9fuBQnISR`G4aJm#&I5blwQPn5@uYJSlNCwUb zW1mT>r<;4Wss=^)*|dl*Yy~%<%};Rb!JN_9800) ziY(yt|1zixkWk8+Y-(94Q?rS5ZV>wjxWr%CgQ$7C-q`RouLl-}^h<)(vV~0l9q|RN zUl+elg#bf~TcAMVE>5Q=97&8+*HLfYL1DQ5kiA|JOJayiFGSp8{7vvsvmO3dSIwJ? z2)l%dgOfKkaskDji`KmwPw>@+hRwe-p?1sYGdjJ{fhg_=TkL2Fy^LO$*U#Z0hPiq8 z65L#m%@h4f6pZH;ENUKpP)a?<0_i6%#Yk8N7P;_`daRPaVU;`VPRBSFPj{VxUS5ya z&$FrJq3zp{=eO0$XN}*GSUAoi2?lWivNWzVb4TUK5=PEj6>fa(7avU2Ei~sV zy=%#bPw;p-0?3Bf(sMB`TwO=nyymjg?L~hATh2{lIuTNFME@#M7L}973%-yF%%~wx zM48C+ul+5=AP6E3SD-SP)P(KHb`DxmHiLqDWR^jxjm7vCvgXx~_y*JfXA1}@&m*En zs7bK#k^yBNLM!CzaW}z3bA2>JU4gSk+hChfw=BU}sXyS5jz1VLjH?1uWP4ScBaFDn z4O-`nCx-A|S2k)q;WS+-_lD@Az4syQj|SLqT6akw15SFO?SN~}(S~;eSob^vowg+O zV?lCc4>2+qKNeS$CYMbKuQ^X|_Kg)7IY-$CUE?ZdG!Hzq%XrybQl6KOTkCvMb%f9< z+3gP@E^w@(=QAj+bXHomk}YiU(eCGs!Q9J1g$>S+1P7vNBA-T+gK$wMmnP8dxjlh6 ztD~N7h^ra&52Q6nMgd0#E7WSwBVJvI${)BOULas^v-YFF;w9JP(h$h@|C$~!@6M~j zp~txdUaX&SHq3wl@Q+fdY{~0aeAhWe-s_t*@YP#f-ChpjcZ7d$uHZ%AqUwtiE)9T{ zp$9b9VwD(O=?0B}_(_=}717&K&43DhwSoYX zt)(lov)x>aof__O{q)=AgmdYcSx+3{S_Pme%SIY~!j-LSXJkY&H%6$8Tl8EZb1ox2 zJ2knt08XztYaSi2xC&?JTFyrg_K@nu-l`RH9$1&^9#t`#uwt{BXLBjQyaipDe=>#Z zSBUK-pp*5~uF80{(jF zXD9AmsYYW85_*Vlea7a-PxhZ|3Blg^&S0<+=ryH0<2nOm4adyI8c{&TL4@64CD}Y< z>=2~xR6536it(D2ZYWEXOyX0M+pMpo``+{e`u_F<+26zen;0zMoWC`L0|5A8{)-s= zz1H5A&GCCp1X#ejw=$zf1|F`w`zuof!Z~V_^OcbYV2N@8$Kd42uArwLOzN0zZgj1l~}RJr3A0n=?|jol|9A*_NTa zcAaCa9Si!^H}OuhVvmJeaNpB6UgRs6)s*xWD%mHC9npJWHzKNZX+YU|_$V2c4-Ckuzqc#vgmV)%`U*Wk{-+B63l zvy=^#2cEbi8}X{se2pKeW<)z1%%Pwch@BCLY{fXjKx)7d#@*bpT#26qz4r~bp)40{1|Bf`M)D<~?KKT@EI`&v+DWv1 z#DM6`IHv5fr4i4CX-CY2HdS$`Qtx-#0U6y~Vg|%n3yqUug<0X5zZ>7*zmOk~6^Ab% z0KofSd!he5>Dd2E(v3|_4P31MtFHBTn&~t)|0m5-(h|RC zEu+KbjRNIl6f89@JgIi`gD3}(JsC*dkr1RIJ+XlPiU1s9EOW!j;x8Bk{Uaij=6|wf z#D9;-!q(!y#$kcKWBxA-QQfmUWJmA~?Hf!BQ)#~neSU_m;4~??Axc35yt&9FVq+X0 zY#`n$*y`!|`7*sO4n|t9L^V0h%ER+JVxJnF`iR_h92o3BhD_TQEi4?!-$SP=2{bRc z{%e?Z={PgkjrrjZc}J8PThrLY48cQYLWS;NRTW?a5fThrvwq0rI>Y%3pC8$;DRE1P zzZ4e;Lnl$i1OzH(AixPM!7R?y5B$M>Gv_H2wAR9+yS*T2q;0;D_+3ql#vTH$K~hS} zH8ST_;KT~wxVnj1auj}I6yMekN?BhJ3n_8Fk8Q|ZGU6ip4hgX zOq@(?+qP}nwr$(?BzK;-?zwf&se11ZU3>opy?3wv^jh6|TehQyXWMzZ#>|(80JY}~ zln%XJIa8Nr6ba@7x382ob>@*~!vB-&twJKq2znnP4t^LFKwY_43KKQC}r zaNo;5`ejM9BsGQ2puO~XFS+-TSwnGv`v`If7T`7KQey7?E27G?4*h1A*1SLvrC zAYuxR)KiSLQy3#CYJ39N4D%4LXdqt zKm9_4ID2w>o}!J|bhqvG@N45e z9T3wxfjj8Z(#_E{m1=7MxIDqrHhOi-NKxSKBOTj#Kl~2rTp|()->gQoVOg%TC2YDa z*1rP?ebemNT+k|?eR@_=@&$$Jj;2@9OK>X+=2iBB41@!^8>k zVJ-K-#H_X)1xw<(GDMezr|4Gax}^qeovs6WrNfR^=6o=l9i6g$?80yf9VxXvB8;P- z#)5O3AlWdw;H2*%LNG>&v>pnA>U*pKtX09S^Jx)HD0`@0jc`FXLmnCHQqrMm8K$bt z_`ku$Ni+s1gE6KI_|@#lWLRnr)(;=2FLK{yAMQANa%W;4AFK7}=`!a6Y_Zti0!)TO zCYy2Es(-P~+D9@kb#XM>zAwFP40xw%Lo{PfQMaU&XoKCv^Zf|qH^b0tmHGz;tU|1! zKOyr38y5R(~s`AiuvdndLP=f;8$_%+U=N zS-*eBj&-#n3oWw)$E(CuVt+*!VGK}Hdj0K$s0`WLCjK5UZ!JUz{te39%xGf>eGSg= z4BO-0O)^*R*G7I1qkqMTYvnpplw*mf9_DPmR25kEd;=)qUt zipeertZLBmgOX5qSc=$c0!M~3atc56^>F?QeM5Lw{35eXvy)-9zMw>H(QgdggD;UH zcN9RAQPZu2t+raqm-TI%l z@qY{n{;vi*;QyqJ|EggeE9FD6|CkZ<15n;)F6%m5B_p(yj{Pvp41^6qos&BXAS~*f zQ^>?w8V0v_e0y(lLd{Ily>*nt(G8}bo}NC*O69GYyV`Mg7}+r2cfNPl%X~*tjSAXO zuF|sM#Rlh>&*Quwqd%{Jy}rh5YivhL5nlr%sqjmj-;^tKOZlKUeUy?Hou~@vvwNs2 zc;h?eGITke+YpP)M5kC@RF3PVBD3h;VVHDFCRO%B|D8EgD~Gu=cyA0m^wbNT#>@nD zjP1jP2X=c5*F0RM4?E_}f`QWF_MdpsJ=mciYErp9Or4YqnX3_Fq!8h>{i$`|y`OyT zHmbev5KKYG9nXnC$r-+_ID)t>^keh?=M|JYAN)ccPx1LNnFgZM;FOY7b0<%AwXK#?#nDJ}w5p*nrpAQKhpKc!l!NqGzuiGV{C^9_@Q#E9( zjx_m;2^D%GBJRa*s6iugY&%ROOa>-sZfFgcoS42zCRJzpQ(jXsNih-p1@nb(eSC*N z$M=4y%?x5FLyC;eFoHp#_KuXfCxMs=y9?=^9$;VSA`kC;_!$M4dT24e$!h`*$)hyL^vb;7%uIlI^B3gT|L_DBz z0gYcJx^9)QVqELl38F;z(&u={#$f6jM@n(jNFla|%&j}P?J?6&(gr{CoF!o;b(f)g zQybBx#qaSaZ`0Uzn&=G0MwfI3JgE^mgf<1*?ODESwp3t(B$u2{Wq@cS=7W5~Sgtp9>V1fu1D3dkys z-@rc(-??*-ZA1zY=;lmFPK_7>rmwIwZO9(kZ}jySrYMFpCAiKJWlanw04-$5Cqo+e zfk|ahX~Ul6RB=O=%{qEMT>oGVO;-9%Q@_VL>$x6ItM$)Hwl&3jo}Ec*^R zW~5G#mAJb^V$cgDjP&RZ-CUe(Jk~LM!cbrP4*iDPwM^GrkFbhgj{t&a;nkh8XbiAF z2*X8nN_li0hr(1ty?H72U+ocat)Hu=_1wBMHKH>t&(_urJ2-;FU0_ULxF6(6@o+1p zcO44LmCjeM2N|a#*_)Tr z<%bPck$_zNEQC!*TJUe~8f1!80jhO0l{&{#&W#VC?<&j9`b>J_84K7;WKQ{%;Jo7>JQ@6hI3#urST`8$ZXmqhq+fh?X-(znto!l$1V!krA6I8X{| zw>X=7)Rj-n2iD|9sn!Y5@L*C}09%T8g5DTKwJ21(=bn(p%rgCB;;OL?Ntry@40hff z$^!+G0%p}UTSWdX-&Jp*ffmitr?AEsuFDV@mG2;Ny2x!_2mNfTZpcR>g%|XKa@v;I zT#V+x=1W{CqGmrECQX@PQn;y^9HD0D^Ji5t`vcY|0@N+BMyMzl!6hVdxAv!tHuh`6 zp?t`ozJ0GEhWW?IoU$?%A*Dx?2qe^%)=H)dhf3VnRMnJrznh0IlMj}DbZ93`yajcd zM&!eLy+n?tnuM+~cXQ~T;l?w=ZF^Fbtg~@tpo(*ctmlZEnDyBSPSZKQRnXGqBLdXU zrfYRFqVe%UedkQ%dq}F}9Qwip!$EuLuVuMv&>cU7JbB~g!&<>|plNtd>PWK0OUUT< zTve}KbgE+0F!oQD{2PAlKvPC|3LwC&V-@6|mmm`?Em#H3cFAJKK{#zU<2{2| zVj6ZcA!V4Jy5P+A_82NrgcS{F1_J7;EjArY*`mVqOM{?}I9Fb2)Yj`e^9-M6p-)ID zB}}1q0!B-@`~1>XR3hWn7(_E~;RHrwN$=Tt&S*?U4xl**YhWj5@z0Kt`(`;y;AAU} z!dZJ4R@%t|Jj~O$w1S!I<0m85G=$pO^}l;&UQGNvMk@|cCD{8WEIkn8fwSq$2lu%O z;sobo`;+y>cy}8d6=#4Is+2rB_&)~AzaTz~t|VZ4o5O7oQ;hFBH(+j8gH@`DzAK5> zo|jEF2(p}*#L?q0GOzdhE}TP^dHI-{-q3lA{&W>w8HSJ@^w2%hw!QB8IqPDz+&}{B zK}ACDjBhWD@>)5bVx-8^OcQ)oba+1fN$aCZT-s)oQ2oMhcc0@}VZ8YwggF+=p31c~Z?YGJg};;AhxyY}G?%%-6l!oG-c+*5JBX~nK&Mt}2p z*@GX&`q^!66&=!$$Hb!58)!)`hd$@(MD>uKA-+u~{6-6Hp!Q+y-IV-ocml?}OW-jD zKCNaf;J>YAkb@%olK2Rxe@@cVL9+!mh0buZVb<@Jcj*a7C~+aU4JX(Rjg@=uxOp_V z)3Hy(K?Vn@ah-F2jDrndb6Zm~I7bU#=xpz?&&R1@N2_xM8rZM7?J)ibM&0c|E_*<3 zm7=0Hm@h(V0hdH7paXpkUxK1(GBD(-ePZOSP#PfEAoDn;ou)!V`EjZGH%A_mLg9VX zu|GLzVTdLEE)qnqvB!9@hs_?+wG@p7pQ~d;&xN$;hy^)&H(-8OC#sY}{cwEw7%8K= z7ZY7uZBB|;C?Qlvfi%jkFSVFWoRaK{Cdx~tpZbDTNiz}?fs-9yDt@u^$A@AG7iukQ z5HtVj0(C`tF&bRz>SnPLQ(b%D9=njZCkHk!0idWg%y8io`iLKONvL3#9 zO@Lr-8HqPD$x@qi93?xldONy0bu0w``noQY8O%7fgo+qPV80c;9b-_hIyu zpvGXH+->mljTk7rsG|k(LrudxLU%}e%KkfM7cGF!(Gcw6ck2N``5jHY=?ZrFNmOMN z#;uzobOB>B7QEnIl~Q@4R{bib`0tb=4{#%+I5hsVXPYlekfpk##^b%Ozx9E zY>06MSfNZS_wLi_nGFdEzZ#MCUMW~K!{*;2n`TB7RXN-D83VrK2kmN3X85E)72Xae-W%UWX`Lg7kX8h*k?bhYkt|Bp94fq{9 z4rAc4#3O^_229SGdlIIQd0;L#`RD2W*8HKgSA#mbB-PT0iloT zQO@+87c&ME{7g$28B^%GzM)Or*3Idl?T%l>F)ZQd)@qT**`mo3OsVtXBf|R>p<6nTe7S2&cEj7dTvp3) z2QhD-KD=ov9{?UektE&I!>^?Qfyh{mEAfz~`WGkTs^H0Z;+ojQIQ@hBd{dZ`GR`vj zVi;rriKL+x6hcHhuqjSxKS(g&fff0ty9vdCZzN6o-bNJy3P$Q4jgz@lQ)&@|&RuZ_ zMS7k8qQozEYU7V1u*fqd=dk5KJ}yJ?#1o2!1LUqCLE5oBsOe=o00UH8Pr@~}IL34# z1w6C{fgj@Nr4Gw|B#1YqG$x?XfX%>%qt*lq_wei~c^_6k)JlQ;L7zCM=mgX$Hg|3R z?352e-fm#A)@GsH$xrXd#$;ySIY<^;SK zUA?1ff}TPVih>>;Kk|21ol)ZN!I01qTzJzR=rkR>CrpSmk{-EKwxxc2wMA5;DBWNh ztgML%!9M!WzK=8rt7&hQhv#oLD($4_tYMS|M3padLH12DufSHbB!I zQW^wn`Plh~-_+q5d{KCQSs88aVOZ#NoN-@NL6M_N1)I#U>DN68yt8uj2oNDZ;I|k9 z2x3z*&>sCOYOgJuK=~Q6wq79V@UDRZjv*ax;i-5NZ^x+1@atEk15D11&FZh-nC%0M}+B$WN@S@d4cxji) zA$f5I^YJ83BZ`ztM&uI)ET)(+4FVzW&HCi1j1wV#;Aet14n%cmj2eD&EvRUO%8<%Y37Nb@|vi|W5)k|L7{@N$u zal_}{;I+@@K0=fG$SVvxt>3PSd{BEOs8_N^Qk)_U0}#74EN8<%KOB%Nz~oSEP>;tQ1X$Z&4NS9wkuZqGn%K% zz~|xc`(aKGJ@WfO(a1cnN?@4o3r*z_HK}^%I3ce~5K4unC|y)IdKg7!?3sZI%^YHG zpIPs;PF_h|&^R#m&ZQYX!8dC@_#;<&pOtOA68sEjL+G>zOr2~^Rx)tw=jJ-ZQwN(V z!LQ%>%9@3e5UN90tTcksEV=4)ipBg1ei=ajVkA+lZCj~r_9?dMYH&kDlI+d8MzK}8Mxfxm z1py8!pvE5xpwFNB)QW9IBW(0(Ylv%eI?tZ~La!mny+H14Cpf$u`?#s^SgSVO>0lry z)2H4FQ(G1As+#``PW{3#`{~Kr?CQU*J$w6IzOyyI&0iiB929A=voHw{Q?0Kx9&PZA zz0{e^ELqvD4g*u2`{1c~mRgvyG1TO2qApBi=(FRr!ja(Gc4kL2Pfi{5UN`I5Tg?kk zW-lC>D2w;e8oNxoO3t@T5ge3pt$hhd2Y1Ol``(JLVZ&t|?2owx`a}ikmV}I}&5Nc! z^@MJ}qT5mjz0f{|V8FfOU3E-Halxob>w6_k)y#fBg0=?Qi%=XDuOx&FE!+6jEJe{y z+wf+w@bbid=KJRFt~^7lyGXc-^7460TZLR{MxyCicr&>G1~<~0xV;hJ4SQ`_^Vv`2 z6k6FStel`#JIt<5X7b$riYvGXJZCLq2`jbI>%7#9T;&dNYWYjJX25oMlt$>;A0c$C z-56y@ua;Xl7ki_Pf{)Bn)41pNOl-_FH0OB28^qou%ZT_$7tu*$w z`G3{5hK(JLIMVmj-w3vNB?TaC>3C`yje#}iEVL*%TUAkHtv2wQ_RZo}SI(w$-`lC4 zt?Pf+kz2Lz2B4iRjV!ZWH}qioVUz`1w@==^in~?6eIxuRqQsstG)uhV!2&qg_iIG` z>O{@E5@c&JBEaFHf5$54k@6)#<%l_AaM=GSzd9y*eTtQ={@u4`?jeYC$<(PQlN|)3 z4Bx5~6+%UpMxm8#Fk||ZM3G{zPYb1eu&jfj$SJ%MOMLQJ7ZjM3oJwFl3Hr19Xi3-( z;9tPM17(mB|NBS+l~FJTk#xzYLv)E26%|g@SI8lS{Y4bmi#-;JZRlIfs75D{TsV^ZU0UYj=-bg3I&E+qZ3% z0H-K)jESiCxXFS%>OHU%lN*tQ-gLkkVjvw$rRBacQpg?de&w>g;Q-38g-D>Q6-R#B zqJ3S-xI)d`=V_tAeet|?Jhx^Ql|GZcFu0p=7NxAM1~XEA7z@|?N0=C`w%9w6d0#5A z8Ua}(qX93l!6Ih}gI`qu=1aIv7?&gmzib*9|4hORY5t#uvcf8Vwc;IC?cPzrff{!d zQXZ8Jdi{}r4_N9%h^jgTX5+Syb**wzp|McI$SaWu+Y@3%xe-t#hBqsZoM{*b7T(?> zqGQ;QDwJZ-Gm3=ZNrVqDb@+a+JbNlIke?4WMKqFR##>dqgBLo;zhgWg8HHLnvnq5w zMB7u<+>a>S0O2vIg-L-OL>vZ^Q%7%v1pq(ZIbapIWWJuW+kWUB#BGX5>o$-Y+a=Wp zvMJ*|Gyfg&A`e_7xToG9NEYd$A?g%;wt`17*tQ2IMf&nS6^&iDEa~wa@5@X?i1lianrL?*3u1?6SLqr|{(HG%!4VB`gT> z6^kazAGs|6I1*q#+18mg72>MC+PFD?^$}}T8)7`{?yry7+4{hN1K~rGYKlx_pO(JG zr-j( zg39LsaGOi3sE-R(^|2_Qe}h3NH&&CH)3g!-C%xC+Xja*t#~!w5BMvCIBAfkZA8onA zuq%-xR1jvm7Y$^)ka@zn9hoX3Y@bjvwub=F&{pFca=$RKeQkk9`t~{Zp1w6W&8}Kb znE^P^osnT9LaX%2dE_Ycm6J~}ZtXTUEc7z$;Z5HR53|If1rimq+LHEDc5yY zw=*^AoXUA{@1M$bUcD>$(J*h>r^-aYkcrqi@mr1|hKvwtHS``V(M%GRIoD=kLD~99tmKg)XSzNW_vlD&624L?X+`HCc zzm`m6g$Fp5RxYXprAfV7<xx9NJo#SpkWB+gat+9?#HHe->W~&#O}aVMRnb`MiP{{YD&g%v|}Z zKw#5OSH%_F@}%?`e?7vKnD7n{)r*KYc&|9*#2_>`@lhGksIIPnyIfa;^;{k2DOmV5 ze_@eFBh)8Qp}b57zwGi{y7;|U=$N3r)X5GPTbF~sXX9F}baLgzQpf<8w9HphKzm@t zwo*+8!J(JxEuhf(RWA31FISn7Ssn5I+4%Z@ATKhVF(06RkXOV12YLM`fdu7$@htyy zL+YY$Z*Ayq>hOPqzfJ!^E&o-`ycUkQ94Wcg`oJuzk}Sf_WIS>%O_k{drkZL6R-#%J z|GR)(q>38537aYw5nh!OlDSyu`C~nITzXfa;%eYO0vw zPR_`?(fTZM>a%;D+TK=<+D1+EOPMTPg8MUf4E1^$v38x`f;qe|uQ|!o>45DE?#PMw zii<4ZY0@umutH<9#k_#t0gj5Q5a(jwr9WbY-fO3tyqG~JGOE9sRSTumsLvu#M-N^; z2A4O-|K54gRuoUX1LCzAfdZrZV>~s%FuFv?5OHlv`Fu zqRW$bikgB2WzLDER}5&gDc^&Z8KeveEWA%!%pO9N;X&zv^YDk}4xqxGLXYC|VyG(8 zVCh+Q!^oQ?Xk6==W0#+S*z}iq63&FAmZq-S$zPL)G|BO};ij@=o92O`JC}KQad%~P z{n)edcVDx3Zc=*T4m1Op^(#3;6cSa;9DpynXF5`v1#B;`NaIbz8$stuBv=n^Uz~NF z8N0z@Xmu|n$TsUztmB3YVx${HndQ=_F2L4+Tm-l=kdV52lX7=x(p4t+?)LKH)3FD4 zXS5WYIImj7WAbR4ee^-YcYvK7oxk-46ye~)q48hS<>s*NfCeBaxaXY&Vy~cC4G=9M z3|#ZT(n8q`iS)u!YxNP41l#U>(;pb%5oLf1A$UWqxVeg;cpl6FnuP+J-J{dh?rQ=A}a0@Un}) zFc)I!X17GFz4s-N29@83E}b^`^2pY_g&ib3_%!9!&cL56u1S=rN-mRrc1`0riI;h! zEH*!f&ppkl4AA6pk7=b@u&3rrDeBWZ|oN#5%hqs9II`rwRT1ut3hNaM@6fm^Pw@Mkyn+ZV83TNN<*z6?KFRlnnmzXxq zRw-^~!5SqAEP27bYD^iANa?>_sSJ8`iDN@JH3rHP&9)KT$*9NJV{p#cJ!_`+8UtJ# zz&eq$2pAS5V7GxuHi_F;N9y!8W>SVH>6q@JM@viei^KB$1B=k|i&r z9Ms4>_MI@Pzjl>jrcd8EDV4$uj2QyFPER>-!vgQvgbvNr;`3_gscoX^e~qZU*{t+$L3h>rSi4s&`mDTK8VD_gCYS)du2Gqvnf$l4cF4}&AjtRMfg zh%Zo-EYH!I8yl%1r03=mn47)hBmveSo_g0$;4Jj>o8QJPjn(w?LSO zVRuy%d`+wWJW6S;IiY=qyiusOwBhWzlYG+YPn)q>d8KH!vwV-oU(jM_TeGwh?zbDZ zwEKNva-Y=SHchNQ-Pt)SjpzryAVIEmzjaD7(10!sd}av+{lDCyZpbRLB8_PT4R&Rl z*!?7hcZc@Qnv>)PPGz}tl&*+g|8`=#R>Ug~#fJ+a5wywMKQWFvrb`dQ=BnnyUy(1U z5(%Kn=f2UZm`8PK-~l4_G|>(4`AXHY`pd$82*^wXL-bj|tsL&~_?ik{6wd4VS|AXY zAvs!CIZmvb?r0@PaMRrEDd=n1x)M)H8ae-t8WkPIwSVp5PbJ|zJ=cG|C|wgU#{HgTpNtZ zST~xGx1=+pZd#{@+l)*Nv0DXatimpfrev>|OwL%Nn*EMrmVZr_dSv0hdoni12a8$< zOi$5Y0*&b^S0`gszi#djs5(l652vPKa-%1*2y0B}lIOwt!u;#+kz=1gLD>FPeYinu zL5EH#ARDd%5+B-T2sy-$>45B=o$Q^6+TM`1!U)Ji4bLso z3pO$X*r!@ZC?|um4DJg0$zjPpHhyOiJ-{B|9+N51shKoAL5|R+PMc;}2PAeLS*EWF zl|blQ-bD+ogHd@5H1_2yYJ3y<-}SfcEmV8VHqy)4P7TS*NBE4Wv5bk(P3ztj%CMpG zkN!Mr;NT8$3QvNv0%dYJCqRk-?Ld?QCfNFCYDbShZ_y zcW^aLG#lIe?+h_RMGFb^`B;)OaZkM0AZBxPIUeG^}m z6CD$9)V-s#iLCuUQSFO$J@DUpndSl&W0|GS*tvH*x!b~hf2NDYDN-Q#aPa)u(vwcYn zEWa|sni>b2_QLfHvMSafD#cF4a#e^prnd@X4d)=rID&Ew6ZPp|!!p+9Ti(Bsh9;DJ z$t)J$=0gRSM~zqCgsC6_CS=|{-*Wwv1tKoYQne-9EV(oX*gI$T_MMWLZRGesr4NZ3 z+3LLN_25E!S_LlRtfs;n`nBu3N+HapIRQsSmWl9!*c%%BwkoI(_QvoHtZ|x62VQF8 zzXG{AU&7gp^RV|xiE!(xklKD$oPK&uwM)sq0-u;xBWx`Ny%}H6_S(SDx-Vim6yCWZCcOLpFdv!ypWPXBDU*<$9D$t+Gya_`|93 zv0SPlz0Y?AV?{xzP$UYmLu5O!t>j0gZCCIwTEis;ap-tK=o(IYn*HTw3_rS`GOO9GnnkyHXQJ zn*NAfXj*}pyqg=4Ba~JD%C?IUO}i0JVb*33*&Ee zg$_<+y^7Bt1lwar@ZKIKe7Mz)35q_-f!?ENVCLnF%SPX&+T6EOr`o&76=0Dwn>po0 zcmQs~h!&fL1sm+`y0{Y>h1R`$cTj+y^P)2(Qfw6U9&zNbwZ)ZIKqsl?a*gR~=BPAWokJcKvvsDe2V~pcx?ll*0JcI@ zZu&a++0X|K1ifLhpo+WXfiAA#^8n^^AnSGc}CyQ z?qpqui;noRhb0d}a>mfVjQ~BP9k-n6yl)ztle2C-q}m<|LJ7PBsU#^yw7)w0!6VTl z<|w}zszbuD+g6c{v0@c$4biY7eMr0wbG#k?AkIAo{248M;hgwJ0f4Eay>WOgplRvBB=Q9<> zR=QVbLvmx!rrtPnQ%f!M_#k)$+iiIn&e8N|&zmt#SWc?0^qK>!LxK)<4bd8R_)`y8 z4MwTVMAj(A)o#8R$U4B`A)O4nLu>w`rKT!ZGNG-erPmmHcfVYcr2gl52r~Ntf7V1J@NxP zX3rA6e@3|9Q&vX-PAjJfKXez1U5mpI3z6i0XikaEe?2*fT(&|JT|6LGT!|HatHzmUrz^MF|ffuJ<4g>GewoWId|K0#cl1vZ#alZwn0GUEZ- zIfQPhzpxX3?cnFqv4lb zp^0VvWyKs~NwM6?!p_m!s(5#c4~J@=g}820Tgb4Nf)VjK^YQnXFLN^Z;>hdr%50iZ z?3TDe4e?yF;p-^tp6qPT&(D?8Mo8CUVH!05PIU^hE-eikY(^QRT z3zOLA2{@i4$nI(RLb3TbPumM5{KSnh*g<)b`T7AeNl`wqtXE%3%fgcggYHZpU8zY7~{7YVraO9aQ^ z-p6Hv2Pq3E&}kDPzsmO4WrRE8_WO-I8`*DF3lT>eT=%FxH#6*4m`J+WaG=#HBo`z} z30Qs&9*N8O6*j1j3FZua$0K_f!L~kL_=AUK_cV$kr1PF{%?-CHAFyH1KG!JYG?Q7)PG7}g?}&U1A+7M>_R~XRTPn-aanMlx$ z4`A;O)Ok`zPxC_dtezb0MI34+TN2vMu*;Xa`b-j zFE?B~p{&lM-2G`t_RM}c1#=hFUINx8!?ed^(y5g9#kWkfi4b8Hh?CE-m>s~cevQAj zfP1h>A%Ac3>nO5b)9PmJ_W0+#^pD6(?i(ut$Ok-__@g43IOiM>hM&{glrw#YF= z%`IazY|Bdknl(gYcL*Va2+M@c%ov)SQ^v#;UX~`#`+;X+rI`IcgnfxecMo|qR^_dc zgM~kWq?s)Y&<}sF)CzY7uuf8fZ0+)}@?E2ZaYxG}a!}bnXQ+7G?U9aRijJ z8J)C9vFZo`<&T6OAgIQG`k+cNrUB3q8Yhd?S=EED3C755Pf!)M4*X zI^o`S;C{ifRsLy-Fnm~EF|l$sHqa@x^A zgZ82kazTvoTu3Z$C@SeHzMB?%!hctC_m1#ZiNwRERwoSP2{m-)b z`PcDB2?h|5u<(ChHrtf_&q!~y|HbJ2{~dSw{N6C%!rV6h&$C-|+m4tE#TUUhcz6&& zA}HO&^`$8O#2@&fo(BnxbiH%2+|*py^RPwR#uaM*;`^nNx-*}}f9opSBR8RCvPR!q zJ$+HRh1s3j!3;GgvaJ$kC(ffhLZc~zJv>FM_6R36^IA+eolM=j;7#p_WS{eMioPi3 zewRz1lJnrzZzYktr!7tjXdC9+sWf1;dQ$OqpbX2+rL*e2M?tP+E1dScgJSin?!?rN z)w*RJ;9F|e$XVXe8*z=V9$e>m=ng>uglJ3fh}Pb}@hek~n=Ip}Ady4F5?eBa5j4<8Uov1<4ehp2NzeqI-^L9Qb$zycDV8NH~y z0vRp&JDtUP@K6J~lc`-=p?HccfU1!_>M~7Ify4o;M2Ff11aIWlbLdfzw#o_0z10a4 z30R40xbO$g0qsLD6MoM|#?E#$@T|v`4|1aM&fS?0Yv9!VEek%hci=R3-_e0Z_#k)S zw2Y&@Go9q`sF*STRdm6+$y({=+2w@`FF@{``y)rjj@(K6@Y@3~+HSj&vvghq-6OoBwr#Q0if3|Y;m_{}wanuI^ma3~vgOkDJt|u78b%YLQE}V*5TDNo zpyxNTGiEz|L7{zc^A_p5uJl6_>8HJU__TvifXk0MR9~FuB^wTc?e$Dh=d>2jJ|Ub- zMI91aV&x~;SJ9xFj^@1Cp11vgWVcx_Ryh;W9K1#9pW>;Jh5+71Kk$P)Q4L6X+c*Mn zqFI@L#%l{qh07_yeH6R!^$!Hi2InA~-oZU5`kLK6)OrN$f5g@0j;l(z2@uD^<(b@JzXAfBCV;fN8vDRHh7L7gwhsmo0Vn0 z-(gU()tn)Lr@5a;@b>(Pm*rMQ#A2ry&XB^8v_I3x)!-BL{odzc0j2emfJ?y8?tG?3 zfyDEe*$>Z&0S1)DL9)r5c@MuQ{%e zh!VpK9Xopri@0fBZ1vkHDqst^*dOn{xUtdK*DM*1fb|@-PuMccKsB7(4+AuL{9h|=dJhd`Q!1- z!)x`^gHIq0smGv-=?N%zE$kPRS!C8k*!BfkFx7E^?`o!4x5dxJhwYl-H|m~_ z{#W-`1_FhC?PvG9y+X~K3F@=wq50cpoi7hSD8}-Ii?hXGEJ`QV4fF(^1-lIXZ?+|t zNdff}Fl&*WaHvEK@bkKU6wZ3cTfIuzbq_8fGLril($EqfJCGgMp5vBkTcl+zLk?9biM0 z5lO5pLn(y-ZDf17C}+D4#3E94=lsUl=E7yhS%}_!nySd9zf~_cNBOb;LMCHEDAuTi z<`l6DN}r3wuo_?~T;j1Kix1q-ztJ`OvN8B~DbH>v`Z)Opqcyaw?KX&R-%?EblzT8x z^V=OZ18vc?+MKf8iJwZeOs&(=&4{EU%n_~~@(0_!K+ z%5N}~gAE_egaBp)+mj|%Rmj=Lf_nZjO`$pdD3)jpUl03g)9D433?aqh+w_wXm429m>p;X6=%qjEaX1jmEd_-hxG2Wj2XjyfBa=TAz$zBd)J91Qgal@ZcT{6G z8B;Km3UK=LBRc=ilu`06l~4*4`L((F-qY^W)qhX^D3RfXAiTea4^I18PL9pDtNW4S|#Y2M=VYiofDcJqwvO^p` zdOaeCyS+qT+sLaf?NdDp<+^$Y+c;;8cP4KvgX5rZZBg9Tcdr(C43QFrW+JDNA>yqe z29Zd=jX0Kv5NO5%SuM|eKm5X~XCgeV@CCH>0yGRT=_Tv{RiPJT3l^zoW0PDdQndpg zk-vEHwo7`~ykGL-w9h@i|Jc31duAh65%o$@dODePD>Eze=Dp`g z`jL%FB8SC1cPq|>_CP}fyQSDDb}K{!o;e7PKL$IciY(T^Zt3OuqF$qKUk@|=3O z<(cS8OHxLQIB_p3z}JfI?T3K!=?teyY-H^-70VUF$D-yw&?ne9%JvAo+B!BC>y#dM zMwe7agcmzjPqj;^%`1SFMO(f~MxlaFXf?uOs_*d- zUU}Vu?fk(Qu>SDCZu8-ZzUbQnurSV2LH2(n2qkunbj1O!I%=d{tHoa--^oPQ0V(02vM z-T&JlMK_NHvnD{G;_vgk@}}G>1ERO=dl(KvNh0D3vqmh}G%&WYy#5Z7dStqq<3!?l zg0kVq%QqLHctlB6lA?AI*Uh)*j#iFXtMR)>zvHO3y9Z~xLWbRe9+^^AgTi0k^0itNp$y=B8X#*msKalB*iEILfia&?KP7=W!b2Goz+$+qxiOOO=!DW?2iE;j<5blpH?I98XV>m$%f5I&p;W}<&wlj7qt z!Yw|_XTt9kONyIXcN)ot_ws(jLwyAvbj0?&N$bPP&8PX%P2X_GSI=_nUVkrN5c^vB z>+v6?2cQUfLv#Ro8eu@I{NJQIxBrKzS!XA6tAE=d!vKx(pGUpMsp2jN@`v<0v<%-F zmuokV$%9KU*8x=^&@xn@B*@&x20Ez~>9v8Tx>WFR)orJ4<7e-Fc;-fDe8|u_`uY~1 zlce@msRN!+HKD%Q_QnDxWEgg7bLzd%aKp~xi!{wfadCzY=zVSYShOl^{rE+XWlyRP$iyL88fU)wrq{rn~QpyDQ>7B=pJI)@9!z*`9kS ziIKGtx!57i&(-p-{eIfUW_+^~=iTxZ84uf@0SM#rAfEt0_{A`zyPg@8(PQBd=aRfv zF0P1Fn)J2!y{+K$Clcu*3F$Jj*C%q$rOitFpbg;Y?>v-`hKQpAKTqFs4<+A+hNlTo z#3^blrm3=p;%!pN(d6B|!0cyhn#d2-KhQ<+P*NzkmLP2~Lc03HJZHh6nkW!sHM~EE zB#WW&@@ax!6VrX-7&62#aDk%~QDG4eGz7s^S%>*jJWI%D^-GpPtSny#lZUbR%U)bR zLkp;e*JmGkzje?;e?O5eTQzETaO4|L>yrXG{mt1G-fIy^BSU)@2Bg~7fz`~(D|#aXOstu2 zr6FH)o~tx*!R!$W$^!315q#Sum;0p!)XHR z4ngcyMJ@1wD9eqAJvRR!S)t_D=yDSVw&Ot$j3Qho{?6YHN}lyV2a&ux>-R%RCrh^| zR~ROkQBPG2LNW0mY>=2NnMJq|e_*z&lH@?p2>+shu9gn!OwFYk{ivt$aa$`39Bjt%6stNNxpgq!q(QC%k z2-}~smzN&BfDnOINhd}gJT=l4pocK8IK?^`;tdlC_p2{ih$~_Pd?<;UaqxDQj%DoW zchC5@O6)U6aWw`;4I?%s_N>~EJg}X2X3ywzZ1R+g)MUOF`%+RqA7~(_@89fLQ(#{@ zyM*<5*x*X~t#6czgNtK1)#yd=2t)C_I?- zFQ9pJv12@o2G>l?z7r)Lhr{xJFUN*BskaW0`w3Q!F|4cO^z45RIrTBJ)=a*kR5RK*v7Ez9H3upm$3ac+Ie?VX)e@zcPK+O z<-pBxDT6kTCY811A6Aobjd(Vv;6_088ozN|#g2pR4|NwHgEulWoWtLNC(Q<0u!x^k zHl2m>Xgt34GMcr3tAI4kv}xzSbTQ6!NkeskC0o2ZeV${kV}17a%fw;f6oIXR3(vp1 z_PL3x`Pe7Wn)Z9SM6@Ef8}IZm#ZUD-W;n=AHbm*^LvcH|B@1AWw&B}_^Im=WRKm~S z?;|!u?WNO9(k;7@Q@<1zY!FMegq#WT44x>YPY`rPGfTI(%ua39g-Z$Lba{~KBl#Ev z21vBb5rXFi3SF|7p;A8y<(rRl2u1p-Dt_x4wk~T0nHE$r^DAnbT0u=&)c9f#lbCi1 z^d+k6Zst_<<0mv$(L`Zpri@4@pFv=spqi+|*EK{^{HS5REH38}J{Cr-R{L|{lGV$F znjfV3Cm$j%=R5G;0YVt;%X8n5Ct*}CJtG%e_XK9W+I?J|bItJDZh`Ed&8WNLq(*_s zoh0{xO?wWiI3;U@j?;<2zU7npML@gU_Y@?x?v#*?>od<=8)jjf(1W^v(Z4bC$#7UH~mL_^yYnA#(Htq*h(2#`$(_ z#*)!f?dAjDe{lD1b-$`s~V)$hS9$*TB;1x-50k54e% zk=HRMB~=K0nOKw+{~ki=!vo9x7=eZbB`!Y#jgmc`c}RQ8^SNTy(+ zlCXBRrP~hpzKaqHiP&%KP+BkleGJ7HGaF_7>L`eE9&89%dIg$uY5BscI(`1O zTNzuMe$n9diu;$OkXS7=2qr8K^M)8teKG<$j|nzD)mSS8Hl6ff4mmanNO6Eam$5L5 zG$$3dIxbKy20gTtpa~{?)oa%`t~62>bizG?k*;N|LAeSM{ZOQzJy|di?`6F?L`FS+ zK82}Sm!?_F58(T|2Jo~UYH?1=ZEvNtW-Ui>h&hA|A=^%|^@o~p>L?0S3dT}}JD}`X zRMt|~)2d=hWiMr)G%JCn5+)kaJ^R0QDJ!?QV3x5|sTeqeO<2Oqm)wQ%Go^vMmOLFu z^+Io-PEY`!=(+~8cHUoLO@?zCdgt~x!U2}r-2>kRS`h4@mrCgi&L*PV{7L)k9E=Bkww@|sg4IXR9y#^&pGm&=bU1t8Uuq4ntFa5vO3BtCFtSk z%r2@to|h-(NQA95{7}-i3|gEp*C83AlNnpy);&4IJa{hPdbV`tuQvK79|Uq+t#p+d z1Et#=O=jY`d^3|Y8pw+xS=9pkHgrK+s>a5A_`pDKUA-4(Jn;>g30h(>eW?Z}Ex93? ziVXO>er=kYt=QL;StReng*zsP>ZX4*Y(qt3wu>_jIeT1Wr1ogW(`m1_`)hgJsL8 z32b9YHB7-<#l&l4wAZ}_^4r=gl75L8oSTB1s0r#yx38J22v1|7JyJZYGLaa$yO=kVqbx{q2A#GE8@yzPOzKQK%fi$z%x-~Bh62{`2t1I8Nc|D0n0?4 zytg1ArA=R+Jdf(7!FS;7p|3T8T(H33;z#`hrh?3{aHs;{rhv2^j||%>kBN!>NCV|( z3wrB3$~M)lQKX-jMF|ni3DawxuzF1}3)VGN_0wAKDh&fChd{{=O8&dtIh3z_{v9i3 z&ep;wBqPzeF7igTmb`5Jnf>%Evk+g;#-XL9buQS0n+^i%xGo`Xtn57}DOK@Gp;-RhUp3^ z<6^6IIl&7{fC*c_F^66u#sQpIRxB2~m!_hK^Ywa=j?&8)NmpajWi>aOIgb4aFTgoh zJH(QFjEL=$h>hZOF@Z5H2#xdh+#)RJWV)oqjwjKbjcVlbD(A@4 z%KxJDac;-{3&WaSJ>W-N$eeUE1QOxKsLbS&+7$ZFz?PxJi zzN3(3a`(qs9c&>WVC0`ko=6LR!y6#?6gyvkU>x35{(L1_z1OGmwS!~}7JQ`$D+9B^ zF$*ByRIU;WBFR>MbnF?ekT9>RfDXk^! z;t;t3AKI3|BDrOy)M+IcW?=&^H)59+XJ9CxaR&NYfoWmT1iG~^nX6$6y~YANK)L$( zA6$0AQHl~AdKALF=^uUPKU<2{D0hap^^;;l}T~CM>z z_glHZJ>7f<2JDB7nQ#DUa}1&;VUY{u88v^Ema)Cf;C$TX{f65yiD0)M@R}r_3Ey;l z4sWl$w8-`zuO*O-L!Uu=Jxz(PE&^fwES9<;a-IqMD-r(&3J(jf-1H~o#~h9lBGAD! zZWAb1`ILBZ&h@(PLUS}R(F$m~N^4NV&qHDq2c zxd)+Qre>H*Wh`IZC(^yLIEsr&l!T5)-WVX~^Th6$pCX;?Pm;q;61B9HB=U9m35$#S z0wrsM^0Vb!F*6NThM{j<^DEIW2)4MnRWgaFiIw8T?o#D*EpA#F=h;N-3D1;@7URp01 zr`F!I$BvG02v7C^4W1!01L3eC+Bv)w3!c_-4g&Fbu~@Cb;x3=GUkz+3sc6nm8!sZ9 z6;H{;#|gq?K$05=b%Rp923-O*PkOO!VDxUsJ_6Bg-$@}kpj?Wk#Uy=6co1_Ae%ZL{ zJC2X#*S=E}ox*5rwhKLG^I2WtxF?qpRkR=89sc?SZm46rh0AZ=lg^d!nf7X-scpm{ za9%U519hF?5?9eIbN41tgxdzCF*g)^g;SN9L#@@Bq%nQM|8~p_^Rk%_ViE2GB>6*6 zsKhz-g8f_FHVD4ifXf0-6S+5N`HM4ta=_vGv_+0}r(g7#i$FZdK}$?(^hRYB+dC=I zPZ13@=iFWSTg{4iNw8=#cKwPOWeq0YTqHTToc5$ci%FX5u(@j_`!5)Fd27wS7C}+^ z7Q#h8z%nFkAd^Y9E2S~>Qgf2Nbo(u}l#|zo&BmWMgJ8%h2~1vO814G!BP2q>yOgN+ zRg}W&5v9(M4p=&C)a?gfB}l|tdpkP81;-lUuxjafc=kR-43f;fh}78Lh1q3|WT z7gp3Cb|@qgBGna4TgrBI>i3x9Rti=-cWIAuG5e)zQSR5Edx(8kq(Fp~fG|dvVc*;3 z@H2=Xhp782>FBsMmdV3pXMd=etq7?$)A^fkeL3~WDUzqY5UJ)T7f~#I1+~KIu+ad- zxjrHgt^!z!>1xn>f2EwLxiT3AJ_^E-!aI*ueW-d>9r`ZDh$lho@upx*a`;wlGfIlji zHL%O_^_-#cM0=i*Np=~mx0?h0wIS%ikg_&UwjxYWq)9xoB3JMpemXd)T7`q<4^D-;)=^hU*TJvYG~xaT`n2Is61E zy4i}#Qh~u|r@{TLk6X|*nn7l!*WH|(QOO7#A!$Polcp2Z9dQQS6ZNG1t#){~B^;a7 zQ(&KI@tdS;8MepaFZqotzq@M>3Dqq&G*JGD9WUhRq<+y8Gk6$OR&+o=n6JWVc#=v{? zW7hRM`8lDNPuHBIZ~3rvql?7|118Macqfj|X^x_z0yZAVyJZ;8eXwoqn43-`4tgh! z;=ejh=`m%k`-DETW$`$XwbTBL>+D}-{QhIvZh?4;od9^@SQz%)NlOmMabq&&7RkHR zcb(J>*y4h62##fIfgw3pd>i~?-7*n&TNHLN-}6DoHSI`Q!Z^ib6Qgy2YJ$f@8`E2r z#|Rij&aaSdoJ8NTv-=cO{43Sgsl*R3EvbzVJyd!P#*)A|Hz_|;+D;KpB!M7MfYi^8 zf6*=dCtoQr7>zGidf+S^^4LzVdg7P8j3r^RsRABq`~1HL@&4>RYkdGO=`sADgLqdk z&tFbs%zueBFfw;EbvAeWx1uYdt4n4(fOv!7=XL-y>8kim?He`OdsQs@ZqQy+OI}ii zLl;zOufQ^iXREXFoL;0gUd>*jQN;52+x_WAzWv8;9{&JUMC*X5y3?7H$?=hLm;K@8w$yVVx&o?Sm0lmnZ{`} zw#m0L=(07k$()mo;mQf$rJ7e7T|8UD$lX)=&>aVhdH7&zm#M~t`CGw_Q`2blvRA5fz(GCI_xmB*~Pm$JNcLW&IRR7ma{~&ILV`1Odpwi(hnsT0hN8H+ei_ z9vuFlxH-7GUNU~HkJXjyiktJvx(OBN_pxj|a-|=2PwNdiIO5918` z`R-mxxS5#mn1hbeyYXE{#wp{v{c;?_=Zzbl|Bi5l6FB2(^fCt9yiY=#%O`%jTHCz+tT-#o94$mf3I_DsKKINy{AY~1{>mFyO8N-w`M!j!Y zHJY91!0ksAs(F;sP6e|B0uKb{M)ha}fAyK}i-7=0m3zY=Jc5qdj&HsSH<00JP49;M z{F(JDa+{G!G#;AE$R!Y1p437w#j8GXre^S5!lXY*m>n|{1JEJSIImlbsKOmpiUs>) zmk1`zF6cm;wGOW-`OAH|T#f7uShN7_rj!(@4u;gEKNmRlkULMe^HjHh8Q&Bq72D;d zfp*`;?Y;52f~5qpE?Myp7SQGwbgkT-xG2A6#`jMy07h!AJE1qbT$b<|4xTO_@J7^K z>6pk2jZZ-p-0{;sH@sfO^!2Mh z1z^8ilB2E^h%=#qWW}r|%-6L!U~3FonMvRSzy>ZZ`z4_C*RDE=nKkAmt^PsRk|s?e zQfsQZC?lFcYoUBtWeJT`AsZHGWg#`#5f15Pgc#K~;H;YP5A4b9S+%;z*>aBxiy59V zZIXCTSOa?LtYm&4@wlN=0gFn)CHd*Rd7B=~E{ElJ7M`RGO)##$Y1ElnI5|_IhotEN z*HH`H(xiEUBnAT)pmcKcM<>FauYD*^`T3xu!IKrCy`apuK*Mx__=V~QKnu>JKJ0{B zPBKD*(&L=MUg{%Q-!hc3vB>;F_^vq6(Fqg^ptQfSUo2k{F^y3LL8zC4%Zz#}-Te^F zRD6b&U}S5N{sFDdt0eD}Xf89(kLIYR<}2${ZRw6AMQz!xGw7enP1jL>94F0iWsO*%{OU zs&)L4n-?8^$hJn^Bw(XrZ(KSG(YEuN#-T0@6h{V&t`c}m5ssJ*v+va;#`bZwa&Oyu zZq95+gt<5rqMxV&4dVRdH9YA0nq#MFqm2xh11V}*<^3giRs|ZM# zP*DZjTm&*I8q2E497eTGlhEUx*%I-N)~|k@p)o0WArlnbPTI4+)aDg|oVAz?(Ro$B ztq_zz)I+9D>6f6n?xjr1Wrh;N$cm;k3JeW0EguXL57bU>>FF?3@rEYH=|S|9ZdDmd zJ*`1|E){e0J8D+}F5h4hsvY#V{V7!nkB2+Ic^BD|9MP5UAqh&mf%HaMK0XJ>pDRXb zlrgNSobHJVp~16=Cfy3mA48W2;C&OQ%r@4r@DOU3OI^MAko`zzPpc>R4Hb56UbAi~ zs?!c6P$gYky2P+Ne$_bqR0uR5BZ?IC&O4EB{5(0A68Cc=8$HwER|*Lx_s>cG!X7MF z+1exNPsb`Sz;T%?B2N@bxj0M>ILFR{BMs_x2L#-b1)Za4o%*(z&dc0y#qoENj{{NV z8nIFSM{YZ$`l?U-;9m-9QQQ*^8pI5^KY?J9T0pkSu9al*EyrkP6vRO3?}J!j$W_Hd zg7lB)@VB*p*4BPO!#ZYBR--lc)N|&+^umi6S!VTojIa4&?pIf?6nSg$L&XT2M&qQ) zwYzq0OrpNo(DN+jtkGwfY`G(pa$ZRMyw$`EiFy@}emtkjB$Ht3_-VVXZH0N^hB9i&TN#mXvP@ZT z?a=htV2J50MaHF>spVx(p@;BgQr*Fz47jj_P9~~#8w?N3MT`cnGIWN!`hw;WY6?w` z`uR*@Mz3>bOtm#M%sGx$Oy|PjoluK70|JFP%54T9U?luE~HCl~pA^vF>p+p_VJm!nW zYN^SNXCaQ>ICRUgkuP zeZ@++Ov_5`@)LI09U9Ut$@uedEn2-axG*Rjx;`Hn^HDjNE}8v(@4AJ{V6T?Xrb%Z- zpc^t?d6UE*Wpseiwx7V}i&F5z!fS5+qI2!DMC^3MdiXJ_3HU6f)qBLD*2;8(TZd$C zDqGUPsKllaCa6d*Qxz-EUM*wB3t}JjU;oFT_+xT)F$_jm^g0S(Y+NWAGfw0 zDKmL_g}?IGobmtoq0?W%tY-=x34fWeO*RPK|W+s20cF z>jVGyyuNH9YRv%#2t_Ev>M<>CS6l8JZ>ZIrTVqsI zC0yjSXi{L@qSDbVKZ|F)kix(YVaX0@40slb8brJ zFQ>d+zu84?D%$kLmpN>+k&EPZghfo)%&VB#eAv||O2tzT^X&!2w4pkR96n-IuIV_q zeJvrSPf1!PEeBRjr%IMPZW|dzEQhcoW&_g=Tf&P_b3*OT7Wveb$gnDT@m-p?AV!_w z*couiIhRR3hEKN9O_3*hY0=q6IMx#M*R67`(Jo`Wd-s@z_yK0R1vTch%Sj>?=&;|! zaWg|70XUO4Ml+ox-BWytfIXcvuu9esfzPNs;EyCZ6wvEp5UDK(5xs~pj7jJoLLM0HGb1_>fUGaMv@Z9Z_$B&CYu%o z#;cP8#aKE*Sq*#xl+jhlUagDGLMi%Y8|Bv4HwVVnCa&~qUGLJ)Spp>+oFiba$^m|1 zi@`BJxMtAHR)CNK|2fj}6eJO{IghO{E+Wxt>?z#Oe1TQfx_IwB>>HQWUTpdKlwQ>g z5_;y#P&u838*b=KU&qG^VUKD^@V@5tM&|)#1qv+npNyN@%>?Ty8iMP-Gm}+Wr$}Jv z;Yj}aJSI?cXjN7b#;C-<2!?P2jHh3uzEQ9=Wyd1q+Rk-6Sduh92Jx%haBNnL*6(R7giOzb$B zH1Alf-6n=5$H|6h5ZYG=AL$F2iYZ@e=?3xr?z73j#q9`X!rj^!`R3Itiyd%^xH8RA zt&lO44yDui5nduvse}H^t6bS?vsO5CaEo%GWCMrmQ6a;c z!LDkH+8$GW1~6nJasQ&8B22&wiGwt*m@vulI9u+Z3FJ$4VoW0+s~gOE;V1s>&vEmLx{Ze`GBG(J8LHhmj-yFNobqz$??luw^le64S% zoH>qCGFK)PaS#YUh)tLB){sv0T=VQPsy0-C%3OpMiD6W}fJrJ;H)vg6|u&iZeVbSqHI)$KqUE+d`Gyn z*6e=<9}4EDE|hS*?r+Cvh43w*Reh8UOO(@lg1d6d7ZGo*i_Kn)(hP1qvMr6q1dIhDyrtHK9G^|0`V7`eF#}}6ph_SC78gE z?u6zlZ8M^32vH? zF<}dHrwbMzlgu__+58@bcIv&f%&>R^Iixn!oA}gsYw#JEbS5Zd_+zTX56GX>%raD>b8lkl* z@y|Us{!qrWtKhEbkgxpDpbAE%BXOW(KaeMk=UUU&Ev=0nfT*~?Gyu}7g2^5zX$kptH&prU#4rr@OVaNlorYsbc?FbsFD~PwAOIiv{Ib z;DRT4MnhWz;eg?uxwyh|1zv>xfMPc?nr;s}t0j~WZVdkb`G=DfU5w`h!2aol^XFs= z`d>)X{}!fp`9BF$*8?IM=kV6eexD1~G!<7xQN5GA`&G;pnt}b&XNKJ*x#tjjq#>~g zhCvBa>+2gDl3ZC=5<#tRHa#bFjD{6_rFE^OJTH6-c_HK%0#Dd0=f$`o2G>eTvnKoFW=F{; zknS14iwO=pDMRO}@kyNo#Dvz3JC8Xjdh-|awuV2oA%pC3x3-0>DAc?m+8-(?j{Aa( z>^Vp}!ZHRL%O=xQk1U936`Ya5n$(bl(~`V`>BRcqDD>+rQ~&(sTfhVh!dKgKPg^>X z;@jvK3qpl{4s1I%P8EbeDPb`O~{QUw+*8?Y%&Bh;)>$pYxP%NF*0n4)OER}e>} z?Cf?gugBxJ?qSj)NAo*)R8kDCsGnt8x1@*|%dFLuB#L!lC*4Pjal#}LA*?4H$tY<7 z)z3=fyJl??&HG18lo-P)2ky9;p)2eh_g=s(ImwB`V7lm>;SUWV2oUHJK%|^WLc(uqPy@{jeIH83p(vUF)}?Qr{rWpVn!L(&J7)M&Z)%|;DWraY#@({NQK z92(!fA#lUs5$F)|xH^L!w3s;f_l)B?uoc%LfbmClK7OLb>h|yOvW-EM%6R!^22j5wM z$DzMPBXeb3tucK32N;5eIRtt=3aeqZH0r%*;CS^n57oT2#rIvz(T4@ zM%j6+GSOl~hM%~ySAO0V_5o|RzG$uUl>?V2gUw^H$;$CTGe-Bw9>nCP7sgSlCn_f4 z794}==}+hYMH8~s8^e^y6;G7y7wF#hDs2|GB1zp&o2xL7tgE?VvP~>G2@1TK-lOQL z79wsSP2*zanp24g$(jfc)Ak$+aSf#_;kKwf_5-*kCW=FmPTV&~9den}V^pI@8Z zG=H8Q`;3{KtY&#l!0D$b>-dh~=;$p28?LcCP0!&g$;|g%LIZ8we3e~O4q>B>yOoEj z+ht9nJ*=M0Mh`$&AnzTV`-xc^odaLbYy^#3D$51LtbE<+V0_eMqGOPU7;3-*x87KI zeLbi4Tu^Dz)ADrbe5?uq;@lXmwZ2>}UBmq`@M{9Y+ zkP8x}AIW|#7OZ@3D5^B;y%c#h#Nso!E{azSR<4j~C@ZOVU?q4Hl7CJ&ARhh`q0FPN zFHYE^v`=u{=F&9a^=+6csM_apVEt*fGWUK<^9T2nVYXA;Zg*aM7M8W(tVL#a*F-2$ z=Notu;1!K%kGUrM@NZ4n6ULs!hOg>2B*ScV`Y`wv7+@TCm(V*j$SX-C{Kv5x&eA{ zL4JBGgp?PE@W{ljdMdLR%>`T+dh_cfOUMD=ZK3N68p#o{^f2Gq%sM)zNH>;FN5z}E zs{zFGQUSVrr30JU1(Tur9Ue`=JHSsEDQ3H3~cCEGv+iXH87p(%vH2TyhAfnoP`mDVEDCz_!kJ1D~#|2Z&Xgln$G&JX?zMtGVu}GO-23JY;S|N^VLqnvXc(>K{9UQ=6eGG2lQz z+9>~g=>f!M|Fh`wKimiZzlkpY7uvswF8{Bq|2Lw`K}w@GfP57ma)EdKTq)zO>XT2L zsJ!6TQ(w~aj!KjL(N&R=!jvRNqBQq5TU$N)?6puC^g_9{CMIB=alVp%0!yrh91ygj z5{DDouNXiS^n4%#=1WUS`U-DW))*ds8-81>5mXgju^4o&JiTh90pcVRt8}SAgQ-Mi zDG$T*jIL;?)lS4w`RS~j${hFr`Apg(B!*2n2v{4WSR)Wu^-eNn_(!GQJ*s2b+t!X1vJlPRJN zf&6uDNqD~!0~UT{u9%F**Hf3uADcs|3(YGemy3$=*>#qaSe^HJkDtztDvO$}LnKDH zK4`*rn<@CQJaErtw~OM4ASfAwqDXm%Y~g`G;oirzNfRqxV1F6X4uF@m9D$fK3av*> znC+aJG^zm-+IlH1ylLX$z}qE6i2VEGi*-b_9<)= zN!6+Md~8~-OV#a~AaIJ>cU!w-Geh3eaF!h*GlA=t^4gVS#p9qS$SK;T!fu!3vzA8; zYm4(~Thg2}qAPgoi+qJ-dxTbJhpKyLl=fT=gB4b_6-P15x4pwOtjg5}ZQ`+M7yIO< z6KKN?l|=6&SCCKR#0MzldJMs`sq6-XWMAC!`~n5rV=}_1XFJ^Eh41C-*Hg*@VD9CE zQ03OE!k|ylB7Z&m+aF6x?!Lu2U5OYF?|UfEBw4V-G_?iNKWy?tW~WQAzH{)xwBrgF z=yvJ0LpjNa3PwL|=*M}%%~W;BG2d0tYpJ5+mNuT|6-~ksLfSpzrMNRmx{(><*A(|p z6Y_s+WrTF?zC8N|@5k5SM`Y5h8u zglsWFbhYwapo~gnPAlMY@{D))3iEs7>QJX&^Z`h~=>_!T|DAyIuTtlKsb2qAulxU? zdR?EWZMn*T8eEAn4R|`@HB**P*b^yP85<*^mnXC|lo~IK90Q^y z+61$vYRKt^hghCeMENu$Hdp#WT>c%C*C$Q32!%`Q0~iFAgP=H=_)ygzF02Gs96kdj zbGXg?K_nq>lHMA3b*OR>+WndlH;keh38)Rd}LJG)FiL?EYI;eN-h+P-u}lI z+aE*%Us6EY-oef8h2c39_9R#WpIZsx%mv2f+sF@(N+w{bU>5GMm79uml?t0brr^nV zPR724t|l3je&>49M16oAvIhHgov=yrVc6OEYW3h>SY?d$T^54T3MrVjw@Mka0lt^- zyS{EuRi*xtF=o;jXmb}QiC=&?2zhKdu9OOs&HX9l3)`7S80dp-ijG3*SH&cOb2Wn} z3qPE)lXwP;5QY9yZ$u^H+^E7R7bgH&H)0hoNK8kAp4^k`1W@d|UQdaORL%~qUs+?H zMT}ZBqg$^Ffn;az$iO5%S;Cif7mhi{?>MYYnmMh(j-A!dkg(jR{w4KvA!{MJw~0pc z&3bQ>piC2L6=tOksbshYFEL-t@Eu@t5D3@l!G639dKVHp^g8ElY@?EK=eI4OjUXEt zQ+_98KVsMi#~WD7*)3Vy!Um;{GhZ!DR-Z#*z`g+5s>olFC@a8!hZFwAdSPg#`=3}Z08&S0|2*0O3Ja?Z z=)V=I7BDdP29RcwTe&fB!Djl*L0H1|TbIQGlFZUENuf!M2algOA7T=WhGSRJK*8%| zoF=y23A1mns(2!WKiB3Iv%_RLpoC@IKWpoth%sqjJBJGm^F%s_6CHSDI*w~7Nu?Fh z$Qq3auZ{$oXqvB}W2Y6<*M#>O#1GOc;7Rl+#g(z`2Zu_Fg;m#y49_ghJ6l|r@;LPV z2x}1%RfscIW+gFfW6rn{E!zpSQud_*`~PG zKP*C=K39^#&;xXc#m6Lubk3@WS}>l5fLV16mT{RD1oq59MkoH^Q>Wo@)b-r}*Yjq7 z5X#h?9TmrX3|1oZX)=yD5u}Qe8Ts^!5oLe(o@HnFI;Z@)5?x_z@yMzB-TC2Z9^VYj z)Q?GiaikCNSig!JYkcF+s!xq!PNZJ+n{u0yZX*lLIVahNubcQI%Xqz#R6XZ2z$&}+ zp~j7~9IzL(laryb)wsyD+i5hwBt0zMQ`tJhT$OWK(;^4$_Nl zeg`Vta_c8FqU5;Ok3Ukue0Ae*1ND?CU^BGJU54n=ACcx@R>E4%$tXOqVY*0kuo}~v zNLgV*Q$y5?H+f;{`OLT@5$2Wz!k3~3$IHRTG2X7YR?ET<9nT77Q*FY*ZBJCh-=_h_ z_F^gG8l7>j6{438D^7L~T4Tr(Px*SKeC_$DUaJZHPU`b6q2s=MB@1fL!%PGJ+z^L) zb-mtVUqpN}>@D+YeHAqrKh>Vk3Txpm)BDrHPM5kTg7^N^!M@`OO1j(N`)!^-C5iMn zQBsWnRmZ`x@01fQrd?;8rttOh<1UA6v8G&iI<9iHAr`uOqRcyTY~z`$>Z?6m)p>lj zca3F_gXv*dk;dN8R3-` zJxr_x2|wnTReKa0Lo%AtT&y>X7p+&O$!%SAYJU_WO@HEqKbu^Q+v`%mhyE5xRQ;9Nk!DQ8Gy&yvd2Sc9QFsbSzM?grNqPO}RUryK#;=j9ElR0{3a$o9$S zXPr)y$ywXgFS?DJ2AldH`=^4R!ot~Nz}Q?KBl7EIdk>LkbUsc)mf91JiIt;nm`Iu1 zy99|7a?ShD)fODj93Z+dcy=^$BCZ19;l~x+ulRL+HWlfl$CdNrK0YFak7n&j`BlUX zz0?QwM}a_;0}AgLXlcDK_S?P%!5k54Q$?ZbG4U${t;5`bPRU2 z;_&NASMuDH{~O3wfeCXu^+x?`dojdhDvBU z;1)cxw${*WWq|1H(B>DXgy-QkW$9it6F%4-ed#xPW-u?ll4mSrA55wlPG5Q!I@61? zcMJ-S=hm`HsXzM$U(@$Gq>3xjOwEQNcNi;;%-&1;@QFO@>|9h?+AqC!3tbNST}@+X zE7Hf*DNROVViO^(^Idz!{2twJoncgLbKp;Fst+MObYlbC=z)`CoQt*bG4F%{nWVB?hcEMrjEx_R%pKH`A|{SB7DV?M;_5@`){dCO zxp7{w{iEUesg7$RpI>7hpZ;?20by}Bck=;jgDA;L@57}%g=g%*hJcm89W5$*%4tP#1`6GS*cGOqB^bB8 z>onrR6Vv;e==JB}%jocK?>8-UmF3BO>`WHf-O@K*hL-`ay)^n}3JC4Zrdb{lA2Z6O zMEWJtAe4_U65NHW4JB+vK7og0c#l22aek-8`I*;Eer>^Nsq!{At}pla#l4<+V;xod zc}FERX;WWDd9_R@z?1m$JO4pKpposFZ3IwpctZU>;{?pfKmor1eyj@qX0L(*!u-ee z-yi3Kzdud&t&M*xOwv2M!2kh&Isyd(`eX#SuB-$G1bn8qV)jqC-?9)!raYS5l?%uW zfNymJvdhu_fGhA5`x6c@N%;2}LL8sTTlZm=>IIj(a_ex_&?&O z2>p#uz#yn8s-mKOjQ|9M$_@mC^2aR{_ z62RI1_hVJ2`y2QlOO=0T6PACmi50+GKaG3zR0lAy4?toB-G5zCA^rh=F1W|{2l)RIQT-nqxh35Ef200xc99({!6gc$<671>Qnzyaf4~gj^=(XO;y*1C z0ap6g0#E^3E=-qg9&&(QwFXEWLi($Kr#}n$x6^t6tT{l`<)6m?Z6+4Lll@cH7X2^S zV!&YV-}MGO*?;t=|CwF_fDQJ?gZ+*DTNw9G?Kj-Nu<4cl#{NIb1w{)uh&?^kI60z({!Kmd zXWH7|#?A=&8~Z=LHhzMk&e+Py2|FOgQw^1-k{zm_ggYNI}us@-HZ;}4#NB(=zHLCs- z`af@&0_y#Hqx7HP|K~whz3C6||8i>m-;RQRy3PKdp%%K!v6zZh=tAtmIzSEd1bUeW zD&vtyK2Qr?**%z^AhFN|4WJ-5%1|@v$$dx`;2TZ>Ivq5W65!1W97+V$4blvkf$gsT HV;~*?_xc|v literal 0 HcmV?d00001 diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index 0014563..355716b 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -121,122 +121,6 @@ function GM:PreDrawHUD() cam.End2D() end --- former cl_voicepanels.lua -local PANEL = {} -local PlayerVoicePanels = {} - -function PANEL:Init() - self.Avatar = vgui.Create("AvatarImage", self) - self.Avatar:Dock(LEFT) - self.Avatar:SetSize(ScreenScaleH(16), ScreenScaleH(16)) - self.Color = color_transparent - self:SetSize(ScreenScaleH(16), ScreenScaleH(16)) - self:DockPadding(ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2)) - self:DockMargin(ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2), ScreenScaleH(2)) - self:Dock(BOTTOM) -end - -function PANEL:Setup(ply) - self.ply = ply - self.Avatar:SetPlayer(ply, 64) - self.Color = team.GetColor(ply:Team()) - self:InvalidateLayout() -end - -function PANEL:Paint(w, h) - if not IsValid(self.ply) then return end - - local volume = self.ply:VoiceVolume() - col = team.GetColor(self.ply:Team()) - - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawRect(0, 0, w, h) - surface.SetDrawColor(col.r, col.g, col.b, 120) - surface.DrawRect(0, 0, w, h) - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(col.r, col.g, col.b, 120) - surface.DrawRect(0, 0, math.floor(w * volume), h) - surface.SetDrawColor(0, 0, 0, 255) - surface.DrawOutlinedRect(0, 0, w * volume, h) - - draw.SimpleText(self.ply:Nick(), "RobotoHUD-12", self.Avatar:GetWide() + ScreenScale(4), h / 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) -end - -function PANEL:Think() - if self.fadeAnim then - self.fadeAnim:Run() - end -end - -function PANEL:FadeOut(anim, delta, data) - if anim.Finished then - if IsValid(PlayerVoicePanels[self.ply]) then - PlayerVoicePanels[self.ply]:Remove() - PlayerVoicePanels[self.ply] = nil - return - end - - return - end - - self:SetAlpha(255 - (255 * delta)) -end - -derma.DefineControl("VoiceNotify", "", PANEL, "DPanel") - -function GM:PlayerStartVoice(ply) - if not IsValid(g_VoicePanelList) then return end - - -- There'd be an exta one if voice_loopback is on, so remove it. - GAMEMODE:PlayerEndVoice(ply) - - if IsValid(PlayerVoicePanels[ply]) then - if PlayerVoicePanels[ply].fadeAnim then - PlayerVoicePanels[ply].fadeAnim:Stop() - PlayerVoicePanels[ply].fadeAnim = nil - end - - PlayerVoicePanels[ply]:SetAlpha(255) - return - end - - if not IsValid(ply) then return end - - local pnl = g_VoicePanelList:Add("VoiceNotify") - pnl:Setup(ply) - PlayerVoicePanels[ply] = pnl -end - -local function VoiceClean() - for k, v in pairs(PlayerVoicePanels) do - if not IsValid(k) or not k:IsPlayer() then - GAMEMODE:PlayerEndVoice(k) - end - end -end -timer.Create("VoiceClean", 10, 0, VoiceClean) - -function GM:PlayerEndVoice(ply) - if IsValid(PlayerVoicePanels[ply]) then - if PlayerVoicePanels[ply].fadeAnim then return end - - PlayerVoicePanels[ply].fadeAnim = Derma_Anim("FadeOut", PlayerVoicePanels[ply], PlayerVoicePanels[ply].FadeOut) - PlayerVoicePanels[ply].fadeAnim:Start(2) - end -end - -local function CreateVoiceVGUI() - g_VoicePanelList = vgui.Create("DPanel") - g_VoicePanelList:ParentToHUD() - g_VoicePanelList:DockMargin(0, 0, ScreenScaleH(16), ScreenScaleH(48)) -- align with ammo hud ig - g_VoicePanelList:Dock(RIGHT) - g_VoicePanelList:SetSize(ScreenScaleH(136), ScreenScaleH(96)) - g_VoicePanelList:SetPaintBackground(false) -end - -hook.Add("InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI) - --former cl_killfeed.lua local killFeedEvents = {} From 85c3c69ef952afe2a16f21d49c5a7abba3c7ac54 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Thu, 2 Oct 2025 01:21:03 +1000 Subject: [PATCH 16/25] remove custom voice hud for now --- gamemodes/ultimateph/gamemode.zip | Bin 53368 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 gamemodes/ultimateph/gamemode.zip diff --git a/gamemodes/ultimateph/gamemode.zip b/gamemodes/ultimateph/gamemode.zip deleted file mode 100644 index b3c55f2eb343af062d69ecff36b040aaafc95ff8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53368 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 HV;~*?_xc|v From 92f072c23afb462b4a228b014cc66cbd3009070b Mon Sep 17 00:00:00 2001 From: queeek180 Date: Sun, 26 Oct 2025 02:18:23 +0000 Subject: [PATCH 17/25] add player pushing, damage blacklist, USE cooldowns E to push - works on hunters, undisguised props and will break func_breakable entities damage blacklist to handle annoying brush entities killing players USE cooldowns to prevent unenterable rooms --- gamemodes/ultimateph/gamemode/sh_config.lua | 96 +++++++++++++-------- gamemodes/ultimateph/gamemode/sv_player.lua | 60 ++++++++++++- 2 files changed, 119 insertions(+), 37 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/sh_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua index 68c9049..ae633a9 100644 --- a/gamemodes/ultimateph/gamemode/sh_config.lua +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -23,48 +23,76 @@ PHOrange = Color(255, 150, 50) -- hunters PHGreen = Color(50, 170, 46) -- aim lazer PHBlue = Color(50, 150, 255) -- props +PHProps = PHBlue +PHHunters = PHOrange +PHScobTextCol = PHRed + -- misc. -CornerRadius = "4" -- set to 0 to disable rounded corners. number is in pixels relative to 480p. -ScobBackground = true -- set to false to disable scoreboard background & credits -GroupTags = false -- set to true to enable group tags. requires ULX -ActEnableAll = false -- set to true to enable all default gmod animations -NameDistance = 500 -- adjusts how close you need to be to see a player's name +PHCornerRadius = "0" -- set to 0 to disable rounded corners. number is in pixels relative to 480p. +PHScobBackground = true -- set to false to disable scoreboard background & credits +PHGroupTags = true -- set to true to enable group tags. requires ULX +PHActEnableAll = false -- set to true to enable all default gmod animations +PHNameDistance = 500 -- adjusts how close you need to be to see a player's name +PHScobText = GM.Name -GroupColors = {} -GroupColors["superadmin"] = PHRed -GroupColors["user"] = PHBlue +PHGroupColors = {} +PHGroupColors["superadmin"] = PHRed +PHGroupColors["user"] = PHBlue -GroupNames = {} -GroupNames["superadmin"] = "Super Admin" +PHGroupNames = {} +PHGroupNames["superadmin"] = "Super Admin" -GroupIcons = {} -GroupIcons["superadmin"] = "icon16/award_star_gold_1.png" +PHGroupIcons = {} +PHGroupIcons["superadmin"] = "icon16/award_star_gold_1.png" -- ulx admin commands to add to the scoreboard click menu. the first bit is the console command, the second is the name to show -ULXCommands = {} -ULXCommands["ulx ph_teamswitch"] = "Team Switch" +PHULXCommands = {} +PHULXCommands["ulx ph_teamswitch"] = "Team Switch" -- it is ideal to delete undesired options rather than setting to false. see https://wiki.facepunch.com/gmod/Enums/ACT -ActWhitelist = {} -ActWhitelist[ACT_GMOD_GESTURE_BOW] = true -ActWhitelist[ACT_GMOD_GESTURE_WAVE] = true -ActWhitelist[ACT_GMOD_GESTURE_AGREE] = true -ActWhitelist[ACT_GMOD_GESTURE_BECON] = true -ActWhitelist[ACT_GMOD_GESTURE_DISAGREE] = true -ActWhitelist[ACT_GMOD_GESTURE_TAUNT_ZOMBIE] = true -ActWhitelist[ACT_GMOD_TAUNT_LAUGH] = true -ActWhitelist[ACT_GMOD_TAUNT_CHEER] = true -ActWhitelist[ACT_GMOD_TAUNT_DANCE] = true -ActWhitelist[ACT_GMOD_TAUNT_ROBOT] = true -ActWhitelist[ACT_GMOD_TAUNT_SALUTE] = true -ActWhitelist[ACT_GMOD_TAUNT_MUSCLE] = true -ActWhitelist[ACT_GMOD_TAUNT_PERSISTENCE] = true -ActWhitelist[ACT_SIGNAL_HALT] = true -ActWhitelist[ACT_SIGNAL_GROUP] = true -ActWhitelist[ACT_SIGNAL_FORWARD] = true +PHActWhitelist = {} +PHActWhitelist[ACT_GMOD_GESTURE_BOW] = true +PHActWhitelist[ACT_GMOD_GESTURE_WAVE] = true +PHActWhitelist[ACT_GMOD_GESTURE_AGREE] = true +PHActWhitelist[ACT_GMOD_GESTURE_BECON] = true +PHActWhitelist[ACT_GMOD_GESTURE_DISAGREE] = true +PHActWhitelist[ACT_GMOD_GESTURE_TAUNT_ZOMBIE] = true +PHActWhitelist[ACT_GMOD_TAUNT_LAUGH] = true +PHActWhitelist[ACT_GMOD_TAUNT_CHEER] = true +PHActWhitelist[ACT_GMOD_TAUNT_DANCE] = true +PHActWhitelist[ACT_GMOD_TAUNT_ROBOT] = true +PHActWhitelist[ACT_GMOD_TAUNT_SALUTE] = true +PHActWhitelist[ACT_GMOD_TAUNT_MUSCLE] = true +PHActWhitelist[ACT_GMOD_TAUNT_PERSISTENCE] = true +PHActWhitelist[ACT_SIGNAL_HALT] = true +PHActWhitelist[ACT_SIGNAL_GROUP] = true +PHActWhitelist[ACT_SIGNAL_FORWARD] = true -- set which HUD elements to hide. see https://wiki.facepunch.com/gmod/HUD_Element_List -- gamemode-added elements include "PropHuntersPlayerNames", -HudBlacklist = {} -HudBlacklist["CHudVoiceSelfStatus"] = true -- you should probably leave this one alone. disabling the custom voice panel is no currently supported -HudBlacklist["CHudVoiceStatus"] = true -- same deal here +PHHudBlacklist = {} +PHHudBlacklist["CHudVoiceSelfStatus"] = true -- you should probably leave this one alone. disabling the custom voice panel is no currently supported +PHHudBlacklist["CHudVoiceStatus"] = true -- same deal here + +-- prevent players from being hurt by select map entities. see https://developer.valvesoftware.com/wiki/List_of_entities +PHDamageBlacklist = {} +PHDamageBlacklist["trigger_hurt"] = true +PHDamageBlacklist["env_fire"] = true +PHDamageBlacklist["func_door"] = true + +-- some ON_USE entities can be spammed to the point nobody can enter! prevent it here +PHAntiExploit = {} +PHAntiExploit["func_door"] = true +PHAntiExploit["func_door_rotating"] = true +PHAntiExploit["prop_door_rotating"] = true + +-- sounds in this table will be played at random when pushing players +PHPushSounds = {} +PHPushSounds[1] = "physics/body/body_medium_impact_hard1.wav" +PHPushSounds[2] = "physics/body/body_medium_impact_hard2.wav" +PHPushSounds[3] = "physics/body/body_medium_impact_hard3.wav" +PHPushSounds[4] = "physics/body/body_medium_impact_hard5.wav" +PHPushSounds[5] = "physics/body/body_medium_impact_hard6.wav" +PHPushSounds[6] = "physics/body/body_medium_impact_soft5.wav" +PHPushSounds[7] = "physics/body/body_medium_impact_soft6.wav" +PHPushSounds[8] = "physics/body/body_medium_impact_soft7.wav" diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index f49f0c3..2716f9f 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -595,11 +595,65 @@ function GM:PlayerDeath(ply, inflictor, attacker) end function GM:KeyPress(ply, key) - if not ply:Alive() or key ~= IN_ATTACK then + if not ply:Alive() then return end - self:PlayerDisguise(ply) + if key == IN_ATTACK then + self:PlayerDisguise(ply) + return + end + + -- the following has been heavily modified from Push Mod by Mr Ibizza/Renard/Jordie. + if key == IN_USE then + ply.PHLastPush = ply.PHLastPush or 0 + local target = ply:GetEyeTrace().Entity + + if ply.PHLastPush + 0.2 > CurTime() then + return + end + + if (ply and not ply:IsValid()) and (target and not target:IsValid()) then + return + end + + if not ply:IsPlayer() then + return + end + + if ply:GetPos():Distance(target:GetPos()) <= 100 then + if target:GetClass() == "func_breakable" or target:GetClass() == "func_breakable_surf" then + target:Fire("SetHealth", 0) + ply:ViewPunch(Angle(math.random(-5, 5), math.random(-5, 5), 0)) + + elseif target:IsPlayer() and (target:Alive() or target:GetMoveType() == MOVETYPE_WALK or not target:IsDisguised()) then + local velAng = ply:EyeAngles():Forward() + target:EmitSound(PHPushSounds[math.random(#PHPushSounds)], 100, 100) + target:SetVelocity(velAng * 500) + target:ViewPunch(Angle(math.random( -30, 30 ), math.random( -30, 30 ), 0)) + end + end + ply.PHLastPush = CurTime() + end +end + +function GM:PlayerUse(ply, ent) + ply.PHLastUse = ply.PHLastUse or 0 + + if IsValid(ent) and PHAntiExploit[ent:GetClass()] and ply.PHLastUse + 0.5 > CurTime() then + return false + end + + ply.PHLastUse = CurTime() + return true +end + +function GM:PlayerShouldTakeDamage(ply, attacker) + if IsValid(attacker) and PHDamageBlacklist[attacker:GetClass()] then + return false + end + + return true end function GM:PlayerSwitchFlashlight(ply) @@ -625,7 +679,7 @@ function GM:StartCommand(ply, cmd) end function GM:PlayerShouldTaunt(ply, actid) - if not ActWhitelist[actid] and not ActEnableAll then + if not PHActWhitelist[actid] and not PHActEnableAll then return false end From 195977c4ebbeca29caa6a5e30fdc823c2333043d Mon Sep 17 00:00:00 2001 From: queeek180 Date: Thu, 30 Oct 2025 06:35:52 +0000 Subject: [PATCH 18/25] random deathsound support, better canseeplayerchat logic the original support for random deathsounds seemed unused/broken. the original canseeplayerchat logic was overcomplicated --- gamemodes/ultimateph/gamemode/sh_config.lua | 5 + gamemodes/ultimateph/gamemode/sv_player.lua | 411 ++++++++------------ 2 files changed, 176 insertions(+), 240 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/sh_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua index ae633a9..d29653c 100644 --- a/gamemodes/ultimateph/gamemode/sh_config.lua +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -86,6 +86,11 @@ PHAntiExploit["func_door"] = true PHAntiExploit["func_door_rotating"] = true PHAntiExploit["prop_door_rotating"] = true +-- it seems like the already existing random sounds didnt actually work? so for now, here. only for disguised props +PHDeathSounds = {} +PHDeathSounds[1] = "ambient/voices/f_scream1.wav" +PHDeathSounds[2] = "ambient/voices/m_scream1.wav" + -- sounds in this table will be played at random when pushing players PHPushSounds = {} PHPushSounds[1] = "physics/body/body_medium_impact_hard1.wav" diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index 2716f9f..afb5378 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -265,10 +265,180 @@ function GM:PlayerSetModel(ply) net.Send(ply) end -function GM:PlayerDeathSound() +function GM:PlayerDeathSound(ply) return true end +function GM:PlayerDeathThink(ply) + if self:CanRespawn(ply) then + ply:UnCSpectate() + ply:Spawn() + return + end + + self:ChooseSpectatee(ply) +end + +function GM:DoPlayerDeath(ply, attacker, dmginfo) + if ply:IsDisguised() and ply:IsProp() then + ply:EmitSound(PHDeathSounds[math.random(#PHDeathSounds)], 100, math.random(55, 155)) + end + + if ply.TauntsUsed then + for k, v in pairs(ply.TauntsUsed) do + ply:StopSound(k) + end + end + + ply.TauntsUsed = {} + ply.TauntEnd = nil + ply.AutoTauntDeadline = nil + + if ply:IsProp() then + self.LastPropDeath = ply + end + + ply:UnDisguise() + ply:Freeze(false) -- why?, *sigh* + ply:CreateRagdoll() + + local ent = ply:GetNWEntity("DeathRagdoll") + if IsValid(ent) then + ply:CSpectate(OBS_MODE_CHASE, ent) + end + + ply:AddDeaths(1) + + if IsValid(attacker) and attacker:IsPlayer() and attacker ~= ply then + attacker:AddFrags(1) + + if attacker:IsHunter() and ply:IsProp() then + attacker.HunterKills = (attacker.HunterKills || 0) + 1 + end + + if self.FirstHunterKill == nil then + self.FirstHunterKill = attacker + end + end + + self:AddKillFeed(ply, attacker, dmginfo) +end + +function GM:PlayerDeath(ply, inflictor, attacker) + ply.NextSpawnTime = CurTime() + 1 + ply.DeathTime = CurTime() + + -- time until player can spectate another player + ply.SpectateTime = CurTime() + 2 + + -- inform the client (currently only for scob updating) + net.Start("PlayerDeath") + net.WriteEntity(ply) + net.Broadcast() +end + +function GM:KeyPress(ply, key) + if not ply:Alive() then + return + end + + if key == IN_ATTACK then + self:PlayerDisguise(ply) + return + end + + -- the following has been heavily modified from Push Mod by Mr Ibizza/Renard/Jordie. + if key == IN_USE then + ply.PHLastPush = ply.PHLastPush or 0 + local target = ply:GetEyeTrace().Entity + + if ply.PHLastPush + 0.2 > CurTime() then + return + end + + if (ply and not ply:IsValid()) and (target and not target:IsValid()) then + return + end + + if not ply:IsPlayer() then + return + end + + if ply:GetPos():Distance(target:GetPos()) <= 100 then + if target:GetClass() == "func_breakable" or target:GetClass() == "func_breakable_surf" then + target:Fire("SetHealth", 0) + ply:ViewPunch(Angle(math.random(-5, 5), math.random(-5, 5), 0)) + + elseif target:IsPlayer() and (target:Alive() or target:GetMoveType() == MOVETYPE_WALK or not target:IsDisguised()) then + local velAng = ply:EyeAngles():Forward() + target:EmitSound(PHPushSounds[math.random(#PHPushSounds)], 100, 100) + target:SetVelocity(velAng * 500) + target:ViewPunch(Angle(math.random( -30, 30 ), math.random( -30, 30 ), 0)) + end + end + ply.PHLastPush = CurTime() + end +end + +function GM:PlayerUse(ply, ent) + ply.PHLastUse = ply.PHLastUse or 0 + + if IsValid(ent) and PHAntiExploit[ent:GetClass()] and ply.PHLastUse + 0.5 > CurTime() then + return false + end + + ply.PHLastUse = CurTime() + return true +end + +function GM:PlayerShouldTakeDamage(ply, attacker) + if IsValid(attacker) and PHDamageBlacklist[attacker:GetClass()] then + return false + end + + return true +end + +function GM:PlayerSwitchFlashlight(ply) + if ply:IsDisguised() then + return false + end + + return true +end + +function GM:PlayerShouldTaunt(ply, actid) + if not PHActWhitelist[actid] and not PHActEnableAll then + return false + end + + return true +end + +function GM:PlayerCanPickupWeapon(ply, wep) + if ply:IsProp() then + return false + end + + return true +end + +function GM:PlayerCanSeePlayersChat(text, teamOnly, listener, speaker) + if (GetConVar("sv_alltalk"):GetInt() < 2 or teamOnly) and listener:Team() ~= speaker:Team() then + return false + end + + return true +end + +function GM:StartCommand(ply, cmd) + if ply:IsBot() then + cmd:SetForwardMove(0) + cmd:SetSideMove(0) + cmd:SetViewAngles(Angle(0, 0, 0)) + end +end + -- This is only a shallow copy. local function mergeTables(...) local args = {...} @@ -490,242 +660,3 @@ end ----------------------------------- ----------------------------------- ----------------------------------- - -function GM:PlayerDeathThink(ply) - if self:CanRespawn(ply) then - ply:UnCSpectate() - ply:Spawn() - else - self:ChooseSpectatee(ply) - end -end - -local defaultDeathsound = Sound("ambient/voices/f_scream1.wav") -local deathsoundsFile = file.Read(GM.Folder .. "/ph_deathsounds.txt", "GAME") || "" -local deathsounds = util.KeyValuesToTable(deathsoundsFile, true, true) - -for _, v in pairs(deathsounds) do - if type(v) == "string" then - resource.AddFile(Sound(v)) - continue - end - - for _, s in ipairs(v) do - resource.AddFile(Sound(s)) - end -end - -local function chooseDeathsound(key) - local ds = deathsounds[key] - if !ds then return nil end - if type(ds) == "string" then return ds end - if #ds == 0 then return nil end - return table.Random(ds) -end - -local function randomDeathsound(ply) - return chooseDeathsound(ply:SteamID()) || chooseDeathsound("default") || defaultDeathsound -end - -function GM:DoPlayerDeath(ply, attacker, dmginfo) - if ply:IsDisguised() && ply:IsProp() then - ply:EmitSound(randomDeathsound(ply)) - end - - if ply.TauntsUsed then - for k, v in pairs(ply.TauntsUsed) do - ply:StopSound(k) - end - end - - ply.TauntsUsed = {} - ply.TauntEnd = nil - ply.AutoTauntDeadline = nil - - -- are they a prop - if ply:IsProp() then - -- set the last death award - self.LastPropDeath = ply - end - - ply:UnDisguise() - ply:Freeze(false) -- why?, *sigh* - ply:CreateRagdoll() - - local ent = ply:GetNWEntity("DeathRagdoll") - if IsValid(ent) then - ply:CSpectate(OBS_MODE_CHASE, ent) - end - - ply:AddDeaths(1) - - if IsValid(attacker) && attacker:IsPlayer() then - if attacker == ply then - attacker:AddFrags(-1) - else - attacker:AddFrags(1) - - -- did a hunter kill a prop - if attacker:IsHunter() && ply:IsProp() then - -- increase their round kills - attacker.HunterKills = (attacker.HunterKills || 0) + 1 - - -- set the first hunter kill award - if self.FirstHunterKill == nil then - self.FirstHunterKill = attacker - end - end - end - end - - self:AddKillFeed(ply, attacker, dmginfo) -end - -function GM:PlayerDeath(ply, inflictor, attacker) - ply.NextSpawnTime = CurTime() + 1 - ply.DeathTime = CurTime() - - -- time until player can spectate another player - ply.SpectateTime = CurTime() + 2 - - -- inform the client (currently only for scob updating) - net.Start("PlayerDeath") - net.WriteEntity(ply) - net.Broadcast() -end - -function GM:KeyPress(ply, key) - if not ply:Alive() then - return - end - - if key == IN_ATTACK then - self:PlayerDisguise(ply) - return - end - - -- the following has been heavily modified from Push Mod by Mr Ibizza/Renard/Jordie. - if key == IN_USE then - ply.PHLastPush = ply.PHLastPush or 0 - local target = ply:GetEyeTrace().Entity - - if ply.PHLastPush + 0.2 > CurTime() then - return - end - - if (ply and not ply:IsValid()) and (target and not target:IsValid()) then - return - end - - if not ply:IsPlayer() then - return - end - - if ply:GetPos():Distance(target:GetPos()) <= 100 then - if target:GetClass() == "func_breakable" or target:GetClass() == "func_breakable_surf" then - target:Fire("SetHealth", 0) - ply:ViewPunch(Angle(math.random(-5, 5), math.random(-5, 5), 0)) - - elseif target:IsPlayer() and (target:Alive() or target:GetMoveType() == MOVETYPE_WALK or not target:IsDisguised()) then - local velAng = ply:EyeAngles():Forward() - target:EmitSound(PHPushSounds[math.random(#PHPushSounds)], 100, 100) - target:SetVelocity(velAng * 500) - target:ViewPunch(Angle(math.random( -30, 30 ), math.random( -30, 30 ), 0)) - end - end - ply.PHLastPush = CurTime() - end -end - -function GM:PlayerUse(ply, ent) - ply.PHLastUse = ply.PHLastUse or 0 - - if IsValid(ent) and PHAntiExploit[ent:GetClass()] and ply.PHLastUse + 0.5 > CurTime() then - return false - end - - ply.PHLastUse = CurTime() - return true -end - -function GM:PlayerShouldTakeDamage(ply, attacker) - if IsValid(attacker) and PHDamageBlacklist[attacker:GetClass()] then - return false - end - - return true -end - -function GM:PlayerSwitchFlashlight(ply) - if ply:IsDisguised() then - return false - end - - return true -end - -function GM:PlayerCanSeePlayersChat(text, teamOnly, listener, speaker) - if !IsValid(speaker) then return false end - local canhear = self:PlayerCanHearChatVoice(listener, speaker, "chat", teamOnly) - return canhear -end - -function GM:StartCommand(ply, cmd) - if ply:IsBot() then - cmd:SetForwardMove(0) - cmd:SetSideMove(0) - cmd:SetViewAngles(Angle(0, 0, 0)) - end -end - -function GM:PlayerShouldTaunt(ply, actid) - if not PHActWhitelist[actid] and not PHActEnableAll then - return false - end - - return true -end - -local sv_alltalk = GetConVar("sv_alltalk") -function GM:PlayerCanHearPlayersVoice(listener, talker) - if !IsValid(talker) then return false end - return self:PlayerCanHearChatVoice(listener, talker, "voice") -end - -function GM:PlayerCanHearChatVoice(listener, talker, typ, teamOnly) - if typ == "chat" && teamOnly then - if listener:Team() != talker:Team() then - return false - end - end - - if sv_alltalk:GetInt() >= 2 then -- sv_alltalk is not a bool. 0 = team only with poximity, 1 = team only without proximity, 2 = everybody with proxitiy, 3 = everybody without proximity - return true - end - - if self:GetGameState() == ROUND_POST || self:GetGameState() == ROUND_WAIT then - return true - end - - -- spectators and dead players can hear everyone - if listener:IsSpectator() || !listener:Alive() then - return true - end - - -- if the player is dead or a spectator we can't hear them - if !talker:Alive() || talker:IsSpectator() then - return false - end - - return true -end - -function GM:PlayerCanPickupWeapon(ply, wep) - if IsValid(wep) then - if ply:IsProp() then - return false - end - end - - return true -end From 7e5ad751812476d021324eadbf3738313a4135ba Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 10 Apr 2026 00:14:53 +0000 Subject: [PATCH 19/25] check legacy taunt paths also fix some ui error --- gamemodes/ultimateph/gamemode/cl_hud.lua | 2 +- gamemodes/ultimateph/gamemode/cl_taunt.lua | 4 ++-- gamemodes/ultimateph/gamemode/sh_taunt.lua | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index 355716b..fffb249 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -4,7 +4,7 @@ function GM:HUDPaint() end function GM:HUDShouldDraw(name) - if HudBlacklist[name] then + if PHHudBlacklist[name] then return end diff --git a/gamemodes/ultimateph/gamemode/cl_taunt.lua b/gamemodes/ultimateph/gamemode/cl_taunt.lua index 4f07b12..94418d0 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -81,7 +81,7 @@ local function addCat(clist, name, taunts, mlist) colMul(colt, 1.2) end - draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, col, true, false, true, false) + draw.RoundedBoxEx(ScreenScaleH(PHCornerRadius), 0, 0, w, h, col, true, false, true, false) draw.SimpleText(dname, "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end @@ -204,7 +204,7 @@ local function openTauntMenu() colMul(colt, 1.2) end - draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, col, true, true, true, true) + draw.RoundedBoxEx(ScreenScaleH(PHCornerRadius), 0, 0, w, h, col, true, true, true, true) draw.SimpleText("Random", "RobotoHUD-15", w / 2, h / 2, colt, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index de443e5..3d77c21 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -160,6 +160,7 @@ end function GM:LoadTaunts() loadTaunts((GM || GAMEMODE).Folder:sub(11) .. "/gamemode/taunts/") loadTaunts("ultimateph/taunts/") + loadTaunts("prophunters/taunts/") end GM:LoadTaunts() From d97229254ad936e9a03f1e867cab293e5a9af49a Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 10 Apr 2026 00:24:14 +0000 Subject: [PATCH 20/25] more ui error fixed --- gamemodes/ultimateph/gamemode/cl_hud.lua | 2 +- gamemodes/ultimateph/gamemode/cl_scoreboard.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/cl_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index fffb249..5f4128f 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -26,7 +26,7 @@ function GM:DrawGameHUD() local eyeTrace = ply:GetEyeTraceNoCursor() if GAMEMODE:HUDShouldDraw("PropHuntersPlayerNames") then -- draw names - if IsValid(eyeTrace.Entity) and eyeTrace.Entity:IsPlayer() and eyeTrace.HitPos:Distance(eyeTrace.StartPos) < NameDistance then + if IsValid(eyeTrace.Entity) and eyeTrace.Entity:IsPlayer() and eyeTrace.HitPos:Distance(eyeTrace.StartPos) < PHNameDistance then -- hunters can only see their teams names if not ply:IsHunter() or ply:Team() == eyeTrace.Entity:Team() then self.LastLooked = eyeTrace.Entity diff --git a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index 9ae4446..eefe68e 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -89,7 +89,7 @@ function PlayerList(parent, pteam, dock) end if LocalPlayer():IsAdmin() then - for cmd, cmdname in pairs(ULXCommands) do + for cmd, cmdname in pairs(PHULXCommands) do PlayerActions:AddOption(cmdname, function() LocalPlayer():ConCommand(cmd.." "..ply:Nick()) end) @@ -183,7 +183,7 @@ function TeamHeader(parent, pteam, dock, text) function TeamHeader:Paint(w, h) surface.SetDrawColor(PHScobDarker) - draw.RoundedBoxEx(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDarker, true, true, false, false) + draw.RoundedBoxEx(ScreenScaleH(PHCornerRadius), 0, 0, w, h, PHScobDarker, true, true, false, false) draw.SimpleText(team.GetName(pteam), "RobotoHUD-18", ScreenScaleH(2), h / 2, team.GetColor(pteam), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end @@ -197,7 +197,7 @@ function SpectatorList(parent, pteam, dock) function SpectatorListBG:Paint(w, h) surface.SetFont("RobotoHUD-18") - draw.RoundedBox(ScreenScaleH(CornerRadius), 0, 0, w, h, PHScobDark) + draw.RoundedBox(ScreenScaleH(PHCornerRadius), 0, 0, w, h, PHScobDark) end JoinTeam(SpectatorListBG, TEAM_SPEC, LEFT, "Spectate") From 2a2b40618b5128c2ed7b58a435e7881afbb7916e Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 10 Apr 2026 02:27:46 +0000 Subject: [PATCH 21/25] Add files via upload --- gamemodes/ultimateph/gamemode/sh_taunt.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index 3d77c21..ddbd478 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -69,6 +69,7 @@ end -- display name, table of sound files, team (name or id), sex (nil for both), table of category ids, [duration in seconds] local function addTaunt(name, snd, pteam, sex, cats, duration, allowedModels) + print(snd) if !name || type(name) != "string" then return end if type(snd) != "table" then snd = {tostring(snd)} end if #snd == 0 then error("No sounds for " .. name) return end @@ -183,9 +184,9 @@ if SERVER then function PlayerMeta:EmitTaunt(filename, durationOverride) local duration = SoundDuration(filename) - if filename:match("%.mp3$") then - duration = durationOverride || 1 - end +-- if filename:match("%.mp3$") then +-- duration = durationOverride || 1 +-- end local sndName = FilenameToSoundname(filename) From 352c255e8ee19bd26d35d5ff08595912110cbc12 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 10 Apr 2026 02:30:01 +0000 Subject: [PATCH 22/25] disable mp3 durationOverride + add auto-discover mp3 issue fixed? auto-discover jank --- .../ultimateph/gamemode/taunts/auto-phe.lua | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 gamemodes/ultimateph/gamemode/taunts/auto-phe.lua diff --git a/gamemodes/ultimateph/gamemode/taunts/auto-phe.lua b/gamemodes/ultimateph/gamemode/taunts/auto-phe.lua new file mode 100644 index 0000000..35686a0 --- /dev/null +++ b/gamemodes/ultimateph/gamemode/taunts/auto-phe.lua @@ -0,0 +1,25 @@ +local hfiles = file.Find("sound/taunts/hunters/*", "THIRDPARTY") +local pfiles = file.Find("sound/taunts/props/*", "THIRDPARTY") + +for i=1, #hfiles do + addTaunt(hfiles[i], "taunts/hunters/" .. hfiles[i], "hunters", nil, {"Auto Hunters"}) +end + +for i=1, #pfiles do + addTaunt(pfiles[i], "taunts/hunters/" .. pfiles[i], "props", nil, {"Auto Props"}) +end + +local files, paths = file.Find("sound/taunts/*", "THIRDPARTY") + +for i=1, #paths do + local ahfiles = file.Find("sound/taunts/" .. paths[1] .. "/" .. "hunters" .. "/*", "THIRDPARTY") + local apfiles = file.Find("sound/taunts/" .. paths[1] .. "/" .. "props" .. "/*", "THIRDPARTY") + + for i=1, #ahfiles do + addTaunt(ahfiles[i], "taunts/" .. paths[1] .. "/" .. "hunters" .. "/" .. ahfiles[i], "hunters", nil, {"Auto Hunters"}) + end + + for i=1, #apfiles do + addTaunt(apfiles[i], "taunts/" .. paths[1] .. "/" .. "props" .. "/" .. apfiles[i], "props", nil, {"Auto Props"}) + end +end From 580e38b477be464577be32975936dcae1c04b91b Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 10 Apr 2026 03:03:52 +0000 Subject: [PATCH 23/25] add option to make props become hunters on death --- gamemodes/ultimateph/gamemode/sh_init.lua | 1 + gamemodes/ultimateph/gamemode/sh_taunt.lua | 1 - gamemodes/ultimateph/gamemode/sv_player.lua | 5 +++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gamemodes/ultimateph/gamemode/sh_init.lua b/gamemodes/ultimateph/gamemode/sh_init.lua index 238ace2..956547c 100644 --- a/gamemodes/ultimateph/gamemode/sh_init.lua +++ b/gamemodes/ultimateph/gamemode/sh_init.lua @@ -122,6 +122,7 @@ GM.PropTpose = CreateConVar("ph_props_tpose", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NO GM.PropUndisguisedThirdperson = CreateConVar("ph_props_undisguised_thirdperson", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Should props start in thirdperson") GM.AutoTeamBalance = CreateConVar("ph_auto_team_balance", 1, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Automatically balance teams") +GM.PropBecomeHunter = CreateConVar("ph_props_become_hunters", 0, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Props become a hunter on death") GM.NumberHunter = CreateConVar("ph_nb_hunter", 2, bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Set the maximum number of hunters, only works if auto team balance is disable") GM.TauntMenuPhrase = CreateConVar("ph_taunt_menu_phrase", "make annoying fart sounds", bit.bor(FCVAR_ARCHIVE, FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE), "Phrase shown at the top of the taunt menu") diff --git a/gamemodes/ultimateph/gamemode/sh_taunt.lua b/gamemodes/ultimateph/gamemode/sh_taunt.lua index ddbd478..10f4976 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -69,7 +69,6 @@ end -- display name, table of sound files, team (name or id), sex (nil for both), table of category ids, [duration in seconds] local function addTaunt(name, snd, pteam, sex, cats, duration, allowedModels) - print(snd) if !name || type(name) != "string" then return end if type(snd) != "table" then snd = {tostring(snd)} end if #snd == 0 then error("No sounds for " .. name) return end diff --git a/gamemodes/ultimateph/gamemode/sv_player.lua b/gamemodes/ultimateph/gamemode/sv_player.lua index afb5378..7cfb4eb 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -322,6 +322,11 @@ function GM:DoPlayerDeath(ply, attacker, dmginfo) end self:AddKillFeed(ply, attacker, dmginfo) + + if GAMEMODE.PropBecomeHunter:GetBool() and ply:IsProp() and self:GetGameState() == ROUND_SEEK then + ply:SetTeam(TEAM_HUNTER) + timer.Simple(1, function() ply:Spawn() end) + end end function GM:PlayerDeath(ply, inflictor, attacker) From 18616356a797c5b09b4f0e4ce69b4e4d70a59038 Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 10 Apr 2026 03:04:41 +0000 Subject: [PATCH 24/25] Add files via upload --- gamemodes/ultimateph/ultimateph.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gamemodes/ultimateph/ultimateph.txt b/gamemodes/ultimateph/ultimateph.txt index 22fa2cf..cc73209 100644 --- a/gamemodes/ultimateph/ultimateph.txt +++ b/gamemodes/ultimateph/ultimateph.txt @@ -424,5 +424,13 @@ "type" "Numeric" "default" "160" } + 26 + { + "name" "ph_props_become_hunters" + "text" "Props Become Hunters On Death" + "help" "Props will revive as a hunter on death, recommended with only a single hunter" + "type" "CheckBox" + "default" "0" + } } } From 827fb810f273f3f4da48ee7ac3a791239d358fad Mon Sep 17 00:00:00 2001 From: queeek180 Date: Fri, 10 Apr 2026 03:05:12 +0000 Subject: [PATCH 25/25] Add files via upload --- lua/ulx/modules/sh/ultimateph.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/ulx/modules/sh/ultimateph.lua b/lua/ulx/modules/sh/ultimateph.lua index 52b88e6..7ceaf74 100644 --- a/lua/ulx/modules/sh/ultimateph.lua +++ b/lua/ulx/modules/sh/ultimateph.lua @@ -125,6 +125,11 @@ commandToUlx("ph_props_tpose", function(c) c:help("Should a prop be fully animated or in T pose") end) +commandToUlx("ph_props_become_hunters", function(c) + c:addParam{ type = ULib.cmds.BoolArg, hint = "enabled", ULib.cmds.optional } + c:help("Should a prop respawn as a hunter on death, recommended with single hunter") +end) + commandToUlx("ph_props_undisguised_thirdperson", function(c) c:addParam{ type = ULib.cmds.BoolArg, hint = "enabled", ULib.cmds.optional } c:help("Should props be in thirdperson when undisguised")