---- Traitor equipment menu local GetTranslation = LANG.GetTranslation local GetPTranslation = LANG.GetParamTranslation local SafeTranslate = LANG.TryTranslation local lower = string.lower local equipment_sorting = CreateClientConVar("ttt_equipment_sorting", "default", true) local equipment_ascending = CreateClientConVar("ttt_equipment_ascending", "1", true) local equipment_hide_unbuyable = CreateClientConVar("ttt_equipment_hide_unbuyable", "0", true) -- Buyable weapons are loaded automatically. Buyable items are defined in -- equip_items_shd.lua local Equipment = nil function GetEquipmentForRole(role) -- need to build equipment cache? if not Equipment then -- start with all the non-weapon goodies local tbl = table.Copy(EquipmentItems) -- find buyable weapons to load info from for k, v in pairs(weapons.GetList()) do if v and v.CanBuy then local data = v.EquipMenuData or {} local base = { id = WEPS.GetClass(v), name = v.PrintName or "Unnamed", limited = v.LimitedStock, kind = v.Kind or WEAPON_NONE, slot = (v.Slot or 0) + 1, material = v.Icon or "vgui/ttt/icon_id", -- the below should be specified in EquipMenuData, in which case -- these values are overwritten type = "Type not specified", model = "models/weapons/w_bugbait.mdl", desc = "No description specified." } -- Force material to nil so that model key is used when we are -- explicitly told to do so (ie. material is false rather than nil). if data.modelicon then base.material = nil end table.Merge(base, data) -- add this buyable weapon to all relevant equipment tables for _, r in pairs(v.CanBuy) do table.insert(tbl[r], base) end end end -- mark custom items for r, is in pairs(tbl) do for _, i in pairs(is) do if i and i.id then i.custom = not table.HasValue(DefaultEquipment[r], i.id) end end end Equipment = tbl end return Equipment and Equipment[role] or {} end local function ItemIsWeapon(item) return not tonumber(item.id) end local function CanCarryWeapon(item) return LocalPlayer():CanCarryType(item.kind) end local color_bad = Color(220, 60, 60, 255) local color_good = Color(0, 200, 0, 255) -- Creates tabel of labels showing the status of ordering prerequisites local function PreqLabels(parent, x, y) local tbl = {} tbl.credits = vgui.Create("DLabel", parent) tbl.credits:SetTooltip(GetTranslation("equip_help_cost")) tbl.credits:SetPos(x, y) tbl.credits.Check = function(s, sel) local credits = LocalPlayer():GetCredits() return credits > 0, GetPTranslation("equip_cost", {num = credits}) end tbl.owned = vgui.Create("DLabel", parent) tbl.owned:SetTooltip(GetTranslation("equip_help_carry")) tbl.owned:CopyPos(tbl.credits) tbl.owned:MoveBelow(tbl.credits, y) tbl.owned.Check = function(s, sel) if ItemIsWeapon(sel) and (not CanCarryWeapon(sel)) then return false, GetPTranslation("equip_carry_slot", {slot = sel.slot}) elseif (not ItemIsWeapon(sel)) and LocalPlayer():HasEquipmentItem(sel.id) then return false, GetTranslation("equip_carry_own") else return true, GetTranslation("equip_carry") end end tbl.bought = vgui.Create("DLabel", parent) tbl.bought:SetTooltip(GetTranslation("equip_help_stock")) tbl.bought:CopyPos(tbl.owned) tbl.bought:MoveBelow(tbl.owned, y) tbl.bought.Check = function(s, sel) if sel.limited and LocalPlayer():HasBought(tostring(sel.id)) then return false, GetTranslation("equip_stock_deny") else return true, GetTranslation("equip_stock_ok") end end for k, pnl in pairs(tbl) do pnl:SetFont("TabLarge") end return function(selected) local allow = true for k, pnl in pairs(tbl) do local result, text = pnl:Check(selected) pnl:SetTextColor(result and color_good or color_bad) pnl:SetText(text) pnl:SizeToContents() allow = allow and result end return allow end end -- quick, very basic override of DPanelSelect local PANEL = {} local function DrawSelectedEquipment(pnl) surface.SetDrawColor(255, 200, 0, 255) surface.DrawOutlinedRect(0, 0, pnl:GetWide(), pnl:GetTall()) end function PANEL:SelectPanel(pnl) self.BaseClass.SelectPanel(self, pnl) if pnl then pnl.PaintOver = DrawSelectedEquipment end end vgui.Register("EquipSelect", PANEL, "DPanelSelect") local function CanOrder(item, owned_ids) local ply = LocalPlayer() if ply:GetCredits() <= 0 then return false end -- already owned if table.HasValue(owned_ids, item.id) then return false end if ItemIsWeapon(item) then -- already carrying a weapon for this slot if not CanCarryWeapon(item) then return false end elseif ply:HasEquipmentItem(tonumber(item.id)) then return false end if item.limited and ply:HasBought(tostring(item.id)) then -- already bought the item before return false end return true end local function GetOwnedEquipment() -- Determine if we already have equipment local owned_ids = {} for _, wep in ipairs(LocalPlayer():GetWeapons()) do if IsValid(wep) and wep:IsEquipment() then table.insert(owned_ids, wep:GetClass()) end end -- Stick to one value for no equipment if #owned_ids == 0 then owned_ids = nil end return owned_ids end local sort_funcs = { default = { name = "equip_sort_default", func = function(a, b) local aItem, bItem = not ItemIsWeapon(a), not ItemIsWeapon(b) if aItem or bItem then -- sort items by id if aItem and bItem then return a.id < b.id end -- keep items above weapons return aItem end return lower(SafeTranslate(a.name)) < lower(SafeTranslate(b.name)) end }, name = { name = "equip_spec_name", func = function(a, b) return lower(SafeTranslate(a.name)) < lower(SafeTranslate(b.name)) end }, slot = { name = "equip_sort_slot", func = function(a, b) local aSlot = a.slot or 0 local bSlot = b.slot or 0 -- sort items by id if aSlot == 0 and bSlot == 0 then return a.id < b.id end if aSlot == bSlot then return lower(SafeTranslate(a.name)) < lower(SafeTranslate(b.name)) end return aSlot < bSlot end } } local function SortEquipmentPanels(pnls) local sort = equipment_sorting:GetString() or "default" local sort_func = sort_funcs[sort].func local ascending = equipment_ascending:GetBool() local hide_unbuyable = equipment_hide_unbuyable:GetBool() local owned_ids = hide_unbuyable and GetOwnedEquipment() table.sort(pnls, function(a, b) local aItem, bItem = a.item, b.item if hide_unbuyable then local aBuyable, bBuyable = CanOrder(aItem, owned_ids), CanOrder(bItem, owned_ids) if aBuyable != bBuyable then return aBuyable end end local ret = sort_func(aItem, bItem) -- if table.sort is comparing an item to itself, don't mess with the result otherwise weird stuff happens if not ascending and aItem.id != bItem.id then ret = not ret end return ret end) end local ListPanel = nil local function ReSortEquipment() if not IsValid(ListPanel) then return end SortEquipmentPanels(ListPanel:GetItems()) ListPanel:InvalidateLayout() end hook.Add("TTTLanguageChanged", "TTT_ReSortEquipment", ReSortEquipment) cvars.AddChangeCallback("ttt_equipment_sorting", ReSortEquipment) cvars.AddChangeCallback("ttt_equipment_ascending", ReSortEquipment) local color_darkened = Color(255,255,255, 80) -- TODO: make set of global role colour defs, these are same as wepswitch local color_slot = { [ROLE_TRAITOR] = Color(180, 50, 40, 255), [ROLE_DETECTIVE] = Color(50, 60, 180, 255) } local fieldstbl = {"name", "type", "desc"} local eqframe = nil local function TraitorMenuPopup() local ply = LocalPlayer() if not IsValid(ply) or not ply:IsActiveSpecial() then return end -- Close any existing traitor menu if eqframe and IsValid(eqframe) then eqframe:Close() end local credits = ply:GetCredits() local can_order = credits > 0 local dframe = vgui.Create("DFrame") local w, h = 570, 412 dframe:SetSize(w, h) dframe:Center() dframe:SetTitle(GetTranslation("equip_title")) dframe:SetVisible(true) dframe:ShowCloseButton(true) dframe:SetMouseInputEnabled(true) dframe:SetDeleteOnClose(true) local m = 5 local dsheet = vgui.Create("DPropertySheet", dframe) -- Add a callback when switching tabs local oldfunc = dsheet.SetActiveTab dsheet.SetActiveTab = function(self, new) if self.m_pActiveTab != new and self.OnTabChanged then self:OnTabChanged(self.m_pActiveTab, new) end oldfunc(self, new) end dsheet:SetPos(0,0) dsheet:StretchToParent(m,m + 25,m,m) local padding = dsheet:GetPadding() local dequip = vgui.Create("DPanel", dsheet) dequip:SetPaintBackground(false) dequip:StretchToParent(padding,padding,padding,padding) local sw, sh = 195, 16 local dsort = vgui.Create("DPanel", dequip) dsort:SetPos(m, 0) dsort:SetSize(sw, sh) dsort:SetPaintBackground(false) local dsortlbl = vgui.Create("DLabel", dsort) dsortlbl:SetFont("DermaDefaultBold") dsortlbl:SetText(GetTranslation("sb_sortby")) dsortlbl:SizeToContentsX() dsortlbl:SetTall(sh) dsortlbl:SetColor(COLOR_WHITE) local dsorttype = vgui.Create("DComboBox", dsort) dsorttype:MoveRightOf(dsortlbl, m) dsorttype:SetSize(dsort:GetWide() - dsortlbl:GetWide() - sh - m, sh) for key, data in pairs(sort_funcs) do dsorttype:AddChoice(GetTranslation(data.name), key, lower(equipment_sorting:GetString()) == key) end dsorttype.OnSelect = function(s, idx, val, data) equipment_sorting:SetString(data) end local dsortasc = vgui.Create("DButton", dsort) dsortasc:SetSize(sh, sh) dsortasc:MoveRightOf(dsorttype) dsortasc:SetText("") dsortasc.Paint = function(s, pw, ph) local name = equipment_ascending:GetBool() and "ButtonUp" or "ButtonDown" derma.SkinHook("Paint", name, s, pw, ph) end dsortasc.DoClick = function(s) equipment_ascending:SetBool(not equipment_ascending:GetBool()) end local owned_ids = GetOwnedEquipment() local dlistw = 216 --- Construct icon listing local dlist = vgui.Create("EquipSelect", dequip) dlist:MoveBelow(dsort) dlist:SetSize(dlistw, h - sh - 75) dlist:EnableVerticalScrollbar() dlist:EnableHorizontal(true) dlist:SetPadding(4) ListPanel = dlist local items = GetEquipmentForRole(ply:GetRole()) for k, item in pairs(items) do local ic = nil -- Create icon panel if item.material then if item.custom then -- Custom marker icon ic = vgui.Create("LayeredIcon", dlist) local marker = vgui.Create("DImage") marker:SetImage("vgui/ttt/custom_marker") marker.PerformLayout = function(s) s:AlignBottom(2) s:AlignRight(2) s:SetSize(16, 16) end marker:SetTooltip(GetTranslation("equip_custom")) ic:AddLayer(marker) ic:EnableMousePassthrough(marker) elseif not ItemIsWeapon(item) then ic = vgui.Create("SimpleIcon", dlist) else ic = vgui.Create("LayeredIcon", dlist) end -- Slot marker icon if ItemIsWeapon(item) then local slot = vgui.Create("SimpleIconLabelled") slot:SetIcon("vgui/ttt/slotcap") slot:SetIconColor(color_slot[ply:GetRole()] or COLOR_GREY) slot:SetIconSize(16) slot:SetIconText(item.slot) slot:SetIconProperties(COLOR_WHITE, "DefaultBold", {opacity=220, offset=1}, {10, 8}) ic:AddLayer(slot) ic:EnableMousePassthrough(slot) end ic:SetIconSize(64) ic:SetIcon(item.material) elseif item.model then ic = vgui.Create("SpawnIcon", dlist) ic:SetModel(item.model) else ErrorNoHalt("Equipment item does not have model or material specified: " .. tostring(item) .. "\n") end ic.item = item local tip = SafeTranslate(item.name) .. " (" .. SafeTranslate(item.type) .. ")" ic:SetTooltip(tip) -- If we cannot order this item, darken it if not CanOrder(item, owned_ids) then ic:SetIconColor(color_darkened) end dlist:AddPanel(ic) end SortEquipmentPanels(dlist:GetItems()) local bw, bh = 100, 25 local dih = h - bh - m*5 local diw = w - dlistw - m*6 - 2 local dinfobg = vgui.Create("DPanel", dequip) dinfobg:SetPaintBackground(false) dinfobg:SetSize(diw, dih) dinfobg:SetPos(dlistw + m, 0) local dinfo = vgui.Create("ColoredBox", dinfobg) dinfo:SetColor(Color(90, 90, 95)) dinfo:SetPos(0,0) dinfo:StretchToParent(0, 0, 0, dih - 135) local dfields = {} for _, k in ipairs(fieldstbl) do dfields[k] = vgui.Create("DLabel", dinfo) dfields[k]:SetTooltip(GetTranslation("equip_spec_" .. k)) dfields[k]:SetPos(m*3, m*2) end dfields.name:SetFont("TabLarge") dfields.type:SetFont("DermaDefault") dfields.type:MoveBelow(dfields.name) dfields.desc:SetFont("DermaDefaultBold") dfields.desc:SetContentAlignment(7) dfields.desc:MoveBelow(dfields.type, 1) local iw, ih = dinfo:GetSize() local dhelp = vgui.Create("ColoredBox", dinfobg) dhelp:SetColor(Color(90, 90, 95)) dhelp:SetSize(diw, dih - 205) dhelp:MoveBelow(dinfo, m) local update_preqs = PreqLabels(dhelp, m*3, m*2) dhelp:SizeToContents() local dconfirm = vgui.Create("DButton", dinfobg) dconfirm:SetPos(0, dih - bh*2) dconfirm:SetSize(bw, bh) dconfirm:SetEnabled(false) dconfirm:SetText(GetTranslation("equip_confirm")) dsheet:AddSheet(GetTranslation("equip_tabtitle"), dequip, "icon16/bomb.png", false, false, GetTranslation("equip_tooltip_main")) -- Item control if ply:HasEquipmentItem(EQUIP_RADAR) then local dradar = RADAR.CreateMenu(dsheet, dframe) dsheet:AddSheet(GetTranslation("radar_name"), dradar, "icon16/magnifier.png", false, false, GetTranslation("equip_tooltip_radar")) end if ply:HasEquipmentItem(EQUIP_DISGUISE) then local ddisguise = DISGUISE.CreateMenu(dsheet) dsheet:AddSheet(GetTranslation("disg_name"), ddisguise, "icon16/user.png", false, false, GetTranslation("equip_tooltip_disguise")) end -- Weapon/item control if IsValid(ply.radio) or ply:HasWeapon("weapon_ttt_radio") then local dradio = TRADIO.CreateMenu(dsheet) dsheet:AddSheet(GetTranslation("radio_name"), dradio, "icon16/transmit.png", false, false, GetTranslation("equip_tooltip_radio")) end -- Credit transferring if credits > 0 then local dtransfer = CreateTransferMenu(dsheet) dsheet:AddSheet(GetTranslation("xfer_name"), dtransfer, "icon16/group_gear.png", false, false, GetTranslation("equip_tooltip_xfer")) end hook.Run("TTTEquipmentTabs", dsheet) -- couple panelselect with info dlist.OnActivePanelChanged = function(self, _, new) for k,v in pairs(new.item) do if dfields[k] then dfields[k]:SetText(SafeTranslate(v)) dfields[k]:SizeToContents() end end -- Trying to force everything to update to -- the right size is a giant pain, so just -- force a good size. dfields.desc:SetTall(70) can_order = update_preqs(new.item) dconfirm:SetEnabled(can_order) end -- select first dlist:SelectPanel(dlist:GetItems()[1]) -- prep confirm action dconfirm.DoClick = function() local pnl = dlist.SelectedPanel if not pnl or not pnl.item then return end local choice = pnl.item RunConsoleCommand("ttt_order_equipment", choice.id) dframe:Close() end -- update some basic info, may have changed in another tab -- specifically the number of credits in the preq list dsheet.OnTabChanged = function(s, old, new) if not IsValid(new) then return end if new:GetPanel() == dequip then can_order = update_preqs(dlist.SelectedPanel.item) dconfirm:SetEnabled(can_order) end end local dcancel = vgui.Create("DButton", dframe) dcancel:SetPos(w - 13 - bw, h - bh - 16) dcancel:SetSize(bw, bh) dcancel:SetEnabled(true) dcancel:SetText(GetTranslation("close")) dcancel.DoClick = function() dframe:Close() end dframe:MakePopup() dframe:SetKeyboardInputEnabled(false) eqframe = dframe end concommand.Add("ttt_cl_traitorpopup", TraitorMenuPopup) local function ForceCloseTraitorMenu(ply, cmd, args) if IsValid(eqframe) then eqframe:Close() end end concommand.Add("ttt_cl_traitorpopup_close", ForceCloseTraitorMenu) function GM:OnContextMenuOpen() local r = GetRoundState() if r == ROUND_ACTIVE and not (LocalPlayer():GetTraitor() or LocalPlayer():GetDetective()) then return elseif r == ROUND_POST or r == ROUND_PREP then CLSCORE:Toggle() return end if IsValid(eqframe) then eqframe:Close() else RunConsoleCommand("ttt_cl_traitorpopup") end end local bitsRequired = util.BitsRequired local function ReceiveEquipment() local ply = LocalPlayer() if not IsValid(ply) then return end ply.equipment_items = net.ReadInt(bitsRequired(EQUIP_MAX, true)) end net.Receive("TTT_Equipment", ReceiveEquipment) local function ReceiveCredits() local ply = LocalPlayer() if not IsValid(ply) then return end ply.equipment_credits = net.ReadUInt(8) end net.Receive("TTT_Credits", ReceiveCredits) local r = 0 local function ReceiveBought() local ply = LocalPlayer() if not IsValid(ply) then return end ply.bought = {} local num = net.ReadUInt(8) for i=1,num do local s = net.ReadString() if s != "" then table.insert(ply.bought, s) end end -- This usermessage sometimes fails to contain the last weapon that was -- bought, even though resending then works perfectly. Possibly a bug in -- bf_read. Anyway, this hack is a workaround: we just request a new umsg. if num != #ply.bought and r < 10 then -- r is an infinite loop guard RunConsoleCommand("ttt_resend_bought") r = r + 1 else r = 0 end end net.Receive("TTT_Bought", ReceiveBought) -- Player received the item he has just bought, so run clientside init local function ReceiveBoughtItem() local is_item = net.ReadBool() local id if is_item then local exponent = net.ReadUInt(bitsRequired(math.log(EQUIP_MAX) / math.log(2))) id = 2 ^ exponent else id = net.ReadString() end -- I can imagine custom equipment wanting this, so making a hook hook.Run("TTTBoughtItem", is_item, id) end net.Receive("TTT_BoughtItem", ReceiveBoughtItem)