348 lines
11 KiB
Lua
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)
|