Files
wnsrc/lua/arccw/shared/sh_attachments.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

569 lines
16 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
ArcCW.AttachmentBlacklistTable = ArcCW.AttachmentBlacklistTable or {}
function ArcCW:PlayerCanAttach(ply, wep, attname, slot, detach)
-- The global variable takes priority over everything
if !ArcCW.EnableCustomization then return false end
-- Attach and Detach require a player (usually the owner)
if !IsValid(ply) then return false end
-- Spectators taking off your attachments is funny, but also cursed
if wep:GetOwner() != ply then return false end
-- Allow hooks to block or force allow attachment usage
local ret = hook.Run("ArcCW_PlayerCanAttach", ply, wep, attname, slot, detach)
if ret == nil and engine.ActiveGamemode() == "terrortown" then
local mode = ArcCW.ConVars["ttt_customizemode"]:GetInt()
if mode == 1 and !ply.ArcCW_AllowCustomize then return false
elseif mode == 2 and !ply.ArcCW_AllowCustomize and GetRoundState() == ROUND_ACTIVE then return false
elseif mode == 3 and !ply.ArcCW_AllowCustomize and !ply:IsActiveTraitor() and !ply:IsActiveDetective() then return false end
elseif ret == nil and ArcCW.ConVars["enable_customization"]:GetInt() <= 0 then
return false
end
return (ret == nil and true) or ret
end
function ArcCW:GetAttsForSlot(slot, wep, random)
local ret = {}
for id, atttbl in pairs(ArcCW.AttachmentTable) do
if !ArcCW:SlotAcceptsAtt(slot, wep, id) then continue end
if random and (atttbl.NoRandom or (atttbl.RandomWeight or 1) <= 0) then continue end
table.insert(ret, id)
end
return ret
end
function ArcCW:GetAttList(name, filter)
if self.AttachmentCachedLists[name] then return self.AttachmentCachedLists[name] end
self.AttachmentCachedLists[name] = {}
for k, v in pairs(self.AttachmentTable) do
local k2, v2 = filter(k, v)
if k2 then
self.AttachmentCachedLists[name][k2] = v2
end
end
return self.AttachmentCachedLists[name]
end
local function weighted_random(tbl, amt)
amt = amt or 1
local max = 0
for k, v in pairs(tbl) do max = max + v end
local ret = {}
for i = 1, amt do
local rng = math.random() * max
for k, v in pairs(tbl) do
rng = rng - v
if rng <= 0 then
ret[k] = (ret[k] or 0) + 1
break
end
end
end
return ret
end
function ArcCW:RollRandomAttachment(all, wep, slot)
for k, v in pairs(self:RollRandomAttachments(1, all, wep, slot)) do return k end
end
function ArcCW:RollRandomAttachments(amt, all, wep, slot)
if wep == nil then
-- cache the list results and randomly get one
local tbl = self:GetAttList("random" .. (all and "_all" or ""), function(k, v)
if ((!v.Free and !v.InvAtt) or all) and !v.NoRandom and (v.RandomWeight or 1) >= 0 then
return k, v.RandomWeight or 1
end
end)
return weighted_random(tbl, amt)
else
-- can't cache this because it is weapon-dependent
local tbl = {}
for id, atttbl in pairs(ArcCW.AttachmentTable) do
if ((!atttbl.Free and !atttbl.InvAtt) or all) and (atttbl.NoRandom or (atttbl.RandomWeight or 1) <= 0) then continue end
if !wep:CheckFlags(atttbl.ExcludeFlags, atttbl.RequireFlags) then continue end
if slot != nil and !ArcCW:SlotAcceptsAtt(slot.Slot, wep, id) then continue end
tbl[id] = atttbl.RandomWeight or 1
end
return weighted_random(tbl, amt)
end
end
function ArcCW:SlotAcceptsAtt(slot, wep, att)
local slots = {}
if isstring(slot) then
slots[slot] = true
elseif istable(slot) then
for _, i in pairs(slot) do
slots[i] = true
end
end
local atttbl = ArcCW.AttachmentTable[att]
if !atttbl then return false end
if atttbl.Hidden or atttbl.Blacklisted or ArcCW.AttachmentBlacklistTable[att] then return false end
local Owner = wep.GetOwner and wep:GetOwner()
if (atttbl.NotForNPC or atttbl.NotForNPCs) and Owner and Owner:IsNPC() then
return false
end
if atttbl.AdminOnly and IsValid(Owner) and !(Owner:IsPlayer() and Owner:IsAdmin()) then return false end
if wep.RejectAttachments and wep.RejectAttachments[att] then return false end
if isstring(atttbl.Slot) then
if !slots[atttbl.Slot] then return false end
elseif istable(atttbl.Slot) then
local yeah = false
for _, i in pairs(atttbl.Slot) do
if slots[i] then
yeah = true
break
end
end
if !yeah then
return false
end
end
if wep and atttbl.Hook_Compatible then
local compat = atttbl.Hook_Compatible(wep, {slot = slot, att = att})
if compat == true then
return true
elseif compat == false then
return false
end
end
return true
end
function ArcCW:WeaponAcceptsAtt(wep, att)
if wep.ArcCW and wep.Attachments then
local tbl = {}
for i, v in pairs(wep.Attachments) do
table.insert(tbl, i)
end
return ArcCW:SlotAcceptsAtt(wep, wep, att)
end
return false
end
function ArcCW:PlayerGetAtts(ply, att)
if !IsValid(ply) then return 0 end
if ArcCW.ConVars["attinv_free"]:GetBool() then return 999 end
if att == "" then return 999 end
local atttbl = ArcCW.AttachmentTable[att]
if !atttbl then return 0 end
if atttbl.Free then return 999 end
if !IsValid(ply) then return 0 end
if !ply:IsAdmin() and atttbl.AdminOnly then
return 0
end
if atttbl.InvAtt then att = atttbl.InvAtt end
if !ply.ArcCW_AttInv then return 0 end
if !ply.ArcCW_AttInv[att] then return 0 end
return ply.ArcCW_AttInv[att]
end
function ArcCW:PlayerGiveAtt(ply, att, amt)
amt = amt or 1
if !IsValid(ply) then return end
if !ply.ArcCW_AttInv then
ply.ArcCW_AttInv = {}
end
local atttbl = ArcCW.AttachmentTable[att]
if !atttbl then print("Invalid att " .. att) return end
if atttbl.Free then return end -- You can't give a free attachment, silly
if atttbl.AdminOnly and !(ply:IsPlayer() and ply:IsAdmin()) then return false end
if atttbl.InvAtt then att = atttbl.InvAtt end
if ArcCW.ConVars["attinv_lockmode"]:GetBool() then
if ply.ArcCW_AttInv[att] == 1 then return end
ply.ArcCW_AttInv[att] = 1
else
ply.ArcCW_AttInv[att] = (ply.ArcCW_AttInv[att] or 0) + amt
end
end
function ArcCW:PlayerTakeAtt(ply, att, amt)
amt = amt or 1
if ArcCW.ConVars["attinv_lockmode"]:GetBool() then return end
if !IsValid(ply) then return end
if !ply.ArcCW_AttInv then
ply.ArcCW_AttInv = {}
end
local atttbl = ArcCW.AttachmentTable[att]
if !atttbl or atttbl.Free then return end
if atttbl.InvAtt then att = atttbl.InvAtt end
ply.ArcCW_AttInv[att] = ply.ArcCW_AttInv[att] or 0
if ply.ArcCW_AttInv[att] < amt then
return false
end
ply.ArcCW_AttInv[att] = ply.ArcCW_AttInv[att] - amt
if ply.ArcCW_AttInv[att] <= 0 then
ply.ArcCW_AttInv[att] = nil
end
return true
end
if CLIENT then
local function postsetup(wpn)
if wpn.SetupModel then
wpn:SetupModel(true)
if wpn:GetOwner() == LocalPlayer() then
wpn:SetupModel(false)
end
wpn:AdjustAtts()
else
timer.Simple(0.1, function()
postsetup(wpn)
end)
end
end
net.Receive("arccw_networkatts", function(len, ply)
local wpn = net.ReadEntity()
if !IsValid(wpn) then return end
if !wpn.ArcCW then return end
local attnum = net.ReadUInt(8)
wpn.Attachments = wpn.Attachments or {}
wpn.SubSlotCount = 0
for i = 1, attnum do
local attid = net.ReadUInt(ArcCW.GetBitNecessity())
wpn.Attachments[i] = wpn.Attachments[i] or {}
if attid == 0 then
if !istable(wpn.Attachments[i]) then continue end
wpn.Attachments[i].Installed = nil
continue
end
local att = ArcCW.AttachmentIDTable[attid]
wpn.Attachments[i].Installed = att
if wpn.Attachments[i].SlideAmount then
wpn.Attachments[i].SlidePos = net.ReadFloat()
end
if ArcCW.AttachmentTable[att].ToggleStats then
wpn.Attachments[i].ToggleNum = net.ReadUInt(8)
end
wpn:AddSubSlot(i, att)
end
wpn.CertainAboutAtts = true
postsetup(wpn)
end)
net.Receive("arccw_sendattinv", function(len, ply)
if !IsValid(LocalPlayer()) then return end -- This might be called before we are valid
LocalPlayer().ArcCW_AttInv = {}
local count = net.ReadUInt(32)
for i = 1, count do
local attid = net.ReadUInt(ArcCW.GetBitNecessity())
local acount = net.ReadUInt(32)
local att = ArcCW.AttachmentIDTable[attid]
LocalPlayer().ArcCW_AttInv[att] = acount
end
-- This function will not exist until initialized (by having an ArcCW weapon exist)!
-- It also obviously needs menu2 open
if ArcCW.InvHUD_FormAttachmentSelect and IsValid(ArcCW.InvHUD) and IsValid(ArcCW.InvHUD_Menu2) then
ArcCW.InvHUD_FormAttachmentSelect()
end
end)
net.Receive("arccw_sendatthp", function(len, ply)
local wpn = LocalPlayer():GetActiveWeapon()
while net.ReadBool() do
local slot = net.ReadUInt(8)
local hp = net.ReadFloat()
wpn.Attachments[slot].HP = hp
end
end)
elseif SERVER then
hook.Add("PlayerDeath", "ArcCW_DeathAttInv", function(ply)
ply.ArcCW_AttInv = ply.ArcCW_AttInv or {}
if !table.IsEmpty(ply.ArcCW_AttInv)
and ArcCW.ConVars["attinv_loseondie"]:GetInt() >= 2
and !ArcCW.ConVars["attinv_free"]:GetBool() then
local boxEnt = ents.Create("arccw_att_dropped")
boxEnt:SetPos(ply:GetPos() + Vector(0, 0, 4))
boxEnt.GiveAttachments = ply.ArcCW_AttInv
boxEnt:Spawn()
boxEnt:SetNWString("boxname", ply:GetName() .. "'s Death Box")
local count = 0
for i, v in pairs(boxEnt.GiveAttachments) do count = count + v end
boxEnt:SetNWInt("boxcount", count)
end
end)
hook.Add("PlayerSpawn", "ArcCW_SpawnAttInv", function(ply, trans)
if trans then return end
if ArcCW.ConVars["attinv_loseondie"]:GetInt() >= 1 then
ply.ArcCW_AttInv = {}
end
local amt = ArcCW.ConVars["attinv_giveonspawn"]:GetInt()
if amt > 0 then
local giv = ArcCW:RollRandomAttachments(amt)
for k, v in pairs(giv) do
ArcCW:PlayerGiveAtt(ply, k, v)
end
end
ArcCW:PlayerSendAttInv(ply)
end)
net.Receive("arccw_rqwpnnet", function(len, ply)
local wpn = net.ReadEntity()
if !wpn.ArcCW then return end
wpn:RecalcAllBuffs()
wpn:NetworkWeapon(ply)
end)
net.Receive("arccw_slidepos", function(len, ply)
local wpn = ply:GetActiveWeapon()
local slot = net.ReadUInt(8)
local pos = net.ReadFloat()
if !wpn.ArcCW then return end
if !wpn.Attachments[slot] then return end
wpn.Attachments[slot].SlidePos = pos
end)
net.Receive("arccw_togglenum", function(len, ply)
local wpn = ply:GetActiveWeapon()
local slot = net.ReadUInt(8)
local num = net.ReadUInt(8)
if !wpn.ArcCW then return end
if !wpn.Attachments[slot] then return end
wpn.Attachments[slot].ToggleNum = num
wpn:AdjustAtts()
wpn:NetworkWeapon()
wpn:SetupModel(false)
wpn:SetupModel(true)
end)
net.Receive("arccw_asktoattach", function(len, ply)
local wpn = ply:GetActiveWeapon()
local slot = net.ReadUInt(8)
local attid = net.ReadUInt(24)
local att = ArcCW.AttachmentIDTable[attid]
if !wpn.ArcCW then return end
if !wpn.Attachments[slot] then return end
if !att then return end
wpn:Attach(slot, att)
end)
net.Receive("arccw_asktodetach", function(len, ply)
local wpn = ply:GetActiveWeapon()
local slot = net.ReadUInt(8)
if !wpn.ArcCW then return end
if !wpn.Attachments[slot] then return end
wpn:Detach(slot)
end)
net.Receive("arccw_asktodrop", function(len, ply)
local attid = net.ReadUInt(24)
local att = ArcCW.AttachmentIDTable[attid]
if ArcCW.ConVars["attinv_free"]:GetBool() then return end
if ArcCW.ConVars["attinv_lockmode"]:GetBool() then return end
if ArcCW.ConVars["enable_customization"]:GetInt() < 0 then return end
if !ArcCW.ConVars["enable_dropping"]:GetBool() then return end
if !att then return end
local atttbl = ArcCW.AttachmentTable[att]
if !atttbl then return end
if atttbl.Free then return end
if ArcCW:PlayerGetAtts(ply, att) < 1 then return end
-- better to do it like this in case you don't want to generate the attachment entities
local ent = ents.Create("arccw_att_base")
if !IsValid(ent) then return end
ent:SetPos(ply:EyePos() + ply:EyeAngles():Forward() * 32)
ent:SetNWInt("attid", attid)
ent.GiveAttachments = {[att] = 1}
ent.Model = atttbl.DroppedModel or atttbl.Model or "models/Items/BoxSRounds.mdl"
ent.Icon = atttbl.Icon
ent.PrintName = atttbl.PrintName or att
ent:Spawn()
timer.Simple(0, function()
local phys = ent:GetPhysicsObject()
if phys:IsValid() then
phys:SetVelocity(ply:EyeAngles():Forward() * 32 * math.max(phys:GetMass(), 4))
end
end)
ArcCW:PlayerTakeAtt(ply, att, 1)
ArcCW:PlayerSendAttInv(ply)
end)
if SERVER then
net.Receive("arccw_applypreset", function(len, ply)
local wpn = net.ReadEntity()
if wpn:GetOwner() != ply or !wpn.ArcCW then return end
if ply.ArcCW_DisableAutosave or ply.ArcCW_Sandbox_RandomAtts then
ply.ArcCW_Sandbox_RandomAtts = nil
return
end
for k, v in pairs(wpn.Attachments) do
wpn:Detach(k, true, true)
end
wpn.Attachments.BaseClass = nil -- AGHHHHHHHHHH
for k, v in SortedPairs(wpn.Attachments) do
local attid = net.ReadUInt(ArcCW.GetBitNecessity())
local attname = ArcCW.AttachmentIDTable[attid or 0] or ""
local atttbl = ArcCW.AttachmentTable[attname]
if !atttbl then continue end
wpn:Attach(k, attname, true, true)
if net.ReadBool() then
v.SlidePos = net.ReadFloat()
v.SlidePos = atttbl.MountPositionOverride or v.SlidePos
else
v.SlidePos = 0.5
end
if atttbl.ToggleStats then
v.ToggleNum = math.Clamp(net.ReadUInt(8), 1, #atttbl.ToggleStats)
else
v.ToggleNum = 1
end
end
wpn:AdjustAtts()
wpn:RefreshBGs()
if ply.ArcCW_Sandbox_FirstSpawn then
-- Curiously, RestoreAmmo has a sync delay only in singleplayer
ply.ArcCW_Sandbox_FirstSpawn = nil
wpn:RestoreAmmo()
end
wpn:NetworkWeapon()
wpn:SetupModel(false)
wpn:SetupModel(true)
net.Start("arccw_applypreset")
net.WriteEntity(wpn)
net.Send(ply)
end)
else
net.Receive("arccw_applypreset", function()
local wpn = net.ReadEntity()
if !IsValid(wpn) then return end
wpn:SavePreset("autosave")
end)
end
function ArcCW:PlayerSendAttInv(ply)
if ArcCW.ConVars["attinv_free"]:GetBool() then return end
if !IsValid(ply) then return end
if !ply.ArcCW_AttInv then return end
net.Start("arccw_sendattinv")
net.WriteUInt(table.Count(ply.ArcCW_AttInv), 32)
for att, count in pairs(ply.ArcCW_AttInv) do
local atttbl = ArcCW.AttachmentTable[att]
local attid = atttbl.ID
net.WriteUInt(attid, ArcCW.GetBitNecessity())
net.WriteUInt(count, 32)
end
net.Send(ply)
end
end