diff --git a/gamemodes/ultimateph/gamemode/cl_aimlaser.lua b/gamemodes/ultimateph/gamemode/cl_aimlaser.lua deleted file mode 100644 index 2c0082e..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 \ No newline at end of file diff --git a/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua b/gamemodes/ultimateph/gamemode/cl_bannedmodels.lua deleted file mode 100644 index a300f29..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 712fd7c..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 5d5d742..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_endroundboard.lua b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua index ddc4ba9..47b4460 100644 --- a/gamemodes/ultimateph/gamemode/cl_endroundboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_endroundboard.lua @@ -1,304 +1,318 @@ -local menu - -local function createEndRoundMenu() - menu = vgui.Create("DFrame") - menu:SetSize(ScrW() * 0.4, ScrH() * 0.6) - menu:Center() - menu:MakePopup() - menu:SetMouseInputEnabled(true) - menu:SetKeyboardInputEnabled(false) - menu:SetDeleteOnClose(false) - - 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(0, 0, 0, 230) - 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.DrawRect(0, 0, w, 22) - - -- Light grey background on lower area - surface.SetDrawColor(60, 60, 60, 230) - 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(Color(150, 150, 150)) - 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(20, 20, 20, 150) - 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(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) - 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/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(20, 20, 20, 150) - 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, Color(150, 150, 150), 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 + +local function createEndRoundMenu() + menu = vgui.Create("DFrame") + menu:SetSize(ScrH() * 0.75, ScrH() * 0.75) + menu:Center() + menu:MakePopup() + menu:SetTitle("") + menu:ShowCloseButton(false) + menu:SetMouseInputEnabled(true) + menu:SetKeyboardInputEnabled(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('PHIcons-8') + 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, PHEndDarker) + 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) + -- 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 + + -- 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 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(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)) + + -- 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) + 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(PHLessWhite) + 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(PHEndDark) + 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(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), 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) + draw.DrawText(award.name, "RobotoHUD-10", 0, 0, PHWhite, 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 + + awards:AddItem(containerPanel) + end + end + + -- 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(PHEndDarker) + surface.DrawRect(0, 0, w, h) + + if GAMEMODE:GetGameState() == ROUND_POST then + local settings = GAMEMODE:GetRoundSettings() + local roundTime = settings.NextRoundTime or 30 + local time = math.max(0, roundTime - GAMEMODE:GetStateRunningTime()) + 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 + + -- 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) + local vbar = mapList:GetVBar() + vbar:SetWide(0) + + function mapList:Paint(w, h) + surface.SetDrawColor(PHEndDark) + surface.DrawRect(0, 0, w, h) + end + + local canvas = mapList:GetCanvas() + canvas:DockPadding(ScreenScaleH(8), 0, ScreenScaleH(8), 0) + + function canvas:OnChildAdded(child) + child:Dock(TOP) + child:DockMargin(0, ScreenScaleH(8), 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(PHEndDark) + surface.DrawRect(0, 0, w, h) + + if GAMEMODE:GetGameState() == ROUND_MAPVOTE then + 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 - ScreenScaleH(2), 0, PHLessWhite, TEXT_ALIGN_RIGHT) + end + end +end + +function GM:EndRoundMenuResults(res) + self:OpenEndRoundMenu() + + menu.setResultsPanelVisibility(true) + menu.setVotemapPanelVisibility(false) + menu.Results = res + menu.setPlayerAwards(res.playerAwards) + menu.setWinningTeamText(res.winningTeam) +end + +function GM:EndRoundMapVote() + self:OpenEndRoundMenu() + + 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(math.Clamp(ScreenScaleH(32), 32, 64)) + + 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 = PHLessWhite + + but.VotesScroll = 0 + but.VotesScrollDir = 1 + + function but:Paint(w, h) + if self.Hovered then + surface.SetDrawColor(PHEndGray) + surface.DrawRect(0, 0, w, h) + end + + 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", but:GetTall() / 0.8, (but:GetTall() / 16) + fg, PHLessWhite, 0) + if png then + surface.SetMaterial(png) + surface.SetDrawColor(PHWhite) + surface.DrawTexturedRect(0, 0, but:GetTall(), but:GetTall()) + else + surface.SetDrawColor(PHEndDarkest) + surface.DrawRect(0, 0, but:GetTall(), but:GetTall()) + surface.SetDrawColor(mcol) + 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") + local i = 0 + for ply, map2 in pairs(GAMEMODE.MapVotes) do + 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 > but:GetTall() then + self.VotesScroll = self.VotesScroll + FrameTime() * 14 * self.VotesScrollDir + if self.VotesScroll > i * fg2 - but:GetTall() 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 not 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) and 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 deleted file mode 100644 index cac258f..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 d16c38a..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_helpscreen.lua b/gamemodes/ultimateph/gamemode/cl_helpscreen.lua index 6ca7a37..7fe7f9c 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) @@ -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_hud.lua b/gamemodes/ultimateph/gamemode/cl_hud.lua index b7aa8b0..5f4128f 100644 --- a/gamemodes/ultimateph/gamemode/cl_hud.lua +++ b/gamemodes/ultimateph/gamemode/cl_hud.lua @@ -1,258 +1,269 @@ -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") - -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 == "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()) - 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(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 +function GM:HUDPaint() + self:DrawGameHUD() + self:DrawRoundTimer() +end + +function GM:HUDShouldDraw(name) + if PHHudBlacklist[name] then + return + end + + return true +end + +function GM:DrawGameHUD() + local ply = LocalPlayer() + + if self:IsCSpectating() and IsValid(self:GetCSpectatee()) and self:GetCSpectatee():IsPlayer() then + ply = self:GetCSpectatee() + end + + if ply ~= LocalPlayer() then + local col = team.GetColor(ply:Team()) + draw.SimpleText(ply:Nick(), "RobotoHUD-30", ScrW() / 2, ScrH() - ScreenScaleH(2), col, 1, 4) + end + + 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) < PHNameDistance then + -- hunters can only see their teams names + if not ply:IsHunter() or ply:Team() == eyeTrace.Entity:Team() then + self.LastLooked = eyeTrace.Entity + self.LookedFade = CurTime() + end + end + + 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.SimpleText(name, "RobotoHUD-20", ScrW() / 2, ScrH() / 1.85, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + 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 + + -- 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") + + if not disguise or not rotationLock or not tauntMenu then + 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.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.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.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.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.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 + + if self:GetGameState() == ROUND_HIDE then + local time = math.ceil(self.HidingTime:GetInt() - self:GetStateRunningTime()) + if time > 0 then + 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 + + if self:GetGameState() ~= ROUND_SEEK then + return + end + + if self:GetStateRunningTime() < 2 then + 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.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() + 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_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.SimpleText(event.attackerName .. " ", FONT, widthOffset, event.y, event.attackerColor) + else + widthOffset = widthOffset + draw.SimpleText(event.victimName .. " ", FONT, widthOffset, event.y, event.victimColor) + end + + widthOffset = widthOffset + draw.SimpleText(event.message .. (event.attackerName and " " or ""), FONT, widthOffset, event.y, event.messageColor) + + if event.attackerName then + draw.SimpleText(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 = color_white + +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 8b6057b..57d7134 100644 --- a/gamemodes/ultimateph/gamemode/cl_init.lua +++ b/gamemodes/ultimateph/gamemode/cl_init.lua @@ -1,172 +1,210 @@ -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 ), - } - - if trace.Hit then - pos = trace.HitPos - else - pos = pos - angles:Forward() * 100 - end - - return { origin=pos, angles=angles, drawviewer=true } - 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("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") +include("cl_endroundboard.lua") +include("cl_taunt.lua") +include("cl_utils.lua") + +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 , { + font = "Roboto-Bold", + size = math.Clamp( ScreenScaleH(s), s, (s * 2) ), + weight = 700, + antialias = true, + italic = false + }) + + surface.CreateFont("RobotoHUD-L" .. s , { + font = "Roboto-Regular", + size = math.Clamp( ScreenScaleH(s), s, (s * 2) ), + weight = 500, + antialias = true, + italic = false + }) +end + +for i = 5, 50, 5 do + createRoboto(i) +end +-- todo: this better +createIcons(8) +createIcons(12) +createIcons(16) +createIcons(24) +createIcons(32) +createIcons(64) +createRoboto(8) +createRoboto(12) +createRoboto(14) +createRoboto(16) +createRoboto(18) +createRoboto(24) + +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 deleted file mode 100644 index 1c85347..0000000 --- a/gamemodes/ultimateph/gamemode/cl_killfeed.lua +++ /dev/null @@ -1,116 +0,0 @@ -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 deleted file mode 100644 index 240fb53..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 b077741..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 ad304a7..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_scoreboard.lua b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua index 7db08aa..eefe68e 100644 --- a/gamemodes/ultimateph/gamemode/cl_scoreboard.lua +++ b/gamemodes/ultimateph/gamemode/cl_scoreboard.lua @@ -1,337 +1,277 @@ -if GAMEMODE && IsValid(GAMEMODE.ScoreboardPanel) then - GAMEMODE.ScoreboardPanel:Remove() -end - -local menu - -surface.CreateFont("ScoreboardPlayer" , { - font = "coolvetica", - size = 32, - weight = 500, - antialias = true, - 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") - -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:SetText("") - - function but:Paint(w, h) - surface.SetDrawColor(color_black) - - 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.ShadowText(ply:Ping(), "RobotoHUD-L20", w - 4, 0, col, 2) - draw.ShadowText(ply:Nick(), "RobotoHUD-L20", s, 0, col, 0) - 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(220, 220, 220, 50) - surface.SetDrawColor(68, 68, 68, 120) - 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.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, Color(68, 68, 68, 120), true, true, false, false) - draw.ShadowText(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.ShadowText("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.ShadowText("Name", "RobotoHUD-15", 4, 0, col, 0) - draw.ShadowText("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(40, 40, 40, 230) - 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) - 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) - 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 = 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 - - draw.RoundedBox(4, 0, 0, w, h, col) - draw.ShadowText("Spectate", "RobotoHUD-15", w / 2, h / 2, colt, 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, 230) - 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() - 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" - 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 - - actions:Open() -end +scoreboard = scoreboard or {} + +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)) + menu:Center() + menu:MakePopup() + menu:SetKeyboardInputEnabled(false) + menu:SetDraggable(false) + menu:ShowCloseButton(false) + menu:SetTitle("") + menu:DockPadding(ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4), ScreenScaleH(4)) + + 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 + + -- 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) + PlayerList(menu, TEAM_HUNTER, LEFT) + PlayerList(menu, TEAM_PROP, RIGHT) + + function scoreboard:hide() + menu:Close() + end +end + +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) + 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 PlayerList(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") -- call the header early so it docks properly + + local PlayerList = vgui.Create("DListView", PlayerListBG) + surface.SetFont("RobotoHUD-L18") + PlayerList:SetSize(PlayerListBG:GetWide(), PlayerListBG:GetTall()) + PlayerList:SetDataHeight(ScreenScaleH(16)) + PlayerList:SetHeaderHeight(draw.GetFontHeight("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")) + 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(PHULXCommands) do + PlayerActions:AddOption(cmdname, function() + LocalPlayer():ConCommand(cmd.." "..ply:Nick()) + end) + end + end + + PlayerActions:Open() + end + + 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, 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 + surface.SetDrawColor(GroupColors[ply:GetUserGroup()]) + self:DrawFilledRect() + end + 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: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(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 + 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 + + 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 id, v in ipairs(PlayerList.Columns) do + function v.Header:Paint(w, h) + self:SetFont("RobotoHUD-L14") + self:SetTextColor(PHLessWhite) + end + + if id <= 2 then + v:SetTextAlign(4) + continue + end + + v:SetTextAlign(5) + 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) + TeamHeader:Dock(dock) + + function TeamHeader:Paint(w, h) + surface.SetDrawColor(PHScobDarker) + 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 + + 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-18") + draw.RoundedBox(ScreenScaleH(PHCornerRadius), 0, 0, w, h, PHScobDark) + end + + JoinTeam(SpectatorListBG, TEAM_SPEC, LEFT, "Spectate") + + local SpectatorList = vgui.Create("DHorizontalScroller", SpectatorListBG) + 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-L14") + line:SetText(ply:Nick()) + line:SetWide(line:GetTextSize() + ScreenScaleH(6)) + SpectatorList:AddPanel(line) + end +end + +function JoinTeam(parent, pteam, dock, text) + local JoinTeam = vgui.Create("DButton", parent) + JoinTeam:Dock(dock) + JoinTeam:SetText("") + surface.SetFont("RobotoHUD-18") + JoinTeam:SetWide(surface.GetTextSize(text) + ScreenScaleH(6)) + + 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.SimpleText(text, "RobotoHUD-18", w - ScreenScaleH(2), h / 2, col, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + function JoinTeam:DoClick() + RunConsoleCommand("ph_jointeam", pteam) + end +end + +function GM:ScoreboardShow() + if IsValid(PHScoreboard) then + scoreboard:hide() + end + + scoreboard:show() +end + +function GM:ScoreboardHide() + if IsValid(PHScoreboard) then + scoreboard:hide() + DermaMenu() + end +end + +-- hack to update player list w/o constantly running through loops. todo: add a proper refresh function +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) + +net.Receive("PlayerSpawn", function(ply) + scoreboard:refresh() +end) diff --git a/gamemodes/ultimateph/gamemode/cl_spectate.lua b/gamemodes/ultimateph/gamemode/cl_spectate.lua deleted file mode 100644 index 0a923b2..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 index 241a384..94418d0 100644 --- a/gamemodes/ultimateph/gamemode/cl_taunt.lua +++ b/gamemodes/ultimateph/gamemode/cl_taunt.lua @@ -1,235 +1,259 @@ -include("sh_taunt.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 - -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) - local col = Color(255, 255, 255) - 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, 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 col = Color(68, 68, 68, 160) - local colt = Color(190, 190, 190) - if !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(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(true) - menu:DockPadding(8, 8 + draw.GetFontHeight("RobotoHUD-25"), 8, 8) - - function menu:Paint(w, h) - surface.SetDrawColor(40, 40, 40, 230) - 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) - 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 col = Color(68, 68, 68, 160) - local colt = Color(190, 190, 190) - if self:IsDown() then - colMul(colt, 0.5) - elseif self:IsHovered() then - colMul(colt, 1.2) - 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(68, 68, 68, 160) - surface.DrawOutlinedRect(0, 0, w, h) - surface.SetDrawColor(55, 55, 55, 120) - 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) +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 !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.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() + 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(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 + + 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(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-8') + 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.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) + 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(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 + + 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) diff --git a/gamemodes/ultimateph/gamemode/cl_utils.lua b/gamemodes/ultimateph/gamemode/cl_utils.lua new file mode 100644 index 0000000..8a85112 --- /dev/null +++ b/gamemodes/ultimateph/gamemode/cl_utils.lua @@ -0,0 +1,105 @@ +-- 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 +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 + +-- former 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 diff --git a/gamemodes/ultimateph/gamemode/cl_voicepanels.lua b/gamemodes/ultimateph/gamemode/cl_voicepanels.lua deleted file mode 100644 index 0a51a9b..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/init.lua b/gamemodes/ultimateph/gamemode/init.lua index cc26a6f..9690edf 100644 --- a/gamemodes/ultimateph/gamemode/init.lua +++ b/gamemodes/ultimateph/gamemode/init.lua @@ -1,128 +1,139 @@ -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("sh_init.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 + +-- what? +util.AddNetworkString("clientIPE") +-- 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") +-- 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") +include("sh_bannedmodels.lua") +include("sh_taunt.lua") +include("sh_mapvote.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) + ply:ConCommand("ph_openhelpmenu") +end + +function GM:ShowSpare1(ply) + ply:ConCommand("ph_menu_taunt") +end + diff --git a/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua b/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua new file mode 100644 index 0000000..de23b0b --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_bannedmodels.lua @@ -0,0 +1,257 @@ +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. + + 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_config.lua b/gamemodes/ultimateph/gamemode/sh_config.lua new file mode 100644 index 0000000..d29653c --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_config.lua @@ -0,0 +1,103 @@ +-- 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 + +PHProps = PHBlue +PHHunters = PHOrange +PHScobTextCol = PHRed + +-- misc. +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 + +PHGroupColors = {} +PHGroupColors["superadmin"] = PHRed +PHGroupColors["user"] = PHBlue + +PHGroupNames = {} +PHGroupNames["superadmin"] = "Super Admin" + +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 +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 +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", +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 + +-- 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" +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/sh_disguise.lua b/gamemodes/ultimateph/gamemode/sh_disguise.lua index 5011a97..1f8512b 100644 --- a/gamemodes/ultimateph/gamemode/sh_disguise.lua +++ b/gamemodes/ultimateph/gamemode/sh_disguise.lua @@ -1,143 +1,399 @@ -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() - 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 +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 not GAMEMODE.PropTpose:GetBool() or not ply:GetNWBool("undisguiseRotationLock") then + return + end + + local ang = ply:GetNWAngle("undisguiseRotationLockAng") + + if ang == ply:GetRenderAngles() then + return + end + + 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(PHRed, 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 = PHGreen + local hullxy, hullz = target:GetPropSize() + if !client:CanFitHull(hullxy, hullxy, hullz) then + col = PHRed + 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(PHRed, "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 not 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(PHRed, "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 6ced0fe..956547c 100644 --- a/gamemodes/ultimateph/gamemode/sh_init.lua +++ b/gamemodes/ultimateph/gamemode/sh_init.lua @@ -1,3 +1,99 @@ +-- former shared.lua +include("sh_config.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") @@ -26,19 +122,19 @@ 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", 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") 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") 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..3a5a8fc --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_mapvote.lua @@ -0,0 +1,310 @@ +if SERVER then +-- former sv_mapvote.lua + 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..c61b56e --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sh_rounds.lua @@ -0,0 +1,385 @@ +if SERVER then + 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.CurrentRound = self.Rounds + self.StateStart = CurTime() + self:NetworkGameState() + end + + 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 + 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 + + if self.Rounds == self.RoundLimit:GetInt() then + GlobalChatMsg(PHRed, "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.CurrentRound = net.ReadUInt(7) + 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 5e731b0..10f4976 100644 --- a/gamemodes/ultimateph/gamemode/sh_taunt.lua +++ b/gamemodes/ultimateph/gamemode/sh_taunt.lua @@ -1,171 +1,304 @@ -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 - - if SERVER then - -- network the taunt - resource.AddFile("sound/" .. v) - end - 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 = {} + +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/") + loadTaunts("prophunters/taunts/") +end + +GM:LoadTaunts() + +if SERVER then +-- former sv_taunt.lua + 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) + + 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 + diff --git a/gamemodes/ultimateph/gamemode/shared.lua b/gamemodes/ultimateph/gamemode/shared.lua deleted file mode 100644 index 91ee5b6..0000000 --- a/gamemodes/ultimateph/gamemode/shared.lua +++ /dev/null @@ -1,90 +0,0 @@ -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(255, 150, 50)) -team.SetUp(TEAM_PROP, "Props", Color(50, 150, 255)) - -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) 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 deleted file mode 100644 index a3c99d6..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 277be86..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 6e652f4..0000000 --- a/gamemodes/ultimateph/gamemode/sv_disguise.lua +++ /dev/null @@ -1,153 +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: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) diff --git a/gamemodes/ultimateph/gamemode/sv_health.lua b/gamemodes/ultimateph/gamemode/sv_health.lua deleted file mode 100644 index 4210c72..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 a7bee8f..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_mapvote.lua b/gamemodes/ultimateph/gamemode/sv_mapvote.lua index f9782ec..e6ec53d 100644 --- a/gamemodes/ultimateph/gamemode/sv_mapvote.lua +++ b/gamemodes/ultimateph/gamemode/sv_mapvote.lua @@ -1,253 +1,248 @@ --- 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 +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..7cfb4eb 100644 --- a/gamemodes/ultimateph/gamemode/sv_player.lua +++ b/gamemodes/ultimateph/gamemode/sv_player.lua @@ -1,682 +1,667 @@ -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 + +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 + +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() + + -- inform the client (currently only for scob updating) + net.Start("PlayerSpawn") + net.WriteEntity(ply) + net.Broadcast() +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(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) + + 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) + 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 = {...} + 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. + +----------------------------------- +----------------------------------- +----------------------------------- diff --git a/gamemodes/ultimateph/gamemode/sv_playercolor.lua b/gamemodes/ultimateph/gamemode/sv_playercolor.lua deleted file mode 100644 index 0bc8ad6..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 1fbd298..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 a127e70..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 60ee9c8..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 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 c95b6b0..0000000 --- a/gamemodes/ultimateph/gamemode/sv_rounds.lua +++ /dev/null @@ -1,327 +0,0 @@ -include("sv_awards.lua") - -util.AddNetworkString("gamestate") -util.AddNetworkString("round_victor") -util.AddNetworkString("gamerules") - -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 - - 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 40be2a0..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 ba996d6..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 4254a50..29f63f3 100644 --- a/gamemodes/ultimateph/gamemode/sv_teams.lua +++ b/gamemodes/ultimateph/gamemode/sv_teams.lua @@ -1,87 +1,224 @@ -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)) + -- 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 +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 + +-- former sv_spectate.lua +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..fe800a4 --- /dev/null +++ b/gamemodes/ultimateph/gamemode/sv_utils.lua @@ -0,0 +1,384 @@ +-- former sv_chatmsg.lua +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 +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 977ac12..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) 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 diff --git a/gamemodes/ultimateph/ultimateph.txt b/gamemodes/ultimateph/ultimateph.txt index 6db82a1..cc73209 100644 --- a/gamemodes/ultimateph/ultimateph.txt +++ b/gamemodes/ultimateph/ultimateph.txt @@ -213,3 +213,224 @@ } +"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" + } + 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" + } + } +} diff --git a/lua/ulx/modules/sh/ultimateph.lua b/lua/ulx/modules/sh/ultimateph.lua index 085f325..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") @@ -206,3 +211,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 796e283..0000000 Binary files a/materials/husklesph/skull.png and /dev/null differ