add sborka

This commit is contained in:
2026-03-31 10:27:04 +03:00
commit f5e5f56c84
2345 changed files with 382127 additions and 0 deletions

View File

@@ -0,0 +1,388 @@
local PLUGIN = PLUGIN
PLUGIN.name = "3D Panels"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds web panels that can be placed on the map."
-- List of available panel dislays.
PLUGIN.list = PLUGIN.list or {}
if (SERVER) then
util.AddNetworkString("ixPanelList")
util.AddNetworkString("ixPanelAdd")
util.AddNetworkString("ixPanelRemove")
-- Called when the player is sending client info.
function PLUGIN:PlayerInitialSpawn(client)
-- Send the list of panel displays.
timer.Simple(1, function()
if (IsValid(client)) then
local json = util.TableToJSON(self.list)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixPanelList")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
-- Adds a panel to the list, sends it to the players, and saves data.
function PLUGIN:AddPanel(position, angles, url, scale, brightness)
scale = math.Clamp((scale or 1) * 0.1, 0.001, 5)
brightness = math.Clamp(math.Round((brightness or 100) * 2.55), 1, 255)
-- Find an ID for this panel within the list.
local index = #self.list + 1
-- Add the panel to the list so it can be sent and saved.
self.list[index] = {position, angles, nil, nil, scale, url, nil, brightness}
-- Send the panel information to the players.
net.Start("ixPanelAdd")
net.WriteUInt(index, 32)
net.WriteVector(position)
net.WriteAngle(angles)
net.WriteFloat(scale)
net.WriteString(url)
net.WriteUInt(brightness, 8)
net.Broadcast()
-- Save the plugin data.
self:SavePanels()
end
-- Removes a panel that are within the radius of a position.
function PLUGIN:RemovePanel(position, radius)
-- Default the radius to 100.
radius = radius or 100
local panelsDeleted = {}
-- Loop through all of the panels.
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
-- Check if the distance from our specified position to the panel is less than the radius.
if (v[1]:Distance(position) <= radius) then
panelsDeleted[#panelsDeleted + 1] = k
end
end
-- Save the plugin data if we actually changed anything.
if (#panelsDeleted > 0) then
-- Invert index table to delete from highest -> lowest
panelsDeleted = table.Reverse(panelsDeleted)
for _, v in ipairs(panelsDeleted) do
-- Remove the panel from the list of panels.
table.remove(self.list, v)
-- Tell the players to stop showing the panel.
net.Start("ixPanelRemove")
net.WriteUInt(v, 32)
net.Broadcast()
end
self:SavePanels()
end
-- Return the number of deleted panels.
return #panelsDeleted
end
-- Called after entities have been loaded on the map.
function PLUGIN:LoadData()
self.list = self:GetData() or {}
-- Formats table to sequential to support legacy panels.
self.list = table.ClearKeys(self.list)
end
-- Called when the plugin needs to save information.
function PLUGIN:SavePanels()
self:SetData(self.list)
end
else
-- Pre-define the zero index in client before the net receives
PLUGIN.list[0] = PLUGIN.list[0] or 0
-- Holds the current cached material and filename.
local cachedPreview = {}
local function CacheMaterial(index)
if (index < 1) then
return
end
local info = PLUGIN.list[index]
local exploded = string.Explode("/", info[6])
local filename = exploded[#exploded]
local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/"
if (file.Exists(path..filename, "DATA")) then
local material = Material("../data/"..path..filename, "noclamp smooth")
if (!material:IsError()) then
info[7] = material
-- Set width and height
info[3] = material:GetInt("$realwidth")
info[4] = material:GetInt("$realheight")
end
else
file.CreateDir(path)
http.Fetch(info[6], function(body)
file.Write(path..filename, body)
local material = Material("../data/"..path..filename, "noclamp smooth")
if (!material:IsError()) then
info[7] = material
-- Set width and height
info[3] = material:GetInt("$realwidth")
info[4] = material:GetInt("$realheight")
end
end)
end
end
local function UpdateCachedPreview(url)
local path = "helix/"..Schema.folder.."/"..PLUGIN.uniqueID.."/"
-- Gets the file name
local exploded = string.Explode("/", url)
local filename = exploded[#exploded]
if (file.Exists(path..filename, "DATA")) then
local preview = Material("../data/"..path..filename, "noclamp smooth")
-- Update the cached preview if success
if (!preview:IsError()) then
cachedPreview = {url, preview}
else
cachedPreview = {}
end
else
file.CreateDir(path)
http.Fetch(url, function(body)
file.Write(path..filename, body)
local preview = Material("../data/"..path..filename, "noclamp smooth")
-- Update the cached preview if success
if (!preview:IsError()) then
cachedPreview = {url, preview}
else
cachedPreview = {}
end
end)
end
end
-- Receives new panel objects that need to be drawn.
net.Receive("ixPanelAdd", function()
local index = net.ReadUInt(32)
local position = net.ReadVector()
local angles = net.ReadAngle()
local scale = net.ReadFloat()
local url = net.ReadString()
local brightness = net.ReadUInt(8)
if (url != "") then
PLUGIN.list[index] = {position, angles, nil, nil, scale, url, nil, brightness}
CacheMaterial(index)
PLUGIN.list[0] = #PLUGIN.list
end
end)
net.Receive("ixPanelRemove", function()
local index = net.ReadUInt(32)
table.remove(PLUGIN.list, index)
PLUGIN.list[0] = #PLUGIN.list
end)
-- Receives a full update on ALL panels.
net.Receive("ixPanelList", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress panel data!\n")
return
end
-- Set the list of panels to the ones provided by the server.
PLUGIN.list = util.JSONToTable(uncompressed)
-- Will be saved, but refresh just to make sure.
PLUGIN.list[0] = #PLUGIN.list
local CacheQueue = {}
-- Loop through the list of panels.
for k, _ in pairs(PLUGIN.list) do
if (k == 0) then
continue
end
CacheQueue[#CacheQueue + 1] = k
end
if (#CacheQueue == 0) then
return
end
timer.Create("ixCache3DPanels", 1, #CacheQueue, function()
if (#CacheQueue > 0) then
CacheMaterial(CacheQueue[1])
table.remove(CacheQueue, 1)
else
timer.Remove("ixCache3DPanels")
end
end)
end)
-- Called after all translucent objects are drawn.
function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox)
if (bDrawingDepth or bDrawingSkybox) then
return
end
-- Panel preview
if (ix.chat.currentCommand == "paneladd") then
self:PreviewPanel()
end
-- Store the position of the player to be more optimized.
local ourPosition = LocalPlayer():GetPos()
local panel = self.list
for i = 1, panel[0] do
local position = panel[i][1]
local image = panel[i][7]
-- Older panels do not have a brightness index
local brightness = panel[i][8] or 255
if (panel[i][7] and ourPosition:DistToSqr(position) <= 4194304) then
cam.Start3D2D(position, panel[i][2], panel[i][5] or 0.1)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
surface.SetDrawColor(brightness, brightness, brightness)
surface.SetMaterial(image)
surface.DrawTexturedRect(0, 0, panel[i][3] or image:Width(), panel[i][4] or image:Height())
render.PopFilterMag()
render.PopFilterMin()
cam.End3D2D()
end
end
end
function PLUGIN:ChatTextChanged(text)
if (ix.chat.currentCommand == "paneladd") then
-- Allow time for ix.chat.currentArguments to update
timer.Simple(0, function()
local arguments = ix.chat.currentArguments
if (!arguments[1]) then
return
end
UpdateCachedPreview(arguments[1])
end)
end
end
function PLUGIN:PreviewPanel()
local arguments = ix.chat.currentArguments
-- if there's no URL, then no preview.
if (!arguments[1]) then
return
end
-- If the material is valid, preview the panel
if (cachedPreview[2] and !cachedPreview[2]:IsError()) then
local trace = LocalPlayer():GetEyeTrace()
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
local position = (trace.HitPos + angles:Up() * 0.1)
local ourPosition = LocalPlayer():GetPos()
-- validate argument types
local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5)
local brightness = math.Clamp(math.Round((tonumber(arguments[3]) or 100) * 2.55), 1, 255)
-- Attempt to collect the dimensions from the Material
local width, height = cachedPreview[2]:GetInt("$realwidth"), cachedPreview[2]:GetInt("$realheight")
if (ourPosition:DistToSqr(position) <= 4194304) then
cam.Start3D2D(position, angles, scale or 0.1)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
surface.SetDrawColor(brightness, brightness, brightness)
surface.SetMaterial(cachedPreview[2])
surface.DrawTexturedRect(0, 0, width or cachedPreview[2]:Width(), height or cachedPreview[2]:Height())
render.PopFilterMag()
render.PopFilterMin()
cam.End3D2D()
end
end
end
end
ix.command.Add("PanelAdd", {
description = "@cmdPanelAdd",
privilege = "Manage Panels",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.number, ix.type.optional),
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, url, scale, brightness)
-- Get the position and angles of the panel.
local trace = client:GetEyeTrace()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
-- Add the panel.
PLUGIN:AddPanel(position + angles:Up() * 0.1, angles, url, scale, brightness)
return "@panelAdded"
end
})
ix.command.Add("PanelRemove", {
description = "@cmdPanelRemove",
privilege = "Manage Panels",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
-- Get the origin to remove panel.
local trace = client:GetEyeTrace()
local position = trace.HitPos
-- Remove the panel(s) and get the amount removed.
local amount = PLUGIN:RemovePanel(position, radius)
return "@panelRemoved", amount
end
})

View File

@@ -0,0 +1,341 @@
local PLUGIN = PLUGIN
PLUGIN.name = "3D Text"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds text that can be placed on the map."
-- List of available text panels
PLUGIN.list = PLUGIN.list or {}
if (SERVER) then
util.AddNetworkString("ixTextList")
util.AddNetworkString("ixTextAdd")
util.AddNetworkString("ixTextRemove")
ix.log.AddType("undo3dText", function(client)
return string.format("%s has removed their last 3D text.", client:GetName())
end)
-- Called when the player is sending client info.
function PLUGIN:PlayerInitialSpawn(client)
timer.Simple(1, function()
if (IsValid(client)) then
local json = util.TableToJSON(self.list)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixTextList")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
-- Adds a text to the list, sends it to the players, and saves data.
function PLUGIN:AddText(position, angles, text, scale)
local index = #self.list + 1
scale = math.Clamp((scale or 1) * 0.1, 0.001, 5)
self.list[index] = {position, angles, text, scale}
net.Start("ixTextAdd")
net.WriteUInt(index, 32)
net.WriteVector(position)
net.WriteAngle(angles)
net.WriteString(text)
net.WriteFloat(scale)
net.Broadcast()
self:SaveText()
return index
end
-- Removes a text that are within the radius of a position.
function PLUGIN:RemoveText(position, radius)
radius = radius or 100
local textDeleted = {}
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
if (v[1]:Distance(position) <= radius) then
textDeleted[#textDeleted + 1] = k
end
end
if (#textDeleted > 0) then
-- Invert index table to delete from highest -> lowest
textDeleted = table.Reverse(textDeleted)
for _, v in ipairs(textDeleted) do
table.remove(self.list, v)
net.Start("ixTextRemove")
net.WriteUInt(v, 32)
net.Broadcast()
end
self:SaveText()
end
return #textDeleted
end
function PLUGIN:RemoveTextByID(id)
local info = self.list[id]
if (!info) then
return false
end
net.Start("ixTextRemove")
net.WriteUInt(id, 32)
net.Broadcast()
table.remove(self.list, id)
return true
end
-- Called after entities have been loaded on the map.
function PLUGIN:LoadData()
self.list = self:GetData() or {}
-- Formats table to sequential to support legacy panels.
self.list = table.ClearKeys(self.list)
end
-- Called when the plugin needs to save information.
function PLUGIN:SaveText()
self:SetData(self.list)
end
else
-- Pre-define the zero index in client before the net receives
PLUGIN.list[0] = PLUGIN.list[0] or 0
language.Add("Undone_ix3dText", "Removed 3D Text")
function PLUGIN:GenerateMarkup(text)
local object = ix.markup.Parse("<font=ix3D2DFont>"..text:gsub("\\n", "\n"))
object.onDrawText = function(surfaceText, font, x, y, color, alignX, alignY, alpha)
-- shadow
surface.SetTextPos(x + 1, y + 1)
surface.SetTextColor(0, 0, 0, alpha)
surface.SetFont(font)
surface.DrawText(surfaceText)
surface.SetTextPos(x, y)
surface.SetTextColor(color.r or 255, color.g or 255, color.b or 255, alpha)
surface.SetFont(font)
surface.DrawText(surfaceText)
end
return object
end
-- Receives new text objects that need to be drawn.
net.Receive("ixTextAdd", function()
local index = net.ReadUInt(32)
local position = net.ReadVector()
local angles = net.ReadAngle()
local text = net.ReadString()
local scale = net.ReadFloat()
if (text != "") then
PLUGIN.list[index] = {
position,
angles,
PLUGIN:GenerateMarkup(text),
scale
}
PLUGIN.list[0] = #PLUGIN.list
end
end)
net.Receive("ixTextRemove", function()
local index = net.ReadUInt(32)
table.remove(PLUGIN.list, index)
PLUGIN.list[0] = #PLUGIN.list
end)
-- Receives a full update on ALL texts.
net.Receive("ixTextList", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress text data!\n")
return
end
PLUGIN.list = util.JSONToTable(uncompressed)
-- Will be saved, but refresh just to make sure.
PLUGIN.list[0] = #PLUGIN.list
for k, v in pairs(PLUGIN.list) do
if (k == 0) then
continue
end
local object = ix.markup.Parse("<font=ix3D2DFont>"..v[3]:gsub("\\n", "\n"))
object.onDrawText = function(text, font, x, y, color, alignX, alignY, alpha)
draw.TextShadow({
pos = {x, y},
color = ColorAlpha(color, alpha),
text = text,
xalign = 0,
yalign = alignY,
font = font
}, 1, alpha)
end
v[3] = object
end
end)
function PLUGIN:StartChat()
self.preview = nil
end
function PLUGIN:FinishChat()
self.preview = nil
end
function PLUGIN:HUDPaint()
if (ix.chat.currentCommand != "textremove") then
return
end
local radius = tonumber(ix.chat.currentArguments[1]) or 100
surface.SetDrawColor(200, 30, 30)
surface.SetTextColor(200, 30, 30)
surface.SetFont("ixMenuButtonFont")
local i = 0
for k, v in pairs(self.list) do
if (k == 0) then
continue
end
if (v[1]:Distance(LocalPlayer():GetEyeTraceNoCursor().HitPos) <= radius) then
local screen = v[1]:ToScreen()
surface.DrawLine(
ScrW() * 0.5,
ScrH() * 0.5,
math.Clamp(screen.x, 0, ScrW()),
math.Clamp(screen.y, 0, ScrH())
)
i = i + 1
end
end
if (i > 0) then
local textWidth, textHeight = surface.GetTextSize(i)
surface.SetTextPos(ScrW() * 0.5 - textWidth * 0.5, ScrH() * 0.5 + textHeight + 8)
surface.DrawText(i)
end
end
function PLUGIN:PostDrawTranslucentRenderables(bDrawingDepth, bDrawingSkybox)
if (bDrawingDepth or bDrawingSkybox) then
return
end
-- preview for textadd command
if (ix.chat.currentCommand == "textadd") then
local arguments = ix.chat.currentArguments
local text = tostring(arguments[1] or "")
local scale = math.Clamp((tonumber(arguments[2]) or 1) * 0.1, 0.001, 5)
local trace = LocalPlayer():GetEyeTraceNoCursor()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
local markup
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
-- markup will error with invalid fonts
pcall(function()
markup = PLUGIN:GenerateMarkup(text)
end)
if (markup) then
cam.Start3D2D(position, angles, scale)
markup:draw(0, 0, 1, 1, 255)
cam.End3D2D()
end
end
local position = LocalPlayer():GetPos()
local texts = self.list
for i = 1, texts[0] do
local distance = texts[i][1]:DistToSqr(position)
if (distance > 1048576) then
continue
end
cam.Start3D2D(texts[i][1], texts[i][2], texts[i][4] or 0.1)
local alpha = (1 - ((distance - 65536) / 768432)) * 255
texts[i][3]:draw(0, 0, 1, 1, alpha)
cam.End3D2D()
end
end
end
ix.command.Add("TextAdd", {
description = "@cmdTextAdd",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.number, ix.type.optional)
},
OnRun = function(self, client, text, scale)
local trace = client:GetEyeTrace()
local position = trace.HitPos
local angles = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Up(), 90)
angles:RotateAroundAxis(angles:Forward(), 90)
local index = PLUGIN:AddText(position + angles:Up() * 0.1, angles, text, scale)
undo.Create("ix3dText")
undo.SetPlayer(client)
undo.AddFunction(function()
if (PLUGIN:RemoveTextByID(index)) then
ix.log.Add(client, "undo3dText")
end
end)
undo.Finish()
return "@textAdded"
end
})
ix.command.Add("TextRemove", {
description = "@cmdTextRemove",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
local trace = client:GetEyeTrace()
local position = trace.HitPos + trace.HitNormal * 2
local amount = PLUGIN:RemoveText(position, radius)
return "@textRemoved", amount
end
})

View File

@@ -0,0 +1,139 @@
local animationTime = 2
local PLUGIN = PLUGIN
PLUGIN.cameraFraction = 0
local function GetHeadBone(client)
local head
for i = 1, client:GetBoneCount() do
local name = client:GetBoneName(i)
if (string.find(name:lower(), "head")) then
head = i
break
end
end
return head
end
function PLUGIN:PlayerBindPress(client, bind, bPressed)
if (!client:GetNetVar("actEnterAngle")) then
return
end
if (bind:find("+jump") and bPressed) then
ix.command.Send("ExitAct")
return true
end
end
function PLUGIN:ShouldDrawLocalPlayer(client)
if (client:GetNetVar("actEnterAngle") and self.cameraFraction > 0.25) then
return true
elseif (self.cameraFraction > 0.25) then
return true
end
end
local forwardOffset = 16
local backwardOffset = -32
local heightOffset = Vector(0, 0, 20)
local idleHeightOffset = Vector(0, 0, 6)
local traceMin = Vector(-4, -4, -4)
local traceMax = Vector(4, 4, 4)
function PLUGIN:CalcView(client, origin)
local enterAngle = client:GetNetVar("actEnterAngle")
local fraction = self.cameraFraction
local offset = self.bIdle and forwardOffset or backwardOffset
local height = self.bIdle and idleHeightOffset or heightOffset
if (!enterAngle) then
if (fraction > 0) then
local view = {
origin = LerpVector(fraction, origin, origin + self.forward * offset + height)
}
if (self.cameraTween) then
self.cameraTween:update(FrameTime())
end
return view
end
return
end
local view = {}
local forward = enterAngle:Forward()
local head = GetHeadBone(client)
local bFirstPerson = true
if (ix.option.Get("thirdpersonEnabled", false)) then
local originPosition = head and client:GetBonePosition(head) or client:GetPos()
-- check if the camera will hit something
local data = util.TraceHull({
start = originPosition,
endpos = originPosition - client:EyeAngles():Forward() * 48,
mins = traceMin * 0.75,
maxs = traceMax * 0.75,
filter = client
})
bFirstPerson = data.Hit
if (!bFirstPerson) then
view.origin = data.HitPos
end
end
if (bFirstPerson) then
if (head) then
local position = client:GetBonePosition(head) + forward * offset + height
local data = {
start = (client:GetBonePosition(head) or Vector(0, 0, 64)) + forward * 8,
endpos = position + forward * offset,
mins = traceMin,
maxs = traceMax,
filter = client
}
data = util.TraceHull(data)
if (data.Hit) then
view.origin = data.HitPos
else
view.origin = position
end
else
view.origin = origin + forward * forwardOffset + height
end
end
view.origin = LerpVector(fraction, origin, view.origin)
if (self.cameraTween) then
self.cameraTween:update(FrameTime())
end
return view
end
net.Receive("ixActEnter", function()
PLUGIN.bIdle = net.ReadBool()
PLUGIN.forward = LocalPlayer():GetNetVar("actEnterAngle"):Forward()
PLUGIN.cameraTween = ix.tween.new(animationTime, PLUGIN, {
cameraFraction = 1
}, "outQuint")
end)
net.Receive("ixActLeave", function()
PLUGIN.cameraTween = ix.tween.new(animationTime * 0.5, PLUGIN, {
cameraFraction = 0
}, "outQuint")
end)

View File

@@ -0,0 +1,166 @@
local function FacingWall(client)
local data = {}
data.start = client:EyePos()
data.endpos = data.start + client:GetForward() * 20
data.filter = client
if (!util.TraceLine(data).Hit) then
return "@faceWall"
end
end
local function FacingWallBack(client)
local data = {}
data.start = client:LocalToWorld(client:OBBCenter())
data.endpos = data.start - client:GetForward() * 20
data.filter = client
if (!util.TraceLine(data).Hit) then
return "@faceWallBack"
end
end
function PLUGIN:SetupActs()
-- sit
ix.act.Register("Sit", {"citizen_male", "citizen_female"}, {
start = {"idle_to_sit_ground", "idle_to_sit_chair"},
sequence = {"sit_ground", "sit_chair"},
finish = {
{"sit_ground_to_idle", duration = 2.1},
""
},
untimed = true,
idle = true
})
ix.act.Register("SitWall", {"citizen_male", "citizen_female"}, {
sequence = {
{"plazaidle4", check = FacingWallBack},
{"injured1", check = FacingWallBack, offset = function(client)
return client:GetForward() * 14
end}
},
untimed = true,
idle = true
})
ix.act.Register("Sit", "vortigaunt", {
sequence = "chess_wait",
untimed = true,
idle = true
})
-- stand
ix.act.Register("Stand", "citizen_male", {
sequence = {"lineidle01", "lineidle02", "lineidle03", "lineidle04"},
untimed = true,
idle = true
})
ix.act.Register("Stand", "citizen_female", {
sequence = {"lineidle01", "lineidle02", "lineidle03"},
untimed = true,
idle = true
})
ix.act.Register("Stand", "metrocop", {
sequence = "plazathreat2"
})
-- cheer
ix.act.Register("Cheer", "citizen_male", {
sequence = {{"cheer1", duration = 1.6}, "cheer2", "wave_smg1"}
})
ix.act.Register("Cheer", "citizen_female", {
sequence = {"cheer1", "wave_smg1"}
})
-- lean
ix.act.Register("Lean", {"citizen_male", "citizen_female"}, {
start = {"idle_to_lean_back", "", ""},
sequence = {
{"lean_back", check = FacingWallBack},
{"plazaidle1", check = FacingWallBack},
{"plazaidle2", check = FacingWallBack}
},
untimed = true,
idle = true
})
ix.act.Register("Lean", {"metrocop"}, {
sequence = {{"idle_baton", check = FacingWallBack}, "busyidle2"},
untimed = true,
idle = true
})
-- injured
ix.act.Register("Injured", "citizen_male", {
sequence = {"d1_town05_wounded_idle_1", "d1_town05_wounded_idle_2", "d1_town05_winston_down"},
untimed = true,
idle = true
})
ix.act.Register("Injured", "citizen_female", {
sequence = "d1_town05_wounded_idle_1",
untimed = true,
idle = true
})
-- arrest
ix.act.Register("ArrestWall", "citizen_male", {
sequence = {
{"apcarrestidle",
check = FacingWall,
offset = function(client)
return -client:GetForward() * 23
end},
"spreadwallidle"
},
untimed = true
})
ix.act.Register("Arrest", "citizen_male", {
sequence = "arrestidle",
untimed = true
})
-- threat
ix.act.Register("Threat", "metrocop", {
sequence = "plazathreat1",
})
-- deny
ix.act.Register("Deny", "metrocop", {
sequence = "harassfront2",
})
-- motion
ix.act.Register("Motion", "metrocop", {
sequence = {"motionleft", "motionright", "luggagewarn"}
})
-- wave
ix.act.Register("Wave", {"citizen_male", "citizen_female"}, {
sequence = {{"wave", duration = 2.75}, {"wave_close", duration = 1.75}}
})
-- pant
ix.act.Register("Pant", {"citizen_male", "citizen_female"}, {
start = {"d2_coast03_postbattle_idle02_entry", "d2_coast03_postbattle_idle01_entry"},
sequence = {"d2_coast03_postbattle_idle02", {"d2_coast03_postbattle_idle01", check = FacingWall}},
untimed = true
})
-- window
ix.act.Register("Window", "citizen_male", {
sequence = "d1_t03_tenements_look_out_window_idle",
untimed = true
})
ix.act.Register("Window", "citizen_female", {
sequence = "d1_t03_lookoutwindow",
untimed = true
})
end

View File

@@ -0,0 +1,259 @@
--[[--
Provides players the ability to perform animations.
]]
-- @module ix.act
local PLUGIN = PLUGIN
PLUGIN.name = "Player Acts"
PLUGIN.description = "Adds animations that can be performed by certain models."
PLUGIN.author = "`impulse"
ix.act = ix.act or {}
ix.act.stored = ix.act.stored or {}
CAMI.RegisterPrivilege({
Name = "Helix - Player Acts",
MinAccess = "user"
})
--- Registers a sequence as a performable animation.
-- @realm shared
-- @string name Name of the animation (in CamelCase)
-- @string modelClass Model class to add this animation to
-- @tab data An `ActInfoStructure` table describing the animation
function ix.act.Register(name, modelClass, data)
ix.act.stored[name] = ix.act.stored[name] or {} -- might be adding onto an existing act
if (!data.sequence) then
return ErrorNoHalt(string.format(
"Act '%s' for '%s' tried to register without a provided sequence\n", name, modelClass
))
end
if (!istable(data.sequence)) then
data.sequence = {data.sequence}
end
if (data.start and istable(data.start) and #data.start != #data.sequence) then
return ErrorNoHalt(string.format(
"Act '%s' tried to register without matching number of enter sequences\n", name
))
end
if (data.finish and istable(data.finish) and #data.finish != #data.sequence) then
return ErrorNoHalt(string.format(
"Act '%s' tried to register without matching number of exit sequences\n", name
))
end
if (istable(modelClass)) then
for _, v in ipairs(modelClass) do
ix.act.stored[name][v] = data
end
else
ix.act.stored[name][modelClass] = data
end
end
--- Removes a sequence from being performable if it has been previously registered.
-- @realm shared
-- @string name Name of the animation
function ix.act.Remove(name)
ix.act.stored[name] = nil
ix.command.list["Act" .. name] = nil
end
ix.util.Include("sh_definitions.lua")
ix.util.Include("sv_hooks.lua")
ix.util.Include("cl_hooks.lua")
function PLUGIN:InitializedPlugins()
hook.Run("SetupActs")
hook.Run("PostSetupActs")
end
function PLUGIN:ExitAct(client)
client.ixUntimedSequence = nil
client:SetNetVar("actEnterAngle")
net.Start("ixActLeave")
net.Send(client)
end
function PLUGIN:PostSetupActs()
-- create chat commands for all stored acts
for act, classes in pairs(ix.act.stored) do
local variants = 1
local COMMAND = {
privilege = "Player Acts"
}
-- check if this act has any variants (i.e /ActSit 2)
for _, v in pairs(classes) do
if (#v.sequence > 1) then
variants = math.max(variants, #v.sequence)
end
end
-- setup command arguments if there are variants for this act
if (variants > 1) then
COMMAND.arguments = bit.bor(ix.type.number, ix.type.optional)
COMMAND.argumentNames = {"variant (1-" .. variants .. ")"}
end
COMMAND.GetDescription = function(command)
return L("cmdAct", act)
end
local privilege = "Helix - " .. COMMAND.privilege
-- we'll perform a model class check in OnCheckAccess to prevent the command from showing up on the client at all
COMMAND.OnCheckAccess = function(command, client)
local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil)
if (!bHasAccess) then
return false
end
local modelClass = ix.anim.GetModelClass(client:GetModel())
if (!classes[modelClass]) then
return false, "modelNoSeq"
end
return true
end
COMMAND.OnRun = function(command, client, variant)
variant = math.Clamp(tonumber(variant) or 1, 1, variants)
if (client:GetNetVar("actEnterAngle")) then
return "@notNow"
end
local modelClass = ix.anim.GetModelClass(client:GetModel())
local bCanEnter, error = PLUGIN:CanPlayerEnterAct(client, modelClass, variant, classes)
if (!bCanEnter) then
return error
end
local data = classes[modelClass]
local mainSequence = data.sequence[variant]
local mainDuration
-- check if the main sequence has any extra info
if (istable(mainSequence)) then
-- any validity checks to perform (i.e facing a wall)
if (mainSequence.check) then
local result = mainSequence.check(client)
if (result) then
return result
end
end
-- position offset
if (mainSequence.offset) then
client.ixOldPosition = client:GetPos()
client:SetPos(client:GetPos() + mainSequence.offset(client))
end
mainDuration = mainSequence.duration
mainSequence = mainSequence[1]
end
local startSequence = data.start and data.start[variant] or ""
local startDuration
if (istable(startSequence)) then
startDuration = startSequence.duration
startSequence = startSequence[1]
end
client:SetNetVar("actEnterAngle", client:GetAngles())
client:ForceSequence(startSequence, function()
-- we've finished the start sequence
client.ixUntimedSequence = data.untimed -- client can exit after the start sequence finishes playing
local duration = client:ForceSequence(mainSequence, function()
-- we've stopped playing the main sequence (either duration expired or user cancelled the act)
if (data.finish) then
local finishSequence = data.finish[variant]
local finishDuration
if (istable(finishSequence)) then
finishDuration = finishSequence.duration
finishSequence = finishSequence[1]
end
client:ForceSequence(finishSequence, function()
-- client has finished the end sequence and is no longer playing any animations
self:ExitAct(client)
end, finishDuration)
else
-- there's no end sequence so we can exit right away
self:ExitAct(client)
end
end, data.untimed and 0 or (mainDuration or nil))
if (!duration) then
-- the model doesn't support this variant
self:ExitAct(client)
client:NotifyLocalized("modelNoSeq")
return
end
end, startDuration, nil)
net.Start("ixActEnter")
net.WriteBool(data.idle or false)
net.Send(client)
client.ixNextAct = CurTime() + 4
end
ix.command.Add("Act" .. act, COMMAND)
end
-- setup exit act command
local COMMAND = {
privilege = "Player Acts",
OnRun = function(command, client)
if (client.ixUntimedSequence) then
client:LeaveSequence()
end
end
}
if (CLIENT) then
-- hide this command from the command list
COMMAND.OnCheckAccess = function(client)
return false
end
end
ix.command.Add("ExitAct", COMMAND)
end
function PLUGIN:UpdateAnimation(client, moveData)
local angle = client:GetNetVar("actEnterAngle")
if (angle) then
client:SetRenderAngles(angle)
end
end
do
local keyBlacklist = IN_ATTACK + IN_ATTACK2
function PLUGIN:StartCommand(client, command)
if (client:GetNetVar("actEnterAngle")) then
command:RemoveKey(keyBlacklist)
end
end
end

View File

@@ -0,0 +1,52 @@
local PLUGIN = PLUGIN
util.AddNetworkString("ixActEnter")
util.AddNetworkString("ixActLeave")
function PLUGIN:CanPlayerEnterAct(client, modelClass, variant, act)
if (!client:Alive() or client:GetLocalVar("ragdoll") or client:WaterLevel() > 0 or !client:IsOnGround()) then
return false, L("notNow", client)
end
-- check if player's model class has an entry in this act table
modelClass = modelClass or ix.anim.GetModelClass(client:GetModel())
local data = act[modelClass]
if (!data) then
return false, L("modelNoSeq", client)
end
-- some models don't support certain variants
local sequence = data.sequence[variant]
if (!sequence) then
return false, L("modelNoSeq", client)
end
return true
end
function PLUGIN:PlayerDeath(client)
if (client.ixUntimedSequence) then
client:SetNetVar("actEnterAngle")
client:LeaveSequence()
client.ixUntimedSequence = nil
end
end
function PLUGIN:PlayerSpawn(client)
if (client.ixUntimedSequence) then
client:SetNetVar("actEnterAngle")
client:LeaveSequence()
client.ixUntimedSequence = nil
end
end
function PLUGIN:OnCharacterFallover(client)
if (client.ixUntimedSequence) then
client:SetNetVar("actEnterAngle")
client:LeaveSequence()
client.ixUntimedSequence = nil
end
end

View File

@@ -0,0 +1,89 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Ammo Saver"
PLUGIN.author = "Black Tea"
PLUGIN.description = "Saves the ammo of a character."
PLUGIN.ammoList = {}
ix.ammo = ix.ammo or {}
function ix.ammo.Register(name)
name = name:lower()
if (!table.HasValue(PLUGIN.ammoList, name)) then
PLUGIN.ammoList[#PLUGIN.ammoList + 1] = name
end
end
-- Register Default HL2 Ammunition.
ix.ammo.Register("ar2")
ix.ammo.Register("pistol")
ix.ammo.Register("357")
ix.ammo.Register("smg1")
ix.ammo.Register("xbowbolt")
ix.ammo.Register("buckshot")
ix.ammo.Register("rpg_round")
ix.ammo.Register("smg1_grenade")
ix.ammo.Register("grenade")
ix.ammo.Register("ar2altfire")
ix.ammo.Register("slam")
-- Register Cut HL2 Ammunition.
ix.ammo.Register("alyxgun")
ix.ammo.Register("sniperround")
ix.ammo.Register("sniperpenetratedround")
ix.ammo.Register("thumper")
ix.ammo.Register("gravity")
ix.ammo.Register("battery")
ix.ammo.Register("gaussenergy")
ix.ammo.Register("combinecannon")
ix.ammo.Register("airboatgun")
ix.ammo.Register("striderminigun")
ix.ammo.Register("helicoptergun")
-- Called right before the character has its information save.
function PLUGIN:CharacterPreSave(character)
-- Get the player from the character.
local client = character:GetPlayer()
-- Check to see if we can get the player's ammo.
if (IsValid(client)) then
local ammoTable = {}
for _, v in ipairs(self.ammoList) do
local ammo = client:GetAmmoCount(v)
if (ammo > 0) then
ammoTable[v] = ammo
end
end
character:SetData("ammo", ammoTable)
end
end
-- Called after the player's loadout has been set.
function PLUGIN:PlayerLoadedCharacter(client)
timer.Simple(0.25, function()
if (!IsValid(client)) then
return
end
-- Get the saved ammo table from the character data.
local character = client:GetCharacter()
if (!character) then
return
end
local ammoTable = character:GetData("ammo")
-- Check if the ammotable is exists.
if (ammoTable) then
for k, v in pairs(ammoTable) do
client:SetAmmo(v, tostring(k))
end
end
end)
end

View File

@@ -0,0 +1,258 @@
local PLUGIN = PLUGIN
local function DrawTextBackground(x, y, text, font, backgroundColor, padding)
font = font or "ixSubTitleFont"
padding = padding or 8
backgroundColor = backgroundColor or Color(88, 88, 88, 255)
surface.SetFont(font)
local textWidth, textHeight = surface.GetTextSize(text)
local width, height = textWidth + padding * 2, textHeight + padding * 2
ix.util.DrawBlurAt(x, y, width, height)
surface.SetDrawColor(0, 0, 0, 40)
surface.DrawRect(x, y, width, height)
derma.SkinFunc("DrawImportantBackground", x, y, width, height, backgroundColor)
surface.SetTextColor(color_white)
surface.SetTextPos(x + padding, y + padding)
surface.DrawText(text)
return height
end
function PLUGIN:InitPostEntity()
hook.Run("SetupAreaProperties")
end
function PLUGIN:ChatboxCreated()
if (IsValid(self.panel)) then
self.panel:Remove()
end
self.panel = vgui.Create("ixArea")
end
function PLUGIN:ChatboxPositionChanged(x, y, width, height)
if (!IsValid(self.panel)) then
return
end
self.panel:SetSize(width, y)
self.panel:SetPos(32, 0)
end
function PLUGIN:ShouldDrawCrosshair()
if (ix.area.bEditing) then
return true
end
end
function PLUGIN:PlayerBindPress(client, bind, bPressed)
if (!ix.area.bEditing) then
return
end
if ((bind:find("invnext") or bind:find("invprev")) and bPressed) then
return true
elseif (bind:find("attack2") and bPressed) then
self:EditRightClick()
return true
elseif (bind:find("attack") and bPressed) then
self:EditClick()
return true
elseif (bind:find("reload") and bPressed) then
self:EditReload()
return true
end
end
function PLUGIN:HUDPaint()
if (!ix.area.bEditing) then
return
end
local id = LocalPlayer():GetArea()
local area = ix.area.stored[id]
local height = ScrH()
local y = 64
y = y + DrawTextBackground(64, y, L("areaEditMode"), nil, ix.config.Get("color"))
if (!self.editStart) then
y = y + DrawTextBackground(64, y, L("areaEditTip"), "ixSmallTitleFont")
DrawTextBackground(64, y, L("areaRemoveTip"), "ixSmallTitleFont")
else
DrawTextBackground(64, y, L("areaFinishTip"), "ixSmallTitleFont")
end
if (area) then
DrawTextBackground(64, height - 64 - ScreenScale(12), id, "ixSmallTitleFont", area.properties.color)
end
end
function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox)
if (bSkybox or !ix.area.bEditing) then
return
end
-- draw all areas
for k, v in pairs(ix.area.stored) do
local center, min, max = self:GetLocalAreaPosition(v.startPosition, v.endPosition)
local color = ColorAlpha(v.properties.color or ix.config.Get("color"), 255)
render.DrawWireframeBox(center, angle_zero, min, max, color)
cam.Start2D()
local centerScreen = center:ToScreen()
local _, textHeight = draw.SimpleText(
k, "BudgetLabel", centerScreen.x, centerScreen.y, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if (v.type != "area") then
draw.SimpleText(
"(" .. L(v.type) .. ")", "BudgetLabel",
centerScreen.x, centerScreen.y + textHeight, color, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER
)
end
cam.End2D()
end
-- draw currently edited area
if (self.editStart) then
local center, min, max = self:GetLocalAreaPosition(self.editStart, self:GetPlayerAreaTrace().HitPos)
local color = Color(255, 255, 255, 25 + (1 + math.sin(SysTime() * 6)) * 115)
render.DrawWireframeBox(center, angle_zero, min, max, color)
cam.Start2D()
local centerScreen = center:ToScreen()
draw.SimpleText(L("areaNew"), "BudgetLabel",
centerScreen.x, centerScreen.y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
cam.End2D()
end
end
function PLUGIN:EditRightClick()
if (self.editStart) then
self.editStart = nil
else
self:StopEditing()
end
end
function PLUGIN:EditClick()
if (!self.editStart) then
self.editStart = LocalPlayer():GetEyeTraceNoCursor().HitPos
elseif (self.editStart and !self.editProperties) then
self.editProperties = true
local panel = vgui.Create("ixAreaEdit")
panel:MakePopup()
end
end
function PLUGIN:EditReload()
if (self.editStart) then
return
end
local id = LocalPlayer():GetArea()
local area = ix.area.stored[id]
if (!area) then
return
end
Derma_Query(L("areaDeleteConfirm", id), L("areaDelete"),
L("no"), nil,
L("yes"), function()
net.Start("ixAreaRemove")
net.WriteString(id)
net.SendToServer()
end
)
end
function PLUGIN:ShouldDisplayArea(id)
if (ix.area.bEditing) then
return false
end
end
function PLUGIN:OnAreaChanged(oldID, newID)
local client = LocalPlayer()
client.ixArea = newID
local area = ix.area.stored[newID]
if (!area) then
client.ixInArea = false
return
end
client.ixInArea = true
if (hook.Run("ShouldDisplayArea", newID) == false or !area.properties.display) then
return
end
local format = newID .. (ix.option.Get("24hourTime", false) and ", %H:%M." or ", %I:%M %p.")
format = ix.date.GetFormatted(format)
self.panel:AddEntry(format, area.properties.color)
end
net.Receive("ixAreaEditStart", function()
PLUGIN:StartEditing()
end)
net.Receive("ixAreaEditEnd", function()
PLUGIN:StopEditing()
end)
net.Receive("ixAreaAdd", function()
local name = net.ReadString()
local type = net.ReadString()
local startPosition, endPosition = net.ReadVector(), net.ReadVector()
local properties = net.ReadTable()
if (name != "") then
ix.area.stored[name] = {
type = type,
startPosition = startPosition,
endPosition = endPosition,
properties = properties
}
end
end)
net.Receive("ixAreaRemove", function()
local name = net.ReadString()
if (ix.area.stored[name]) then
ix.area.stored[name] = nil
end
end)
net.Receive("ixAreaSync", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress area data!\n")
return
end
-- Set the list of texts to the ones provided by the server.
ix.area.stored = util.JSONToTable(uncompressed)
end)
net.Receive("ixAreaChanged", function()
local oldID, newID = net.ReadString(), net.ReadString()
hook.Run("OnAreaChanged", oldID, newID)
end)

View File

@@ -0,0 +1,26 @@
local PLUGIN = PLUGIN
function PLUGIN:GetPlayerAreaTrace()
local client = LocalPlayer()
return util.TraceLine({
start = client:GetShootPos(),
endpos = client:GetShootPos() + client:GetForward() * 96,
filter = client
})
end
function PLUGIN:StartEditing()
ix.area.bEditing = true
self.editStart = nil
self.editProperties = nil
end
function PLUGIN:StopEditing()
ix.area.bEditing = false
if (IsValid(ix.gui.areaEdit)) then
ix.gui.areaEdit:Remove()
end
end

View File

@@ -0,0 +1,182 @@
-- area entry
DEFINE_BASECLASS("Panel")
local PANEL = {}
AccessorFunc(PANEL, "text", "Text", FORCE_STRING)
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
AccessorFunc(PANEL, "tickSound", "TickSound", FORCE_STRING)
AccessorFunc(PANEL, "tickSoundRange", "TickSoundRange")
AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha", FORCE_NUMBER)
AccessorFunc(PANEL, "expireTime", "ExpireTime", FORCE_NUMBER)
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
function PANEL:Init()
self:DockPadding(4, 4, 4, 4)
self:SetSize(self:GetParent():GetWide(), 0)
self.label = self:Add("DLabel")
self.label:Dock(FILL)
self.label:SetFont("ixMediumLightFont")
self.label:SetTextColor(color_white)
self.label:SetExpensiveShadow(1, color_black)
self.label:SetText("Area")
self.text = ""
self.tickSound = "ui/buttonrollover.wav"
self.tickSoundRange = {190, 200}
self.backgroundAlpha = 255
self.expireTime = 8
self.animationTime = 2
self.character = 1
self.createTime = RealTime()
self.currentAlpha = 255
self.currentHeight = 0
self.nextThink = RealTime()
end
function PANEL:Show()
self:CreateAnimation(0.5, {
index = -1,
target = {currentHeight = self.label:GetTall() + 8},
easing = "outQuint",
Think = function(animation, panel)
panel:SetTall(panel.currentHeight)
end
})
end
function PANEL:SetFont(font)
self.label:SetFont(font)
end
function PANEL:SetText(text)
if (text:sub(1, 1) == "@") then
text = L(text:sub(2))
end
self.label:SetText(text)
self.text = text
self.character = 1
end
function PANEL:Think()
local time = RealTime()
if (time >= self.nextThink) then
if (self.character < self.text:utf8len()) then
self.character = self.character + 1
self.label:SetText(string.utf8sub(self.text, 1, self.character))
LocalPlayer():EmitSound(self.tickSound, 100, math.random(self.tickSoundRange[1], self.tickSoundRange[2]))
end
if (time >= self.createTime + self.expireTime and !self.bRemoving) then
self:Remove()
end
self.nextThink = time + 0.05
end
end
function PANEL:SizeToContents()
self:SetWide(self:GetParent():GetWide())
self.label:SetWide(self:GetWide())
self.label:SizeToContentsY()
end
function PANEL:Paint(width, height)
self.backgroundAlpha = math.max(self.backgroundAlpha - 200 * FrameTime(), 0)
derma.SkinFunc("PaintAreaEntry", self, width, height)
end
function PANEL:Remove()
if (self.bRemoving) then
return
end
self:CreateAnimation(self.animationTime, {
target = {currentAlpha = 0},
Think = function(animation, panel)
panel:SetAlpha(panel.currentAlpha)
end,
OnComplete = function(animation, panel)
panel:CreateAnimation(0.5, {
index = -1,
target = {currentHeight = 0},
easing = "outQuint",
Think = function(_, sizePanel)
sizePanel:SetTall(sizePanel.currentHeight)
end,
OnComplete = function(_, sizePanel)
sizePanel:OnRemove()
BaseClass.Remove(sizePanel)
end
})
end
})
self.bRemoving = true
end
function PANEL:OnRemove()
end
vgui.Register("ixAreaEntry", PANEL, "Panel")
-- main panel
PANEL = {}
function PANEL:Init()
local chatWidth, _ = chat.GetChatBoxSize()
local _, chatY = chat.GetChatBoxPos()
self:SetSize(chatWidth, chatY)
self:SetPos(32, 0)
self:ParentToHUD()
self.entries = {}
ix.gui.area = self
end
function PANEL:AddEntry(entry, color)
color = color or ix.config.Get("color")
local id = #self.entries + 1
local panel = entry
if (isstring(entry)) then
panel = self:Add("ixAreaEntry")
panel:SetText(entry)
end
panel:SetBackgroundColor(color)
panel:SizeToContents()
panel:Dock(BOTTOM)
panel:Show()
panel.OnRemove = function()
for k, v in pairs(self.entries) do
if (v == panel) then
table.remove(self.entries, k)
break
end
end
end
self.entries[id] = panel
return id
end
function PANEL:GetEntries()
return self.entries
end
vgui.Register("ixArea", PANEL, "Panel")

View File

@@ -0,0 +1,194 @@
local PLUGIN = PLUGIN
local PANEL = {}
function PANEL:Init()
if (IsValid(ix.gui.areaEdit)) then
ix.gui.areaEdit:Remove()
end
ix.gui.areaEdit = self
self.list = {}
self.properties = {}
self:SetDeleteOnClose(true)
self:SetSizable(true)
self:SetTitle(L("areaNew"))
-- scroll panel
self.canvas = self:Add("DScrollPanel")
self.canvas:Dock(FILL)
-- name entry
self.nameEntry = vgui.Create("ixTextEntry")
self.nameEntry:SetFont("ixMediumLightFont")
self.nameEntry:SetText(L("areaNew"))
local listRow = self.canvas:Add("ixListRow")
listRow:SetList(self.list)
listRow:SetLabelText(L("name"))
listRow:SetRightPanel(self.nameEntry)
listRow:Dock(TOP)
listRow:SizeToContents()
-- type entry
self.typeEntry = self.canvas:Add("DComboBox")
self.typeEntry:Dock(RIGHT)
self.typeEntry:SetFont("ixMediumLightFont")
self.typeEntry:SetTextColor(color_black)
self.typeEntry.OnSelect = function(panel)
panel:SizeToContents()
panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice)
end
for id, name in pairs(ix.area.types) do
self.typeEntry:AddChoice(L(name), id, id == "area")
end
listRow = self.canvas:Add("ixListRow")
listRow:SetList(self.list)
listRow:SetLabelText(L("type"))
listRow:SetRightPanel(self.typeEntry)
listRow:Dock(TOP)
listRow:SizeToContents()
-- properties
for k, v in pairs(ix.area.properties) do
local panel
if (v.type == ix.type.string or v.type == ix.type.number) then
panel = vgui.Create("ixTextEntry")
panel:SetFont("ixMenuButtonFont")
panel:SetText(tostring(v.default))
if (v.type == ix.type.number) then
panel.realGetValue = panel.GetValue
panel.GetValue = function()
return tonumber(panel:realGetValue()) or v.default
end
end
elseif (v.type == ix.type.bool) then
panel = vgui.Create("ixCheckBox")
panel:SetChecked(v.default, true)
panel:SetFont("ixMediumLightFont")
elseif (v.type == ix.type.color) then
panel = vgui.Create("DButton")
panel.value = v.default
panel:SetText("")
panel:SetSize(64, 64)
panel.picker = vgui.Create("DColorCombo")
panel.picker:SetColor(panel.value)
panel.picker:SetVisible(false)
panel.picker.OnValueChanged = function(_, newColor)
panel.value = newColor
end
panel.Paint = function(_, width, height)
surface.SetDrawColor(0, 0, 0, 255)
surface.DrawOutlinedRect(0, 0, width, height)
surface.SetDrawColor(panel.value)
surface.DrawRect(4, 4, width - 8, height - 8)
end
panel.DoClick = function()
if (!panel.picker:IsVisible()) then
local x, y = panel:LocalToScreen(0, 0)
panel.picker:SetPos(x, y + 32)
panel.picker:SetColor(panel.value)
panel.picker:SetVisible(true)
panel.picker:MakePopup()
else
panel.picker:SetVisible(false)
end
end
panel.OnRemove = function()
panel.picker:Remove()
end
panel.GetValue = function()
return panel.picker:GetColor()
end
end
if (IsValid(panel)) then
local row = self.canvas:Add("ixListRow")
row:SetList(self.list)
row:SetLabelText(L(k))
row:SetRightPanel(panel)
row:Dock(TOP)
row:SizeToContents()
end
self.properties[k] = function()
return panel:GetValue()
end
end
-- save button
self.saveButton = self:Add("DButton")
self.saveButton:SetText(L("save"))
self.saveButton:SizeToContents()
self.saveButton:Dock(BOTTOM)
self.saveButton.DoClick = function()
self:Submit()
end
self:SizeToContents()
self:SetPos(64, 0)
self:CenterVertical()
end
function PANEL:SizeToContents()
local width = 600
local height = 37
for _, v in ipairs(self.canvas:GetCanvas():GetChildren()) do
width = math.max(width, v:GetLabelWidth())
height = height + v:GetTall()
end
self:SetWide(width + 200)
self:SetTall(height + self.saveButton:GetTall() + 50)
end
function PANEL:Submit()
local name = self.nameEntry:GetValue()
if (ix.area.stored[name]) then
ix.util.NotifyLocalized("areaAlreadyExists")
return
end
local properties = {}
for k, v in pairs(self.properties) do
properties[k] = v()
end
local _, type = self.typeEntry:GetSelected()
net.Start("ixAreaAdd")
net.WriteString(name)
net.WriteString(type)
net.WriteVector(PLUGIN.editStart)
net.WriteVector(PLUGIN:GetPlayerAreaTrace().HitPos)
net.WriteTable(properties)
net.SendToServer()
PLUGIN.editStart = nil
self:Remove()
end
function PANEL:OnRemove()
PLUGIN.editProperties = nil
end
vgui.Register("ixAreaEdit", PANEL, "DFrame")
if (IsValid(ix.gui.areaEdit)) then
ix.gui.areaEdit:Remove()
end

View File

@@ -0,0 +1,16 @@
LANGUAGE = {
area = "Area",
areas = "Areas",
areaEditMode = "Area Edit Mode",
areaNew = "New Area",
areaAlreadyExists = "An area with this name already exists!",
areaDoesntExist = "An area with that name doesn't exist!",
areaInvalidType = "You have specified an invalid area type!",
areaEditTip = "Click to start creating an area. Right click to exit.",
areaFinishTip = "Click again to finish drawing the area. Right click to go back.",
areaRemoveTip = "Press reload to remove the area you're currently in.",
areaDeleteConfirm = "Are you sure you want to delete the area \"%s\"?",
areaDelete = "Delete Area",
cmdAreaEdit = "Enters area edit mode."
}

View File

@@ -0,0 +1,16 @@
LANGUAGE = {
area = "Зона",
areas = "Зоны",
areaEditMode = "Редактирование зоны",
areaNew = "Новая зона",
areaAlreadyExists = "Зона с таким названием уже существует!",
areaDoesntExist = "Зона с таким названием не существует!",
areaInvalidType = "Вы указали неверный тип зоны!",
areaEditTip = "Нажмите, чтобы начать создание зоны. Щелкните правой кнопкой мыши, чтобы выйти.",
areaFinishTip = "Нажмите еще раз, чтобы закончить создание зоны. Щелкните правой кнопкой мыши, чтобы вернуться.",
areaRemoveTip = "Нажмите перезарядку, чтобы удалить зону, в которой вы находитесь.",
areaDeleteConfirm = "Вы уверены, что хотите удалить зону \"%s\"?",
areaDelete = "Удалить зону",
cmdAreaEdit = "Вход в режим редактирования зоны."
}

View File

@@ -0,0 +1,90 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Areas"
PLUGIN.author = "`impulse"
PLUGIN.description = "Provides customizable area definitions."
ix.area = ix.area or {}
ix.area.types = ix.area.types or {}
ix.area.properties = ix.area.properties or {}
ix.area.stored = ix.area.stored or {}
ix.config.Add("areaTickTime", 1, "How many seconds between each time a character's current area is calculated.",
function(oldValue, newValue)
if (SERVER) then
timer.Remove("ixAreaThink")
timer.Create("ixAreaThink", newValue, 0, function()
PLUGIN:AreaThink()
end)
end
end,
{
data = {min = 0.1, max = 4},
category = "areas"
}
)
function ix.area.AddProperty(name, type, default, data)
ix.area.properties[name] = {
type = type,
default = default
}
end
function ix.area.AddType(type, name)
name = name or type
-- only store localized strings on the client
ix.area.types[type] = CLIENT and name or true
end
function PLUGIN:SetupAreaProperties()
ix.area.AddType("area")
ix.area.AddProperty("color", ix.type.color, ix.config.Get("color"))
ix.area.AddProperty("display", ix.type.bool, true)
end
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")
ix.util.Include("sv_hooks.lua")
ix.util.Include("cl_hooks.lua")
-- return world center, local min, and local max from world start/end positions
function PLUGIN:GetLocalAreaPosition(startPosition, endPosition)
local center = LerpVector(0.5, startPosition, endPosition)
local min = WorldToLocal(startPosition, angle_zero, center, angle_zero)
local max = WorldToLocal(endPosition, angle_zero, center, angle_zero)
return center, min, max
end
do
local COMMAND = {}
COMMAND.description = "@cmdAreaEdit"
COMMAND.adminOnly = true
function COMMAND:OnRun(client)
client:SetWepRaised(false)
net.Start("ixAreaEditStart")
net.Send(client)
end
ix.command.Add("AreaEdit", COMMAND)
end
do
local PLAYER = FindMetaTable("Player")
-- returns the current area the player is in, or the last valid one if the player is not in an area
function PLAYER:GetArea()
return self.ixArea
end
-- returns true if the player is in any area, this does not use the last valid area like GetArea does
function PLAYER:IsInArea()
return self.ixInArea
end
end

View File

@@ -0,0 +1,126 @@
function PLUGIN:LoadData()
hook.Run("SetupAreaProperties")
ix.area.stored = self:GetData() or {}
timer.Create("ixAreaThink", ix.config.Get("areaTickTime"), 0, function()
self:AreaThink()
end)
end
function PLUGIN:SaveData()
self:SetData(ix.area.stored)
end
function PLUGIN:PlayerInitialSpawn(client)
timer.Simple(1, function()
if (IsValid(client)) then
local json = util.TableToJSON(ix.area.stored)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixAreaSync")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
end)
end
function PLUGIN:PlayerLoadedCharacter(client)
client.ixArea = ""
client.ixInArea = nil
end
function PLUGIN:PlayerSpawn(client)
client.ixArea = ""
client.ixInArea = nil
end
function PLUGIN:AreaThink()
for _, client in player.Iterator() do
local character = client:GetCharacter()
if (!client:Alive() or !character) then
continue
end
local overlappingBoxes = {}
local position = client:GetPos() + client:OBBCenter()
for id, info in pairs(ix.area.stored) do
if (position:WithinAABox(info.startPosition, info.endPosition)) then
overlappingBoxes[#overlappingBoxes + 1] = id
end
end
if (#overlappingBoxes > 0) then
local oldID = client:GetArea()
local id = overlappingBoxes[1]
if (oldID != id) then
hook.Run("OnPlayerAreaChanged", client, client.ixArea, id)
client.ixArea = id
end
client.ixInArea = true
else
client.ixInArea = false
end
end
end
function PLUGIN:OnPlayerAreaChanged(client, oldID, newID)
net.Start("ixAreaChanged")
net.WriteString(oldID)
net.WriteString(newID)
net.Send(client)
end
net.Receive("ixAreaAdd", function(length, client)
if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then
return
end
local id = net.ReadString()
local type = net.ReadString()
local startPosition, endPosition = net.ReadVector(), net.ReadVector()
local properties = net.ReadTable()
if (!ix.area.types[type]) then
client:NotifyLocalized("areaInvalidType")
return
end
if (ix.area.stored[id]) then
client:NotifyLocalized("areaAlreadyExists")
return
end
for k, v in pairs(properties) do
if (!isstring(k) or !ix.area.properties[k]) then
continue
end
properties[k] = ix.util.SanitizeType(ix.area.properties[k].type, v)
end
ix.area.Create(id, type, startPosition, endPosition, nil, properties)
ix.log.Add(client, "areaAdd", id)
end)
net.Receive("ixAreaRemove", function(length, client)
if (!client:Alive() or !CAMI.PlayerHasAccess(client, "Helix - AreaEdit", nil)) then
return
end
local id = net.ReadString()
if (!ix.area.stored[id]) then
client:NotifyLocalized("areaDoesntExist")
return
end
ix.area.Remove(id)
ix.log.Add(client, "areaRemove", id)
end)

View File

@@ -0,0 +1,55 @@
util.AddNetworkString("ixAreaSync")
util.AddNetworkString("ixAreaAdd")
util.AddNetworkString("ixAreaRemove")
util.AddNetworkString("ixAreaChanged")
util.AddNetworkString("ixAreaEditStart")
util.AddNetworkString("ixAreaEditEnd")
ix.log.AddType("areaAdd", function(client, name)
return string.format("%s has added area \"%s\".", client:Name(), tostring(name))
end)
ix.log.AddType("areaRemove", function(client, name)
return string.format("%s has removed area \"%s\".", client:Name(), tostring(name))
end)
local function SortVector(first, second)
return Vector(math.min(first.x, second.x), math.min(first.y, second.y), math.min(first.z, second.z)),
Vector(math.max(first.x, second.x), math.max(first.y, second.y), math.max(first.z, second.z))
end
function ix.area.Create(name, type, startPosition, endPosition, bNoReplicate, properties)
local min, max = SortVector(startPosition, endPosition)
ix.area.stored[name] = {
type = type or "area",
startPosition = min,
endPosition = max,
bNoReplicate = bNoReplicate,
properties = properties
}
-- network to clients if needed
if (!bNoReplicate) then
net.Start("ixAreaAdd")
net.WriteString(name)
net.WriteString(type)
net.WriteVector(startPosition)
net.WriteVector(endPosition)
net.WriteTable(properties)
net.Broadcast()
end
end
function ix.area.Remove(name, bNoReplicate)
ix.area.stored[name] = nil
-- network to clients if needed
if (!bNoReplicate) then
net.Start("ixAreaRemove")
net.WriteString(name)
net.Broadcast()
end
end

View File

@@ -0,0 +1,180 @@
ENT.Type = "anim"
ENT.PrintName = "Container"
ENT.Category = "Helix"
ENT.Spawnable = false
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("Int", 0, "ID")
self:NetworkVar("Bool", 0, "Locked")
self:NetworkVar("String", 0, "DisplayName")
end
if (SERVER) then
function ENT:Initialize()
self:PhysicsInit(SOLID_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self.receivers = {}
local definition = ix.container.stored[self:GetModel():lower()]
if (definition) then
self:SetDisplayName(definition.name)
end
local physObj = self:GetPhysicsObject()
if (IsValid(physObj)) then
physObj:EnableMotion(true)
physObj:Wake()
end
end
function ENT:SetInventory(inventory)
if (inventory) then
self:SetID(inventory:GetID())
end
end
function ENT:SetMoney(amount)
self.money = math.max(0, math.Round(tonumber(amount) or 0))
end
function ENT:GetMoney()
return self.money or 0
end
function ENT:OnRemove()
local index = self:GetID()
if (!ix.shuttingDown and !self.ixIsSafe and ix.entityDataLoaded and index) then
local inventory = ix.item.inventories[index]
if (inventory) then
ix.item.inventories[index] = nil
local query = mysql:Delete("ix_items")
query:Where("inventory_id", index)
query:Execute()
query = mysql:Delete("ix_inventories")
query:Where("inventory_id", index)
query:Execute()
hook.Run("ContainerRemoved", self, inventory)
end
end
end
function ENT:OpenInventory(activator)
local inventory = self:GetInventory()
if (inventory) then
local name = self:GetDisplayName()
local definition = ix.container.stored[self:GetModel():lower()]
ix.storage.Open(activator, inventory, {
name = name,
entity = self,
searchTime = ix.config.Get("containerOpenTime", 0.7),
data = {money = self:GetMoney()},
OnPlayerOpen = function()
if (definition.OnOpen) then
definition.OnOpen(self, activator)
end
end,
OnPlayerClose = function()
if (definition.OnClose) then
definition.OnClose(self, activator)
end
ix.log.Add(activator, "closeContainer", name, inventory:GetID())
end
})
if (self:GetLocked()) then
self.Sessions[activator:GetCharacter():GetID()] = true
end
ix.log.Add(activator, "openContainer", name, inventory:GetID())
end
end
function ENT:Use(activator)
local inventory = self:GetInventory()
if (inventory and (activator.ixNextOpen or 0) < CurTime()) then
local character = activator:GetCharacter()
if (character) then
local definition = ix.container.stored[self:GetModel():lower()]
if (self:GetLocked() and !self.Sessions[character:GetID()]) then
self:EmitSound(definition.locksound or "doors/default_locked.wav")
if (!self.keypad) then
net.Start("ixContainerPassword")
net.WriteEntity(self)
net.Send(activator)
end
else
self:OpenInventory(activator)
end
end
activator.ixNextOpen = CurTime() + 1
end
end
else
ENT.PopulateEntityInfo = true
local COLOR_LOCKED = Color(200, 38, 19, 200)
local COLOR_UNLOCKED = Color(135, 211, 124, 200)
function ENT:OnPopulateEntityInfo(tooltip)
local definition = ix.container.stored[self:GetModel():lower()]
local bLocked = self:GetLocked()
surface.SetFont("ixIconsSmall")
local iconText = bLocked and "P" or "Q"
local iconWidth, iconHeight = surface.GetTextSize(iconText)
-- minimal tooltips have centered text so we'll draw the icon above the name instead
if (tooltip:IsMinimal()) then
local icon = tooltip:AddRow("icon")
icon:SetFont("ixIconsSmall")
icon:SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED)
icon:SetText(iconText)
icon:SizeToContents()
end
local title = tooltip:AddRow("name")
title:SetImportant()
title:SetText(self:GetDisplayName())
title:SetBackgroundColor(ix.config.Get("color"))
title:SetTextInset(iconWidth + 8, 0)
title:SizeToContents()
if (!tooltip:IsMinimal()) then
title.Paint = function(panel, width, height)
panel:PaintBackground(width, height)
surface.SetFont("ixIconsSmall")
surface.SetTextColor(bLocked and COLOR_LOCKED or COLOR_UNLOCKED)
surface.SetTextPos(4, height * 0.5 - iconHeight * 0.5)
surface.DrawText(iconText)
end
end
local description = tooltip:AddRow("description")
description:SetText(definition.description)
description:SizeToContents()
end
end
function ENT:GetInventory()
return ix.item.inventories[self:GetID()]
end

View File

@@ -0,0 +1,126 @@
--[[
ix.container.Register(model, {
name = "Crate",
description = "A simple wooden create.",
width = 4,
height = 4,
locksound = "",
opensound = ""
})
]]--
ix.container.Register("models/props_junk/wood_crate001a.mdl", {
name = "Crate",
description = "A simple wooden crate.",
width = 4,
height = 4,
})
ix.container.Register("models/props_c17/lockers001a.mdl", {
name = "Locker",
description = "A white locker.",
width = 3,
height = 5,
})
ix.container.Register("models/props_wasteland/controlroom_storagecloset001a.mdl", {
name = "Metal Cabinet",
description = "A green metal cabinet.",
width = 4,
height = 5,
})
ix.container.Register("models/props_wasteland/controlroom_storagecloset001b.mdl", {
name = "Metal Cabinet",
description = "A green metal cabinet.",
width = 4,
height = 5,
})
ix.container.Register("models/props_wasteland/controlroom_filecabinet001a.mdl", {
name = "File Cabinet",
description = "A metal filing cabinet.",
width = 5,
height = 3
})
ix.container.Register("models/props_wasteland/controlroom_filecabinet002a.mdl", {
name = "File Cabinet",
description = "A metal filing cabinet.",
width = 3,
height = 6,
})
ix.container.Register("models/props_lab/filecabinet02.mdl", {
name = "File Cabinet",
description = "A metal filing cabinet.",
width = 5,
height = 3
})
ix.container.Register("models/props_c17/furniturefridge001a.mdl", {
name = "Refrigerator",
description = "A metal box for keeping food in.",
width = 2,
height = 3,
})
ix.container.Register("models/props_wasteland/kitchen_fridge001a.mdl", {
name = "Large Refrigerator",
description = "A large metal box for storing even more food in.",
width = 4,
height = 5,
})
ix.container.Register("models/props_junk/trashbin01a.mdl", {
name = "Trash Bin",
description = "What do you expect to find in here?",
width = 2,
height = 2,
})
ix.container.Register("models/props_junk/trashdumpster01a.mdl", {
name = "Dumpster",
description = "A dumpster meant to stow away trash. It emanates an unpleasant smell.",
width = 6,
height = 3
})
ix.container.Register("models/items/ammocrate_smg1.mdl", {
name = "Ammo Crate",
description = "A heavy crate that stores ammo.",
width = 5,
height = 3,
OnOpen = function(entity, activator)
local closeSeq = entity:LookupSequence("Close")
entity:ResetSequence(closeSeq)
timer.Simple(2, function()
if (entity and IsValid(entity)) then
local openSeq = entity:LookupSequence("Open")
entity:ResetSequence(openSeq)
end
end)
end
})
ix.container.Register("models/props_forest/footlocker01_closed.mdl", {
name = "Footlocker",
description = "A small chest to store belongings in.",
width = 5,
height = 3
})
ix.container.Register("models/Items/item_item_crate.mdl", {
name = "Item Crate",
description = "A crate to store some belongings in.",
width = 5,
height = 3
})
ix.container.Register("models/props_c17/cashregister01a.mdl", {
name = "Cash Register",
description = "A register with some buttons and a drawer.",
width = 2,
height = 1
})

View File

@@ -0,0 +1,346 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Containers"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Provides the ability to store items."
ix.container = ix.container or {}
ix.container.stored = ix.container.stored or {}
ix.config.Add("containerSave", true, "Whether or not containers will save after a server restart.", nil, {
category = "Containers"
})
ix.config.Add("containerOpenTime", 0.7, "How long it takes to open a container.", nil, {
data = {min = 0, max = 50},
category = "Containers"
})
function ix.container.Register(model, data)
ix.container.stored[model:lower()] = data
end
ix.util.Include("sh_definitions.lua")
if (SERVER) then
util.AddNetworkString("ixContainerPassword")
function PLUGIN:PlayerSpawnedProp(client, model, entity)
model = tostring(model):lower()
local data = ix.container.stored[model]
if (data) then
if (hook.Run("CanPlayerSpawnContainer", client, model, entity) == false) then return end
local container = ents.Create("ix_container")
container:SetPos(entity:GetPos())
container:SetAngles(entity:GetAngles())
container:SetModel(model)
container:Spawn()
ix.inventory.New(0, "container:" .. model:lower(), function(inventory)
-- we'll technically call this a bag since we don't want other bags to go inside
inventory.vars.isBag = true
inventory.vars.isContainer = true
if (IsValid(container)) then
container:SetInventory(inventory)
self:SaveContainer()
end
end)
entity:Remove()
end
end
function PLUGIN:CanSaveContainer(entity, inventory)
return ix.config.Get("containerSave", true)
end
function PLUGIN:SaveContainer()
local data = {}
for _, v in ipairs(ents.FindByClass("ix_container")) do
if (hook.Run("CanSaveContainer", v, v:GetInventory()) != false) then
local inventory = v:GetInventory()
if (inventory) then
data[#data + 1] = {
v:GetPos(),
v:GetAngles(),
inventory:GetID(),
v:GetModel(),
v.password,
v:GetDisplayName(),
v:GetMoney()
}
end
else
local index = v:GetID()
local query = mysql:Delete("ix_items")
query:Where("inventory_id", index)
query:Execute()
query = mysql:Delete("ix_inventories")
query:Where("inventory_id", index)
query:Execute()
end
end
self:SetData(data)
end
function PLUGIN:SaveData()
if (!ix.shuttingDown) then
self:SaveContainer()
end
end
function PLUGIN:ContainerRemoved(entity, inventory)
self:SaveContainer()
end
function PLUGIN:LoadData()
local data = self:GetData()
if (data) then
for _, v in ipairs(data) do
local data2 = ix.container.stored[v[4]:lower()]
if (data2) then
local inventoryID = tonumber(v[3])
if (!inventoryID or inventoryID < 1) then
ErrorNoHalt(string.format(
"[Helix] Attempted to restore container inventory with invalid inventory ID '%s' (%s, %s)\n",
tostring(inventoryID), v[6] or "no name", v[4] or "no model"))
continue
end
local entity = ents.Create("ix_container")
entity:SetPos(v[1])
entity:SetAngles(v[2])
entity:Spawn()
entity:SetModel(v[4])
entity:SetSolid(SOLID_VPHYSICS)
entity:PhysicsInit(SOLID_VPHYSICS)
if (v[5]) then
entity.password = v[5]
entity:SetLocked(true)
entity.Sessions = {}
entity.PasswordAttempts = {}
end
if (v[6]) then
entity:SetDisplayName(v[6])
end
if (v[7]) then
entity:SetMoney(v[7])
end
ix.inventory.Restore(inventoryID, data2.width, data2.height, function(inventory)
inventory.vars.isBag = true
inventory.vars.isContainer = true
if (IsValid(entity)) then
entity:SetInventory(inventory)
end
end)
local physObject = entity:GetPhysicsObject()
if (IsValid(physObject)) then
physObject:EnableMotion()
end
end
end
end
end
net.Receive("ixContainerPassword", function(length, client)
if ((client.ixNextContainerPassword or 0) > RealTime()) then
return
end
local entity = net.ReadEntity()
local steamID = client:SteamID()
local attempts = entity.PasswordAttempts[steamID]
if (attempts and attempts >= 10) then
client:NotifyLocalized("passwordAttemptLimit")
return
end
local password = net.ReadString()
local dist = entity:GetPos():DistToSqr(client:GetPos())
if (dist < 16384 and password) then
if (entity.password and entity.password == password) then
entity:OpenInventory(client)
else
entity.PasswordAttempts[steamID] = attempts and attempts + 1 or 1
client:NotifyLocalized("wrongPassword")
end
end
client.ixNextContainerPassword = RealTime() + 1
end)
ix.log.AddType("containerPassword", function(client, ...)
local arg = {...}
return string.format("%s has %s the password for '%s'.", client:Name(), arg[3] and "set" or "removed", arg[1], arg[2])
end)
ix.log.AddType("containerName", function(client, ...)
local arg = {...}
if (arg[3]) then
return string.format("%s has set container %d name to '%s'.", client:Name(), arg[2], arg[1])
else
return string.format("%s has removed container %d name.", client:Name(), arg[2])
end
end)
ix.log.AddType("openContainer", function(client, ...)
local arg = {...}
return string.format("%s opened the '%s' #%d container.", client:Name(), arg[1], arg[2])
end, FLAG_NORMAL)
ix.log.AddType("closeContainer", function(client, ...)
local arg = {...}
return string.format("%s closed the '%s' #%d container.", client:Name(), arg[1], arg[2])
end, FLAG_NORMAL)
else
net.Receive("ixContainerPassword", function(length)
local entity = net.ReadEntity()
Derma_StringRequest(
L("containerPasswordWrite"),
L("containerPasswordWrite"),
"",
function(val)
net.Start("ixContainerPassword")
net.WriteEntity(entity)
net.WriteString(val)
net.SendToServer()
end
)
end)
end
function PLUGIN:InitializedPlugins()
for k, v in pairs(ix.container.stored) do
if (v.name and v.width and v.height) then
ix.inventory.Register("container:" .. k:lower(), v.width, v.height)
else
ErrorNoHalt("[Helix] Container for '"..k.."' is missing all inventory information!\n")
ix.container.stored[k] = nil
end
end
end
-- properties
properties.Add("container_setpassword", {
MenuLabel = "Set Password",
Order = 400,
MenuIcon = "icon16/lock_edit.png",
Filter = function(self, entity, client)
if (entity:GetClass() != "ix_container") then return false end
if (!gamemode.Call("CanProperty", client, "container_setpassword", entity)) then return false end
return true
end,
Action = function(self, entity)
Derma_StringRequest(L("containerPasswordWrite"), "", "", function(text)
self:MsgStart()
net.WriteEntity(entity)
net.WriteString(text)
self:MsgEnd()
end)
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
local password = net.ReadString()
entity.Sessions = {}
entity.PasswordAttempts = {}
if (password:len() != 0) then
entity:SetLocked(true)
entity.password = password
client:NotifyLocalized("containerPassword", password)
else
entity:SetLocked(false)
entity.password = nil
client:NotifyLocalized("containerPasswordRemove")
end
local name = entity:GetDisplayName()
local inventory = entity:GetInventory()
ix.log.Add(client, "containerPassword", name, inventory:GetID(), password:len() != 0)
end
})
properties.Add("container_setname", {
MenuLabel = "Set Name",
Order = 400,
MenuIcon = "icon16/tag_blue_edit.png",
Filter = function(self, entity, client)
if (entity:GetClass() != "ix_container") then return false end
if (!gamemode.Call("CanProperty", client, "container_setname", entity)) then return false end
return true
end,
Action = function(self, entity)
Derma_StringRequest(L("containerNameWrite"), "", "", function(text)
self:MsgStart()
net.WriteEntity(entity)
net.WriteString(text)
self:MsgEnd()
end)
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
local name = net.ReadString()
if (name:len() != 0) then
entity:SetDisplayName(name)
client:NotifyLocalized("containerName", name)
else
local definition = ix.container.stored[entity:GetModel():lower()]
entity:SetDisplayName(definition.name)
client:NotifyLocalized("containerNameRemove")
end
local inventory = entity:GetInventory()
ix.log.Add(client, "containerName", name, inventory:GetID(), name:len() != 0)
end
})

View File

@@ -0,0 +1,110 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Crosshair"
PLUGIN.author = "Black Tea"
PLUGIN.description = "A Crosshair."
if (CLIENT) then
local function drawdot( pos, size, col )
local color = col[2]
surface.SetDrawColor(color.r, color.g, color.b, color.a)
surface.DrawRect(pos[1] - size/2, pos[2] - size/2, size, size)
color = col[1]
surface.SetDrawColor(color.r, color.g, color.b, color.a)
surface.DrawOutlinedRect(pos[1] - size/2, pos[2] - size/2 , size, size)
end
local aimVector, punchAngle, ft, screen, scaleFraction, distance
local math_round = math.Round
local curGap = 0
local curAlpha = 0
local maxDistance = 1000 ^ 2
local crossSize = 4
local crossGap = 0
local colors = {color_black}
local filter = {}
function PLUGIN:DrawCrosshair(x, y, trace)
local entity = trace.Entity
distance = trace.StartPos:DistToSqr(trace.HitPos)
scaleFraction = 1 - math.Clamp(distance / maxDistance, 0, .5)
crossSize = 4
crossGap = 25 * (scaleFraction - (LocalPlayer():IsWepRaised() and 0 or .1))
if (IsValid(entity) and entity:GetClass() == "ix_item" and
entity:GetPos():DistToSqr(trace.StartPos) <= 16384) then
crossGap = 0
crossSize = 5
end
curGap = Lerp(ft * 2, curGap, crossGap)
curAlpha = Lerp(ft * 2, curAlpha, !LocalPlayer():IsWepRaised() and 255 or 150)
curAlpha = hook.Run("GetCrosshairAlpha", curAlpha) or curAlpha
colors[2] = Color(255, curAlpha, curAlpha, curAlpha)
if (curAlpha > 1) then
drawdot( {math_round(screen.x), math_round(screen.y)}, crossSize, colors)
drawdot( {math_round(screen.x + curGap), math_round(screen.y)}, crossSize, colors)
drawdot( {math_round(screen.x - curGap), math_round(screen.y)}, crossSize, colors)
drawdot( {math_round(screen.x), math_round(screen.y + curGap * .8)}, crossSize, colors)
drawdot( {math_round(screen.x), math_round(screen.y - curGap * .8)}, crossSize, colors)
end
end
-- luacheck: globals g_ContextMenu
function PLUGIN:PostDrawHUD()
local client = LocalPlayer()
if (!client:GetCharacter() or !client:Alive()) then
return
end
local entity = Entity(client:GetLocalVar("ragdoll", 0))
if (entity:IsValid()) then
return
end
local wep = client:GetActiveWeapon()
local bShouldDraw = hook.Run("ShouldDrawCrosshair", client, wep)
if (bShouldDraw == false or !IsValid(wep) or wep.DrawCrosshair == false) then
return
end
if (bShouldDraw == false or g_ContextMenu:IsVisible() or
(IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing())) then
return
end
aimVector = client:EyeAngles()
punchAngle = client:GetViewPunchAngles()
ft = FrameTime()
filter = {client}
local vehicle = client:GetVehicle()
if (vehicle and IsValid(vehicle)) then
aimVector = aimVector + vehicle:GetAngles()
table.insert(filter, vehicle)
end
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + (aimVector + punchAngle):Forward() * 65535
data.filter = filter
local trace = util.TraceLine(data)
local drawTarget = self
local drawFunction = self.DrawCrosshair
-- we'll manually call this since CHudCrosshair is never drawn; checks are already performed
if (wep.DoDrawCrosshair) then
drawTarget = wep
drawFunction = wep.DoDrawCrosshair
end
screen = trace.HitPos:ToScreen()
drawFunction(drawTarget, screen.x, screen.y, trace)
end
end

View File

@@ -0,0 +1,204 @@
-- luacheck: globals ACCESS_LABELS
ACCESS_LABELS = {}
ACCESS_LABELS[DOOR_OWNER] = "owner"
ACCESS_LABELS[DOOR_TENANT] = "tenant"
ACCESS_LABELS[DOOR_GUEST] = "guest"
ACCESS_LABELS[DOOR_NONE] = "none"
function PLUGIN:GetDefaultDoorInfo(door)
local owner = IsValid(door:GetDTEntity(0)) and door:GetDTEntity(0) or nil
local name = door:GetNetVar("title", door:GetNetVar("name", IsValid(owner) and L"dTitleOwned" or L"dTitle"))
local description = door:GetNetVar("ownable") and L("dIsOwnable") or L("dIsNotOwnable")
local color = ix.config.Get("color")
local faction = door:GetNetVar("faction")
local class = door:GetNetVar("class")
if (class) then
local classData = ix.class.list[class]
if (classData) then
if (classData.color) then
color = classData.color
end
if (!owner) then
description = L("dOwnedBy", L2(classData.name) or classData.name)
end
end
elseif (faction) then
local info = ix.faction.indices[faction]
color = team.GetColor(faction)
if (info and !owner) then
description = L("dOwnedBy", L2(info.name) or info.name)
end
end
if (owner) then
description = L("dOwnedBy", owner:GetName())
end
return {
name = name,
description = description,
color = color
}
end
function PLUGIN:DrawDoorInfo(door, width, position, angles, scale, clientPosition)
local alpha = math.max((1 - clientPosition:DistToSqr(door:GetPos()) / 65536) * 255, 0)
if (alpha < 1) then
return
end
local info = hook.Run("GetDoorInfo", door) or self:GetDefaultDoorInfo(door)
if (!istable(info) or table.IsEmpty(info)) then
return
end
-- title + background
surface.SetFont("ix3D2DMediumFont")
local nameWidth, nameHeight = surface.GetTextSize(info.name)
derma.SkinFunc("DrawImportantBackground", -width * 0.5, -nameHeight * 0.5,
width, nameHeight, ColorAlpha(info.color, alpha * 0.5))
surface.SetTextColor(ColorAlpha(color_white, alpha))
surface.SetTextPos(-nameWidth * 0.5, -nameHeight * 0.5)
surface.DrawText(info.name)
-- description
local lines = ix.util.WrapText(info.description, width, "ix3D2DSmallFont")
local y = nameHeight * 0.5 + 4
for i = 1, #lines do
local line = lines[i]
local textWidth, textHeight = surface.GetTextSize(line)
surface.SetTextPos(-textWidth * 0.5, y)
surface.DrawText(line)
y = y + textHeight
end
-- background blur
ix.util.PushBlur(function()
cam.Start3D2D(position, angles, scale)
surface.SetDrawColor(11, 11, 11, math.max(alpha - 100, 0))
surface.DrawRect(-width * 0.5, -nameHeight * 0.5, width, y + nameHeight * 0.5 + 4)
cam.End3D2D()
end)
end
function PLUGIN:PostDrawTranslucentRenderables(bDepth, bSkybox)
if (bDepth or bSkybox or !LocalPlayer():GetCharacter()) then
return
end
local entities = ents.FindInSphere(EyePos(), 256)
local clientPosition = LocalPlayer():GetPos()
for _, v in ipairs(entities) do
if (!IsValid(v) or !v:IsDoor() or !v:GetNetVar("visible")) then
continue
end
local color = v:GetColor()
if (v:IsEffectActive(EF_NODRAW) or color.a <= 0) then
continue
end
local position = v:LocalToWorld(v:OBBCenter())
local mins, maxs = v:GetCollisionBounds()
local width = 0
local size = maxs - mins
local trace = {
collisiongroup = COLLISION_GROUP_WORLD,
ignoreworld = true,
endpos = position
}
-- trace from shortest side to center to get correct position for rendering
if (size.z < size.x and size.z < size.y) then
trace.start = position - v:GetUp() * size.z
width = size.y
elseif (size.x < size.y) then
trace.start = position - v:GetForward() * size.x
width = size.y
elseif (size.y < size.x) then
trace.start = position - v:GetRight() * size.y
width = size.x
end
width = math.max(width, 12)
trace = util.TraceLine(trace)
local angles = trace.HitNormal:Angle()
local anglesOpposite = trace.HitNormal:Angle()
angles:RotateAroundAxis(angles:Forward(), 90)
angles:RotateAroundAxis(angles:Right(), 90)
anglesOpposite:RotateAroundAxis(anglesOpposite:Forward(), 90)
anglesOpposite:RotateAroundAxis(anglesOpposite:Right(), -90)
local positionFront = trace.HitPos - (((position - trace.HitPos):Length() * 2) + 1) * trace.HitNormal
local positionOpposite = trace.HitPos + (trace.HitNormal * 2)
if (trace.HitNormal:Dot((clientPosition - position):GetNormalized()) < 0) then
-- draw front
cam.Start3D2D(positionFront, angles, 0.1)
self:DrawDoorInfo(v, width * 8, positionFront, angles, 0.1, clientPosition)
cam.End3D2D()
else
-- draw back
cam.Start3D2D(positionOpposite, anglesOpposite, 0.1)
self:DrawDoorInfo(v, width * 8, positionOpposite, anglesOpposite, 0.1, clientPosition)
cam.End3D2D()
end
end
end
net.Receive("ixDoorMenu", function()
if (IsValid(ix.gui.door)) then
return ix.gui.door:Remove()
end
local door = net.ReadEntity()
local access = net.ReadTable()
if (IsValid(door) and !table.IsEmpty(access)) then
local entity = net.ReadEntity()
ix.gui.door = vgui.Create("ixDoorMenu")
ix.gui.door:SetDoor(door, access, entity)
end
end)
net.Receive("ixDoorPermission", function()
local door = net.ReadEntity()
if (!IsValid(door)) then
return
end
local target = net.ReadEntity()
local access = net.ReadUInt(4)
local panel = door.ixPanel
if (IsValid(panel) and IsValid(target)) then
panel.access[target] = access
for _, v in ipairs(panel.access:GetLines()) do
if (v.player == target) then
v:SetColumnText(2, L(ACCESS_LABELS[access or 0]))
return
end
end
end
end)

View File

@@ -0,0 +1,102 @@
local PANEL = {}
local function DoorSetPermission(door, target, permission)
net.Start("ixDoorPermission")
net.WriteEntity(door)
net.WriteEntity(target)
net.WriteUInt(permission, 4)
net.SendToServer()
end
function PANEL:Init()
self:SetSize(280, 240)
self:SetTitle(L"doorSettings")
self:Center()
self:MakePopup()
self.access = self:Add("DListView")
self.access:Dock(FILL)
self.access:AddColumn(L"name").Header:SetTextColor(Color(25, 25, 25))
self.access:AddColumn(L"access").Header:SetTextColor(Color(25, 25, 25))
self.access.OnClickLine = function(this, line, selected)
if (IsValid(line.player)) then
local menu = DermaMenu()
menu:AddOption(L"tenant", function()
if (self.accessData and self.accessData[line.player] != DOOR_TENANT) then
DoorSetPermission(self.door, line.player, DOOR_TENANT)
end
end):SetImage("icon16/user_add.png")
menu:AddOption(L"guest", function()
if (self.accessData and self.accessData[line.player] != DOOR_GUEST) then
DoorSetPermission(self.door, line.player, DOOR_GUEST)
end
end):SetImage("icon16/user_green.png")
menu:AddOption(L"none", function()
if (self.accessData and self.accessData[line.player] != DOOR_NONE) then
DoorSetPermission(self.door, line.player, DOOR_NONE)
end
end):SetImage("icon16/user_red.png")
menu:Open()
end
end
end
function PANEL:SetDoor(door, access, door2)
door.ixPanel = self
self.accessData = access
self.door = door
for _, v in player.Iterator() do
if (v != LocalPlayer() and v:GetCharacter()) then
self.access:AddLine(v:Name(), L(ACCESS_LABELS[access[v] or 0])).player = v
end
end
if (self:CheckAccess(DOOR_OWNER)) then
self.sell = self:Add("DButton")
self.sell:Dock(BOTTOM)
self.sell:SetText(L"sell")
self.sell:SetTextColor(color_white)
self.sell:DockMargin(0, 5, 0, 0)
self.sell.DoClick = function(this)
self:Remove()
ix.command.Send("doorsell")
end
end
if (self:CheckAccess(DOOR_TENANT)) then
self.name = self:Add("DTextEntry")
self.name:Dock(TOP)
self.name:DockMargin(0, 0, 0, 5)
self.name.Think = function(this)
if (!this:IsEditing()) then
local entity = IsValid(door2) and door2 or door
self.name:SetText(entity:GetNetVar("title", L"dTitleOwned"))
end
end
self.name.OnEnter = function(this)
ix.command.Send("doorsettitle", this:GetText())
end
end
end
function PANEL:CheckAccess(access)
access = access or DOOR_GUEST
if ((self.accessData[LocalPlayer()] or 0) >= access) then
return true
end
return false
end
function PANEL:Think()
if (self.accessData and !IsValid(self.door) and self:CheckAccess()) then
self:Remove()
end
end
vgui.Register("ixDoorMenu", PANEL, "DFrame")

View File

@@ -0,0 +1,201 @@
AddCSLuaFile()
if (CLIENT) then
SWEP.PrintName = "Keys"
SWEP.Slot = 0
SWEP.SlotPos = 2
SWEP.DrawAmmo = false
SWEP.DrawCrosshair = false
end
SWEP.Author = "Chessnut"
SWEP.Instructions = "Primary Fire: Lock\nSecondary Fire: Unlock"
SWEP.Purpose = "Hitting things and knocking on doors."
SWEP.Drop = false
SWEP.ViewModelFOV = 45
SWEP.ViewModelFlip = false
SWEP.AnimPrefix = "rpg"
SWEP.ViewTranslation = 4
SWEP.Primary.ClipSize = -1
SWEP.Primary.DefaultClip = -1
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = ""
SWEP.Primary.Damage = 5
SWEP.Primary.Delay = 0.75
SWEP.Secondary.ClipSize = -1
SWEP.Secondary.DefaultClip = 0
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = ""
SWEP.ViewModel = Model("models/weapons/c_arms_animations.mdl")
SWEP.WorldModel = ""
SWEP.UseHands = false
SWEP.LowerAngles = Angle(0, 5, -14)
SWEP.LowerAngles2 = Angle(0, 5, -22)
SWEP.IsAlwaysLowered = true
SWEP.FireWhenLowered = true
SWEP.HoldType = "passive"
-- luacheck: globals ACT_VM_FISTS_DRAW ACT_VM_FISTS_HOLSTER
ACT_VM_FISTS_DRAW = 2
ACT_VM_FISTS_HOLSTER = 1
function SWEP:Holster()
if (!IsValid(self.Owner)) then
return
end
local viewModel = self.Owner:GetViewModel()
if (IsValid(viewModel)) then
viewModel:SetPlaybackRate(1)
viewModel:ResetSequence(ACT_VM_FISTS_HOLSTER)
end
return true
end
function SWEP:Precache()
end
function SWEP:Initialize()
self:SetHoldType(self.HoldType)
end
function SWEP:PrimaryAttack()
local time = ix.config.Get("doorLockTime", 1)
local time2 = math.max(time, 1)
self:SetNextPrimaryFire(CurTime() + time2)
self:SetNextSecondaryFire(CurTime() + time2)
if (!IsFirstTimePredicted()) then
return
end
if (CLIENT) then
return
end
local data = {}
data.start = self.Owner:GetShootPos()
data.endpos = data.start + self.Owner:GetAimVector()*96
data.filter = self.Owner
local entity = util.TraceLine(data).Entity
--[[
Locks the entity if the contiditon fits:
1. The entity is door and client has access to the door.
2. The entity is vehicle and the "owner" variable is same as client's character ID.
--]]
if (IsValid(entity) and
(
(entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or
(entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner)
)
) then
self.Owner:SetAction("@locking", time, function()
self:ToggleLock(entity, true)
end)
return
end
end
function SWEP:ToggleLock(door, state)
if (IsValid(self.Owner) and self.Owner:GetPos():Distance(door:GetPos()) > 96) then
return
end
if (door:IsDoor()) then
local partner = door:GetDoorPartner()
if (state) then
if (IsValid(partner)) then
partner:Fire("lock")
end
door:Fire("lock")
self.Owner:EmitSound("doors/door_latch3.wav")
hook.Run("PlayerLockedDoor", self.Owner, door, partner)
else
if (IsValid(partner)) then
partner:Fire("unlock")
end
door:Fire("unlock")
self.Owner:EmitSound("doors/door_latch1.wav")
hook.Run("PlayerUnlockedDoor", self.Owner, door, partner)
end
elseif (door:IsVehicle()) then
if (state) then
door:Fire("lock")
if (door.IsSimfphyscar) then
door.IsLocked = true
end
self.Owner:EmitSound("doors/door_latch3.wav")
hook.Run("PlayerLockedVehicle", self.Owner, door)
else
door:Fire("unlock")
if (door.IsSimfphyscar) then
door.IsLocked = nil
end
self.Owner:EmitSound("doors/door_latch1.wav")
hook.Run("PlayerUnlockedVehicle", self.Owner, door)
end
end
end
function SWEP:SecondaryAttack()
local time = ix.config.Get("doorLockTime", 1)
local time2 = math.max(time, 1)
self:SetNextPrimaryFire(CurTime() + time2)
self:SetNextSecondaryFire(CurTime() + time2)
if (!IsFirstTimePredicted()) then
return
end
if (CLIENT) then
return
end
local data = {}
data.start = self.Owner:GetShootPos()
data.endpos = data.start + self.Owner:GetAimVector()*96
data.filter = self.Owner
local entity = util.TraceLine(data).Entity
--[[
Unlocks the entity if the contiditon fits:
1. The entity is door and client has access to the door.
2. The entity is vehicle and the "owner" variable is same as client's character ID.
]]--
if (IsValid(entity) and
(
(entity:IsDoor() and entity:CheckDoorAccess(self.Owner)) or
(entity:IsVehicle() and entity.CPPIGetOwner and entity:CPPIGetOwner() == self.Owner)
)
) then
self.Owner:SetAction("@unlocking", time, function()
self:ToggleLock(entity, false)
end)
return
end
end

View File

@@ -0,0 +1,497 @@
local PLUGIN = PLUGIN
ix.command.Add("DoorSell", {
description = "@cmdDoorSell",
OnRun = function(self, client, arguments)
-- Get the entity 96 units infront of the player.
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
-- Check if the entity is a valid door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Check if the player owners the door.
if (client == entity:GetDTEntity(0)) then
entity = IsValid(entity.ixParent) and entity.ixParent or entity
-- Get the price that the door is sold for.
local price = math.Round(entity:GetNetVar("price", ix.config.Get("doorCost")) * ix.config.Get("doorSellRatio"))
local character = client:GetCharacter()
-- Remove old door information.
entity:RemoveDoorAccessData()
local doors = character:GetVar("doors") or {}
for k, v in ipairs(doors) do
if (v == entity) then
table.remove(doors, k)
end
end
character:SetVar("doors", doors, true)
-- Take their money and notify them.
character:GiveMoney(price)
hook.Run("OnPlayerPurchaseDoor", client, entity, false, PLUGIN.CallOnDoorChildren)
ix.log.Add(client, "selldoor")
return "@dSold", ix.currency.Get(price)
else
-- Otherwise tell them they can not.
return "@notOwner"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorBuy", {
description = "@cmdDoorBuy",
OnRun = function(self, client, arguments)
-- Get the entity 96 units infront of the player.
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
-- Check if the entity is a valid door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (!entity:GetNetVar("ownable") or entity:GetNetVar("faction") or entity:GetNetVar("class")) then
return "@dNotAllowedToOwn"
end
if (IsValid(entity:GetDTEntity(0))) then
return "@dOwnedBy", entity:GetDTEntity(0):Name()
end
entity = IsValid(entity.ixParent) and entity.ixParent or entity
-- Get the price that the door is bought for.
local price = entity:GetNetVar("price", ix.config.Get("doorCost"))
local character = client:GetCharacter()
-- Check if the player can actually afford it.
if (character:HasMoney(price)) then
-- Set the door to be owned by this player.
entity:SetDTEntity(0, client)
entity.ixAccess = {
[client] = DOOR_OWNER
}
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetDTEntity(0, client)
end)
local doors = character:GetVar("doors") or {}
doors[#doors + 1] = entity
character:SetVar("doors", doors, true)
-- Take their money and notify them.
character:TakeMoney(price)
hook.Run("OnPlayerPurchaseDoor", client, entity, true, PLUGIN.CallOnDoorChildren)
ix.log.Add(client, "buydoor")
return "@dPurchased", ix.currency.Get(price)
else
-- Otherwise tell them they can not.
return "@canNotAfford"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetUnownable", {
description = "@cmdDoorSetUnownable",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.text,
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Set it so it is unownable.
entity:SetNetVar("ownable", nil)
-- Change the name of the door if needed.
if (name:find("%S")) then
entity:SetNetVar("name", name)
end
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("ownable", nil)
if (name:find("%S")) then
child:SetNetVar("name", name)
end
end)
-- Save the door information.
PLUGIN:SaveDoorData()
return "@dMadeUnownable"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetOwnable", {
description = "@cmdDoorSetOwnable",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.text,
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Set it so it is ownable.
entity:SetNetVar("ownable", true)
entity:SetNetVar("visible", true)
-- Update the name.
if (name:find("%S")) then
entity:SetNetVar("name", name)
end
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("ownable", true)
child:SetNetVar("visible", true)
if (name:find("%S")) then
child:SetNetVar("name", name)
end
end)
-- Save the door information.
PLUGIN:SaveDoorData()
return "@dMadeOwnable"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetFaction", {
description = "@cmdDoorSetFaction",
privilege = "Manage Doors",
adminOnly = true,
arguments = bit.bor(ix.type.text, ix.type.optional),
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (!name or name == "") then
entity.ixFactionID = nil
entity:SetNetVar("faction", nil)
PLUGIN:CallOnDoorChildren(entity, function()
entity.ixFactionID = nil
entity:SetNetVar("faction", nil)
end)
PLUGIN:SaveDoorData()
return "@dRemoveFaction"
end
local faction
-- Loop through each faction, checking the uniqueID and name.
for k, v in pairs(ix.faction.teams) do
if (ix.util.StringMatches(k, name) or ix.util.StringMatches(L(v.name, client), name)) then
-- This faction matches the provided string.
faction = v
-- Escape the loop.
break
end
end
-- Check if a faction was found.
if (faction) then
entity.ixFactionID = faction.uniqueID
entity:SetNetVar("faction", faction.index)
PLUGIN:CallOnDoorChildren(entity, function()
entity.ixFactionID = faction.uniqueID
entity:SetNetVar("faction", faction.index)
end)
PLUGIN:SaveDoorData()
return "@dSetFaction", L(faction.name, client)
-- The faction was not found.
else
return "@invalidFaction"
end
end
end
})
ix.command.Add("DoorSetDisabled", {
description = "@cmdDoorSetDisabled",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.bool,
OnRun = function(self, client, bDisabled)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor()) then
-- Set it so it is ownable.
entity:SetNetVar("disabled", bDisabled)
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("disabled", bDisabled)
end)
PLUGIN:SaveDoorData()
-- Tell the player they have made the door (un)disabled.
return "@dSet" .. (bDisabled and "" or "Not") .. "Disabled"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetTitle", {
description = "@cmdDoorSetTitle",
arguments = ix.type.text,
OnRun = function(self, client, name)
-- Get the door infront of the player.
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
-- Validate the door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
-- Make sure the name contains actual characters.
if (!name:find("%S")) then
return "@invalidArg", 1
end
--[[
NOTE: Here, we are setting two different networked names.
The title is a temporary name, while the other name is the
default name for the door. The reason for this is so when the
server closes while someone owns the door, it doesn't save THEIR
title, which could lead to unwanted things.
--]]
name = name:utf8sub(1, 24)
-- Check if they are allowed to change the door's name.
if (entity:CheckDoorAccess(client, DOOR_TENANT)) then
entity:SetNetVar("title", name)
elseif (CAMI.PlayerHasAccess(client, "Helix - Manage Doors", nil)) then
entity:SetNetVar("name", name)
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("name", name)
end)
else
-- Otherwise notify the player he/she can't.
return "@notOwner"
end
else
-- Notification of the door not being valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetParent", {
description = "@cmdDoorSetParent",
privilege = "Manage Doors",
adminOnly = true,
OnRun = function(self, client, arguments)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
client.ixDoorParent = entity
return "@dSetParentDoor"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetChild", {
description = "@cmdDoorSetChild",
privilege = "Manage Doors",
adminOnly = true,
OnRun = function(self, client, arguments)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (client.ixDoorParent == entity) then
return "@dCanNotSetAsChild"
end
-- Check if the player has set a door as a parent.
if (IsValid(client.ixDoorParent)) then
-- Add the door to the parent's list of children.
client.ixDoorParent.ixChildren = client.ixDoorParent.ixChildren or {}
client.ixDoorParent.ixChildren[entity:MapCreationID()] = true
-- Set the door's parent to the parent.
entity.ixParent = client.ixDoorParent
-- Save the door information.
PLUGIN:SaveDoorData()
PLUGIN:CopyParentDoor(entity)
return "@dAddChildDoor"
else
-- Tell the player they do not have a door parent.
return "@dNoParentDoor"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorRemoveChild", {
description = "@cmdDoorRemoveChild",
privilege = "Manage Doors",
adminOnly = true,
OnRun = function(self, client, arguments)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (client.ixDoorParent == entity) then
PLUGIN:CallOnDoorChildren(entity, function(child)
child.ixParent = nil
end)
entity.ixChildren = nil
return "@dRemoveChildren"
end
-- Check if the player has set a door as a parent.
if (IsValid(entity.ixParent) and entity.ixParent.ixChildren) then
-- Remove the door from the list of children.
entity.ixParent.ixChildren[entity:MapCreationID()] = nil
-- Remove the variable for the parent.
entity.ixParent = nil
PLUGIN:SaveDoorData()
return "@dRemoveChildDoor"
end
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetHidden", {
description = "@cmdDoorSetHidden",
privilege = "Manage Doors",
adminOnly = true,
arguments = ix.type.bool,
OnRun = function(self, client, bHidden)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor()) then
entity:SetNetVar("visible", !bHidden)
PLUGIN:CallOnDoorChildren(entity, function(child)
child:SetNetVar("visible", !bHidden)
end)
PLUGIN:SaveDoorData()
-- Tell the player they have made the door (un)hidden.
return "@dSet" .. (bHidden and "" or "Not") .. "Hidden"
else
-- Tell the player the door isn't valid.
return "@dNotValid"
end
end
})
ix.command.Add("DoorSetClass", {
description = "@cmdDoorSetClass",
privilege = "Manage Doors",
adminOnly = true,
arguments = bit.bor(ix.type.text, ix.type.optional),
OnRun = function(self, client, name)
-- Get the door the player is looking at.
local entity = client:GetEyeTrace().Entity
-- Validate it is a door.
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("disabled")) then
if (!name or name == "") then
entity:SetNetVar("class", nil)
PLUGIN:CallOnDoorChildren(entity, function()
entity:SetNetVar("class", nil)
end)
PLUGIN:SaveDoorData()
return "@dRemoveClass"
end
local class, classData
for k, v in pairs(ix.class.list) do
if (ix.util.StringMatches(v.name, name) or ix.util.StringMatches(L(v.name, client), name)) then
class, classData = k, v
break
end
end
-- Check if a faction was found.
if (class) then
entity.ixClassID = class
entity:SetNetVar("class", class)
PLUGIN:CallOnDoorChildren(entity, function()
entity.ixClassID = class
entity:SetNetVar("class", class)
end)
PLUGIN:SaveDoorData()
return "@dSetClass", L(classData.name, client)
else
return "@invalidClass"
end
end
end
})

View File

@@ -0,0 +1,83 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Doors"
PLUGIN.author = "Chessnut"
PLUGIN.description = "A simple door system."
-- luacheck: globals DOOR_OWNER DOOR_TENANT DOOR_GUEST DOOR_NONE
DOOR_OWNER = 3
DOOR_TENANT = 2
DOOR_GUEST = 1
DOOR_NONE = 0
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")
ix.util.Include("sh_commands.lua")
do
local entityMeta = FindMetaTable("Entity")
function entityMeta:CheckDoorAccess(client, access)
if (!self:IsDoor()) then
return false
end
access = access or DOOR_GUEST
local parent = self.ixParent
if (IsValid(parent)) then
return parent:CheckDoorAccess(client, access)
end
if (hook.Run("CanPlayerAccessDoor", client, self, access)) then
return true
end
if (self.ixAccess and (self.ixAccess[client] or 0) >= access) then
return true
end
return false
end
if (SERVER) then
function entityMeta:RemoveDoorAccessData()
local receivers = {}
for k, _ in pairs(self.ixAccess or {}) do
receivers[#receivers + 1] = k
end
if (#receivers > 0) then
net.Start("ixDoorMenu")
net.WriteEntity(self)
net.WriteTable({})
net.Send(receivers)
end
self.ixAccess = {}
self:SetDTEntity(0, nil)
-- Remove door information on child doors
PLUGIN:CallOnDoorChildren(self, function(child)
child:SetDTEntity(0, nil)
end)
end
end
end
-- Configurations for door prices.
ix.config.Add("doorCost", 10, "The price to purchase a door.", nil, {
data = {min = 0, max = 500},
category = "dConfigName"
})
ix.config.Add("doorSellRatio", 0.5, "How much of the door price is returned when selling a door.", nil, {
data = {min = 0, max = 1.0, decimals = 1},
category = "dConfigName"
})
ix.config.Add("doorLockTime", 1, "How long it takes to (un)lock a door.", nil, {
data = {min = 0, max = 10.0, decimals = 1},
category = "dConfigName"
})

View File

@@ -0,0 +1,285 @@
util.AddNetworkString("ixDoorMenu")
util.AddNetworkString("ixDoorPermission")
-- Variables for door data.
local variables = {
-- Whether or not the door will be disabled.
"disabled",
-- The name of the door.
"name",
-- Price of the door.
"price",
-- If the door is ownable.
"ownable",
-- The faction that owns a door.
"faction",
-- The class that owns a door.
"class",
-- Whether or not the door will be hidden.
"visible"
}
function PLUGIN:CallOnDoorChildren(entity, callback)
local parent
if (entity.ixChildren) then
parent = entity
elseif (entity.ixParent) then
parent = entity.ixParent
end
if (IsValid(parent)) then
callback(parent)
for k, _ in pairs(parent.ixChildren) do
local child = ents.GetMapCreatedEntity(k)
if (IsValid(child)) then
callback(child)
end
end
end
end
function PLUGIN:CopyParentDoor(child)
local parent = child.ixParent
if (IsValid(parent)) then
for _, v in ipairs(variables) do
local value = parent:GetNetVar(v)
if (child:GetNetVar(v) != value) then
child:SetNetVar(v, value)
end
end
end
end
-- Called after the entities have loaded.
function PLUGIN:LoadData()
-- Restore the saved door information.
local data = self:GetData()
if (!data) then
return
end
-- Loop through all of the saved doors.
for k, v in pairs(data) do
-- Get the door entity from the saved ID.
local entity = ents.GetMapCreatedEntity(k)
-- Check it is a valid door in-case something went wrong.
if (IsValid(entity) and entity:IsDoor()) then
-- Loop through all of our door variables.
for k2, v2 in pairs(v) do
if (k2 == "children") then
entity.ixChildren = v2
for index, _ in pairs(v2) do
local door = ents.GetMapCreatedEntity(index)
if (IsValid(door)) then
door.ixParent = entity
end
end
elseif (k2 == "faction") then
for k3, v3 in pairs(ix.faction.teams) do
if (k3 == v2) then
entity.ixFactionID = k3
entity:SetNetVar("faction", v3.index)
break
end
end
else
entity:SetNetVar(k2, v2)
end
end
end
end
end
-- Called before the gamemode shuts down.
function PLUGIN:SaveDoorData()
-- Create an empty table to save information in.
local data = {}
local doors = {}
for _, v in ents.Iterator() do
if (v:IsDoor()) then
doors[v:MapCreationID()] = v
end
end
local doorData
-- Loop through doors with information.
for k, v in pairs(doors) do
-- Another empty table for actual information regarding the door.
doorData = {}
-- Save all of the needed variables to the doorData table.
for _, v2 in ipairs(variables) do
local value = v:GetNetVar(v2)
if (value) then
doorData[v2] = v:GetNetVar(v2)
end
end
if (v.ixChildren) then
doorData.children = v.ixChildren
end
if (v.ixClassID) then
doorData.class = v.ixClassID
end
if (v.ixFactionID) then
doorData.faction = v.ixFactionID
end
-- Add the door to the door information.
if (!table.IsEmpty(doorData)) then
data[k] = doorData
end
end
-- Save all of the door information.
self:SetData(data)
end
function PLUGIN:CanPlayerUseDoor(client, entity)
if (entity:GetNetVar("disabled")) then
return false
end
end
-- Whether or not a player a player has any abilities over the door, such as locking.
function PLUGIN:CanPlayerAccessDoor(client, door, access)
local faction = door:GetNetVar("faction")
-- If the door has a faction set which the client is a member of, allow access.
if (faction and client:Team() == faction) then
return true
end
local class = door:GetNetVar("class")
-- If the door has a faction set which the client is a member of, allow access.
local classData = ix.class.list[class]
local charClass = client:GetCharacter():GetClass()
local classData2 = ix.class.list[charClass]
if (class and classData and classData2) then
if (classData.team) then
if (classData.team != classData2.team) then
return false
end
else
if (charClass != class) then
return false
end
end
return true
end
end
function PLUGIN:PostPlayerLoadout(client)
client:Give("ix_keys")
end
function PLUGIN:ShowTeam(client)
local data = {}
data.start = client:GetShootPos()
data.endpos = data.start + client:GetAimVector() * 96
data.filter = client
local trace = util.TraceLine(data)
local entity = trace.Entity
if (IsValid(entity) and entity:IsDoor() and !entity:GetNetVar("faction") and !entity:GetNetVar("class")) then
if (entity:CheckDoorAccess(client, DOOR_TENANT)) then
local door = entity
if (IsValid(door.ixParent)) then
door = door.ixParent
end
net.Start("ixDoorMenu")
net.WriteEntity(door)
net.WriteTable(door.ixAccess)
net.WriteEntity(entity)
net.Send(client)
elseif (!IsValid(entity:GetDTEntity(0))) then
ix.command.Run(client, "doorbuy")
else
client:NotifyLocalized("notAllowed")
end
return true
end
end
function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar)
if (prevChar) then
local doors = prevChar:GetVar("doors") or {}
for _, v in ipairs(doors) do
if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then
v:RemoveDoorAccessData()
end
end
prevChar:SetVar("doors", nil)
end
end
function PLUGIN:PlayerDisconnected(client)
local character = client:GetCharacter()
if (character) then
local doors = character:GetVar("doors") or {}
for _, v in ipairs(doors) do
if (IsValid(v) and v:IsDoor() and v:GetDTEntity(0) == client) then
v:RemoveDoorAccessData()
end
end
character:SetVar("doors", nil)
end
end
net.Receive("ixDoorPermission", function(length, client)
local door = net.ReadEntity()
local target = net.ReadEntity()
local access = net.ReadUInt(4)
if (IsValid(target) and target:GetCharacter() and door.ixAccess and door:GetDTEntity(0) == client and target != client) then
access = math.Clamp(access or 0, DOOR_NONE, DOOR_TENANT)
if (access == door.ixAccess[target]) then
return
end
door.ixAccess[target] = access
local recipient = {}
for k, v in pairs(door.ixAccess) do
if (v > DOOR_GUEST) then
recipient[#recipient + 1] = k
end
end
if (#recipient > 0) then
net.Start("ixDoorPermission")
net.WriteEntity(door)
net.WriteEntity(target)
net.WriteUInt(access, 4)
net.Send(recipient)
end
end
end)

View File

@@ -0,0 +1,228 @@
PLUGIN.name = "Logging"
PLUGIN.author = "Black Tea"
PLUGIN.description = "You can modfiy the logging text/lists on this plugin."
if (SERVER) then
local L = Format
ix.log.AddType("chat", function(client, ...)
local arg = {...}
return L("[%s] %s: %s", arg[1], client:Name(), arg[2])
end)
ix.log.AddType("command", function(client, ...)
local arg = {...}
if (arg[2] and #arg[2] > 0) then
return L("%s used command '%s %s'.", client:Name(), arg[1], arg[2])
else
return L("%s used command '%s'.", client:Name(), arg[1])
end
end)
ix.log.AddType("cfgSet", function(client, ...)
local arg = {...}
return L("%s set %s to '%s'.", client:Name(), arg[1], arg[2])
end, FLAG_DANGER)
ix.log.AddType("connect", function(client, ...)
return L("%s has connected.", client:SteamName())
end, FLAG_NORMAL)
ix.log.AddType("disconnect", function(client, ...)
if (client:IsTimingOut()) then
return L("%s (%s) has disconnected (timed out).", client:SteamName(), client:SteamID())
else
return L("%s (%s) has disconnected.", client:SteamName(), client:SteamID())
end
end, FLAG_NORMAL)
ix.log.AddType("charCreate", function(client, ...)
local arg = {...}
return L("%s created the character '%s'", client:SteamName(), arg[1])
end, FLAG_SERVER)
ix.log.AddType("charLoad", function(client, ...)
local arg = {...}
return L("%s loaded the character '%s'", client:SteamName(), arg[1])
end, FLAG_SERVER)
ix.log.AddType("charDelete", function(client, ...)
local arg = {...}
return L("%s (%s) deleted character '%s'", client:SteamName(), client:SteamID(), arg[1])
end, FLAG_SERVER)
ix.log.AddType("itemAction", function(client, ...)
local arg = {...}
local item = arg[2]
return L("%s ran '%s' on item '%s' (#%s)", client:Name(), arg[1], item:GetName(), item:GetID())
end, FLAG_NORMAL)
ix.log.AddType("itemDestroy", function(client, itemName, itemID)
local name = client:GetName() ~= "" and client:GetName() or client:GetClass()
return L("%s destroyed a '%s' #%d.", name, itemName, itemID)
end, FLAG_WARNING)
ix.log.AddType("shipmentTake", function(client, ...)
local arg = {...}
return L("%s took '%s' from the shipment", client:Name(), arg[1])
end, FLAG_WARNING)
ix.log.AddType("shipmentOrder", function(client, ...)
return L("%s ordered a shipment", client:Name())
end, FLAG_SUCCESS)
ix.log.AddType("buy", function(client, ...)
local arg = {...}
return L("%s purchased '%s' from the NPC", client:Name(), arg[1])
end, FLAG_SUCCESS)
ix.log.AddType("buydoor", function(client, ...)
return L("%s has purchased a door.", client:Name())
end, FLAG_SUCCESS)
ix.log.AddType("selldoor", function(client, ...)
return L("%s has sold a door.", client:Name())
end, FLAG_SUCCESS)
ix.log.AddType("playerHurt", function(client, ...)
local arg = {...}
return L("%s has taken %d damage from %s.", client:Name(), arg[1], arg[2])
end, FLAG_WARNING)
ix.log.AddType("playerDeath", function(client, ...)
local arg = {...}
return L("%s has killed %s%s.", arg[1], client:Name(), arg[2] and (" with " .. arg[2]) or "")
end, FLAG_DANGER)
ix.log.AddType("money", function(client, amount)
return L("%s has %s %s.", client:Name(), amount < 0 and "lost" or "gained", ix.currency.Get(math.abs(amount)))
end, FLAG_SUCCESS)
ix.log.AddType("inventoryAdd", function(client, characterName, itemName, itemID)
return L("%s has gained a '%s' #%d.", characterName, itemName, itemID)
end, FLAG_WARNING)
ix.log.AddType("inventoryRemove", function(client, characterName, itemName, itemID)
return L("%s has lost a '%s' #%d.", characterName, itemName, itemID)
end, FLAG_WARNING)
ix.log.AddType("storageMoneyTake", function(client, entity, amount, total)
local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName()
return string.format("%s has taken %d %s from '%s' #%d (%d %s left).",
client:GetName(), amount, ix.currency.plural, name,
entity:GetInventory():GetID(), total, ix.currency.plural)
end)
ix.log.AddType("storageMoneyGive", function(client, entity, amount, total)
local name = entity.GetDisplayName and entity:GetDisplayName() or entity:GetName()
return string.format("%s has given %d %s to '%s' #%d (%d %s left).",
client:GetName(), amount, ix.currency.plural, name,
entity:GetInventory():GetID(), total, ix.currency.plural)
end)
ix.log.AddType("roll", function(client, value, max)
return string.format("%s rolled %d out of %d.", client:Name(), value, max)
end)
ix.log.AddType("pluginLoaded", function(client, uniqueID)
return string.format("%s has enabled the %s plugin for next restart.", client:GetName(), uniqueID)
end)
ix.log.AddType("pluginUnloaded", function(client, uniqueID)
return string.format("%s has disabled the %s plugin for next restart.", client:GetName(), uniqueID)
end)
function PLUGIN:PlayerInitialSpawn(client)
ix.log.Add(client, "connect")
end
function PLUGIN:PlayerDisconnected(client)
ix.log.Add(client, "disconnect")
end
function PLUGIN:OnCharacterCreated(client, character)
ix.log.Add(client, "charCreate", character:GetName())
end
function PLUGIN:CharacterLoaded(character)
local client = character:GetPlayer()
ix.log.Add(client, "charLoad", character:GetName())
end
function PLUGIN:PreCharacterDeleted(client, character)
ix.log.Add(client, "charDelete", character:GetName())
end
function PLUGIN:ShipmentItemTaken(client, itemClass, amount)
local itemTable = ix.item.list[itemClass]
ix.log.Add(client, "shipmentTake", itemTable:GetName())
end
function PLUGIN:CreateShipment(client, shipmentEntity)
ix.log.Add(client, "shipmentOrder")
end
function PLUGIN:CharacterVendorTraded(client, vendor, x, y, invID, price, isSell)
end
function PLUGIN:PlayerInteractItem(client, action, item)
if (isentity(item)) then
if (IsValid(item)) then
local itemID = item.ixItemID
item = ix.item.instances[itemID]
else
return
end
elseif (isnumber(item)) then
item = ix.item.instances[item]
end
if (!item) then
return
end
ix.log.Add(client, "itemAction", action, item)
end
function PLUGIN:InventoryItemAdded(oldInv, inventory, item)
if (!inventory.owner or (oldInv and oldInv.owner == inventory.owner)) then
return
end
local character = ix.char.loaded[inventory.owner]
ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), item:GetName(), item:GetID())
if (item.isBag) then
local bagInventory = item:GetInventory()
if (!bagInventory) then
return
end
for k, _ in bagInventory:Iter() do
ix.log.Add(character:GetPlayer(), "inventoryAdd", character:GetName(), k:GetName(), k:GetID())
end
end
end
function PLUGIN:InventoryItemRemoved(inventory, item)
if (!inventory.owner) then
return
end
local character = ix.char.loaded[inventory.owner]
ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), item:GetName(), item:GetID())
if (item.isBag) then
for k, _ in item:GetInventory():Iter() do
ix.log.Add(character:GetPlayer(), "inventoryRemove", character:GetName(), k:GetName(), k:GetID())
end
end
end
end

View File

@@ -0,0 +1,277 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Map Scenes"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds areas of the map that are visible during character selection."
PLUGIN.scenes = PLUGIN.scenes or {}
local x3, y3 = 0, 0
local realOrigin = Vector(0, 0, 0)
local realAngles = Angle(0, 0, 0)
local view = {}
if (CLIENT) then
PLUGIN.ordered = PLUGIN.ordered or {}
function PLUGIN:CalcView(client, origin, angles, fov)
local scenes = self.scenes
if (IsValid(ix.gui.characterMenu) and !IsValid(ix.gui.menu) and !ix.gui.characterMenu:IsClosing() and
!table.IsEmpty(scenes)) then
local key = self.index
local value = scenes[self.index]
if (!self.index or !value) then
value, key = table.Random(scenes)
self.index = key
end
if (self.orderedIndex or value.origin or isvector(key)) then
local curTime = CurTime()
self.orderedIndex = self.orderedIndex or 1
local ordered = self.ordered[self.orderedIndex]
if (ordered) then
key = ordered[1]
value = ordered[2]
end
if (!self.startTime) then
self.startTime = curTime
self.finishTime = curTime + 30
end
local fraction = math.min(math.TimeFraction(self.startTime, self.finishTime, CurTime()), 1)
if (value) then
realOrigin = LerpVector(fraction, key, value[1])
realAngles = LerpAngle(fraction, value[2], value[3])
end
if (fraction >= 1) then
self.startTime = curTime
self.finishTime = curTime + 30
if (ordered) then
self.orderedIndex = self.orderedIndex + 1
if (self.orderedIndex > #self.ordered) then
self.orderedIndex = 1
end
else
local keys = {}
for k, _ in pairs(scenes) do
if (isvector(k)) then
keys[#keys + 1] = k
end
end
self.index = table.Random(keys)
end
end
elseif (value) then
realOrigin = value[1]
realAngles = value[2]
end
local x, y = gui.MousePos()
local x2, y2 = surface.ScreenWidth() * 0.5, surface.ScreenHeight() * 0.5
local frameTime = FrameTime() * 0.5
y3 = Lerp(frameTime, y3, math.Clamp((y - y2) / y2, -1, 1) * -6)
x3 = Lerp(frameTime, x3, math.Clamp((x - x2) / x2, -1, 1) * 6)
view.origin = realOrigin + realAngles:Up()*y3 + realAngles:Right()*x3
view.angles = realAngles + Angle(y3 * -0.5, x3 * -0.5, 0)
return view
end
end
function PLUGIN:PreDrawViewModel(viewModel, client, weapon)
if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) then
return true
end
end
net.Receive("ixMapSceneAdd", function()
local data = net.ReadTable()
PLUGIN.scenes[#PLUGIN.scenes + 1] = data
end)
net.Receive("ixMapSceneRemove", function()
local index = net.ReadUInt(16)
PLUGIN.scenes[index] = nil
end)
net.Receive("ixMapSceneAddPair", function()
local data = net.ReadTable()
local origin = net.ReadVector()
PLUGIN.scenes[origin] = data
table.insert(PLUGIN.ordered, {origin, data})
end)
net.Receive("ixMapSceneRemovePair", function()
local key = net.ReadVector()
PLUGIN.scenes[key] = nil
for k, v in ipairs(PLUGIN.ordered) do
if (v[1] == key) then
table.remove(PLUGIN.ordered, k)
break
end
end
end)
net.Receive("ixMapSceneSync", function()
local length = net.ReadUInt(32)
local data = net.ReadData(length)
local uncompressed = util.Decompress(data)
if (!uncompressed) then
ErrorNoHalt("[Helix] Unable to decompress map scene data!\n")
return
end
-- Set the list of texts to the ones provided by the server.
PLUGIN.scenes = util.JSONToTable(uncompressed)
for k, v in pairs(PLUGIN.scenes) do
if (v.origin or isvector(k)) then
table.insert(PLUGIN.ordered, {v.origin and v.origin or k, v})
end
end
end)
else
util.AddNetworkString("ixMapSceneSync")
util.AddNetworkString("ixMapSceneAdd")
util.AddNetworkString("ixMapSceneRemove")
util.AddNetworkString("ixMapSceneAddPair")
util.AddNetworkString("ixMapSceneRemovePair")
function PLUGIN:SaveScenes()
self:SetData(self.scenes)
end
function PLUGIN:LoadData()
self.scenes = self:GetData() or {}
end
function PLUGIN:PlayerInitialSpawn(client)
local json = util.TableToJSON(self.scenes)
local compressed = util.Compress(json)
local length = compressed:len()
net.Start("ixMapSceneSync")
net.WriteUInt(length, 32)
net.WriteData(compressed, length)
net.Send(client)
end
function PLUGIN:AddScene(position, angles, position2, angles2)
local data
if (position2) then
data = {origin=position, position2, angles, angles2}
self.scenes[#self.scenes + 1] = data
net.Start("ixMapSceneAddPair")
net.WriteTable(data)
net.WriteVector(position)
net.Broadcast()
else
data = {position, angles}
self.scenes[#self.scenes + 1] = data
net.Start("ixMapSceneAdd")
net.WriteTable(data)
net.Broadcast()
end
self:SaveScenes()
end
end
ix.command.Add("MapSceneAdd", {
description = "@cmdMapSceneAdd",
privilege = "Manage Map Scenes",
adminOnly = true,
arguments = bit.bor(ix.type.bool, ix.type.optional),
OnRun = function(self, client, bIsPair)
local position, angles = client:EyePos(), client:EyeAngles()
-- This scene is in a pair for moving scenes.
if (tobool(bIsPair) and !client.ixScnPair) then
client.ixScnPair = {position, angles}
return "@mapRepeat"
else
if (client.ixScnPair) then
PLUGIN:AddScene(client.ixScnPair[1], client.ixScnPair[2], position, angles)
client.ixScnPair = nil
else
PLUGIN:AddScene(position, angles)
end
return "@mapAdd"
end
end
})
ix.command.Add("MapSceneRemove", {
description = "@cmdMapSceneRemove",
privilege = "Manage Map Scenes",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
radius = radius or 280
local position = client:GetPos()
local i = 0
for k, v in pairs(PLUGIN.scenes) do
local delete = false
if (isvector(k)) then
if (k:Distance(position) <= radius or v[1]:Distance(position) <= radius) then
delete = true
end
elseif (v[1]:Distance(position) <= radius) then
delete = true
end
if (delete) then
if (isvector(k)) then
net.Start("ixMapSceneRemovePair")
net.WriteVector(k)
net.Broadcast()
else
net.Start("ixMapSceneRemove")
net.WriteString(k)
net.Broadcast()
end
PLUGIN.scenes[k] = nil
i = i + 1
end
end
if (i > 0) then
PLUGIN:SaveScenes()
end
return "@mapDel", i
end
})

View File

@@ -0,0 +1,357 @@
-- luacheck: globals pac pace
-- This Library is just for PAC3 Integration.
-- You must install PAC3 to make this library work.
PLUGIN.name = "PAC3 Integration"
PLUGIN.author = "Black Tea"
PLUGIN.description = "PAC3 integration for item parts."
if (!pace) then return end
ix.pac = ix.pac or {}
ix.pac.list = ix.pac.list or {}
CAMI.RegisterPrivilege({
Name = "Helix - Manage PAC",
MinAccess = "superadmin"
})
-- this stores pac3 part information to plugin's table'
function ix.pac.RegisterPart(id, outfit)
ix.pac.list[id] = outfit
end
-- Fixing the PAC3's default stuffs to fit on Helix.
if (CLIENT) then
-- Disable the "in editor" HUD element.
hook.Add("InitializedPlugins", "PAC3Fixer", function()
hook.Remove("HUDPaint", "pac_in_editor")
end)
-- Remove PAC3 LoadParts
function pace.LoadParts(name, clear, override_part) end
-- Prohibits players from deleting their own PAC3 outfit.
concommand.Add("pac_clear_parts", function()
RunConsoleCommand("pac_restart")
end)
-- you need the proper permission to open the editor
function PLUGIN:PrePACEditorOpen()
if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage PAC", nil)) then
return false
end
end
end
function PLUGIN:pac_CanWearParts(client)
if (!CAMI.PlayerHasAccess(client, "Helix - Manage PAC", nil)) then
return false
end
end
local meta = FindMetaTable("Player")
-- Get Player's PAC3 Parts.
function meta:GetParts()
if (!pac) then return end
return self:GetNetVar("parts", {})
end
if (SERVER) then
util.AddNetworkString("ixPartWear")
util.AddNetworkString("ixPartRemove")
util.AddNetworkString("ixPartReset")
function meta:AddPart(uniqueID, item)
if (!pac) then return end
local curParts = self:GetParts()
-- wear the parts.
net.Start("ixPartWear")
net.WriteEntity(self)
net.WriteString(uniqueID)
net.Broadcast()
curParts[uniqueID] = true
self:SetNetVar("parts", curParts)
end
function meta:RemovePart(uniqueID)
if (!pac) then return end
local curParts = self:GetParts()
-- remove the parts.
net.Start("ixPartRemove")
net.WriteEntity(self)
net.WriteString(uniqueID)
net.Broadcast()
curParts[uniqueID] = nil
self:SetNetVar("parts", curParts)
end
function meta:ResetParts()
if (!pac) then return end
net.Start("ixPartReset")
net.WriteEntity(self)
net.WriteTable(self:GetParts())
net.Broadcast()
self:SetNetVar("parts", {})
end
function PLUGIN:PlayerLoadedCharacter(client, curChar, prevChar)
-- Reset the characters parts.
local curParts = client:GetParts()
if (curParts) then
client:ResetParts()
end
-- After resetting all PAC3 outfits, wear all equipped PAC3 outfits.
if (curChar) then
local inv = curChar:GetInventory()
for k, _ in inv:Iter() do
if (k:GetData("equip") == true and k.pacData) then
client:AddPart(k.uniqueID, k)
end
end
end
end
function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon)
local oldItem = IsValid(oldWeapon) and oldWeapon.ixItem
local newItem = IsValid(newWeapon) and newWeapon.ixItem
if (oldItem and oldItem.isWeapon and oldItem:GetData("equip") and oldItem.pacData) then
oldItem:WearPAC(client)
end
if (newItem and newItem.isWeapon and newItem.pacData) then
newItem:RemovePAC(client)
end
end
-- Hides PAC parts when a player enters observer.
function PLUGIN:OnPlayerObserve(client, state)
local curParts = client:GetParts()
-- Remove all the parts
if (curParts) then
client:ResetParts()
end
-- If exiting of observer, re-add all parts.
if (!state) then
local character = client:GetCharacter()
local inventory = character:GetInventory()
for k, _ in inventory:Iter() do
if (k:GetData("equip") == true and k.pacData) then
client:AddPart(k.uniqueID, k)
end
end
end
end
else
local function AttachPart(client, uniqueID)
local itemTable = ix.item.list[uniqueID]
local pacData = ix.pac.list[uniqueID]
if (pacData) then
if (itemTable and itemTable.pacAdjust) then
pacData = table.Copy(pacData)
pacData = itemTable:pacAdjust(pacData, client)
end
if (isfunction(client.AttachPACPart)) then
client:AttachPACPart(pacData)
else
pac.SetupENT(client)
timer.Simple(0.1, function()
if (IsValid(client) and isfunction(client.AttachPACPart)) then
client:AttachPACPart(pacData)
end
end)
end
end
end
local function RemovePart(client, uniqueID)
local itemTable = ix.item.list[uniqueID]
local pacData = ix.pac.list[uniqueID]
if (pacData) then
if (itemTable and itemTable.pacAdjust) then
pacData = table.Copy(pacData)
pacData = itemTable:pacAdjust(pacData, client)
end
if (isfunction(client.RemovePACPart)) then
client:RemovePACPart(pacData)
else
pac.SetupENT(client)
end
end
end
hook.Add("Think", "ix_pacupdate", function()
if (!pac) then
hook.Remove("Think", "ix_pacupdate")
return
end
if (IsValid(pac.LocalPlayer)) then
for _, v in player.Iterator() do
local character = v:GetCharacter()
if (character) then
local parts = v:GetParts()
for k2, _ in pairs(parts) do
AttachPart(v, k2)
end
end
end
hook.Remove("Think", "ix_pacupdate")
end
end)
net.Receive("ixPartWear", function(length)
if (!pac) then return end
local wearer = net.ReadEntity()
local uid = net.ReadString()
if (!wearer.pac_owner) then
pac.SetupENT(wearer)
end
AttachPart(wearer, uid)
end)
net.Receive("ixPartRemove", function(length)
if (!pac) then return end
local wearer = net.ReadEntity()
local uid = net.ReadString()
if (!wearer.pac_owner) then
pac.SetupENT(wearer)
end
RemovePart(wearer, uid)
end)
net.Receive("ixPartReset", function(length)
if (!pac) then return end
local wearer = net.ReadEntity()
local uidList = net.ReadTable()
if (!wearer.pac_owner) then
pac.SetupENT(wearer)
end
for k, _ in pairs(uidList) do
RemovePart(wearer, k)
end
end)
function PLUGIN:DrawPlayerRagdoll(entity)
local ply = entity.objCache
if (IsValid(ply)) then
if (!entity.overridePAC3) then
if ply.pac_parts then
for _, part in pairs(ply.pac_parts) do
if part.last_owner and part.last_owner:IsValid() then
hook.Run("OnPAC3PartTransferred", part)
part:SetOwner(entity)
part.last_owner = entity
end
end
end
ply.pac_playerspawn = pac.RealTime -- used for events
entity.overridePAC3 = true
end
end
end
function PLUGIN:OnEntityCreated(entity)
local class = entity:GetClass()
-- For safe progress, I skip one frame.
timer.Simple(0.01, function()
if (class == "prop_ragdoll") then
if (entity:GetNetVar("player")) then
entity.RenderOverride = function()
entity.objCache = entity:GetNetVar("player")
entity:DrawModel()
hook.Run("DrawPlayerRagdoll", entity)
end
end
end
if (class:find("HL2MPRagdoll")) then
for _, v in player.Iterator() do
if (v:GetRagdollEntity() == entity) then
entity.objCache = v
end
end
entity.RenderOverride = function()
entity:DrawModel()
hook.Run("DrawPlayerRagdoll", entity)
end
end
end)
end
function PLUGIN:DrawCharacterOverview()
if (!pac) then
return
end
if (LocalPlayer().pac_outfits) then
pac.RenderOverride(LocalPlayer(), "opaque")
pac.RenderOverride(LocalPlayer(), "translucent", true)
end
end
function PLUGIN:DrawHelixModelView(panel, ent)
if (!pac) then
return
end
if (LocalPlayer():GetCharacter()) then
pac.RenderOverride(ent, "opaque")
pac.RenderOverride(ent, "translucent", true)
end
end
end
function PLUGIN:InitializedPlugins()
local items = ix.item.list
for _, v in pairs(items) do
if (v.pacData) then
ix.pac.list[v.uniqueID] = v.pacData
end
end
end

View File

@@ -0,0 +1,37 @@
PLUGIN.name = "Permakill"
PLUGIN.author = "Thadah Denyse"
PLUGIN.description = "Adds permanent death in the server options."
ix.config.Add("permakill", false, "Whether or not permakill is activated on the server.", nil, {
category = "Permakill"
})
ix.config.Add("permakillWorld", false, "Whether or not world and self damage produce permanent death.", nil, {
category = "Permakill"
})
function PLUGIN:PlayerDeath(client, inflictor, attacker)
local character = client:GetCharacter()
if (ix.config.Get("permakill") and character) then
if (hook.Run("ShouldPermakillCharacter", client, character, inflictor, attacker) == false) then
return
end
if (ix.config.Get("permakillWorld") and (client == attacker or inflictor:IsWorld())) then
return
end
character:SetData("permakilled", true)
end
end
function PLUGIN:PlayerSpawn(client)
local character = client:GetCharacter()
if (ix.config.Get("permakill") and character and character:GetData("permakilled")) then
character:Ban()
character:SetData("permakilled")
end
end

View File

@@ -0,0 +1,195 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Persistence"
PLUGIN.description = "Define entities to persist through restarts."
PLUGIN.author = "alexgrist"
PLUGIN.stored = PLUGIN.stored or {}
local function GetRealModel(entity)
return entity:GetClass() == "prop_effect" and entity.AttachedEntity:GetModel() or entity:GetModel()
end
properties.Add("persist", {
MenuLabel = "#makepersistent",
Order = 400,
MenuIcon = "icon16/link.png",
Filter = function(self, entity, client)
if (entity:IsPlayer() or entity:IsVehicle() or entity.bNoPersist) then return false end
if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end
return !entity:GetNetVar("Persistent", false)
end,
Action = function(self, entity)
self:MsgStart()
net.WriteEntity(entity)
self:MsgEnd()
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
PLUGIN.stored[#PLUGIN.stored + 1] = entity
entity:SetNetVar("Persistent", true)
ix.log.Add(client, "persist", GetRealModel(entity), true)
end
})
properties.Add("persist_end", {
MenuLabel = "#stoppersisting",
Order = 400,
MenuIcon = "icon16/link_break.png",
Filter = function(self, entity, client)
if (entity:IsPlayer()) then return false end
if (!gamemode.Call("CanProperty", client, "persist", entity)) then return false end
return entity:GetNetVar("Persistent", false)
end,
Action = function(self, entity)
self:MsgStart()
net.WriteEntity(entity)
self:MsgEnd()
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
for k, v in ipairs(PLUGIN.stored) do
if (v == entity) then
table.remove(PLUGIN.stored, k)
break
end
end
entity:SetNetVar("Persistent", false)
ix.log.Add(client, "persist", GetRealModel(entity), false)
end
})
function PLUGIN:PhysgunPickup(client, entity)
if (entity:GetNetVar("Persistent", false)) then
return false
end
end
if (SERVER) then
function PLUGIN:LoadData()
local entities = self:GetData() or {}
for _, v in ipairs(entities) do
local entity = ents.Create(v.Class)
if (IsValid(entity)) then
entity:SetPos(v.Pos)
entity:SetAngles(v.Angle)
entity:SetModel(v.Model)
entity:SetSkin(v.Skin)
entity:SetColor(v.Color)
entity:SetMaterial(v.Material)
entity:Spawn()
entity:Activate()
if (v.bNoCollision) then
entity:SetCollisionGroup(COLLISION_GROUP_WORLD)
end
if (istable(v.BodyGroups)) then
for k2, v2 in pairs(v.BodyGroups) do
entity:SetBodygroup(k2, v2)
end
end
if (istable(v.SubMaterial)) then
for k2, v2 in pairs(v.SubMaterial) do
if (!isnumber(k2) or !isstring(v2)) then
continue
end
entity:SetSubMaterial(k2 - 1, v2)
end
end
local physicsObject = entity:GetPhysicsObject()
if (IsValid(physicsObject)) then
physicsObject:EnableMotion(v.Movable)
end
self.stored[#self.stored + 1] = entity
entity:SetNetVar("Persistent", true)
end
end
end
function PLUGIN:SaveData()
local entities = {}
for _, v in ipairs(self.stored) do
if (IsValid(v)) then
local data = {}
data.Class = v.ClassOverride or v:GetClass()
data.Pos = v:GetPos()
data.Angle = v:GetAngles()
data.Model = GetRealModel(v)
data.Skin = v:GetSkin()
data.Color = v:GetColor()
data.Material = v:GetMaterial()
data.bNoCollision = v:GetCollisionGroup() == COLLISION_GROUP_WORLD
local materials = v:GetMaterials()
if (istable(materials)) then
data.SubMaterial = {}
for k2, _ in pairs(materials) do
if (v:GetSubMaterial(k2 - 1) != "") then
data.SubMaterial[k2] = v:GetSubMaterial(k2 - 1)
end
end
end
local bodyGroups = v:GetBodyGroups()
if (istable(bodyGroups)) then
data.BodyGroups = {}
for _, v2 in pairs(bodyGroups) do
if (v:GetBodygroup(v2.id) > 0) then
data.BodyGroups[v2.id] = v:GetBodygroup(v2.id)
end
end
end
local physicsObject = v:GetPhysicsObject()
if (IsValid(physicsObject)) then
data.Movable = physicsObject:IsMoveable()
end
entities[#entities + 1] = data
end
end
self:SetData(entities)
end
ix.log.AddType("persist", function(client, ...)
local arg = {...}
return string.format("%s has %s persistence for '%s'.", client:Name(), arg[2] and "enabled" or "disabled", arg[1])
end)
end

View File

@@ -0,0 +1,157 @@
PLUGIN.name = "Basic Prop Protection"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds a simple prop protection system."
CAMI.RegisterPrivilege({
Name = "Helix - Bypass Prop Protection",
MinAccess = "admin"
})
local PROP_BLACKLIST = {
["models/props_combine/combinetrain02b.mdl"] = true,
["models/props_combine/combinetrain02a.mdl"] = true,
["models/props_combine/combinetrain01.mdl"] = true,
["models/cranes/crane_frame.mdl"] = true,
["models/props_junk/trashdumpster02.mdl"] = true,
["models/props_c17/oildrum001_explosive.mdl"] = true,
["models/props_canal/canal_bridge02.mdl"] = true,
["models/props_canal/canal_bridge01.mdl"] = true,
["models/props_canal/canal_bridge03a.mdl"] = true,
["models/props_canal/canal_bridge03b.mdl"] = true,
["models/props_wasteland/cargo_container01.mdl"] = true,
["models/props_wasteland/cargo_container01c.mdl"] = true,
["models/props_wasteland/cargo_container01b.mdl"] = true,
["models/props_combine/combine_mine01.mdl"] = true,
["models/props_junk/glassjug01.mdl"] = true,
["models/props_c17/paper01.mdl"] = true,
["models/props_junk/garbage_takeoutcarton001a.mdl"] = true,
["models/props_c17/trappropeller_engine.mdl"] = true,
["models/props/cs_office/microwave.mdl"] = true,
["models/items/item_item_crate.mdl"] = true,
["models/props_junk/gascan001a.mdl"] = true,
["models/props_c17/consolebox01a.mdl"] = true,
["models/props_buildings/building_002a.mdl"] = true,
["models/props_phx/mk-82.mdl"] = true,
["models/props_phx/cannonball.mdl"] = true,
["models/props_phx/ball.mdl"] = true,
["models/props_phx/amraam.mdl"] = true,
["models/props_phx/misc/flakshell_big.mdl"] = true,
["models/props_phx/ww2bomb.mdl"] = true,
["models/props_phx/torpedo.mdl"] = true,
["models/props/de_train/biohazardtank.mdl"] = true,
["models/props_buildings/project_building01.mdl"] = true,
["models/props_combine/prison01c.mdl"] = true,
["models/props/cs_militia/silo_01.mdl"] = true,
["models/props_phx/huge/evildisc_corp.mdl"] = true,
["models/props_phx/misc/potato_launcher_explosive.mdl"] = true,
["models/props_combine/combine_citadel001.mdl"] = true,
["models/props_phx/oildrum001_explosive.mdl"] = true,
["models/props_junk/wood_crate01_explosive.mdl"] = true,
["models/props_junk/propane_tank001a.mdl"] = true,
["models/props_explosive/explosive_butane_can.mdl"] = true,
["models/props_explosive/explosive_butane_can02.mdl"] = true
}
if (SERVER) then
ix.log.AddType("spawnProp", function(client, ...)
local arg = {...}
return string.format("%s has spawned '%s'.", client:Name(), arg[1])
end)
ix.log.AddType("spawnEntity", function(client, ...)
local arg = {...}
return string.format("%s has spawned a '%s'.", client:Name(), arg[1])
end)
function PLUGIN:PlayerSpawnObject(client, model, entity)
if ((client.ixNextSpawn or 0) < CurTime()) then
client.ixNextSpawn = CurTime() + 0.75
else
return false
end
if (!client:IsAdmin() and PROP_BLACKLIST[model:lower()]) then
return false
end
end
function PLUGIN:PhysgunPickup(client, entity)
local characterID = client:GetCharacter():GetID()
if (entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:OnPhysgunReload(weapon, client)
local characterID = client:GetCharacter():GetID()
local trace = client:GetEyeTrace()
if (IsValid(trace.Entity) and trace.Entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanProperty(client, property, entity)
local characterID = client:GetCharacter():GetID()
if (entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanTool(client, trace, tool)
local entity = trace.Entity
local characterID = client:GetCharacter():GetID()
if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:PlayerSpawnedProp(client, model, entity)
ix.log.Add(client, "spawnProp", model)
end
PLUGIN.PlayerSpawnedEffect = PLUGIN.PlayerSpawnedProp
PLUGIN.PlayerSpawnedRagdoll = PLUGIN.PlayerSpawnedProp
function PLUGIN:PlayerSpawnedNPC(client, entity)
ix.log.Add(client, "spawnEntity", entity)
end
PLUGIN.PlayerSpawnedSWEP = PLUGIN.PlayerSpawnedNPC
PLUGIN.PlayerSpawnedSENT = PLUGIN.PlayerSpawnedNPC
PLUGIN.PlayerSpawnedVehicle = PLUGIN.PlayerSpawnedNPC
else
function PLUGIN:PhysgunPickup(client, entity)
if (entity:GetNetVar("owner", 0) != client:GetCharacter():GetID()
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanProperty(client, property, entity)
local characterID = client:GetCharacter():GetID()
if (entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
function PLUGIN:CanTool(client, trace, tool)
local entity = trace.Entity
local characterID = client:GetCharacter():GetID()
if (IsValid(entity) and entity:GetNetVar("owner", 0) != characterID
and !CAMI.PlayerHasAccess(client, "Helix - Bypass Prop Protection", nil)) then
return false
end
end
end

View File

@@ -0,0 +1,121 @@
PLUGIN.name = "Save Items"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Saves items that were dropped."
--[[
function PLUGIN:OnSavedItemLoaded(items)
for k, v in ipairs(items) do
-- do something
end
end
function PLUGIN:ShouldDeleteSavedItems()
return true
end
]]--
-- as title says.
function PLUGIN:LoadData()
local items = self:GetData()
if (items) then
local idRange = {}
local info = {}
for _, v in ipairs(items) do
idRange[#idRange + 1] = v[1]
info[v[1]] = {v[2], v[3], v[4]}
end
if (#idRange > 0) then
if (hook.Run("ShouldDeleteSavedItems") == true) then
-- don't spawn saved item and just delete them.
local query = mysql:Delete("ix_items")
query:WhereIn("item_id", idRange)
query:Execute()
print("Server Deleted Server Items (does not includes Logical Items)")
else
local query = mysql:Select("ix_items")
query:Select("item_id")
query:Select("unique_id")
query:Select("data")
query:WhereIn("item_id", idRange)
query:Callback(function(result)
if (istable(result)) then
local loadedItems = {}
local bagInventories = {}
for _, v in ipairs(result) do
local itemID = tonumber(v.item_id)
local data = util.JSONToTable(v.data or "[]")
local uniqueID = v.unique_id
local itemTable = ix.item.list[uniqueID]
if (itemTable and itemID) then
local item = ix.item.New(uniqueID, itemID)
item.data = data or {}
local itemInfo = info[itemID]
local position, angles, bMovable = itemInfo[1], itemInfo[2], true
if (isbool(itemInfo[3])) then
bMovable = itemInfo[3]
end
local itemEntity = item:Spawn(position, angles)
itemEntity.ixItemID = itemID
local physicsObject = itemEntity:GetPhysicsObject()
if (IsValid(physicsObject)) then
physicsObject:EnableMotion(bMovable)
end
item.invID = 0
loadedItems[#loadedItems + 1] = item
if (item.isBag) then
local invType = ix.item.inventoryTypes[uniqueID]
bagInventories[item:GetData("id")] = {invType.w, invType.h}
end
end
end
-- we need to manually restore bag inventories in the world since they don't have a current owner
-- that it can automatically restore along with the character when it's loaded
if (!table.IsEmpty(bagInventories)) then
ix.inventory.Restore(bagInventories)
end
hook.Run("OnSavedItemLoaded", loadedItems) -- when you have something in the dropped item.
end
end)
query:Execute()
end
end
end
end
function PLUGIN:SaveData()
local items = {}
for _, v in ipairs(ents.FindByClass("ix_item")) do
if (v.ixItemID and !v.bTemporary) then
local physicsObject = v:GetPhysicsObject()
local bMovable = nil
if (IsValid(physicsObject)) then
bMovable = physicsObject:IsMoveable()
end
items[#items + 1] = {
v.ixItemID, v:GetPos(), v:GetAngles(), bMovable
}
end
end
self:SetData(items)
end

View File

@@ -0,0 +1,205 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Spawns"
PLUGIN.description = "Spawn points for factions and classes."
PLUGIN.author = "Chessnut"
PLUGIN.spawns = PLUGIN.spawns or {}
local function IsSpawnPointClear(pos)
local mins = Vector(-16, -16, 0)
local maxs = Vector(16, 16, 72)
for _, ply in ipairs(player.GetAll()) do
if ply:Alive() and ply:GetPos():Distance(pos) < 50 then
return false
end
end
local ents = ents.FindInBox(pos + mins, pos + maxs)
for _, ent in ipairs(ents) do
if IsValid(ent) and not ent:IsPlayer() and ent:GetSolid() ~= SOLID_NONE then
return false
end
end
return true
end
function PLUGIN:PlayerLoadout(client)
local character = client:GetCharacter()
if (self.spawns and !table.IsEmpty(self.spawns) and character) then
local class = character:GetClass()
local points
local className = "default"
for k, v in ipairs(ix.faction.indices) do
if (k == client:Team()) then
points = self.spawns[v.uniqueID] or {}
break
end
end
if (points) then
local factionTable = ix.faction.indices[client:Team()]
local podr = character:GetPodr()
if (podr and factionTable and factionTable.Podr and factionTable.Podr[podr]) then
className = factionTable.Podr[podr].name:lower()
else
for _, v in ipairs(ix.class.list) do
if (class == v.index) then
className = v.uniqueID
break
end
end
end
points = points[className] or points["default"]
if (points and !table.IsEmpty(points)) then
local tries = 0
local position
repeat
position = table.Random(points)
tries = tries + 1
until IsSpawnPointClear(position) or tries > 10
client:SetPos(position)
end
end
end
end
function PLUGIN:LoadData()
self.spawns = self:GetData() or {}
end
function PLUGIN:SaveSpawns()
self:SetData(self.spawns)
end
ix.command.Add("SpawnAdd", {
description = "@cmdSpawnAdd",
privilege = "Manage Spawn Points",
adminOnly = true,
arguments = {
ix.type.string,
bit.bor(ix.type.text, ix.type.optional)
},
OnRun = function(self, client, name, class)
local full = (name .. " " .. (class or "")):Trim()
local words = string.Explode(" ", full)
local info, info2, faction, className
-- Перебираем возможные разбиения строки на фракцию и класс
for i = 1, #words do
local fName = table.concat(words, " ", 1, i)
local cName = table.concat(words, " ", i + 1)
for _, v in ipairs(ix.faction.indices) do
-- Проверяем совпадение фракции
if (ix.util.StringMatches(v.uniqueID, fName) or ix.util.StringMatches(L(v.name, client), fName)) then
local potentialInfo = v
local potentialFaction = v.uniqueID
local potentialClassName, potentialInfo2
if (cName == "") then
potentialClassName = "default"
else
-- Ищем класс
for _, c in ipairs(ix.class.list) do
if (c.faction == v.index and (c.uniqueID:lower() == cName:lower() or ix.util.StringMatches(L(c.name, client), cName))) then
potentialInfo2 = c
potentialClassName = c.uniqueID
break
end
end
-- Ищем подразделение (Podr)
if (!potentialClassName and v.Podr) then
for _, p in pairs(v.Podr) do
local pName = p.name:lower()
local qName = cName:lower()
-- Сравнение с запасом на окончания (по первым 4-5 символам) или по вхождению
if (ix.util.StringMatches(pName, qName) or ix.util.StringMatches(qName, pName) or
pName:find(qName, 1, true) or qName:find(pName, 1, true) or
(pName:sub(1, 8) == qName:sub(1, 8))) then -- 8 байт = ~4 русских символа
potentialInfo2 = p
potentialClassName = p.name:lower()
break
end
end
end
end
if (potentialClassName) then
info = potentialInfo
faction = potentialFaction
className = potentialClassName
info2 = potentialInfo2
break
end
end
end
if (className) then break end
end
if (info) then
if (!className) then
return "@invalidClass"
end
PLUGIN.spawns[faction] = PLUGIN.spawns[faction] or {}
PLUGIN.spawns[faction][className] = PLUGIN.spawns[faction][className] or {}
table.insert(PLUGIN.spawns[faction][className], client:GetPos())
PLUGIN:SaveSpawns()
local logName = L(info.name, client)
if (info2) then
logName = logName .. " (" .. L(info2.name, client) .. ")"
end
return "@spawnAdded", logName
else
return "@invalidFaction"
end
end
})
ix.command.Add("SpawnRemove", {
description = "@cmdSpawnRemove",
privilege = "Manage Spawn Points",
adminOnly = true,
arguments = bit.bor(ix.type.number, ix.type.optional),
OnRun = function(self, client, radius)
radius = radius or 120
local position = client:GetPos()
local i = 0
for _, v in pairs(PLUGIN.spawns) do
for _, v2 in pairs(v) do
for k3, v3 in pairs(v2) do
if (v3:Distance(position) <= radius) then
v2[k3] = nil
i = i + 1
end
end
end
end
if (i > 0) then
PLUGIN:SaveSpawns()
end
return "@spawnDeleted", i
end
})

View File

@@ -0,0 +1,2 @@
ATTRIBUTE.name = "Endurance"
ATTRIBUTE.description = "Affects how long you can run for."

View File

@@ -0,0 +1,6 @@
ATTRIBUTE.name = "Stamina"
ATTRIBUTE.description = "Affects how fast you can run."
function ATTRIBUTE:OnSetup(client, value)
client:SetRunSpeed(ix.config.Get("runSpeed") + value)
end

View File

@@ -0,0 +1,184 @@
PLUGIN.name = "Stamina"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds a stamina system to limit running."
-- luacheck: push ignore 631
ix.config.Add("staminaDrain", 1, "How much stamina to drain per tick (every quarter second). This is calculated before attribute reduction.", nil, {
data = {min = 0, max = 10, decimals = 2},
category = "characters"
})
ix.config.Add("staminaRegeneration", 1.75, "How much stamina to regain per tick (every quarter second).", nil, {
data = {min = 0, max = 10, decimals = 2},
category = "characters"
})
ix.config.Add("staminaCrouchRegeneration", 2, "How much stamina to regain per tick (every quarter second) while crouching.", nil, {
data = {min = 0, max = 10, decimals = 2},
category = "characters"
})
ix.config.Add("punchStamina", 10, "How much stamina punches use up.", nil, {
data = {min = 0, max = 100},
category = "characters"
})
-- luacheck: pop
local function CalcStaminaChange(client)
local character = client:GetCharacter()
if (!character) then
return 0
end
-- Если в технике или в ноуклипе - сразу реген и выходим
local inVehicle = client:InVehicle() or IsValid(client:GetVehicle())
if (not inVehicle and client.lvsGetVehicle) then
local v = client:lvsGetVehicle()
inVehicle = IsValid(v)
end
if (not inVehicle and IsValid(client:GetParent()) and client:GetParent():IsVehicle()) then inVehicle = true end
local isNoClip = client:GetMoveType() == MOVETYPE_NOCLIP
if (inVehicle or isNoClip) then
local offset = ix.config.Get("staminaRegeneration", 1.75)
if (SERVER) then
local current = client:GetLocalVar("stm", 0)
client:SetLocalVar("stm", math.Clamp(current + offset, 0, 100))
end
return offset
end
local walkSpeed = ix.config.Get("walkSpeed")
local maxAttributes = ix.config.Get("maxAttributes", 100)
local offset
if (client:KeyDown(IN_SPEED) and client:GetVelocity():LengthSqr() >= 5000 and client:OnGround()) then
-- characters could have attribute values greater than max if the config was changed
offset = -ix.config.Get("staminaDrain", 1) + math.min(character:GetAttribute("end", 0), maxAttributes) / 100
-- Ensure minimum drain
if (offset > -0.2) then
offset = -0.2
end
-- Armor weight influence (built-in fix)
local armor = client:Armor()
if (armor > 0) then
offset = offset * (1 + (math.min(armor, 100) / 100) * 0.7)
end
else
offset = client:Crouching() and ix.config.Get("staminaCrouchRegeneration", 2) or ix.config.Get("staminaRegeneration", 1.75)
-- Armor slows down regeneration but doesn't cause drain while standing
local armor = client:Armor()
if (armor > 0 and offset > 0) then
offset = offset * math.max(1 - (math.min(armor, 100) / 100) * 0.5, 0.1)
end
-- Гарантируем, что регенерация это регенерация (не меньше нуля)
offset = math.max(offset, 0.05)
end
offset = hook.Run("AdjustStaminaOffset", client, offset) or offset
if (CLIENT) then
return offset -- for the client we need to return the estimated stamina change
else
local current = client:GetLocalVar("stm", 0)
local value = math.Clamp(current + offset, 0, 100)
if (current != value) then
client:SetLocalVar("stm", value)
if (value == 0 and !client:GetNetVar("brth", false)) then
client:SetNetVar("brth", true)
character:UpdateAttrib("end", 0.1)
character:UpdateAttrib("stm", 0.01)
hook.Run("PlayerStaminaLost", client)
elseif (value >= 50 and client:GetNetVar("brth", false)) then
client:SetNetVar("brth", nil)
hook.Run("PlayerStaminaGained", client)
end
end
end
end
function PLUGIN:SetupMove(client, mv, cmd)
if (client:GetNetVar("brth", false)) then
mv:SetMaxClientSpeed(client:GetWalkSpeed())
end
end
if (SERVER) then
function PLUGIN:PostPlayerLoadout(client)
local uniqueID = "ixStam" .. client:SteamID()
timer.Create(uniqueID, 0.25, 0, function()
if (!IsValid(client)) then
timer.Remove(uniqueID)
return
end
CalcStaminaChange(client)
end)
end
function PLUGIN:CharacterPreSave(character)
local client = character:GetPlayer()
if (IsValid(client)) then
character:SetData("stamina", client:GetLocalVar("stm", 0))
end
end
function PLUGIN:PlayerLoadedCharacter(client, character)
timer.Simple(0.25, function()
client:SetLocalVar("stm", character:GetData("stamina", 100))
end)
end
local playerMeta = FindMetaTable("Player")
function playerMeta:RestoreStamina(amount)
local current = self:GetLocalVar("stm", 0)
local value = math.Clamp(current + amount, 0, 100)
self:SetLocalVar("stm", value)
end
function playerMeta:ConsumeStamina(amount)
local current = self:GetLocalVar("stm", 0)
local value = math.Clamp(current - amount, 0, 100)
self:SetLocalVar("stm", value)
end
else
local predictedStamina = 100
function PLUGIN:Think()
local offset = CalcStaminaChange(LocalPlayer())
-- the server check it every 0.25 sec, here we check it every [FrameTime()] seconds
offset = math.Remap(FrameTime(), 0, 0.25, 0, offset)
if (offset != 0) then
predictedStamina = math.Clamp(predictedStamina + offset, 0, 100)
end
end
function PLUGIN:OnLocalVarSet(key, var)
if (key != "stm") then return end
if (math.abs(predictedStamina - var) > 5) then
predictedStamina = var
end
end
ix.bar.Add(function()
return predictedStamina / 100
end, Color(200, 200, 40), nil, "stm")
end

View File

@@ -0,0 +1,2 @@
ATTRIBUTE.name = "Strength"
ATTRIBUTE.description = "A measure of how strong you are."

View File

@@ -0,0 +1,24 @@
PLUGIN.name = "Strength"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds a strength attribute."
if (SERVER) then
function PLUGIN:GetPlayerPunchDamage(client, damage, context)
if (client:GetCharacter()) then
-- Add to the total fist damage.
context.damage = context.damage + (client:GetCharacter():GetAttribute("str", 0) * ix.config.Get("strengthMultiplier"))
end
end
function PLUGIN:PlayerThrowPunch(client, trace)
if (client:GetCharacter() and IsValid(trace.Entity) and trace.Entity:IsPlayer()) then
client:GetCharacter():UpdateAttrib("str", 0.001)
end
end
end
-- Configuration for the plugin
ix.config.Add("strengthMultiplier", 0.3, "The strength multiplier scale", nil, {
data = {min = 0, max = 1.0, decimals = 1},
category = "Strength"
})

View File

@@ -0,0 +1,233 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Third Person"
PLUGIN.author = "Black Tea"
PLUGIN.description = "Enables third person camera usage."
-- Localization
ix.lang.AddTable("russian", {
optThirdPerson = "Вид от третьего лица",
optThirdPersonEnabled = "Включить вид от 3-го лица",
optThirdPersonEnabledDesc = "Использовать камеру от третьего лица",
optThirdPersonClassic = "Классический режим",
optThirdPersonClassicDesc = "Использовать классический вид камеры",
optThirdPersonVertical = "Вертикальное смещение",
optThirdPersonVerticalDesc = "Высота камеры над персонажем",
optThirdPersonHorizontal = "Горизонтальное смещение",
optThirdPersonHorizontalDesc = "Боковое смещение камеры",
optThirdPersonDistance = "Расстояние камеры",
optThirdPersonDistanceDesc = "Дистанция камеры от персонажа",
})
ix.lang.AddTable("english", {
optThirdPerson = "Third Person",
optThirdPersonEnabled = "Enable Third Person",
optThirdPersonEnabledDesc = "Use third person camera view",
optThirdPersonClassic = "Classic Mode",
optThirdPersonClassicDesc = "Use classic camera view",
optThirdPersonVertical = "Vertical Offset",
optThirdPersonVerticalDesc = "Camera height above character",
optThirdPersonHorizontal = "Horizontal Offset",
optThirdPersonHorizontalDesc = "Camera side offset",
optThirdPersonDistance = "Camera Distance",
optThirdPersonDistanceDesc = "Distance of camera from character",
})
ix.config.Add("thirdperson", false, "Allow Thirdperson in the server.", nil, {
category = "server"
})
if (CLIENT) then
local function isHidden()
return !ix.config.Get("thirdperson")
end
ix.option.Add("thirdpersonEnabled", ix.type.bool, false, {
category = "optThirdPerson",
name = "optThirdPersonEnabled",
description = "optThirdPersonEnabledDesc",
hidden = isHidden,
OnChanged = function(oldValue, value)
hook.Run("ThirdPersonToggled", oldValue, value)
end
})
ix.option.Add("thirdpersonClassic", ix.type.bool, false, {
category = "optThirdPerson",
name = "optThirdPersonClassic",
description = "optThirdPersonClassicDesc",
hidden = isHidden
})
ix.option.Add("thirdpersonVertical", ix.type.number, 10, {
category = "optThirdPerson",
name = "optThirdPersonVertical",
description = "optThirdPersonVerticalDesc",
min = 0, max = 30,
hidden = isHidden
})
ix.option.Add("thirdpersonHorizontal", ix.type.number, 0, {
category = "optThirdPerson",
name = "optThirdPersonHorizontal",
description = "optThirdPersonHorizontalDesc",
min = -30, max = 30,
hidden = isHidden
})
ix.option.Add("thirdpersonDistance", ix.type.number, 50, {
category = "optThirdPerson",
name = "optThirdPersonDistance",
description = "optThirdPersonDistanceDesc",
category = "thirdperson", min = 0, max = 100,
hidden = isHidden
})
concommand.Add("ix_togglethirdperson", function()
local bEnabled = !ix.option.Get("thirdpersonEnabled", false)
ix.option.Set("thirdpersonEnabled", bEnabled)
end)
local function isAllowed()
return ix.config.Get("thirdperson")
end
local playerMeta = FindMetaTable("Player")
local traceMin = Vector(-10, -10, -10)
local traceMax = Vector(10, 10, 10)
function playerMeta:CanOverrideView()
local entity = Entity(self:GetLocalVar("ragdoll", 0))
if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing() and ix.gui.characterMenu:IsVisible()) then
return false
end
if (IsValid(ix.gui.menu) and ix.gui.menu:GetCharacterOverview()) then
return false
end
if (ix.option.Get("thirdpersonEnabled", false) and
!IsValid(self:GetVehicle()) and
isAllowed() and
IsValid(self) and
self:GetCharacter() and
!self:GetNetVar("actEnterAngle") and
!IsValid(entity) and
LocalPlayer():Alive()
) then
return true
end
end
local view, traceData, traceData2, aimOrigin, crouchFactor, ft, curAng, owner
local clmp = math.Clamp
crouchFactor = 0
function PLUGIN:CalcView(client, origin, angles, fov)
ft = FrameTime()
owner = client
if (client:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then
local bNoclip = LocalPlayer():GetMoveType() == MOVETYPE_NOCLIP
if ((client:OnGround() and client:KeyDown(IN_DUCK)) or client:Crouching()) then
crouchFactor = Lerp(ft*5, crouchFactor, 1)
else
crouchFactor = Lerp(ft*5, crouchFactor, 0)
end
curAng = owner.camAng or angle_zero
view = {}
local headPos = client:GetPos() + client:GetViewOffset()
local offset = curAng:Up() * ix.option.Get("thirdpersonVertical", 10) +
curAng:Right() * ix.option.Get("thirdpersonHorizontal", 0) -
client:GetViewOffsetDucked() * .5 * crouchFactor
local startTrace = util.TraceLine({
start = headPos,
endpos = headPos + offset,
filter = client
})
traceData = {}
traceData.start = startTrace.HitPos
traceData.endpos = traceData.start - curAng:Forward() * ix.option.Get("thirdpersonDistance", 50)
traceData.filter = client
traceData.ignoreworld = bNoclip
traceData.mins = traceMin
traceData.maxs = traceMax
view.origin = util.TraceHull(traceData).HitPos
aimOrigin = view.origin
view.angles = curAng + client:GetViewPunchAngles()
traceData2 = {}
traceData2.start = aimOrigin
traceData2.endpos = aimOrigin + curAng:Forward() * 65535
traceData2.filter = client
traceData2.ignoreworld = bNoclip
local bClassic = ix.option.Get("thirdpersonClassic", false)
if (bClassic or owner:IsWepRaised() or
(owner:KeyDown(bit.bor(IN_FORWARD, IN_BACK, IN_MOVELEFT, IN_MOVERIGHT)) and owner:GetVelocity():Length() >= 10)) then
client:SetEyeAngles((util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle())
else
local currentAngles = client:EyeAngles()
currentAngles.pitch = (util.TraceLine(traceData2).HitPos - client:GetShootPos()):Angle().pitch
client:SetEyeAngles(currentAngles)
end
return view
end
end
local diff, fm, sm
function PLUGIN:CreateMove(cmd)
owner = LocalPlayer()
if (owner:CanOverrideView() and owner:GetMoveType() != MOVETYPE_NOCLIP and
LocalPlayer():GetViewEntity() == LocalPlayer()) then
local fm = cmd:GetForwardMove()
local sm = cmd:GetSideMove()
local camAng = owner.camAng or owner:EyeAngles()
local eyeAng = owner:EyeAngles()
local wishDir = (camAng:Forward() * fm) + (camAng:Right() * sm)
wishDir.z = 0
local localMove = WorldToLocal(wishDir, angle_zero, vector_origin, Angle(0, eyeAng.y, 0))
cmd:SetForwardMove(localMove.x)
cmd:SetSideMove(-localMove.y)
return false
end
end
function PLUGIN:InputMouseApply(cmd, x, y, ang)
owner = LocalPlayer()
if (!owner.camAng) then
owner.camAng = Angle(0, 0, 0)
end
owner.camAng.p = clmp(math.NormalizeAngle(owner.camAng.p + y / 50), -85, 85)
owner.camAng.y = math.NormalizeAngle(owner.camAng.y - x / 50)
if (owner:CanOverrideView() and LocalPlayer():GetViewEntity() == LocalPlayer()) then
return true
end
end
function PLUGIN:ShouldDrawLocalPlayer()
if (LocalPlayer():GetViewEntity() == LocalPlayer() and !IsValid(LocalPlayer():GetVehicle())) then
return LocalPlayer():CanOverrideView()
end
end
end

View File

@@ -0,0 +1,244 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Typing Indicator"
PLUGIN.description = "Shows an indicator when someone is typing."
PLUGIN.author = "`impulse"
PLUGIN.animationTime = 0.5
if (CLIENT) then
local standingOffset = Vector(0, 0, 72)
local crouchingOffset = Vector(0, 0, 38)
local boneOffset = Vector(0, 0, 10)
local textColor = Color(250, 250, 250)
local shadowColor = Color(66, 66, 66)
local currentClass
-- we can't rely on matching non-alphanumeric characters (i.e %W) due to patterns matching single bytes and not UTF-8 chars
local symbolPattern = "[~`!@#$%%%^&*()_%+%-={}%[%]|;:'\",%./<>?]"
function PLUGIN:LoadFonts(font, genericFont)
surface.CreateFont("ixTypingIndicator", {
font = genericFont,
size = 128,
extended = true,
weight = 1000
})
end
function PLUGIN:ChatTextChanged(text)
if (!IsValid(LocalPlayer())) then
return
end
local character = LocalPlayer():GetCharacter()
if (!character) then
return
end
if (text == "") then
currentClass = nil
net.Start("ixTypeClass")
net.WriteString("")
net.SendToServer()
return
end
local newClass = hook.Run("GetTypingIndicator", character, text)
if (newClass != currentClass) then
currentClass = newClass
net.Start("ixTypeClass")
net.WriteString(currentClass or "")
net.SendToServer()
end
end
function PLUGIN:FinishChat()
currentClass = nil
net.Start("ixTypeClass")
net.WriteString("")
net.SendToServer()
end
function PLUGIN:GetTypingIndicator(character, text)
local prefix = text:utf8sub(1, 1)
if (!prefix:find(symbolPattern) and text:utf8len() > 1) then
return "ic"
else
local chatType = ix.chat.Parse(nil, text)
if (chatType and chatType != "ic") then
return !ix.chat.classes[chatType].bNoIndicator and chatType or nil
end
-- some commands will have their own typing indicator, so we'll make sure we're actually typing out a command first
local start, _, commandName = text:find("/(%S+)%s")
if (start == 1) then
for uniqueID, command in pairs(ix.command.list) do
if (command.bNoIndicator) then
continue
end
if (commandName == uniqueID) then
return command.indicator and "@" .. command.indicator or "ooc"
end
end
end
end
end
function PLUGIN:GetTypingIndicatorPosition(client)
local head
for i = 1, client:GetBoneCount() do
local name = client:GetBoneName(i)
if (string.find(name:lower(), "head")) then
head = i
break
end
end
local position = head and client:GetBonePosition(head) or (client:Crouching() and crouchingOffset or standingOffset)
return position + boneOffset
end
function PLUGIN:PostDrawTranslucentRenderables()
local client = LocalPlayer()
local position = client:GetPos()
for _, v in player.Iterator() do
if (v == client) then
continue
end
local distance = v:GetPos():DistToSqr(position)
local moveType = v:GetMoveType()
if (!IsValid(v) or !v:Alive() or
(moveType != MOVETYPE_WALK and moveType != MOVETYPE_NONE) or
!v.ixChatClassText or
distance >= v.ixChatClassRange) then
continue
end
local text = v.ixChatClassText
local range = v.ixChatClassRange
local bAnimation = !ix.option.Get("disableAnimations", false)
local fraction
if (bAnimation) then
local bComplete = v.ixChatClassTween:update(FrameTime())
if (bComplete and !v.ixChatStarted) then
v.ixChatClassText = nil
v.ixChatClassRange = nil
continue
end
fraction = v.ixChatClassAnimation
else
fraction = 1
end
local angle = EyeAngles()
angle:RotateAroundAxis(angle:Forward(), 90)
angle:RotateAroundAxis(angle:Right(), 90)
cam.Start3D2D(self:GetTypingIndicatorPosition(v), Angle(0, angle.y, 90), 0.05)
surface.SetFont("ixTypingIndicator")
local _, textHeight = surface.GetTextSize(text)
local alpha = bAnimation and ((1 - math.min(distance, range) / range) * 255 * fraction) or 255
draw.SimpleTextOutlined(text, "ixTypingIndicator", 0,
-textHeight * 0.5 * fraction,
ColorAlpha(textColor, alpha),
TEXT_ALIGN_CENTER,
TEXT_ALIGN_CENTER, 4,
ColorAlpha(shadowColor, alpha)
)
cam.End3D2D()
end
end
net.Receive("ixTypeClass", function()
local client = net.ReadEntity()
if (!IsValid(client) or client == LocalPlayer()) then
return
end
local newClass = net.ReadString()
local chatClass = ix.chat.classes[newClass]
local text
local range
if (chatClass) then
text = L(chatClass.indicator or "chatTyping")
range = chatClass.range or math.pow(ix.config.Get("chatRange", 280), 2)
elseif (newClass and newClass:sub(1, 1) == "@") then
text = L(newClass:sub(2))
range = math.pow(ix.config.Get("chatRange", 280), 2)
end
if (ix.option.Get("disableAnimations", false)) then
client.ixChatClassText = text
client.ixChatClassRange = range
else
client.ixChatClassAnimation = tonumber(client.ixChatClassAnimation) or 0
if (text and !client.ixChatStarted) then
client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 1}, "outCubic")
client.ixChatClassText = text
client.ixChatClassRange = range
client.ixChatStarted = true
elseif (!text and client.ixChatStarted) then
client.ixChatClassTween = ix.tween.new(PLUGIN.animationTime, client, {ixChatClassAnimation = 0}, "inCubic")
client.ixChatStarted = nil
end
end
end)
else
util.AddNetworkString("ixTypeClass")
function PLUGIN:PlayerSpawn(client)
net.Start("ixTypeClass")
net.WriteEntity(client)
net.WriteString("")
net.Broadcast()
end
net.Receive("ixTypeClass", function(length, client)
if ((client.ixNextTypeClass or 0) > RealTime()) then
return
end
local newClass = net.ReadString()
-- send message to players in pvs only since they're the only ones who can see the indicator
-- we'll broadcast if the type class is empty because they might move out of pvs before the ending net message is sent
net.Start("ixTypeClass")
net.WriteEntity(client)
net.WriteString(newClass)
if (newClass == "") then
net.Broadcast()
else
net.SendPVS(client:GetPos())
end
client.ixNextTypeClass = RealTime() + 0.2
end)
end

View File

@@ -0,0 +1,287 @@
local PANEL = {}
AccessorFunc(PANEL, "bReadOnly", "ReadOnly", FORCE_BOOL)
function PANEL:Init()
self:SetSize(ScrW() * 0.45, ScrH() * 0.65)
self:SetTitle("")
self:MakePopup()
self:Center()
local header = self:Add("DPanel")
header:SetTall(34)
header:Dock(TOP)
self.vendorName = header:Add("DLabel")
self.vendorName:Dock(LEFT)
self.vendorName:SetWide(self:GetWide() * 0.5 - 7)
self.vendorName:SetText("John Doe")
self.vendorName:SetTextInset(4, 0)
self.vendorName:SetTextColor(color_white)
self.vendorName:SetFont("ixMediumFont")
self.ourName = header:Add("DLabel")
self.ourName:Dock(RIGHT)
self.ourName:SetWide(self:GetWide() * 0.5 - 7)
self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")")
self.ourName:SetTextInset(0, 0)
self.ourName:SetTextColor(color_white)
self.ourName:SetFont("ixMediumFont")
local footer = self:Add("DPanel")
footer:SetTall(34)
footer:Dock(BOTTOM)
footer:SetPaintBackground(false)
self.vendorSell = footer:Add("DButton")
self.vendorSell:SetFont("ixMediumFont")
self.vendorSell:SetWide(self.vendorName:GetWide())
self.vendorSell:Dock(LEFT)
self.vendorSell:SetContentAlignment(5)
-- The text says purchase but the vendor is selling it to us.
self.vendorSell:SetText(L"purchase")
self.vendorSell:SetTextColor(color_white)
self.vendorSell.DoClick = function(this)
if (IsValid(self.activeSell)) then
net.Start("ixVendorTrade")
net.WriteString(self.activeSell.item)
net.WriteBool(false)
net.SendToServer()
end
end
self.vendorBuy = footer:Add("DButton")
self.vendorBuy:SetFont("ixMediumFont")
self.vendorBuy:SetWide(self.ourName:GetWide())
self.vendorBuy:Dock(RIGHT)
self.vendorBuy:SetContentAlignment(5)
self.vendorBuy:SetText(L"sell")
self.vendorBuy:SetTextColor(color_white)
self.vendorBuy.DoClick = function(this)
if (IsValid(self.activeBuy)) then
net.Start("ixVendorTrade")
net.WriteString(self.activeBuy.item)
net.WriteBool(true)
net.SendToServer()
end
end
self.selling = self:Add("DScrollPanel")
self.selling:SetWide(self:GetWide() * 0.5 - 7)
self.selling:Dock(LEFT)
self.selling:DockMargin(0, 4, 0, 4)
self.selling:SetPaintBackground(true)
self.sellingItems = self.selling:Add("DListLayout")
self.sellingItems:SetSize(self.selling:GetSize())
self.sellingItems:DockPadding(0, 0, 0, 4)
self.sellingItems:SetTall(ScrH())
self.buying = self:Add("DScrollPanel")
self.buying:SetWide(self:GetWide() * 0.5 - 7)
self.buying:Dock(RIGHT)
self.buying:DockMargin(0, 4, 0, 4)
self.buying:SetPaintBackground(true)
self.buyingItems = self.buying:Add("DListLayout")
self.buyingItems:SetSize(self.buying:GetSize())
self.buyingItems:DockPadding(0, 0, 0, 4)
self.sellingList = {}
self.buyingList = {}
end
function PANEL:addItem(uniqueID, listID)
local entity = self.entity
local items = entity.items
local data = items[uniqueID]
if ((!listID or listID == "selling") and !IsValid(self.sellingList[uniqueID])
and ix.item.list[uniqueID]) then
if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_BUYONLY) then
local item = self.sellingItems:Add("ixVendorItem")
item:Setup(uniqueID)
self.sellingList[uniqueID] = item
self.sellingItems:InvalidateLayout()
end
end
if ((!listID or listID == "buying") and !IsValid(self.buyingList[uniqueID])
and LocalPlayer():GetCharacter():GetInventory():HasItem(uniqueID)) then
if (data and data[VENDOR_MODE] and data[VENDOR_MODE] != VENDOR_SELLONLY) then
local item = self.buyingItems:Add("ixVendorItem")
item:Setup(uniqueID)
item.isLocal = true
self.buyingList[uniqueID] = item
self.buyingItems:InvalidateLayout()
end
end
end
function PANEL:removeItem(uniqueID, listID)
if (!listID or listID == "selling") then
if (IsValid(self.sellingList[uniqueID])) then
self.sellingList[uniqueID]:Remove()
self.sellingItems:InvalidateLayout()
end
end
if (!listID or listID == "buying") then
if (IsValid(self.buyingList[uniqueID])) then
self.buyingList[uniqueID]:Remove()
self.buyingItems:InvalidateLayout()
end
end
end
function PANEL:Setup(entity)
self.entity = entity
self:SetTitle(entity:GetDisplayName())
self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..entity.money..")" or ""))
self.vendorBuy:SetEnabled(!self:GetReadOnly())
self.vendorSell:SetEnabled(!self:GetReadOnly())
for k, _ in SortedPairs(entity.items) do
self:addItem(k, "selling")
end
for _, v in SortedPairs(LocalPlayer():GetCharacter():GetInventory():GetItems()) do
self:addItem(v.uniqueID, "buying")
end
end
function PANEL:OnRemove()
net.Start("ixVendorClose")
net.SendToServer()
if (IsValid(ix.gui.vendorEditor)) then
ix.gui.vendorEditor:Remove()
end
end
function PANEL:Think()
local entity = self.entity
if (!IsValid(entity)) then
self:Remove()
return
end
if ((self.nextUpdate or 0) < CurTime()) then
self:SetTitle(self.entity:GetDisplayName())
self.vendorName:SetText(entity:GetDisplayName()..(entity.money and " ("..ix.currency.Get(entity.money)..")" or ""))
self.ourName:SetText(L"you".." ("..ix.currency.Get(LocalPlayer():GetCharacter():GetMoney())..")")
self.nextUpdate = CurTime() + 0.25
end
end
function PANEL:OnItemSelected(panel)
local price = self.entity:GetPrice(panel.item, panel.isLocal)
if (panel.isLocal) then
self.vendorBuy:SetText(L"sell".." ("..ix.currency.Get(price)..")")
else
self.vendorSell:SetText(L"purchase".." ("..ix.currency.Get(price)..")")
end
end
vgui.Register("ixVendor", PANEL, "DFrame")
PANEL = {}
function PANEL:Init()
self:SetTall(36)
self:DockMargin(4, 4, 4, 0)
self.icon = self:Add("SpawnIcon")
self.icon:SetPos(2, 2)
self.icon:SetSize(32, 32)
self.icon:SetModel("models/error.mdl")
self.name = self:Add("DLabel")
self.name:Dock(FILL)
self.name:DockMargin(42, 0, 0, 0)
self.name:SetFont("ixChatFont")
self.name:SetTextColor(color_white)
self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200))
self.click = self:Add("DButton")
self.click:Dock(FILL)
self.click:SetText("")
self.click.Paint = function() end
self.click.DoClick = function(this)
if (self.isLocal) then
ix.gui.vendor.activeBuy = self
else
ix.gui.vendor.activeSell = self
end
ix.gui.vendor:OnItemSelected(self)
end
end
function PANEL:SetCallback(callback)
self.click.DoClick = function(this)
callback()
self.selected = true
end
end
function PANEL:Setup(uniqueID)
local item = ix.item.list[uniqueID]
if (item) then
self.item = uniqueID
self.icon:SetModel(item:GetModel(), item:GetSkin())
self.name:SetText(item:GetName())
self.itemName = item:GetName()
self.click:SetHelixTooltip(function(tooltip)
ix.hud.PopulateItemTooltip(tooltip, item)
local entity = ix.gui.vendor.entity
if (entity and entity.items[self.item] and entity.items[self.item][VENDOR_MAXSTOCK]) then
local info = entity.items[self.item]
local stock = tooltip:AddRowAfter("name", "stock")
stock:SetText(string.format("Stock: %d/%d", info[VENDOR_STOCK], info[VENDOR_MAXSTOCK]))
stock:SetBackgroundColor(derma.GetColor("Info", self))
stock:SizeToContents()
end
end)
end
end
function PANEL:Think()
if ((self.nextUpdate or 0) < CurTime()) then
local entity = ix.gui.vendor.entity
if (entity and self.isLocal) then
local count = LocalPlayer():GetCharacter():GetInventory():GetItemCount(self.item)
if (count == 0) then
self:Remove()
end
end
self.nextUpdate = CurTime() + 0.1
end
end
function PANEL:Paint(w, h)
if (ix.gui.vendor.activeBuy == self or ix.gui.vendor.activeSell == self) then
surface.SetDrawColor(ix.config.Get("color"))
else
surface.SetDrawColor(0, 0, 0, 100)
end
surface.DrawRect(0, 0, w, h)
end
vgui.Register("ixVendorItem", PANEL, "DPanel")

View File

@@ -0,0 +1,286 @@
local PANEL = {}
function PANEL:Init()
local entity = ix.gui.vendor.entity
self:SetSize(320, 480)
self:MoveLeftOf(ix.gui.vendor, 8)
self:MakePopup()
self:CenterVertical()
self:SetTitle(L"vendorEditor")
self.lblTitle:SetTextColor(color_white)
self.name = self:Add("DTextEntry")
self.name:Dock(TOP)
self.name:SetText(entity:GetDisplayName())
self.name:SetPlaceholderText(L"name")
self.name.OnEnter = function(this)
if (entity:GetDisplayName() != this:GetText()) then
self:updateVendor("name", this:GetText())
end
end
self.description = self:Add("DTextEntry")
self.description:Dock(TOP)
self.description:DockMargin(0, 4, 0, 0)
self.description:SetText(entity:GetDescription())
self.description:SetPlaceholderText(L"description")
self.description.OnEnter = function(this)
if (entity:GetDescription() != this:GetText()) then
self:updateVendor("description", this:GetText())
end
end
self.model = self:Add("DTextEntry")
self.model:Dock(TOP)
self.model:DockMargin(0, 4, 0, 0)
self.model:SetText(entity:GetModel())
self.model:SetPlaceholderText(L"model")
self.model.OnEnter = function(this)
if (entity:GetModel():lower() != this:GetText():lower()) then
self:updateVendor("model", this:GetText():lower())
end
end
local useMoney = tonumber(entity.money) != nil
self.money = self:Add("DTextEntry")
self.money:Dock(TOP)
self.money:DockMargin(0, 4, 0, 0)
self.money:SetText(!useMoney and "" or entity.money)
self.money:SetPlaceholderText(L"money")
self.money:SetDisabled(!useMoney)
self.money:SetEnabled(useMoney)
self.money:SetNumeric(true)
self.money.OnEnter = function(this)
local value = tonumber(this:GetText()) or entity.money
if (value == entity.money) then
return
end
self:updateVendor("money", value)
end
self.bubble = self:Add("DCheckBoxLabel")
self.bubble:SetText(L"vendorNoBubble")
self.bubble:Dock(TOP)
self.bubble:DockMargin(0, 4, 0, 0)
self.bubble:SetValue(entity:GetNoBubble() and 1 or 0)
self.bubble.OnChange = function(this, value)
if (this.noSend) then
this.noSend = nil
else
self:updateVendor("bubble", value)
end
end
self.useMoney = self:Add("DCheckBoxLabel")
self.useMoney:SetText(L"vendorUseMoney")
self.useMoney:Dock(TOP)
self.useMoney:DockMargin(0, 4, 0, 0)
self.useMoney:SetChecked(useMoney)
self.useMoney.OnChange = function(this, value)
self:updateVendor("useMoney")
end
self.sellScale = self:Add("DNumSlider")
self.sellScale:Dock(TOP)
self.sellScale:DockMargin(0, 4, 0, 0)
self.sellScale:SetText(L"vendorSellScale")
self.sellScale.Label:SetTextColor(color_white)
self.sellScale.TextArea:SetTextColor(color_white)
self.sellScale:SetDecimals(1)
self.sellScale.noSend = true
self.sellScale:SetValue(entity.scale)
self.sellScale.OnValueChanged = function(this, value)
if (this.noSend) then
this.noSend = nil
else
timer.Create("ixVendorScale", 1, 1, function()
if (IsValid(self) and IsValid(self.sellScale)) then
value = self.sellScale:GetValue()
if (value != entity.scale) then
self:updateVendor("scale", value)
end
end
end)
end
end
self.faction = self:Add("DButton")
self.faction:SetText(L"vendorFaction")
self.faction:Dock(TOP)
self.faction:SetTextColor(color_white)
self.faction:DockMargin(0, 4, 0, 0)
self.faction.DoClick = function(this)
if (IsValid(ix.gui.editorFaction)) then
ix.gui.editorFaction:Remove()
end
ix.gui.editorFaction = vgui.Create("ixVendorFactionEditor")
ix.gui.editorFaction.updateVendor = self.updateVendor
ix.gui.editorFaction.entity = entity
ix.gui.editorFaction:Setup()
end
self.searchBar = self:Add("DTextEntry")
self.searchBar:Dock(TOP)
self.searchBar:DockMargin(0, 4, 0, 0)
self.searchBar:SetUpdateOnType(true)
self.searchBar:SetPlaceholderText("Search...")
self.searchBar.OnValueChange = function(this, value)
self:ReloadItemList(value)
end
local menu
self.items = self:Add("DListView")
self.items:Dock(FILL)
self.items:DockMargin(0, 4, 0, 0)
self.items:AddColumn(L"name").Header:SetTextColor(color_black)
self.items:AddColumn(L"category").Header:SetTextColor(color_black)
self.items:AddColumn(L"mode").Header:SetTextColor(color_black)
self.items:AddColumn(L"price").Header:SetTextColor(color_black)
self.items:AddColumn(L"stock").Header:SetTextColor(color_black)
self.items:SetMultiSelect(false)
self.items.OnRowRightClick = function(this, index, line)
if (IsValid(menu)) then
menu:Remove()
end
local uniqueID = line.item
menu = DermaMenu()
-- Modes of the item.
local mode, panel = menu:AddSubMenu(L"mode")
panel:SetImage("icon16/key.png")
-- Disable buying/selling of the item.
mode:AddOption(L"none", function()
self:updateVendor("mode", {uniqueID, nil})
end):SetImage("icon16/cog_error.png")
-- Allow the vendor to sell and buy this item.
mode:AddOption(L"vendorBoth", function()
self:updateVendor("mode", {uniqueID, VENDOR_SELLANDBUY})
end):SetImage("icon16/cog.png")
-- Only allow the vendor to buy this item from players.
mode:AddOption(L"vendorBuy", function()
self:updateVendor("mode", {uniqueID, VENDOR_BUYONLY})
end):SetImage("icon16/cog_delete.png")
-- Only allow the vendor to sell this item to players.
mode:AddOption(L"vendorSell", function()
self:updateVendor("mode", {uniqueID, VENDOR_SELLONLY})
end):SetImage("icon16/cog_add.png")
local itemTable = ix.item.list[uniqueID]
-- Set the price of the item.
menu:AddOption(L"price", function()
Derma_StringRequest(
itemTable.GetName and itemTable:GetName() or L(itemTable.name),
L"vendorPriceReq",
entity:GetPrice(uniqueID),
function(text)
text = tonumber(text)
if (text == itemTable.price) then
text = nil
end
self:updateVendor("price", {uniqueID, text})
end
)
end):SetImage("icon16/coins.png")
-- Set the stock of the item or disable it.
local stock, menuPanel = menu:AddSubMenu(L"stock")
menuPanel:SetImage("icon16/table.png")
-- Disable the use of stocks for this item.
stock:AddOption(L"disable", function()
self:updateVendor("stockDisable", uniqueID)
end):SetImage("icon16/table_delete.png")
-- Edit the maximum stock for this item.
stock:AddOption(L"edit", function()
local _, max = entity:GetStock(uniqueID)
Derma_StringRequest(
itemTable.GetName and itemTable:GetName() or L(itemTable.name),
L"vendorStockReq",
max or 1,
function(text)
self:updateVendor("stockMax", {uniqueID, text})
end
)
end):SetImage("icon16/table_edit.png")
-- Edit the current stock of this item.
stock:AddOption(L"vendorEditCurStock", function()
Derma_StringRequest(
itemTable.GetName and itemTable:GetName() or L(itemTable.name),
L"vendorStockCurReq",
entity:GetStock(uniqueID) or 0,
function(text)
self:updateVendor("stock", {uniqueID, text})
end
)
end):SetImage("icon16/table_edit.png")
menu:Open()
end
self:ReloadItemList()
end
function PANEL:ReloadItemList(filter)
local entity = ix.gui.vendor.entity
self.lines = {}
self.items:Clear()
for k, v in SortedPairs(ix.item.list) do
local itemName = v.GetName and v:GetName() or L(v.name)
if (filter and !itemName:lower():find(filter:lower(), 1, false)) then
continue
end
local mode = entity.items[k] and entity.items[k][VENDOR_MODE]
local current, max = entity:GetStock(k)
local panel = self.items:AddLine(
itemName,
v.category or L"none",
mode and L(VENDOR_TEXT[mode]) or L"none",
entity:GetPrice(k),
max and current.."/"..max or "-"
)
panel.item = k
self.lines[k] = panel
end
end
function PANEL:OnRemove()
if (IsValid(ix.gui.vendor)) then
ix.gui.vendor:Remove()
end
if (IsValid(ix.gui.editorFaction)) then
ix.gui.editorFaction:Remove()
end
end
function PANEL:updateVendor(key, value)
net.Start("ixVendorEdit")
net.WriteString(key)
net.WriteType(value)
net.SendToServer()
end
vgui.Register("ixVendorEditor", PANEL, "DFrame")

View File

@@ -0,0 +1,60 @@
local PANEL = {}
function PANEL:Init()
self:SetSize(256, 280)
self:Center()
self:MakePopup()
self:SetTitle(L"vendorFaction")
self.scroll = self:Add("DScrollPanel")
self.scroll:Dock(FILL)
self.scroll:DockPadding(0, 0, 0, 4)
self.factions = {}
self.classes = {}
for k, v in ipairs(ix.faction.indices) do
local panel = self.scroll:Add("DPanel")
panel:Dock(TOP)
panel:DockPadding(4, 4, 4, 4)
panel:DockMargin(0, 0, 0, 4)
local faction = panel:Add("DCheckBoxLabel")
faction:Dock(TOP)
faction:SetText(L(v.name))
faction:DockMargin(0, 0, 0, 4)
faction.OnChange = function(this, state)
self:updateVendor("faction", v.uniqueID)
end
self.factions[v.uniqueID] = faction
for _, v2 in ipairs(ix.class.list) do
if (v2.faction == k) then
local class = panel:Add("DCheckBoxLabel")
class:Dock(TOP)
class:DockMargin(16, 0, 0, 4)
class:SetText(L(v2.name))
class.OnChange = function(this, state)
self:updateVendor("class", v2.uniqueID)
end
self.classes[v2.uniqueID] = class
panel:SetTall(panel:GetTall() + class:GetTall() + 4)
end
end
end
end
function PANEL:Setup()
for k, _ in pairs(self.entity.factions or {}) do
self.factions[k]:SetChecked(true)
end
for k, _ in pairs(self.entity.classes or {}) do
self.classes[k]:SetChecked(true)
end
end
vgui.Register("ixVendorFactionEditor", PANEL, "DFrame")

View File

@@ -0,0 +1,349 @@
ENT.Type = "anim"
ENT.PrintName = "Vendor"
ENT.Category = "Helix"
ENT.Spawnable = true
ENT.AdminOnly = true
ENT.isVendor = true
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("Bool", 0, "NoBubble")
self:NetworkVar("String", 0, "DisplayName")
self:NetworkVar("String", 1, "Description")
end
function ENT:Initialize()
if (SERVER) then
self:SetModel("models/mossman.mdl")
self:SetUseType(SIMPLE_USE)
self:SetMoveType(MOVETYPE_NONE)
self:DrawShadow(true)
self:InitPhysObj()
self:AddCallback("OnAngleChange", function(entity)
local mins, maxs = entity:GetAxisAlignedBoundingBox()
entity:SetCollisionBounds(mins, maxs)
end)
self.items = {}
self.messages = {}
self.factions = {}
self.classes = {}
self:SetDisplayName("John Doe")
self:SetDescription("")
self.receivers = {}
end
timer.Simple(1, function()
if (IsValid(self)) then
self:SetAnim()
end
end)
end
function ENT:InitPhysObj()
local mins, maxs = self:GetAxisAlignedBoundingBox()
local bPhysObjCreated = self:PhysicsInitBox(mins, maxs)
if (bPhysObjCreated) then
local physObj = self:GetPhysicsObject()
physObj:EnableMotion(false)
physObj:Sleep()
end
end
function ENT:GetAxisAlignedBoundingBox()
local mins, maxs = self:GetModelBounds()
mins = Vector(mins.x, mins.y, 0)
mins, maxs = self:GetRotatedAABB(mins, maxs)
return mins, maxs
end
function ENT:CanAccess(client)
local bAccess = false
local uniqueID = ix.faction.indices[client:Team()].uniqueID
if (self.factions and !table.IsEmpty(self.factions)) then
if (self.factions[uniqueID]) then
bAccess = true
else
return false
end
end
if (bAccess and self.classes and !table.IsEmpty(self.classes)) then
local class = ix.class.list[client:GetCharacter():GetClass()]
local classID = class and class.uniqueID
if (classID and !self.classes[classID]) then
return false
end
end
return true
end
function ENT:GetStock(uniqueID)
if (self.items[uniqueID] and self.items[uniqueID][VENDOR_MAXSTOCK]) then
return self.items[uniqueID][VENDOR_STOCK] or 0, self.items[uniqueID][VENDOR_MAXSTOCK]
end
end
function ENT:GetPrice(uniqueID, selling)
local price = ix.item.list[uniqueID] and self.items[uniqueID] and
self.items[uniqueID][VENDOR_PRICE] or ix.item.list[uniqueID].price or 0
if (selling) then
price = math.floor(price * (self.scale or 0.5))
end
return price
end
function ENT:CanSellToPlayer(client, uniqueID)
local data = self.items[uniqueID]
if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then
return false
end
if (data[VENDOR_MODE] == VENDOR_BUYONLY) then
return false
end
if (!client:GetCharacter():HasMoney(self:GetPrice(uniqueID))) then
return false
end
if (data[VENDOR_STOCK] and data[VENDOR_STOCK] < 1) then
return false
end
return true
end
function ENT:CanBuyFromPlayer(client, uniqueID)
local data = self.items[uniqueID]
if (!data or !client:GetCharacter() or !ix.item.list[uniqueID]) then
return false
end
if (data[VENDOR_MODE] != VENDOR_SELLONLY) then
return false
end
if (!self:HasMoney(data[VENDOR_PRICE] or ix.item.list[uniqueID].price or 0)) then
return false
end
return true
end
function ENT:HasMoney(amount)
-- Vendor not using money system so they can always afford it.
if (!self.money) then
return true
end
return self.money >= amount
end
function ENT:SetAnim()
for k, v in ipairs(self:GetSequenceList()) do
if (v:lower():find("idle") and v != "idlenoise") then
return self:ResetSequence(k)
end
end
if (self:GetSequenceCount() > 1) then
self:ResetSequence(4)
end
end
if (SERVER) then
local PLUGIN = PLUGIN
function ENT:SpawnFunction(client, trace)
local angles = (trace.HitPos - client:GetPos()):Angle()
angles.r = 0
angles.p = 0
angles.y = angles.y + 180
local entity = ents.Create("ix_vendor")
entity:SetPos(trace.HitPos)
entity:SetAngles(angles)
entity:Spawn()
PLUGIN:SaveData()
return entity
end
function ENT:Use(activator)
local character = activator:GetCharacter()
if (!self:CanAccess(activator) or hook.Run("CanPlayerUseVendor", activator, self) == false) then
if (self.messages[VENDOR_NOTRADE]) then
activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_NOTRADE])
else
activator:NotifyLocalized("vendorNoTrade")
end
return
end
self.receivers[#self.receivers + 1] = activator
if (self.messages[VENDOR_WELCOME]) then
activator:ChatPrint(self:GetDisplayName()..": "..self.messages[VENDOR_WELCOME])
end
local items = {}
-- Only send what is needed.
for k, v in pairs(self.items) do
if (!table.IsEmpty(v) and (CAMI.PlayerHasAccess(activator, "Helix - Manage Vendors", nil) or v[VENDOR_MODE])) then
items[k] = v
end
end
self.scale = self.scale or 0.5
activator.ixVendor = self
-- force sync to prevent outdated inventories while buying/selling
if (character) then
character:GetInventory():Sync(activator, true)
end
net.Start("ixVendorOpen")
net.WriteEntity(self)
net.WriteUInt(self.money or 0, 16)
net.WriteTable(items)
net.WriteFloat(self.scale or 0.5)
net.Send(activator)
ix.log.Add(activator, "vendorUse", self:GetDisplayName())
end
function ENT:SetMoney(value)
self.money = value
net.Start("ixVendorMoney")
net.WriteUInt(value and value or -1, 16)
net.Send(self.receivers)
end
function ENT:GiveMoney(value)
if (self.money) then
self:SetMoney(self:GetMoney() + value)
end
end
function ENT:TakeMoney(value)
if (self.money) then
self:GiveMoney(-value)
end
end
function ENT:SetStock(uniqueID, value)
if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then
return
end
self.items[uniqueID] = self.items[uniqueID] or {}
self.items[uniqueID][VENDOR_STOCK] = math.min(value, self.items[uniqueID][VENDOR_MAXSTOCK])
net.Start("ixVendorStock")
net.WriteString(uniqueID)
net.WriteUInt(value, 16)
net.Send(self.receivers)
end
function ENT:AddStock(uniqueID, value)
if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then
return
end
self:SetStock(uniqueID, self:GetStock(uniqueID) + (value or 1))
end
function ENT:TakeStock(uniqueID, value)
if (!self.items[uniqueID][VENDOR_MAXSTOCK]) then
return
end
self:AddStock(uniqueID, -(value or 1))
end
else
function ENT:CreateBubble()
self.bubble = ClientsideModel("models/extras/info_speech.mdl", RENDERGROUP_OPAQUE)
self.bubble:SetPos(self:GetPos() + Vector(0, 0, 84))
self.bubble:SetModelScale(0.6, 0)
end
function ENT:Draw()
local bubble = self.bubble
if (IsValid(bubble)) then
local realTime = RealTime()
bubble:SetRenderOrigin(self:GetPos() + Vector(0, 0, 84 + math.sin(realTime * 3) * 0.05))
bubble:SetRenderAngles(Angle(0, realTime * 100, 0))
end
self:DrawModel()
end
function ENT:Think()
local noBubble = self:GetNoBubble()
if (IsValid(self.bubble) and noBubble) then
self.bubble:Remove()
elseif (!IsValid(self.bubble) and !noBubble) then
self:CreateBubble()
end
if ((self.nextAnimCheck or 0) < CurTime()) then
self:SetAnim()
self.nextAnimCheck = CurTime() + 60
end
self:SetNextClientThink(CurTime() + 0.25)
return true
end
function ENT:OnRemove()
if (IsValid(self.bubble)) then
self.bubble:Remove()
end
end
ENT.PopulateEntityInfo = true
function ENT:OnPopulateEntityInfo(container)
local name = container:AddRow("name")
name:SetImportant()
name:SetText(self:GetDisplayName())
name:SizeToContents()
local descriptionText = self:GetDescription()
if (descriptionText != "") then
local description = container:AddRow("description")
description:SetText(self:GetDescription())
description:SizeToContents()
end
end
end
function ENT:GetMoney()
return self.money
end

View File

@@ -0,0 +1,711 @@
-- luacheck: globals VENDOR_BUY VENDOR_SELL VENDOR_BOTH VENDOR_WELCOME VENDOR_LEAVE VENDOR_NOTRADE VENDOR_PRICE
-- luacheck: globals VENDOR_STOCK VENDOR_MODE VENDOR_MAXSTOCK VENDOR_SELLANDBUY VENDOR_SELLONLY VENDOR_BUYONLY VENDOR_TEXT
local PLUGIN = PLUGIN
PLUGIN.name = "Vendors"
PLUGIN.author = "Chessnut"
PLUGIN.description = "Adds NPC vendors that can sell things."
CAMI.RegisterPrivilege({
Name = "Helix - Manage Vendors",
MinAccess = "admin"
})
VENDOR_BUY = 1
VENDOR_SELL = 2
VENDOR_BOTH = 3
-- Keys for vendor messages.
VENDOR_WELCOME = 1
VENDOR_LEAVE = 2
VENDOR_NOTRADE = 3
-- Keys for item information.
VENDOR_PRICE = 1
VENDOR_STOCK = 2
VENDOR_MODE = 3
VENDOR_MAXSTOCK = 4
-- Sell and buy the item.
VENDOR_SELLANDBUY = 1
-- Only sell the item to the player.
VENDOR_SELLONLY = 2
-- Only buy the item from the player.
VENDOR_BUYONLY = 3
if (SERVER) then
util.AddNetworkString("ixVendorOpen")
util.AddNetworkString("ixVendorClose")
util.AddNetworkString("ixVendorTrade")
util.AddNetworkString("ixVendorEdit")
util.AddNetworkString("ixVendorEditFinish")
util.AddNetworkString("ixVendorEditor")
util.AddNetworkString("ixVendorMoney")
util.AddNetworkString("ixVendorStock")
util.AddNetworkString("ixVendorAddItem")
function PLUGIN:SaveData()
local data = {}
for _, entity in ipairs(ents.FindByClass("ix_vendor")) do
local bodygroups = {}
for _, v in ipairs(entity:GetBodyGroups() or {}) do
bodygroups[v.id] = entity:GetBodygroup(v.id)
end
data[#data + 1] = {
name = entity:GetDisplayName(),
description = entity:GetDescription(),
pos = entity:GetPos(),
angles = entity:GetAngles(),
model = entity:GetModel(),
skin = entity:GetSkin(),
bodygroups = bodygroups,
bubble = entity:GetNoBubble(),
items = entity.items,
factions = entity.factions,
classes = entity.classes,
money = entity.money,
scale = entity.scale
}
end
self:SetData(data)
end
function PLUGIN:LoadData()
for _, v in ipairs(self:GetData() or {}) do
local entity = ents.Create("ix_vendor")
entity:SetPos(v.pos)
entity:SetAngles(v.angles)
entity:Spawn()
entity:SetModel(v.model)
entity:SetSkin(v.skin or 0)
entity:InitPhysObj()
entity:SetNoBubble(v.bubble)
entity:SetDisplayName(v.name)
entity:SetDescription(v.description)
for id, bodygroup in pairs(v.bodygroups or {}) do
entity:SetBodygroup(id, bodygroup)
end
local items = {}
for uniqueID, data in pairs(v.items) do
items[tostring(uniqueID)] = data
end
entity.items = items
entity.factions = v.factions or {}
entity.classes = v.classes or {}
entity.money = v.money
entity.scale = v.scale or 0.5
end
end
function PLUGIN:CanVendorSellItem(client, vendor, itemID)
local tradeData = vendor.items[itemID]
local char = client:GetCharacter()
if (!tradeData or !char) then
return false
end
if (!char:HasMoney(tradeData[1] or 0)) then
return false
end
return true
end
ix.log.AddType("vendorUse", function(client, ...)
local arg = {...}
return string.format("%s used the '%s' vendor.", client:Name(), arg[1])
end)
ix.log.AddType("vendorBuy", function(client, ...)
local arg = {...}
return string.format("%s purchased a '%s' from the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3])
end)
ix.log.AddType("vendorSell", function(client, ...)
local arg = {...}
return string.format("%s sold a '%s' to the '%s' vendor for %s.", client:Name(), arg[1], arg[2], arg[3])
end)
net.Receive("ixVendorClose", function(length, client)
local entity = client.ixVendor
if (IsValid(entity)) then
for k, v in ipairs(entity.receivers) do
if (v == client) then
table.remove(entity.receivers, k)
break
end
end
client.ixVendor = nil
end
end)
local function UpdateEditReceivers(receivers, key, value)
net.Start("ixVendorEdit")
net.WriteString(key)
net.WriteType(value)
net.Send(receivers)
end
net.Receive("ixVendorEdit", function(length, client)
if (!CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil)) then
return
end
local entity = client.ixVendor
if (!IsValid(entity)) then
return
end
local key = net.ReadString()
local data = net.ReadType()
local feedback = true
if (key == "name") then
entity:SetDisplayName(data)
elseif (key == "description") then
entity:SetDescription(data)
elseif (key == "bubble") then
entity:SetNoBubble(data)
elseif (key == "mode") then
local uniqueID = data[1]
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_MODE] = data[2]
UpdateEditReceivers(entity.receivers, key, data)
elseif (key == "price") then
local uniqueID = data[1]
data[2] = tonumber(data[2])
if (data[2]) then
data[2] = math.Round(data[2])
end
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_PRICE] = data[2]
UpdateEditReceivers(entity.receivers, key, data)
data = uniqueID
elseif (key == "stockDisable") then
local uniqueID = data[1]
entity.items[data] = entity.items[uniqueID] or {}
entity.items[data][VENDOR_MAXSTOCK] = nil
UpdateEditReceivers(entity.receivers, key, data)
elseif (key == "stockMax") then
local uniqueID = data[1]
data[2] = math.max(math.Round(tonumber(data[2]) or 1), 1)
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2]
entity.items[uniqueID][VENDOR_STOCK] = math.Clamp(entity.items[uniqueID][VENDOR_STOCK] or data[2], 1, data[2])
data[3] = entity.items[uniqueID][VENDOR_STOCK]
UpdateEditReceivers(entity.receivers, key, data)
data = uniqueID
elseif (key == "stock") then
local uniqueID = data[1]
entity.items[uniqueID] = entity.items[uniqueID] or {}
if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then
data[2] = math.max(math.Round(tonumber(data[2]) or 0), 0)
entity.items[uniqueID][VENDOR_MAXSTOCK] = data[2]
end
data[2] = math.Clamp(math.Round(tonumber(data[2]) or 0), 0, entity.items[uniqueID][VENDOR_MAXSTOCK])
entity.items[uniqueID][VENDOR_STOCK] = data[2]
UpdateEditReceivers(entity.receivers, key, data)
data = uniqueID
elseif (key == "faction") then
local faction = ix.faction.teams[data]
if (faction) then
entity.factions[data] = !entity.factions[data]
if (!entity.factions[data]) then
entity.factions[data] = nil
end
end
local uniqueID = data
data = {uniqueID, entity.factions[uniqueID]}
elseif (key == "class") then
local class
for _, v in ipairs(ix.class.list) do
if (v.uniqueID == data) then
class = v
break
end
end
if (class) then
entity.classes[data] = !entity.classes[data]
if (!entity.classes[data]) then
entity.classes[data] = nil
end
end
local uniqueID = data
data = {uniqueID, entity.classes[uniqueID]}
elseif (key == "model") then
entity:SetModel(data)
entity:InitPhysObj()
entity:SetAnim()
elseif (key == "useMoney") then
if (entity.money) then
entity:SetMoney()
else
entity:SetMoney(0)
end
elseif (key == "money") then
data = math.Round(math.abs(tonumber(data) or 0))
entity:SetMoney(data)
feedback = false
elseif (key == "scale") then
data = tonumber(data) or 0.5
entity.scale = data
UpdateEditReceivers(entity.receivers, key, data)
end
PLUGIN:SaveData()
if (feedback) then
local receivers = {}
for _, v in ipairs(entity.receivers) do
if (CAMI.PlayerHasAccess(v, "Helix - Manage Vendors", nil)) then
receivers[#receivers + 1] = v
end
end
net.Start("ixVendorEditFinish")
net.WriteString(key)
net.WriteType(data)
net.Send(receivers)
end
end)
net.Receive("ixVendorTrade", function(length, client)
if ((client.ixVendorTry or 0) < CurTime()) then
client.ixVendorTry = CurTime() + 0.33
else
return
end
local entity = client.ixVendor
if (!IsValid(entity) or client:GetPos():Distance(entity:GetPos()) > 192) then
return
end
if (!entity:CanAccess(client)) then
return
end
local uniqueID = net.ReadString()
local isSellingToVendor = net.ReadBool()
if (entity.items[uniqueID] and
hook.Run("CanPlayerTradeWithVendor", client, entity, uniqueID, isSellingToVendor) != false) then
local price = entity:GetPrice(uniqueID, isSellingToVendor)
if (isSellingToVendor) then
local found = false
local name
if (!entity:HasMoney(price)) then
return client:NotifyLocalized("vendorNoMoney")
end
local stock, max = entity:GetStock(uniqueID)
if (stock and stock >= max) then
return client:NotifyLocalized("vendorMaxStock")
end
local invOkay = true
for k, _ in client:GetCharacter():GetInventory():Iter() do
if (k.uniqueID == uniqueID and k:GetID() != 0 and ix.item.instances[k:GetID()] and k:GetData("equip", false) == false) then
invOkay = k:Remove()
found = true
name = L(k.name, client)
break
end
end
if (!found) then
return
end
if (!invOkay) then
client:GetCharacter():GetInventory():Sync(client, true)
return client:NotifyLocalized("tellAdmin", "trd!iid")
end
client:GetCharacter():GiveMoney(price, price == 0)
client:NotifyLocalized("businessSell", name, ix.currency.Get(price))
entity:TakeMoney(price)
entity:AddStock(uniqueID)
ix.log.Add(client, "vendorSell", name, entity:GetDisplayName(), ix.currency.Get(price))
else
local stock = entity:GetStock(uniqueID)
if (stock and stock < 1) then
return client:NotifyLocalized("vendorNoStock")
end
if (!client:GetCharacter():HasMoney(price)) then
return client:NotifyLocalized("canNotAfford")
end
if !entity:CanSellToPlayer(client, uniqueID) then
return false
end
local name = L(ix.item.list[uniqueID].name, client)
client:GetCharacter():TakeMoney(price, price == 0)
client:NotifyLocalized("businessPurchase", name, ix.currency.Get(price))
entity:GiveMoney(price)
if (!client:GetCharacter():GetInventory():Add(uniqueID)) then
ix.item.Spawn(uniqueID, client)
else
net.Start("ixVendorAddItem")
net.WriteString(uniqueID)
net.Send(client)
end
entity:TakeStock(uniqueID)
ix.log.Add(client, "vendorBuy", name, entity:GetDisplayName(), ix.currency.Get(price))
end
PLUGIN:SaveData()
hook.Run("CharacterVendorTraded", client, entity, uniqueID, isSellingToVendor)
else
client:NotifyLocalized("vendorNoTrade")
end
end)
else
VENDOR_TEXT = {}
VENDOR_TEXT[VENDOR_SELLANDBUY] = "vendorBoth"
VENDOR_TEXT[VENDOR_BUYONLY] = "vendorBuy"
VENDOR_TEXT[VENDOR_SELLONLY] = "vendorSell"
net.Receive("ixVendorOpen", function()
local entity = net.ReadEntity()
if (!IsValid(entity)) then
return
end
entity.money = net.ReadUInt(16)
entity.items = net.ReadTable()
entity.scale = net.ReadFloat()
ix.gui.vendor = vgui.Create("ixVendor")
ix.gui.vendor:SetReadOnly(false)
ix.gui.vendor:Setup(entity)
end)
net.Receive("ixVendorEditor", function()
local entity = net.ReadEntity()
if (!IsValid(entity) or !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Vendors", nil)) then
return
end
entity.money = net.ReadUInt(16)
entity.items = net.ReadTable()
entity.scale = net.ReadFloat()
entity.messages = net.ReadTable()
entity.factions = net.ReadTable()
entity.classes = net.ReadTable()
ix.gui.vendor = vgui.Create("ixVendor")
ix.gui.vendor:SetReadOnly(true)
ix.gui.vendor:Setup(entity)
ix.gui.vendorEditor = vgui.Create("ixVendorEditor")
end)
net.Receive("ixVendorEdit", function()
local panel = ix.gui.vendor
if (!IsValid(panel)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local key = net.ReadString()
local data = net.ReadType()
if (key == "mode") then
entity.items[data[1]] = entity.items[data[1]] or {}
entity.items[data[1]][VENDOR_MODE] = data[2]
if (!data[2]) then
panel:removeItem(data[1])
elseif (data[2] == VENDOR_SELLANDBUY) then
panel:addItem(data[1])
else
panel:addItem(data[1], data[2] == VENDOR_SELLONLY and "selling" or "buying")
panel:removeItem(data[1], data[2] == VENDOR_SELLONLY and "buying" or "selling")
end
elseif (key == "price") then
local uniqueID = data[1]
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_PRICE] = tonumber(data[2])
elseif (key == "stockDisable") then
if (entity.items[data]) then
entity.items[data][VENDOR_MAXSTOCK] = nil
end
elseif (key == "stockMax") then
local uniqueID = data[1]
local value = data[2]
local current = data[3]
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_MAXSTOCK] = value
entity.items[uniqueID][VENDOR_STOCK] = current
elseif (key == "stock") then
local uniqueID = data[1]
local value = data[2]
entity.items[uniqueID] = entity.items[uniqueID] or {}
if (!entity.items[uniqueID][VENDOR_MAXSTOCK]) then
entity.items[uniqueID][VENDOR_MAXSTOCK] = value
end
entity.items[uniqueID][VENDOR_STOCK] = value
elseif (key == "scale") then
entity.scale = data
end
end)
net.Receive("ixVendorEditFinish", function()
local panel = ix.gui.vendor
local editor = ix.gui.vendorEditor
if (!IsValid(panel) or !IsValid(editor)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local key = net.ReadString()
local data = net.ReadType()
if (key == "name") then
editor.name:SetText(data)
elseif (key == "description") then
editor.description:SetText(data)
elseif (key == "bubble") then
editor.bubble.noSend = true
editor.bubble:SetValue(data and 1 or 0)
elseif (key == "mode") then
if (data[2] == nil) then
editor.lines[data[1]]:SetValue(3, L"none")
else
editor.lines[data[1]]:SetValue(3, L(VENDOR_TEXT[data[2]]))
end
elseif (key == "price") then
editor.lines[data]:SetValue(4, entity:GetPrice(data))
elseif (key == "stockDisable") then
editor.lines[data]:SetValue(5, "-")
elseif (key == "stockMax" or key == "stock") then
local current, max = entity:GetStock(data)
editor.lines[data]:SetValue(5, current.."/"..max)
elseif (key == "faction") then
local uniqueID = data[1]
local state = data[2]
local editPanel = ix.gui.editorFaction
entity.factions[uniqueID] = state
if (IsValid(editPanel) and IsValid(editPanel.factions[uniqueID])) then
editPanel.factions[uniqueID]:SetChecked(state == true)
end
elseif (key == "class") then
local uniqueID = data[1]
local state = data[2]
local editPanel = ix.gui.editorFaction
entity.classes[uniqueID] = state
if (IsValid(editPanel) and IsValid(editPanel.classes[uniqueID])) then
editPanel.classes[uniqueID]:SetChecked(state == true)
end
elseif (key == "model") then
editor.model:SetText(entity:GetModel())
elseif (key == "scale") then
editor.sellScale.noSend = true
editor.sellScale:SetValue(data)
end
surface.PlaySound("buttons/button14.wav")
end)
net.Receive("ixVendorMoney", function()
local panel = ix.gui.vendor
if (!IsValid(panel)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local value = net.ReadUInt(16)
value = value != -1 and value or nil
entity.money = value
local editor = ix.gui.vendorEditor
if (IsValid(editor)) then
local useMoney = tonumber(value) != nil
editor.money:SetDisabled(!useMoney)
editor.money:SetEnabled(useMoney)
editor.money:SetText(useMoney and value or "")
end
end)
net.Receive("ixVendorStock", function()
local panel = ix.gui.vendor
if (!IsValid(panel)) then
return
end
local entity = panel.entity
if (!IsValid(entity)) then
return
end
local uniqueID = net.ReadString()
local amount = net.ReadUInt(16)
entity.items[uniqueID] = entity.items[uniqueID] or {}
entity.items[uniqueID][VENDOR_STOCK] = amount
local editor = ix.gui.vendorEditor
if (IsValid(editor)) then
local _, max = entity:GetStock(uniqueID)
editor.lines[uniqueID]:SetValue(4, amount .. "/" .. max)
end
end)
net.Receive("ixVendorAddItem", function()
local uniqueID = net.ReadString()
if (IsValid(ix.gui.vendor)) then
ix.gui.vendor:addItem(uniqueID, "buying")
end
end)
end
properties.Add("vendor_edit", {
MenuLabel = "Edit Vendor",
Order = 999,
MenuIcon = "icon16/user_edit.png",
Filter = function(self, entity, client)
if (!IsValid(entity)) then return false end
if (entity:GetClass() != "ix_vendor") then return false end
if (!gamemode.Call( "CanProperty", client, "vendor_edit", entity)) then return false end
return CAMI.PlayerHasAccess(client, "Helix - Manage Vendors", nil)
end,
Action = function(self, entity)
self:MsgStart()
net.WriteEntity(entity)
self:MsgEnd()
end,
Receive = function(self, length, client)
local entity = net.ReadEntity()
if (!IsValid(entity)) then return end
if (!self:Filter(entity, client)) then return end
entity.receivers[#entity.receivers + 1] = client
local itemsTable = {}
for k, v in pairs(entity.items) do
if (!table.IsEmpty(v)) then
itemsTable[k] = v
end
end
client.ixVendor = entity
net.Start("ixVendorEditor")
net.WriteEntity(entity)
net.WriteUInt(entity.money or 0, 16)
net.WriteTable(itemsTable)
net.WriteFloat(entity.scale or 0.5)
net.WriteTable(entity.messages)
net.WriteTable(entity.factions)
net.WriteTable(entity.classes)
net.Send(client)
end
})

View File

@@ -0,0 +1,209 @@
PLUGIN.name = "Weapon Select"
PLUGIN.author = "Chessnut"
PLUGIN.description = "A replacement for the default weapon selection."
if (CLIENT) then
PLUGIN.index = PLUGIN.index or 1
PLUGIN.deltaIndex = PLUGIN.deltaIndex or PLUGIN.index
PLUGIN.infoAlpha = PLUGIN.infoAlpha or 0
PLUGIN.alpha = PLUGIN.alpha or 0
PLUGIN.alphaDelta = PLUGIN.alphaDelta or PLUGIN.alpha
PLUGIN.fadeTime = PLUGIN.fadeTime or 0
local matrixScale = Vector(1, 1, 0)
function PLUGIN:LoadFonts(font, genericFont)
surface.CreateFont("ixWeaponSelectFont", {
font = font,
size = ScreenScale(16),
extended = true,
weight = 1000
})
end
function PLUGIN:HUDShouldDraw(name)
if (name == "CHudWeaponSelection") then
return false
end
end
function PLUGIN:HUDPaint()
local frameTime = FrameTime()
self.alphaDelta = Lerp(frameTime * 10, self.alphaDelta, self.alpha)
local fraction = self.alphaDelta
if (fraction > 0.01) then
local x, y = ScrW() * 0.5, ScrH() * 0.5
local spacing = math.pi * 0.85
local radius = 240 * self.alphaDelta
local shiftX = ScrW() * .02
self.deltaIndex = Lerp(frameTime * 12, self.deltaIndex, self.index)
local weapons = LocalPlayer():GetWeapons()
local index = self.deltaIndex
if (!weapons[self.index]) then
self.index = #weapons
end
for i = 1, #weapons do
local theta = (i - index) * 0.1
local color = ColorAlpha(
i == self.index and ix.config.Get("color") or color_white,
(255 - math.abs(theta * 3) * 255) * fraction
)
local lastY = 0
if (self.markup and (i < self.index or i == 1)) then
if (self.index != 1) then
local _, h = self.markup:Size()
lastY = h * fraction
end
if (i == 1 or i == self.index - 1) then
self.infoAlpha = Lerp(frameTime * 3, self.infoAlpha, 255)
self.markup:Draw(x + 6 + shiftX, y + 30, 0, 0, self.infoAlpha * fraction)
end
end
surface.SetFont("ixWeaponSelectFont")
local weaponName = language.GetPhrase(weapons[i]:GetPrintName()):utf8upper()
local _, ty = surface.GetTextSize(weaponName)
local scale = 1 - math.abs(theta * 2)
local matrix = Matrix()
matrix:Translate(Vector(
shiftX + x + math.cos(theta * spacing + math.pi) * radius + radius,
y + lastY + math.sin(theta * spacing + math.pi) * radius - ty / 2 ,
1))
matrix:Scale(matrixScale * scale)
cam.PushModelMatrix(matrix)
ix.util.DrawText(weaponName, 2, ty / 2, color, 0, 1, "ixWeaponSelectFont")
cam.PopModelMatrix()
end
if (self.fadeTime < CurTime() and self.alpha > 0) then
self.alpha = 0
end
end
end
function PLUGIN:OnIndexChanged(weapon)
self.alpha = 1
self.fadeTime = CurTime() + 5
self.markup = nil
if (IsValid(weapon)) then
local instructions = weapon.Instructions
local text = ""
if (instructions != nil and instructions:find("%S")) then
local color = ix.config.Get("color")
text = text .. string.format(
"<font=ixItemBoldFont><color=%d,%d,%d>%s</font></color>\n%s\n",
color.r, color.g, color.b, L("Instructions"), instructions
)
end
if (text != "") then
self.markup = markup.Parse("<font=ixItemDescFont>"..text, ScrW() * 0.3)
self.infoAlpha = 0
end
local source, pitch = hook.Run("WeaponCycleSound")
LocalPlayer():EmitSound(source or "common/talk.wav", 50, pitch or 180)
end
end
function PLUGIN:PlayerBindPress(client, bind, pressed)
bind = bind:lower()
// Блокировка селектора при изменении обвесов CW 2.0
-- if client:Alive() then
-- local wep = client:GetActiveWeapon()
-- if weapons.IsBasedOn(wep:GetClass(), "cw_base") then
-- if wep.dt.State == CW_CUSTOMIZE then return end
-- end
-- end
if (!pressed or !bind:find("invprev") and !bind:find("invnext")
and !bind:find("slot") and !bind:find("attack")) then
return
end
local currentWeapon = client:GetActiveWeapon()
local bValid = IsValid(currentWeapon)
local bTool
if (client:InVehicle() or (bValid and currentWeapon:GetClass() == "weapon_physgun" and client:KeyDown(IN_ATTACK))) then
return
end
if (bValid and currentWeapon:GetClass() == "gmod_tool") then
local tool = client:GetTool()
bTool = tool and (tool.Scroll != nil)
end
local weapons = client:GetWeapons()
if (bind:find("invprev") and !bTool) then
local oldIndex = self.index
self.index = math.min(self.index + 1, #weapons)
if (self.alpha == 0 or oldIndex != self.index) then
self:OnIndexChanged(weapons[self.index])
end
return true
elseif (bind:find("invnext") and !bTool) then
local oldIndex = self.index
self.index = math.max(self.index - 1, 1)
if (self.alpha == 0 or oldIndex != self.index) then
self:OnIndexChanged(weapons[self.index])
end
return true
elseif (bind:find("slot")) then
self.index = math.Clamp(tonumber(bind:match("slot(%d)")) or 1, 1, #weapons)
self:OnIndexChanged(weapons[self.index])
return true
elseif (bind:find("attack") and self.alpha > 0) then
local weapon = weapons[self.index]
if (IsValid(weapon)) then
LocalPlayer():EmitSound(hook.Run("WeaponSelectSound", weapon) or "HL2Player.Use")
input.SelectWeapon(weapon)
self.alpha = 0
end
return true
end
end
function PLUGIN:Think()
local client = LocalPlayer()
if (!IsValid(client) or !client:Alive()) then
self.alpha = 0
end
end
function PLUGIN:ScoreboardShow()
self.alpha = 0
end
function PLUGIN:ShouldPopulateEntityInfo(entity)
if (self.alpha > 0) then
return false
end
end
end