Files
garrysmod-coop/gamemode/cl_progression.lua
T

348 lines
11 KiB
Lua

if not CLIENT then return end
local Progression = CustomCoopProgression
local shopFrame
local scoreboardFrame
local shopState = {
score = 0,
credits = 0,
equippedModel = Progression.DefaultModelID,
equippedWeapon = Progression.DefaultWeaponID,
ownedModels = {},
ownedWeapons = {}
}
surface.CreateFont("CustomCoopProgressionTitle", {
font = "Roboto",
size = 28,
weight = 700
})
surface.CreateFont("CustomCoopProgressionHeading", {
font = "Roboto",
size = 20,
weight = 700
})
surface.CreateFont("CustomCoopProgressionBody", {
font = "Roboto",
size = 17,
weight = 500
})
surface.CreateFont("CustomCoopProgressionSmall", {
font = "Roboto",
size = 15,
weight = 400
})
local colors = {
background = Color(20, 24, 29, 245),
panel = Color(31, 37, 44, 250),
panelHover = Color(39, 47, 56, 250),
accent = Color(75, 176, 255),
success = Color(92, 205, 122),
warning = Color(244, 190, 74),
text = Color(240, 244, 248),
muted = Color(170, 181, 191)
}
local function RequestShop()
net.Start("CustomCoopProgressionRequestShop")
net.SendToServer()
end
concommand.Add("coop_shop", RequestShop)
hook.Add("ShowSpare1", "CustomCoopProgressionF3", function()
RequestShop()
return true
end)
-- Some client configurations do not route F3 through ShowSpare1. This fallback
-- keeps the advertised key reliable while the server-side rate limit absorbs a
-- possible duplicate request.
local nextF3Request = 0
hook.Add("PlayerButtonDown", "CustomCoopProgressionF3Fallback", function(ply, button)
if ply ~= LocalPlayer() or button ~= KEY_F3 or RealTime() < nextF3Request then return end
nextF3Request = RealTime() + 0.5
RequestShop()
end)
local function SelectItem(itemType, itemID)
net.Start("CustomCoopProgressionSelectItem")
net.WriteString(itemType)
net.WriteString(itemID)
net.SendToServer()
end
local function AddText(parent, text, font, color)
local label = vgui.Create("DLabel", parent)
label:SetText(text)
label:SetFont(font)
label:SetTextColor(color)
label:SetWrap(true)
return label
end
local function AddItemCard(layout, itemType, item)
local isModel = itemType == "model"
local owned = isModel and shopState.ownedModels[item.id] or shopState.ownedWeapons[item.id]
local equipped = isModel and shopState.equippedModel == item.id or shopState.equippedWeapon == item.id
local card = layout:Add("DPanel")
card:SetSize(252, 292)
card.Paint = function(self, width, height)
local color = self:IsHovered() and colors.panelHover or colors.panel
draw.RoundedBox(8, 0, 0, width, height, color)
if equipped then
surface.SetDrawColor(colors.success)
surface.DrawOutlinedRect(0, 0, width, height, 2)
end
end
local previewModel = item.model
if not isModel and item.weaponClass ~= "" then
local storedWeapon = weapons.GetStored(item.weaponClass)
previewModel = storedWeapon and storedWeapon.WorldModel
end
if previewModel and previewModel ~= "" then
local preview = vgui.Create("DModelPanel", card)
preview:SetPos(8, 8)
preview:SetSize(236, 166)
preview:SetModel(previewModel)
preview:SetFOV(isModel and 30 or 38)
if isModel then
preview:SetCamPos(Vector(68, 0, 64))
preview:SetLookAt(Vector(0, 0, 64))
else
preview:SetCamPos(Vector(45, 45, 28))
preview:SetLookAt(Vector(0, 0, 0))
end
preview.LayoutEntity = function(_, entity)
if IsValid(entity) then entity:SetAngles(Angle(0, RealTime() * 18 % 360, 0)) end
end
else
local noPreview = AddText(card, itemType == "weapon" and "MAP LOADOUT" or "NO PREVIEW", "CustomCoopProgressionHeading", colors.muted)
noPreview:SetContentAlignment(5)
noPreview:SetPos(8, 8)
noPreview:SetSize(236, 166)
end
local title = AddText(card, item.name, "CustomCoopProgressionHeading", colors.text)
title:SetPos(12, 177)
title:SetSize(228, 25)
local description = AddText(card, item.description, "CustomCoopProgressionSmall", colors.muted)
description:SetPos(12, 202)
description:SetSize(228, 38)
local priceText = item.price == 0 and "FREE" or (item.price .. " credits")
local price = AddText(card, priceText, "CustomCoopProgressionBody", item.price <= shopState.credits and colors.success or colors.warning)
price:SetPos(12, 239)
price:SetSize(100, 22)
local button = vgui.Create("DButton", card)
button:SetPos(112, 244)
button:SetSize(128, 36)
button:SetFont("CustomCoopProgressionBody")
button:SetText(equipped and "Equipped" or (owned or item.price == 0) and "Equip" or "Buy")
button:SetEnabled(not equipped)
button.DoClick = function()
button:SetEnabled(false)
button:SetText("Please wait...")
SelectItem(itemType, item.id)
end
end
local function CreateCataloguePage(sheet, title, itemType, items)
local page = vgui.Create("DPanel", sheet)
page:DockPadding(10, 10, 10, 10)
page.Paint = function(_, width, height)
draw.RoundedBox(6, 0, 0, width, height, Color(24, 29, 35))
end
local scroll = vgui.Create("DScrollPanel", page)
scroll:Dock(FILL)
local layout = vgui.Create("DIconLayout", scroll)
layout:Dock(TOP)
layout:SetSpaceX(12)
layout:SetSpaceY(12)
layout:SetTall(math.ceil(#items / 3) * 304)
for _, item in ipairs(items) do
AddItemCard(layout, itemType, item)
end
sheet:AddSheet(title, page)
end
local function OpenShop()
if IsValid(shopFrame) then shopFrame:Remove() end
shopFrame = vgui.Create("DFrame")
shopFrame:SetSize(math.min(ScrW() - 40, 850), math.min(ScrH() - 40, 650))
shopFrame:Center()
shopFrame:SetTitle("")
shopFrame:ShowCloseButton(true)
shopFrame:MakePopup()
shopFrame.Paint = function(_, width, height)
draw.RoundedBox(10, 0, 0, width, height, colors.background)
draw.SimpleText("CO-OP REWARDS SHOP", "CustomCoopProgressionTitle", 18, 16, colors.text)
end
local summary = AddText(
shopFrame,
string.format(
"Lifetime Score: %d Credits: %d Rank: %s",
shopState.score,
shopState.credits,
Progression.GetRank(shopState.score)
),
"CustomCoopProgressionBody",
colors.accent
)
summary:SetPos(19, 52)
summary:SetSize(shopFrame:GetWide() - 38, 25)
local help = AddText(
shopFrame,
"Kills add to both totals. Purchases and deaths spend Credits only; Lifetime Score and rank never decrease.",
"CustomCoopProgressionSmall",
colors.muted
)
help:SetPos(19, 77)
help:SetSize(shopFrame:GetWide() - 38, 20)
local sheet = vgui.Create("DPropertySheet", shopFrame)
sheet:SetPos(14, 104)
sheet:SetSize(shopFrame:GetWide() - 28, shopFrame:GetTall() - 118)
CreateCataloguePage(sheet, "Player Models", "model", Progression.Models)
CreateCataloguePage(sheet, "Bonus Weapons", "weapon", Progression.Weapons)
end
net.Receive("CustomCoopProgressionOpenShop", function()
shopState.score = net.ReadUInt(32)
shopState.credits = net.ReadUInt(32)
shopState.equippedModel = net.ReadString()
shopState.equippedWeapon = net.ReadString()
shopState.ownedModels = { [Progression.DefaultModelID] = true }
shopState.ownedWeapons = { [Progression.DefaultWeaponID] = true }
for _ = 1, net.ReadUInt(8) do
local itemType = net.ReadString()
local itemID = net.ReadString()
if itemType == "model" then
shopState.ownedModels[itemID] = true
elseif itemType == "weapon" then
shopState.ownedWeapons[itemID] = true
end
end
OpenShop()
end)
net.Receive("CustomCoopProgressionNotice", function()
local success = net.ReadBool()
local message = net.ReadString()
notification.AddLegacy(message, success and NOTIFY_GENERIC or NOTIFY_ERROR, 4)
surface.PlaySound(success and "buttons/button15.wav" or "buttons/button10.wav")
end)
local previousScore
local gainedScore = 0
local gainExpires = 0
hook.Add("HUDPaint", "CustomCoopProgressionHUD", function()
local ply = LocalPlayer()
if not IsValid(ply) then return end
local score = ply:GetNW2Int("CustomCoopScore", -1)
if score < 0 then return end
if previousScore and score > previousScore then
gainedScore = score - previousScore
gainExpires = CurTime() + 2
surface.PlaySound("buttons/button14.wav")
end
previousScore = score
local credits = ply:GetNW2Int("CustomCoopCredits", 0)
local rank = ply:GetNW2String("CustomCoopRank", Progression.GetRank(score))
local width, height = 288, 66
local x, y = ScrW() - width - 18, 18
draw.RoundedBox(8, x, y, width, height, colors.background)
draw.SimpleText(rank, "CustomCoopProgressionHeading", x + 12, y + 9, colors.accent)
draw.SimpleText("Score " .. score, "CustomCoopProgressionBody", x + 12, y + 37, colors.text)
draw.SimpleText("Credits " .. credits, "CustomCoopProgressionBody", x + 151, y + 37, colors.text)
if CurTime() < gainExpires then
draw.SimpleText("+" .. gainedScore, "CustomCoopProgressionHeading", x - 10, y + height / 2, colors.success, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
end)
local function RefreshScoreboard(list)
if not IsValid(list) then return end
list:Clear()
local players = player.GetAll()
table.sort(players, function(left, right)
return left:GetNW2Int("CustomCoopScore", 0) > right:GetNW2Int("CustomCoopScore", 0)
end)
for _, ply in ipairs(players) do
list:AddLine(
ply:Nick(),
ply:GetNW2String("CustomCoopRank", "Newbie"),
ply:GetNW2Int("CustomCoopScore", 0),
ply:Ping()
)
end
end
hook.Add("ScoreboardShow", "CustomCoopProgressionScoreboardShow", function()
if IsValid(scoreboardFrame) then return true end
scoreboardFrame = vgui.Create("DFrame")
scoreboardFrame:SetSize(math.min(ScrW() - 60, 760), math.min(ScrH() - 80, 600))
scoreboardFrame:Center()
scoreboardFrame:SetTitle("CO-OP LIFETIME SCORES")
scoreboardFrame:ShowCloseButton(false)
scoreboardFrame:SetDraggable(false)
scoreboardFrame:SetKeyboardInputEnabled(false)
scoreboardFrame:SetMouseInputEnabled(false)
scoreboardFrame.Paint = function(_, width, height)
draw.RoundedBox(8, 0, 0, width, height, colors.background)
end
local list = vgui.Create("DListView", scoreboardFrame)
list:Dock(FILL)
list:DockMargin(10, 8, 10, 10)
list:AddColumn("Player")
list:AddColumn("Rank")
list:AddColumn("Lifetime Score")
list:AddColumn("Ping")
RefreshScoreboard(list)
scoreboardFrame.NextRefresh = RealTime() + 0.5
scoreboardFrame.Think = function(self)
if RealTime() < self.NextRefresh then return end
self.NextRefresh = RealTime() + 0.5
RefreshScoreboard(list)
end
return true
end)
hook.Add("ScoreboardHide", "CustomCoopProgressionScoreboardHide", function()
if IsValid(scoreboardFrame) then scoreboardFrame:Remove() end
scoreboardFrame = nil
return true
end)