mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-17 21:53:46 +03:00
Upload
This commit is contained in:
608
gamemodes/helix/gamemode/core/cl_skin.lua
Normal file
608
gamemodes/helix/gamemode/core/cl_skin.lua
Normal file
@@ -0,0 +1,608 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- luacheck: globals VerticalScale VScale ScreenScaleMin SScaleMin
|
||||
function VerticalScale( size )
|
||||
return size * ( ScrH() / 360.0 )
|
||||
end
|
||||
|
||||
VScale = VerticalScale
|
||||
|
||||
function ScreenScaleMin( size )
|
||||
return math.min(SScale(size), VScale(size))
|
||||
end
|
||||
|
||||
SScaleMin = ScreenScaleMin
|
||||
|
||||
local gradient = surface.GetTextureID("vgui/gradient-d")
|
||||
local gradientUp = surface.GetTextureID("vgui/gradient-u")
|
||||
local gradientLeft = surface.GetTextureID("vgui/gradient-l")
|
||||
local gradientRadial = Material("helix/gui/radial-gradient.png")
|
||||
local defaultBackgroundColor = Color(30, 30, 30, 200)
|
||||
|
||||
local SKIN = {}
|
||||
derma.DefineSkin("helix", "The base skin for the Helix framework.", SKIN)
|
||||
|
||||
SKIN.fontCategory = "ixMediumLightFont"
|
||||
SKIN.fontCategoryBlur = "ixMediumLightBlurFont"
|
||||
SKIN.fontSegmentedProgress = "ixMediumLightFont"
|
||||
|
||||
SKIN.Colours = table.Copy(derma.SkinList.Default.Colours)
|
||||
|
||||
SKIN.Colours.Info = Color(100, 185, 255)
|
||||
SKIN.Colours.Success = Color(64, 185, 85)
|
||||
SKIN.Colours.Error = Color(255, 100, 100)
|
||||
SKIN.Colours.Warning = Color(230, 180, 0)
|
||||
SKIN.Colours.MenuLabel = color_white
|
||||
SKIN.Colours.DarkerBackground = Color(0, 0, 0, 77)
|
||||
|
||||
SKIN.Colours.SegmentedProgress = {}
|
||||
SKIN.Colours.SegmentedProgress.Bar = Color(64, 185, 85)
|
||||
SKIN.Colours.SegmentedProgress.Text = color_white
|
||||
|
||||
SKIN.Colours.Area = {}
|
||||
|
||||
SKIN.Colours.Window.TitleActive = Color(0, 0, 0)
|
||||
SKIN.Colours.Window.TitleInactive = color_white
|
||||
|
||||
SKIN.Colours.Button.Normal = color_white
|
||||
SKIN.Colours.Button.Hover = color_white
|
||||
SKIN.Colours.Button.Down = Color(180, 180, 180)
|
||||
SKIN.Colours.Button.Disabled = Color(0, 0, 0, 100)
|
||||
|
||||
SKIN.Colours.Label.Default = color_white
|
||||
|
||||
function SKIN.tex.Menu_Strip(x, y, width, height, color)
|
||||
surface.SetDrawColor(0, 0, 0, 200)
|
||||
surface.DrawRect(x, y, width, height)
|
||||
|
||||
surface.SetDrawColor(ColorAlpha(color or ix.config.Get("color"), 175))
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(x, y, width, height)
|
||||
|
||||
surface.SetTextColor(color_white)
|
||||
end
|
||||
|
||||
hook.Add("ColorSchemeChanged", "ixSkin", function(color)
|
||||
SKIN.Colours.Area.Background = color
|
||||
end)
|
||||
|
||||
function SKIN:DrawHelixCurved(x, y, radius, segments, barHeight, fraction, color, altColor)
|
||||
radius = radius or math.min(ScreenScale(72), 128) * 2
|
||||
segments = segments or 76
|
||||
barHeight = barHeight or 64
|
||||
color = color or ix.config.Get("color")
|
||||
altColor = altColor or Color(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
|
||||
fraction = fraction or 1
|
||||
|
||||
surface.SetTexture(-1)
|
||||
|
||||
for i = 1, math.ceil(segments) do
|
||||
local angle = math.rad((i / segments) * -360)
|
||||
local barX = x + math.sin(angle + (fraction * math.pi * 2)) * radius
|
||||
local barY = y + math.cos(angle + (fraction * math.pi * 2)) * radius
|
||||
local barOffset = math.sin(SysTime() + i * 0.5)
|
||||
|
||||
if (barOffset > 0) then
|
||||
surface.SetDrawColor(color)
|
||||
else
|
||||
surface.SetDrawColor(altColor)
|
||||
end
|
||||
|
||||
surface.DrawTexturedRectRotated(barX, barY, 4, barOffset * (barHeight * fraction), math.deg(angle))
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:DrawHelix(x, y, width, height, segments, color, fraction, speed)
|
||||
segments = segments or width * 0.05
|
||||
color = color or ix.config.Get("color")
|
||||
fraction = fraction or 0.25
|
||||
speed = speed or 1
|
||||
|
||||
for i = 1, math.ceil(segments) do
|
||||
local offset = math.sin((SysTime() + speed) + i * fraction)
|
||||
local barHeight = height * offset
|
||||
|
||||
surface.SetTexture(-1)
|
||||
|
||||
if (offset > 0) then
|
||||
surface.SetDrawColor(color)
|
||||
else
|
||||
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
|
||||
end
|
||||
|
||||
surface.DrawTexturedRectRotated(x + (i / segments) * width, y + height * 0.5, 4, barHeight, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintFrame(panel)
|
||||
if (!panel.bNoBackgroundBlur) then
|
||||
ix.util.DrawBlur(panel, 10)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(30, 30, 30, 150)
|
||||
surface.DrawRect(0, 0, panel:GetWide(), panel:GetTall())
|
||||
|
||||
if (panel:GetTitle() != "" or panel.btnClose:IsVisible()) then
|
||||
surface.SetDrawColor(ix.config.Get("color"))
|
||||
surface.DrawRect(0, 0, panel:GetWide(), 24)
|
||||
|
||||
if (panel.bHighlighted) then
|
||||
self:DrawImportantBackground(0, 0, panel:GetWide(), 24, ColorAlpha(color_white, 22))
|
||||
end
|
||||
end
|
||||
|
||||
surface.SetDrawColor(ix.config.Get("color"))
|
||||
surface.DrawOutlinedRect(0, 0, panel:GetWide(), panel:GetTall())
|
||||
end
|
||||
|
||||
function SKIN:PaintBaseFrame(panel, width, height)
|
||||
if (!panel.bNoBackgroundBlur) then
|
||||
ix.util.DrawBlur(panel, 10)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(30, 30, 30, 150)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
surface.SetDrawColor(ix.config.Get("color"))
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:DrawImportantBackground(x, y, width, height, color)
|
||||
color = color or defaultBackgroundColor
|
||||
|
||||
surface.SetTexture(gradientLeft)
|
||||
surface.SetDrawColor(color)
|
||||
surface.DrawTexturedRect(x, y, width, height)
|
||||
end
|
||||
|
||||
function SKIN:DrawCharacterStatusBackground(panel, fraction)
|
||||
surface.SetDrawColor(0, 0, 0, fraction * 100)
|
||||
surface.DrawRect(0, 0, ScrW(), ScrH())
|
||||
ix.util.DrawBlurAt(0, 0, ScrW(), ScrH(), 5, nil, fraction * 255)
|
||||
end
|
||||
|
||||
function SKIN:PaintPanel(panel)
|
||||
if (panel.m_bBackground) then
|
||||
local width, height = panel:GetSize()
|
||||
if (panel.m_bgColor) then
|
||||
surface.SetDrawColor(panel.m_bgColor)
|
||||
else
|
||||
surface.SetDrawColor(30, 30, 30, 100)
|
||||
end
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
surface.SetDrawColor(0, 0, 0, 150)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintMenuBackground(panel, width, height, alphaFraction)
|
||||
alphaFraction = alphaFraction or 1
|
||||
|
||||
surface.SetDrawColor(ColorAlpha(color_black, alphaFraction * 255))
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
|
||||
ix.util.DrawBlur(panel, alphaFraction * 15, nil, 200)
|
||||
end
|
||||
|
||||
function SKIN:PaintPlaceholderPanel(panel, width, height, barWidth, padding)
|
||||
local size = math.max(width, height)
|
||||
barWidth = barWidth or size * 0.05
|
||||
|
||||
local segments = size / barWidth
|
||||
|
||||
for i = 1, segments do
|
||||
surface.SetTexture(-1)
|
||||
surface.SetDrawColor(0, 0, 0, 88)
|
||||
surface.DrawTexturedRectRotated(i * barWidth, i * barWidth, barWidth, size * 2, -45)
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintCategoryPanel(panel, text, color)
|
||||
text = text or ""
|
||||
color = color or ix.config.Get("color")
|
||||
|
||||
surface.SetFont(self.fontCategoryBlur)
|
||||
|
||||
local textHeight = select(2, surface.GetTextSize(text)) + 6
|
||||
local width, height = panel:GetSize()
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, 100)
|
||||
surface.DrawRect(0, textHeight, width, height - textHeight)
|
||||
|
||||
self:DrawImportantBackground(0, 0, width, textHeight, color)
|
||||
|
||||
surface.SetTextColor(color_black)
|
||||
surface.SetTextPos(4, 4)
|
||||
surface.DrawText(text)
|
||||
|
||||
surface.SetFont(self.fontCategory)
|
||||
surface.SetTextColor(color_white)
|
||||
surface.SetTextPos(4, 4)
|
||||
surface.DrawText(text)
|
||||
|
||||
surface.SetDrawColor(color)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
|
||||
return 1, textHeight, 1, 1
|
||||
end
|
||||
|
||||
function SKIN:PaintButton(panel)
|
||||
if (panel.m_bBackground) then
|
||||
local w, h = panel:GetWide(), panel:GetTall()
|
||||
local alpha = 50
|
||||
|
||||
if (panel:GetDisabled()) then
|
||||
alpha = 10
|
||||
elseif (panel.Depressed) then
|
||||
alpha = 180
|
||||
elseif (panel.Hovered) then
|
||||
alpha = 75
|
||||
end
|
||||
|
||||
if (panel:GetParent() and panel:GetParent():GetName() == "DListView_Column") then
|
||||
surface.SetDrawColor(color_white)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(30, 30, 30, alpha)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, 180)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
|
||||
surface.SetDrawColor(180, 180, 180, 2)
|
||||
surface.DrawOutlinedRect(1, 1, w - 2, h - 2)
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintEntityInfoBackground(panel, width, height)
|
||||
ix.util.DrawBlur(panel, 1)
|
||||
|
||||
surface.SetDrawColor(self.Colours.DarkerBackground)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintTooltipBackground(panel, width, height)
|
||||
ix.util.DrawBlur(panel, 1)
|
||||
|
||||
surface.SetDrawColor(self.Colours.DarkerBackground)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintTooltipMinimalBackground(panel, width, height)
|
||||
surface.SetDrawColor(0, 0, 0, 150 * panel.fraction)
|
||||
surface.SetMaterial(gradientRadial)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintSegmentedProgressBackground(panel, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintSegmentedProgress(panel, width, height)
|
||||
local font = panel:GetFont() or self.fontSegmentedProgress
|
||||
local textColor = panel:GetTextColor() or self.Colours.SegmentedProgress.Text
|
||||
local barColor = panel:GetBarColor() or self.Colours.SegmentedProgress.Bar
|
||||
local segments = panel:GetSegments()
|
||||
local segmentHalfWidth = width / #segments * 0.5
|
||||
|
||||
surface.SetDrawColor(barColor)
|
||||
surface.DrawRect(0, 0, panel:GetFraction() * width, height)
|
||||
|
||||
for i = 1, #segments do
|
||||
local text = segments[i]
|
||||
local x = (i - 1) / #segments * width + segmentHalfWidth
|
||||
local y = height * 0.5
|
||||
|
||||
draw.SimpleText(text, font, x, y, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintCharacterCreateBackground(panel, width, height)
|
||||
surface.SetDrawColor(40, 40, 40, 255)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintCharacterLoadBackground(panel, width, height)
|
||||
surface.SetDrawColor(40, 40, 40, panel:GetBackgroundFraction() * 255)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintCharacterTransitionOverlay(panel, x, y, width, height, color)
|
||||
color = color or ix.config.Get("color")
|
||||
|
||||
surface.SetDrawColor(color)
|
||||
surface.DrawRect(x, y, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintAreaEntry(panel, width, height)
|
||||
local color = ColorAlpha(panel:GetBackgroundColor() or self.Colours.Area.Background, panel:GetBackgroundAlpha())
|
||||
|
||||
self:DrawImportantBackground(0, 0, width, height, color)
|
||||
end
|
||||
|
||||
function SKIN:PaintListRow(panel, width, height)
|
||||
surface.SetDrawColor(0, 0, 0, 150)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintSettingsRowBackground(panel, width, height)
|
||||
local index = panel:GetBackgroundIndex()
|
||||
local bReset = panel:GetShowReset()
|
||||
|
||||
if (index == 0) then
|
||||
surface.SetDrawColor(30, 30, 30, 45)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
if (bReset) then
|
||||
surface.SetDrawColor(self.Colours.Warning)
|
||||
surface.DrawRect(0, 0, 2, height)
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintVScrollBar(panel, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintScrollBarGrip(panel, width, height)
|
||||
local parent = panel:GetParent()
|
||||
local upButtonHeight = parent.btnUp:GetTall()
|
||||
local downButtonHeight = parent.btnDown:GetTall()
|
||||
|
||||
DisableClipping(true)
|
||||
surface.SetDrawColor(30, 30, 30, 200)
|
||||
surface.DrawRect(4, -upButtonHeight, width - 8, height + upButtonHeight + downButtonHeight)
|
||||
DisableClipping(false)
|
||||
end
|
||||
|
||||
function SKIN:PaintButtonUp(panel, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintButtonDown(panel, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintComboBox(panel, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintComboDownArrow(panel, width, height)
|
||||
surface.SetFont("ixIconsSmall")
|
||||
|
||||
local textWidth, textHeight = surface.GetTextSize("r")
|
||||
local alpha = (panel.ComboBox:IsMenuOpen() or panel.ComboBox.Hovered) and 200 or 100
|
||||
|
||||
surface.SetTextColor(ColorAlpha(ix.config.Get("color"), alpha))
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5)
|
||||
surface.DrawText("r")
|
||||
end
|
||||
|
||||
function SKIN:PaintMenu(panel, width, height)
|
||||
ix.util.DrawBlur(panel)
|
||||
|
||||
surface.SetDrawColor(30, 30, 30, 150)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintMenuOption(panel, width, height)
|
||||
if (panel.m_bBackground and (panel.Hovered or panel.Highlight)) then
|
||||
self:DrawImportantBackground(0, 0, width, height, ix.config.Get("color"))
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintHelixSlider(panel, width, height)
|
||||
surface.SetDrawColor(self.Colours.DarkerBackground)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
surface.SetDrawColor(self.Colours.Success)
|
||||
surface.DrawRect(0, 0, panel:GetVisualFraction() * width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintChatboxTabButton(panel, width, height)
|
||||
if (panel:GetActive()) then
|
||||
surface.SetDrawColor(ix.config.Get("color", Color(75, 119, 190, 255)))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
else
|
||||
surface.SetDrawColor(0, 0, 0, 100)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
if (panel:GetUnread()) then
|
||||
surface.SetDrawColor(ColorAlpha(self.Colours.Warning, Lerp(panel.unreadAlpha, 0, 100)))
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, 0, width, height - 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- border
|
||||
surface.SetDrawColor(color_black)
|
||||
surface.DrawRect(width - 1, 0, 1, height) -- right
|
||||
end
|
||||
|
||||
function SKIN:PaintChatboxTabs(panel, width, height, alpha)
|
||||
surface.SetDrawColor(0, 0, 0, 33)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, 100)
|
||||
surface.SetTexture(gradient)
|
||||
surface.DrawTexturedRect(0, height * 0.5, width, height * 0.5)
|
||||
|
||||
local tab = panel:GetActiveTab()
|
||||
|
||||
if (tab) then
|
||||
local button = tab:GetButton()
|
||||
local x, _ = button:GetPos()
|
||||
|
||||
-- outline
|
||||
surface.SetDrawColor(0, 0, 0, 200)
|
||||
surface.DrawRect(0, height - 1, x, 1) -- left
|
||||
surface.DrawRect(x + button:GetWide(), height - 1, width - x - button:GetWide(), 1) -- right
|
||||
end
|
||||
end
|
||||
|
||||
function SKIN:PaintChatboxBackground(panel, width, height)
|
||||
ix.util.DrawBlur(panel, 10)
|
||||
|
||||
if (panel:GetActive()) then
|
||||
surface.SetDrawColor(ColorAlpha(ix.config.Get("color"), 120))
|
||||
surface.SetTexture(gradientUp)
|
||||
surface.DrawTexturedRect(0, panel.tabs.buttons:GetTall(), width, height * 0.25)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(color_black)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintChatboxEntry(panel, width, height)
|
||||
surface.SetDrawColor(0, 0, 0, 66)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
panel:DrawTextEntryText(color_white, ix.config.Get("color"), color_white)
|
||||
|
||||
surface.SetDrawColor(color_black)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:DrawChatboxPreviewBox(x, y, text, color)
|
||||
color = color or ix.config.Get("color")
|
||||
|
||||
local textWidth, textHeight = surface.GetTextSize(text)
|
||||
local width, height = textWidth + 8, textHeight + 8
|
||||
|
||||
-- background
|
||||
surface.SetDrawColor(color)
|
||||
surface.DrawRect(x, y, width, height)
|
||||
|
||||
-- text
|
||||
surface.SetTextColor(color_white)
|
||||
surface.SetTextPos(x + width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- outline
|
||||
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255)
|
||||
surface.DrawOutlinedRect(x, y, width, height)
|
||||
|
||||
return width
|
||||
end
|
||||
|
||||
function SKIN:DrawChatboxPrefixBox(panel, width, height)
|
||||
local color = panel:GetBackgroundColor()
|
||||
|
||||
-- background
|
||||
surface.SetDrawColor(color)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
-- outline
|
||||
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, 255)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
|
||||
function SKIN:PaintChatboxAutocompleteEntry(panel, width, height)
|
||||
-- selected background
|
||||
if (panel.highlightAlpha > 0) then
|
||||
self:DrawImportantBackground(0, 0, width, height, ColorAlpha(ix.config.Get("color"), panel.highlightAlpha * 66))
|
||||
end
|
||||
|
||||
-- lower border
|
||||
surface.SetDrawColor(200, 200, 200, 33)
|
||||
surface.DrawRect(0, height - 1, width, 1)
|
||||
end
|
||||
|
||||
function SKIN:PaintWindowMinimizeButton(panel, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintWindowMaximizeButton(panel, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintInfoBar(panel, width, height, color)
|
||||
-- bar
|
||||
surface.SetDrawColor(color.r, color.g, color.b, 250)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
-- gradient overlay
|
||||
surface.SetDrawColor(230, 230, 230, 8)
|
||||
surface.SetTexture(gradientUp)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function SKIN:PaintInfoBarBackground(panel, width, height)
|
||||
surface.SetDrawColor(230, 230, 230, 15)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
surface.DrawOutlinedRect(0, 0, width, height)
|
||||
|
||||
panel.bar:PaintManual()
|
||||
|
||||
DisableClipping(true)
|
||||
panel.label:PaintManual()
|
||||
DisableClipping(false)
|
||||
end
|
||||
|
||||
function SKIN:PaintInventorySlot(panel, width, height)
|
||||
surface.SetDrawColor(35, 35, 35, 85)
|
||||
surface.DrawRect(1, 1, width - 2, height - 2)
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, 250)
|
||||
surface.DrawOutlinedRect(1, 1, width - 2, height - 2)
|
||||
end
|
||||
|
||||
do
|
||||
-- check if sounds exist, otherwise fall back to default UI sounds
|
||||
local bWhoosh = file.Exists("sound/helix/ui/whoosh1.wav", "GAME")
|
||||
local bRollover = file.Exists("sound/helix/ui/rollover.wav", "GAME")
|
||||
local bPress = file.Exists("sound/helix/ui/press.wav", "GAME")
|
||||
local bNotify = file.Exists("sound/helix/ui/REPLACEME.wav", "GAME") -- @todo
|
||||
|
||||
sound.Add({
|
||||
name = "Helix.Whoosh",
|
||||
channel = CHAN_STATIC,
|
||||
volume = 0.4,
|
||||
level = 80,
|
||||
pitch = bWhoosh and {90, 105} or 100,
|
||||
sound = bWhoosh and {
|
||||
"helix/ui/whoosh1.wav",
|
||||
"helix/ui/whoosh2.wav",
|
||||
"helix/ui/whoosh3.wav",
|
||||
"helix/ui/whoosh4.wav",
|
||||
"helix/ui/whoosh5.wav",
|
||||
"helix/ui/whoosh6.wav"
|
||||
} or ""
|
||||
})
|
||||
|
||||
sound.Add({
|
||||
name = "Helix.Rollover",
|
||||
channel = CHAN_STATIC,
|
||||
volume = 0.5,
|
||||
level = 80,
|
||||
pitch = {95, 105},
|
||||
sound = bRollover and "helix/ui/rollover.wav" or "ui/buttonrollover.wav"
|
||||
})
|
||||
|
||||
sound.Add({
|
||||
name = "Helix.Press",
|
||||
channel = CHAN_STATIC,
|
||||
volume = 0.5,
|
||||
level = 80,
|
||||
pitch = bPress and {95, 110} or 100,
|
||||
sound = bPress and "helix/ui/press.wav" or "ui/buttonclickrelease.wav"
|
||||
})
|
||||
|
||||
sound.Add({
|
||||
name = "Helix.Notify",
|
||||
channel = CHAN_STATIC,
|
||||
volume = 0.35,
|
||||
level = 80,
|
||||
pitch = 140,
|
||||
sound = bNotify and "helix/ui/REPLACEME.wav" or "weapons/grenade/tick1.wav"
|
||||
})
|
||||
end
|
||||
|
||||
derma.RefreshSkins()
|
||||
174
gamemodes/helix/gamemode/core/derma/cl_attribute.lua
Normal file
174
gamemodes/helix/gamemode/core/derma/cl_attribute.lua
Normal file
@@ -0,0 +1,174 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local gradient = ix.util.GetMaterial("vgui/gradient-u")
|
||||
local gradient2 = ix.util.GetMaterial("vgui/gradient-d")
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "color", "Color")
|
||||
AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "boostValue", "Boost", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetTall(20)
|
||||
|
||||
self.add = self:Add("DImageButton")
|
||||
self.add:SetSize(16, 16)
|
||||
self.add:Dock(RIGHT)
|
||||
self.add:DockMargin(2, 2, 2, 2)
|
||||
self.add:SetImage("icon16/add.png")
|
||||
self.add.OnMousePressed = function()
|
||||
self.pressing = 1
|
||||
self:DoChange()
|
||||
self.add:SetAlpha(150)
|
||||
end
|
||||
self.add.OnMouseReleased = function()
|
||||
if (self.pressing) then
|
||||
self.pressing = nil
|
||||
self.add:SetAlpha(255)
|
||||
end
|
||||
end
|
||||
self.add.OnCursorExited = self.add.OnMouseReleased
|
||||
|
||||
self.sub = self:Add("DImageButton")
|
||||
self.sub:SetSize(16, 16)
|
||||
self.sub:Dock(LEFT)
|
||||
self.sub:DockMargin(2, 2, 2, 2)
|
||||
self.sub:SetImage("icon16/delete.png")
|
||||
self.sub.OnMousePressed = function()
|
||||
self.pressing = -1
|
||||
self:DoChange()
|
||||
self.sub:SetAlpha(150)
|
||||
end
|
||||
self.sub.OnMouseReleased = function()
|
||||
if (self.pressing) then
|
||||
self.pressing = nil
|
||||
self.sub:SetAlpha(255)
|
||||
end
|
||||
end
|
||||
self.sub.OnCursorExited = self.sub.OnMouseReleased
|
||||
|
||||
self.value = 0
|
||||
self.deltaValue = self.value
|
||||
self.max = 10
|
||||
|
||||
self.bar = self:Add("DPanel")
|
||||
self.bar:Dock(FILL)
|
||||
self.bar.Paint = function(this, w, h)
|
||||
surface.SetDrawColor(35, 35, 35, 250)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
|
||||
w, h = w - 4, h - 4
|
||||
|
||||
local value = self.deltaValue / self.max
|
||||
|
||||
if (value > 0) then
|
||||
local color = self.color and self.color or ix.config.Get("color")
|
||||
local boostedValue = self.boostValue or 0
|
||||
local add = 0
|
||||
|
||||
if (self.deltaValue != self.value) then
|
||||
add = 35
|
||||
end
|
||||
|
||||
-- your stat
|
||||
do
|
||||
if !(boostedValue < 0 and math.abs(boostedValue) > self.value) then
|
||||
surface.SetDrawColor(color.r + add, color.g + add, color.b + add, 230)
|
||||
surface.DrawRect(2, 2, w * value, h)
|
||||
|
||||
surface.SetDrawColor(255, 255, 255, 35)
|
||||
surface.SetMaterial(gradient)
|
||||
surface.DrawTexturedRect(2, 2, w * value, h)
|
||||
end
|
||||
end
|
||||
|
||||
-- boosted stat
|
||||
do
|
||||
local boostValue
|
||||
|
||||
if (boostedValue != 0) then
|
||||
if (boostedValue < 0) then
|
||||
local please = math.min(self.value, math.abs(boostedValue))
|
||||
boostValue = ((please or 0) / self.max) * (self.deltaValue / self.value)
|
||||
else
|
||||
boostValue = ((boostedValue or 0) / self.max) * (self.deltaValue / self.value)
|
||||
end
|
||||
|
||||
if (boostedValue < 0) then
|
||||
surface.SetDrawColor(200, 40, 40, 230)
|
||||
|
||||
local bWidth = math.abs(w * boostValue)
|
||||
surface.DrawRect(2 + w * value - bWidth, 2, bWidth, h)
|
||||
|
||||
surface.SetDrawColor(255, 255, 255, 35)
|
||||
surface.SetMaterial(gradient)
|
||||
surface.DrawTexturedRect(2 + w * value - bWidth, 2, bWidth, h)
|
||||
else
|
||||
surface.SetDrawColor(40, 200, 40, 230)
|
||||
surface.DrawRect(2 + w * value, 2, w * boostValue, h)
|
||||
|
||||
surface.SetDrawColor(255, 255, 255, 35)
|
||||
surface.SetMaterial(gradient)
|
||||
surface.DrawTexturedRect(2 + w * value, 2, w * boostValue, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
surface.SetDrawColor(255, 255, 255, 5)
|
||||
surface.SetMaterial(gradient2)
|
||||
surface.DrawTexturedRect(2, 2, w, h)
|
||||
end
|
||||
|
||||
self.label = self.bar:Add("DLabel")
|
||||
self.label:Dock(FILL)
|
||||
self.label:SetExpensiveShadow(1, Color(0, 0, 60))
|
||||
self.label:SetContentAlignment(5)
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (self.pressing) then
|
||||
if ((self.nextPress or 0) < CurTime()) then
|
||||
self:DoChange()
|
||||
end
|
||||
end
|
||||
|
||||
self.deltaValue = math.Approach(self.deltaValue, self.value, FrameTime() * 15)
|
||||
end
|
||||
|
||||
function PANEL:DoChange()
|
||||
if ((self.value == 0 and self.pressing == -1) or (self.value == self.max and self.pressing == 1)) then
|
||||
return
|
||||
end
|
||||
|
||||
self.nextPress = CurTime() + 0.2
|
||||
|
||||
if (self:OnChanged(self.pressing) != false) then
|
||||
self.value = math.Clamp(self.value + self.pressing, 0, self.max)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnChanged(difference)
|
||||
end
|
||||
|
||||
function PANEL:SetText(text)
|
||||
self.label:SetText(text)
|
||||
end
|
||||
|
||||
function PANEL:SetReadOnly()
|
||||
self.sub:Remove()
|
||||
self.add:Remove()
|
||||
end
|
||||
|
||||
vgui.Register("ixAttributeBar", PANEL, "DPanel")
|
||||
207
gamemodes/helix/gamemode/core/derma/cl_bar.lua
Normal file
207
gamemodes/helix/gamemode/core/derma/cl_bar.lua
Normal file
@@ -0,0 +1,207 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- bar manager
|
||||
-- this manages positions for bar panels
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(ScrW() * 0.35, ScrH())
|
||||
self:SetPos(4, 4)
|
||||
self:ParentToHUD()
|
||||
|
||||
self.bars = {}
|
||||
self.padding = 2
|
||||
|
||||
-- add bars that were registered before manager creation
|
||||
for _, v in ipairs(ix.bar.list) do
|
||||
v.panel = self:AddBar(v.index, v.color, v.priority)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetAll()
|
||||
return self.bars
|
||||
end
|
||||
|
||||
function PANEL:Clear()
|
||||
for k, v in ipairs(self.bars) do
|
||||
v:Remove()
|
||||
|
||||
table.remove(self.bars, k)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:AddBar(index, color, priority)
|
||||
local panel = self:Add("ixInfoBar")
|
||||
panel:SetSize(self:GetWide(), BAR_HEIGHT)
|
||||
panel:SetVisible(false)
|
||||
panel:SetID(index)
|
||||
panel:SetColor(color)
|
||||
panel:SetPriority(priority)
|
||||
|
||||
self.bars[#self.bars + 1] = panel
|
||||
self:Sort()
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:RemoveBar(panel)
|
||||
local toRemove
|
||||
|
||||
for k, v in ipairs(self.bars) do
|
||||
if (v == panel) then
|
||||
toRemove = k
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (toRemove) then
|
||||
table.remove(self.bars, toRemove)
|
||||
|
||||
-- Decrease index value for the next bars
|
||||
for i = toRemove, #self.bars do
|
||||
ix.bar.list[i].index = i
|
||||
self.bars[i]:SetID(i)
|
||||
end
|
||||
end
|
||||
|
||||
panel:Remove()
|
||||
self:Sort()
|
||||
end
|
||||
|
||||
-- sort bars by priority
|
||||
function PANEL:Sort()
|
||||
table.sort(self.bars, function(a, b)
|
||||
return a:GetPriority() < b:GetPriority()
|
||||
end)
|
||||
end
|
||||
|
||||
-- update target Y positions
|
||||
function PANEL:Organize()
|
||||
local currentY = 0
|
||||
|
||||
for _, v in ipairs(self.bars) do
|
||||
if (!v:IsVisible()) then
|
||||
continue
|
||||
end
|
||||
|
||||
v:SetPos(0, currentY)
|
||||
|
||||
currentY = currentY + self.padding + v:GetTall()
|
||||
end
|
||||
|
||||
self:SetSize(self:GetWide(), currentY)
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
local menu = (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu:IsClosing()) and ix.gui.characterMenu
|
||||
or IsValid(ix.gui.menu) and ix.gui.menu
|
||||
local fraction = menu and 1 - menu.currentAlpha / 255 or 1
|
||||
|
||||
self:SetAlpha(255 * fraction)
|
||||
|
||||
-- don't update bars when not visible
|
||||
if (fraction == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
local curTime = CurTime()
|
||||
local bShouldHide = hook.Run("ShouldHideBars")
|
||||
local bAlwaysShow = ix.option.Get("alwaysShowBars", false)
|
||||
|
||||
for _, v in ipairs(self.bars) do
|
||||
local info = ix.bar.list[v:GetID()]
|
||||
local realValue, barText = info.GetValue()
|
||||
|
||||
if (bShouldHide or realValue == false) then
|
||||
v:SetVisible(false)
|
||||
continue
|
||||
end
|
||||
|
||||
if (v:GetDelta() != realValue) then
|
||||
v:SetLifetime(curTime + 5)
|
||||
end
|
||||
|
||||
if (v:GetLifetime() < curTime and !info.visible and !bAlwaysShow and !hook.Run("ShouldBarDraw", info)) then
|
||||
v:SetVisible(false)
|
||||
continue
|
||||
end
|
||||
|
||||
v:SetVisible(true)
|
||||
v:SetValue(realValue)
|
||||
v:SetText(isstring(barText) and barText or "")
|
||||
end
|
||||
|
||||
self:Organize()
|
||||
end
|
||||
|
||||
function PANEL:OnRemove()
|
||||
self:Clear()
|
||||
end
|
||||
|
||||
vgui.Register("ixInfoBarManager", PANEL, "Panel")
|
||||
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "index", "ID", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "color", "Color")
|
||||
AccessorFunc(PANEL, "priority", "Priority", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "value", "Value", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "delta", "Delta", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "lifetime", "Lifetime", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self.value = 0
|
||||
self.delta = 0
|
||||
self.lifetime = 0
|
||||
|
||||
self.bar = self:Add("DPanel")
|
||||
self.bar:SetPaintedManually(true)
|
||||
self.bar:Dock(FILL)
|
||||
self.bar:DockMargin(2, 2, 2, 2)
|
||||
self.bar.Paint = function(this, width, height)
|
||||
width = width * math.min(self.delta, 1)
|
||||
|
||||
derma.SkinFunc("PaintInfoBar", self, width, height, self.color)
|
||||
end
|
||||
|
||||
self.label = self:Add("DLabel")
|
||||
self.label:SetFont("ixSmallFont")
|
||||
self.label:SetContentAlignment(5)
|
||||
self.label:SetText("")
|
||||
self.label:SetTextColor(Color(240, 240, 240))
|
||||
self.label:SetExpensiveShadow(2, Color(20, 20, 20))
|
||||
self.label:SetPaintedManually(true)
|
||||
self.label:SizeToContents()
|
||||
self.label:Dock(FILL)
|
||||
end
|
||||
|
||||
function PANEL:SetText(text)
|
||||
self.label:SetText(text)
|
||||
self.label:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
self.delta = math.Approach(self.delta, self.value, FrameTime())
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintInfoBarBackground", self, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixInfoBar", PANEL, "Panel")
|
||||
|
||||
if (IsValid(ix.gui.bars)) then
|
||||
ix.gui.bars:Remove()
|
||||
ix.gui.bars = vgui.Create("ixInfoBarManager")
|
||||
end
|
||||
513
gamemodes/helix/gamemode/core/derma/cl_business.lua
Normal file
513
gamemodes/helix/gamemode/core/derma/cl_business.lua
Normal file
@@ -0,0 +1,513 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
-- being relative.
|
||||
local size = 120
|
||||
self:SetSize(size, size * 1.4)
|
||||
end
|
||||
|
||||
function PANEL:SetItem(itemTable)
|
||||
self.itemName = L(itemTable.name):lower()
|
||||
|
||||
self.price = self:Add("DLabel")
|
||||
self.price:Dock(BOTTOM)
|
||||
self.price:SetText(itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper())
|
||||
self.price:SetContentAlignment(5)
|
||||
self.price:SetTextColor(color_white)
|
||||
self.price:SetFont("ixSmallFont")
|
||||
self.price:SetExpensiveShadow(1, Color(0, 0, 0, 200))
|
||||
|
||||
self.name = self:Add("DLabel")
|
||||
self.name:Dock(TOP)
|
||||
self.name:SetText(itemTable.GetName and itemTable:GetName() or L(itemTable.name))
|
||||
self.name:SetContentAlignment(5)
|
||||
self.name:SetTextColor(color_white)
|
||||
self.name:SetFont("ixSmallFont")
|
||||
self.name:SetExpensiveShadow(1, Color(0, 0, 0, 200))
|
||||
self.name.Paint = function(this, w, h)
|
||||
surface.SetDrawColor(0, 0, 0, 75)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end
|
||||
|
||||
self.icon = self:Add("SpawnIcon")
|
||||
self.icon:SetZPos(1)
|
||||
self.icon:SetSize(self:GetWide(), self:GetWide())
|
||||
self.icon:Dock(FILL)
|
||||
self.icon:DockMargin(5, 5, 5, 10)
|
||||
self.icon:InvalidateLayout(true)
|
||||
self.icon:SetModel(itemTable:GetModel(), itemTable:GetSkin())
|
||||
self.icon:SetHelixTooltip(function(tooltip)
|
||||
ix.hud.PopulateItemTooltip(tooltip, itemTable)
|
||||
end)
|
||||
self.icon.itemTable = itemTable
|
||||
self.icon.DoClick = function(this)
|
||||
if (IsValid(ix.gui.checkout)) then
|
||||
return
|
||||
end
|
||||
|
||||
local parent = ix.gui.business
|
||||
local bAdded = parent:BuyItem(itemTable.uniqueID)
|
||||
|
||||
if (bAdded) then
|
||||
surface.PlaySound("buttons/button14.wav")
|
||||
end
|
||||
end
|
||||
self.icon.PaintOver = function(this)
|
||||
if (itemTable and itemTable.PaintOver) then
|
||||
local w, h = this:GetSize()
|
||||
|
||||
itemTable.PaintOver(this, itemTable, w, h)
|
||||
end
|
||||
end
|
||||
|
||||
if ((itemTable.iconCam and !ICON_RENDER_QUEUE[itemTable.uniqueID]) or itemTable.forceRender) then
|
||||
local iconCam = itemTable.iconCam
|
||||
iconCam = {
|
||||
cam_pos = iconCam.pos,
|
||||
cam_fov = iconCam.fov,
|
||||
cam_ang = iconCam.ang,
|
||||
}
|
||||
ICON_RENDER_QUEUE[itemTable.uniqueID] = true
|
||||
|
||||
self.icon:RebuildSpawnIconEx(
|
||||
iconCam
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixBusinessItem", PANEL, "DPanel")
|
||||
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
ix.gui.business = self
|
||||
|
||||
self:SetSize(self:GetParent():GetSize())
|
||||
|
||||
self.categories = self:Add("DScrollPanel")
|
||||
self.categories:Dock(LEFT)
|
||||
self.categories:SetWide(260)
|
||||
self.categories.Paint = function(this, w, h)
|
||||
surface.SetDrawColor(0, 0, 0, 150)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end
|
||||
self.categories:DockPadding(5, 5, 5, 5)
|
||||
self.categories:DockMargin(0, 46, 0, 0)
|
||||
self.categoryPanels = {}
|
||||
|
||||
self.scroll = self:Add("DScrollPanel")
|
||||
self.scroll:Dock(FILL)
|
||||
|
||||
self.search = self:Add("DTextEntry")
|
||||
self.search:Dock(TOP)
|
||||
self.search:SetTall(36)
|
||||
self.search:SetFont("ixMediumFont")
|
||||
self.search:DockMargin(10 + self:GetWide() * 0.35, 0, 5, 5)
|
||||
self.search.OnTextChanged = function(this)
|
||||
local text = self.search:GetText():lower()
|
||||
|
||||
if (self.selected) then
|
||||
self:LoadItems(self.selected.category, text:find("%S") and text or nil)
|
||||
self.scroll:InvalidateLayout()
|
||||
end
|
||||
end
|
||||
self.search.PaintOver = function(this, cw, ch)
|
||||
if (self.search:GetValue() == "" and !self.search:HasFocus()) then
|
||||
ix.util.DrawText("V", 10, ch/2 - 1, color_black, 3, 1, "ixIconsSmall")
|
||||
end
|
||||
end
|
||||
|
||||
self.itemList = self.scroll:Add("DIconLayout")
|
||||
self.itemList:Dock(TOP)
|
||||
self.itemList:DockMargin(10, 1, 5, 5)
|
||||
self.itemList:SetSpaceX(10)
|
||||
self.itemList:SetSpaceY(10)
|
||||
self.itemList:SetMinimumSize(128, 400)
|
||||
|
||||
self.checkout = self:Add("DButton")
|
||||
self.checkout:Dock(BOTTOM)
|
||||
self.checkout:SetTextColor(color_white)
|
||||
self.checkout:SetTall(36)
|
||||
self.checkout:SetFont("ixMediumFont")
|
||||
self.checkout:DockMargin(10, 10, 0, 0)
|
||||
self.checkout:SetExpensiveShadow(1, Color(0, 0, 0, 150))
|
||||
self.checkout:SetText(L("checkout", 0))
|
||||
self.checkout.DoClick = function()
|
||||
if (!IsValid(ix.gui.checkout) and self:GetCartCount() > 0) then
|
||||
vgui.Create("ixBusinessCheckout"):SetCart(self.cart)
|
||||
end
|
||||
end
|
||||
|
||||
self.cart = {}
|
||||
|
||||
local dark = Color(0, 0, 0, 50)
|
||||
local first = true
|
||||
|
||||
for k, v in pairs(ix.item.list) do
|
||||
if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), k) == false) then
|
||||
continue
|
||||
end
|
||||
|
||||
if (!self.categoryPanels[L(v.category)]) then
|
||||
self.categoryPanels[L(v.category)] = v.category
|
||||
end
|
||||
end
|
||||
|
||||
for category, realName in SortedPairs(self.categoryPanels) do
|
||||
local button = self.categories:Add("DButton")
|
||||
button:SetTall(36)
|
||||
button:SetText(category)
|
||||
button:Dock(TOP)
|
||||
button:SetTextColor(color_white)
|
||||
button:DockMargin(5, 5, 5, 0)
|
||||
button:SetFont("ixMediumFont")
|
||||
button:SetExpensiveShadow(1, Color(0, 0, 0, 150))
|
||||
button.Paint = function(this, w, h)
|
||||
surface.SetDrawColor(self.selected == this and ix.config.Get("color") or dark)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, 50)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
end
|
||||
button.DoClick = function(this)
|
||||
if (self.selected != this) then
|
||||
self.selected = this
|
||||
self:LoadItems(realName)
|
||||
timer.Simple(0.01, function()
|
||||
self.scroll:InvalidateLayout()
|
||||
end)
|
||||
end
|
||||
end
|
||||
button.category = realName
|
||||
|
||||
if (first) then
|
||||
self.selected = button
|
||||
first = false
|
||||
end
|
||||
|
||||
self.categoryPanels[realName] = button
|
||||
end
|
||||
|
||||
if (self.selected) then
|
||||
self:LoadItems(self.selected.category)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetCartCount()
|
||||
local count = 0
|
||||
|
||||
for _, v in pairs(self.cart) do
|
||||
count = count + v
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
function PANEL:BuyItem(uniqueID)
|
||||
local currentCount = self.cart[uniqueID] or 0
|
||||
|
||||
if (currentCount >= 10) then
|
||||
return false
|
||||
end
|
||||
|
||||
self.cart[uniqueID] = currentCount + 1
|
||||
self.checkout:SetText(L("checkout", self:GetCartCount()))
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function PANEL:LoadItems(category, search)
|
||||
category = category or "misc"
|
||||
local items = ix.item.list
|
||||
|
||||
self.itemList:Clear()
|
||||
self.itemList:InvalidateLayout(true)
|
||||
|
||||
for uniqueID, itemTable in SortedPairsByMemberValue(items, "name") do
|
||||
if (hook.Run("CanPlayerUseBusiness", LocalPlayer(), uniqueID) == false) then
|
||||
continue
|
||||
end
|
||||
|
||||
if (itemTable.category == category) then
|
||||
if (search and search != "" and !L(itemTable.name):lower():find(search, 1, true)) then
|
||||
continue
|
||||
end
|
||||
|
||||
self.itemList:Add("ixBusinessItem"):SetItem(itemTable)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetPage()
|
||||
end
|
||||
|
||||
function PANEL:GetPageItems()
|
||||
end
|
||||
|
||||
vgui.Register("ixBusiness", PANEL, "EditablePanel")
|
||||
|
||||
DEFINE_BASECLASS("DFrame")
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.gui.checkout)) then
|
||||
ix.gui.checkout:Remove()
|
||||
end
|
||||
|
||||
ix.gui.checkout = self
|
||||
|
||||
self:SetTitle(L("checkout", 0))
|
||||
self:SetSize(ScrW() / 4 > 200 and ScrW() / 4 or ScrW() / 2, ScrH() / 2 > 300 and ScrH() / 2 or ScrH())
|
||||
self:MakePopup()
|
||||
self:Center()
|
||||
self:SetBackgroundBlur(true)
|
||||
self:SetSizable(true)
|
||||
|
||||
self.items = self:Add("DScrollPanel")
|
||||
self.items:Dock(FILL)
|
||||
self.items.Paint = function(this, w, h)
|
||||
surface.SetDrawColor(0, 0, 0, 100)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end
|
||||
self.items:DockMargin(0, 0, 0, 4)
|
||||
|
||||
self.buy = self:Add("DButton")
|
||||
self.buy:Dock(BOTTOM)
|
||||
self.buy:SetText(L"purchase")
|
||||
self.buy:SetTextColor(color_white)
|
||||
self.buy.DoClick = function(this)
|
||||
if ((this.nextClick or 0) < CurTime()) then
|
||||
this.nextClick = CurTime() + 0.5
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
if (self.preventBuy) then
|
||||
self.finalGlow:SetText(self.final:GetText())
|
||||
self.finalGlow:SetAlpha(255)
|
||||
self.finalGlow:AlphaTo(0, 0.5)
|
||||
|
||||
return surface.PlaySound("buttons/button11.wav")
|
||||
end
|
||||
|
||||
net.Start("ixBusinessBuy")
|
||||
net.WriteUInt(table.Count(self.itemData), 8)
|
||||
|
||||
for k, v in pairs(self.itemData) do
|
||||
net.WriteString(k)
|
||||
net.WriteUInt(v, 8)
|
||||
end
|
||||
|
||||
net.SendToServer()
|
||||
|
||||
self.itemData = {}
|
||||
self.items:Remove()
|
||||
self.data:Remove()
|
||||
self.buy:Remove()
|
||||
self:ShowCloseButton(false)
|
||||
|
||||
if (IsValid(ix.gui.business)) then
|
||||
ix.gui.business.cart = {}
|
||||
ix.gui.business.checkout:SetText(L("checkout", 0))
|
||||
end
|
||||
|
||||
self.text = self:Add("DLabel")
|
||||
self.text:Dock(FILL)
|
||||
self.text:SetContentAlignment(5)
|
||||
self.text:SetTextColor(color_white)
|
||||
self.text:SetText(L"purchasing")
|
||||
self.text:SetFont("ixMediumFont")
|
||||
|
||||
net.Receive("ixBusinessResponse", function()
|
||||
if (IsValid(self)) then
|
||||
self.text:SetText(L"buyGood")
|
||||
self.done = true
|
||||
self:ShowCloseButton(true)
|
||||
|
||||
surface.PlaySound("buttons/button3.wav")
|
||||
end
|
||||
end)
|
||||
|
||||
timer.Simple(4, function()
|
||||
if (IsValid(self) and !self.done) then
|
||||
self.text:SetText(L"buyFailed")
|
||||
self:ShowCloseButton(true)
|
||||
|
||||
surface.PlaySound("buttons/button11.wav")
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
self.data = self:Add("DPanel")
|
||||
self.data:Dock(BOTTOM)
|
||||
self.data:SetTall(64)
|
||||
self.data:DockMargin(0, 0, 0, 4)
|
||||
|
||||
self.current = self.data:Add("DLabel")
|
||||
self.current:SetFont("ixSmallFont")
|
||||
self.current:SetContentAlignment(6)
|
||||
self.current:SetTextColor(color_white)
|
||||
self.current:Dock(TOP)
|
||||
self.current:SetTextInset(4, 0)
|
||||
|
||||
self.total = self.data:Add("DLabel")
|
||||
self.total:SetFont("ixSmallFont")
|
||||
self.total:SetContentAlignment(6)
|
||||
self.total:SetTextColor(color_white)
|
||||
self.total:Dock(TOP)
|
||||
self.total:SetTextInset(4, 0)
|
||||
|
||||
local line = self.data:Add("DPanel")
|
||||
line:SetTall(1)
|
||||
line:DockMargin(128, 0, 4, 0)
|
||||
line:Dock(TOP)
|
||||
line.Paint = function(this, w, h)
|
||||
surface.SetDrawColor(255, 255, 255, 150)
|
||||
surface.DrawLine(0, 0, w, 0)
|
||||
end
|
||||
|
||||
self.final = self.data:Add("DLabel")
|
||||
self.final:SetFont("ixSmallFont")
|
||||
self.final:SetContentAlignment(6)
|
||||
self.final:SetTextColor(color_white)
|
||||
self.final:Dock(TOP)
|
||||
self.final:SetTextInset(4, 0)
|
||||
|
||||
self.finalGlow = self.final:Add("DLabel")
|
||||
self.finalGlow:Dock(FILL)
|
||||
self.finalGlow:SetFont("ixSmallFont")
|
||||
self.finalGlow:SetTextColor(color_white)
|
||||
self.finalGlow:SetContentAlignment(6)
|
||||
self.finalGlow:SetAlpha(0)
|
||||
self.finalGlow:SetTextInset(4, 0)
|
||||
|
||||
self:SetFocusTopLevel(true)
|
||||
self.itemData = {}
|
||||
self:OnQuantityChanged()
|
||||
end
|
||||
|
||||
function PANEL:OnQuantityChanged()
|
||||
local price = 0
|
||||
local money = LocalPlayer():GetCharacter():GetMoney()
|
||||
local valid = 0
|
||||
|
||||
for k, v in pairs(self.itemData) do
|
||||
local itemTable = ix.item.list[k]
|
||||
|
||||
if (itemTable and v > 0) then
|
||||
valid = valid + 1
|
||||
price = price + (v * (itemTable.price or 0))
|
||||
end
|
||||
end
|
||||
|
||||
self.current:SetText(L"currentMoney" .. ix.currency.Get(money))
|
||||
self.total:SetText("- " .. ix.currency.Get(price))
|
||||
self.final:SetText(L"moneyLeft" .. ix.currency.Get(money - price))
|
||||
self.final:SetTextColor((money - price) >= 0 and Color(46, 204, 113) or Color(217, 30, 24))
|
||||
|
||||
self.preventBuy = (money - price) < 0 or valid == 0
|
||||
|
||||
if (IsValid(ix.gui.business)) then
|
||||
ix.gui.business.checkout:SetText(L("checkout", ix.gui.business:GetCartCount()))
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetCart(items)
|
||||
self.itemData = items
|
||||
|
||||
for k, v in SortedPairs(items) do
|
||||
local itemTable = ix.item.list[k]
|
||||
|
||||
if (itemTable) then
|
||||
local slot = self.items:Add("DPanel")
|
||||
slot:SetTall(36)
|
||||
slot:Dock(TOP)
|
||||
slot:DockMargin(5, 5, 5, 0)
|
||||
|
||||
slot.icon = slot:Add("SpawnIcon")
|
||||
slot.icon:SetPos(2, 2)
|
||||
slot.icon:SetSize(32, 32)
|
||||
slot.icon:SetModel(itemTable:GetModel())
|
||||
slot.icon:SetTooltip()
|
||||
|
||||
slot.name = slot:Add("DLabel")
|
||||
slot.name:SetPos(40, 2)
|
||||
slot.name:SetFont("ixChatFont")
|
||||
slot.name:SetText(string.format(
|
||||
"%s (%s)",
|
||||
L(itemTable.GetName and itemTable:GetName() or L(itemTable.name)),
|
||||
itemTable.price and ix.currency.Get(itemTable.price) or L"free":utf8upper()
|
||||
))
|
||||
slot.name:SetTextColor(color_white)
|
||||
slot.name:SizeToContents()
|
||||
slot.name:DockMargin(40, 0, 0, 0)
|
||||
slot.name:Dock(FILL)
|
||||
|
||||
slot.quantity = slot:Add("DTextEntry")
|
||||
slot.quantity:SetSize(32, 32)
|
||||
slot.quantity:Dock(RIGHT)
|
||||
slot.quantity:DockMargin(4, 4, 4, 4)
|
||||
slot.quantity:SetContentAlignment(5)
|
||||
slot.quantity:SetNumeric(true)
|
||||
slot.quantity:SetText(v)
|
||||
slot.quantity:SetFont("ixChatFont")
|
||||
slot.quantity.OnTextChanged = function(this)
|
||||
local value = tonumber(this:GetValue())
|
||||
|
||||
if (!value) then
|
||||
this:SetValue(1)
|
||||
return
|
||||
end
|
||||
|
||||
value = math.Clamp(math.Round(value), 0, 10)
|
||||
|
||||
if (value == 0) then
|
||||
items[k] = nil
|
||||
|
||||
slot:Remove()
|
||||
else
|
||||
items[k] = value
|
||||
end
|
||||
|
||||
self:OnQuantityChanged()
|
||||
end
|
||||
slot.quantity.OnLoseFocus = function(this)
|
||||
local value = math.Clamp(tonumber(this:GetValue()) or 1, 0, 10)
|
||||
this:SetText(value)
|
||||
end
|
||||
else
|
||||
items[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
self:OnQuantityChanged()
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (!self:HasFocus()) then
|
||||
self:MakePopup()
|
||||
end
|
||||
|
||||
BaseClass.Think(self)
|
||||
end
|
||||
|
||||
vgui.Register("ixBusinessCheckout", PANEL, "DFrame")
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixBusiness", function(tabs)
|
||||
if (hook.Run("BuildBusinessMenu") != false) then
|
||||
tabs["business"] = function(container)
|
||||
container:Add("ixBusiness")
|
||||
end
|
||||
end
|
||||
end)
|
||||
551
gamemodes/helix/gamemode/core/derma/cl_character.lua
Normal file
551
gamemodes/helix/gamemode/core/derma/cl_character.lua
Normal file
@@ -0,0 +1,551 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local gradient = surface.GetTextureID("vgui/gradient-d")
|
||||
local audioFadeInTime = 2
|
||||
local animationTime = 0.5
|
||||
local matrixZScale = Vector(1, 1, 0.0001)
|
||||
|
||||
-- character menu panel
|
||||
DEFINE_BASECLASS("ixSubpanelParent")
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(self:GetParent():GetSize())
|
||||
self:SetPos(0, 0)
|
||||
|
||||
self.childPanels = {}
|
||||
self.subpanels = {}
|
||||
self.activeSubpanel = ""
|
||||
|
||||
self.currentDimAmount = 0
|
||||
self.currentY = 0
|
||||
self.currentScale = 1
|
||||
self.currentAlpha = 255
|
||||
self.targetDimAmount = 255
|
||||
self.targetScale = 0.9
|
||||
end
|
||||
|
||||
function PANEL:Dim(length, callback)
|
||||
length = length or animationTime
|
||||
self.currentDimAmount = 0
|
||||
|
||||
self:CreateAnimation(length, {
|
||||
target = {
|
||||
currentDimAmount = self.targetDimAmount,
|
||||
currentScale = self.targetScale
|
||||
},
|
||||
easing = "outCubic",
|
||||
OnComplete = callback
|
||||
})
|
||||
|
||||
self:OnDim()
|
||||
end
|
||||
|
||||
function PANEL:Undim(length, callback)
|
||||
length = length or animationTime
|
||||
self.currentDimAmount = self.targetDimAmount
|
||||
|
||||
self:CreateAnimation(length, {
|
||||
target = {
|
||||
currentDimAmount = 0,
|
||||
currentScale = 1
|
||||
},
|
||||
easing = "outCubic",
|
||||
OnComplete = callback
|
||||
})
|
||||
|
||||
self:OnUndim()
|
||||
end
|
||||
|
||||
function PANEL:OnDim()
|
||||
end
|
||||
|
||||
function PANEL:OnUndim()
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
local amount = self.currentDimAmount
|
||||
local bShouldScale = self.currentScale != 1
|
||||
local matrix
|
||||
|
||||
-- draw child panels with scaling if needed
|
||||
if (bShouldScale) then
|
||||
matrix = Matrix()
|
||||
matrix:Scale(matrixZScale * self.currentScale)
|
||||
matrix:Translate(Vector(
|
||||
ScrW() * 0.5 - (ScrW() * self.currentScale * 0.5),
|
||||
ScrH() * 0.5 - (ScrH() * self.currentScale * 0.5),
|
||||
1
|
||||
))
|
||||
|
||||
cam.PushModelMatrix(matrix)
|
||||
self.currentMatrix = matrix
|
||||
end
|
||||
|
||||
BaseClass.Paint(self, width, height)
|
||||
|
||||
if (bShouldScale) then
|
||||
cam.PopModelMatrix()
|
||||
self.currentMatrix = nil
|
||||
end
|
||||
|
||||
if (amount > 0) then
|
||||
local color = Color(0, 0, 0, amount)
|
||||
|
||||
surface.SetDrawColor(color)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixCharMenuPanel", PANEL, "ixSubpanelParent")
|
||||
|
||||
-- character menu main button list
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
local parent = self:GetParent()
|
||||
self:SetSize(parent:GetWide() * 0.25, parent:GetTall())
|
||||
|
||||
self:GetVBar():SetWide(0)
|
||||
self:GetVBar():SetVisible(false)
|
||||
end
|
||||
|
||||
function PANEL:Add(name)
|
||||
local panel = vgui.Create(name, self)
|
||||
panel:Dock(TOP)
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
self:GetCanvas():InvalidateLayout(true)
|
||||
|
||||
-- if the canvas has extra space, forcefully dock to the bottom so it doesn't anchor to the top
|
||||
if (self:GetTall() > self:GetCanvas():GetTall()) then
|
||||
self:GetCanvas():Dock(BOTTOM)
|
||||
else
|
||||
self:GetCanvas():Dock(NODOCK)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixCharMenuButtonList", PANEL, "DScrollPanel")
|
||||
|
||||
-- main character menu panel
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "bUsingCharacter", "UsingCharacter", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
local parent = self:GetParent()
|
||||
local padding = self:GetPadding()
|
||||
local halfWidth = ScrW() * 0.5
|
||||
local halfPadding = padding * 0.5
|
||||
local bHasCharacter = #ix.characters > 0
|
||||
|
||||
self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
|
||||
self:DockPadding(padding, padding, padding, padding)
|
||||
|
||||
local infoLabel = self:Add("DLabel")
|
||||
infoLabel:SetTextColor(Color(255, 255, 255, 25))
|
||||
infoLabel:SetFont("ixMenuMiniFont")
|
||||
infoLabel:SetText(L("helix") .. " " .. GAMEMODE.Version)
|
||||
infoLabel:SizeToContents()
|
||||
infoLabel:SetPos(ScrW() - infoLabel:GetWide() - 4, ScrH() - infoLabel:GetTall() - 4)
|
||||
|
||||
local logoPanel = self:Add("Panel")
|
||||
logoPanel:SetSize(ScrW(), ScrH() * 0.25)
|
||||
logoPanel:SetPos(0, ScrH() * 0.25)
|
||||
logoPanel.Paint = function(panel, width, height)
|
||||
local matrix = self.currentMatrix
|
||||
|
||||
-- don't scale the background because it fucks the blur
|
||||
if (matrix) then
|
||||
cam.PopModelMatrix()
|
||||
end
|
||||
|
||||
local newHeight = Lerp(1 - (self.currentDimAmount / 255), 0, height)
|
||||
local y = height * 0.5 - newHeight * 0.5
|
||||
local _, screenY = panel:LocalToScreen(0, 0)
|
||||
screenY = screenY + y
|
||||
|
||||
render.SetScissorRect(0, screenY, width, screenY + newHeight, true)
|
||||
ix.util.DrawBlur(panel, 15, nil, 200)
|
||||
|
||||
-- background dim
|
||||
surface.SetDrawColor(0, 0, 0, 100)
|
||||
surface.DrawRect(0, y, width, newHeight)
|
||||
|
||||
-- border lines
|
||||
surface.SetDrawColor(ix.config.Get("color") or color_white)
|
||||
surface.DrawRect(0, y, width, 1)
|
||||
surface.DrawRect(0, y + newHeight - 1, width, 1)
|
||||
|
||||
if (matrix) then
|
||||
cam.PushModelMatrix(matrix)
|
||||
end
|
||||
|
||||
for _, v in ipairs(panel:GetChildren()) do
|
||||
v:PaintManual()
|
||||
end
|
||||
|
||||
render.SetScissorRect(0, 0, 0, 0, false)
|
||||
end
|
||||
|
||||
-- draw schema logo material instead of text if available
|
||||
local logo = Schema.logo and ix.util.GetMaterial(Schema.logo)
|
||||
|
||||
if (logo and !logo:IsError()) then
|
||||
local logoImage = logoPanel:Add("DImage")
|
||||
logoImage:SetMaterial(logo)
|
||||
logoImage:SetSize(halfWidth, halfWidth * logo:Height() / logo:Width())
|
||||
logoImage:SetPos(halfWidth - logoImage:GetWide() * 0.5, halfPadding)
|
||||
logoImage:SetPaintedManually(true)
|
||||
|
||||
logoPanel:SetTall(logoImage:GetTall() + padding)
|
||||
else
|
||||
local newHeight = padding
|
||||
local subtitle = L2("schemaDesc") or Schema.description
|
||||
|
||||
local titleLabel = logoPanel:Add("DLabel")
|
||||
titleLabel:SetTextColor(color_white)
|
||||
titleLabel:SetFont("ixTitleFont")
|
||||
titleLabel:SetText(L2("schemaName") or Schema.name or L"unknown")
|
||||
titleLabel:SizeToContents()
|
||||
titleLabel:SetPos(halfWidth - titleLabel:GetWide() * 0.5, halfPadding)
|
||||
titleLabel:SetPaintedManually(true)
|
||||
newHeight = newHeight + titleLabel:GetTall()
|
||||
|
||||
if (subtitle) then
|
||||
local subtitleLabel = logoPanel:Add("DLabel")
|
||||
subtitleLabel:SetTextColor(color_white)
|
||||
subtitleLabel:SetFont("ixSubTitleFont")
|
||||
subtitleLabel:SetText(subtitle)
|
||||
subtitleLabel:SizeToContents()
|
||||
subtitleLabel:SetPos(halfWidth - subtitleLabel:GetWide() * 0.5, 0)
|
||||
subtitleLabel:MoveBelow(titleLabel)
|
||||
subtitleLabel:SetPaintedManually(true)
|
||||
newHeight = newHeight + subtitleLabel:GetTall()
|
||||
end
|
||||
|
||||
logoPanel:SetTall(newHeight)
|
||||
end
|
||||
|
||||
-- button list
|
||||
self.mainButtonList = self:Add("ixCharMenuButtonList")
|
||||
self.mainButtonList:Dock(LEFT)
|
||||
|
||||
-- create character button
|
||||
local createButton = self.mainButtonList:Add("ixMenuButton")
|
||||
createButton:SetText("create")
|
||||
createButton:SizeToContents()
|
||||
createButton.DoClick = function()
|
||||
local maximum = hook.Run("GetMaxPlayerCharacter", LocalPlayer()) or ix.config.Get("maxCharacters", 5)
|
||||
-- don't allow creation if we've hit the character limit
|
||||
if (#ix.characters >= maximum) then
|
||||
self:GetParent():ShowNotice(3, L("maxCharacters"))
|
||||
return
|
||||
end
|
||||
|
||||
self:Dim()
|
||||
parent.newCharacterPanel:SetActiveSubpanel("faction", 0)
|
||||
parent.newCharacterPanel:SlideUp()
|
||||
end
|
||||
|
||||
-- load character button
|
||||
self.loadButton = self.mainButtonList:Add("ixMenuButton")
|
||||
self.loadButton:SetText("load")
|
||||
self.loadButton:SizeToContents()
|
||||
self.loadButton.DoClick = function()
|
||||
self:Dim()
|
||||
parent.loadCharacterPanel:SlideUp()
|
||||
end
|
||||
|
||||
if (!bHasCharacter) then
|
||||
self.loadButton:SetDisabled(true)
|
||||
end
|
||||
|
||||
-- community button
|
||||
local extraURL = ix.config.Get("communityURL", "")
|
||||
local extraText = ix.config.Get("communityText", "@community")
|
||||
|
||||
if (extraURL != "" and extraText != "") then
|
||||
if (extraText:sub(1, 1) == "@") then
|
||||
extraText = L(extraText:sub(2))
|
||||
end
|
||||
|
||||
local extraButton = self.mainButtonList:Add("ixMenuButton")
|
||||
extraButton:SetText(extraText, true)
|
||||
extraButton:SizeToContents()
|
||||
extraButton.DoClick = function()
|
||||
gui.OpenURL(extraURL)
|
||||
end
|
||||
end
|
||||
|
||||
-- leave/return button
|
||||
self.returnButton = self.mainButtonList:Add("ixMenuButton")
|
||||
self:UpdateReturnButton()
|
||||
self.returnButton.DoClick = function()
|
||||
if (self.bUsingCharacter) then
|
||||
parent:Close()
|
||||
else
|
||||
RunConsoleCommand("disconnect")
|
||||
end
|
||||
end
|
||||
|
||||
self.mainButtonList:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:UpdateReturnButton(bValue)
|
||||
if (bValue == nil) then
|
||||
bValue = self.bUsingCharacter
|
||||
end
|
||||
|
||||
self.returnButton:SetText(bValue and "return" or "leave")
|
||||
self.returnButton:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:OnDim()
|
||||
-- disable input on this panel since it will still be in the background while invisible - prone to stray clicks if the
|
||||
-- panels overtop slide out of the way
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
end
|
||||
|
||||
function PANEL:OnUndim()
|
||||
self:SetMouseInputEnabled(true)
|
||||
self:SetKeyboardInputEnabled(true)
|
||||
|
||||
-- we may have just deleted a character so update the status of the return button
|
||||
self.bUsingCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
|
||||
self:UpdateReturnButton()
|
||||
end
|
||||
|
||||
function PANEL:OnClose()
|
||||
for _, v in pairs(self:GetChildren()) do
|
||||
if (IsValid(v)) then
|
||||
v:SetVisible(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
local padding = self:GetPadding()
|
||||
|
||||
self.mainButtonList:SetPos(padding, height - self.mainButtonList:GetTall() - padding)
|
||||
end
|
||||
|
||||
vgui.Register("ixCharMenuMain", PANEL, "ixCharMenuPanel")
|
||||
|
||||
-- container panel
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.gui.loading)) then
|
||||
ix.gui.loading:Remove()
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.characterMenu)) then
|
||||
if (IsValid(ix.gui.characterMenu.channel)) then
|
||||
ix.gui.characterMenu.channel:Stop()
|
||||
end
|
||||
|
||||
ix.gui.characterMenu:Remove()
|
||||
end
|
||||
|
||||
self:SetSize(ScrW(), ScrH())
|
||||
self:SetPos(0, 0)
|
||||
|
||||
-- main menu panel
|
||||
self.mainPanel = self:Add("ixCharMenuMain")
|
||||
|
||||
-- new character panel
|
||||
self.newCharacterPanel = self:Add("ixCharMenuNew")
|
||||
self.newCharacterPanel:SlideDown(0)
|
||||
|
||||
-- load character panel
|
||||
self.loadCharacterPanel = self:Add("ixCharMenuLoad")
|
||||
self.loadCharacterPanel:SlideDown(0)
|
||||
|
||||
-- notice bar
|
||||
self.notice = self:Add("ixNoticeBar")
|
||||
|
||||
-- finalization
|
||||
self:MakePopup()
|
||||
self.currentAlpha = 255
|
||||
self.volume = 0
|
||||
|
||||
ix.gui.characterMenu = self
|
||||
|
||||
if (!IsValid(ix.gui.intro)) then
|
||||
self:PlayMusic()
|
||||
end
|
||||
|
||||
hook.Run("OnCharacterMenuCreated", self)
|
||||
end
|
||||
|
||||
function PANEL:PlayMusic()
|
||||
local path = "sound/" .. ix.config.Get("music")
|
||||
local url = path:match("http[s]?://.+")
|
||||
local play = url and sound.PlayURL or sound.PlayFile
|
||||
path = url and url or path
|
||||
|
||||
play(path, "noplay", function(channel, error, message)
|
||||
if (!IsValid(channel)) then
|
||||
return
|
||||
end
|
||||
|
||||
channel:SetVolume(self.volume or 0)
|
||||
channel:Play()
|
||||
|
||||
self.channel = channel
|
||||
|
||||
self:CreateAnimation(audioFadeInTime, {
|
||||
index = 10,
|
||||
target = {volume = 1},
|
||||
|
||||
Think = function(animation, panel)
|
||||
if (IsValid(panel.channel)) then
|
||||
panel.channel:SetVolume(self.volume * 0.5)
|
||||
end
|
||||
end
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
function PANEL:ShowNotice(type, text)
|
||||
self.notice:SetType(type)
|
||||
self.notice:SetText(text)
|
||||
self.notice:Show()
|
||||
end
|
||||
|
||||
function PANEL:HideNotice()
|
||||
if (IsValid(self.notice) and !self.notice:GetHidden()) then
|
||||
self.notice:Slide("up", 0.5, true)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnCharacterDeleted(character)
|
||||
if (#ix.characters == 0) then
|
||||
self.mainPanel.loadButton:SetDisabled(true)
|
||||
self.mainPanel:Undim() -- undim since the load panel will slide down
|
||||
else
|
||||
self.mainPanel.loadButton:SetDisabled(false)
|
||||
end
|
||||
|
||||
self.loadCharacterPanel:OnCharacterDeleted(character)
|
||||
end
|
||||
|
||||
function PANEL:OnCharacterLoadFailed(error)
|
||||
self.loadCharacterPanel:SetMouseInputEnabled(true)
|
||||
self.loadCharacterPanel:SlideUp()
|
||||
self:ShowNotice(3, error)
|
||||
end
|
||||
|
||||
function PANEL:IsClosing()
|
||||
return self.bClosing
|
||||
end
|
||||
|
||||
function PANEL:Close(bFromMenu)
|
||||
self.bClosing = true
|
||||
self.bFromMenu = bFromMenu
|
||||
|
||||
local fadeOutTime = animationTime * 8
|
||||
|
||||
self:CreateAnimation(fadeOutTime, {
|
||||
index = 1,
|
||||
target = {currentAlpha = 0},
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.currentAlpha)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel:Remove()
|
||||
end
|
||||
})
|
||||
|
||||
self:CreateAnimation(fadeOutTime - 0.1, {
|
||||
index = 10,
|
||||
target = {volume = 0},
|
||||
|
||||
Think = function(animation, panel)
|
||||
if (IsValid(panel.channel)) then
|
||||
panel.channel:SetVolume(self.volume * 0.5)
|
||||
end
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
if (IsValid(panel.channel)) then
|
||||
panel.channel:Stop()
|
||||
panel.channel = nil
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- hide children if we're already dimmed
|
||||
if (bFromMenu) then
|
||||
for _, v in pairs(self:GetChildren()) do
|
||||
if (IsValid(v)) then
|
||||
v:SetVisible(false)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- fade out the main panel quicker because it significantly blocks the screen
|
||||
self.mainPanel.currentAlpha = 255
|
||||
|
||||
self.mainPanel:CreateAnimation(animationTime * 2, {
|
||||
target = {currentAlpha = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.currentAlpha)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel:SetVisible(false)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
-- relinquish mouse control
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
gui.EnableScreenClicker(false)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetTexture(gradient)
|
||||
surface.SetDrawColor(0, 0, 0, 255)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
|
||||
if (!ix.option.Get("cheapBlur", false)) then
|
||||
surface.SetDrawColor(0, 0, 0, 150)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
ix.util.DrawBlur(self, Lerp((self.currentAlpha - 200) / 255, 0, 10))
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PaintOver(width, height)
|
||||
if (self.bClosing and self.bFromMenu) then
|
||||
surface.SetDrawColor(color_black)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixCharMenu", PANEL, "EditablePanel")
|
||||
|
||||
if (IsValid(ix.gui.characterMenu)) then
|
||||
ix.gui.characterMenu:Remove()
|
||||
|
||||
--TODO: REMOVE ME
|
||||
ix.gui.characterMenu = vgui.Create("ixCharMenu")
|
||||
end
|
||||
509
gamemodes/helix/gamemode/core/derma/cl_charcreate.lua
Normal file
509
gamemodes/helix/gamemode/core/derma/cl_charcreate.lua
Normal file
@@ -0,0 +1,509 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local padding = ScreenScale(32)
|
||||
|
||||
-- create character panel
|
||||
DEFINE_BASECLASS("ixCharMenuPanel")
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
local parent = self:GetParent()
|
||||
local halfWidth = parent:GetWide() * 0.5 - (padding * 2)
|
||||
local halfHeight = parent:GetTall() * 0.5 - (padding * 2)
|
||||
local modelFOV = (ScrW() > ScrH() * 1.8) and 100 or 78
|
||||
|
||||
self:ResetPayload(true)
|
||||
|
||||
self.factionButtons = {}
|
||||
self.repopulatePanels = {}
|
||||
|
||||
-- faction selection subpanel
|
||||
self.factionPanel = self:AddSubpanel("faction", true)
|
||||
self.factionPanel:SetTitle("chooseFaction")
|
||||
self.factionPanel.OnSetActive = function()
|
||||
-- if we only have one faction, we are always selecting that one so we can skip to the description section
|
||||
if (#self.factionButtons == 1) then
|
||||
self:SetActiveSubpanel("description", 0)
|
||||
end
|
||||
end
|
||||
|
||||
local modelList = self.factionPanel:Add("Panel")
|
||||
modelList:Dock(RIGHT)
|
||||
modelList:SetSize(halfWidth + padding * 2, halfHeight)
|
||||
|
||||
local proceed = modelList:Add("ixMenuButton")
|
||||
proceed:SetText("proceed")
|
||||
proceed:SetContentAlignment(6)
|
||||
proceed:Dock(BOTTOM)
|
||||
proceed:SizeToContents()
|
||||
proceed.DoClick = function()
|
||||
self.progress:IncrementProgress()
|
||||
|
||||
self:Populate()
|
||||
self:SetActiveSubpanel("description")
|
||||
end
|
||||
|
||||
self.factionModel = modelList:Add("ixModelPanel")
|
||||
self.factionModel:Dock(FILL)
|
||||
self.factionModel:SetModel("models/error.mdl")
|
||||
self.factionModel:SetFOV(modelFOV)
|
||||
self.factionModel.PaintModel = self.factionModel.Paint
|
||||
|
||||
self.factionButtonsPanel = self.factionPanel:Add("ixCharMenuButtonList")
|
||||
self.factionButtonsPanel:SetWide(halfWidth)
|
||||
self.factionButtonsPanel:Dock(FILL)
|
||||
|
||||
local factionBack = self.factionPanel:Add("ixMenuButton")
|
||||
factionBack:SetText("return")
|
||||
factionBack:SizeToContents()
|
||||
factionBack:Dock(BOTTOM)
|
||||
factionBack.DoClick = function()
|
||||
self.progress:DecrementProgress()
|
||||
|
||||
self:SetActiveSubpanel("faction", 0)
|
||||
self:SlideDown()
|
||||
|
||||
parent.mainPanel:Undim()
|
||||
end
|
||||
|
||||
-- character customization subpanel
|
||||
self.description = self:AddSubpanel("description")
|
||||
self.description:SetTitle("chooseDescription")
|
||||
|
||||
local descriptionModelList = self.description:Add("Panel")
|
||||
descriptionModelList:Dock(LEFT)
|
||||
descriptionModelList:SetSize(halfWidth, halfHeight)
|
||||
|
||||
local descriptionBack = descriptionModelList:Add("ixMenuButton")
|
||||
descriptionBack:SetText("return")
|
||||
descriptionBack:SetContentAlignment(4)
|
||||
descriptionBack:SizeToContents()
|
||||
descriptionBack:Dock(BOTTOM)
|
||||
descriptionBack.DoClick = function()
|
||||
self.progress:DecrementProgress()
|
||||
|
||||
if (#self.factionButtons == 1) then
|
||||
factionBack:DoClick()
|
||||
else
|
||||
self:SetActiveSubpanel("faction")
|
||||
end
|
||||
end
|
||||
|
||||
self.descriptionModel = descriptionModelList:Add("ixModelPanel")
|
||||
self.descriptionModel:Dock(FILL)
|
||||
self.descriptionModel:SetModel(self.factionModel:GetModel())
|
||||
self.descriptionModel:SetFOV(modelFOV - 13)
|
||||
self.descriptionModel.PaintModel = self.descriptionModel.Paint
|
||||
|
||||
self.descriptionPanel = self.description:Add("Panel")
|
||||
self.descriptionPanel:SetWide(halfWidth + padding * 2)
|
||||
self.descriptionPanel:Dock(RIGHT)
|
||||
|
||||
local descriptionProceed = self.descriptionPanel:Add("ixMenuButton")
|
||||
descriptionProceed:SetText("proceed")
|
||||
descriptionProceed:SetContentAlignment(6)
|
||||
descriptionProceed:SizeToContents()
|
||||
descriptionProceed:Dock(BOTTOM)
|
||||
descriptionProceed.DoClick = function()
|
||||
if (self:VerifyProgression("description")) then
|
||||
-- there are no panels on the attributes section other than the create button, so we can just create the character
|
||||
if (#self.attributesPanel:GetChildren() < 2) then
|
||||
self:SendPayload()
|
||||
return
|
||||
end
|
||||
|
||||
self.progress:IncrementProgress()
|
||||
self:SetActiveSubpanel("attributes")
|
||||
end
|
||||
end
|
||||
|
||||
-- attributes subpanel
|
||||
self.attributes = self:AddSubpanel("attributes")
|
||||
self.attributes:SetTitle("chooseSkills")
|
||||
|
||||
local attributesModelList = self.attributes:Add("Panel")
|
||||
attributesModelList:Dock(LEFT)
|
||||
attributesModelList:SetSize(halfWidth, halfHeight)
|
||||
|
||||
local attributesBack = attributesModelList:Add("ixMenuButton")
|
||||
attributesBack:SetText("return")
|
||||
attributesBack:SetContentAlignment(4)
|
||||
attributesBack:SizeToContents()
|
||||
attributesBack:Dock(BOTTOM)
|
||||
attributesBack.DoClick = function()
|
||||
self.progress:DecrementProgress()
|
||||
self:SetActiveSubpanel("description")
|
||||
end
|
||||
|
||||
self.attributesModel = attributesModelList:Add("ixModelPanel")
|
||||
self.attributesModel:Dock(FILL)
|
||||
self.attributesModel:SetModel(self.factionModel:GetModel())
|
||||
self.attributesModel:SetFOV(modelFOV - 13)
|
||||
self.attributesModel.PaintModel = self.attributesModel.Paint
|
||||
|
||||
self.attributesPanel = self.attributes:Add("Panel")
|
||||
self.attributesPanel:SetWide(halfWidth + padding * 2)
|
||||
self.attributesPanel:Dock(RIGHT)
|
||||
|
||||
local create = self.attributesPanel:Add("ixMenuButton")
|
||||
create:SetText("finish")
|
||||
create:SetContentAlignment(6)
|
||||
create:SizeToContents()
|
||||
create:Dock(BOTTOM)
|
||||
create.DoClick = function()
|
||||
self:SendPayload()
|
||||
end
|
||||
|
||||
-- creation progress panel
|
||||
self.progress = self:Add("ixSegmentedProgress")
|
||||
self.progress:SetBarColor(ix.config.Get("color"))
|
||||
self.progress:SetSize(parent:GetWide(), 0)
|
||||
self.progress:SizeToContents()
|
||||
self.progress:SetPos(0, parent:GetTall() - self.progress:GetTall())
|
||||
|
||||
-- setup payload hooks
|
||||
self:AddPayloadHook("model", function(value)
|
||||
local faction = ix.faction.indices[self.payload.faction]
|
||||
|
||||
if (faction) then
|
||||
local model = faction:GetModels(LocalPlayer())[value]
|
||||
|
||||
-- assuming bodygroups
|
||||
if (istable(model)) then
|
||||
self.factionModel:SetModel(model[1], model[2] or 0, model[3])
|
||||
self.descriptionModel:SetModel(model[1], model[2] or 0, model[3])
|
||||
self.attributesModel:SetModel(model[1], model[2] or 0, model[3])
|
||||
else
|
||||
self.factionModel:SetModel(model)
|
||||
self.descriptionModel:SetModel(model)
|
||||
self.attributesModel:SetModel(model)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- setup character creation hooks
|
||||
net.Receive("ixCharacterAuthed", function()
|
||||
timer.Remove("ixCharacterCreateTimeout")
|
||||
self.awaitingResponse = false
|
||||
|
||||
local id = net.ReadUInt(32)
|
||||
local indices = net.ReadUInt(6)
|
||||
local charList = {}
|
||||
|
||||
for _ = 1, indices do
|
||||
charList[#charList + 1] = net.ReadUInt(32)
|
||||
end
|
||||
|
||||
ix.characters = charList
|
||||
|
||||
self:SlideDown()
|
||||
|
||||
if (!IsValid(self) or !IsValid(parent)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (LocalPlayer():GetCharacter()) then
|
||||
parent.mainPanel:Undim()
|
||||
parent:ShowNotice(2, L("charCreated"))
|
||||
elseif (id) then
|
||||
self.bMenuShouldClose = true
|
||||
|
||||
net.Start("ixCharacterChoose")
|
||||
net.WriteUInt(id, 32)
|
||||
net.SendToServer()
|
||||
else
|
||||
self:SlideDown()
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixCharacterAuthFailed", function()
|
||||
timer.Remove("ixCharacterCreateTimeout")
|
||||
self.awaitingResponse = false
|
||||
|
||||
local fault = net.ReadString()
|
||||
local args = net.ReadTable()
|
||||
|
||||
self:SlideDown()
|
||||
|
||||
parent.mainPanel:Undim()
|
||||
parent:ShowNotice(3, L(fault, unpack(args)))
|
||||
end)
|
||||
end
|
||||
|
||||
function PANEL:SendPayload()
|
||||
if (self.awaitingResponse or !self:VerifyProgression()) then
|
||||
return
|
||||
end
|
||||
|
||||
self.awaitingResponse = true
|
||||
|
||||
timer.Create("ixCharacterCreateTimeout", 10, 1, function()
|
||||
if (IsValid(self) and self.awaitingResponse) then
|
||||
local parent = self:GetParent()
|
||||
|
||||
self.awaitingResponse = false
|
||||
self:SlideDown()
|
||||
|
||||
parent.mainPanel:Undim()
|
||||
parent:ShowNotice(3, L("unknownError"))
|
||||
end
|
||||
end)
|
||||
|
||||
self.payload:Prepare()
|
||||
|
||||
net.Start("ixCharacterCreate")
|
||||
net.WriteUInt(table.Count(self.payload), 8)
|
||||
|
||||
for k, v in pairs(self.payload) do
|
||||
net.WriteString(k)
|
||||
net.WriteType(v)
|
||||
end
|
||||
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function PANEL:OnSlideUp()
|
||||
self:ResetPayload()
|
||||
self:Populate()
|
||||
self.progress:SetProgress(1)
|
||||
|
||||
-- the faction subpanel will skip to next subpanel if there is only one faction to choose from,
|
||||
-- so we don't have to worry about it here
|
||||
self:SetActiveSubpanel("faction", 0)
|
||||
end
|
||||
|
||||
function PANEL:OnSlideDown()
|
||||
end
|
||||
|
||||
function PANEL:ResetPayload(bWithHooks)
|
||||
if (bWithHooks) then
|
||||
self.hooks = {}
|
||||
end
|
||||
|
||||
self.payload = {}
|
||||
|
||||
-- TODO: eh..
|
||||
function self.payload.Set(payload, key, value)
|
||||
self:SetPayload(key, value)
|
||||
end
|
||||
|
||||
function self.payload.AddHook(payload, key, callback)
|
||||
self:AddPayloadHook(key, callback)
|
||||
end
|
||||
|
||||
function self.payload.Prepare(payload)
|
||||
self.payload.Set = nil
|
||||
self.payload.AddHook = nil
|
||||
self.payload.Prepare = nil
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetPayload(key, value)
|
||||
self.payload[key] = value
|
||||
self:RunPayloadHook(key, value)
|
||||
end
|
||||
|
||||
function PANEL:AddPayloadHook(key, callback)
|
||||
if (!self.hooks[key]) then
|
||||
self.hooks[key] = {}
|
||||
end
|
||||
|
||||
self.hooks[key][#self.hooks[key] + 1] = callback
|
||||
end
|
||||
|
||||
function PANEL:RunPayloadHook(key, value)
|
||||
local hooks = self.hooks[key] or {}
|
||||
|
||||
for _, v in ipairs(hooks) do
|
||||
v(value)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetContainerPanel(name)
|
||||
-- TODO: yuck
|
||||
if (name == "description") then
|
||||
return self.descriptionPanel
|
||||
elseif (name == "attributes") then
|
||||
return self.attributesPanel
|
||||
end
|
||||
|
||||
return self.descriptionPanel
|
||||
end
|
||||
|
||||
function PANEL:AttachCleanup(panel)
|
||||
self.repopulatePanels[#self.repopulatePanels + 1] = panel
|
||||
end
|
||||
|
||||
function PANEL:Populate()
|
||||
if (!self.bInitialPopulate) then
|
||||
-- setup buttons for the faction panel
|
||||
-- TODO: make this a bit less janky
|
||||
local lastSelected
|
||||
|
||||
for _, v in pairs(self.factionButtons) do
|
||||
if (v:GetSelected()) then
|
||||
lastSelected = v.faction
|
||||
end
|
||||
|
||||
if (IsValid(v)) then
|
||||
v:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
self.factionButtons = {}
|
||||
|
||||
for _, v in SortedPairs(ix.faction.teams) do
|
||||
if (ix.faction.HasWhitelist(v.index)) then
|
||||
local button = self.factionButtonsPanel:Add("ixMenuSelectionButton")
|
||||
button:SetBackgroundColor(v.color or color_white)
|
||||
button:SetText(L(v.name):utf8upper())
|
||||
button:SizeToContents()
|
||||
button:SetButtonList(self.factionButtons)
|
||||
button.faction = v.index
|
||||
button.OnSelected = function(panel)
|
||||
local faction = ix.faction.indices[panel.faction]
|
||||
local models = faction:GetModels(LocalPlayer())
|
||||
|
||||
self.payload:Set("faction", panel.faction)
|
||||
self.payload:Set("model", math.random(1, #models))
|
||||
end
|
||||
|
||||
if ((lastSelected and lastSelected == v.index) or (!lastSelected and v.isDefault)) then
|
||||
button:SetSelected(true)
|
||||
lastSelected = v.index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- remove panels created for character vars
|
||||
for i = 1, #self.repopulatePanels do
|
||||
self.repopulatePanels[i]:Remove()
|
||||
end
|
||||
|
||||
self.repopulatePanels = {}
|
||||
|
||||
-- payload is empty because we attempted to send it - for whatever reason we're back here again so we need to repopulate
|
||||
if (!self.payload.faction) then
|
||||
for _, v in pairs(self.factionButtons) do
|
||||
if (v:GetSelected()) then
|
||||
v:SetSelected(true)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.factionButtonsPanel:SizeToContents()
|
||||
|
||||
local zPos = 1
|
||||
|
||||
-- set up character vars
|
||||
for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do
|
||||
if (!v.bNoDisplay and k != "__SortedIndex") then
|
||||
local container = self:GetContainerPanel(v.category or "description")
|
||||
|
||||
if (v.ShouldDisplay and v:ShouldDisplay(container, self.payload) == false) then
|
||||
continue
|
||||
end
|
||||
|
||||
local panel
|
||||
|
||||
-- if the var has a custom way of displaying, we'll use that instead
|
||||
if (v.OnDisplay) then
|
||||
panel = v:OnDisplay(container, self.payload)
|
||||
elseif (isstring(v.default)) then
|
||||
panel = container:Add("ixTextEntry")
|
||||
panel:Dock(TOP)
|
||||
panel:SetFont("ixMenuButtonHugeFont")
|
||||
panel:SetUpdateOnType(true)
|
||||
panel.OnValueChange = function(this, text)
|
||||
self.payload:Set(k, text)
|
||||
end
|
||||
end
|
||||
|
||||
if (IsValid(panel)) then
|
||||
-- add label for entry
|
||||
local label = container:Add("DLabel")
|
||||
label:SetFont("ixMenuButtonLabelFont")
|
||||
label:SetText(L(k):utf8upper())
|
||||
label:SizeToContents()
|
||||
label:DockMargin(0, 16, 0, 2)
|
||||
label:Dock(TOP)
|
||||
|
||||
-- we need to set the docking order so the label is above the panel
|
||||
label:SetZPos(zPos - 1)
|
||||
panel:SetZPos(zPos)
|
||||
|
||||
self:AttachCleanup(label)
|
||||
self:AttachCleanup(panel)
|
||||
|
||||
if (v.OnPostSetup) then
|
||||
v:OnPostSetup(panel, self.payload)
|
||||
end
|
||||
|
||||
zPos = zPos + 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (!self.bInitialPopulate) then
|
||||
-- setup progress bar segments
|
||||
if (#self.factionButtons > 1) then
|
||||
self.progress:AddSegment("@faction")
|
||||
end
|
||||
|
||||
self.progress:AddSegment("@description")
|
||||
|
||||
if (#self.attributesPanel:GetChildren() > 1) then
|
||||
self.progress:AddSegment("@skills")
|
||||
end
|
||||
|
||||
-- we don't need to show the progress bar if there's only one segment
|
||||
if (#self.progress:GetSegments() == 1) then
|
||||
self.progress:SetVisible(false)
|
||||
end
|
||||
end
|
||||
|
||||
self.bInitialPopulate = true
|
||||
end
|
||||
|
||||
function PANEL:VerifyProgression(name)
|
||||
for k, v in SortedPairsByMemberValue(ix.char.vars, "index") do
|
||||
if (name ~= nil and (v.category or "description") != name) then
|
||||
continue
|
||||
end
|
||||
|
||||
local value = self.payload[k]
|
||||
|
||||
if (!v.bNoDisplay or v.OnValidate) then
|
||||
if (v.OnValidate) then
|
||||
local result = {v:OnValidate(value, self.payload, LocalPlayer())}
|
||||
|
||||
if (result[1] == false) then
|
||||
self:GetParent():ShowNotice(3, L(unpack(result, 2)))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
self.payload[k] = value
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintCharacterCreateBackground", self, width, height)
|
||||
BaseClass.Paint(self, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixCharMenuNew", PANEL, "ixCharMenuPanel")
|
||||
486
gamemodes/helix/gamemode/core/derma/cl_charload.lua
Normal file
486
gamemodes/helix/gamemode/core/derma/cl_charload.lua
Normal file
@@ -0,0 +1,486 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local errorModel = "models/error.mdl"
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
|
||||
|
||||
local function SetCharacter(self, character)
|
||||
self.character = character
|
||||
|
||||
if (character) then
|
||||
self:SetModel(character:GetModel())
|
||||
self:SetSkin(character:GetData("skin", 0))
|
||||
|
||||
for i = 0, (self:GetNumBodyGroups() - 1) do
|
||||
self:SetBodygroup(i, 0)
|
||||
end
|
||||
|
||||
local bodygroups = character:GetData("groups", nil)
|
||||
|
||||
if (istable(bodygroups)) then
|
||||
for k, v in pairs(bodygroups) do
|
||||
self:SetBodygroup(k, v)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:SetModel(errorModel)
|
||||
end
|
||||
end
|
||||
|
||||
local function GetCharacter(self)
|
||||
return self.character
|
||||
end
|
||||
|
||||
function PANEL:Init()
|
||||
self.activeCharacter = ClientsideModel(errorModel)
|
||||
self.activeCharacter:SetNoDraw(true)
|
||||
self.activeCharacter.SetCharacter = SetCharacter
|
||||
self.activeCharacter.GetCharacter = GetCharacter
|
||||
|
||||
self.lastCharacter = ClientsideModel(errorModel)
|
||||
self.lastCharacter:SetNoDraw(true)
|
||||
self.lastCharacter.SetCharacter = SetCharacter
|
||||
self.lastCharacter.GetCharacter = GetCharacter
|
||||
|
||||
self.animationTime = 0.5
|
||||
|
||||
self.shadeY = 0
|
||||
self.shadeHeight = 0
|
||||
|
||||
self.cameraPosition = Vector(80, 0, 35)
|
||||
self.cameraAngle = Angle(0, 180, 0)
|
||||
self.lastPaint = 0
|
||||
end
|
||||
|
||||
function PANEL:ResetSequence(model, lastModel)
|
||||
local sequence = model:LookupSequence("idle_unarmed")
|
||||
|
||||
if (sequence <= 0) then
|
||||
sequence = model:SelectWeightedSequence(ACT_IDLE)
|
||||
end
|
||||
|
||||
if (sequence > 0) then
|
||||
model:ResetSequence(sequence)
|
||||
else
|
||||
local found = false
|
||||
|
||||
for _, v in ipairs(model:GetSequenceList()) do
|
||||
if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then
|
||||
model:ResetSequence(v)
|
||||
found = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!found) then
|
||||
model:ResetSequence(4)
|
||||
end
|
||||
end
|
||||
|
||||
model:SetIK(false)
|
||||
|
||||
-- copy cycle if we can to avoid a jarring transition from resetting the sequence
|
||||
if (lastModel) then
|
||||
model:SetCycle(lastModel:GetCycle())
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:RunAnimation(model)
|
||||
model:FrameAdvance((RealTime() - self.lastPaint) * 0.5)
|
||||
end
|
||||
|
||||
function PANEL:LayoutEntity(model)
|
||||
model:SetIK(false)
|
||||
|
||||
self:RunAnimation(model)
|
||||
end
|
||||
|
||||
function PANEL:SetActiveCharacter(character)
|
||||
self.shadeY = self:GetTall()
|
||||
self.shadeHeight = self:GetTall()
|
||||
|
||||
-- set character immediately if we're an error (something isn't selected yet)
|
||||
if (self.activeCharacter:GetModel() == errorModel) then
|
||||
self.activeCharacter:SetCharacter(character)
|
||||
self:ResetSequence(self.activeCharacter)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- if the animation is already playing, we update its parameters so we can avoid restarting
|
||||
local shade = self:GetTweenAnimation(1)
|
||||
local shadeHide = self:GetTweenAnimation(2)
|
||||
|
||||
if (shade) then
|
||||
shade.newCharacter = character
|
||||
return
|
||||
elseif (shadeHide) then
|
||||
shadeHide.queuedCharacter = character
|
||||
return
|
||||
end
|
||||
|
||||
self.lastCharacter:SetCharacter(self.activeCharacter:GetCharacter())
|
||||
self:ResetSequence(self.lastCharacter, self.activeCharacter)
|
||||
|
||||
shade = self:CreateAnimation(self.animationTime * 0.5, {
|
||||
index = 1,
|
||||
target = {
|
||||
shadeY = 0,
|
||||
shadeHeight = self:GetTall()
|
||||
},
|
||||
easing = "linear",
|
||||
|
||||
OnComplete = function(shadeAnimation, shadePanel)
|
||||
shadePanel.activeCharacter:SetCharacter(shadeAnimation.newCharacter)
|
||||
shadePanel:ResetSequence(shadePanel.activeCharacter)
|
||||
|
||||
shadePanel:CreateAnimation(shadePanel.animationTime, {
|
||||
index = 2,
|
||||
target = {shadeHeight = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
if (animation.queuedCharacter) then
|
||||
panel:SetActiveCharacter(animation.queuedCharacter)
|
||||
else
|
||||
panel.lastCharacter:SetCharacter(nil)
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
})
|
||||
|
||||
shade.newCharacter = character
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
local x, y = self:LocalToScreen(0, 0)
|
||||
local bTransition = self.lastCharacter:GetModel() != errorModel
|
||||
local modelFOV = (ScrW() > ScrH() * 1.8) and 92 or 70
|
||||
|
||||
cam.Start3D(self.cameraPosition, self.cameraAngle, modelFOV, x, y, width, height)
|
||||
render.SuppressEngineLighting(true)
|
||||
render.SetLightingOrigin(self.activeCharacter:GetPos())
|
||||
|
||||
-- setup lighting
|
||||
render.SetModelLighting(0, 1.5, 1.5, 1.5)
|
||||
|
||||
for i = 1, 4 do
|
||||
render.SetModelLighting(i, 0.4, 0.4, 0.4)
|
||||
end
|
||||
|
||||
render.SetModelLighting(5, 0.04, 0.04, 0.04)
|
||||
|
||||
-- clip anything out of bounds
|
||||
local curparent = self
|
||||
local rightx = self:GetWide()
|
||||
local leftx = 0
|
||||
local topy = 0
|
||||
local bottomy = self:GetTall()
|
||||
local previous = curparent
|
||||
|
||||
while (curparent:GetParent() != nil) do
|
||||
local lastX, lastY = previous:GetPos()
|
||||
curparent = curparent:GetParent()
|
||||
|
||||
topy = math.Max(lastY, topy + lastY)
|
||||
leftx = math.Max(lastX, leftx + lastX)
|
||||
bottomy = math.Min(lastY + previous:GetTall(), bottomy + lastY)
|
||||
rightx = math.Min(lastX + previous:GetWide(), rightx + lastX)
|
||||
|
||||
previous = curparent
|
||||
end
|
||||
|
||||
ix.util.ResetStencilValues()
|
||||
render.SetStencilEnable(true)
|
||||
render.SetStencilWriteMask(30)
|
||||
render.SetStencilTestMask(30)
|
||||
render.SetStencilReferenceValue(31)
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_ALWAYS)
|
||||
render.SetStencilPassOperation(STENCIL_REPLACE)
|
||||
render.SetStencilFailOperation(STENCIL_KEEP)
|
||||
render.SetStencilZFailOperation(STENCIL_KEEP)
|
||||
|
||||
self:LayoutEntity(self.activeCharacter)
|
||||
|
||||
if (bTransition) then
|
||||
-- only need to layout while it's used
|
||||
self:LayoutEntity(self.lastCharacter)
|
||||
|
||||
render.SetScissorRect(leftx, topy, rightx, bottomy - (self:GetTall() - self.shadeHeight), true)
|
||||
self.lastCharacter:DrawModel()
|
||||
|
||||
render.SetScissorRect(leftx, topy + self.shadeHeight, rightx, bottomy, true)
|
||||
self.activeCharacter:DrawModel()
|
||||
|
||||
render.SetScissorRect(leftx, topy, rightx, bottomy, true)
|
||||
else
|
||||
self.activeCharacter:DrawModel()
|
||||
end
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_EQUAL)
|
||||
render.SetStencilPassOperation(STENCIL_KEEP)
|
||||
|
||||
cam.Start2D()
|
||||
derma.SkinFunc("PaintCharacterTransitionOverlay", self, 0, self.shadeY, width, self.shadeHeight)
|
||||
cam.End2D()
|
||||
render.SetStencilEnable(false)
|
||||
|
||||
render.SetScissorRect(0, 0, 0, 0, false)
|
||||
render.SuppressEngineLighting(false)
|
||||
cam.End3D()
|
||||
|
||||
self.lastPaint = RealTime()
|
||||
end
|
||||
|
||||
function PANEL:OnRemove()
|
||||
self.lastCharacter:Remove()
|
||||
self.activeCharacter:Remove()
|
||||
end
|
||||
|
||||
vgui.Register("ixCharMenuCarousel", PANEL, "Panel")
|
||||
|
||||
-- character load panel
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "backgroundFraction", "BackgroundFraction", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
local parent = self:GetParent()
|
||||
local padding = self:GetPadding()
|
||||
local halfWidth = parent:GetWide() * 0.5 - (padding * 2)
|
||||
local halfHeight = parent:GetTall() * 0.5 - (padding * 2)
|
||||
local modelFOV = (ScrW() > ScrH() * 1.8) and 102 or 78
|
||||
|
||||
self.animationTime = 1
|
||||
self.backgroundFraction = 1
|
||||
|
||||
-- main panel
|
||||
self.panel = self:AddSubpanel("main")
|
||||
self.panel:SetTitle("loadTitle")
|
||||
self.panel.OnSetActive = function()
|
||||
self:CreateAnimation(self.animationTime, {
|
||||
index = 2,
|
||||
target = {backgroundFraction = 1},
|
||||
easing = "outQuint",
|
||||
})
|
||||
end
|
||||
|
||||
-- character button list
|
||||
local controlList = self.panel:Add("Panel")
|
||||
controlList:Dock(LEFT)
|
||||
controlList:SetSize(halfWidth, halfHeight)
|
||||
|
||||
local back = controlList:Add("ixMenuButton")
|
||||
back:Dock(BOTTOM)
|
||||
back:SetText("return")
|
||||
back:SizeToContents()
|
||||
back.DoClick = function()
|
||||
self:SlideDown()
|
||||
parent.mainPanel:Undim()
|
||||
end
|
||||
|
||||
self.characterList = controlList:Add("ixCharMenuButtonList")
|
||||
self.characterList.buttons = {}
|
||||
self.characterList:Dock(FILL)
|
||||
|
||||
-- right-hand side with carousel and buttons
|
||||
local infoPanel = self.panel:Add("Panel")
|
||||
infoPanel:Dock(FILL)
|
||||
|
||||
local infoButtons = infoPanel:Add("Panel")
|
||||
infoButtons:Dock(BOTTOM)
|
||||
infoButtons:SetTall(back:GetTall()) -- hmm...
|
||||
|
||||
local continueButton = infoButtons:Add("ixMenuButton")
|
||||
continueButton:Dock(FILL)
|
||||
continueButton:SetText("choose")
|
||||
continueButton:SetContentAlignment(6)
|
||||
continueButton:SizeToContents()
|
||||
continueButton.DoClick = function()
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:Slide("down", self.animationTime, function()
|
||||
net.Start("ixCharacterChoose")
|
||||
net.WriteUInt(self.character:GetID(), 32)
|
||||
net.SendToServer()
|
||||
end, true)
|
||||
end
|
||||
|
||||
local deleteButton = infoButtons:Add("ixMenuButton")
|
||||
deleteButton:Dock(LEFT)
|
||||
deleteButton:SetText("delete")
|
||||
deleteButton:SetContentAlignment(5)
|
||||
deleteButton:SetTextInset(0, 0)
|
||||
deleteButton:SizeToContents()
|
||||
deleteButton:SetTextColor(derma.GetColor("Error", deleteButton))
|
||||
deleteButton.DoClick = function()
|
||||
self:SetActiveSubpanel("delete")
|
||||
end
|
||||
|
||||
self.carousel = infoPanel:Add("ixCharMenuCarousel")
|
||||
self.carousel:Dock(FILL)
|
||||
|
||||
-- character deletion panel
|
||||
self.delete = self:AddSubpanel("delete")
|
||||
self.delete:SetTitle(nil)
|
||||
self.delete.OnSetActive = function()
|
||||
self.deleteModel:SetModel(self.character:GetModel())
|
||||
self:CreateAnimation(self.animationTime, {
|
||||
index = 2,
|
||||
target = {backgroundFraction = 0},
|
||||
easing = "outQuint"
|
||||
})
|
||||
end
|
||||
|
||||
local deleteInfo = self.delete:Add("Panel")
|
||||
deleteInfo:SetSize(parent:GetWide() * 0.5, parent:GetTall())
|
||||
deleteInfo:Dock(LEFT)
|
||||
|
||||
local deleteReturn = deleteInfo:Add("ixMenuButton")
|
||||
deleteReturn:Dock(BOTTOM)
|
||||
deleteReturn:SetText("no")
|
||||
deleteReturn:SizeToContents()
|
||||
deleteReturn.DoClick = function()
|
||||
self:SetActiveSubpanel("main")
|
||||
end
|
||||
|
||||
local deleteConfirm = self.delete:Add("ixMenuButton")
|
||||
deleteConfirm:Dock(BOTTOM)
|
||||
deleteConfirm:SetText("yes")
|
||||
deleteConfirm:SetContentAlignment(6)
|
||||
deleteConfirm:SizeToContents()
|
||||
deleteConfirm:SetTextColor(derma.GetColor("Error", deleteConfirm))
|
||||
deleteConfirm.DoClick = function()
|
||||
local id = self.character:GetID()
|
||||
|
||||
parent:ShowNotice(1, L("deleteComplete", self.character:GetName()))
|
||||
self:Populate(id)
|
||||
self:SetActiveSubpanel("main")
|
||||
|
||||
net.Start("ixCharacterDelete")
|
||||
net.WriteUInt(id, 32)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
self.deleteModel = deleteInfo:Add("ixModelPanel")
|
||||
self.deleteModel:Dock(FILL)
|
||||
self.deleteModel:SetModel(errorModel)
|
||||
self.deleteModel:SetFOV(modelFOV)
|
||||
self.deleteModel.PaintModel = self.deleteModel.Paint
|
||||
|
||||
local deleteNag = self.delete:Add("Panel")
|
||||
deleteNag:SetTall(parent:GetTall() * 0.5)
|
||||
deleteNag:Dock(BOTTOM)
|
||||
|
||||
local deleteTitle = deleteNag:Add("DLabel")
|
||||
deleteTitle:SetFont("ixTitleFont")
|
||||
deleteTitle:SetText(L("areYouSure"):utf8upper())
|
||||
deleteTitle:SetTextColor(ix.config.Get("color"))
|
||||
deleteTitle:SizeToContents()
|
||||
deleteTitle:Dock(TOP)
|
||||
|
||||
local deleteText = deleteNag:Add("DLabel")
|
||||
deleteText:SetFont("ixMenuButtonFont")
|
||||
deleteText:SetText(L("deleteConfirm"))
|
||||
deleteText:SetTextColor(color_white)
|
||||
deleteText:SetContentAlignment(7)
|
||||
deleteText:Dock(FILL)
|
||||
|
||||
-- finalize setup
|
||||
self:SetActiveSubpanel("main", 0)
|
||||
end
|
||||
|
||||
function PANEL:OnCharacterDeleted(character)
|
||||
if (self.bActive and #ix.characters == 0) then
|
||||
self:SlideDown()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Populate(ignoreID)
|
||||
self.characterList:Clear()
|
||||
self.characterList.buttons = {}
|
||||
|
||||
local bSelected
|
||||
|
||||
-- loop backwards to preserve order since we're docking to the bottom
|
||||
for i = 1, #ix.characters do
|
||||
local id = ix.characters[i]
|
||||
local character = ix.char.loaded[id]
|
||||
|
||||
if (!character or character:GetID() == ignoreID) then
|
||||
continue
|
||||
end
|
||||
|
||||
local index = character:GetFaction()
|
||||
local faction = ix.faction.indices[index]
|
||||
local color = faction and faction.color or color_white
|
||||
|
||||
local button = self.characterList:Add("ixMenuSelectionButton")
|
||||
button:SetBackgroundColor(color)
|
||||
button:SetText(character:GetName())
|
||||
button:SizeToContents()
|
||||
button:SetButtonList(self.characterList.buttons)
|
||||
button.character = character
|
||||
button.OnSelected = function(panel)
|
||||
self:OnCharacterButtonSelected(panel)
|
||||
end
|
||||
|
||||
-- select currently loaded character if available
|
||||
local localCharacter = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
|
||||
|
||||
if (localCharacter and character:GetID() == localCharacter:GetID()) then
|
||||
button:SetSelected(true)
|
||||
self.characterList:ScrollToChild(button)
|
||||
|
||||
bSelected = true
|
||||
end
|
||||
end
|
||||
|
||||
if (!bSelected) then
|
||||
local buttons = self.characterList.buttons
|
||||
|
||||
if (#buttons > 0) then
|
||||
local button = buttons[#buttons]
|
||||
|
||||
button:SetSelected(true)
|
||||
self.characterList:ScrollToChild(button)
|
||||
else
|
||||
self.character = nil
|
||||
end
|
||||
end
|
||||
|
||||
self.characterList:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:OnSlideUp()
|
||||
self.bActive = true
|
||||
self:Populate()
|
||||
end
|
||||
|
||||
function PANEL:OnSlideDown()
|
||||
self.bActive = false
|
||||
end
|
||||
|
||||
function PANEL:OnCharacterButtonSelected(panel)
|
||||
self.carousel:SetActiveCharacter(panel.character)
|
||||
self.character = panel.character
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintCharacterLoadBackground", self, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixCharMenuLoad", PANEL, "ixCharMenuPanel")
|
||||
169
gamemodes/helix/gamemode/core/derma/cl_classes.lua
Normal file
169
gamemodes/helix/gamemode/core/derma/cl_classes.lua
Normal file
@@ -0,0 +1,169 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetTall(64)
|
||||
|
||||
local function AssignClick(panel)
|
||||
panel.OnMousePressed = function()
|
||||
self.pressing = -1
|
||||
self:OnClick()
|
||||
end
|
||||
|
||||
panel.OnMouseReleased = function()
|
||||
if (self.pressing) then
|
||||
self.pressing = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
self.icon = self:Add("SpawnIcon")
|
||||
self.icon:SetSize(128, 64)
|
||||
self.icon:InvalidateLayout(true)
|
||||
self.icon:Dock(LEFT)
|
||||
self.icon.PaintOver = function(this, w, h)
|
||||
end
|
||||
AssignClick(self.icon)
|
||||
|
||||
self.limit = self:Add("DLabel")
|
||||
self.limit:Dock(RIGHT)
|
||||
self.limit:SetMouseInputEnabled(true)
|
||||
self.limit:SetCursor("hand")
|
||||
self.limit:SetExpensiveShadow(1, Color(0, 0, 60))
|
||||
self.limit:SetContentAlignment(5)
|
||||
self.limit:SetFont("ixMediumFont")
|
||||
self.limit:SetWide(64)
|
||||
AssignClick(self.limit)
|
||||
|
||||
self.label = self:Add("DLabel")
|
||||
self.label:Dock(FILL)
|
||||
self.label:SetMouseInputEnabled(true)
|
||||
self.label:SetCursor("hand")
|
||||
self.label:SetExpensiveShadow(1, Color(0, 0, 60))
|
||||
self.label:SetContentAlignment(5)
|
||||
self.label:SetFont("ixMediumFont")
|
||||
AssignClick(self.label)
|
||||
end
|
||||
|
||||
function PANEL:OnClick()
|
||||
ix.command.Send("BecomeClass", self.class)
|
||||
end
|
||||
|
||||
function PANEL:SetNumber(number)
|
||||
local limit = self.data.limit
|
||||
|
||||
if (limit > 0) then
|
||||
self.limit:SetText(Format("%s/%s", number, limit))
|
||||
else
|
||||
self.limit:SetText("∞")
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetClass(data)
|
||||
if (data.model) then
|
||||
local model = data.model
|
||||
if (istable(model)) then
|
||||
model = table.Random(model)
|
||||
end
|
||||
|
||||
self.icon:SetModel(model)
|
||||
else
|
||||
local char = LocalPlayer():GetCharacter()
|
||||
local model = LocalPlayer():GetModel()
|
||||
|
||||
if (char) then
|
||||
model = char:GetModel()
|
||||
end
|
||||
|
||||
self.icon:SetModel(model)
|
||||
end
|
||||
|
||||
self.label:SetText(L(data.name))
|
||||
self.data = data
|
||||
self.class = data.index
|
||||
|
||||
self:SetNumber(#ix.class.GetPlayers(data.index))
|
||||
end
|
||||
|
||||
vgui.Register("ixClassPanel", PANEL, "DPanel")
|
||||
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
ix.gui.classes = self
|
||||
|
||||
self:SetSize(self:GetParent():GetSize())
|
||||
|
||||
self.list = vgui.Create("DPanelList", self)
|
||||
self.list:Dock(FILL)
|
||||
self.list:EnableVerticalScrollbar()
|
||||
self.list:SetSpacing(5)
|
||||
self.list:SetPadding(5)
|
||||
|
||||
self.classPanels = {}
|
||||
self:LoadClasses()
|
||||
end
|
||||
|
||||
function PANEL:LoadClasses()
|
||||
self.list:Clear()
|
||||
|
||||
for k, v in ipairs(ix.class.list) do
|
||||
local no, why = ix.class.CanSwitchTo(LocalPlayer(), k)
|
||||
local itsFull = ("class is full" == why)
|
||||
|
||||
if (no or itsFull) then
|
||||
local panel = vgui.Create("ixClassPanel", self.list)
|
||||
panel:SetClass(v)
|
||||
table.insert(self.classPanels, panel)
|
||||
|
||||
self.list:AddItem(panel)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixClasses", PANEL, "EditablePanel")
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixClasses", function(tabs)
|
||||
local cnt = table.Count(ix.class.list)
|
||||
|
||||
if (cnt <= 1) then return end
|
||||
|
||||
for k, _ in ipairs(ix.class.list) do
|
||||
if (!ix.class.CanSwitchTo(LocalPlayer(), k)) then
|
||||
continue
|
||||
else
|
||||
tabs["classes"] = function(container)
|
||||
container:Add("ixClasses")
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixClassUpdate", function()
|
||||
local client = net.ReadEntity()
|
||||
|
||||
if (ix.gui.classes and ix.gui.classes:IsVisible()) then
|
||||
if (client == LocalPlayer()) then
|
||||
ix.gui.classes:LoadClasses()
|
||||
else
|
||||
for _, v in ipairs(ix.gui.classes.classPanels) do
|
||||
local data = v.data
|
||||
|
||||
v:SetNumber(#ix.class.GetPlayers(data.index))
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
210
gamemodes/helix/gamemode/core/derma/cl_config.lua
Normal file
210
gamemodes/helix/gamemode/core/derma/cl_config.lua
Normal file
@@ -0,0 +1,210 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- config manager
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
self:SetSearchEnabled(true)
|
||||
|
||||
self:Populate()
|
||||
end
|
||||
|
||||
function PANEL:Populate()
|
||||
-- gather categories
|
||||
local categories = {}
|
||||
local categoryIndices = {}
|
||||
|
||||
for k, v in pairs(ix.config.stored) do
|
||||
local index = v.data and v.data.category or "misc"
|
||||
|
||||
categories[index] = categories[index] or {}
|
||||
categories[index][k] = v
|
||||
end
|
||||
|
||||
-- sort by category phrase
|
||||
for k, _ in pairs(categories) do
|
||||
categoryIndices[#categoryIndices + 1] = k
|
||||
end
|
||||
|
||||
table.sort(categoryIndices, function(a, b)
|
||||
return L(a) < L(b)
|
||||
end)
|
||||
|
||||
-- add panels
|
||||
for _, category in ipairs(categoryIndices) do
|
||||
local categoryPhrase = L(category)
|
||||
self:AddCategory(categoryPhrase)
|
||||
|
||||
-- we can use sortedpairs since configs don't have phrases to account for
|
||||
for k, v in SortedPairs(categories[category]) do
|
||||
if (isfunction(v.hidden) and v.hidden()) then
|
||||
continue
|
||||
end
|
||||
|
||||
local data = v.data.data
|
||||
local type = v.type
|
||||
local value = ix.util.SanitizeType(type, ix.config.Get(k))
|
||||
|
||||
local row = self:AddRow(type, categoryPhrase)
|
||||
row:SetText(ix.util.ExpandCamelCase(k))
|
||||
|
||||
-- type-specific properties
|
||||
if (type == ix.type.number) then
|
||||
row:SetMin(data and data.min or 0)
|
||||
row:SetMax(data and data.max or 1)
|
||||
row:SetDecimals(data and data.decimals or 0)
|
||||
end
|
||||
|
||||
row:SetValue(value, true)
|
||||
row:SetShowReset(value != v.default, k, v.default)
|
||||
|
||||
row.OnValueChanged = function(panel)
|
||||
local newValue = ix.util.SanitizeType(type, panel:GetValue())
|
||||
|
||||
panel:SetShowReset(newValue != v.default, k, v.default)
|
||||
|
||||
net.Start("ixConfigSet")
|
||||
net.WriteString(k)
|
||||
net.WriteType(newValue)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
row.OnResetClicked = function(panel)
|
||||
panel:SetValue(v.default, true)
|
||||
panel:SetShowReset(false)
|
||||
|
||||
net.Start("ixConfigSet")
|
||||
net.WriteString(k)
|
||||
net.WriteType(v.default)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
row:GetLabel():SetHelixTooltip(function(tooltip)
|
||||
local title = tooltip:AddRow("name")
|
||||
title:SetImportant()
|
||||
title:SetText(k)
|
||||
title:SizeToContents()
|
||||
title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5))
|
||||
|
||||
local description = tooltip:AddRow("description")
|
||||
description:SetText(v.description)
|
||||
description:SizeToContents()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
self:SizeToContents()
|
||||
end
|
||||
|
||||
vgui.Register("ixConfigManager", PANEL, "ixSettings")
|
||||
|
||||
-- plugin manager
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
self:SetSearchEnabled(true)
|
||||
|
||||
self.loadedCategory = L("loadedPlugins")
|
||||
self.unloadedCategory = L("unloadedPlugins")
|
||||
|
||||
if (!ix.gui.bReceivedUnloadedPlugins) then
|
||||
net.Start("ixConfigRequestUnloadedList")
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
self:Populate()
|
||||
end
|
||||
|
||||
function PANEL:OnPluginToggled(uniqueID, bEnabled)
|
||||
net.Start("ixConfigPluginToggle")
|
||||
net.WriteString(uniqueID)
|
||||
net.WriteBool(bEnabled)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function PANEL:Populate()
|
||||
self:AddCategory(self.loadedCategory)
|
||||
self:AddCategory(self.unloadedCategory)
|
||||
|
||||
-- add loaded plugins
|
||||
for k, v in SortedPairsByMemberValue(ix.plugin.list, "name") do
|
||||
local row = self:AddRow(ix.type.bool, self.loadedCategory)
|
||||
row.id = k
|
||||
|
||||
row.setting:SetEnabledText(L("on"):utf8upper())
|
||||
row.setting:SetDisabledText(L("off"):utf8upper())
|
||||
row.setting:SizeToContents()
|
||||
|
||||
-- if this plugin is not in the unloaded list currently, then it's queued for an unload
|
||||
row:SetValue(!ix.plugin.unloaded[k], true)
|
||||
row:SetText(v.name)
|
||||
|
||||
row.OnValueChanged = function(panel, bEnabled)
|
||||
self:OnPluginToggled(k, bEnabled)
|
||||
end
|
||||
|
||||
row:GetLabel():SetHelixTooltip(function(tooltip)
|
||||
local title = tooltip:AddRow("name")
|
||||
title:SetImportant()
|
||||
title:SetText(v.name)
|
||||
title:SizeToContents()
|
||||
title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5))
|
||||
|
||||
local description = tooltip:AddRow("description")
|
||||
description:SetText(v.description)
|
||||
description:SizeToContents()
|
||||
end)
|
||||
end
|
||||
|
||||
self:UpdateUnloaded(true)
|
||||
self:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:UpdatePlugin(uniqueID, bEnabled)
|
||||
for _, v in pairs(self:GetRows()) do
|
||||
if (v.id == uniqueID) then
|
||||
v:SetValue(bEnabled, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- called from Populate and from the ixConfigUnloadedList net message
|
||||
function PANEL:UpdateUnloaded(bNoSizeToContents)
|
||||
for _, v in pairs(self:GetRows()) do
|
||||
if (ix.plugin.unloaded[v.id]) then
|
||||
v:SetValue(false, true)
|
||||
end
|
||||
end
|
||||
|
||||
for k, _ in SortedPairs(ix.plugin.unloaded) do
|
||||
if (ix.plugin.list[k]) then
|
||||
-- if this plugin is in the loaded plugins list then it's queued for an unload - don't display it in this category
|
||||
continue
|
||||
end
|
||||
|
||||
local row = self:AddRow(ix.type.bool, self.unloadedCategory)
|
||||
row:SetText(k)
|
||||
row:SetValue(false, true)
|
||||
|
||||
row.OnValueChanged = function(panel, bEnabled)
|
||||
self:OnPluginToggled(k, bEnabled)
|
||||
end
|
||||
end
|
||||
|
||||
if (!bNoSizeToContents) then
|
||||
self:SizeToContents()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixPluginManager", PANEL, "ixSettings")
|
||||
318
gamemodes/helix/gamemode/core/derma/cl_credits.lua
Normal file
318
gamemodes/helix/gamemode/core/derma/cl_credits.lua
Normal file
@@ -0,0 +1,318 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local CREDITS = {
|
||||
{"Alex Grist", "76561197979205163", {"creditLeadDeveloper", "creditManager"}},
|
||||
{"Igor Radovanovic", "76561197990111113", {"creditLeadDeveloper", "creditUIDesigner"}},
|
||||
{"Jaydawg", "76561197970371430", {"creditTester"}}
|
||||
}
|
||||
|
||||
local SPECIALS = {
|
||||
{
|
||||
{"Luna", "76561197988658543"},
|
||||
{"Rain GBizzle", "76561198036111376"}
|
||||
},
|
||||
{
|
||||
{"Black Tea", "76561197999893894"}
|
||||
}
|
||||
}
|
||||
|
||||
local MISC = {
|
||||
{"nebulous", "Staff members finding bugs and providing input"},
|
||||
{"Contributors", "Ongoing support from various developers via GitHub"},
|
||||
{"NutScript", "Providing the base framework to build upon"}
|
||||
}
|
||||
|
||||
local url = "https://gethelix.co/"
|
||||
local padding = 32
|
||||
|
||||
-- logo
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetTall(ScrH() * 0.60)
|
||||
self:Dock(TOP)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25)
|
||||
|
||||
-- title
|
||||
surface.SetFont("ixIntroSubtitleFont")
|
||||
local text = L("helix"):lower()
|
||||
local textWidth, textHeight = surface.GetTextSize(text)
|
||||
|
||||
surface.SetTextColor(color_white)
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.5)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- version
|
||||
surface.SetFont("ixMenuMiniFont")
|
||||
surface.SetTextColor(200, 200, 200, 255)
|
||||
surface.SetTextPos(width * 0.5 + textWidth * 0.5, height * 0.5 - textHeight * 0.5)
|
||||
surface.DrawText(GAMEMODE.Version)
|
||||
end
|
||||
|
||||
vgui.Register("ixCreditsLogo", PANEL, "Panel")
|
||||
|
||||
-- nametag
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.name = self:Add("DLabel")
|
||||
self.name:SetFont("ixMenuButtonFontThick")
|
||||
|
||||
self.avatar = self:Add("AvatarImage")
|
||||
end
|
||||
|
||||
function PANEL:SetName(name)
|
||||
self.name:SetText(name)
|
||||
end
|
||||
|
||||
function PANEL:SetAvatar(steamid)
|
||||
self.avatar:SetSteamID(steamid, 64)
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
self.name:SetPos(width - self.name:GetWide(), 0)
|
||||
self.avatar:MoveLeftOf(self.name, padding * 0.5)
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
self.name:SizeToContents()
|
||||
|
||||
local tall = self.name:GetTall()
|
||||
self.avatar:SetSize(tall, tall)
|
||||
self:SetSize(self.name:GetWide() + self.avatar:GetWide() + padding * 0.5, self.name:GetTall())
|
||||
end
|
||||
|
||||
vgui.Register("ixCreditsNametag", PANEL, "Panel")
|
||||
|
||||
-- name row
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:DockMargin(0, padding, 0, 0)
|
||||
self:Dock(TOP)
|
||||
|
||||
self.nametag = self:Add("ixCreditsNametag")
|
||||
|
||||
self.tags = self:Add("DLabel")
|
||||
self.tags:SetFont("ixMenuButtonFont")
|
||||
|
||||
self:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:SetName(name)
|
||||
self.nametag:SetName(name)
|
||||
end
|
||||
|
||||
function PANEL:SetAvatar(steamid)
|
||||
self.nametag:SetAvatar(steamid)
|
||||
end
|
||||
|
||||
function PANEL:SetTags(tags)
|
||||
for i = 1, #tags do
|
||||
tags[i] = L(tags[i])
|
||||
end
|
||||
|
||||
self.tags:SetText(table.concat(tags, "\n"))
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetDrawColor(ix.config.Get("color"))
|
||||
surface.DrawRect(width * 0.5 - 1, 0, 1, height)
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
self.nametag:SetPos(width * 0.5 - self.nametag:GetWide() - padding, 0)
|
||||
self.tags:SetPos(width * 0.5 + padding, 0)
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
self.nametag:SizeToContents()
|
||||
self.tags:SizeToContents()
|
||||
|
||||
self:SetTall(math.max(self.nametag:GetTall(), self.tags:GetTall()))
|
||||
end
|
||||
|
||||
vgui.Register("ixCreditsRow", PANEL, "Panel")
|
||||
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.left = {}
|
||||
self.right = {}
|
||||
end
|
||||
|
||||
function PANEL:AddLeft(name, steamid)
|
||||
local nametag = self:Add("ixCreditsNametag")
|
||||
nametag:SetName(name)
|
||||
nametag:SetAvatar(steamid)
|
||||
nametag:SizeToContents()
|
||||
|
||||
self.left[#self.left + 1] = nametag
|
||||
end
|
||||
|
||||
function PANEL:AddRight(name, steamid)
|
||||
local nametag = self:Add("ixCreditsNametag")
|
||||
nametag:SetName(name)
|
||||
nametag:SetAvatar(steamid)
|
||||
nametag:SizeToContents()
|
||||
|
||||
self.right[#self.right + 1] = nametag
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
local y = 0
|
||||
|
||||
for _, v in ipairs(self.left) do
|
||||
v:SetPos(width * 0.25 - v:GetWide() * 0.5, y)
|
||||
y = y + v:GetTall() + padding
|
||||
end
|
||||
|
||||
y = 0
|
||||
|
||||
for _, v in ipairs(self.right) do
|
||||
v:SetPos(width * 0.75 - v:GetWide() * 0.5, y)
|
||||
y = y + v:GetTall() + padding
|
||||
end
|
||||
|
||||
if (IsValid(self.center)) then
|
||||
self.center:SetPos(width * 0.5 - self.center:GetWide() * 0.5, y)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local heightLeft, heightRight, centerHeight = 0, 0, 0
|
||||
|
||||
if (#self.left > #self.right) then
|
||||
local center = self.left[#self.left]
|
||||
centerHeight = center:GetTall()
|
||||
|
||||
self.center = center
|
||||
self.left[#self.left] = nil
|
||||
elseif (#self.right > #self.left) then
|
||||
local center = self.right[#self.right]
|
||||
centerHeight = center:GetTall()
|
||||
|
||||
self.center = center
|
||||
self.right[#self.right] = nil
|
||||
end
|
||||
|
||||
for _, v in ipairs(self.left) do
|
||||
heightLeft = heightLeft + v:GetTall() + padding
|
||||
end
|
||||
|
||||
for _, v in ipairs(self.right) do
|
||||
heightRight = heightRight + v:GetTall() + padding
|
||||
end
|
||||
|
||||
self:SetTall(math.max(heightLeft, heightRight) + centerHeight)
|
||||
end
|
||||
|
||||
vgui.Register("ixCreditsSpecials", PANEL, "Panel")
|
||||
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Add("ixCreditsLogo")
|
||||
|
||||
local link = self:Add("DLabel", self)
|
||||
link:SetFont("ixMenuMiniFont")
|
||||
link:SetTextColor(Color(200, 200, 200, 255))
|
||||
link:SetText(url)
|
||||
link:SetContentAlignment(5)
|
||||
link:Dock(TOP)
|
||||
link:SizeToContents()
|
||||
link:SetMouseInputEnabled(true)
|
||||
link:SetCursor("hand")
|
||||
link.OnMousePressed = function()
|
||||
gui.OpenURL(url)
|
||||
end
|
||||
|
||||
for _, v in ipairs(CREDITS) do
|
||||
local row = self:Add("ixCreditsRow")
|
||||
row:SetName(v[1])
|
||||
row:SetAvatar(v[2])
|
||||
row:SetTags(v[3])
|
||||
row:SizeToContents()
|
||||
end
|
||||
|
||||
local specials = self:Add("ixLabel")
|
||||
specials:SetFont("ixMenuButtonFont")
|
||||
specials:SetText(L("creditSpecial"):utf8upper())
|
||||
specials:SetTextColor(ix.config.Get("color"))
|
||||
specials:SetDropShadow(1)
|
||||
specials:SetKerning(16)
|
||||
specials:SetContentAlignment(5)
|
||||
specials:DockMargin(0, padding * 2, 0, padding)
|
||||
specials:Dock(TOP)
|
||||
specials:SizeToContents()
|
||||
|
||||
local specialList = self:Add("ixCreditsSpecials")
|
||||
specialList:DockMargin(0, padding, 0, 0)
|
||||
specialList:Dock(TOP)
|
||||
|
||||
for _, v in ipairs(SPECIALS[1]) do
|
||||
specialList:AddLeft(v[1], v[2])
|
||||
end
|
||||
|
||||
for _, v in ipairs(SPECIALS[2]) do
|
||||
specialList:AddRight(v[1], v[2])
|
||||
end
|
||||
|
||||
specialList:SizeToContents()
|
||||
|
||||
-- less more padding if there's a center column nametag
|
||||
if (IsValid(specialList.center)) then
|
||||
specialList:DockMargin(0, padding, 0, padding)
|
||||
end
|
||||
|
||||
for _, v in ipairs(MISC) do
|
||||
local title = self:Add("DLabel")
|
||||
title:SetFont("ixMenuButtonFontThick")
|
||||
title:SetText(v[1])
|
||||
title:SetContentAlignment(5)
|
||||
title:SizeToContents()
|
||||
title:DockMargin(0, padding, 0, 0)
|
||||
title:Dock(TOP)
|
||||
|
||||
local description = self:Add("DLabel")
|
||||
description:SetFont("ixSmallTitleFont")
|
||||
description:SetText(v[2])
|
||||
description:SetContentAlignment(5)
|
||||
description:SizeToContents()
|
||||
description:Dock(TOP)
|
||||
end
|
||||
|
||||
self:Dock(TOP)
|
||||
self:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local height = padding
|
||||
|
||||
for _, v in pairs(self:GetChildren()) do
|
||||
local _, top, _, bottom = v:GetDockMargin()
|
||||
height = height + v:GetTall() + top + bottom
|
||||
end
|
||||
|
||||
self:SetTall(height)
|
||||
end
|
||||
|
||||
vgui.Register("ixCredits", PANEL, "Panel")
|
||||
|
||||
hook.Add("PopulateHelpMenu", "ixCredits", function(tabs)
|
||||
tabs["credits"] = function(container)
|
||||
container:Add("ixCredits")
|
||||
end
|
||||
end)
|
||||
271
gamemodes/helix/gamemode/core/derma/cl_dev_icon.lua
Normal file
271
gamemodes/helix/gamemode/core/derma/cl_dev_icon.lua
Normal file
@@ -0,0 +1,271 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--Icon Editor Base and Math Scale Functions from: https://github.com/TeslaCloud/flux-ce/tree/master
|
||||
|
||||
local scaleFactorX = 1 / 1920
|
||||
local scaleFactorY = 1 / 1080
|
||||
|
||||
local function scale(size)
|
||||
return math.floor(size * (ScrH() * scaleFactorY))
|
||||
end
|
||||
|
||||
local function scale_x(size)
|
||||
return math.floor(size * (ScrW() * scaleFactorX))
|
||||
end
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.gui.dev_icon)) then
|
||||
ix.gui.dev_icon:Remove()
|
||||
end
|
||||
|
||||
ix.gui.dev_icon = self
|
||||
local pW, pH = ScrW() * 0.6, ScrH() * 0.6
|
||||
self:SetSize(pW, pH)
|
||||
self:MakePopup()
|
||||
self:Center()
|
||||
|
||||
local buttonSize = scale(48)
|
||||
|
||||
self.model = vgui.Create("DAdjustableModelPanel", self)
|
||||
self.model:SetSize(pW * 0.5, pH - buttonSize)
|
||||
self.model:Dock(LEFT)
|
||||
self.model:DockMargin(0, 0, 0, buttonSize + scale(8))
|
||||
self.model:SetModel("models/props_borealis/bluebarrel001.mdl")
|
||||
self.model:SetLookAt(Vector(0, 0, 0))
|
||||
|
||||
self.model.LayoutEntity = function() end
|
||||
|
||||
local x = scale_x(4)
|
||||
self.best = vgui.Create("DButton", self)
|
||||
self.best:SetSize(buttonSize, buttonSize)
|
||||
self.best:SetPos(x, pH - buttonSize - scale(4))
|
||||
self.best:SetFont("ixIconsMenuButton")
|
||||
self.best:SetText("b")
|
||||
self.best:SetTooltip(L("iconEditorAlignBest"))
|
||||
|
||||
self.best.DoClick = function()
|
||||
local entity = self.model:GetEntity()
|
||||
local pos = entity:GetPos()
|
||||
local camData = PositionSpawnIcon(entity, pos)
|
||||
|
||||
if (camData) then
|
||||
self.model:SetCamPos(camData.origin)
|
||||
self.model:SetFOV(camData.fov)
|
||||
self.model:SetLookAng(camData.angles)
|
||||
end
|
||||
end
|
||||
|
||||
x = x + buttonSize + scale_x(4)
|
||||
|
||||
self.front = vgui.Create("DButton", self)
|
||||
self.front:SetSize(buttonSize, buttonSize)
|
||||
self.front:SetPos(x, pH - buttonSize - scale(4))
|
||||
self.front:SetFont("ixIconsMenuButton")
|
||||
self.front:SetText("m")
|
||||
self.front:SetTooltip(L("iconEditorAlignFront"))
|
||||
|
||||
self.front.DoClick = function()
|
||||
local entity = self.model:GetEntity()
|
||||
local pos = entity:GetPos()
|
||||
local camPos = pos + Vector(-200, 0, 0)
|
||||
self.model:SetCamPos(camPos)
|
||||
self.model:SetFOV(45)
|
||||
self.model:SetLookAng((camPos * -1):Angle())
|
||||
end
|
||||
|
||||
x = x + buttonSize + scale_x(4)
|
||||
|
||||
self.above = vgui.Create("DButton", self)
|
||||
self.above:SetSize(buttonSize, buttonSize)
|
||||
self.above:SetPos(x, pH - buttonSize - scale(4))
|
||||
self.above:SetFont("ixIconsMenuButton")
|
||||
self.above:SetText("u")
|
||||
self.above:SetTooltip(L("iconEditorAlignAbove"))
|
||||
|
||||
self.above.DoClick = function()
|
||||
local entity = self.model:GetEntity()
|
||||
local pos = entity:GetPos()
|
||||
local camPos = pos + Vector(0, 0, 200)
|
||||
self.model:SetCamPos(camPos)
|
||||
self.model:SetFOV(45)
|
||||
self.model:SetLookAng((camPos * -1):Angle())
|
||||
end
|
||||
|
||||
x = x + buttonSize + scale_x(4)
|
||||
|
||||
self.right = vgui.Create("DButton", self)
|
||||
self.right:SetSize(buttonSize, buttonSize)
|
||||
self.right:SetPos(x, pH - buttonSize - scale(4))
|
||||
self.right:SetFont("ixIconsMenuButton")
|
||||
self.right:SetText("t")
|
||||
self.right:SetTooltip(L("iconEditorAlignRight"))
|
||||
|
||||
self.right.DoClick = function()
|
||||
local entity = self.model:GetEntity()
|
||||
local pos = entity:GetPos()
|
||||
local camPos = pos + Vector(0, 200, 0)
|
||||
self.model:SetCamPos(camPos)
|
||||
self.model:SetFOV(45)
|
||||
self.model:SetLookAng((camPos * -1):Angle())
|
||||
end
|
||||
|
||||
x = x + buttonSize + scale_x(4)
|
||||
|
||||
self.center = vgui.Create("DButton", self)
|
||||
self.center:SetSize(buttonSize, buttonSize)
|
||||
self.center:SetPos(x, pH - buttonSize - scale(4))
|
||||
self.center:SetFont("ixIconsMenuButton")
|
||||
self.center:SetText("T")
|
||||
self.center:SetTooltip(L("iconEditorAlignCenter"))
|
||||
|
||||
self.center.DoClick = function()
|
||||
local entity = self.model:GetEntity()
|
||||
local pos = entity:GetPos()
|
||||
self.model:SetCamPos(pos)
|
||||
self.model:SetFOV(45)
|
||||
self.model:SetLookAng(Angle(0, -180, 0))
|
||||
end
|
||||
|
||||
self.best:DoClick()
|
||||
self.preview = vgui.Create("DPanel", self)
|
||||
self.preview:Dock(FILL)
|
||||
self.preview:DockMargin(scale_x(4), 0, 0, 0)
|
||||
self.preview:DockPadding(scale_x(4), scale(4), scale_x(4), scale(4))
|
||||
|
||||
self.preview.Paint = function(pnl, w, h)
|
||||
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100))
|
||||
end
|
||||
|
||||
self.modelLabel = self.preview:Add("DLabel")
|
||||
self.modelLabel:Dock(TOP)
|
||||
self.modelLabel:SetText("Model")
|
||||
self.modelLabel:SetFont("ixMenuButtonFontSmall")
|
||||
self.modelLabel:DockMargin(4, 4, 4, 4)
|
||||
|
||||
self.modelPath = vgui.Create("ixTextEntry", self.preview)
|
||||
self.modelPath:SetValue(self.model:GetModel())
|
||||
self.modelPath:Dock(TOP)
|
||||
self.modelPath:SetFont("ixMenuButtonFontSmall")
|
||||
self.modelPath:SetPlaceholderText("Model...")
|
||||
|
||||
self.modelPath.OnEnter = function(pnl)
|
||||
local model = pnl:GetValue()
|
||||
|
||||
if (model and model != "") then
|
||||
self.model:SetModel(model)
|
||||
self.item:Rebuild()
|
||||
end
|
||||
end
|
||||
|
||||
self.modelPath.OnLoseFocus = function(pnl)
|
||||
local model = pnl:GetValue()
|
||||
|
||||
if (model and model != "") then
|
||||
self.model:SetModel(model)
|
||||
self.item:Rebuild()
|
||||
end
|
||||
end
|
||||
|
||||
self.width = vgui.Create("ixSettingsRowNumber", self.preview)
|
||||
self.width:Dock(TOP)
|
||||
self.width:SetText(L("iconEditorWidth"))
|
||||
self.width:SetMin(1)
|
||||
self.width:SetMax(24)
|
||||
self.width:SetDecimals(0)
|
||||
self.width:SetValue(1)
|
||||
|
||||
self.width.OnValueChanged = function(pnl, value)
|
||||
self.item:Rebuild()
|
||||
end
|
||||
|
||||
self.height = vgui.Create("ixSettingsRowNumber", self.preview)
|
||||
self.height:Dock(TOP)
|
||||
self.height:SetText(L("iconEditorHeight"))
|
||||
self.height:SetMin(1)
|
||||
self.height:SetMax(24)
|
||||
self.height:SetDecimals(0)
|
||||
self.height:SetValue(1)
|
||||
|
||||
self.height.OnValueChanged = function(pnl, value)
|
||||
self.item:Rebuild()
|
||||
end
|
||||
|
||||
self.itemPanel = vgui.Create("DPanel", self.preview)
|
||||
self.itemPanel:Dock(FILL)
|
||||
|
||||
self.itemPanel.Paint = function(pnl, w, h)
|
||||
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, 100))
|
||||
end
|
||||
|
||||
self.item = vgui.Create("DModelPanel", self.itemPanel)
|
||||
self.item:SetMouseInputEnabled(false)
|
||||
self.item.LayoutEntity = function() end
|
||||
|
||||
self.item.PaintOver = function(pnl, w, h)
|
||||
surface.SetDrawColor(color_white)
|
||||
surface.DrawOutlinedRect(0, 0, w, h)
|
||||
end
|
||||
|
||||
self.item.Rebuild = function(pnl)
|
||||
local slotSize = 64
|
||||
local padding = scale(2)
|
||||
local slotWidth, slotHeight = math.Round(self.width:GetValue()), math.Round(self.height:GetValue())
|
||||
local w, h = slotWidth * (slotSize + padding) - padding, slotHeight * (slotSize + padding) - padding
|
||||
pnl:SetModel(self.model:GetModel())
|
||||
pnl:SetCamPos(self.model:GetCamPos())
|
||||
pnl:SetFOV(self.model:GetFOV())
|
||||
pnl:SetLookAng(self.model:GetLookAng())
|
||||
pnl:SetSize(w, h)
|
||||
pnl:Center()
|
||||
end
|
||||
|
||||
self.item:Rebuild()
|
||||
|
||||
timer.Create("ix_icon_editor_update", 0.5, 0, function()
|
||||
if IsValid(self) and IsValid(self.model) then
|
||||
self.item:Rebuild()
|
||||
else
|
||||
timer.Remove("ix_icon_editor_update")
|
||||
end
|
||||
end)
|
||||
|
||||
self.copy = vgui.Create("DButton", self)
|
||||
self.copy:SetSize(buttonSize, buttonSize)
|
||||
self.copy:SetPos(pW - buttonSize - scale_x(12), pH - buttonSize - scale(12))
|
||||
self.copy:SetFont("ixIconsMenuButton")
|
||||
self.copy:SetText("}")
|
||||
self.copy:SetTooltip(L("iconEditorCopy"))
|
||||
self.copy.DoClick = function()
|
||||
local camPos = self.model:GetCamPos()
|
||||
local camAng = self.model:GetLookAng()
|
||||
local str = "ITEM.model = \""..self.model:GetModel().."\"\n"
|
||||
.."ITEM.width = "..math.Round(self.width:GetValue()).."\n"
|
||||
.."ITEM.height = "..math.Round(self.height:GetValue()).."\n"
|
||||
.."ITEM.iconCam = {\n"
|
||||
.." pos = Vector("..math.Round(camPos.x, 2)..", "..math.Round(camPos.y, 2)..", "..math.Round(camPos.z, 2).."),\n"
|
||||
.." ang = Angle("..math.Round(camAng.p, 2)..", "..math.Round(camAng.y, 2)..", "..math.Round(camAng.r, 2).."),\n"
|
||||
.." fov = "..math.Round(self.model:GetFOV(), 2).."\n"
|
||||
.."}\n"
|
||||
|
||||
SetClipboardText(str)
|
||||
ix.util.Notify(L("iconEditorCopied"))
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ix_icon_editor", PANEL, "DFrame")
|
||||
|
||||
concommand.Add("ix_dev_icon", function()
|
||||
if (LocalPlayer():IsAdmin()) then
|
||||
vgui.Create("ix_icon_editor")
|
||||
end
|
||||
end)
|
||||
287
gamemodes/helix/gamemode/core/derma/cl_entitymenu.lua
Normal file
287
gamemodes/helix/gamemode/core/derma/cl_entitymenu.lua
Normal file
@@ -0,0 +1,287 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local animationTime = 1
|
||||
local padding = 32
|
||||
|
||||
-- entity menu button
|
||||
DEFINE_BASECLASS("ixMenuButton")
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "callback", "Callback")
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetTall(ScrH() * 0.1)
|
||||
self:Dock(TOP)
|
||||
end
|
||||
|
||||
function PANEL:DoClick()
|
||||
local bStatus = true
|
||||
local parent = ix.menu.panel
|
||||
local entity = parent:GetEntity()
|
||||
|
||||
if (parent.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
if (isfunction(self.callback)) then
|
||||
bStatus = self.callback()
|
||||
end
|
||||
|
||||
if (bStatus != false) then
|
||||
ix.menu.NetworkChoice(entity, self.originalText, bStatus)
|
||||
end
|
||||
|
||||
parent:Remove()
|
||||
end
|
||||
|
||||
function PANEL:SetText(text)
|
||||
self.originalText = text
|
||||
BaseClass.SetText(self, text)
|
||||
end
|
||||
|
||||
vgui.Register("ixEntityMenuButton", PANEL, "ixMenuButton")
|
||||
|
||||
-- entity menu list
|
||||
DEFINE_BASECLASS("EditablePanel")
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.list = {}
|
||||
end
|
||||
|
||||
function PANEL:AddOption(text, callback)
|
||||
local panel = self:Add("ixEntityMenuButton")
|
||||
panel:SetText(text)
|
||||
panel:SetCallback(callback)
|
||||
panel:Dock(TOP)
|
||||
|
||||
if (self.bPaintedManually) then
|
||||
panel:SetPaintedManually(true)
|
||||
end
|
||||
|
||||
self.list[#self.list + 1] = panel
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local height = 0
|
||||
|
||||
for i = 1, #self.list do
|
||||
height = height + self.list[i]:GetTall()
|
||||
end
|
||||
|
||||
self:SetSize(ScrW() * 0.5 - padding * 2, height)
|
||||
end
|
||||
|
||||
function PANEL:Center()
|
||||
local parent = self:GetParent()
|
||||
|
||||
self:SetPos(
|
||||
ScrW() * 0.5 + padding,
|
||||
parent:GetTall() * 0.5 - self:GetTall() * 0.5
|
||||
)
|
||||
end
|
||||
|
||||
function PANEL:SetPaintedManually(bValue)
|
||||
if (bValue) then
|
||||
for i = 1, #self.list do
|
||||
self.list[i]:SetPaintedManually(true)
|
||||
end
|
||||
|
||||
self.bPaintedManually = true
|
||||
end
|
||||
|
||||
BaseClass.SetPaintedManually(self, bValue)
|
||||
end
|
||||
|
||||
function PANEL:PaintManual()
|
||||
BaseClass.PaintManual(self)
|
||||
local list = self.list
|
||||
|
||||
for i = 1, #list do
|
||||
list[i]:PaintManual()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixEntityMenuList", PANEL, "EditablePanel")
|
||||
|
||||
-- entity menu
|
||||
DEFINE_BASECLASS("EditablePanel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "entity", "Entity")
|
||||
AccessorFunc(PANEL, "bClosing", "IsClosing", FORCE_BOOL)
|
||||
AccessorFunc(PANEL, "desiredHeight", "DesiredHeight", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.menu.panel)) then
|
||||
self:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
-- close entity tooltip if it's open
|
||||
if (IsValid(ix.gui.entityInfo)) then
|
||||
ix.gui.entityInfo:Remove()
|
||||
end
|
||||
|
||||
ix.menu.panel = self
|
||||
|
||||
self:SetSize(ScrW(), ScrH())
|
||||
self:SetPos(0, 0)
|
||||
|
||||
self.list = self:Add("ixEntityMenuList")
|
||||
self.list:SetPaintedManually(true)
|
||||
|
||||
self.desiredHeight = 0
|
||||
self.blur = 0
|
||||
self.alpha = 1
|
||||
self.bClosing = false
|
||||
self.lastPosition = vector_origin
|
||||
|
||||
self:CreateAnimation(animationTime, {
|
||||
target = {blur = 1},
|
||||
easing = "outQuint"
|
||||
})
|
||||
|
||||
self:MakePopup()
|
||||
end
|
||||
|
||||
function PANEL:SetOptions(options)
|
||||
for k, v in pairs(options) do
|
||||
self.list:AddOption(k, v)
|
||||
end
|
||||
|
||||
self.list:SizeToContents()
|
||||
self.list:Center()
|
||||
end
|
||||
|
||||
function PANEL:GetApproximateScreenHeight(entity, distanceSqr)
|
||||
return IsValid(entity) and
|
||||
(entity:BoundingRadius() * (entity:IsPlayer() and 1.5 or 1) or 0) / math.sqrt(distanceSqr) * self:GetTall() or 0
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
local entity = self.entity
|
||||
local distance = 0
|
||||
|
||||
if (IsValid(entity)) then
|
||||
local position = entity:GetPos()
|
||||
distance = LocalPlayer():GetShootPos():DistToSqr(position)
|
||||
|
||||
if (distance > 65536) then
|
||||
self:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
self.lastPosition = position
|
||||
end
|
||||
|
||||
self.desiredHeight = math.max(self.list:GetTall() + padding * 2, self:GetApproximateScreenHeight(entity, distance))
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height) -- luacheck: ignore 312
|
||||
local selfHalf = self:GetTall() * 0.5
|
||||
local entity = self.entity
|
||||
|
||||
height = self.desiredHeight + padding * 2
|
||||
width = self.blur * width
|
||||
|
||||
local y = selfHalf - height * 0.5
|
||||
|
||||
DisableClipping(true) -- for cheap blur
|
||||
render.SetScissorRect(0, y, width, y + height, true)
|
||||
if (IsValid(entity)) then
|
||||
cam.Start3D()
|
||||
ix.util.ResetStencilValues()
|
||||
render.SetStencilEnable(true)
|
||||
cam.IgnoreZ(true)
|
||||
render.SetStencilWriteMask(29)
|
||||
render.SetStencilTestMask(29)
|
||||
render.SetStencilReferenceValue(29)
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_ALWAYS)
|
||||
render.SetStencilPassOperation(STENCIL_REPLACE)
|
||||
render.SetStencilFailOperation(STENCIL_KEEP)
|
||||
render.SetStencilZFailOperation(STENCIL_KEEP)
|
||||
|
||||
entity:DrawModel()
|
||||
|
||||
render.SetStencilCompareFunction(STENCIL_NOTEQUAL)
|
||||
render.SetStencilPassOperation(STENCIL_KEEP)
|
||||
|
||||
cam.Start2D()
|
||||
ix.util.DrawBlur(self, 10)
|
||||
cam.End2D()
|
||||
cam.IgnoreZ(false)
|
||||
render.SetStencilEnable(false)
|
||||
cam.End3D()
|
||||
else
|
||||
ix.util.DrawBlur(self, 10)
|
||||
end
|
||||
render.SetScissorRect(0, 0, 0, 0, false)
|
||||
DisableClipping(false)
|
||||
|
||||
-- scissor again because 3d rendering messes with the clipping apparently?
|
||||
render.SetScissorRect(0, y, width, y + height, true)
|
||||
surface.SetDrawColor(ix.config.Get("color"))
|
||||
surface.DrawRect(ScrW() * 0.5, y + padding, 1, height - padding * 2)
|
||||
|
||||
self.list:PaintManual()
|
||||
render.SetScissorRect(0, 0, 0, 0, false)
|
||||
end
|
||||
|
||||
function PANEL:GetOverviewInfo(origin, angles)
|
||||
local entity = self.entity
|
||||
|
||||
if (IsValid(entity)) then
|
||||
local radius = entity:BoundingRadius() * (entity:IsPlayer() and 0.5 or 1)
|
||||
local center = entity:LocalToWorld(entity:OBBCenter()) + LocalPlayer():GetRight() * radius
|
||||
|
||||
return LerpAngle(self.bClosing and self.alpha or self.blur, angles, (center - origin):Angle())
|
||||
end
|
||||
|
||||
return angles
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed(code)
|
||||
if (code == MOUSE_LEFT) then
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Remove()
|
||||
if (self.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
self.bClosing = true
|
||||
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
gui.EnableScreenClicker(false)
|
||||
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
target = {alpha = 0},
|
||||
index = 2,
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.alpha * 255)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
ix.menu.panel = nil
|
||||
BaseClass.Remove(self)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
vgui.Register("ixEntityMenu", PANEL, "EditablePanel")
|
||||
933
gamemodes/helix/gamemode/core/derma/cl_generic.lua
Normal file
933
gamemodes/helix/gamemode/core/derma/cl_generic.lua
Normal file
@@ -0,0 +1,933 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- generic panels that are applicable anywhere
|
||||
|
||||
-- used for prominent text entries
|
||||
DEFINE_BASECLASS("DTextEntry")
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetPaintBackground(false)
|
||||
self:SetTextColor(color_white)
|
||||
|
||||
self.backgroundColor = Color(255, 255, 255, 25)
|
||||
end
|
||||
|
||||
function PANEL:SetFont(font)
|
||||
surface.SetFont(font)
|
||||
local _, height = surface.GetTextSize("W@")
|
||||
|
||||
self:SetTall(height)
|
||||
BaseClass.SetFont(self, font)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor)
|
||||
BaseClass.Paint(self, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixTextEntry", PANEL, "DTextEntry")
|
||||
|
||||
-- similar to a frame, but is mainly used for grouping panels together in a list
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "text", "Text", FORCE_STRING)
|
||||
AccessorFunc(PANEL, "color", "Color")
|
||||
|
||||
function PANEL:Init()
|
||||
self.text = ""
|
||||
self.paddingTop = 32
|
||||
|
||||
local skin = self:GetSkin()
|
||||
|
||||
if (skin and skin.fontCategoryBlur) then
|
||||
surface.SetFont(skin.fontCategoryBlur)
|
||||
self.paddingTop = select(2, surface.GetTextSize("W@")) + 6
|
||||
end
|
||||
|
||||
self:DockPadding(1, self.paddingTop, 1, 1)
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local height = self.paddingTop + 1
|
||||
|
||||
for _, v in ipairs(self:GetChildren()) do
|
||||
if (IsValid(v) and v:IsVisible()) then
|
||||
local _, top, _, bottom = v:GetDockMargin()
|
||||
|
||||
height = height + v:GetTall() + top + bottom
|
||||
end
|
||||
end
|
||||
|
||||
self:SetTall(height)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintCategoryPanel", self, self.text, self.color)
|
||||
end
|
||||
|
||||
vgui.Register("ixCategoryPanel", PANEL, "EditablePanel")
|
||||
|
||||
-- segmented progress bar
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "font", "Font", FORCE_STRING)
|
||||
AccessorFunc(PANEL, "barColor", "BarColor")
|
||||
AccessorFunc(PANEL, "textColor", "TextColor")
|
||||
AccessorFunc(PANEL, "progress", "Progress", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "easingType", "EasingType", FORCE_STRING)
|
||||
|
||||
function PANEL:Init()
|
||||
self.segments = {}
|
||||
self.padding = ScrH() * 0.01
|
||||
self.fraction = 0
|
||||
self.animationTime = 0.5
|
||||
self.easingType = "outQuint"
|
||||
self.progress = 0
|
||||
end
|
||||
|
||||
function PANEL:AddSegment(text)
|
||||
local id = #self.segments + 1
|
||||
|
||||
if (text:sub(1, 1) == "@") then
|
||||
text = L(text:sub(2))
|
||||
end
|
||||
|
||||
self.segments[id] = text
|
||||
return id
|
||||
end
|
||||
|
||||
function PANEL:AddSegments(...)
|
||||
local segments = {...}
|
||||
|
||||
for i = 1, #segments do
|
||||
self:AddSegment(segments[i])
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetSegments()
|
||||
return self.segments
|
||||
end
|
||||
|
||||
function PANEL:SetProgress(segment)
|
||||
self.progress = math.Clamp(segment, 0, #self.segments)
|
||||
|
||||
self:CreateAnimation(self.animationTime, {
|
||||
target = {fraction = self.progress / #self.segments},
|
||||
easing = self.easingType
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:IncrementProgress(amount)
|
||||
self:SetProgress(self.progress + (amount or 1))
|
||||
end
|
||||
|
||||
function PANEL:DecrementProgress(amount)
|
||||
self:SetProgress(self.progress - (amount or 1))
|
||||
end
|
||||
|
||||
function PANEL:GetFraction()
|
||||
return self.fraction
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
self:SetTall(draw.GetFontHeight(self.font or self:GetSkin().fontSegmentedProgress) + self.padding)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintSegmentedProgressBackground", self, width, height)
|
||||
|
||||
if (#self.segments > 0) then
|
||||
derma.SkinFunc("PaintSegmentedProgress", self, width, height)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixSegmentedProgress", PANEL, "Panel")
|
||||
|
||||
-- list of labelled information
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "labelColor", "LabelColor")
|
||||
AccessorFunc(PANEL, "textColor", "TextColor")
|
||||
AccessorFunc(PANEL, "list", "List")
|
||||
AccessorFunc(PANEL, "minWidth", "MinimumWidth", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self.label = self:Add("DLabel")
|
||||
self.label:SetFont("ixMediumFont")
|
||||
self.label:SetExpensiveShadow(1)
|
||||
self.label:SetTextColor(color_white)
|
||||
self.label:SetText("Label")
|
||||
self.label:SetContentAlignment(5)
|
||||
self.label:Dock(LEFT)
|
||||
self.label:DockMargin(0, 0, 4, 0)
|
||||
self.label:SizeToContents()
|
||||
self.label.Paint = function(this, width, height)
|
||||
derma.SkinFunc("PaintListRow", this, width, height)
|
||||
end
|
||||
|
||||
self.text = self:Add("DLabel")
|
||||
self.text:SetFont("ixMediumLightFont")
|
||||
self.text:SetTextColor(color_white)
|
||||
self.text:SetText("Text")
|
||||
self.text:SetTextInset(8, 0)
|
||||
self.text:Dock(FILL)
|
||||
self.text:DockMargin(4, 0, 0, 0)
|
||||
self.text:SizeToContents()
|
||||
self.text.Paint = function(this, width, height)
|
||||
derma.SkinFunc("PaintListRow", this, width, height)
|
||||
end
|
||||
|
||||
self:DockMargin(0, 0, 0, 8)
|
||||
|
||||
self.list = {}
|
||||
self.minWidth = 100
|
||||
end
|
||||
|
||||
function PANEL:SetRightPanel(panel)
|
||||
self.text:Remove()
|
||||
|
||||
self.text = self:Add(panel)
|
||||
self.text:Dock(FILL)
|
||||
self.text:DockMargin(8, 4, 4, 4)
|
||||
self.text:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:SetList(list, bNoAdd)
|
||||
if (!bNoAdd) then
|
||||
list[#list + 1] = self
|
||||
end
|
||||
|
||||
self.list = list
|
||||
end
|
||||
|
||||
function PANEL:UpdateLabelWidths()
|
||||
local maxWidth = self.label:GetWide()
|
||||
|
||||
for i = 1, #self.list do
|
||||
maxWidth = math.max(self.list[i]:GetLabelWidth(), maxWidth)
|
||||
end
|
||||
|
||||
maxWidth = math.max(self.minWidth, maxWidth)
|
||||
|
||||
for i = 1, #self.list do
|
||||
self.list[i]:SetLabelWidth(maxWidth)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetLabelColor(color)
|
||||
self.label:SetTextColor(color)
|
||||
end
|
||||
|
||||
function PANEL:SetTextColor(color)
|
||||
self.text:SetTextColor(color)
|
||||
end
|
||||
|
||||
function PANEL:SetLabelText(text)
|
||||
self.label:SetText(text)
|
||||
self.label:SizeToContents()
|
||||
|
||||
self:UpdateLabelWidths()
|
||||
end
|
||||
|
||||
function PANEL:SetText(text)
|
||||
self.text:SetText(text)
|
||||
self.text:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:SetLabelWidth(width)
|
||||
self.label:SetWide(width)
|
||||
end
|
||||
|
||||
function PANEL:GetLabelWidth(bWithoutMargin)
|
||||
if (!bWithoutMargin) then
|
||||
return self.label:GetWide()
|
||||
end
|
||||
|
||||
local left, _, right, _ = self.label:GetDockMargin()
|
||||
return self.label:GetWide() + left + right
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
self:SetTall(math.max(self.label:GetTall(), self.text:GetTall()) + 8)
|
||||
end
|
||||
|
||||
vgui.Register("ixListRow", PANEL, "Panel")
|
||||
|
||||
-- alternative checkbox
|
||||
DEFINE_BASECLASS("EditablePanel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "enabledText", "EnabledText", FORCE_STRING)
|
||||
AccessorFunc(PANEL, "disabledText", "DisabledText", FORCE_STRING)
|
||||
AccessorFunc(PANEL, "font", "Font", FORCE_STRING)
|
||||
AccessorFunc(PANEL, "bChecked", "Checked", FORCE_BOOL)
|
||||
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER)
|
||||
|
||||
PANEL.GetValue = PANEL.GetChecked
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetMouseInputEnabled(true)
|
||||
self:SetCursor("hand")
|
||||
|
||||
self.enabledText = L("yes"):utf8upper()
|
||||
self.disabledText = L("no"):utf8upper()
|
||||
self.font = "ixMenuButtonFont"
|
||||
self.animationTime = 0.5
|
||||
self.bChecked = false
|
||||
self.labelPadding = 8
|
||||
self.animationOffset = 0
|
||||
|
||||
self:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
BaseClass.SizeToContents(self)
|
||||
|
||||
surface.SetFont(self.font)
|
||||
self:SetWide(math.max(surface.GetTextSize(self.enabledText), surface.GetTextSize(self.disabledText)) + self.labelPadding)
|
||||
end
|
||||
|
||||
-- can be overidden to change audio params
|
||||
function PANEL:GetAudioFeedback()
|
||||
return "weapons/ar2/ar2_empty.wav", 75, self.bChecked and 150 or 125, 0.25
|
||||
end
|
||||
|
||||
function PANEL:EmitFeedback()
|
||||
LocalPlayer():EmitSound(self:GetAudioFeedback())
|
||||
end
|
||||
|
||||
function PANEL:SetChecked(bChecked, bInstant)
|
||||
self.bChecked = tobool(bChecked)
|
||||
|
||||
self:CreateAnimation(bInstant and 0 or self.animationTime, {
|
||||
index = 1,
|
||||
target = {
|
||||
animationOffset = bChecked and 1 or 0
|
||||
},
|
||||
easing = "outElastic"
|
||||
})
|
||||
|
||||
if (!bInstant) then
|
||||
self:EmitFeedback()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed(code)
|
||||
if (code == MOUSE_LEFT) then
|
||||
self:SetChecked(!self.bChecked)
|
||||
self:DoClick()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:DoClick()
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
local offset = self.animationOffset
|
||||
surface.SetFont(self.font)
|
||||
|
||||
local text = self.disabledText
|
||||
local textWidth, textHeight = surface.GetTextSize(text)
|
||||
local y = offset * -textHeight
|
||||
|
||||
surface.SetTextColor(250, 60, 60, 255)
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
|
||||
surface.DrawText(text)
|
||||
|
||||
text = self.enabledText
|
||||
y = y + textHeight
|
||||
textWidth, textHeight = surface.GetTextSize(text)
|
||||
|
||||
surface.SetTextColor(30, 250, 30, 255)
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
|
||||
surface.DrawText(text)
|
||||
end
|
||||
|
||||
vgui.Register("ixCheckBox", PANEL, "EditablePanel")
|
||||
|
||||
-- alternative num slider
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "labelPadding", "LabelPadding", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self.labelPadding = 8
|
||||
|
||||
surface.SetFont("ixMenuButtonFont")
|
||||
local totalWidth = surface.GetTextSize("999") -- start off with 3 digit width
|
||||
|
||||
self.label = self:Add("DLabel")
|
||||
self.label:Dock(RIGHT)
|
||||
self.label:SetWide(totalWidth + self.labelPadding)
|
||||
self.label:SetContentAlignment(5)
|
||||
self.label:SetFont("ixMenuButtonFont")
|
||||
self.label.Paint = function(panel, width, height)
|
||||
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
self.label.SizeToContents = function(panel)
|
||||
surface.SetFont(panel:GetFont())
|
||||
local textWidth = surface.GetTextSize(panel:GetText())
|
||||
|
||||
if (textWidth > totalWidth) then
|
||||
panel:SetWide(textWidth + self.labelPadding)
|
||||
elseif (panel:GetWide() > totalWidth + self.labelPadding) then
|
||||
panel:SetWide(totalWidth + self.labelPadding)
|
||||
end
|
||||
end
|
||||
|
||||
self.slider = self:Add("ixSlider")
|
||||
self.slider:Dock(FILL)
|
||||
self.slider:DockMargin(0, 0, 4, 0)
|
||||
self.slider.OnValueChanged = function(panel)
|
||||
self:OnValueChanged()
|
||||
end
|
||||
self.slider.OnValueUpdated = function(panel)
|
||||
self.label:SetText(string.format("%0." .. tostring(panel:GetDecimals()) .. "f", tostring(panel:GetValue())))
|
||||
self.label:SizeToContents()
|
||||
|
||||
self:OnValueUpdated()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetLabel()
|
||||
return self.label
|
||||
end
|
||||
|
||||
function PANEL:GetSlider()
|
||||
return self.slider
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value, bNoNotify)
|
||||
value = tonumber(value) or self.slider:GetMin()
|
||||
|
||||
self.slider:SetValue(value, bNoNotify)
|
||||
self.label:SetText(string.format("%0." .. tostring(self:GetDecimals()) .. "f", tostring(self.slider:GetValue())))
|
||||
self.label:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return self.slider:GetValue()
|
||||
end
|
||||
|
||||
function PANEL:GetFraction()
|
||||
return self.slider:GetFraction()
|
||||
end
|
||||
|
||||
function PANEL:GetVisualFraction()
|
||||
return self.slider:GetVisualFraction()
|
||||
end
|
||||
|
||||
function PANEL:SetMin(value)
|
||||
self.slider:SetMin(value)
|
||||
end
|
||||
|
||||
function PANEL:SetMax(value)
|
||||
self.slider:SetMax(value)
|
||||
end
|
||||
|
||||
function PANEL:GetMin()
|
||||
return self.slider:GetMin()
|
||||
end
|
||||
|
||||
function PANEL:GetMax()
|
||||
return self.slider:GetMax()
|
||||
end
|
||||
|
||||
function PANEL:SetDecimals(value)
|
||||
self.slider:SetDecimals(value)
|
||||
end
|
||||
|
||||
function PANEL:GetDecimals()
|
||||
return self.slider:GetDecimals()
|
||||
end
|
||||
|
||||
-- called when changed by user
|
||||
function PANEL:OnValueChanged()
|
||||
end
|
||||
|
||||
-- called when changed while dragging bar
|
||||
function PANEL:OnValueUpdated()
|
||||
end
|
||||
|
||||
vgui.Register("ixNumSlider", PANEL, "Panel")
|
||||
|
||||
-- alternative slider
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "bDragging", "Dragging", FORCE_BOOL)
|
||||
AccessorFunc(PANEL, "min", "Min", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "max", "Max", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "decimals", "Decimals", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self.min = 0
|
||||
self.max = 10
|
||||
self.value = 0
|
||||
self.visualValue = 0
|
||||
self.decimals = 0
|
||||
|
||||
self:SetCursor("hand")
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value, bNoNotify)
|
||||
self.value = math.Clamp(math.Round(tonumber(value) or self.min, self.decimals), self.min, self.max)
|
||||
self:ValueUpdated(bNoNotify)
|
||||
|
||||
if (!bNoNotify) then
|
||||
self:OnValueChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return self.value
|
||||
end
|
||||
|
||||
function PANEL:GetFraction()
|
||||
return math.Remap(self.value, self.min, self.max, 0, 1)
|
||||
end
|
||||
|
||||
function PANEL:GetVisualFraction()
|
||||
return math.Remap(self.visualValue, self.min, self.max, 0, 1)
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed(key)
|
||||
if (key == MOUSE_LEFT) then
|
||||
self.bDragging = true
|
||||
self:MouseCapture(true)
|
||||
|
||||
self:OnCursorMoved(self:CursorPos())
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnMouseReleased(key)
|
||||
if (self.bDragging) then
|
||||
self:OnValueChanged()
|
||||
end
|
||||
|
||||
self.bDragging = false
|
||||
self:MouseCapture(false)
|
||||
end
|
||||
|
||||
function PANEL:OnCursorMoved(x, y)
|
||||
if (!self.bDragging) then
|
||||
return
|
||||
end
|
||||
|
||||
x = math.Clamp(x, 0, self:GetWide())
|
||||
local oldValue = self.value
|
||||
|
||||
self.value = math.Clamp(math.Round(
|
||||
math.Remap(x / self:GetWide(), 0, 1, self.min, self.max), self.decimals
|
||||
), self.min, self.max)
|
||||
|
||||
self:CreateAnimation(0.5, {
|
||||
index = 1,
|
||||
target = {visualValue = self.value},
|
||||
easing = "outQuint"
|
||||
})
|
||||
|
||||
if (self.value != oldValue) then
|
||||
self:ValueUpdated()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnValueChanged()
|
||||
end
|
||||
|
||||
function PANEL:ValueUpdated(bNoNotify)
|
||||
self:CreateAnimation(bNoNotify and 0 or 0.5, {
|
||||
index = 1,
|
||||
target = {visualValue = self.value},
|
||||
easing = "outQuint"
|
||||
})
|
||||
|
||||
if (!bNoNotify) then
|
||||
self:OnValueUpdated()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnValueUpdated()
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintHelixSlider", self, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixSlider", PANEL, "EditablePanel")
|
||||
|
||||
--- Alternative to DLabel that adds extra functionality.
|
||||
-- This panel is meant for drawing single-line text. It can add extra kerning (spaces between letters), and it can forcefully
|
||||
-- scale the text down to fit the current width, without cutting off any letters. Text scaling is most useful when docking this
|
||||
-- this panel without knowing what the width could be. For example, text scaling is used for the character name in the character
|
||||
-- status menu.
|
||||
-- local label = vgui.Create("ixLabel")
|
||||
-- label:SetText("hello world")
|
||||
-- label:SetFont("ixMenuButtonHugeFont")
|
||||
-- label:SetContentAlignment(5)
|
||||
-- label:SetTextColor(Color(255, 255, 255, 255))
|
||||
-- label:SetBackgroundColor(Color(200, 30, 30, 255))
|
||||
-- label:SetPadding(8)
|
||||
-- label:SetScaleWidth(true)
|
||||
-- label:SizeToContents()
|
||||
-- @panel ixLabel
|
||||
PANEL = {}
|
||||
|
||||
--- Sets the text for this label to display.
|
||||
-- @realm client
|
||||
-- @string text Text to display
|
||||
-- @function SetText
|
||||
|
||||
--- Returns the current text for this panel.
|
||||
-- @realm client
|
||||
-- @treturn string Current text
|
||||
-- @function GetText
|
||||
AccessorFunc(PANEL, "text", "Text", FORCE_STRING)
|
||||
|
||||
--- Sets the color of the text to use when drawing.
|
||||
-- @realm client
|
||||
-- @color color New color to use
|
||||
-- @function SetTextColor
|
||||
|
||||
--- Returns the current text color for this panel.
|
||||
-- @realm client
|
||||
-- @treturn color Current text color
|
||||
-- @function GetTextColor
|
||||
AccessorFunc(PANEL, "color", "TextColor")
|
||||
|
||||
--- Sets the color of the background to draw behind the text.
|
||||
-- @realm client
|
||||
-- @color color New color to use
|
||||
-- @function SetBackgroundColor
|
||||
|
||||
--- Returns the current background color for this panel.
|
||||
-- @realm client
|
||||
-- @treturn color Current background color
|
||||
-- @function GetBackgroundColor
|
||||
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
|
||||
|
||||
--- Sets the spacing between each character of the text in pixels. Set to `0` to disable. Kerning is disabled by default.
|
||||
-- @realm client
|
||||
-- @number kerning How far apart to draw each letter
|
||||
-- @function SetKerning
|
||||
|
||||
--- Returns the current kerning for this panel.
|
||||
-- @realm client
|
||||
-- @treturn number Current kerning
|
||||
-- @function GetKerning
|
||||
AccessorFunc(PANEL, "kerning", "Kerning", FORCE_NUMBER)
|
||||
|
||||
--- Sets the font used to draw the text.
|
||||
-- @realm client
|
||||
-- @string font Name of the font to use
|
||||
-- @function SetFont
|
||||
|
||||
--- Returns the current font for this panel.
|
||||
-- @realm client
|
||||
-- @treturn string Name of current font
|
||||
-- @function GetFont
|
||||
AccessorFunc(PANEL, "font", "Font", FORCE_STRING)
|
||||
|
||||
--- Changes how the text is aligned when drawing. Valid content alignment values include numbers `1` through `9`. Each number's
|
||||
-- corresponding alignment is based on its position on a numpad. For example, `1` is bottom-left, `5` is centered, `9` is
|
||||
-- top-right, etc.
|
||||
-- @realm client
|
||||
-- @number alignment Alignment to use
|
||||
-- @function SetContentAlignment
|
||||
|
||||
--- Returns the current content alignment for this panel.
|
||||
-- @realm client
|
||||
-- @treturn number Current content alignment
|
||||
-- @function GetContentAlignment
|
||||
AccessorFunc(PANEL, "contentAlignment", "ContentAlignment", FORCE_NUMBER)
|
||||
|
||||
--- Whether or not to scale the width of the text down to fit the width of this panel, if needed.
|
||||
-- @realm client
|
||||
-- @bool bScale Whether or not to scale
|
||||
-- @function SetScaleWidth
|
||||
|
||||
--- Returns whether or not this panel will scale its text down to fit its width.
|
||||
-- @realm client
|
||||
-- @treturn bool Whether or not this panel will scale its text
|
||||
-- @function GetScaleWidth
|
||||
AccessorFunc(PANEL, "bScaleWidth", "ScaleWidth", FORCE_BOOL)
|
||||
|
||||
--- How much spacing to use around the text when its drawn. This uses uniform padding on the top, left, right, and bottom of
|
||||
-- this panel.
|
||||
-- @realm client
|
||||
-- @number padding Padding to use
|
||||
-- @function SetPadding
|
||||
|
||||
--- Returns how much padding this panel has around its text.
|
||||
-- @realm client
|
||||
-- @treturn number Current padding
|
||||
-- @function GetPadding
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self.text = ""
|
||||
self.color = color_white
|
||||
self.backgroundColor = Color(255, 255, 255, 0)
|
||||
self.kerning = 0
|
||||
self.font = "DermaDefault"
|
||||
self.scaledFont = "DermaDefault"
|
||||
self.contentAlignment = 5
|
||||
self.bScaleWidth = false
|
||||
self.padding = 0
|
||||
|
||||
self.shadowDistance = 0
|
||||
self.bCurrentlyScaling = false
|
||||
end
|
||||
|
||||
function PANEL:SetText(text)
|
||||
self.text = tostring(text)
|
||||
end
|
||||
|
||||
function PANEL:SetFont(font)
|
||||
self.font = font
|
||||
self.scaledFont = font
|
||||
end
|
||||
|
||||
--- Sets the drop shadow to draw behind the text.
|
||||
-- @realm client
|
||||
-- @number distance How far away to draw the shadow in pixels. Set to `0` to disable
|
||||
-- @color[opt] color Color of the shadow. Defaults to a dimmed version of the text color
|
||||
function PANEL:SetDropShadow(distance, color)
|
||||
self.shadowDistance = distance or 1
|
||||
self.shadowColor = color or ix.util.DimColor(self.color, 0.5)
|
||||
end
|
||||
|
||||
PANEL.SetExpensiveShadow = PANEL.SetDropShadow -- aliasing for easier conversion from DLabels
|
||||
|
||||
--- Returns the X and Y location of the text taking into account the text alignment and padding.
|
||||
-- @realm client
|
||||
-- @internal
|
||||
-- @number width Width of the panel
|
||||
-- @number height Height of the panel
|
||||
-- @number textWidth Width of the text
|
||||
-- @number textHeight Height of the text
|
||||
-- @treturn number X location to draw the text
|
||||
-- @treturn number Y location to draw the text
|
||||
function PANEL:CalculateAlignment(width, height, textWidth, textHeight)
|
||||
local alignment = self.contentAlignment
|
||||
local x, y
|
||||
|
||||
if (self.bCurrentlyScaling) then
|
||||
-- if the text is currently being scaled down, then it's always centered
|
||||
x = width * 0.5 - textWidth * 0.5
|
||||
else
|
||||
-- x alignment
|
||||
if (alignment == 7 or alignment == 4 or alignment == 1) then
|
||||
-- left
|
||||
x = self.padding
|
||||
elseif (alignment == 8 or alignment == 5 or alignment == 2) then
|
||||
-- center
|
||||
x = width * 0.5 - textWidth * 0.5
|
||||
elseif (alignment == 9 or alignment == 6 or alignment == 3) then
|
||||
x = width - textWidth - self.padding
|
||||
end
|
||||
end
|
||||
|
||||
-- y alignment
|
||||
if (alignment <= 3) then
|
||||
-- bottom
|
||||
y = height - textHeight - self.padding
|
||||
elseif (alignment <= 6) then
|
||||
-- center
|
||||
y = height * 0.5 - textHeight * 0.5
|
||||
else
|
||||
-- top
|
||||
y = self.padding
|
||||
end
|
||||
|
||||
return x, y
|
||||
end
|
||||
|
||||
--- Draws the current text with the current kerning.
|
||||
-- @realm client
|
||||
-- @internal
|
||||
-- @number width Width of the panel
|
||||
-- @number height Height of the panel
|
||||
function PANEL:DrawKernedText(width, height)
|
||||
local contentWidth, contentHeight = self:GetContentSize()
|
||||
local x, y = self:CalculateAlignment(width, height, contentWidth, contentHeight)
|
||||
|
||||
for i = 1, self.text:utf8len() do
|
||||
local character = self.text:utf8sub(i, i)
|
||||
local textWidth, _ = surface.GetTextSize(character)
|
||||
local kerning = i == 1 and 0 or self.kerning
|
||||
local shadowDistance = self.shadowDistance
|
||||
|
||||
-- shadow
|
||||
if (self.shadowDistance > 0) then
|
||||
surface.SetTextColor(self.shadowColor)
|
||||
surface.SetTextPos(x + kerning + shadowDistance, y + shadowDistance)
|
||||
surface.DrawText(character)
|
||||
end
|
||||
|
||||
-- character
|
||||
surface.SetTextColor(self.color)
|
||||
surface.SetTextPos(x + kerning, y)
|
||||
surface.DrawText(character)
|
||||
|
||||
x = x + textWidth + kerning
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws the current text.
|
||||
-- @realm client
|
||||
-- @internal
|
||||
-- @number width Width of the panel
|
||||
-- @number height Height of the panel
|
||||
function PANEL:DrawText(width, height)
|
||||
local textWidth, textHeight = surface.GetTextSize(self.text)
|
||||
local x, y = self:CalculateAlignment(width, height, textWidth, textHeight)
|
||||
|
||||
-- shadow
|
||||
if (self.shadowDistance > 0) then
|
||||
surface.SetTextColor(self.shadowColor)
|
||||
surface.SetTextPos(x + self.shadowDistance, y + self.shadowDistance)
|
||||
surface.DrawText(self.text)
|
||||
end
|
||||
|
||||
-- text
|
||||
surface.SetTextColor(self.color)
|
||||
surface.SetTextPos(x, y)
|
||||
surface.DrawText(self.text)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetFont(self.font)
|
||||
surface.SetDrawColor(self.backgroundColor)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
if (self.bScaleWidth) then
|
||||
local contentWidth, contentHeight = self:GetContentSize()
|
||||
|
||||
if (contentWidth > (width - self.padding * 2)) then
|
||||
local x, y = self:LocalToScreen(self:GetPos())
|
||||
local scale = width / (contentWidth + self.padding * 2)
|
||||
local translation = Vector(x + width * 0.5, y - contentHeight * 0.5 + self.padding, 0)
|
||||
local matrix = Matrix()
|
||||
|
||||
matrix:Translate(translation)
|
||||
matrix:Scale(Vector(scale, scale, 0))
|
||||
matrix:Translate(-translation)
|
||||
|
||||
cam.PushModelMatrix(matrix, true)
|
||||
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
|
||||
DisableClipping(true)
|
||||
|
||||
self.bCurrentlyScaling = true
|
||||
end
|
||||
end
|
||||
|
||||
if (self.kerning > 0) then
|
||||
self:DrawKernedText(width, height)
|
||||
else
|
||||
self:DrawText(width, height)
|
||||
end
|
||||
|
||||
if (self.bCurrentlyScaling) then
|
||||
DisableClipping(false)
|
||||
render.PopFilterMin()
|
||||
cam.PopModelMatrix()
|
||||
|
||||
self.bCurrentlyScaling = false
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the size of the text, taking into account the current kerning.
|
||||
-- @realm client
|
||||
-- @bool[opt=false] bCalculate Whether or not to recalculate the content size instead of using the cached copy
|
||||
-- @treturn number Width of the text
|
||||
-- @treturn number Height of the text
|
||||
function PANEL:GetContentSize(bCalculate)
|
||||
if (bCalculate or !self.contentSize) then
|
||||
surface.SetFont(self.font)
|
||||
|
||||
if (self.kerning > 0) then
|
||||
local width = 0
|
||||
|
||||
for i = 1, self.text:utf8len() do
|
||||
local textWidth, _ = surface.GetTextSize(self.text:utf8sub(i, i))
|
||||
width = width + textWidth + self.kerning
|
||||
end
|
||||
|
||||
self.contentSize = {width, draw.GetFontHeight(self.font)}
|
||||
else
|
||||
self.contentSize = {surface.GetTextSize(self.text)}
|
||||
end
|
||||
end
|
||||
|
||||
return self.contentSize[1], self.contentSize[2]
|
||||
end
|
||||
|
||||
--- Sets the size of the panel to fit the content size with the current padding. The content size is recalculated when this
|
||||
-- method is called.
|
||||
-- @realm client
|
||||
function PANEL:SizeToContents()
|
||||
local contentWidth, contentHeight = self:GetContentSize(true)
|
||||
|
||||
self:SetSize(contentWidth + self.padding * 2, contentHeight + self.padding * 2)
|
||||
end
|
||||
|
||||
vgui.Register("ixLabel", PANEL, "Panel")
|
||||
|
||||
-- text entry with icon
|
||||
DEFINE_BASECLASS("ixTextEntry")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "icon", "Icon", FORCE_STRING)
|
||||
AccessorFunc(PANEL, "iconColor", "IconColor")
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetIcon("V")
|
||||
self:SetFont("ixSmallTitleFont")
|
||||
|
||||
self.iconColor = Color(200, 200, 200, 160)
|
||||
end
|
||||
|
||||
function PANEL:SetIcon(newIcon)
|
||||
surface.SetFont("ixSmallTitleIcons")
|
||||
|
||||
self.iconWidth, self.iconHeight = surface.GetTextSize(newIcon)
|
||||
self.icon = newIcon
|
||||
|
||||
self:DockMargin(self.iconWidth + 4, 0, 0, 8)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
BaseClass.Paint(self, width, height)
|
||||
|
||||
-- there's no inset for text entries so we'll have to get creative
|
||||
DisableClipping(true)
|
||||
surface.SetDrawColor(self:GetBackgroundColor())
|
||||
surface.DrawRect(-self.iconWidth - 4, 0, self.iconWidth + 4, height)
|
||||
|
||||
surface.SetFont("ixSmallTitleIcons")
|
||||
surface.SetTextColor(self.iconColor)
|
||||
surface.SetTextPos(-self.iconWidth - 2, 0)
|
||||
surface.DrawText(self:GetIcon())
|
||||
DisableClipping(false)
|
||||
end
|
||||
|
||||
vgui.Register("ixIconTextEntry", PANEL, "ixTextEntry")
|
||||
379
gamemodes/helix/gamemode/core/derma/cl_help.lua
Normal file
379
gamemodes/helix/gamemode/core/derma/cl_help.lua
Normal file
@@ -0,0 +1,379 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local backgroundColor = Color(0, 0, 0, 66)
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetWide(180)
|
||||
self:Dock(LEFT)
|
||||
|
||||
self.maxWidth = ScrW() * 0.2
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetDrawColor(backgroundColor)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local width = 0
|
||||
|
||||
for _, v in ipairs(self:GetChildren()) do
|
||||
width = math.max(width, v:GetWide())
|
||||
end
|
||||
|
||||
self:SetSize(math.max(32, math.min(width, self.maxWidth)), self:GetParent():GetTall())
|
||||
end
|
||||
|
||||
vgui.Register("ixHelpMenuCategories", PANEL, "EditablePanel")
|
||||
|
||||
-- help menu
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
|
||||
self.categories = {}
|
||||
self.categorySubpanels = {}
|
||||
self.categoryPanel = self:Add("ixHelpMenuCategories")
|
||||
|
||||
self.canvasPanel = self:Add("EditablePanel")
|
||||
self.canvasPanel:Dock(FILL)
|
||||
|
||||
self.idlePanel = self.canvasPanel:Add("Panel")
|
||||
self.idlePanel:Dock(FILL)
|
||||
self.idlePanel:DockMargin(8, 0, 0, 0)
|
||||
self.idlePanel.Paint = function(_, width, height)
|
||||
surface.SetDrawColor(backgroundColor)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
derma.SkinFunc("DrawHelixCurved", width * 0.5, height * 0.5, width * 0.25)
|
||||
|
||||
surface.SetFont("ixIntroSubtitleFont")
|
||||
local text = L("helix"):lower()
|
||||
local textWidth, textHeight = surface.GetTextSize(text)
|
||||
|
||||
surface.SetTextColor(color_white)
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.5 - textHeight * 0.75)
|
||||
surface.DrawText(text)
|
||||
|
||||
surface.SetFont("ixMediumLightFont")
|
||||
text = L("helpIdle")
|
||||
local infoWidth, _ = surface.GetTextSize(text)
|
||||
|
||||
surface.SetTextColor(color_white)
|
||||
surface.SetTextPos(width * 0.5 - infoWidth * 0.5, height * 0.5 + textHeight * 0.25)
|
||||
surface.DrawText(text)
|
||||
end
|
||||
|
||||
local categories = {}
|
||||
hook.Run("PopulateHelpMenu", categories)
|
||||
|
||||
for k, v in SortedPairs(categories) do
|
||||
if (!isstring(k)) then
|
||||
ErrorNoHalt("expected string for help menu key\n")
|
||||
continue
|
||||
elseif (!isfunction(v)) then
|
||||
ErrorNoHalt(string.format("expected function for help menu entry '%s'\n", k))
|
||||
continue
|
||||
end
|
||||
|
||||
self:AddCategory(k)
|
||||
self.categories[k] = v
|
||||
end
|
||||
|
||||
self.categoryPanel:SizeToContents()
|
||||
|
||||
if (ix.gui.lastHelpMenuTab) then
|
||||
self:OnCategorySelected(ix.gui.lastHelpMenuTab)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:AddCategory(name)
|
||||
local button = self.categoryPanel:Add("ixMenuButton")
|
||||
button:SetText(L(name))
|
||||
button:SizeToContents()
|
||||
-- @todo don't hardcode this but it's the only panel that needs docking at the bottom so it'll do for now
|
||||
button:Dock(name == "credits" and BOTTOM or TOP)
|
||||
button.DoClick = function()
|
||||
self:OnCategorySelected(name)
|
||||
end
|
||||
|
||||
local panel = self.canvasPanel:Add("DScrollPanel")
|
||||
panel:SetVisible(false)
|
||||
panel:Dock(FILL)
|
||||
panel:DockMargin(8, 0, 0, 0)
|
||||
panel:GetCanvas():DockPadding(8, 8, 8, 8)
|
||||
|
||||
panel.Paint = function(_, width, height)
|
||||
surface.SetDrawColor(backgroundColor)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
-- reverts functionality back to a standard panel in the case that a category will manage its own scrolling
|
||||
panel.DisableScrolling = function()
|
||||
panel:GetCanvas():SetVisible(false)
|
||||
panel:GetVBar():SetVisible(false)
|
||||
panel.OnChildAdded = function() end
|
||||
end
|
||||
|
||||
self.categorySubpanels[name] = panel
|
||||
end
|
||||
|
||||
function PANEL:OnCategorySelected(name)
|
||||
local panel = self.categorySubpanels[name]
|
||||
|
||||
if (!IsValid(panel)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (!panel.bPopulated) then
|
||||
self.categories[name](panel)
|
||||
panel.bPopulated = true
|
||||
end
|
||||
|
||||
if (IsValid(self.activeCategory)) then
|
||||
self.activeCategory:SetVisible(false)
|
||||
end
|
||||
|
||||
panel:SetVisible(true)
|
||||
self.idlePanel:SetVisible(false)
|
||||
|
||||
self.activeCategory = panel
|
||||
ix.gui.lastHelpMenuTab = name
|
||||
end
|
||||
|
||||
vgui.Register("ixHelpMenu", PANEL, "EditablePanel")
|
||||
|
||||
local function DrawHelix(width, height, color) -- luacheck: ignore 211
|
||||
local segments = 76
|
||||
local radius = math.min(width, height) * 0.375
|
||||
|
||||
surface.SetTexture(-1)
|
||||
|
||||
for i = 1, math.ceil(segments) do
|
||||
local angle = math.rad((i / segments) * -360)
|
||||
local x = width * 0.5 + math.sin(angle + math.pi * 2) * radius
|
||||
local y = height * 0.5 + math.cos(angle + math.pi * 2) * radius
|
||||
local barOffset = math.sin(SysTime() + i * 0.5)
|
||||
local barHeight = barOffset * radius * 0.25
|
||||
|
||||
if (barOffset > 0) then
|
||||
surface.SetDrawColor(color)
|
||||
else
|
||||
surface.SetDrawColor(color.r * 0.5, color.g * 0.5, color.b * 0.5, color.a)
|
||||
end
|
||||
|
||||
surface.DrawTexturedRectRotated(x, y, 4, barHeight, math.deg(angle))
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixHelpMenu", function(tabs)
|
||||
tabs["help"] = function(container)
|
||||
container:Add("ixHelpMenu")
|
||||
end
|
||||
end)
|
||||
|
||||
hook.Add("PopulateHelpMenu", "ixHelpMenu", function(tabs)
|
||||
tabs["commands"] = function(container)
|
||||
-- info text
|
||||
local info = container:Add("DLabel")
|
||||
info:SetFont("ixSmallFont")
|
||||
info:SetText(L("helpCommands"))
|
||||
info:SetContentAlignment(5)
|
||||
info:SetTextColor(color_white)
|
||||
info:SetExpensiveShadow(1, color_black)
|
||||
info:Dock(TOP)
|
||||
info:DockMargin(0, 0, 0, 8)
|
||||
info:SizeToContents()
|
||||
info:SetTall(info:GetTall() + 16)
|
||||
|
||||
info.Paint = function(_, width, height)
|
||||
surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
-- commands
|
||||
for uniqueID, command in SortedPairs(ix.command.list) do
|
||||
if (command.OnCheckAccess and !command:OnCheckAccess(LocalPlayer())) then
|
||||
continue
|
||||
end
|
||||
|
||||
local bIsAlias = false
|
||||
local aliasText = ""
|
||||
|
||||
-- we want to show aliases in the same entry for better readability
|
||||
if (command.alias) then
|
||||
local alias = istable(command.alias) and command.alias or {command.alias}
|
||||
|
||||
for _, v in ipairs(alias) do
|
||||
if (v:lower() == uniqueID) then
|
||||
bIsAlias = true
|
||||
break
|
||||
end
|
||||
|
||||
aliasText = aliasText .. ", /" .. v
|
||||
end
|
||||
|
||||
if (bIsAlias) then
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
-- command name
|
||||
local title = container:Add("DLabel")
|
||||
title:SetFont("ixMediumLightFont")
|
||||
title:SetText("/" .. command.name .. aliasText)
|
||||
title:Dock(TOP)
|
||||
title:SetTextColor(ix.config.Get("color"))
|
||||
title:SetExpensiveShadow(1, color_black)
|
||||
title:SizeToContents()
|
||||
|
||||
-- syntax
|
||||
local syntaxText = command.syntax
|
||||
local syntax
|
||||
|
||||
if (syntaxText != "" and syntaxText != "[none]") then
|
||||
syntax = container:Add("DLabel")
|
||||
syntax:SetFont("ixMediumLightFont")
|
||||
syntax:SetText(syntaxText)
|
||||
syntax:Dock(TOP)
|
||||
syntax:SetTextColor(color_white)
|
||||
syntax:SetExpensiveShadow(1, color_black)
|
||||
syntax:SetWrap(true)
|
||||
syntax:SetAutoStretchVertical(true)
|
||||
syntax:SizeToContents()
|
||||
end
|
||||
|
||||
-- description
|
||||
local descriptionText = command:GetDescription()
|
||||
|
||||
if (descriptionText != "") then
|
||||
local description = container:Add("DLabel")
|
||||
description:SetFont("ixSmallFont")
|
||||
description:SetText(descriptionText)
|
||||
description:Dock(TOP)
|
||||
description:SetTextColor(color_white)
|
||||
description:SetExpensiveShadow(1, color_black)
|
||||
description:SetWrap(true)
|
||||
description:SetAutoStretchVertical(true)
|
||||
description:SizeToContents()
|
||||
description:DockMargin(0, 0, 0, 8)
|
||||
elseif (syntax) then
|
||||
syntax:DockMargin(0, 0, 0, 8)
|
||||
else
|
||||
title:DockMargin(0, 0, 0, 8)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tabs["flags"] = function(container)
|
||||
-- info text
|
||||
local info = container:Add("DLabel")
|
||||
info:SetFont("ixSmallFont")
|
||||
info:SetText(L("helpFlags"))
|
||||
info:SetContentAlignment(5)
|
||||
info:SetTextColor(color_white)
|
||||
info:SetExpensiveShadow(1, color_black)
|
||||
info:Dock(TOP)
|
||||
info:DockMargin(0, 0, 0, 8)
|
||||
info:SizeToContents()
|
||||
info:SetTall(info:GetTall() + 16)
|
||||
|
||||
info.Paint = function(_, width, height)
|
||||
surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", info), 160))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
-- flags
|
||||
for k, v in SortedPairs(ix.flag.list) do
|
||||
local background = ColorAlpha(
|
||||
LocalPlayer():GetCharacter():HasFlags(k) and derma.GetColor("Success", info) or derma.GetColor("Error", info), 88
|
||||
)
|
||||
|
||||
local panel = container:Add("Panel")
|
||||
panel:Dock(TOP)
|
||||
panel:DockMargin(0, 0, 0, 8)
|
||||
panel:DockPadding(4, 4, 4, 4)
|
||||
panel.Paint = function(_, width, height)
|
||||
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, background)
|
||||
end
|
||||
|
||||
local flag = panel:Add("DLabel")
|
||||
flag:SetFont("ixMonoMediumFont")
|
||||
flag:SetText(string.format("[%s]", k))
|
||||
flag:Dock(LEFT)
|
||||
flag:SetTextColor(color_white)
|
||||
flag:SetExpensiveShadow(1, color_black)
|
||||
flag:SetTextInset(4, 0)
|
||||
flag:SizeToContents()
|
||||
flag:SetTall(flag:GetTall() + 8)
|
||||
|
||||
local description = panel:Add("DLabel")
|
||||
description:SetFont("ixMediumLightFont")
|
||||
description:SetText(v.description)
|
||||
description:Dock(FILL)
|
||||
description:SetTextColor(color_white)
|
||||
description:SetExpensiveShadow(1, color_black)
|
||||
description:SetTextInset(8, 0)
|
||||
description:SizeToContents()
|
||||
description:SetTall(description:GetTall() + 8)
|
||||
|
||||
panel:SizeToChildren(false, true)
|
||||
end
|
||||
end
|
||||
|
||||
tabs["plugins"] = function(container)
|
||||
for _, v in SortedPairsByMemberValue(ix.plugin.list, "name") do
|
||||
-- name
|
||||
local title = container:Add("DLabel")
|
||||
title:SetFont("ixMediumLightFont")
|
||||
title:SetText(v.name or "Unknown")
|
||||
title:Dock(TOP)
|
||||
title:SetTextColor(ix.config.Get("color"))
|
||||
title:SetExpensiveShadow(1, color_black)
|
||||
title:SizeToContents()
|
||||
|
||||
-- author
|
||||
local author = container:Add("DLabel")
|
||||
author:SetFont("ixSmallFont")
|
||||
author:SetText(string.format("%s: %s", L("author"), v.author))
|
||||
author:Dock(TOP)
|
||||
author:SetTextColor(color_white)
|
||||
author:SetExpensiveShadow(1, color_black)
|
||||
author:SetWrap(true)
|
||||
author:SetAutoStretchVertical(true)
|
||||
author:SizeToContents()
|
||||
|
||||
-- description
|
||||
local descriptionText = v.description
|
||||
|
||||
if (descriptionText != "") then
|
||||
local description = container:Add("DLabel")
|
||||
description:SetFont("ixSmallFont")
|
||||
description:SetText(descriptionText)
|
||||
description:Dock(TOP)
|
||||
description:SetTextColor(color_white)
|
||||
description:SetExpensiveShadow(1, color_black)
|
||||
description:SetWrap(true)
|
||||
description:SetAutoStretchVertical(true)
|
||||
description:SizeToContents()
|
||||
description:DockMargin(0, 0, 0, 8)
|
||||
else
|
||||
author:DockMargin(0, 0, 0, 8)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
283
gamemodes/helix/gamemode/core/derma/cl_information.lua
Normal file
283
gamemodes/helix/gamemode/core/derma/cl_information.lua
Normal file
@@ -0,0 +1,283 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
local parent = self:GetParent()
|
||||
|
||||
self:SetSize(parent:GetWide() * 0.6, parent:GetTall())
|
||||
self:Dock(RIGHT)
|
||||
self:DockMargin(0, ScrH() * 0.05, 0, 0)
|
||||
|
||||
self.VBar:SetWide(0)
|
||||
|
||||
-- entry setup
|
||||
local suppress = {}
|
||||
hook.Run("CanCreateCharacterInfo", suppress)
|
||||
|
||||
if (!suppress.time) then
|
||||
local format = "%A, %B %d, %Y. %H:%M"
|
||||
|
||||
self.time = self:Add("DLabel")
|
||||
self.time:SetFont("ixMediumFont")
|
||||
self.time:SetTall(28)
|
||||
self.time:SetContentAlignment(5)
|
||||
self.time:Dock(TOP)
|
||||
self.time:SetTextColor(color_white)
|
||||
self.time:SetExpensiveShadow(1, Color(0, 0, 0, 150))
|
||||
self.time:DockMargin(0, 0, 0, 32)
|
||||
self.time:SetText(ix.date.GetFormatted(format))
|
||||
self.time.Think = function(this)
|
||||
if ((this.nextTime or 0) < CurTime()) then
|
||||
this:SetText(ix.date.GetFormatted(format))
|
||||
this.nextTime = CurTime() + 0.5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (!suppress.name) then
|
||||
self.name = self:Add("ixLabel")
|
||||
self.name:Dock(TOP)
|
||||
self.name:DockMargin(0, 0, 0, 8)
|
||||
self.name:SetFont("ixMenuButtonHugeFont")
|
||||
self.name:SetContentAlignment(5)
|
||||
self.name:SetTextColor(color_white)
|
||||
self.name:SetPadding(8)
|
||||
self.name:SetScaleWidth(true)
|
||||
end
|
||||
|
||||
if (!suppress.description) then
|
||||
self.description = self:Add("DLabel")
|
||||
self.description:Dock(TOP)
|
||||
self.description:DockMargin(0, 0, 0, 8)
|
||||
self.description:SetFont("ixMenuButtonFont")
|
||||
self.description:SetTextColor(color_white)
|
||||
self.description:SetContentAlignment(5)
|
||||
self.description:SetMouseInputEnabled(true)
|
||||
self.description:SetCursor("hand")
|
||||
|
||||
self.description.Paint = function(this, width, height)
|
||||
surface.SetDrawColor(0, 0, 0, 150)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
self.description.OnMousePressed = function(this, code)
|
||||
if (code == MOUSE_LEFT) then
|
||||
ix.command.Send("CharDesc")
|
||||
|
||||
if (IsValid(ix.gui.menu)) then
|
||||
ix.gui.menu:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.description.SizeToContents = function(this)
|
||||
if (this.bWrap) then
|
||||
-- sizing contents after initial wrapping does weird things so we'll just ignore (lol)
|
||||
return
|
||||
end
|
||||
|
||||
local width, height = this:GetContentSize()
|
||||
|
||||
if (width > self:GetWide()) then
|
||||
this:SetWide(self:GetWide())
|
||||
this:SetTextInset(16, 8)
|
||||
this:SetWrap(true)
|
||||
this:SizeToContentsY()
|
||||
this:SetTall(this:GetTall() + 16) -- eh
|
||||
|
||||
-- wrapping doesn't like middle alignment so we'll do top-center
|
||||
self.description:SetContentAlignment(8)
|
||||
this.bWrap = true
|
||||
else
|
||||
this:SetSize(width + 16, height + 16)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (!suppress.characterInfo) then
|
||||
self.characterInfo = self:Add("Panel")
|
||||
self.characterInfo.list = {}
|
||||
self.characterInfo:Dock(TOP) -- no dock margin because this is handled by ixListRow
|
||||
self.characterInfo.SizeToContents = function(this)
|
||||
local height = 0
|
||||
|
||||
for _, v in ipairs(this:GetChildren()) do
|
||||
if (IsValid(v) and v:IsVisible()) then
|
||||
local _, top, _, bottom = v:GetDockMargin()
|
||||
height = height + v:GetTall() + top + bottom
|
||||
end
|
||||
end
|
||||
|
||||
this:SetTall(height)
|
||||
end
|
||||
|
||||
if (!suppress.faction) then
|
||||
self.faction = self.characterInfo:Add("ixListRow")
|
||||
self.faction:SetList(self.characterInfo.list)
|
||||
self.faction:Dock(TOP)
|
||||
end
|
||||
|
||||
if (!suppress.class) then
|
||||
self.class = self.characterInfo:Add("ixListRow")
|
||||
self.class:SetList(self.characterInfo.list)
|
||||
self.class:Dock(TOP)
|
||||
end
|
||||
|
||||
if (!suppress.money) then
|
||||
self.money = self.characterInfo:Add("ixListRow")
|
||||
self.money:SetList(self.characterInfo.list)
|
||||
self.money:Dock(TOP)
|
||||
self.money:SizeToContents()
|
||||
end
|
||||
|
||||
hook.Run("CreateCharacterInfo", self.characterInfo)
|
||||
self.characterInfo:SizeToContents()
|
||||
end
|
||||
|
||||
-- no need to update since we aren't showing the attributes panel
|
||||
if (!suppress.attributes) then
|
||||
local character = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
|
||||
|
||||
if (character) then
|
||||
self.attributes = self:Add("ixCategoryPanel")
|
||||
self.attributes:SetText(L("attributes"))
|
||||
self.attributes:Dock(TOP)
|
||||
self.attributes:DockMargin(0, 0, 0, 8)
|
||||
|
||||
local boost = character:GetBoosts()
|
||||
local bFirst = true
|
||||
|
||||
for k, v in SortedPairsByMemberValue(ix.attributes.list, "name") do
|
||||
local attributeBoost = 0
|
||||
|
||||
if (boost[k]) then
|
||||
for _, bValue in pairs(boost[k]) do
|
||||
attributeBoost = attributeBoost + bValue
|
||||
end
|
||||
end
|
||||
|
||||
local bar = self.attributes:Add("ixAttributeBar")
|
||||
bar:Dock(TOP)
|
||||
|
||||
if (!bFirst) then
|
||||
bar:DockMargin(0, 3, 0, 0)
|
||||
else
|
||||
bFirst = false
|
||||
end
|
||||
|
||||
local value = character:GetAttribute(k, 0)
|
||||
|
||||
if (attributeBoost) then
|
||||
bar:SetValue(value - attributeBoost or 0)
|
||||
else
|
||||
bar:SetValue(value)
|
||||
end
|
||||
|
||||
local maximum = v.maxValue or ix.config.Get("maxAttributes", 100)
|
||||
bar:SetMax(maximum)
|
||||
bar:SetReadOnly()
|
||||
bar:SetText(Format("%s [%.1f/%.1f] (%.1f%%)", L(v.name), value, maximum, value / maximum * 100))
|
||||
|
||||
if (attributeBoost) then
|
||||
bar:SetBoost(attributeBoost)
|
||||
end
|
||||
end
|
||||
|
||||
self.attributes:SizeToContents()
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("CreateCharacterInfoCategory", self)
|
||||
end
|
||||
|
||||
function PANEL:Update(character)
|
||||
if (!character) then
|
||||
return
|
||||
end
|
||||
|
||||
local faction = ix.faction.indices[character:GetFaction()]
|
||||
local class = ix.class.list[character:GetClass()]
|
||||
|
||||
if (self.name) then
|
||||
self.name:SetText(character:GetName())
|
||||
|
||||
if (faction) then
|
||||
self.name.backgroundColor = ColorAlpha(faction.color, 150) or Color(0, 0, 0, 150)
|
||||
end
|
||||
|
||||
self.name:SizeToContents()
|
||||
end
|
||||
|
||||
if (self.description) then
|
||||
self.description:SetText(character:GetDescription())
|
||||
self.description:SizeToContents()
|
||||
end
|
||||
|
||||
if (self.faction) then
|
||||
self.faction:SetLabelText(L("faction"))
|
||||
self.faction:SetText(L(faction.name))
|
||||
self.faction:SizeToContents()
|
||||
end
|
||||
|
||||
if (self.class) then
|
||||
-- don't show class label if the class is the same name as the faction
|
||||
if (class and class.name != faction.name) then
|
||||
self.class:SetLabelText(L("class"))
|
||||
self.class:SetText(L(class.name))
|
||||
self.class:SizeToContents()
|
||||
else
|
||||
self.class:SetVisible(false)
|
||||
end
|
||||
end
|
||||
|
||||
if (self.money) then
|
||||
self.money:SetLabelText(L("money"))
|
||||
self.money:SetText(ix.currency.Get(character:GetMoney()))
|
||||
self.money:SizeToContents()
|
||||
end
|
||||
|
||||
hook.Run("UpdateCharacterInfo", self.characterInfo, character)
|
||||
|
||||
self.characterInfo:SizeToContents()
|
||||
|
||||
hook.Run("UpdateCharacterInfoCategory", self, character)
|
||||
end
|
||||
|
||||
function PANEL:OnSubpanelRightClick()
|
||||
properties.OpenEntityMenu(LocalPlayer())
|
||||
end
|
||||
|
||||
vgui.Register("ixCharacterInfo", PANEL, "DScrollPanel")
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixCharInfo", function(tabs)
|
||||
tabs["you"] = {
|
||||
bHideBackground = true,
|
||||
buttonColor = team.GetColor(LocalPlayer():Team()),
|
||||
Create = function(info, container)
|
||||
container.infoPanel = container:Add("ixCharacterInfo")
|
||||
|
||||
container.OnMouseReleased = function(this, key)
|
||||
if (key == MOUSE_RIGHT) then
|
||||
this.infoPanel:OnSubpanelRightClick()
|
||||
end
|
||||
end
|
||||
end,
|
||||
OnSelected = function(info, container)
|
||||
container.infoPanel:Update(LocalPlayer():GetCharacter())
|
||||
ix.gui.menu:SetCharacterOverview(true)
|
||||
end,
|
||||
OnDeselected = function(info, container)
|
||||
ix.gui.menu:SetCharacterOverview(false)
|
||||
end
|
||||
}
|
||||
end)
|
||||
378
gamemodes/helix/gamemode/core/derma/cl_intro.lua
Normal file
378
gamemodes/helix/gamemode/core/derma/cl_intro.lua
Normal file
@@ -0,0 +1,378 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local waveSegments = 32
|
||||
local helixSegments = 76
|
||||
local helixHeight = 64
|
||||
local backgroundColor = Color(115, 53, 142)
|
||||
local dimColor = Color(165, 134, 179)
|
||||
|
||||
DEFINE_BASECLASS("EditablePanel")
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.gui.intro)) then
|
||||
ix.gui.intro:Remove()
|
||||
end
|
||||
|
||||
ix.gui.intro = self
|
||||
|
||||
self:SetSize(ScrW(), ScrH())
|
||||
self:SetPos(0, 0)
|
||||
self:SetZPos(99999)
|
||||
self:MakePopup()
|
||||
|
||||
-- animation parameters
|
||||
self.bBackground = true
|
||||
self.volume = 1
|
||||
self.sunbeamOffset = 0
|
||||
self.textOne = 0
|
||||
self.textTwo = 0
|
||||
self.kickTarget = 0
|
||||
self.helix = 0
|
||||
self.helixAlpha = 0
|
||||
self.continueText = 0
|
||||
self.pulse = 0
|
||||
|
||||
self.waves = {
|
||||
{1.1, 0},
|
||||
{1.1, math.pi},
|
||||
{1.1, math.pi * 1.6},
|
||||
{1.1, math.pi * 0.5}
|
||||
}
|
||||
end
|
||||
|
||||
-- @todo h a c k
|
||||
function PANEL:Think()
|
||||
if (IsValid(LocalPlayer())) then
|
||||
self:BeginIntro()
|
||||
self.Think = nil
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:BeginIntro()
|
||||
-- something could have errored on startup and invalidated all options, so we'll be extra careful with setting the option
|
||||
-- because if it errors here, the sound will play each tick and proceed to hurt ears
|
||||
local bLoaded = false
|
||||
|
||||
if (ix and ix.option and ix.option.Set) then
|
||||
local bSuccess, _ = pcall(ix.option.Set, "showIntro", false)
|
||||
bLoaded = bSuccess
|
||||
end
|
||||
|
||||
if (!bLoaded) then
|
||||
self:Remove()
|
||||
|
||||
if (ix and ix.gui and IsValid(ix.gui.characterMenu)) then
|
||||
ix.gui.characterMenu:Remove()
|
||||
end
|
||||
|
||||
ErrorNoHalt(
|
||||
"[Helix] Something has errored and prevented the framework from loading correctly - check your console for errors!\n")
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self:MoveToFront()
|
||||
self:RequestFocus()
|
||||
|
||||
sound.PlayFile("sound/buttons/combine_button2.wav", "", function()
|
||||
timer.Create("ixIntroStart", 2, 1, function()
|
||||
sound.PlayFile("sound/helix/intro.mp3", "", function(channel, status, error)
|
||||
if (IsValid(channel)) then
|
||||
channel:SetVolume(self.volume)
|
||||
self.channel = channel
|
||||
end
|
||||
|
||||
self:BeginAnimation()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
function PANEL:AnimateWaves(target, bReverse)
|
||||
for i = bReverse and #self.waves or 1,
|
||||
bReverse and 1 or #self.waves,
|
||||
bReverse and -1 or 1 do
|
||||
|
||||
local animation = self:CreateAnimation(2, {
|
||||
index = 20 + (bReverse and (#self.waves - i) or i),
|
||||
bAutoFire = false,
|
||||
target = {
|
||||
waves = {
|
||||
[i] = {target}
|
||||
}
|
||||
},
|
||||
easing = bReverse and "inQuart" or "outQuint"
|
||||
})
|
||||
|
||||
timer.Simple((bReverse and (#self.waves - i) or i) * 0.1, function()
|
||||
if (IsValid(self) and animation) then
|
||||
animation:Fire()
|
||||
end
|
||||
end)
|
||||
|
||||
-- return last animation that plays
|
||||
if ((bReverse and i == 1) or (!bReverse and i == #self.waves)) then
|
||||
return animation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:BeginAnimation()
|
||||
self:CreateAnimation(2, {
|
||||
target = {textOne = 1},
|
||||
easing = "inQuint",
|
||||
bIgnoreConfig = true
|
||||
})
|
||||
:CreateAnimation(2, {
|
||||
target = {textOne = 0},
|
||||
easing = "inQuint",
|
||||
bIgnoreConfig = true
|
||||
})
|
||||
:CreateAnimation(2, {
|
||||
target = {textTwo = 1},
|
||||
easing = "inQuint",
|
||||
bIgnoreConfig = true,
|
||||
OnComplete = function(animation, panel)
|
||||
self:AnimateWaves(0)
|
||||
end
|
||||
})
|
||||
:CreateAnimation(2, {
|
||||
target = {textTwo = 0},
|
||||
easing = "inQuint",
|
||||
bIgnoreConfig = true
|
||||
})
|
||||
:CreateAnimation(4, {
|
||||
target = {sunbeamOffset = 1},
|
||||
bIgnoreConfig = true,
|
||||
OnComplete = function()
|
||||
self:CreateAnimation(2,{
|
||||
target = {helixAlpha = 1},
|
||||
easing = "inCubic"
|
||||
})
|
||||
end
|
||||
})
|
||||
:CreateAnimation(2, {
|
||||
target = {helix = 1},
|
||||
easing = "outQuart",
|
||||
bIgnoreConfig = true
|
||||
})
|
||||
:CreateAnimation(2, {
|
||||
target = {continueText = 1},
|
||||
easing = "linear",
|
||||
bIgnoreConfig = true
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:PaintCurve(y, width, offset, scale)
|
||||
offset = offset or 1
|
||||
scale = scale or 32
|
||||
|
||||
local points = {
|
||||
[1] = {
|
||||
x = 0,
|
||||
y = ScrH()
|
||||
}
|
||||
}
|
||||
|
||||
for i = 0, waveSegments do
|
||||
local angle = math.rad((i / waveSegments) * -360)
|
||||
|
||||
points[#points + 1] = {
|
||||
x = (width / waveSegments) * i,
|
||||
y = y + (math.sin(angle * 0.5 + offset) - 1) * scale
|
||||
}
|
||||
end
|
||||
|
||||
points[#points + 1] = {
|
||||
x = width,
|
||||
y = ScrH()
|
||||
}
|
||||
|
||||
draw.NoTexture()
|
||||
surface.DrawPoly(points)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
local time = SysTime()
|
||||
local text = L("helix"):lower()
|
||||
local centerY = height * self.waves[#self.waves][1] + height * 0.5
|
||||
local sunbeamOffsetEasing = math.sin(math.pi * self.sunbeamOffset)
|
||||
local textWidth, textHeight
|
||||
local fft
|
||||
|
||||
-- background
|
||||
if (self.bBackground) then
|
||||
surface.SetDrawColor(0, 0, 0, 255)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
if (self.sunbeamOffset == 1) then
|
||||
fft = {}
|
||||
|
||||
if (IsValid(self.channel)) then
|
||||
self.channel:FFT(fft, FFT_2048)
|
||||
|
||||
local kick = (fft[4] or 0) * 8192
|
||||
self.kickTarget = math.Approach(self.kickTarget, kick, 8 * math.abs(kick - self.kickTarget) * FrameTime())
|
||||
end
|
||||
end
|
||||
|
||||
-- waves
|
||||
for i = 1, #self.waves do
|
||||
local wave = self.waves[i]
|
||||
local ratio = i / #self.waves
|
||||
local color = Color(
|
||||
backgroundColor.r * ratio,
|
||||
backgroundColor.g * ratio,
|
||||
backgroundColor.b * ratio,
|
||||
self.bBackground and 255 or (ratio * 320)
|
||||
)
|
||||
|
||||
surface.SetDrawColor(color)
|
||||
self:PaintCurve(height * wave[1], width, wave[2])
|
||||
end
|
||||
|
||||
-- helix
|
||||
if (self.helix > 0) then
|
||||
local alpha = self.helixAlpha * 255
|
||||
|
||||
derma.SkinFunc("DrawHelixCurved",
|
||||
width * 0.5, centerY,
|
||||
math.min(ScreenScale(72), 128) * 2, -- font sizes are clamped to 128
|
||||
helixSegments * self.helix, helixHeight, self.helix,
|
||||
ColorAlpha(color_white, alpha),
|
||||
ColorAlpha(dimColor, alpha)
|
||||
)
|
||||
end
|
||||
|
||||
-- title text glow
|
||||
surface.SetTextColor(255, 255, 255,
|
||||
self.sunbeamOffset == 1 and self.kickTarget or sunbeamOffsetEasing * 255
|
||||
)
|
||||
surface.SetFont("ixIntroTitleBlurFont")
|
||||
|
||||
local logoTextWidth, logoTextHeight = surface.GetTextSize(text)
|
||||
surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- title text
|
||||
surface.SetTextColor(255, 255, 255, self.sunbeamOffset * 255)
|
||||
surface.SetFont("ixIntroTitleFont")
|
||||
|
||||
logoTextWidth, logoTextHeight = surface.GetTextSize(text)
|
||||
surface.SetTextPos(width * 0.5 - logoTextWidth * 0.5, centerY - logoTextHeight * 0.5)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- text one
|
||||
surface.SetFont("ixIntroSubtitleFont")
|
||||
text = L("introTextOne"):lower()
|
||||
textWidth = surface.GetTextSize(text)
|
||||
|
||||
surface.SetTextColor(255, 255, 255, self.textOne * 255)
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- text two
|
||||
text = L("introTextTwo", Schema.author or "nebulous"):lower()
|
||||
textWidth = surface.GetTextSize(text)
|
||||
|
||||
surface.SetTextColor(255, 255, 255, self.textTwo * 255)
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, height * 0.66)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- continue text
|
||||
surface.SetFont("ixIntroSmallFont")
|
||||
text = L("introContinue"):lower()
|
||||
textWidth, textHeight = surface.GetTextSize(text)
|
||||
|
||||
if (self.continueText == 1) then
|
||||
self.pulse = self.pulse + 6 * FrameTime()
|
||||
|
||||
if (self.pulse >= 360) then
|
||||
self.pulse = 0
|
||||
end
|
||||
end
|
||||
|
||||
surface.SetTextColor(255, 255, 255, self.continueText * 255 - (math.sin(self.pulse) * 100), 0)
|
||||
surface.SetTextPos(width * 0.5 - textWidth * 0.5, centerY * 2 - textHeight * 2)
|
||||
surface.DrawText(text)
|
||||
|
||||
-- sunbeams
|
||||
if (self.sunbeamOffset > 0 and self.sunbeamOffset != 1) then
|
||||
DrawSunbeams(0.25, sunbeamOffsetEasing * 0.1, 0.02,
|
||||
(((width * 0.5 - logoTextWidth * 0.5) - 32) / width) + ((logoTextWidth + 64) / width) * self.sunbeamOffset,
|
||||
0.5 + math.sin(time * 2) * 0.01
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnKeyCodePressed(key)
|
||||
if (key == KEY_SPACE and self.continueText > 0.25) then
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnRemove()
|
||||
timer.Remove("ixIntroStart")
|
||||
|
||||
if (IsValid(self.channel)) then
|
||||
self.channel:Stop()
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.characterMenu)) then
|
||||
ix.gui.characterMenu:PlayMusic()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Remove(bForce)
|
||||
if (bForce) then
|
||||
BaseClass.Remove(self)
|
||||
return
|
||||
end
|
||||
|
||||
if (self.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
self.bClosing = true
|
||||
self.bBackground = nil
|
||||
|
||||
-- waves
|
||||
local animation = self:AnimateWaves(1.1, true)
|
||||
|
||||
animation.OnComplete = function(anim, panel)
|
||||
panel:SetMouseInputEnabled(false)
|
||||
panel:SetKeyboardInputEnabled(false)
|
||||
end
|
||||
|
||||
-- audio
|
||||
self:CreateAnimation(4.5, {
|
||||
index = 1,
|
||||
target = {volume = 0},
|
||||
|
||||
Think = function(anim, panel)
|
||||
if (IsValid(panel.channel)) then
|
||||
panel.channel:SetVolume(panel.volume)
|
||||
end
|
||||
end,
|
||||
|
||||
OnComplete = function()
|
||||
timer.Simple(0, function()
|
||||
BaseClass.Remove(self)
|
||||
end)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
vgui.Register("ixIntro", PANEL, "EditablePanel")
|
||||
806
gamemodes/helix/gamemode/core/derma/cl_inventory.lua
Normal file
806
gamemodes/helix/gamemode/core/derma/cl_inventory.lua
Normal file
@@ -0,0 +1,806 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local RECEIVER_NAME = "ixInventoryItem"
|
||||
|
||||
-- The queue for the rendered icons.
|
||||
ICON_RENDER_QUEUE = ICON_RENDER_QUEUE or {}
|
||||
|
||||
-- To make making inventory variant, This must be followed up.
|
||||
local function RenderNewIcon(panel, itemTable)
|
||||
local model = itemTable:GetModel()
|
||||
|
||||
-- re-render icons
|
||||
if ((itemTable.iconCam and !ICON_RENDER_QUEUE[string.lower(model)]) or itemTable.forceRender) then
|
||||
local iconCam = itemTable.iconCam
|
||||
iconCam = {
|
||||
cam_pos = iconCam.pos,
|
||||
cam_ang = iconCam.ang,
|
||||
cam_fov = iconCam.fov,
|
||||
}
|
||||
ICON_RENDER_QUEUE[string.lower(model)] = true
|
||||
|
||||
panel.Icon:RebuildSpawnIconEx(
|
||||
iconCam
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function InventoryAction(action, itemID, invID, data)
|
||||
net.Start("ixInventoryAction")
|
||||
net.WriteString(action)
|
||||
net.WriteUInt(itemID, 32)
|
||||
net.WriteUInt(invID, 32)
|
||||
net.WriteTable(data or {})
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "itemTable", "ItemTable")
|
||||
AccessorFunc(PANEL, "inventoryID", "InventoryID")
|
||||
|
||||
function PANEL:Init()
|
||||
self:Droppable(RECEIVER_NAME)
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed(code)
|
||||
if (code == MOUSE_LEFT and self:IsDraggable()) then
|
||||
self:MouseCapture(true)
|
||||
self:DragMousePress(code)
|
||||
|
||||
self.clickX, self.clickY = input.GetCursorPos()
|
||||
elseif (code == MOUSE_RIGHT and self.DoRightClick) then
|
||||
self:DoRightClick()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnMouseReleased(code)
|
||||
-- move the item into the world if we're dropping on something that doesn't handle inventory item drops
|
||||
if (!dragndrop.m_ReceiverSlot or dragndrop.m_ReceiverSlot.Name != RECEIVER_NAME) then
|
||||
self:OnDrop(dragndrop.IsDragging())
|
||||
end
|
||||
|
||||
self:DragMouseRelease(code)
|
||||
self:SetZPos(99)
|
||||
self:MouseCapture(false)
|
||||
end
|
||||
|
||||
function PANEL:DoRightClick()
|
||||
local itemTable = self.itemTable
|
||||
local inventory = self.inventoryID
|
||||
|
||||
if (itemTable and inventory) then
|
||||
itemTable.player = LocalPlayer()
|
||||
|
||||
local menu = DermaMenu()
|
||||
local override = hook.Run("CreateItemInteractionMenu", self, menu, itemTable)
|
||||
|
||||
if (override == true) then
|
||||
if (menu.Remove) then
|
||||
menu:Remove()
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
for k, v in SortedPairs(itemTable.functions) do
|
||||
if (k == "drop" or k == "combine" or (v.OnCanRun and v.OnCanRun(itemTable) == false)) then
|
||||
continue
|
||||
end
|
||||
|
||||
-- is Multi-Option Function
|
||||
if (v.isMulti) then
|
||||
local subMenu, subMenuOption = menu:AddSubMenu(L(v.name or k), function()
|
||||
itemTable.player = LocalPlayer()
|
||||
local send = true
|
||||
|
||||
if (v.OnClick) then
|
||||
send = v.OnClick(itemTable)
|
||||
end
|
||||
|
||||
if (v.sound) then
|
||||
surface.PlaySound(v.sound)
|
||||
end
|
||||
|
||||
if (send != false) then
|
||||
InventoryAction(k, itemTable.id, inventory)
|
||||
end
|
||||
itemTable.player = nil
|
||||
end)
|
||||
subMenuOption:SetImage(v.icon or "icon16/brick.png")
|
||||
|
||||
if (v.multiOptions) then
|
||||
local options = isfunction(v.multiOptions) and v.multiOptions(itemTable, LocalPlayer()) or v.multiOptions
|
||||
|
||||
for _, sub in pairs(options) do
|
||||
subMenu:AddOption(L(sub.name or "subOption"), function()
|
||||
itemTable.player = LocalPlayer()
|
||||
local send = true
|
||||
|
||||
if (sub.OnClick) then
|
||||
send = sub.OnClick(itemTable)
|
||||
end
|
||||
|
||||
if (sub.sound) then
|
||||
surface.PlaySound(sub.sound)
|
||||
end
|
||||
|
||||
if (send != false) then
|
||||
InventoryAction(k, itemTable.id, inventory, sub.data)
|
||||
end
|
||||
itemTable.player = nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
else
|
||||
menu:AddOption(L(v.name or k), function()
|
||||
itemTable.player = LocalPlayer()
|
||||
local send = true
|
||||
|
||||
if (v.OnClick) then
|
||||
send = v.OnClick(itemTable)
|
||||
end
|
||||
|
||||
if (v.sound) then
|
||||
surface.PlaySound(v.sound)
|
||||
end
|
||||
|
||||
if (send != false) then
|
||||
InventoryAction(k, itemTable.id, inventory)
|
||||
end
|
||||
itemTable.player = nil
|
||||
end):SetImage(v.icon or "icon16/brick.png")
|
||||
end
|
||||
end
|
||||
|
||||
-- we want drop to show up as the last option
|
||||
local info = itemTable.functions.drop
|
||||
|
||||
if (info and info.OnCanRun and info.OnCanRun(itemTable) != false) then
|
||||
menu:AddOption(L(info.name or "drop"), function()
|
||||
itemTable.player = LocalPlayer()
|
||||
local send = true
|
||||
|
||||
if (info.OnClick) then
|
||||
send = info.OnClick(itemTable)
|
||||
end
|
||||
|
||||
if (info.sound) then
|
||||
surface.PlaySound(info.sound)
|
||||
end
|
||||
|
||||
if (send != false) then
|
||||
InventoryAction("drop", itemTable.id, inventory)
|
||||
end
|
||||
itemTable.player = nil
|
||||
end):SetImage(info.icon or "icon16/brick.png")
|
||||
end
|
||||
|
||||
menu:Open()
|
||||
itemTable.player = nil
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnDrop(bDragging, inventoryPanel, inventory, gridX, gridY)
|
||||
local item = self.itemTable
|
||||
|
||||
if (!item or !bDragging) then
|
||||
return
|
||||
end
|
||||
|
||||
if (!IsValid(inventoryPanel)) then
|
||||
local inventoryID = self.inventoryID
|
||||
|
||||
if (inventoryID) then
|
||||
InventoryAction("drop", item.id, inventoryID, {})
|
||||
end
|
||||
elseif (inventoryPanel:IsAllEmpty(gridX, gridY, item.width, item.height, self)) then
|
||||
local oldX, oldY = self.gridX, self.gridY
|
||||
|
||||
if (oldX != gridX or oldY != gridY or self.inventoryID != inventoryPanel.invID) then
|
||||
self:Move(gridX, gridY, inventoryPanel)
|
||||
end
|
||||
elseif (inventoryPanel.combineItem) then
|
||||
local combineItem = inventoryPanel.combineItem
|
||||
local inventoryID = combineItem.invID
|
||||
|
||||
if (inventoryID) then
|
||||
combineItem.player = LocalPlayer()
|
||||
if (combineItem.functions.combine.sound) then
|
||||
surface.PlaySound(combineItem.functions.combine.sound)
|
||||
end
|
||||
|
||||
InventoryAction("combine", combineItem.id, inventoryID, {item.id})
|
||||
combineItem.player = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Move(newX, newY, givenInventory, bNoSend)
|
||||
local iconSize = givenInventory.iconSize
|
||||
local oldX, oldY = self.gridX, self.gridY
|
||||
local oldParent = self:GetParent()
|
||||
|
||||
if (givenInventory:OnTransfer(oldX, oldY, newX, newY, oldParent, bNoSend) == false) then
|
||||
return
|
||||
end
|
||||
|
||||
local x = (newX - 1) * iconSize + 4
|
||||
local y = (newY - 1) * iconSize + givenInventory:GetPadding(2)
|
||||
|
||||
self.gridX = newX
|
||||
self.gridY = newY
|
||||
|
||||
self:SetParent(givenInventory)
|
||||
self:SetPos(x, y)
|
||||
|
||||
if (self.slots) then
|
||||
for _, v in ipairs(self.slots) do
|
||||
if (IsValid(v) and v.item == self) then
|
||||
v.item = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.slots = {}
|
||||
|
||||
for currentX = 1, self.gridW do
|
||||
for currentY = 1, self.gridH do
|
||||
local slot = givenInventory.slots[self.gridX + currentX - 1][self.gridY + currentY - 1]
|
||||
|
||||
slot.item = self
|
||||
self.slots[#self.slots + 1] = slot
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PaintOver(width, height)
|
||||
local itemTable = self.itemTable
|
||||
|
||||
if (itemTable and itemTable.PaintOver) then
|
||||
itemTable.PaintOver(self, itemTable, width, height)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:ExtraPaint(width, height)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetDrawColor(0, 0, 0, 85)
|
||||
surface.DrawRect(2, 2, width - 4, height - 4)
|
||||
|
||||
self:ExtraPaint(width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixItemIcon", PANEL, "SpawnIcon")
|
||||
|
||||
PANEL = {}
|
||||
DEFINE_BASECLASS("DFrame")
|
||||
|
||||
AccessorFunc(PANEL, "iconSize", "IconSize", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "bHighlighted", "Highlighted", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetIconSize(64)
|
||||
self:ShowCloseButton(false)
|
||||
self:SetDraggable(true)
|
||||
self:SetSizable(true)
|
||||
self:SetTitle(L"inv")
|
||||
self:Receiver(RECEIVER_NAME, self.ReceiveDrop)
|
||||
|
||||
self.btnMinim:SetVisible(false)
|
||||
self.btnMinim:SetMouseInputEnabled(false)
|
||||
self.btnMaxim:SetVisible(false)
|
||||
self.btnMaxim:SetMouseInputEnabled(false)
|
||||
|
||||
self.panels = {}
|
||||
end
|
||||
|
||||
function PANEL:GetPadding(index)
|
||||
return select(index, self:GetDockPadding())
|
||||
end
|
||||
|
||||
function PANEL:SetTitle(text)
|
||||
if (text == nil) then
|
||||
self.oldPadding = {self:GetDockPadding()}
|
||||
|
||||
self.lblTitle:SetText("")
|
||||
self.lblTitle:SetVisible(false)
|
||||
|
||||
self:DockPadding(5, 5, 5, 5)
|
||||
else
|
||||
if (self.oldPadding) then
|
||||
self:DockPadding(unpack(self.oldPadding))
|
||||
self.oldPadding = nil
|
||||
end
|
||||
|
||||
BaseClass.SetTitle(self, text)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:FitParent(invWidth, invHeight)
|
||||
local parent = self:GetParent()
|
||||
|
||||
if (!IsValid(parent)) then
|
||||
return
|
||||
end
|
||||
|
||||
local width, height = parent:GetSize()
|
||||
local padding = 4
|
||||
local iconSize
|
||||
|
||||
if (invWidth > invHeight) then
|
||||
iconSize = (width - padding * 2) / invWidth
|
||||
elseif (invHeight > invWidth) then
|
||||
iconSize = (height - padding * 2) / invHeight
|
||||
else
|
||||
-- we use height because the titlebar will make it more tall than it is wide
|
||||
iconSize = (height - padding * 2) / invHeight - 4
|
||||
end
|
||||
|
||||
self:SetSize(iconSize * invWidth + padding * 2, iconSize * invHeight + padding * 2)
|
||||
self:SetIconSize(iconSize)
|
||||
end
|
||||
|
||||
function PANEL:OnRemove()
|
||||
if (self.childPanels) then
|
||||
for _, v in ipairs(self.childPanels) do
|
||||
if (v != self) then
|
||||
v:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:ViewOnly()
|
||||
self.viewOnly = true
|
||||
|
||||
for _, icon in pairs(self.panels) do
|
||||
icon.OnMousePressed = nil
|
||||
icon.OnMouseReleased = nil
|
||||
icon.doRightClick = nil
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetInventory(inventory, bFitParent)
|
||||
if (inventory.slots) then
|
||||
local invWidth, invHeight = inventory:GetSize()
|
||||
self.invID = inventory:GetID()
|
||||
|
||||
if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels and inventory != LocalPlayer():GetCharacter():GetInventory()) then
|
||||
self:SetIconSize(ix.gui.inv1:GetIconSize())
|
||||
self:SetPaintedManually(true)
|
||||
self.bNoBackgroundBlur = true
|
||||
|
||||
ix.gui.inv1.childPanels[#ix.gui.inv1.childPanels + 1] = self
|
||||
elseif (bFitParent) then
|
||||
self:FitParent(invWidth, invHeight)
|
||||
else
|
||||
self:SetSize(self.iconSize, self.iconSize)
|
||||
end
|
||||
|
||||
self:SetGridSize(invWidth, invHeight)
|
||||
|
||||
for x, items in pairs(inventory.slots) do
|
||||
for y, data in pairs(items) do
|
||||
if (!data.id) then continue end
|
||||
|
||||
local item = ix.item.instances[data.id]
|
||||
|
||||
if (item and !IsValid(self.panels[item.id])) then
|
||||
local icon = self:AddIcon(item:GetModel() or "models/props_junk/popcan01a.mdl",
|
||||
x, y, item.width, item.height, item:GetSkin())
|
||||
|
||||
if (IsValid(icon)) then
|
||||
icon:SetHelixTooltip(function(tooltip)
|
||||
ix.hud.PopulateItemTooltip(tooltip, item)
|
||||
end)
|
||||
|
||||
self.panels[item.id] = icon
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetGridSize(w, h)
|
||||
local iconSize = self.iconSize
|
||||
local newWidth = w * iconSize + 8
|
||||
local newHeight = h * iconSize + self:GetPadding(2) + self:GetPadding(4)
|
||||
|
||||
self.gridW = w
|
||||
self.gridH = h
|
||||
|
||||
self:SetSize(newWidth, newHeight)
|
||||
self:SetMinWidth(newWidth)
|
||||
self:SetMinHeight(newHeight)
|
||||
self:BuildSlots()
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
BaseClass.PerformLayout(self, width, height)
|
||||
|
||||
if (self.Sizing and self.gridW and self.gridH) then
|
||||
local newWidth = (width - 8) / self.gridW
|
||||
local newHeight = (height - self:GetPadding(2) + self:GetPadding(4)) / self.gridH
|
||||
|
||||
self:SetIconSize((newWidth + newHeight) / 2)
|
||||
self:RebuildItems()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:BuildSlots()
|
||||
local iconSize = self.iconSize
|
||||
|
||||
self.slots = self.slots or {}
|
||||
|
||||
for _, v in ipairs(self.slots) do
|
||||
for _, v2 in ipairs(v) do
|
||||
v2:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
self.slots = {}
|
||||
|
||||
for x = 1, self.gridW do
|
||||
self.slots[x] = {}
|
||||
|
||||
for y = 1, self.gridH do
|
||||
local slot = self:Add("DPanel")
|
||||
slot:SetZPos(-999)
|
||||
slot.gridX = x
|
||||
slot.gridY = y
|
||||
slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2))
|
||||
slot:SetSize(iconSize, iconSize)
|
||||
slot.Paint = function(panel, width, height)
|
||||
derma.SkinFunc("PaintInventorySlot", panel, width, height)
|
||||
end
|
||||
|
||||
self.slots[x][y] = slot
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:RebuildItems()
|
||||
local iconSize = self.iconSize
|
||||
|
||||
for x = 1, self.gridW do
|
||||
for y = 1, self.gridH do
|
||||
local slot = self.slots[x][y]
|
||||
|
||||
slot:SetPos((x - 1) * iconSize + 4, (y - 1) * iconSize + self:GetPadding(2))
|
||||
slot:SetSize(iconSize, iconSize)
|
||||
end
|
||||
end
|
||||
|
||||
for _, v in pairs(self.panels) do
|
||||
if (IsValid(v)) then
|
||||
v:SetPos(self.slots[v.gridX][v.gridY]:GetPos())
|
||||
v:SetSize(v.gridW * iconSize, v.gridH * iconSize)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PaintDragPreview(width, height, mouseX, mouseY, itemPanel)
|
||||
local iconSize = self.iconSize
|
||||
local item = itemPanel:GetItemTable()
|
||||
|
||||
if (item) then
|
||||
local inventory = ix.item.inventories[self.invID]
|
||||
local dropX = math.ceil((mouseX - 4 - (itemPanel.gridW - 1) * 32) / iconSize)
|
||||
local dropY = math.ceil((mouseY - self:GetPadding(2) - (itemPanel.gridH - 1) * 32) / iconSize)
|
||||
|
||||
local hoveredPanel = vgui.GetHoveredPanel()
|
||||
|
||||
if (IsValid(hoveredPanel) and hoveredPanel != itemPanel and hoveredPanel.GetItemTable) then
|
||||
local hoveredItem = hoveredPanel:GetItemTable()
|
||||
|
||||
if (hoveredItem) then
|
||||
local info = hoveredItem.functions.combine
|
||||
|
||||
if (info and info.OnCanRun and info.OnCanRun(hoveredItem, {item.id}) != false) then
|
||||
surface.SetDrawColor(ColorAlpha(derma.GetColor("Info", self, Color(200, 0, 0)), 20))
|
||||
surface.DrawRect(
|
||||
hoveredPanel.x,
|
||||
hoveredPanel.y,
|
||||
hoveredPanel:GetWide(),
|
||||
hoveredPanel:GetTall()
|
||||
)
|
||||
|
||||
self.combineItem = hoveredItem
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.combineItem = nil
|
||||
|
||||
-- don't draw grid if we're dragging it out of bounds
|
||||
if (inventory) then
|
||||
local invWidth, invHeight = inventory:GetSize()
|
||||
|
||||
if (dropX < 1 or dropY < 1 or
|
||||
dropX + itemPanel.gridW - 1 > invWidth or
|
||||
dropY + itemPanel.gridH - 1 > invHeight) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local bEmpty = true
|
||||
|
||||
for x = 0, itemPanel.gridW - 1 do
|
||||
for y = 0, itemPanel.gridH - 1 do
|
||||
local x2 = dropX + x
|
||||
local y2 = dropY + y
|
||||
|
||||
bEmpty = self:IsEmpty(x2, y2, itemPanel)
|
||||
|
||||
if (!bEmpty) then
|
||||
-- no need to iterate further since we know something is blocking the hovered grid cells, break through both loops
|
||||
goto finish
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
::finish::
|
||||
local previewColor = ColorAlpha(derma.GetColor(bEmpty and "Success" or "Error", self, Color(200, 0, 0)), 20)
|
||||
|
||||
surface.SetDrawColor(previewColor)
|
||||
surface.DrawRect(
|
||||
(dropX - 1) * iconSize + 4,
|
||||
(dropY - 1) * iconSize + self:GetPadding(2),
|
||||
itemPanel:GetWide(),
|
||||
itemPanel:GetTall()
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PaintOver(width, height)
|
||||
local panel = self.previewPanel
|
||||
|
||||
if (IsValid(panel)) then
|
||||
local itemPanel = (dragndrop.GetDroppable() or {})[1]
|
||||
|
||||
if (IsValid(itemPanel)) then
|
||||
self:PaintDragPreview(width, height, self.previewX, self.previewY, itemPanel)
|
||||
end
|
||||
end
|
||||
|
||||
self.previewPanel = nil
|
||||
end
|
||||
|
||||
function PANEL:IsEmpty(x, y, this)
|
||||
return (self.slots[x] and self.slots[x][y]) and (!IsValid(self.slots[x][y].item) or self.slots[x][y].item == this)
|
||||
end
|
||||
|
||||
function PANEL:IsAllEmpty(x, y, width, height, this)
|
||||
for x2 = 0, width - 1 do
|
||||
for y2 = 0, height - 1 do
|
||||
if (!self:IsEmpty(x + x2, y + y2, this)) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function PANEL:OnTransfer(oldX, oldY, x, y, oldInventory, noSend)
|
||||
local inventories = ix.item.inventories
|
||||
local inventory = inventories[oldInventory.invID]
|
||||
local inventory2 = inventories[self.invID]
|
||||
local item
|
||||
|
||||
if (inventory) then
|
||||
item = inventory:GetItemAt(oldX, oldY)
|
||||
|
||||
if (!item) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (hook.Run("CanTransferItem", item, inventories[oldInventory.invID], inventories[self.invID]) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
if (item.CanTransfer and
|
||||
item:CanTransfer(inventory, inventory != inventory2 and inventory2 or nil) == false) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if (!noSend) then
|
||||
net.Start("ixInventoryMove")
|
||||
net.WriteUInt(oldX, 6)
|
||||
net.WriteUInt(oldY, 6)
|
||||
net.WriteUInt(x, 6)
|
||||
net.WriteUInt(y, 6)
|
||||
net.WriteUInt(oldInventory.invID, 32)
|
||||
net.WriteUInt(self != oldInventory and self.invID or oldInventory.invID, 32)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
if (inventory) then
|
||||
inventory.slots[oldX][oldY] = nil
|
||||
end
|
||||
|
||||
if (item and inventory2) then
|
||||
inventory2.slots[x] = inventory2.slots[x] or {}
|
||||
inventory2.slots[x][y] = item
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:AddIcon(model, x, y, w, h, skin)
|
||||
local iconSize = self.iconSize
|
||||
|
||||
w = w or 1
|
||||
h = h or 1
|
||||
|
||||
if (self.slots[x] and self.slots[x][y]) then
|
||||
local panel = self:Add("ixItemIcon")
|
||||
panel:SetSize(w * iconSize, h * iconSize)
|
||||
panel:SetZPos(999)
|
||||
panel:InvalidateLayout(true)
|
||||
panel:SetModel(model, skin)
|
||||
panel:SetPos(self.slots[x][y]:GetPos())
|
||||
panel.gridX = x
|
||||
panel.gridY = y
|
||||
panel.gridW = w
|
||||
panel.gridH = h
|
||||
|
||||
local inventory = ix.item.inventories[self.invID]
|
||||
|
||||
if (!inventory) then
|
||||
return
|
||||
end
|
||||
|
||||
local itemTable = inventory:GetItemAt(panel.gridX, panel.gridY)
|
||||
|
||||
panel:SetInventoryID(inventory:GetID())
|
||||
panel:SetItemTable(itemTable)
|
||||
|
||||
if (self.panels[itemTable:GetID()]) then
|
||||
self.panels[itemTable:GetID()]:Remove()
|
||||
end
|
||||
|
||||
if (itemTable.exRender) then
|
||||
panel.Icon:SetVisible(false)
|
||||
panel.ExtraPaint = function(this, panelX, panelY)
|
||||
local exIcon = ikon:GetIcon(itemTable.uniqueID)
|
||||
if (exIcon) then
|
||||
surface.SetMaterial(exIcon)
|
||||
surface.SetDrawColor(color_white)
|
||||
surface.DrawTexturedRect(0, 0, panelX, panelY)
|
||||
else
|
||||
ikon:renderIcon(
|
||||
itemTable.uniqueID,
|
||||
itemTable.width,
|
||||
itemTable.height,
|
||||
itemTable:GetModel(),
|
||||
itemTable.iconCam
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- yeah..
|
||||
RenderNewIcon(panel, itemTable)
|
||||
end
|
||||
|
||||
panel.slots = {}
|
||||
|
||||
for i = 0, w - 1 do
|
||||
for i2 = 0, h - 1 do
|
||||
local slot = self.slots[x + i] and self.slots[x + i][y + i2]
|
||||
|
||||
if (IsValid(slot)) then
|
||||
slot.item = panel
|
||||
panel.slots[#panel.slots + 1] = slot
|
||||
else
|
||||
for _, v in ipairs(panel.slots) do
|
||||
v.item = nil
|
||||
end
|
||||
|
||||
panel:Remove()
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return panel
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:ReceiveDrop(panels, bDropped, menuIndex, x, y)
|
||||
local panel = panels[1]
|
||||
|
||||
if (!IsValid(panel)) then
|
||||
self.previewPanel = nil
|
||||
return
|
||||
end
|
||||
|
||||
if (bDropped) then
|
||||
local inventory = ix.item.inventories[self.invID]
|
||||
|
||||
if (inventory and panel.OnDrop) then
|
||||
local dropX = math.ceil((x - 4 - (panel.gridW - 1) * 32) / self.iconSize)
|
||||
local dropY = math.ceil((y - self:GetPadding(2) - (panel.gridH - 1) * 32) / self.iconSize)
|
||||
|
||||
panel:OnDrop(true, self, inventory, dropX, dropY)
|
||||
end
|
||||
|
||||
self.previewPanel = nil
|
||||
else
|
||||
self.previewPanel = panel
|
||||
self.previewX = x
|
||||
self.previewY = y
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixInventory", PANEL, "DFrame")
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixInventory", function(tabs)
|
||||
if (hook.Run("CanPlayerViewInventory") == false) then
|
||||
return
|
||||
end
|
||||
|
||||
tabs["inv"] = {
|
||||
bDefault = true,
|
||||
Create = function(info, container)
|
||||
local canvas = container:Add("DTileLayout")
|
||||
local canvasLayout = canvas.PerformLayout
|
||||
canvas.PerformLayout = nil -- we'll layout after we add the panels instead of each time one is added
|
||||
canvas:SetBorder(0)
|
||||
canvas:SetSpaceX(2)
|
||||
canvas:SetSpaceY(2)
|
||||
canvas:Dock(FILL)
|
||||
|
||||
ix.gui.menuInventoryContainer = canvas
|
||||
|
||||
local panel = canvas:Add("ixInventory")
|
||||
panel:SetPos(0, 0)
|
||||
panel:SetDraggable(false)
|
||||
panel:SetSizable(false)
|
||||
panel:SetTitle(nil)
|
||||
panel.bNoBackgroundBlur = true
|
||||
panel.childPanels = {}
|
||||
|
||||
local inventory = LocalPlayer():GetCharacter():GetInventory()
|
||||
|
||||
if (inventory) then
|
||||
panel:SetInventory(inventory)
|
||||
end
|
||||
|
||||
ix.gui.inv1 = panel
|
||||
|
||||
if (ix.option.Get("openBags", true)) then
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (!v.isBag) then
|
||||
continue
|
||||
end
|
||||
|
||||
v.functions.View.OnClick(v)
|
||||
end
|
||||
end
|
||||
|
||||
canvas.PerformLayout = canvasLayout
|
||||
canvas:Layout()
|
||||
end
|
||||
}
|
||||
end)
|
||||
|
||||
hook.Add("PostRenderVGUI", "ixInvHelper", function()
|
||||
local pnl = ix.gui.inv1
|
||||
|
||||
hook.Run("PostDrawInventory", pnl)
|
||||
end)
|
||||
501
gamemodes/helix/gamemode/core/derma/cl_menu.lua
Normal file
501
gamemodes/helix/gamemode/core/derma/cl_menu.lua
Normal file
@@ -0,0 +1,501 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local animationTime = 1
|
||||
local matrixZScale = Vector(1, 1, 0.0001)
|
||||
|
||||
DEFINE_BASECLASS("ixSubpanelParent")
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "bCharacterOverview", "CharacterOverview", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.gui.menu)) then
|
||||
ix.gui.menu:Remove()
|
||||
end
|
||||
|
||||
ix.gui.menu = self
|
||||
|
||||
-- properties
|
||||
self.manualChildren = {}
|
||||
self.noAnchor = CurTime() + 0.4
|
||||
self.anchorMode = true
|
||||
self.rotationOffset = Angle(0, 180, 0)
|
||||
self.projectedTexturePosition = Vector(0, 0, 6)
|
||||
self.projectedTextureRotation = Angle(-45, 60, 0)
|
||||
|
||||
self.bCharacterOverview = false
|
||||
self.bOverviewOut = false
|
||||
self.overviewFraction = 0
|
||||
|
||||
self.currentAlpha = 0
|
||||
self.currentBlur = 0
|
||||
|
||||
-- setup
|
||||
self:SetPadding(ScreenScale(16), true)
|
||||
self:SetSize(ScrW(), ScrH())
|
||||
self:SetPos(0, 0)
|
||||
self:SetLeftOffset(self:GetWide() * 0.25 + self:GetPadding())
|
||||
|
||||
-- main button panel
|
||||
self.buttons = self:Add("Panel")
|
||||
self.buttons:SetSize(self:GetWide() * 0.25, self:GetTall() - self:GetPadding() * 2)
|
||||
self.buttons:Dock(LEFT)
|
||||
self.buttons:SetPaintedManually(true)
|
||||
|
||||
local close = self.buttons:Add("ixMenuButton")
|
||||
close:SetText("return")
|
||||
close:SizeToContents()
|
||||
close:Dock(BOTTOM)
|
||||
close.DoClick = function()
|
||||
self:Remove()
|
||||
end
|
||||
|
||||
local characters = self.buttons:Add("ixMenuButton")
|
||||
characters:SetText("characters")
|
||||
characters:SizeToContents()
|
||||
characters:Dock(BOTTOM)
|
||||
characters.DoClick = function()
|
||||
self:Remove()
|
||||
vgui.Create("ixCharMenu")
|
||||
end
|
||||
|
||||
-- @todo make a better way to avoid clicks in the padding PLEASE
|
||||
self.guard = self:Add("Panel")
|
||||
self.guard:SetPos(0, 0)
|
||||
self.guard:SetSize(self:GetPadding(), self:GetTall())
|
||||
|
||||
-- tabs
|
||||
self.tabs = self.buttons:Add("Panel")
|
||||
self.tabs.buttons = {}
|
||||
self.tabs:Dock(FILL)
|
||||
self:PopulateTabs()
|
||||
|
||||
self:MakePopup()
|
||||
self:OnOpened()
|
||||
end
|
||||
|
||||
function PANEL:OnOpened()
|
||||
self:SetAlpha(0)
|
||||
|
||||
self:CreateAnimation(animationTime, {
|
||||
target = {currentAlpha = 255},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.currentAlpha)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:GetActiveTab()
|
||||
return (self:GetActiveSubpanel() or {}).subpanelName
|
||||
end
|
||||
|
||||
function PANEL:TransitionSubpanel(id)
|
||||
local lastSubpanel = self:GetActiveSubpanel()
|
||||
|
||||
-- don't transition to the same panel
|
||||
if (IsValid(lastSubpanel) and lastSubpanel.subpanelID == id) then
|
||||
return
|
||||
end
|
||||
|
||||
local subpanel = self:GetSubpanel(id)
|
||||
|
||||
if (IsValid(subpanel)) then
|
||||
if (!subpanel.bPopulated) then
|
||||
-- we need to set the size of the subpanel if it's a section since it will be 0, 0
|
||||
if (subpanel.sectionParent) then
|
||||
subpanel:SetSize(self:GetStandardSubpanelSize())
|
||||
end
|
||||
|
||||
local info = subpanel.info
|
||||
subpanel.Paint = nil
|
||||
|
||||
if (istable(info) and info.Create) then
|
||||
info:Create(subpanel)
|
||||
elseif (isfunction(info)) then
|
||||
info(subpanel)
|
||||
end
|
||||
|
||||
hook.Run("MenuSubpanelCreated", subpanel.subpanelName, subpanel)
|
||||
subpanel.bPopulated = true
|
||||
end
|
||||
|
||||
-- only play whoosh sound only when the menu was already open
|
||||
if (IsValid(lastSubpanel)) then
|
||||
LocalPlayer():EmitSound("Helix.Whoosh")
|
||||
end
|
||||
|
||||
self:SetActiveSubpanel(id)
|
||||
end
|
||||
|
||||
subpanel = self:GetActiveSubpanel()
|
||||
|
||||
local info = subpanel.info
|
||||
local bHideBackground = istable(info) and (info.bHideBackground != nil and info.bHideBackground or false) or false
|
||||
|
||||
if (bHideBackground) then
|
||||
self:HideBackground()
|
||||
else
|
||||
self:ShowBackground()
|
||||
end
|
||||
|
||||
-- call hooks if we've changed subpanel
|
||||
if (IsValid(lastSubpanel) and istable(lastSubpanel.info) and lastSubpanel.info.OnDeselected) then
|
||||
lastSubpanel.info:OnDeselected(lastSubpanel)
|
||||
end
|
||||
|
||||
if (IsValid(subpanel) and istable(subpanel.info) and subpanel.info.OnSelected) then
|
||||
subpanel.info:OnSelected(subpanel)
|
||||
end
|
||||
|
||||
ix.gui.lastMenuTab = id
|
||||
end
|
||||
|
||||
function PANEL:SetCharacterOverview(bValue, length)
|
||||
bValue = tobool(bValue)
|
||||
length = length or animationTime
|
||||
|
||||
if (bValue) then
|
||||
if (!IsValid(self.projectedTexture)) then
|
||||
self.projectedTexture = ProjectedTexture()
|
||||
end
|
||||
|
||||
local faction = ix.faction.indices[LocalPlayer():Team()]
|
||||
local color = faction and faction.color or color_white
|
||||
|
||||
self.projectedTexture:SetEnableShadows(false)
|
||||
self.projectedTexture:SetNearZ(12)
|
||||
self.projectedTexture:SetFarZ(64)
|
||||
self.projectedTexture:SetFOV(90)
|
||||
self.projectedTexture:SetColor(color)
|
||||
self.projectedTexture:SetTexture("effects/flashlight/soft")
|
||||
|
||||
self:CreateAnimation(length, {
|
||||
index = 3,
|
||||
target = {overviewFraction = 1},
|
||||
easing = "outQuint",
|
||||
bIgnoreConfig = true
|
||||
})
|
||||
|
||||
self.bOverviewOut = false
|
||||
self.bCharacterOverview = true
|
||||
else
|
||||
self:CreateAnimation(length, {
|
||||
index = 3,
|
||||
target = {overviewFraction = 0},
|
||||
easing = "outQuint",
|
||||
bIgnoreConfig = true,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel.bCharacterOverview = false
|
||||
|
||||
if (IsValid(panel.projectedTexture)) then
|
||||
panel.projectedTexture:Remove()
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
self.bOverviewOut = true
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetOverviewInfo(origin, angles, fov)
|
||||
local originAngles = Angle(0, angles.yaw, angles.roll)
|
||||
local target = LocalPlayer():GetObserverTarget()
|
||||
local fraction = self.overviewFraction
|
||||
local bDrawPlayer = ((fraction > 0.2) or (!self.bOverviewOut and (fraction > 0.2))) and !IsValid(target)
|
||||
local forward = originAngles:Forward() * 58 - originAngles:Right() * 16
|
||||
forward.z = 0
|
||||
|
||||
local newOrigin
|
||||
|
||||
if (IsValid(target)) then
|
||||
newOrigin = target:GetPos() + forward
|
||||
else
|
||||
newOrigin = origin - LocalPlayer():OBBCenter() * 0.6 + forward
|
||||
end
|
||||
|
||||
local newAngles = originAngles + self.rotationOffset
|
||||
newAngles.pitch = 5
|
||||
newAngles.roll = 0
|
||||
|
||||
return LerpVector(fraction, origin, newOrigin), LerpAngle(fraction, angles, newAngles), Lerp(fraction, fov, 90), bDrawPlayer
|
||||
end
|
||||
|
||||
function PANEL:HideBackground()
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 2,
|
||||
target = {currentBlur = 0},
|
||||
easing = "outQuint"
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:ShowBackground()
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 2,
|
||||
target = {currentBlur = 1},
|
||||
easing = "outQuint"
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:GetStandardSubpanelSize()
|
||||
return ScrW() * 0.75 - self:GetPadding() * 3, ScrH() - self:GetPadding() * 2
|
||||
end
|
||||
|
||||
function PANEL:SetupTab(name, info, sectionParent)
|
||||
local bTable = istable(info)
|
||||
local buttonColor = (bTable and info.buttonColor) or (ix.config.Get("color") or Color(140, 140, 140, 255))
|
||||
local bDefault = (bTable and info.bDefault) or false
|
||||
local qualifiedName = sectionParent and (sectionParent.name .. "/" .. name) or name
|
||||
|
||||
-- setup subpanels without populating them so we can retain the order
|
||||
local subpanel = self:AddSubpanel(qualifiedName, true)
|
||||
local id = subpanel.subpanelID
|
||||
subpanel.info = info
|
||||
subpanel.sectionParent = sectionParent and qualifiedName
|
||||
subpanel:SetPaintedManually(true)
|
||||
subpanel:SetTitle(nil)
|
||||
|
||||
if (sectionParent) then
|
||||
-- hide section subpanels if they haven't been populated to seeing more subpanels than necessary
|
||||
-- fly by as you navigate tabs in the menu
|
||||
subpanel:SetSize(0, 0)
|
||||
else
|
||||
subpanel:SetSize(self:GetStandardSubpanelSize())
|
||||
|
||||
-- this is called while the subpanel has not been populated
|
||||
subpanel.Paint = function(panel, width, height)
|
||||
derma.SkinFunc("PaintPlaceholderPanel", panel, width, height)
|
||||
end
|
||||
end
|
||||
|
||||
local button
|
||||
|
||||
if (sectionParent) then
|
||||
button = sectionParent:AddSection(L(name))
|
||||
name = qualifiedName
|
||||
else
|
||||
button = self.tabs:Add("ixMenuSelectionButton")
|
||||
button:SetText(L(name))
|
||||
button:SizeToContents()
|
||||
button:Dock(TOP)
|
||||
button:SetButtonList(self.tabs.buttons)
|
||||
button:SetBackgroundColor(buttonColor)
|
||||
end
|
||||
|
||||
button.name = name
|
||||
button.id = id
|
||||
button.OnSelected = function()
|
||||
self:TransitionSubpanel(id)
|
||||
end
|
||||
|
||||
if (bTable and info.PopulateTabButton) then
|
||||
info:PopulateTabButton(button)
|
||||
end
|
||||
|
||||
-- don't allow sections in sections
|
||||
if (sectionParent or !bTable or !info.Sections) then
|
||||
return bDefault, button, subpanel
|
||||
end
|
||||
|
||||
-- create button sections
|
||||
for sectionName, sectionInfo in pairs(info.Sections) do
|
||||
self:SetupTab(sectionName, sectionInfo, button)
|
||||
end
|
||||
|
||||
return bDefault, button, subpanel
|
||||
end
|
||||
|
||||
function PANEL:PopulateTabs()
|
||||
local default
|
||||
local tabs = {}
|
||||
|
||||
hook.Run("CreateMenuButtons", tabs)
|
||||
|
||||
for name, info in SortedPairs(tabs) do
|
||||
local bDefault, button = self:SetupTab(name, info)
|
||||
|
||||
if (bDefault) then
|
||||
default = button
|
||||
end
|
||||
end
|
||||
|
||||
if (ix.gui.lastMenuTab) then
|
||||
for i = 1, #self.tabs.buttons do
|
||||
local button = self.tabs.buttons[i]
|
||||
|
||||
if (button.id == ix.gui.lastMenuTab) then
|
||||
default = button
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (!IsValid(default) and #self.tabs.buttons > 0) then
|
||||
default = self.tabs.buttons[1]
|
||||
end
|
||||
|
||||
if (IsValid(default)) then
|
||||
default:SetSelected(true)
|
||||
self:SetActiveSubpanel(default.id, 0)
|
||||
end
|
||||
|
||||
self.buttons:MoveToFront()
|
||||
self.guard:MoveToBefore(self.buttons)
|
||||
end
|
||||
|
||||
function PANEL:AddManuallyPaintedChild(panel)
|
||||
panel:SetParent(self)
|
||||
panel:SetPaintedManually(panel)
|
||||
|
||||
self.manualChildren[#self.manualChildren + 1] = panel
|
||||
end
|
||||
|
||||
function PANEL:OnKeyCodePressed(key)
|
||||
self.noAnchor = CurTime() + 0.5
|
||||
|
||||
if (key == KEY_TAB) then
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (IsValid(self.projectedTexture)) then
|
||||
local forward = LocalPlayer():GetForward()
|
||||
forward.z = 0
|
||||
|
||||
local right = LocalPlayer():GetRight()
|
||||
right.z = 0
|
||||
|
||||
self.projectedTexture:SetBrightness(self.overviewFraction * 4)
|
||||
self.projectedTexture:SetPos(LocalPlayer():GetPos() + right * 16 - forward * 8 + self.projectedTexturePosition)
|
||||
self.projectedTexture:SetAngles(forward:Angle() + self.projectedTextureRotation)
|
||||
self.projectedTexture:Update()
|
||||
end
|
||||
|
||||
if (self.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
local bTabDown = input.IsKeyDown(KEY_TAB)
|
||||
|
||||
if (bTabDown and (self.noAnchor or CurTime() + 0.4) < CurTime() and self.anchorMode) then
|
||||
self.anchorMode = false
|
||||
surface.PlaySound("buttons/lightswitch2.wav")
|
||||
end
|
||||
|
||||
if ((!self.anchorMode and !bTabDown) or gui.IsGameUIVisible()) then
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintMenuBackground", self, width, height, self.currentBlur)
|
||||
|
||||
local bShouldScale = self.currentAlpha != 255
|
||||
|
||||
if (bShouldScale) then
|
||||
local currentScale = Lerp(self.currentAlpha / 255, 0.9, 1)
|
||||
local matrix = Matrix()
|
||||
|
||||
matrix:Scale(matrixZScale * currentScale)
|
||||
matrix:Translate(Vector(
|
||||
ScrW() * 0.5 - (ScrW() * currentScale * 0.5),
|
||||
ScrH() * 0.5 - (ScrH() * currentScale * 0.5),
|
||||
1
|
||||
))
|
||||
|
||||
cam.PushModelMatrix(matrix)
|
||||
end
|
||||
|
||||
BaseClass.Paint(self, width, height)
|
||||
self:PaintSubpanels(width, height)
|
||||
self.buttons:PaintManual()
|
||||
|
||||
for i = 1, #self.manualChildren do
|
||||
self.manualChildren[i]:PaintManual()
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then
|
||||
for i = 1, #ix.gui.inv1.childPanels do
|
||||
local panel = ix.gui.inv1.childPanels[i]
|
||||
|
||||
if (IsValid(panel)) then
|
||||
panel:PaintManual()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (bShouldScale) then
|
||||
cam.PopModelMatrix()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout()
|
||||
self.guard:SetSize(self.tabs:GetWide() + self:GetPadding() * 2, self:GetTall())
|
||||
end
|
||||
|
||||
function PANEL:Remove()
|
||||
self.bClosing = true
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
self:SetCharacterOverview(false, animationTime * 0.5)
|
||||
|
||||
-- remove input from opened child panels since they grab focus
|
||||
if (IsValid(ix.gui.inv1) and ix.gui.inv1.childPanels) then
|
||||
for i = 1, #ix.gui.inv1.childPanels do
|
||||
local panel = ix.gui.inv1.childPanels[i]
|
||||
|
||||
if (IsValid(panel)) then
|
||||
panel:SetMouseInputEnabled(false)
|
||||
panel:SetKeyboardInputEnabled(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
CloseDermaMenus()
|
||||
gui.EnableScreenClicker(false)
|
||||
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
index = 2,
|
||||
target = {currentBlur = 0},
|
||||
easing = "outQuint"
|
||||
})
|
||||
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
target = {currentAlpha = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
-- we don't animate the blur because blurring doesn't draw things
|
||||
-- with amount < 1 very well, resulting in jarring transition
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.currentAlpha)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
if (IsValid(panel.projectedTexture)) then
|
||||
panel.projectedTexture:Remove()
|
||||
end
|
||||
|
||||
BaseClass.Remove(panel)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
vgui.Register("ixMenu", PANEL, "ixSubpanelParent")
|
||||
|
||||
if (IsValid(ix.gui.menu)) then
|
||||
ix.gui.menu:Remove()
|
||||
end
|
||||
|
||||
ix.gui.lastMenuTab = nil
|
||||
305
gamemodes/helix/gamemode/core/derma/cl_menubutton.lua
Normal file
305
gamemodes/helix/gamemode/core/derma/cl_menubutton.lua
Normal file
@@ -0,0 +1,305 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local buttonPadding = ScreenScale(14) * 0.5
|
||||
local animationTime = 0.5
|
||||
|
||||
-- base menu button
|
||||
DEFINE_BASECLASS("DButton")
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
|
||||
AccessorFunc(PANEL, "backgroundAlpha", "BackgroundAlpha")
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetFont("ixMenuButtonFont")
|
||||
self:SetTextColor(color_white)
|
||||
self:SetPaintBackground(false)
|
||||
self:SetContentAlignment(4)
|
||||
self:SetTextInset(buttonPadding, 0)
|
||||
|
||||
self.padding = {32, 12, 32, 12} -- left, top, right, bottom
|
||||
self.backgroundColor = Color(0, 0, 0)
|
||||
self.backgroundAlpha = 128
|
||||
self.currentBackgroundAlpha = 0
|
||||
end
|
||||
|
||||
function PANEL:GetPadding()
|
||||
return self.padding
|
||||
end
|
||||
|
||||
function PANEL:SetPadding(left, top, right, bottom)
|
||||
self.padding = {
|
||||
left or self.padding[1],
|
||||
top or self.padding[2],
|
||||
right or self.padding[3],
|
||||
bottom or self.padding[4]
|
||||
}
|
||||
end
|
||||
|
||||
function PANEL:SetText(text, noTranslation)
|
||||
BaseClass.SetText(self, noTranslation and text:utf8upper() or L(text):utf8upper())
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
BaseClass.SizeToContents(self)
|
||||
|
||||
local width, height = self:GetSize()
|
||||
self:SetSize(width + self.padding[1] + self.padding[3], height + self.padding[2] + self.padding[4])
|
||||
end
|
||||
|
||||
function PANEL:PaintBackground(width, height)
|
||||
surface.SetDrawColor(ColorAlpha(self.backgroundColor, self.currentBackgroundAlpha))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
self:PaintBackground(width, height)
|
||||
BaseClass.Paint(self, width, height)
|
||||
end
|
||||
|
||||
function PANEL:SetTextColorInternal(color)
|
||||
BaseClass.SetTextColor(self, color)
|
||||
self:SetFGColor(color)
|
||||
end
|
||||
|
||||
function PANEL:SetTextColor(color)
|
||||
self:SetTextColorInternal(color)
|
||||
self.color = color
|
||||
end
|
||||
|
||||
function PANEL:SetDisabled(bValue)
|
||||
local color = self.color
|
||||
|
||||
if (bValue) then
|
||||
self:SetTextColorInternal(Color(math.max(color.r - 60, 0), math.max(color.g - 60, 0), math.max(color.b - 60, 0)))
|
||||
else
|
||||
self:SetTextColorInternal(color)
|
||||
end
|
||||
|
||||
BaseClass.SetDisabled(self, bValue)
|
||||
end
|
||||
|
||||
function PANEL:OnCursorEntered()
|
||||
if (self:GetDisabled()) then
|
||||
return
|
||||
end
|
||||
|
||||
local color = self:GetTextColor()
|
||||
self:SetTextColorInternal(Color(math.max(color.r - 25, 0), math.max(color.g - 25, 0), math.max(color.b - 25, 0)))
|
||||
|
||||
self:CreateAnimation(0.15, {
|
||||
target = {currentBackgroundAlpha = self.backgroundAlpha}
|
||||
})
|
||||
|
||||
LocalPlayer():EmitSound("Helix.Rollover")
|
||||
end
|
||||
|
||||
function PANEL:OnCursorExited()
|
||||
if (self:GetDisabled()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self.color) then
|
||||
self:SetTextColor(self.color)
|
||||
else
|
||||
self:SetTextColor(color_white)
|
||||
end
|
||||
|
||||
self:CreateAnimation(0.15, {
|
||||
target = {currentBackgroundAlpha = 0}
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed(code)
|
||||
if (self:GetDisabled()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self.color) then
|
||||
self:SetTextColor(self.color)
|
||||
else
|
||||
self:SetTextColor(ix.config.Get("color"))
|
||||
end
|
||||
|
||||
LocalPlayer():EmitSound("Helix.Press")
|
||||
|
||||
if (code == MOUSE_LEFT and self.DoClick) then
|
||||
self:DoClick(self)
|
||||
elseif (code == MOUSE_RIGHT and self.DoRightClick) then
|
||||
self:DoRightClick(self)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnMouseReleased(key)
|
||||
if (self:GetDisabled()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (self.color) then
|
||||
self:SetTextColor(self.color)
|
||||
else
|
||||
self:SetTextColor(color_white)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixMenuButton", PANEL, "DButton")
|
||||
|
||||
-- selection menu button
|
||||
DEFINE_BASECLASS("ixMenuButton")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
|
||||
AccessorFunc(PANEL, "selected", "Selected", FORCE_BOOL)
|
||||
AccessorFunc(PANEL, "buttonList", "ButtonList")
|
||||
|
||||
function PANEL:Init()
|
||||
self.backgroundColor = color_white
|
||||
self.selected = false
|
||||
self.buttonList = {}
|
||||
self.sectionPanel = nil -- sub-sections this button has; created only if it has any sections
|
||||
end
|
||||
|
||||
function PANEL:PaintBackground(width, height)
|
||||
local alpha = self.selected and 255 or self.currentBackgroundAlpha
|
||||
|
||||
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ColorAlpha(self.backgroundColor, alpha))
|
||||
end
|
||||
|
||||
function PANEL:SetSelected(bValue, bSelectedSection)
|
||||
self.selected = bValue
|
||||
|
||||
if (bValue) then
|
||||
self:OnSelected()
|
||||
|
||||
if (self.sectionPanel) then
|
||||
self.sectionPanel:Show()
|
||||
elseif (self.sectionParent) then
|
||||
self.sectionParent.sectionPanel:Show()
|
||||
end
|
||||
elseif (self.sectionPanel and self.sectionPanel:IsVisible() and !bSelectedSection) then
|
||||
self.sectionPanel:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetButtonList(list, bNoAdd)
|
||||
if (!bNoAdd) then
|
||||
list[#list + 1] = self
|
||||
end
|
||||
|
||||
self.buttonList = list
|
||||
end
|
||||
|
||||
function PANEL:GetSectionPanel()
|
||||
return self.sectionPanel
|
||||
end
|
||||
|
||||
function PANEL:AddSection(name)
|
||||
if (!IsValid(self.sectionPanel)) then
|
||||
-- add section panel to regular button list
|
||||
self.sectionPanel = vgui.Create("ixMenuSelectionList", self:GetParent())
|
||||
self.sectionPanel:Dock(self:GetDock())
|
||||
self.sectionPanel:SetParentButton(self)
|
||||
end
|
||||
|
||||
return self.sectionPanel:AddButton(name, self.buttonList)
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed(key)
|
||||
for _, v in pairs(self.buttonList) do
|
||||
if (IsValid(v) and v != self) then
|
||||
v:SetSelected(false, self.sectionParent == v)
|
||||
end
|
||||
end
|
||||
|
||||
self:SetSelected(true)
|
||||
BaseClass.OnMousePressed(self, key)
|
||||
end
|
||||
|
||||
function PANEL:OnSelected()
|
||||
end
|
||||
|
||||
vgui.Register("ixMenuSelectionButton", PANEL, "ixMenuButton")
|
||||
|
||||
-- collapsable list for menu button sections
|
||||
PANEL = {}
|
||||
AccessorFunc(PANEL, "parent", "ParentButton")
|
||||
|
||||
function PANEL:Init()
|
||||
self.parent = nil -- button that is responsible for controlling this list
|
||||
self.height = 0
|
||||
self.targetHeight = 0
|
||||
|
||||
self:DockPadding(0, 1, 0, 1)
|
||||
self:SetVisible(false)
|
||||
self:SetTall(0)
|
||||
end
|
||||
|
||||
function PANEL:AddButton(name, buttonList)
|
||||
assert(IsValid(self.parent), "attempted to add button to ixMenuSelectionList without a ParentButton")
|
||||
assert(buttonList ~= nil, "attempted to add button to ixMenuSelectionList without a buttonList")
|
||||
|
||||
local button = self:Add("ixMenuSelectionButton")
|
||||
button.sectionParent = self.parent
|
||||
button:SetTextInset(buttonPadding * 2, 0)
|
||||
button:SetPadding(nil, 8, nil, 8)
|
||||
button:SetFont("ixMenuButtonFontSmall")
|
||||
button:Dock(TOP)
|
||||
button:SetText(name)
|
||||
button:SizeToContents()
|
||||
button:SetButtonList(buttonList)
|
||||
button:SetBackgroundColor(self.parent:GetBackgroundColor())
|
||||
|
||||
self.targetHeight = self.targetHeight + button:GetTall()
|
||||
return button
|
||||
end
|
||||
|
||||
function PANEL:Show()
|
||||
self:SetVisible(true)
|
||||
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 1,
|
||||
target = {
|
||||
height = self.targetHeight + 2 -- +2 for padding
|
||||
},
|
||||
easing = "outQuart",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetTall(panel.height)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:Hide()
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 1,
|
||||
target = {
|
||||
height = 0
|
||||
},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetTall(panel.height)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel:SetVisible(false)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetDrawColor(Color(255, 255, 255, 33))
|
||||
surface.DrawRect(0, 0, width, 1)
|
||||
surface.DrawRect(0, height - 1, width, 1)
|
||||
end
|
||||
|
||||
vgui.Register("ixMenuSelectionList", PANEL, "Panel")
|
||||
130
gamemodes/helix/gamemode/core/derma/cl_modelpanel.lua
Normal file
130
gamemodes/helix/gamemode/core/derma/cl_modelpanel.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
DEFINE_BASECLASS("DModelPanel")
|
||||
|
||||
local PANEL = {}
|
||||
local MODEL_ANGLE = Angle(0, 45, 0)
|
||||
|
||||
function PANEL:Init()
|
||||
self.brightness = 1
|
||||
self:SetCursor("none")
|
||||
end
|
||||
|
||||
function PANEL:SetModel(model, skin, bodygroups)
|
||||
if (IsValid(self.Entity)) then
|
||||
self.Entity:Remove()
|
||||
self.Entity = nil
|
||||
end
|
||||
|
||||
if (!ClientsideModel) then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = ClientsideModel(model, RENDERGROUP_OPAQUE)
|
||||
|
||||
if (!IsValid(entity)) then
|
||||
return
|
||||
end
|
||||
|
||||
entity:SetNoDraw(true)
|
||||
entity:SetIK(false)
|
||||
|
||||
if (skin) then
|
||||
entity:SetSkin(skin)
|
||||
end
|
||||
|
||||
if (isstring(bodygroups)) then
|
||||
entity:SetBodyGroups(bodygroups)
|
||||
end
|
||||
|
||||
local sequence = entity:LookupSequence("idle_unarmed")
|
||||
|
||||
if (sequence <= 0) then
|
||||
sequence = entity:SelectWeightedSequence(ACT_IDLE)
|
||||
end
|
||||
|
||||
if (sequence > 0) then
|
||||
entity:ResetSequence(sequence)
|
||||
else
|
||||
local found = false
|
||||
|
||||
for _, v in ipairs(entity:GetSequenceList()) do
|
||||
if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then
|
||||
entity:ResetSequence(v)
|
||||
found = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!found) then
|
||||
entity:ResetSequence(4)
|
||||
end
|
||||
end
|
||||
|
||||
self.Entity = entity
|
||||
end
|
||||
|
||||
function PANEL:LayoutEntity()
|
||||
local scrW, scrH = ScrW(), ScrH()
|
||||
local xRatio = gui.MouseX() / scrW
|
||||
local yRatio = gui.MouseY() / scrH
|
||||
local x, _ = self:LocalToScreen(self:GetWide() / 2)
|
||||
local xRatio2 = x / scrW
|
||||
local entity = self.Entity
|
||||
|
||||
entity:SetPoseParameter("head_pitch", yRatio*90 - 30)
|
||||
entity:SetPoseParameter("head_yaw", (xRatio - xRatio2)*90 - 5)
|
||||
entity:SetAngles(MODEL_ANGLE)
|
||||
entity:SetIK(false)
|
||||
|
||||
if (self.copyLocalSequence) then
|
||||
entity:SetSequence(LocalPlayer():GetSequence())
|
||||
entity:SetPoseParameter("move_yaw", 360 * LocalPlayer():GetPoseParameter("move_yaw") - 180)
|
||||
end
|
||||
|
||||
self:RunAnimation()
|
||||
end
|
||||
|
||||
function PANEL:DrawModel()
|
||||
local brightness = self.brightness * 0.4
|
||||
local brightness2 = self.brightness * 1.5
|
||||
|
||||
render.SetStencilEnable(false)
|
||||
render.SetColorMaterial()
|
||||
render.SetColorModulation(1, 1, 1)
|
||||
render.SetModelLighting(0, brightness2, brightness2, brightness2)
|
||||
|
||||
for i = 1, 4 do
|
||||
render.SetModelLighting(i, brightness, brightness, brightness)
|
||||
end
|
||||
|
||||
local fraction = (brightness / 1) * 0.1
|
||||
|
||||
render.SetModelLighting(5, fraction, fraction, fraction)
|
||||
|
||||
-- Excecute Some stuffs
|
||||
if (self.enableHook) then
|
||||
hook.Run("DrawHelixModelView", self, self.Entity)
|
||||
end
|
||||
|
||||
self.Entity:DrawModel()
|
||||
|
||||
if (self.enableHook) then
|
||||
hook.Run("PostDrawHelixModelView", self, self.Entity)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed()
|
||||
end
|
||||
|
||||
vgui.Register("ixModelPanel", PANEL, "DModelPanel")
|
||||
280
gamemodes/helix/gamemode/core/derma/cl_notice.lua
Normal file
280
gamemodes/helix/gamemode/core/derma/cl_notice.lua
Normal file
@@ -0,0 +1,280 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local animationTime = 0.75
|
||||
|
||||
-- notice manager
|
||||
-- this manages positions/animations for notice panels
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(ScrW() * 0.4, ScrH())
|
||||
self:SetPos(ScrW() - ScrW() * 0.4, 0)
|
||||
self:SetZPos(-99999)
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
|
||||
self.notices = {}
|
||||
self.padding = 4
|
||||
end
|
||||
|
||||
function PANEL:GetAll()
|
||||
return self.notices
|
||||
end
|
||||
|
||||
function PANEL:Clear()
|
||||
for _, v in ipairs(self.notices) do
|
||||
self:RemoveNotice(v)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:AddNotice(text, bError)
|
||||
if (IsValid(ix.gui.characterMenu) and !ix.gui.characterMenu.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
local textLength = text:utf8len()
|
||||
|
||||
local panel = self:Add("ixNotice")
|
||||
panel:SetText(text)
|
||||
panel:SetError(bError or text:utf8sub(textLength, textLength) == "!")
|
||||
panel:SizeToContents()
|
||||
panel.currentY = -panel:GetTall()
|
||||
panel:SetPos(self.padding, panel.currentY)
|
||||
|
||||
-- setup duration timer
|
||||
panel:CreateAnimation(ix.option.Get("noticeDuration", 8), {
|
||||
index = 2,
|
||||
target = {duration = 1},
|
||||
bIgnoreConfig = true,
|
||||
|
||||
OnComplete = function(animation, this)
|
||||
self:RemoveNotice(this)
|
||||
end
|
||||
})
|
||||
|
||||
table.insert(self.notices, 1, panel)
|
||||
self:Organize()
|
||||
|
||||
-- remove old notice if we've hit the limit of notices
|
||||
if (#self.notices > ix.option.Get("noticeMax", 4)) then
|
||||
for i = #self.notices, 1, -1 do
|
||||
local notice = self.notices[i]
|
||||
|
||||
if (IsValid(notice) and !notice.bClosing) then
|
||||
self:RemoveNotice(notice)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:RemoveNotice(panel)
|
||||
panel.bClosing = true
|
||||
panel:CreateAnimation(animationTime, {
|
||||
index = 3,
|
||||
target = {outAnimation = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
OnComplete = function(animation, this)
|
||||
local toRemove
|
||||
|
||||
for k, v in ipairs(self.notices) do
|
||||
if (v == this) then
|
||||
toRemove = k
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (toRemove) then
|
||||
table.remove(self.notices, toRemove)
|
||||
end
|
||||
|
||||
this:SetText("") -- (hack) text remains for a frame after remove is called, so let's make sure we don't draw it
|
||||
this:Remove()
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
-- update target Y positions and animations
|
||||
function PANEL:Organize()
|
||||
local currentTarget = self.padding
|
||||
|
||||
for _, v in ipairs(self.notices) do
|
||||
v:CreateAnimation(animationTime, {
|
||||
index = 1,
|
||||
target = {currentY = currentTarget},
|
||||
easing = "outElastic",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetPos(
|
||||
self:GetWide() - panel:GetWide() - self.padding,
|
||||
math.min(panel.currentY + 1, currentTarget) -- easing eventually hits subpixel movement so we level it off
|
||||
)
|
||||
end
|
||||
})
|
||||
|
||||
currentTarget = currentTarget + self.padding + v:GetTall()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixNoticeManager", PANEL, "Panel")
|
||||
|
||||
-- notice panel
|
||||
-- these do not manage their own enter/exit animations or lifetime
|
||||
DEFINE_BASECLASS("DLabel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "bError", "Error", FORCE_BOOL)
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(256, 36)
|
||||
self:SetContentAlignment(5)
|
||||
self:SetExpensiveShadow(1, Color(0, 0, 0, 150))
|
||||
self:SetFont("ixNoticeFont")
|
||||
self:SetTextColor(color_white)
|
||||
self:SetDrawOnTop(true)
|
||||
self:DockPadding(0, 0, 0, 0)
|
||||
self:DockMargin(0, 0, 0, 0)
|
||||
|
||||
self.bError = false
|
||||
self.bHovered = false
|
||||
|
||||
self.errorAnimation = 0
|
||||
self.padding = 8
|
||||
self.currentY = 0
|
||||
self.duration = 0
|
||||
self.outAnimation = 1
|
||||
self.alpha = 255
|
||||
|
||||
LocalPlayer():EmitSound("Helix.Notify")
|
||||
end
|
||||
|
||||
function PANEL:SetError(bValue)
|
||||
self.bError = tobool(bValue)
|
||||
|
||||
if (bValue) then
|
||||
self.errorAnimation = 1
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 5,
|
||||
target = {errorAnimation = 0},
|
||||
easing = "outQuint"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local contentWidth, contentHeight = self:GetContentSize()
|
||||
contentWidth = contentWidth + self.padding * 2
|
||||
contentHeight = contentHeight + self.padding * 2
|
||||
|
||||
local manager = ix.gui.notices
|
||||
local maxWidth = math.min(IsValid(manager) and (manager:GetWide() - manager:GetPadding() * 2) or ScrW(), contentWidth)
|
||||
|
||||
if (contentWidth > maxWidth) then
|
||||
self:SetWide(maxWidth)
|
||||
self:SetTextInset(self.padding * 2, 0)
|
||||
self:SetWrap(true)
|
||||
|
||||
self:SizeToContentsY()
|
||||
self:SetWide(self:GetContentSize())
|
||||
else
|
||||
self:SetSize(contentWidth, contentHeight)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SizeToContentsY()
|
||||
BaseClass.SizeToContentsY(self)
|
||||
self:SetTall(self:GetTall() + self.padding * 2)
|
||||
end
|
||||
|
||||
function PANEL:OnMouseHover()
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
index = 4,
|
||||
target = {alpha = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.alpha)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:OnMouseLeave()
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
index = 4,
|
||||
target = {alpha = 255},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.alpha)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
if (self.outAnimation < 1) then
|
||||
local x, y = self:LocalToScreen(0, 0)
|
||||
render.SetScissorRect(x, y, x + self:GetWide(), y + (self:GetTall() * self.outAnimation), true)
|
||||
end
|
||||
|
||||
local x, y = self:LocalToScreen(0, 0)
|
||||
local mouseX, mouseY = gui.MousePos()
|
||||
|
||||
if (mouseX >= x and mouseX <= x + width and
|
||||
mouseY >= y and mouseY <= y + height) then
|
||||
if (!self.bHovered) then
|
||||
self.bHovered = true
|
||||
self:OnMouseHover()
|
||||
end
|
||||
elseif (self.bHovered) then
|
||||
self.bHovered = false
|
||||
self:OnMouseLeave()
|
||||
end
|
||||
|
||||
ix.util.DrawBlur(self)
|
||||
|
||||
if (self.errorAnimation > 0) then
|
||||
local color = derma.GetColor("Error", self)
|
||||
|
||||
surface.SetDrawColor(
|
||||
color.r * self.errorAnimation,
|
||||
color.g * self.errorAnimation,
|
||||
color.b * self.errorAnimation,
|
||||
self.errorAnimation * 255 + ((1 - self.errorAnimation) * 66)
|
||||
)
|
||||
else
|
||||
surface.SetDrawColor(0, 0, 0, 66)
|
||||
end
|
||||
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
surface.SetDrawColor(self.bError and derma.GetColor("Error", self) or ix.config.Get("color"))
|
||||
surface.DrawRect(0, height - 1, width * self.duration, 1)
|
||||
end
|
||||
|
||||
function PANEL:PaintOver(width, height)
|
||||
render.SetScissorRect(0, 0, 0, 0, false)
|
||||
end
|
||||
|
||||
vgui.Register("ixNotice", PANEL, "DLabel")
|
||||
|
||||
if (IsValid(ix.gui.notices)) then
|
||||
ix.gui.notices:Remove()
|
||||
ix.gui.notices = vgui.Create("ixNoticeManager")
|
||||
else
|
||||
ix.gui.notices = vgui.Create("ixNoticeManager")
|
||||
end
|
||||
98
gamemodes/helix/gamemode/core/derma/cl_noticebar.lua
Normal file
98
gamemodes/helix/gamemode/core/derma/cl_noticebar.lua
Normal file
@@ -0,0 +1,98 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PANEL = {
|
||||
types = {
|
||||
"Info", -- info
|
||||
"Success", -- success
|
||||
"Error" -- error
|
||||
}
|
||||
}
|
||||
|
||||
AccessorFunc(PANEL, "type", "Type", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "length", "Length", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "hidden", "Hidden", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
self.type = 1
|
||||
self.padding = 8
|
||||
self.length = 4
|
||||
self.currentY = 0
|
||||
self.hidden = true
|
||||
|
||||
self.text = self:Add("DLabel")
|
||||
self.text:SetFont("ixNoticeFont")
|
||||
self.text:SetContentAlignment(5)
|
||||
self.text:SetTextColor(color_white)
|
||||
self.text:SizeToContents()
|
||||
self.text:Dock(FILL)
|
||||
|
||||
self:SetSize(self:GetParent():GetWide() - (self.padding * 4), self.text:GetTall() + (self.padding * 2))
|
||||
self:SetPos(self.padding * 2, -self:GetTall() - self.padding)
|
||||
end
|
||||
|
||||
function PANEL:SetFont(value)
|
||||
self.text:SetFont(value)
|
||||
self.text:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:SetText(text)
|
||||
self.text:SetText(text)
|
||||
self.text:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:Slide(direction, length)
|
||||
direction = direction or "up"
|
||||
length = length or 0.5
|
||||
|
||||
timer.Remove("ixNoticeBarAnimation")
|
||||
|
||||
local x, _ = self:GetPos()
|
||||
local baseY = direction == "up" and self.padding * 2 or (-self:GetTall() - self.padding)
|
||||
local targetY = direction == "up" and (-self:GetTall() - self.padding) or self.padding * 2
|
||||
local easing = direction == "up" and "outQuint" or "outElastic"
|
||||
|
||||
self:SetPos(x, baseY)
|
||||
self.currentY = baseY
|
||||
self.hidden = direction == "up"
|
||||
|
||||
self:CreateAnimation(length, {
|
||||
target = {currentY = targetY},
|
||||
easing = easing,
|
||||
|
||||
Think = function(animation, panel)
|
||||
local lastX, _ = panel:GetPos()
|
||||
panel:SetPos(lastX, panel.currentY)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:Show(bRemove)
|
||||
self:Slide("down")
|
||||
|
||||
timer.Create("ixNoticeBarAnimation", self.length - 0.5, 1, function()
|
||||
if (!IsValid(self)) then
|
||||
return
|
||||
end
|
||||
|
||||
self:Slide("up")
|
||||
end)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
local color = derma.GetColor(self.types[self.type], self)
|
||||
|
||||
surface.SetDrawColor(color)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixNoticeBar", PANEL, "Panel")
|
||||
253
gamemodes/helix/gamemode/core/derma/cl_overrides.lua
Normal file
253
gamemodes/helix/gamemode/core/derma/cl_overrides.lua
Normal file
@@ -0,0 +1,253 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- overrides standard derma panels to add/change functionality
|
||||
|
||||
local PANEL = {}
|
||||
local OVERRIDES = {}
|
||||
|
||||
-- @todo remove me when autorefresh support is no longer needed
|
||||
local function OverridePanel(name, func)
|
||||
PANEL = vgui.GetControlTable(name)
|
||||
|
||||
if (!istable(PANEL)) then
|
||||
return
|
||||
end
|
||||
|
||||
OVERRIDES = {}
|
||||
func()
|
||||
|
||||
for k, _ in pairs(PANEL) do
|
||||
local overrideName = "ix" .. k
|
||||
|
||||
if (PANEL[overrideName] and !OVERRIDES[k]) then
|
||||
print("unhooking override ", overrideName)
|
||||
|
||||
PANEL[k] = PANEL[overrideName]
|
||||
PANEL[overrideName] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function Override(name)
|
||||
local oldMethod = "ix" .. name
|
||||
OVERRIDES[name] = true
|
||||
|
||||
if (PANEL[oldMethod]) then
|
||||
return
|
||||
end
|
||||
|
||||
PANEL[oldMethod] = PANEL[name]
|
||||
end
|
||||
|
||||
OverridePanel("DMenuOption", function()
|
||||
function PANEL:PerformLayout()
|
||||
self:SizeToContents()
|
||||
self:SetWide(self:GetWide() + 30)
|
||||
|
||||
local w = math.max(self:GetParent():GetWide(), self:GetWide())
|
||||
|
||||
self:SetSize(w, self:GetTall() + 4)
|
||||
|
||||
if (self.SubMenuArrow) then
|
||||
self.SubMenuArrow:SetSize(15, 15)
|
||||
self.SubMenuArrow:CenterVertical()
|
||||
self.SubMenuArrow:AlignRight(4)
|
||||
end
|
||||
|
||||
DButton.PerformLayout(self)
|
||||
end
|
||||
end)
|
||||
|
||||
OverridePanel("DMenu", function()
|
||||
local animationTime = 0.33
|
||||
|
||||
Override("Init")
|
||||
function PANEL:Init(...)
|
||||
self:ixInit(...)
|
||||
|
||||
self.ixAnimation = 0
|
||||
end
|
||||
|
||||
function PANEL:SetFont(font)
|
||||
for _, v in pairs(self:GetCanvas():GetChildren()) do
|
||||
v:SetFont(font)
|
||||
v:SizeToContents()
|
||||
end
|
||||
|
||||
-- reposition for the new font
|
||||
self:InvalidateLayout(true)
|
||||
self:Open(self.ixX, self.ixY, false, self.ixOwnerPanel)
|
||||
end
|
||||
|
||||
Override("SetSize")
|
||||
function PANEL:SetSize(width, height)
|
||||
self:ixSetSize(width, height)
|
||||
self.ixTargetHeight = height
|
||||
end
|
||||
|
||||
Override("PerformLayout")
|
||||
function PANEL:PerformLayout(...)
|
||||
self:ixPerformLayout(...)
|
||||
|
||||
if (self.ixAnimating) then
|
||||
self.VBar:SetAlpha(0) -- setvisible doesn't seem to work here
|
||||
self:SetTall(self.ixAnimation * self.ixTargetHeight)
|
||||
else
|
||||
self.VBar:SetAlpha(255)
|
||||
end
|
||||
end
|
||||
|
||||
Override("OnMouseWheeled")
|
||||
function PANEL:OnMouseWheeled(delta)
|
||||
self:ixOnMouseWheeled(delta)
|
||||
|
||||
-- don't allow the input event to fall through
|
||||
return true
|
||||
end
|
||||
|
||||
Override("AddOption")
|
||||
function PANEL:AddOption(...)
|
||||
local panel = self:ixAddOption(...)
|
||||
|
||||
panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black))
|
||||
panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
Override("AddSubMenu")
|
||||
function PANEL:AddSubMenu(...)
|
||||
local menu, panel = self:ixAddSubMenu(...)
|
||||
|
||||
panel:SetTextColor(derma.GetColor("MenuLabel", self, color_black))
|
||||
panel:SetTextInset(6, 0) -- there is no icon functionality in DComboBoxes
|
||||
|
||||
return menu, panel
|
||||
end
|
||||
|
||||
Override("Open")
|
||||
function PANEL:Open(x, y, bSkipAnimation, ownerPanel)
|
||||
self.ixX, self.ixY, self.ixOwnerPanel = x, y, ownerPanel
|
||||
self:ixOpen(x, y, bSkipAnimation, ownerPanel)
|
||||
|
||||
if (ix.option.Get("disableAnimations")) then
|
||||
return
|
||||
end
|
||||
|
||||
-- remove pac3 derma menu hooks since animations don't play nicely
|
||||
hook.Remove("CloseDermaMenus", self)
|
||||
hook.Remove("Think", self)
|
||||
|
||||
self.ixAnimating = true
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 1,
|
||||
target = {ixAnimation = 1},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:InvalidateLayout(true)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel.ixAnimating = nil
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
Override("Hide")
|
||||
function PANEL:Hide()
|
||||
if (ix.option.Get("disableAnimations")) then
|
||||
self:ixHide()
|
||||
return
|
||||
end
|
||||
|
||||
self.ixAnimating = true
|
||||
self:SetVisible(true)
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
index = 1,
|
||||
target = {ixAnimation = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:InvalidateLayout(true)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel.ixAnimating = false
|
||||
panel:ixHide()
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
Override("Remove")
|
||||
function PANEL:Remove()
|
||||
if (self.ixRemoving) then
|
||||
return
|
||||
end
|
||||
|
||||
if (ix.option.Get("disableAnimations")) then
|
||||
self:ixRemove()
|
||||
return
|
||||
end
|
||||
|
||||
self.ixAnimating = true
|
||||
self.ixRemoving = true
|
||||
self:SetVisible(true)
|
||||
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
index = 1,
|
||||
target = {ixAnimation = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:InvalidateLayout(true)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel:ixRemove()
|
||||
end
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
OverridePanel("DComboBox", function()
|
||||
Override("OpenMenu")
|
||||
function PANEL:OpenMenu()
|
||||
self:ixOpenMenu()
|
||||
|
||||
if (IsValid(self.Menu)) then
|
||||
local _, y = self.Menu:LocalToScreen(self.Menu:GetPos())
|
||||
|
||||
self.Menu:SetFont(self:GetFont())
|
||||
self.Menu:SetMaxHeight(ScrH() - y)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
OverridePanel("DScrollPanel", function()
|
||||
Override("ScrollToChild")
|
||||
function PANEL:ScrollToChild(panel)
|
||||
-- docked panels required InvalidateParent in order to retrieve their position correctly
|
||||
if (panel:GetDock() != NODOCK) then
|
||||
panel:InvalidateParent(true)
|
||||
else
|
||||
self:PerformLayout()
|
||||
end
|
||||
|
||||
local _, y = self.pnlCanvas:GetChildPosition(panel)
|
||||
|
||||
y = y + panel:GetTall() * 0.5
|
||||
y = y - self:GetTall() * 0.5
|
||||
|
||||
self.VBar:SetScroll(y)
|
||||
end
|
||||
end)
|
||||
359
gamemodes/helix/gamemode/core/derma/cl_scoreboard.lua
Normal file
359
gamemodes/helix/gamemode/core/derma/cl_scoreboard.lua
Normal file
@@ -0,0 +1,359 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local rowPaintFunctions = {
|
||||
function(width, height)
|
||||
end,
|
||||
|
||||
function(width, height)
|
||||
surface.SetDrawColor(30, 30, 30, 25)
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
}
|
||||
|
||||
-- character icon
|
||||
-- we can't customize the rendering of ModelImage so we have to do it ourselves
|
||||
local PANEL = {}
|
||||
local BODYGROUPS_EMPTY = "000000000"
|
||||
|
||||
AccessorFunc(PANEL, "model", "Model", FORCE_STRING)
|
||||
AccessorFunc(PANEL, "bHidden", "Hidden", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(64, 64)
|
||||
self.bodygroups = BODYGROUPS_EMPTY
|
||||
end
|
||||
|
||||
function PANEL:SetModel(model, skin, bodygroups)
|
||||
model = model:gsub("\\", "/")
|
||||
|
||||
if (isstring(bodygroups)) then
|
||||
if (bodygroups:len() == 9) then
|
||||
for i = 1, bodygroups:len() do
|
||||
self:SetBodygroup(i, tonumber(bodygroups[i]) or 0)
|
||||
end
|
||||
else
|
||||
self.bodygroups = BODYGROUPS_EMPTY
|
||||
end
|
||||
end
|
||||
|
||||
self.model = model
|
||||
self.skin = skin
|
||||
self.path = "materials/spawnicons/" ..
|
||||
model:sub(1, #model - 4) .. -- remove extension
|
||||
((isnumber(skin) and skin > 0) and ("_skin" .. tostring(skin)) or "") .. -- skin number
|
||||
(self.bodygroups != BODYGROUPS_EMPTY and ("_" .. self.bodygroups) or "") .. -- bodygroups
|
||||
".png"
|
||||
|
||||
local material = Material(self.path, "smooth")
|
||||
|
||||
-- we don't have a cached spawnicon texture, so we need to forcefully generate one
|
||||
if (material:IsError()) then
|
||||
self.id = "ixScoreboardIcon" .. self.path
|
||||
self.renderer = self:Add("ModelImage")
|
||||
self.renderer:SetVisible(false)
|
||||
self.renderer:SetModel(model, skin, self.bodygroups)
|
||||
self.renderer:RebuildSpawnIcon()
|
||||
|
||||
-- this is the only way to get a callback for generated spawn icons, it's bad but it's only done once
|
||||
hook.Add("SpawniconGenerated", self.id, function(lastModel, filePath, modelsLeft)
|
||||
filePath = filePath:gsub("\\", "/"):lower()
|
||||
|
||||
if (filePath == self.path) then
|
||||
hook.Remove("SpawniconGenerated", self.id)
|
||||
|
||||
self.material = Material(filePath, "smooth")
|
||||
self.renderer:Remove()
|
||||
end
|
||||
end)
|
||||
else
|
||||
self.material = material
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetBodygroup(k, v)
|
||||
if (k < 0 or k > 8 or v < 0 or v > 9) then
|
||||
return
|
||||
end
|
||||
|
||||
self.bodygroups = self.bodygroups:SetChar(k + 1, v)
|
||||
end
|
||||
|
||||
function PANEL:GetModel()
|
||||
return self.model or "models/error.mdl"
|
||||
end
|
||||
|
||||
function PANEL:GetSkin()
|
||||
return self.skin or 1
|
||||
end
|
||||
|
||||
function PANEL:DoClick()
|
||||
end
|
||||
|
||||
function PANEL:DoRightClick()
|
||||
end
|
||||
|
||||
function PANEL:OnMouseReleased(key)
|
||||
if (key == MOUSE_LEFT) then
|
||||
self:DoClick()
|
||||
elseif (key == MOUSE_RIGHT) then
|
||||
self:DoRightClick()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
if (!self.material) then
|
||||
return
|
||||
end
|
||||
|
||||
surface.SetMaterial(self.material)
|
||||
surface.SetDrawColor(self.bHidden and color_black or color_white)
|
||||
surface.DrawTexturedRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function PANEL:Remove()
|
||||
if (self.id) then
|
||||
hook.Remove("SpawniconGenerated", self.id)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixScoreboardIcon", PANEL, "Panel")
|
||||
|
||||
-- player row
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "paintFunction", "BackgroundPaintFunction")
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetTall(64)
|
||||
|
||||
self.icon = self:Add("ixScoreboardIcon")
|
||||
self.icon:Dock(LEFT)
|
||||
self.icon.DoRightClick = function()
|
||||
local client = self.player
|
||||
|
||||
if (!IsValid(client)) then
|
||||
return
|
||||
end
|
||||
|
||||
local menu = DermaMenu()
|
||||
|
||||
menu:AddOption(L("viewProfile"), function()
|
||||
client:ShowProfile()
|
||||
end)
|
||||
|
||||
menu:AddOption(L("copySteamID"), function()
|
||||
SetClipboardText(client:IsBot() and client:EntIndex() or client:SteamID())
|
||||
end)
|
||||
|
||||
hook.Run("PopulateScoreboardPlayerMenu", client, menu)
|
||||
menu:Open()
|
||||
end
|
||||
|
||||
self.icon:SetHelixTooltip(function(tooltip)
|
||||
local client = self.player
|
||||
|
||||
if (IsValid(self) and IsValid(client)) then
|
||||
ix.hud.PopulatePlayerTooltip(tooltip, client)
|
||||
end
|
||||
end)
|
||||
|
||||
self.name = self:Add("DLabel")
|
||||
self.name:DockMargin(4, 4, 0, 0)
|
||||
self.name:Dock(TOP)
|
||||
self.name:SetTextColor(color_white)
|
||||
self.name:SetFont("ixGenericFont")
|
||||
|
||||
self.description = self:Add("DLabel")
|
||||
self.description:DockMargin(5, 0, 0, 0)
|
||||
self.description:Dock(TOP)
|
||||
self.description:SetTextColor(color_white)
|
||||
self.description:SetFont("ixSmallFont")
|
||||
|
||||
self.paintFunction = rowPaintFunctions[1]
|
||||
self.nextThink = CurTime() + 1
|
||||
end
|
||||
|
||||
function PANEL:Update()
|
||||
local client = self.player
|
||||
local model = client:GetModel()
|
||||
local skin = client:GetSkin()
|
||||
local name = client:GetName()
|
||||
local description = hook.Run("GetCharacterDescription", client) or
|
||||
(client:GetCharacter() and client:GetCharacter():GetDescription()) or ""
|
||||
|
||||
local bRecognize = false
|
||||
local localCharacter = LocalPlayer():GetCharacter()
|
||||
local character = IsValid(self.player) and self.player:GetCharacter()
|
||||
|
||||
if (localCharacter and character) then
|
||||
bRecognize = hook.Run("IsCharacterRecognized", localCharacter, character:GetID())
|
||||
or hook.Run("IsPlayerRecognized", self.player)
|
||||
end
|
||||
|
||||
self.icon:SetHidden(!bRecognize)
|
||||
self:SetZPos(bRecognize and 1 or 2)
|
||||
|
||||
-- no easy way to check bodygroups so we'll just set them anyway
|
||||
for _, v in pairs(client:GetBodyGroups()) do
|
||||
self.icon:SetBodygroup(v.id, client:GetBodygroup(v.id))
|
||||
end
|
||||
|
||||
if (self.icon:GetModel() != model or self.icon:GetSkin() != skin) then
|
||||
self.icon:SetModel(model, skin)
|
||||
self.icon:SetTooltip(nil)
|
||||
end
|
||||
|
||||
if (self.name:GetText() != name) then
|
||||
self.name:SetText(name)
|
||||
self.name:SizeToContents()
|
||||
end
|
||||
|
||||
if (self.description:GetText() != description) then
|
||||
self.description:SetText(description)
|
||||
self.description:SizeToContents()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (CurTime() >= self.nextThink) then
|
||||
local client = self.player
|
||||
|
||||
if (!IsValid(client) or !client:GetCharacter() or self.character != client:GetCharacter() or self.team != client:Team()) then
|
||||
self:Remove()
|
||||
self:GetParent():SizeToContents()
|
||||
end
|
||||
|
||||
self.nextThink = CurTime() + 1
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetPlayer(client)
|
||||
self.player = client
|
||||
self.team = client:Team()
|
||||
self.character = client:GetCharacter()
|
||||
|
||||
self:Update()
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
self.paintFunction(width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixScoreboardRow", PANEL, "EditablePanel")
|
||||
|
||||
-- faction grouping
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "faction", "Faction")
|
||||
|
||||
function PANEL:Init()
|
||||
self:DockMargin(0, 0, 0, 16)
|
||||
self:SetTall(32)
|
||||
|
||||
self.nextThink = 0
|
||||
end
|
||||
|
||||
function PANEL:AddPlayer(client, index)
|
||||
if (!IsValid(client) or !client:GetCharacter() or hook.Run("ShouldShowPlayerOnScoreboard", client) == false) then
|
||||
return false
|
||||
end
|
||||
|
||||
local id = index % 2 == 0 and 1 or 2
|
||||
local panel = self:Add("ixScoreboardRow")
|
||||
panel:SetPlayer(client)
|
||||
panel:Dock(TOP)
|
||||
panel:SetZPos(2)
|
||||
panel:SetBackgroundPaintFunction(rowPaintFunctions[id])
|
||||
|
||||
self:SizeToContents()
|
||||
client.ixScoreboardSlot = panel
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function PANEL:SetFaction(faction)
|
||||
self:SetColor(faction.color)
|
||||
self:SetText(L(faction.name))
|
||||
|
||||
self.faction = faction
|
||||
end
|
||||
|
||||
function PANEL:Update()
|
||||
local faction = self.faction
|
||||
|
||||
if (team.NumPlayers(faction.index) == 0) then
|
||||
self:SetVisible(false)
|
||||
self:GetParent():InvalidateLayout()
|
||||
else
|
||||
local bHasPlayers
|
||||
|
||||
for k, v in ipairs(team.GetPlayers(faction.index)) do
|
||||
if (!IsValid(v.ixScoreboardSlot)) then
|
||||
if (self:AddPlayer(v, k)) then
|
||||
bHasPlayers = true
|
||||
end
|
||||
else
|
||||
v.ixScoreboardSlot:Update()
|
||||
bHasPlayers = true
|
||||
end
|
||||
end
|
||||
|
||||
self:SetVisible(bHasPlayers)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixScoreboardFaction", PANEL, "ixCategoryPanel")
|
||||
|
||||
-- main scoreboard panel
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.gui.scoreboard)) then
|
||||
ix.gui.scoreboard:Remove()
|
||||
end
|
||||
|
||||
self:Dock(FILL)
|
||||
|
||||
self.factions = {}
|
||||
self.nextThink = 0
|
||||
|
||||
for i = 1, #ix.faction.indices do
|
||||
local faction = ix.faction.indices[i]
|
||||
|
||||
local panel = self:Add("ixScoreboardFaction")
|
||||
panel:SetFaction(faction)
|
||||
panel:Dock(TOP)
|
||||
|
||||
self.factions[i] = panel
|
||||
end
|
||||
|
||||
ix.gui.scoreboard = self
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (CurTime() >= self.nextThink) then
|
||||
for i = 1, #self.factions do
|
||||
local factionPanel = self.factions[i]
|
||||
|
||||
factionPanel:Update()
|
||||
end
|
||||
|
||||
self.nextThink = CurTime() + 0.5
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixScoreboard", PANEL, "DScrollPanel")
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixScoreboard", function(tabs)
|
||||
tabs["scoreboard"] = function(container)
|
||||
container:Add("ixScoreboard")
|
||||
end
|
||||
end)
|
||||
781
gamemodes/helix/gamemode/core/derma/cl_settings.lua
Normal file
781
gamemodes/helix/gamemode/core/derma/cl_settings.lua
Normal file
@@ -0,0 +1,781 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local panelMap = {
|
||||
[ix.type.bool] = "ixSettingsRowBool",
|
||||
[ix.type.array] = "ixSettingsRowArray",
|
||||
[ix.type.string] = "ixSettingsRowString",
|
||||
[ix.type.number] = "ixSettingsRowNumber",
|
||||
[ix.type.color] = "ixSettingsRowColor"
|
||||
}
|
||||
|
||||
local function EmitChange(pitch)
|
||||
LocalPlayer():EmitSound("weapons/ar2/ar2_empty.wav", 75, pitch or 150, 0.25)
|
||||
end
|
||||
|
||||
-- color setting
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self.color = table.Copy(color_white)
|
||||
self.padding = 4
|
||||
|
||||
self.panel = self:Add("Panel")
|
||||
self.panel:SetCursor("hand")
|
||||
self.panel:SetMouseInputEnabled(true)
|
||||
self.panel:Dock(RIGHT)
|
||||
self.panel.Paint = function(panel, width, height)
|
||||
local padding = self.padding
|
||||
|
||||
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
|
||||
surface.SetDrawColor(self.color)
|
||||
surface.DrawRect(padding, padding, width - padding * 2, height - padding * 2)
|
||||
end
|
||||
|
||||
self.panel.OnMousePressed = function(panel, key)
|
||||
if (key == MOUSE_LEFT) then
|
||||
self:OpenPicker()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OpenPicker()
|
||||
if (IsValid(self.picker)) then
|
||||
self.picker:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
self.picker = vgui.Create("ixSettingsRowColorPicker")
|
||||
self.picker:Attach(self)
|
||||
self.picker:SetValue(self.color)
|
||||
|
||||
self.picker.OnValueChanged = function(panel)
|
||||
local newColor = panel:GetValue()
|
||||
|
||||
if (newColor != self.color) then
|
||||
self.color = newColor
|
||||
self:OnValueChanged(newColor)
|
||||
end
|
||||
end
|
||||
|
||||
self.picker.OnValueUpdated = function(panel)
|
||||
self.color = panel:GetValue()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value)
|
||||
self.color = Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255)
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return self.color
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
surface.SetFont("ixMenuButtonFont")
|
||||
local totalWidth = surface.GetTextSize("999")
|
||||
|
||||
self.panel:SetSize(totalWidth + self.padding * 2, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRowColor", PANEL, "ixSettingsRow")
|
||||
|
||||
-- color setting picker
|
||||
DEFINE_BASECLASS("Panel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
self.m_bIsMenuComponent = true
|
||||
self.bDeleteSelf = true
|
||||
|
||||
self.realHeight = 200
|
||||
self.height = 200
|
||||
self:SetSize(250, 200)
|
||||
self:DockPadding(4, 4, 4, 4)
|
||||
|
||||
self.picker = self:Add("DColorMixer")
|
||||
self.picker:Dock(FILL)
|
||||
self.picker.ValueChanged = function()
|
||||
self:OnValueUpdated()
|
||||
end
|
||||
|
||||
self:MakePopup()
|
||||
RegisterDermaMenuForClose(self)
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value)
|
||||
self.picker:SetColor(Color(value.r or 255, value.g or 255, value.b or 255, value.a or 255))
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return self.picker:GetColor()
|
||||
end
|
||||
|
||||
function PANEL:Attach(panel)
|
||||
self.attached = panel
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
local panel = self.attached
|
||||
|
||||
if (IsValid(panel)) then
|
||||
local width, height = self:GetSize()
|
||||
local x, y = panel:LocalToScreen(0, 0)
|
||||
|
||||
self:SetPos(
|
||||
math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width),
|
||||
math.Clamp(y + panel:GetTall(), 0, ScrH() - height)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function PANEL:OnValueChanged()
|
||||
end
|
||||
|
||||
function PANEL:OnValueUpdated()
|
||||
end
|
||||
|
||||
function PANEL:Remove()
|
||||
if (self.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
self:OnValueChanged()
|
||||
|
||||
-- @todo open/close animations
|
||||
self.bClosing = true
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
BaseClass.Remove(self)
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRowColorPicker", PANEL, "EditablePanel")
|
||||
|
||||
-- number setting
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.setting = self:Add("ixNumSlider")
|
||||
self.setting.nextUpdate = 0
|
||||
self.setting:Dock(RIGHT)
|
||||
self.setting.OnValueChanged = function(panel)
|
||||
self:OnValueChanged(self:GetValue())
|
||||
end
|
||||
self.setting.OnValueUpdated = function(panel)
|
||||
local fraction = panel:GetFraction()
|
||||
|
||||
if (fraction == 0) then
|
||||
EmitChange(75)
|
||||
return
|
||||
elseif (fraction == 1) then
|
||||
EmitChange(120)
|
||||
return
|
||||
end
|
||||
|
||||
if (SysTime() > panel.nextUpdate) then
|
||||
EmitChange(85 + fraction * 15)
|
||||
panel.nextUpdate = SysTime() + 0.05
|
||||
end
|
||||
end
|
||||
|
||||
local panel = self.setting:GetLabel()
|
||||
panel:SetCursor("hand")
|
||||
panel:SetMouseInputEnabled(true)
|
||||
panel.OnMousePressed = function(_, key)
|
||||
if (key == MOUSE_LEFT) then
|
||||
self:OpenEntry()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OpenEntry()
|
||||
if (IsValid(self.entry)) then
|
||||
self.entry:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
self.entry = vgui.Create("ixSettingsRowNumberEntry")
|
||||
self.entry:Attach(self)
|
||||
self.entry:SetValue(self:GetValue(), true)
|
||||
self.entry.OnValueChanged = function(panel)
|
||||
local value = math.Round(panel:GetValue(), self:GetDecimals())
|
||||
|
||||
if (value != self:GetValue()) then
|
||||
self:SetValue(value, true)
|
||||
self:OnValueChanged(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value, bNoNotify)
|
||||
self.setting:SetValue(value, bNoNotify)
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return self.setting:GetValue()
|
||||
end
|
||||
|
||||
function PANEL:SetMin(value)
|
||||
self.setting:SetMin(value)
|
||||
end
|
||||
|
||||
function PANEL:SetMax(value)
|
||||
self.setting:SetMax(value)
|
||||
end
|
||||
|
||||
function PANEL:SetDecimals(value)
|
||||
self.setting:SetDecimals(value)
|
||||
end
|
||||
|
||||
function PANEL:GetDecimals()
|
||||
return self.setting:GetDecimals()
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
self.setting:SetWide(width * 0.5)
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRowNumber", PANEL, "ixSettingsRow")
|
||||
|
||||
-- number setting entry
|
||||
DEFINE_BASECLASS("Panel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "bDeleteSelf", "DeleteSelf", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
surface.SetFont("ixMenuButtonFont")
|
||||
local width, height = surface.GetTextSize("999999")
|
||||
|
||||
self.m_bIsMenuComponent = true
|
||||
self.bDeleteSelf = true
|
||||
|
||||
self.realHeight = 200
|
||||
self.height = 200
|
||||
self:SetSize(width, height)
|
||||
self:DockPadding(4, 4, 4, 4)
|
||||
|
||||
self.textEntry = self:Add("ixTextEntry")
|
||||
self.textEntry:SetNumeric(true)
|
||||
self.textEntry:SetFont("ixMenuButtonFont")
|
||||
self.textEntry:Dock(FILL)
|
||||
self.textEntry:RequestFocus()
|
||||
self.textEntry.OnEnter = function()
|
||||
self:Remove()
|
||||
end
|
||||
|
||||
self:MakePopup()
|
||||
RegisterDermaMenuForClose(self)
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value, bInitial)
|
||||
value = tostring(value)
|
||||
self.textEntry:SetValue(value)
|
||||
|
||||
if (bInitial) then
|
||||
self.textEntry:SetCaretPos(value:utf8len())
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return tonumber(self.textEntry:GetValue()) or 0
|
||||
end
|
||||
|
||||
function PANEL:Attach(panel)
|
||||
self.attached = panel
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
local panel = self.attached
|
||||
|
||||
if (IsValid(panel)) then
|
||||
local width, height = self:GetSize()
|
||||
local x, y = panel:LocalToScreen(0, 0)
|
||||
|
||||
self:SetPos(
|
||||
math.Clamp(x + panel:GetWide() - width, 0, ScrW() - width),
|
||||
math.Clamp(y + panel:GetTall(), 0, ScrH() - height)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
surface.SetDrawColor(derma.GetColor("DarkerBackground", self))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
|
||||
function PANEL:OnValueChanged()
|
||||
end
|
||||
|
||||
function PANEL:OnValueUpdated()
|
||||
end
|
||||
|
||||
function PANEL:Remove()
|
||||
if (self.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
self:OnValueChanged()
|
||||
|
||||
-- @todo open/close animations
|
||||
self.bClosing = true
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
BaseClass.Remove(self)
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRowNumberEntry", PANEL, "EditablePanel")
|
||||
|
||||
-- string setting
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.setting = self:Add("ixTextEntry")
|
||||
self.setting:Dock(RIGHT)
|
||||
self.setting:SetFont("ixMenuButtonFont")
|
||||
self.setting:SetBackgroundColor(derma.GetColor("DarkerBackground", self))
|
||||
self.setting.OnEnter = function()
|
||||
self:OnValueChanged(self:GetValue())
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value)
|
||||
self.setting:SetValue(tostring(value))
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return self.setting:GetValue()
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(width, height)
|
||||
self.setting:SetWide(width * 0.5)
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRowString", PANEL, "ixSettingsRow")
|
||||
|
||||
-- bool setting
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.setting = self:Add("ixCheckBox")
|
||||
self.setting:Dock(RIGHT)
|
||||
self.setting.DoClick = function(panel)
|
||||
self:OnValueChanged(self:GetValue())
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetValue(bValue)
|
||||
bValue = tobool(bValue)
|
||||
|
||||
self.setting:SetChecked(bValue, true)
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return self.setting:GetChecked()
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRowBool", PANEL, "ixSettingsRow")
|
||||
|
||||
-- array setting
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.array = {}
|
||||
|
||||
self.setting = self:Add("DComboBox")
|
||||
self.setting:Dock(RIGHT)
|
||||
self.setting:SetFont("ixMenuButtonFont")
|
||||
self.setting:SetTextColor(color_white)
|
||||
self.setting.OnSelect = function(panel)
|
||||
self:OnValueChanged(self:GetValue())
|
||||
|
||||
panel:SizeToContents()
|
||||
panel:SetWide(panel:GetWide() + 12) -- padding for arrow (nice)
|
||||
|
||||
if (!self.bInitial) then
|
||||
EmitChange()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Populate(key, info)
|
||||
if (!isfunction(info.populate)) then
|
||||
ErrorNoHalt(string.format("expected populate function for array option '%s'", key))
|
||||
return
|
||||
end
|
||||
|
||||
local entries = info.populate()
|
||||
local i = 1
|
||||
|
||||
for k, v in pairs(entries) do
|
||||
self.setting:AddChoice(v, k)
|
||||
self.array[k] = i
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value)
|
||||
self.bInitial = true
|
||||
self.setting:ChooseOptionID(self.array[value])
|
||||
self.bInitial = false
|
||||
end
|
||||
|
||||
function PANEL:GetValue()
|
||||
return select(2, self.setting:GetSelected())
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRowArray", PANEL, "ixSettingsRow")
|
||||
|
||||
-- settings row
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "backgroundIndex", "BackgroundIndex", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "bShowReset", "ShowReset", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
self:DockPadding(4, 4, 4, 4)
|
||||
|
||||
self.text = self:Add("DLabel")
|
||||
self.text:Dock(LEFT)
|
||||
self.text:SetFont("ixMenuButtonFont")
|
||||
self.text:SetExpensiveShadow(1, color_black)
|
||||
|
||||
self.backgroundIndex = 0
|
||||
end
|
||||
|
||||
function PANEL:SetShowReset(value, name, default)
|
||||
value = tobool(value)
|
||||
|
||||
if (value and !IsValid(self.reset)) then
|
||||
self.reset = self:Add("DButton")
|
||||
self.reset:SetFont("ixSmallTitleIcons")
|
||||
self.reset:SetText("x")
|
||||
self.reset:SetTextColor(ColorAlpha(derma.GetColor("Warning", self), 100))
|
||||
self.reset:Dock(LEFT)
|
||||
self.reset:DockMargin(4, 0, 0, 0)
|
||||
self.reset:SizeToContents()
|
||||
self.reset.Paint = nil
|
||||
self.reset.DoClick = function()
|
||||
self:OnResetClicked()
|
||||
end
|
||||
self.reset:SetHelixTooltip(function(tooltip)
|
||||
local title = tooltip:AddRow("title")
|
||||
title:SetImportant()
|
||||
title:SetText(L("resetDefault"))
|
||||
title:SetBackgroundColor(derma.GetColor("Warning", self))
|
||||
title:SizeToContents()
|
||||
|
||||
local description = tooltip:AddRow("description")
|
||||
description:SetText(L("resetDefaultDescription", tostring(name), tostring(default)))
|
||||
description:SizeToContents()
|
||||
end)
|
||||
elseif (!value and IsValid(self.reset)) then
|
||||
self.reset:Remove()
|
||||
end
|
||||
|
||||
self.bShowReset = value
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (IsValid(self.reset)) then
|
||||
self.reset:SetVisible(self:IsHovered() or self:IsOurChild(vgui.GetHoveredPanel()))
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:OnResetClicked()
|
||||
end
|
||||
|
||||
function PANEL:GetLabel()
|
||||
return self.text
|
||||
end
|
||||
|
||||
function PANEL:SetText(text)
|
||||
self.text:SetText(text)
|
||||
self:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:GetText()
|
||||
return self.text:GetText()
|
||||
end
|
||||
|
||||
-- implemented by row types
|
||||
function PANEL:GetValue()
|
||||
end
|
||||
|
||||
function PANEL:SetValue(value)
|
||||
end
|
||||
|
||||
-- meant for array types to populate combo box values
|
||||
function PANEL:Populate(key, info)
|
||||
end
|
||||
|
||||
-- called when value is changed by user
|
||||
function PANEL:OnValueChanged(newValue)
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
local _, top, _, bottom = self:GetDockPadding()
|
||||
|
||||
self.text:SizeToContents()
|
||||
self:SetTall(self.text:GetTall() + top + bottom)
|
||||
self.ixRealHeight = self:GetTall()
|
||||
self.ixHeight = self.ixRealHeight
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintSettingsRowBackground", self, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixSettingsRow", PANEL, "EditablePanel")
|
||||
|
||||
-- settings panel
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
self.rows = {}
|
||||
self.categories = {}
|
||||
|
||||
-- scroll panel
|
||||
DEFINE_BASECLASS("DScrollPanel")
|
||||
|
||||
self.canvas = self:Add("DScrollPanel")
|
||||
self.canvas:Dock(FILL)
|
||||
self.canvas.PerformLayout = function(panel)
|
||||
BaseClass.PerformLayout(panel)
|
||||
|
||||
if (!panel.VBar.Enabled) then
|
||||
panel.pnlCanvas:SetWide(panel:GetWide() - panel.VBar:GetWide())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:GetRowPanelName(type)
|
||||
return panelMap[type] or "ixSettingsRow"
|
||||
end
|
||||
|
||||
function PANEL:AddCategory(name)
|
||||
local panel = self.categories[name]
|
||||
|
||||
if (!IsValid(panel)) then
|
||||
panel = self.canvas:Add("ixCategoryPanel")
|
||||
panel:SetText(name)
|
||||
panel:Dock(TOP)
|
||||
panel:DockMargin(0, 8, 0, 0)
|
||||
|
||||
self.categories[name] = panel
|
||||
return panel
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:AddRow(type, category)
|
||||
category = self.categories[category]
|
||||
local id = panelMap[type]
|
||||
|
||||
if (!id) then
|
||||
ErrorNoHalt("attempted to create row with unimplemented type '" .. tostring(ix.type[type]) .. "'\n")
|
||||
id = "ixSettingsRow"
|
||||
end
|
||||
|
||||
local panel = (IsValid(category) and category or self.canvas):Add(id)
|
||||
panel:Dock(TOP)
|
||||
panel:SetBackgroundIndex(#self.rows % 2)
|
||||
|
||||
self.rows[#self.rows + 1] = panel
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:GetRows()
|
||||
return self.rows
|
||||
end
|
||||
|
||||
function PANEL:Clear()
|
||||
for _, v in ipairs(self.rows) do
|
||||
if (IsValid(v)) then
|
||||
v:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
self.rows = {}
|
||||
end
|
||||
|
||||
function PANEL:SetSearchEnabled(bValue)
|
||||
if (!bValue) then
|
||||
if (IsValid(self.searchEntry)) then
|
||||
self.searchEntry:Remove()
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- search entry
|
||||
self.searchEntry = self:Add("ixIconTextEntry")
|
||||
self.searchEntry:Dock(TOP)
|
||||
self.searchEntry:SetEnterAllowed(false)
|
||||
|
||||
self.searchEntry.OnChange = function(entry)
|
||||
self:FilterRows(entry:GetValue())
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:FilterRows(query)
|
||||
query = string.PatternSafe(query:lower())
|
||||
|
||||
local bEmpty = query == ""
|
||||
|
||||
for categoryName, category in pairs(self.categories) do
|
||||
category.size = 0
|
||||
category:CreateAnimation(0.5, {
|
||||
index = 21,
|
||||
target = {size = 1},
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SizeToContents()
|
||||
end
|
||||
})
|
||||
|
||||
for _, row in ipairs(category:GetChildren()) do
|
||||
local bFound = bEmpty or row:GetText():lower():find(query) or categoryName:lower():find(query)
|
||||
|
||||
row:SetVisible(true)
|
||||
row:CreateAnimation(0.5, {
|
||||
index = 21,
|
||||
target = {ixHeight = bFound and row.ixRealHeight or 0},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetTall(bFound and math.min(panel.ixHeight + 2, panel.ixRealHeight) or math.max(panel.ixHeight - 2, 0))
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel:SetVisible(bFound)
|
||||
|
||||
-- need this so categories are sized properly when animations are disabled - there is no guaranteed order
|
||||
-- that animations will think so we SizeToContents here. putting it here will result in redundant calls but
|
||||
-- I guess we have the performance to spare
|
||||
if (ix.option.Get("disableAnimations", false)) then
|
||||
category:SizeToContents()
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
for _, v in pairs(self.categories) do
|
||||
v:SizeToContents()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixSettings", PANEL, "Panel")
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixSettings", function(tabs)
|
||||
tabs["settings"] = {
|
||||
PopulateTabButton = function(info, button)
|
||||
local menu = ix.gui.menu
|
||||
|
||||
if (!IsValid(menu)) then
|
||||
return
|
||||
end
|
||||
|
||||
DEFINE_BASECLASS("ixMenuButton")
|
||||
button:SetZPos(9999)
|
||||
button.Paint = function(panel, width, height)
|
||||
BaseClass.Paint(panel, width, height)
|
||||
|
||||
surface.SetDrawColor(255, 255, 255, 33)
|
||||
surface.DrawRect(0, 0, width, 1)
|
||||
end
|
||||
end,
|
||||
|
||||
Create = function(info, container)
|
||||
local panel = container:Add("ixSettings")
|
||||
panel:SetSearchEnabled(true)
|
||||
|
||||
for category, options in SortedPairs(ix.option.GetAllByCategories(true)) do
|
||||
category = L(category)
|
||||
panel:AddCategory(category)
|
||||
|
||||
-- sort options by language phrase rather than the key
|
||||
table.sort(options, function(a, b)
|
||||
return L(a.phrase) < L(b.phrase)
|
||||
end)
|
||||
|
||||
for _, data in pairs(options) do
|
||||
local key = data.key
|
||||
local row = panel:AddRow(data.type, category)
|
||||
local value = ix.util.SanitizeType(data.type, ix.option.Get(key))
|
||||
|
||||
row:SetText(L(data.phrase))
|
||||
row:Populate(key, data)
|
||||
|
||||
-- type-specific properties
|
||||
if (data.type == ix.type.number) then
|
||||
row:SetMin(data.min or 0)
|
||||
row:SetMax(data.max or 10)
|
||||
row:SetDecimals(data.decimals or 0)
|
||||
end
|
||||
|
||||
row:SetValue(value, true)
|
||||
row:SetShowReset(value != data.default, key, data.default)
|
||||
row.OnValueChanged = function()
|
||||
local newValue = row:GetValue()
|
||||
|
||||
row:SetShowReset(newValue != data.default, key, data.default)
|
||||
ix.option.Set(key, newValue)
|
||||
end
|
||||
|
||||
row.OnResetClicked = function()
|
||||
row:SetShowReset(false)
|
||||
row:SetValue(data.default, true)
|
||||
|
||||
ix.option.Set(key, data.default)
|
||||
end
|
||||
|
||||
row:GetLabel():SetHelixTooltip(function(tooltip)
|
||||
local title = tooltip:AddRow("name")
|
||||
title:SetImportant()
|
||||
title:SetText(key)
|
||||
title:SizeToContents()
|
||||
title:SetMaxWidth(math.max(title:GetMaxWidth(), ScrW() * 0.5))
|
||||
|
||||
local description = tooltip:AddRow("description")
|
||||
description:SetText(L(data.description))
|
||||
description:SizeToContents()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
panel:SizeToContents()
|
||||
container.panel = panel
|
||||
end,
|
||||
|
||||
OnSelected = function(info, container)
|
||||
container.panel.searchEntry:RequestFocus()
|
||||
end
|
||||
}
|
||||
end)
|
||||
130
gamemodes/helix/gamemode/core/derma/cl_shipment.lua
Normal file
130
gamemodes/helix/gamemode/core/derma/cl_shipment.lua
Normal file
@@ -0,0 +1,130 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(460, 360)
|
||||
self:SetTitle(L"shipment")
|
||||
self:Center()
|
||||
self:MakePopup()
|
||||
|
||||
self.scroll = self:Add("DScrollPanel")
|
||||
self.scroll:Dock(FILL)
|
||||
|
||||
self.list = self.scroll:Add("DListLayout")
|
||||
self.list:Dock(FILL)
|
||||
end
|
||||
|
||||
function PANEL:SetItems(entity, items)
|
||||
self.entity = entity
|
||||
self.items = true
|
||||
self.itemPanels = {}
|
||||
|
||||
for k, v in SortedPairs(items) do
|
||||
local itemTable = ix.item.list[k]
|
||||
|
||||
if (itemTable) then
|
||||
local item = self.list:Add("DPanel")
|
||||
item:SetTall(36)
|
||||
item:Dock(TOP)
|
||||
item:DockMargin(4, 4, 4, 0)
|
||||
|
||||
item.icon = item:Add("SpawnIcon")
|
||||
item.icon:SetPos(2, 2)
|
||||
item.icon:SetSize(32, 32)
|
||||
item.icon:SetModel(itemTable:GetModel())
|
||||
item.icon:SetHelixTooltip(function(tooltip)
|
||||
ix.hud.PopulateItemTooltip(tooltip, itemTable)
|
||||
end)
|
||||
|
||||
item.quantity = item.icon:Add("DLabel")
|
||||
item.quantity:SetSize(32, 32)
|
||||
item.quantity:SetContentAlignment(3)
|
||||
item.quantity:SetTextInset(0, 0)
|
||||
item.quantity:SetText(v)
|
||||
item.quantity:SetFont("DermaDefaultBold")
|
||||
item.quantity:SetExpensiveShadow(1, Color(0, 0, 0, 150))
|
||||
|
||||
item.name = item:Add("DLabel")
|
||||
item.name:SetPos(38, 0)
|
||||
item.name:SetSize(200, 36)
|
||||
item.name:SetFont("ixSmallFont")
|
||||
item.name:SetText(L(itemTable.name))
|
||||
item.name:SetContentAlignment(4)
|
||||
item.name:SetTextColor(color_white)
|
||||
|
||||
item.take = item:Add("DButton")
|
||||
item.take:Dock(RIGHT)
|
||||
item.take:SetText(L"take")
|
||||
item.take:SetWide(48)
|
||||
item.take:DockMargin(3, 3, 3, 3)
|
||||
item.take:SetTextColor(color_white)
|
||||
item.take.DoClick = function(this)
|
||||
net.Start("ixShipmentUse")
|
||||
net.WriteString(k)
|
||||
net.WriteBool(false)
|
||||
net.SendToServer()
|
||||
|
||||
items[k] = items[k] - 1
|
||||
|
||||
item.quantity:SetText(items[k])
|
||||
|
||||
if (items[k] <= 0) then
|
||||
item:Remove()
|
||||
items[k] = nil
|
||||
end
|
||||
|
||||
if (table.IsEmpty(items)) then
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
item.drop = item:Add("DButton")
|
||||
item.drop:Dock(RIGHT)
|
||||
item.drop:SetText(L"drop")
|
||||
item.drop:SetWide(48)
|
||||
item.drop:DockMargin(3, 3, 0, 3)
|
||||
item.drop:SetTextColor(color_white)
|
||||
item.drop.DoClick = function(this)
|
||||
net.Start("ixShipmentUse")
|
||||
net.WriteString(k)
|
||||
net.WriteBool(true)
|
||||
net.SendToServer()
|
||||
|
||||
items[k] = items[k] - 1
|
||||
|
||||
item.quantity:SetText(items[k])
|
||||
|
||||
if (items[k] <= 0) then
|
||||
item:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
self.itemPanels[k] = item
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Close()
|
||||
net.Start("ixShipmentClose")
|
||||
net.SendToServer()
|
||||
|
||||
self:Remove()
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (self.items and !IsValid(self.entity)) then
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixShipment", PANEL, "DFrame")
|
||||
107
gamemodes/helix/gamemode/core/derma/cl_spawnicon.lua
Normal file
107
gamemodes/helix/gamemode/core/derma/cl_spawnicon.lua
Normal file
@@ -0,0 +1,107 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
DEFINE_BASECLASS("DModelPanel")
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.defaultEyeTarget = Vector(0, 0, 64)
|
||||
self:SetHidden(false)
|
||||
|
||||
for i = 0, 5 do
|
||||
if (i == 1 or i == 5) then
|
||||
self:SetDirectionalLight(i, Color(155, 155, 155))
|
||||
else
|
||||
self:SetDirectionalLight(i, Color(255, 255, 255))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetModel(model, skin, hidden)
|
||||
BaseClass.SetModel(self, model)
|
||||
|
||||
local entity = self.Entity
|
||||
|
||||
if (skin) then
|
||||
entity:SetSkin(skin)
|
||||
end
|
||||
|
||||
local sequence = entity:SelectWeightedSequence(ACT_IDLE)
|
||||
|
||||
if (sequence <= 0) then
|
||||
sequence = entity:LookupSequence("idle_unarmed")
|
||||
end
|
||||
|
||||
if (sequence > 0) then
|
||||
entity:ResetSequence(sequence)
|
||||
else
|
||||
local found = false
|
||||
|
||||
for _, v in ipairs(entity:GetSequenceList()) do
|
||||
if ((v:lower():find("idle") or v:lower():find("fly")) and v != "idlenoise") then
|
||||
entity:ResetSequence(v)
|
||||
found = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!found) then
|
||||
entity:ResetSequence(4)
|
||||
end
|
||||
end
|
||||
|
||||
local data = PositionSpawnIcon(entity, entity:GetPos())
|
||||
|
||||
if (data) then
|
||||
self:SetFOV(data.fov)
|
||||
self:SetCamPos(data.origin)
|
||||
self:SetLookAng(data.angles)
|
||||
end
|
||||
|
||||
entity:SetIK(false)
|
||||
entity:SetEyeTarget(self.defaultEyeTarget)
|
||||
end
|
||||
|
||||
function PANEL:SetHidden(hidden)
|
||||
if (hidden) then
|
||||
self:SetAmbientLight(color_black)
|
||||
self:SetColor(Color(0, 0, 0))
|
||||
|
||||
for i = 0, 5 do
|
||||
self:SetDirectionalLight(i, color_black)
|
||||
end
|
||||
else
|
||||
self:SetAmbientLight(Color(20, 20, 20))
|
||||
self:SetAlpha(255)
|
||||
|
||||
for i = 0, 5 do
|
||||
if (i == 1 or i == 5) then
|
||||
self:SetDirectionalLight(i, Color(155, 155, 155))
|
||||
else
|
||||
self:SetDirectionalLight(i, Color(255, 255, 255))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:LayoutEntity()
|
||||
self:RunAnimation()
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed()
|
||||
if (self.DoClick) then
|
||||
self:DoClick()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixSpawnIcon", PANEL, "DModelPanel")
|
||||
207
gamemodes/helix/gamemode/core/derma/cl_storage.lua
Normal file
207
gamemodes/helix/gamemode/core/derma/cl_storage.lua
Normal file
@@ -0,0 +1,207 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "money", "Money", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self:DockPadding(1, 1, 1, 1)
|
||||
self:SetTall(64)
|
||||
self:Dock(BOTTOM)
|
||||
|
||||
self.moneyLabel = self:Add("DLabel")
|
||||
self.moneyLabel:Dock(TOP)
|
||||
self.moneyLabel:SetFont("ixGenericFont")
|
||||
self.moneyLabel:SetText("")
|
||||
self.moneyLabel:SetTextInset(2, 0)
|
||||
self.moneyLabel:SizeToContents()
|
||||
self.moneyLabel.Paint = function(panel, width, height)
|
||||
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, ix.config.Get("color"))
|
||||
end
|
||||
|
||||
self.amountEntry = self:Add("ixTextEntry")
|
||||
self.amountEntry:Dock(FILL)
|
||||
self.amountEntry:SetFont("ixGenericFont")
|
||||
self.amountEntry:SetNumeric(true)
|
||||
self.amountEntry:SetValue("0")
|
||||
|
||||
self.transferButton = self:Add("DButton")
|
||||
self.transferButton:SetFont("ixIconsMedium")
|
||||
self:SetLeft(false)
|
||||
self.transferButton.DoClick = function()
|
||||
local amount = math.max(0, math.Round(tonumber(self.amountEntry:GetValue()) or 0))
|
||||
self.amountEntry:SetValue("0")
|
||||
|
||||
if (amount != 0) then
|
||||
self:OnTransfer(amount)
|
||||
end
|
||||
end
|
||||
|
||||
self.bNoBackgroundBlur = true
|
||||
end
|
||||
|
||||
function PANEL:SetLeft(bValue)
|
||||
if (bValue) then
|
||||
self.transferButton:Dock(LEFT)
|
||||
self.transferButton:SetText("s")
|
||||
else
|
||||
self.transferButton:Dock(RIGHT)
|
||||
self.transferButton:SetText("t")
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetMoney(money)
|
||||
local name = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "")
|
||||
|
||||
self.money = math.max(math.Round(tonumber(money) or 0), 0)
|
||||
self.moneyLabel:SetText(string.format("%s: %d", name, money))
|
||||
end
|
||||
|
||||
function PANEL:OnTransfer(amount)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
derma.SkinFunc("PaintBaseFrame", self, width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixStorageMoney", PANEL, "EditablePanel")
|
||||
|
||||
DEFINE_BASECLASS("Panel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "fadeTime", "FadeTime", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "frameMargin", "FrameMargin", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "storageID", "StorageID", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
if (IsValid(ix.gui.openedStorage)) then
|
||||
ix.gui.openedStorage:Remove()
|
||||
end
|
||||
|
||||
ix.gui.openedStorage = self
|
||||
|
||||
self:SetSize(ScrW(), ScrH())
|
||||
self:SetPos(0, 0)
|
||||
self:SetFadeTime(0.25)
|
||||
self:SetFrameMargin(4)
|
||||
|
||||
self.storageInventory = self:Add("ixInventory")
|
||||
self.storageInventory.bNoBackgroundBlur = true
|
||||
self.storageInventory:ShowCloseButton(true)
|
||||
self.storageInventory:SetTitle("Storage")
|
||||
self.storageInventory.Close = function(this)
|
||||
net.Start("ixStorageClose")
|
||||
net.SendToServer()
|
||||
self:Remove()
|
||||
end
|
||||
|
||||
self.storageMoney = self.storageInventory:Add("ixStorageMoney")
|
||||
self.storageMoney:SetVisible(false)
|
||||
self.storageMoney.OnTransfer = function(_, amount)
|
||||
net.Start("ixStorageMoneyTake")
|
||||
net.WriteUInt(self.storageID, 32)
|
||||
net.WriteUInt(amount, 32)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
ix.gui.inv1 = self:Add("ixInventory")
|
||||
ix.gui.inv1.bNoBackgroundBlur = true
|
||||
ix.gui.inv1:ShowCloseButton(true)
|
||||
ix.gui.inv1.Close = function(this)
|
||||
net.Start("ixStorageClose")
|
||||
net.SendToServer()
|
||||
self:Remove()
|
||||
end
|
||||
|
||||
self.localMoney = ix.gui.inv1:Add("ixStorageMoney")
|
||||
self.localMoney:SetVisible(false)
|
||||
self.localMoney:SetLeft(true)
|
||||
self.localMoney.OnTransfer = function(_, amount)
|
||||
net.Start("ixStorageMoneyGive")
|
||||
net.WriteUInt(self.storageID, 32)
|
||||
net.WriteUInt(amount, 32)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
self:SetAlpha(0)
|
||||
self:AlphaTo(255, self:GetFadeTime())
|
||||
|
||||
self.storageInventory:MakePopup()
|
||||
ix.gui.inv1:MakePopup()
|
||||
end
|
||||
|
||||
function PANEL:OnChildAdded(panel)
|
||||
panel:SetPaintedManually(true)
|
||||
end
|
||||
|
||||
function PANEL:SetLocalInventory(inventory)
|
||||
if (IsValid(ix.gui.inv1) and !IsValid(ix.gui.menu)) then
|
||||
ix.gui.inv1:SetInventory(inventory)
|
||||
ix.gui.inv1:SetPos(self:GetWide() / 2 + self:GetFrameMargin() / 2, self:GetTall() / 2 - ix.gui.inv1:GetTall() / 2)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetLocalMoney(money)
|
||||
if (!self.localMoney:IsVisible()) then
|
||||
self.localMoney:SetVisible(true)
|
||||
ix.gui.inv1:SetTall(ix.gui.inv1:GetTall() + self.localMoney:GetTall() + 2)
|
||||
end
|
||||
|
||||
self.localMoney:SetMoney(money)
|
||||
end
|
||||
|
||||
function PANEL:SetStorageTitle(title)
|
||||
self.storageInventory:SetTitle(title)
|
||||
end
|
||||
|
||||
function PANEL:SetStorageInventory(inventory)
|
||||
self.storageInventory:SetInventory(inventory)
|
||||
self.storageInventory:SetPos(
|
||||
self:GetWide() / 2 - self.storageInventory:GetWide() - 2,
|
||||
self:GetTall() / 2 - self.storageInventory:GetTall() / 2
|
||||
)
|
||||
|
||||
ix.gui["inv" .. inventory:GetID()] = self.storageInventory
|
||||
end
|
||||
|
||||
function PANEL:SetStorageMoney(money)
|
||||
if (!self.storageMoney:IsVisible()) then
|
||||
self.storageMoney:SetVisible(true)
|
||||
self.storageInventory:SetTall(self.storageInventory:GetTall() + self.storageMoney:GetTall() + 2)
|
||||
end
|
||||
|
||||
self.storageMoney:SetMoney(money)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
ix.util.DrawBlurAt(0, 0, width, height)
|
||||
|
||||
for _, v in ipairs(self:GetChildren()) do
|
||||
v:PaintManual()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Remove()
|
||||
self:SetAlpha(255)
|
||||
self:AlphaTo(0, self:GetFadeTime(), 0, function()
|
||||
BaseClass.Remove(self)
|
||||
end)
|
||||
end
|
||||
|
||||
function PANEL:OnRemove()
|
||||
if (!IsValid(ix.gui.menu)) then
|
||||
self.storageInventory:Remove()
|
||||
ix.gui.inv1:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("ixStorageView", PANEL, "Panel")
|
||||
312
gamemodes/helix/gamemode/core/derma/cl_subpanel.lua
Normal file
312
gamemodes/helix/gamemode/core/derma/cl_subpanel.lua
Normal file
@@ -0,0 +1,312 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local DEFAULT_PADDING = ScreenScale(32)
|
||||
local DEFAULT_ANIMATION_TIME = 1
|
||||
local DEFAULT_SUBPANEL_ANIMATION_TIME = 0.5
|
||||
|
||||
-- parent subpanel
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
local parent = self:GetParent()
|
||||
local padding = parent.GetPadding and parent:GetPadding() or DEFAULT_PADDING
|
||||
|
||||
self:SetSize(parent:GetWide() - (padding * 2), parent:GetTall() - (padding * 2))
|
||||
self:Center()
|
||||
end
|
||||
|
||||
function PANEL:SetTitle(text, bNoTranslation, bNoUpper)
|
||||
if (text == nil) then
|
||||
if (IsValid(self.title)) then
|
||||
self.title:Remove()
|
||||
end
|
||||
|
||||
return
|
||||
elseif (!IsValid(self.title)) then
|
||||
self.title = self:Add("DLabel")
|
||||
self.title:SetFont("ixTitleFont")
|
||||
self.title:SizeToContents()
|
||||
self.title:SetTextColor(ix.config.Get("color") or color_white)
|
||||
self.title:Dock(TOP)
|
||||
end
|
||||
|
||||
local newText = bNoTranslation and text or L(text)
|
||||
newText = bNoUpper and newText or newText:utf8upper()
|
||||
|
||||
self.title:SetText(newText)
|
||||
self.title:SizeToContents()
|
||||
end
|
||||
|
||||
function PANEL:SetLeftPanel(panel)
|
||||
self.left = panel
|
||||
end
|
||||
|
||||
function PANEL:GetLeftPanel()
|
||||
return self.left
|
||||
end
|
||||
|
||||
function PANEL:SetRightPanel(panel)
|
||||
self.right = panel
|
||||
end
|
||||
|
||||
function PANEL:GetRightPanel()
|
||||
return self.right
|
||||
end
|
||||
|
||||
function PANEL:OnSetActive()
|
||||
end
|
||||
|
||||
vgui.Register("ixSubpanel", PANEL, "EditablePanel")
|
||||
|
||||
-- subpanel parent
|
||||
DEFINE_BASECLASS("EditablePanel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "padding", "Padding", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "animationTime", "AnimationTime", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "subpanelAnimationTime", "SubpanelAnimationTime", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "leftOffset", "LeftOffset", FORCE_NUMBER)
|
||||
|
||||
function PANEL:Init()
|
||||
self.subpanels = {}
|
||||
self.childPanels = {}
|
||||
|
||||
self.currentSubpanelX = DEFAULT_PADDING
|
||||
self.targetSubpanelX = DEFAULT_PADDING
|
||||
self.padding = DEFAULT_PADDING
|
||||
self.leftOffset = 0
|
||||
|
||||
self.animationTime = DEFAULT_ANIMATION_TIME
|
||||
self.subpanelAnimationTime = DEFAULT_SUBPANEL_ANIMATION_TIME
|
||||
end
|
||||
|
||||
function PANEL:SetPadding(amount, bSetDockPadding)
|
||||
self.currentSubpanelX = amount
|
||||
self.targetSubpanelX = amount
|
||||
self.padding = amount
|
||||
|
||||
if (bSetDockPadding) then
|
||||
self:DockPadding(amount, amount, amount, amount)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Add(name)
|
||||
local panel = BaseClass.Add(self, name)
|
||||
|
||||
if (panel.SetPaintedManually) then
|
||||
panel:SetPaintedManually(true)
|
||||
self.childPanels[#self.childPanels + 1] = panel
|
||||
end
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:AddSubpanel(name)
|
||||
local id = #self.subpanels + 1
|
||||
local panel = BaseClass.Add(self, "ixSubpanel")
|
||||
panel.subpanelName = name
|
||||
panel.subpanelID = id
|
||||
panel:SetTitle(L(name))
|
||||
|
||||
self.subpanels[id] = panel
|
||||
self:SetupSubpanelReferences()
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:SetupSubpanelReferences()
|
||||
local lastPanel
|
||||
|
||||
for i = 1, #self.subpanels do
|
||||
local panel = self.subpanels[i]
|
||||
local nextPanel = self.subpanels[i + 1]
|
||||
|
||||
if (IsValid(lastPanel)) then
|
||||
lastPanel:SetRightPanel(panel)
|
||||
panel:SetLeftPanel(lastPanel)
|
||||
end
|
||||
|
||||
if (IsValid(nextPanel)) then
|
||||
panel:SetRightPanel(nextPanel)
|
||||
end
|
||||
|
||||
lastPanel = panel
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetSubpanelPos(id, x)
|
||||
local currentPanel = self.subpanels[id]
|
||||
|
||||
if (!currentPanel) then
|
||||
return
|
||||
end
|
||||
|
||||
local _, oldY = currentPanel:GetPos()
|
||||
currentPanel:SetPos(x, oldY)
|
||||
|
||||
-- traverse left
|
||||
while (IsValid(currentPanel)) do
|
||||
local left = currentPanel:GetLeftPanel()
|
||||
|
||||
if (IsValid(left)) then
|
||||
left:MoveLeftOf(currentPanel, self.padding + self.leftOffset)
|
||||
end
|
||||
|
||||
currentPanel = left
|
||||
end
|
||||
|
||||
currentPanel = self.subpanels[id]
|
||||
|
||||
-- traverse right
|
||||
while (IsValid(currentPanel)) do
|
||||
local right = currentPanel:GetRightPanel()
|
||||
|
||||
if (IsValid(right)) then
|
||||
right:MoveRightOf(currentPanel, self.padding)
|
||||
end
|
||||
|
||||
currentPanel = right
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SetActiveSubpanel(id, length)
|
||||
if (isstring(id)) then
|
||||
for i = 1, #self.subpanels do
|
||||
if (self.subpanels[i].subpanelName == id) then
|
||||
id = i
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local activePanel = self.subpanels[id]
|
||||
|
||||
if (!activePanel) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (length == 0 or !self.activeSubpanel) then
|
||||
self:SetSubpanelPos(id, self.padding + self.leftOffset)
|
||||
else
|
||||
local x, _ = activePanel:GetPos()
|
||||
local target = self.targetSubpanelX + self.leftOffset
|
||||
self.currentSubpanelX = x + self.padding + self.leftOffset
|
||||
|
||||
self:CreateAnimation(length or self.subpanelAnimationTime, {
|
||||
index = 420,
|
||||
target = {currentSubpanelX = target},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetSubpanelPos(id, panel.currentSubpanelX)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
panel:SetSubpanelPos(id, target)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
self.activeSubpanel = id
|
||||
activePanel:OnSetActive()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function PANEL:GetSubpanel(id)
|
||||
return self.subpanels[id]
|
||||
end
|
||||
|
||||
function PANEL:GetActiveSubpanel()
|
||||
return self.subpanels[self.activeSubpanel]
|
||||
end
|
||||
|
||||
function PANEL:GetActiveSubpanelID()
|
||||
return self.activeSubpanel
|
||||
end
|
||||
|
||||
function PANEL:Slide(direction, length, callback, bIgnoreConfig)
|
||||
local _, height = self:GetParent():GetSize()
|
||||
local x, _ = self:GetPos()
|
||||
local targetY = direction == "up" and 0 or height
|
||||
|
||||
self:SetVisible(true)
|
||||
|
||||
if (length == 0) then
|
||||
self:SetPos(x, targetY)
|
||||
else
|
||||
length = length or self.animationTime
|
||||
self.currentY = direction == "up" and height or 0
|
||||
|
||||
self:CreateAnimation(length or self.animationTime, {
|
||||
index = -1,
|
||||
target = {currentY = targetY},
|
||||
easing = "outExpo",
|
||||
bIgnoreConfig = bIgnoreConfig,
|
||||
|
||||
Think = function(animation, panel)
|
||||
local currentX, _ = panel:GetPos()
|
||||
|
||||
panel:SetPos(currentX, panel.currentY)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
if (direction == "down") then
|
||||
panel:SetVisible(false)
|
||||
end
|
||||
|
||||
if (callback) then
|
||||
callback(panel)
|
||||
end
|
||||
end
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:SlideUp(...)
|
||||
self:SetMouseInputEnabled(true)
|
||||
self:SetKeyboardInputEnabled(true)
|
||||
|
||||
self:OnSlideUp()
|
||||
self:Slide("up", ...)
|
||||
end
|
||||
|
||||
function PANEL:SlideDown(...)
|
||||
self:SetMouseInputEnabled(false)
|
||||
self:SetKeyboardInputEnabled(false)
|
||||
|
||||
self:OnSlideDown()
|
||||
self:Slide("down", ...)
|
||||
end
|
||||
|
||||
function PANEL:OnSlideUp()
|
||||
end
|
||||
|
||||
function PANEL:OnSlideDown()
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
for i = 1, #self.childPanels do
|
||||
self.childPanels[i]:PaintManual()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PaintSubpanels(width, height)
|
||||
for i = 1, #self.subpanels do
|
||||
self.subpanels[i]:PaintManual()
|
||||
end
|
||||
end
|
||||
|
||||
-- ????
|
||||
PANEL.Remove = BaseClass.Remove
|
||||
|
||||
vgui.Register("ixSubpanelParent", PANEL, "EditablePanel")
|
||||
604
gamemodes/helix/gamemode/core/derma/cl_tooltip.lua
Normal file
604
gamemodes/helix/gamemode/core/derma/cl_tooltip.lua
Normal file
@@ -0,0 +1,604 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--- Text container for `ixTooltip`.
|
||||
-- Rows are the main way of interacting with `ixTooltip`s. These derive from
|
||||
-- [DLabel](https://wiki.garrysmod.com/page/Category:DLabel) panels, which means that making use of this panel
|
||||
-- will be largely the same as any DLabel panel.
|
||||
-- @panel ixTooltipRow
|
||||
|
||||
local animationTime = 1
|
||||
|
||||
-- panel meta
|
||||
do
|
||||
local PANEL = FindMetaTable("Panel")
|
||||
local ixChangeTooltip = ChangeTooltip
|
||||
local ixRemoveTooltip = RemoveTooltip
|
||||
local tooltip
|
||||
local lastHover
|
||||
|
||||
function PANEL:SetHelixTooltip(callback)
|
||||
self:SetMouseInputEnabled(true)
|
||||
self.ixTooltip = callback
|
||||
end
|
||||
|
||||
function ChangeTooltip(panel, ...) -- luacheck: globals ChangeTooltip
|
||||
if (!panel.ixTooltip) then
|
||||
return ixChangeTooltip(panel, ...)
|
||||
end
|
||||
|
||||
RemoveTooltip()
|
||||
|
||||
timer.Create("ixTooltip", 0.1, 1, function()
|
||||
if (!IsValid(panel) or lastHover != panel) then
|
||||
return
|
||||
end
|
||||
|
||||
tooltip = vgui.Create("ixTooltip")
|
||||
panel.ixTooltip(tooltip)
|
||||
tooltip:SizeToContents()
|
||||
end)
|
||||
|
||||
lastHover = panel
|
||||
end
|
||||
|
||||
function RemoveTooltip() -- luacheck: globals RemoveTooltip
|
||||
if (IsValid(tooltip)) then
|
||||
tooltip:Remove()
|
||||
tooltip = nil
|
||||
end
|
||||
|
||||
timer.Remove("ixTooltip")
|
||||
lastHover = nil
|
||||
|
||||
return ixRemoveTooltip()
|
||||
end
|
||||
end
|
||||
|
||||
DEFINE_BASECLASS("DLabel")
|
||||
local PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "backgroundColor", "BackgroundColor")
|
||||
AccessorFunc(PANEL, "maxWidth", "MaxWidth", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "bNoMinimal", "MinimalHidden", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetFont("ixSmallFont")
|
||||
self:SetText(L("unknown"))
|
||||
self:SetTextColor(color_white)
|
||||
self:SetTextInset(4, 0)
|
||||
self:SetContentAlignment(4)
|
||||
self:Dock(TOP)
|
||||
|
||||
self.maxWidth = ScrW() * 0.2
|
||||
self.bNoMinimal = false
|
||||
self.bMinimal = false
|
||||
end
|
||||
|
||||
--- Whether or not this tooltip row should be displayed in a minimal format. This usually means no background and/or
|
||||
-- smaller font. You probably won't need this if you're using regular `ixTooltipRow` panels, but you should take into
|
||||
-- account if you're creating your own panels that derive from `ixTooltipRow`.
|
||||
-- @realm client
|
||||
-- @treturn bool True if this tooltip row should be displayed in a minimal format
|
||||
function PANEL:IsMinimal()
|
||||
return self.bMinimal
|
||||
end
|
||||
|
||||
--- Sets this row to be more prominent with a larger font and more noticable background color. This should usually
|
||||
-- be used once per tooltip as a title row. For example, item tooltips have one "important" row consisting of the
|
||||
-- item's name. Note that this function is a fire-and-forget function; you cannot revert a row back to it's regular state
|
||||
-- unless you set the font/colors manually.
|
||||
-- @realm client
|
||||
function PANEL:SetImportant()
|
||||
self:SetFont("ixSmallTitleFont")
|
||||
self:SetExpensiveShadow(1, color_black)
|
||||
self:SetBackgroundColor(ix.config.Get("color"))
|
||||
end
|
||||
|
||||
--- Sets the background color of this row. This should be used sparingly to avoid overwhelming players with a
|
||||
-- bunch of different colors that could convey different meanings.
|
||||
-- @realm client
|
||||
-- @color color New color of the background. The alpha is clamped to 100-255 to ensure visibility
|
||||
function PANEL:SetBackgroundColor(color)
|
||||
color = table.Copy(color)
|
||||
color.a = math.min(color.a or 255, 100)
|
||||
|
||||
self.backgroundColor = color
|
||||
end
|
||||
|
||||
--- Resizes this panel to fit its contents. This should be called after setting the text.
|
||||
-- @realm client
|
||||
function PANEL:SizeToContents()
|
||||
local contentWidth, contentHeight = self:GetContentSize()
|
||||
contentWidth = contentWidth + 4
|
||||
contentHeight = contentHeight + 4
|
||||
|
||||
if (contentWidth > self.maxWidth) then
|
||||
self:SetWide(self.maxWidth - 4) -- to account for text inset
|
||||
self:SetTextInset(4, 0)
|
||||
self:SetWrap(true)
|
||||
|
||||
self:SizeToContentsY()
|
||||
else
|
||||
self:SetSize(contentWidth, contentHeight)
|
||||
end
|
||||
end
|
||||
|
||||
--- Resizes the height of this panel to fit its contents.
|
||||
-- @internal
|
||||
-- @realm client
|
||||
function PANEL:SizeToContentsY()
|
||||
BaseClass.SizeToContentsY(self)
|
||||
self:SetTall(self:GetTall() + 4)
|
||||
end
|
||||
|
||||
--- Called when the background of this row should be painted. This will paint the background with the
|
||||
-- `DrawImportantBackground` function set in the skin by default.
|
||||
-- @realm client
|
||||
-- @number width Width of the panel
|
||||
-- @number height Height of the panel
|
||||
function PANEL:PaintBackground(width, height)
|
||||
if (self.backgroundColor) then
|
||||
derma.SkinFunc("DrawImportantBackground", 0, 0, width, height, self.backgroundColor)
|
||||
end
|
||||
end
|
||||
|
||||
--- Called when the foreground of this row should be painted. If you are overriding this in a subclassed panel,
|
||||
-- make sure you call `ixTooltipRow:PaintBackground` at the *beginning* of your function to make its style
|
||||
-- consistent with the rest of the framework.
|
||||
-- @realm client
|
||||
-- @number width Width of the panel
|
||||
-- @number height Height of the panel
|
||||
function PANEL:Paint(width, height)
|
||||
self:PaintBackground(width, height)
|
||||
end
|
||||
|
||||
vgui.Register("ixTooltipRow", PANEL, "DLabel")
|
||||
|
||||
--- Generic information panel.
|
||||
-- Tooltips are used extensively throughout Helix: for item information, character displays, entity status, etc.
|
||||
-- The tooltip system can be used on any panel or entity you would like to show standardized information for. Tooltips
|
||||
-- consist of the parent container panel (`ixTooltip`), which is filled with rows of information (usually
|
||||
-- `ixTooltipRow`, but can be any docked panel if non-text information needs to be shown, like an item's size).
|
||||
--
|
||||
-- Tooltips can be added to panel with `panel:SetHelixTooltip()`. An example taken from the scoreboard:
|
||||
-- panel:SetHelixTooltip(function(tooltip)
|
||||
-- local name = tooltip:AddRow("name")
|
||||
-- name:SetImportant()
|
||||
-- name:SetText(client:SteamName())
|
||||
-- name:SetBackgroundColor(team.GetColor(client:Team()))
|
||||
-- name:SizeToContents()
|
||||
--
|
||||
-- tooltip:SizeToContents()
|
||||
-- end)
|
||||
-- @panel ixTooltip
|
||||
DEFINE_BASECLASS("Panel")
|
||||
PANEL = {}
|
||||
|
||||
AccessorFunc(PANEL, "entity", "Entity")
|
||||
AccessorFunc(PANEL, "mousePadding", "MousePadding", FORCE_NUMBER)
|
||||
AccessorFunc(PANEL, "bDrawArrow", "DrawArrow", FORCE_BOOL)
|
||||
AccessorFunc(PANEL, "arrowColor", "ArrowColor")
|
||||
AccessorFunc(PANEL, "bHideArrowWhenRaised", "HideArrowWhenRaised", FORCE_BOOL)
|
||||
AccessorFunc(PANEL, "bArrowFollowEntity", "ArrowFollowEntity", FORCE_BOOL)
|
||||
|
||||
function PANEL:Init()
|
||||
self.fraction = 0
|
||||
self.mousePadding = 16
|
||||
self.arrowColor = ix.config.Get("color")
|
||||
self.bHideArrowWhenRaised = true
|
||||
self.bArrowFollowEntity = true
|
||||
self.bMinimal = false
|
||||
|
||||
self.lastX, self.lastY = self:GetCursorPosition()
|
||||
self.arrowX, self.arrowY = ScrW() * 0.5, ScrH() * 0.5
|
||||
|
||||
self:SetAlpha(0)
|
||||
self:SetSize(0, 0)
|
||||
self:SetDrawOnTop(true)
|
||||
self:SetMouseInputEnabled(false)
|
||||
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 1,
|
||||
target = {fraction = 1},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.fraction * 255)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
--- Whether or not this tooltip should be displayed in a minimal format.
|
||||
-- @realm client
|
||||
-- @treturn bool True if this tooltip should be displayed in a minimal format
|
||||
-- @see ixTooltipRow:IsMinimal
|
||||
function PANEL:IsMinimal()
|
||||
return self.bMinimal
|
||||
end
|
||||
|
||||
-- ensure all children are painted manually
|
||||
function PANEL:Add(...)
|
||||
local panel = BaseClass.Add(self, ...)
|
||||
panel:SetPaintedManually(true)
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
--- Creates a new `ixTooltipRow` panel and adds it to the bottom of this tooltip.
|
||||
-- @realm client
|
||||
-- @string id Name of the new row. This is used to reorder rows if needed
|
||||
-- @treturn panel Created row
|
||||
function PANEL:AddRow(id)
|
||||
local panel = self:Add("ixTooltipRow")
|
||||
panel.id = id
|
||||
panel:SetZPos(#self:GetChildren() * 10)
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
--- Creates a new `ixTooltipRow` and adds it after the row with the given `id`. The order of the rows is set via
|
||||
-- setting the Z position of the panels, as this is how VGUI handles ordering with docked panels.
|
||||
-- @realm client
|
||||
-- @string after Name of the row to insert after
|
||||
-- @string id Name of the newly created row
|
||||
-- @treturn panel Created row
|
||||
function PANEL:AddRowAfter(after, id)
|
||||
local panel = self:AddRow(id)
|
||||
after = self:GetRow(after)
|
||||
|
||||
if (!IsValid(after)) then
|
||||
return panel
|
||||
end
|
||||
|
||||
panel:SetZPos(after:GetZPos() + 1)
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
--- Sets the entity associated with this tooltip. Note that this function is not how you get entities to show tooltips.
|
||||
-- @internal
|
||||
-- @realm client
|
||||
-- @entity entity Entity to associate with this tooltip
|
||||
function PANEL:SetEntity(entity)
|
||||
if (!IsValid(entity)) then
|
||||
self.bEntity = false
|
||||
return
|
||||
end
|
||||
|
||||
-- don't show entity tooltips if we have an entity menu open
|
||||
if (IsValid(ix.menu.panel)) then
|
||||
self:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
if (entity:IsPlayer()) then
|
||||
local character = entity:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
-- we want to group things that will most likely have backgrounds (e.g name/health status)
|
||||
hook.Run("PopulateImportantCharacterInfo", entity, character, self)
|
||||
hook.Run("PopulateCharacterInfo", entity, character, self)
|
||||
end
|
||||
else
|
||||
if (entity.OnPopulateEntityInfo) then
|
||||
entity:OnPopulateEntityInfo(self)
|
||||
else
|
||||
hook.Run("PopulateEntityInfo", entity, self)
|
||||
end
|
||||
end
|
||||
|
||||
self:SizeToContents()
|
||||
|
||||
self.entity = entity
|
||||
self.bEntity = true
|
||||
end
|
||||
|
||||
function PANEL:PaintUnder(width, height)
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
self:PaintUnder()
|
||||
|
||||
-- directional arrow
|
||||
self.bRaised = LocalPlayer():IsWepRaised()
|
||||
|
||||
if (!self.bClosing) then
|
||||
if (self.bEntity and IsValid(self.entity) and self.bArrowFollowEntity) then
|
||||
local entity = self.entity
|
||||
local position = select(1, entity:GetBonePosition(entity:LookupBone("ValveBiped.Bip01_Spine") or -1)) or
|
||||
entity:LocalToWorld(entity:OBBCenter())
|
||||
|
||||
position = position:ToScreen()
|
||||
self.arrowX = math.Clamp(position.x, 0, ScrW())
|
||||
self.arrowY = math.Clamp(position.y, 0, ScrH())
|
||||
end
|
||||
end
|
||||
|
||||
-- arrow
|
||||
if (self.bDrawArrow or (self.bDrawArrow and self.bRaised and !self.bHideArrowWhenRaised)) then
|
||||
local x, y = self:ScreenToLocal(self.arrowX, self.arrowY)
|
||||
|
||||
DisableClipping(true)
|
||||
surface.SetDrawColor(self.arrowColor)
|
||||
surface.DrawLine(0, 0, x * self.fraction, y * self.fraction)
|
||||
surface.DrawRect((x - 2) * self.fraction, (y - 2) * self.fraction, 4, 4)
|
||||
DisableClipping(false)
|
||||
end
|
||||
|
||||
-- contents
|
||||
local x, y = self:GetPos()
|
||||
|
||||
render.SetScissorRect(x, y, x + width * self.fraction, y + height, true)
|
||||
derma.SkinFunc("PaintTooltipBackground", self, width, height)
|
||||
|
||||
for _, v in ipairs(self:GetChildren()) do
|
||||
if (IsValid(v)) then
|
||||
v:PaintManual()
|
||||
end
|
||||
end
|
||||
render.SetScissorRect(0, 0, 0, 0, false)
|
||||
end
|
||||
|
||||
--- Returns the current position of the mouse cursor on the screen.
|
||||
-- @realm client
|
||||
-- @treturn number X position of cursor
|
||||
-- @treturn number Y position of cursor
|
||||
function PANEL:GetCursorPosition()
|
||||
local width, height = self:GetSize()
|
||||
local mouseX, mouseY = gui.MousePos()
|
||||
|
||||
return math.Clamp(mouseX + self.mousePadding, 0, ScrW() - width), math.Clamp(mouseY, 0, ScrH() - height)
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if (!self.bEntity) then
|
||||
if (!vgui.CursorVisible()) then
|
||||
self:SetPos(self.lastX, self.lastY)
|
||||
|
||||
-- if the cursor isn't visible then we don't really need the tooltip to be shown
|
||||
if (!self.bClosing) then
|
||||
self:Remove()
|
||||
end
|
||||
else
|
||||
local newX, newY = self:GetCursorPosition()
|
||||
|
||||
self:SetPos(newX, newY)
|
||||
self.lastX, self.lastY = newX, newY
|
||||
end
|
||||
|
||||
self:MoveToFront() -- dragging a panel w/ tooltip will push the tooltip beneath even the menu panel(???)
|
||||
elseif (IsValid(self.entity) and !self.bClosing) then
|
||||
if (self.bRaised) then
|
||||
self:SetPos(
|
||||
ScrW() * 0.5 - self:GetWide() * 0.5,
|
||||
math.min(ScrH() * 0.5 + self:GetTall() + 32, ScrH() - self:GetTall())
|
||||
)
|
||||
else
|
||||
local entity = self.entity
|
||||
local min, max = entity:GetRotatedAABB(entity:OBBMins() * 0.5, entity:OBBMaxs() * 0.5)
|
||||
min = entity:LocalToWorld(min):ToScreen().x
|
||||
max = entity:LocalToWorld(max):ToScreen().x
|
||||
|
||||
self:SetPos(
|
||||
math.Clamp(math.max(min, max), ScrW() * 0.5 + 64, ScrW() - self:GetWide()),
|
||||
ScrH() * 0.5 - self:GetTall() * 0.5
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns an `ixTooltipRow` corresponding to the given name.
|
||||
-- @realm client
|
||||
-- @string id Name of the row
|
||||
-- @treturn[1] panel Corresponding row
|
||||
-- @treturn[2] nil If the row doesn't exist
|
||||
function PANEL:GetRow(id)
|
||||
for _, v in ipairs(self:GetChildren()) do
|
||||
if (IsValid(v) and v.id == id) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Resizes the tooltip to fit all of the child panels. You should always call this after you are done
|
||||
-- adding all of your rows.
|
||||
-- @realm client
|
||||
function PANEL:SizeToContents()
|
||||
local height = 0
|
||||
local width = 0
|
||||
|
||||
for _, v in ipairs(self:GetChildren()) do
|
||||
if (v:GetWide() > width) then
|
||||
width = v:GetWide()
|
||||
end
|
||||
|
||||
height = height + v:GetTall()
|
||||
end
|
||||
|
||||
self:SetSize(width, height)
|
||||
end
|
||||
|
||||
function PANEL:Remove()
|
||||
if (self.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
self.bClosing = true
|
||||
self:CreateAnimation(animationTime * 0.5, {
|
||||
target = {fraction = 0},
|
||||
easing = "outQuint",
|
||||
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.fraction * 255)
|
||||
end,
|
||||
|
||||
OnComplete = function(animation, panel)
|
||||
BaseClass.Remove(panel)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
vgui.Register("ixTooltip", PANEL, "Panel")
|
||||
|
||||
-- legacy tooltip row
|
||||
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.bMinimal = true
|
||||
self.ixAlpha = 0 -- to avoid conflicts if we're animating a non-tooltip panel
|
||||
|
||||
self:SetExpensiveShadow(1, color_black)
|
||||
self:SetContentAlignment(5)
|
||||
end
|
||||
|
||||
function PANEL:SetImportant()
|
||||
self:SetFont("ixMinimalTitleFont")
|
||||
self:SetBackgroundColor(ix.config.Get("color"))
|
||||
end
|
||||
|
||||
-- background color will affect text instead in minimal tooltips
|
||||
function PANEL:SetBackgroundColor(color)
|
||||
color = table.Copy(color)
|
||||
color.a = math.min(color.a or 255, 100)
|
||||
|
||||
self:SetTextColor(color)
|
||||
self.backgroundColor = color
|
||||
end
|
||||
|
||||
function PANEL:PaintBackground()
|
||||
end
|
||||
|
||||
vgui.Register("ixTooltipMinimalRow", PANEL, "ixTooltipRow")
|
||||
|
||||
-- legacy tooltip
|
||||
DEFINE_BASECLASS("ixTooltip")
|
||||
PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.bMinimal = true
|
||||
|
||||
-- we don't want to animate the alpha since children will handle their own animation, but we want to keep the fraction
|
||||
-- for the background to animate
|
||||
self:CreateAnimation(animationTime, {
|
||||
index = 1,
|
||||
target = {fraction = 1},
|
||||
easing = "outQuint",
|
||||
})
|
||||
|
||||
self:SetAlpha(255)
|
||||
end
|
||||
|
||||
-- we don't need the children to be painted manually
|
||||
function PANEL:Add(...)
|
||||
local panel = BaseClass.Add(self, ...)
|
||||
panel:SetPaintedManually(false)
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:AddRow(id)
|
||||
local panel = self:Add("ixTooltipMinimalRow")
|
||||
panel.id = id
|
||||
panel:SetZPos(#self:GetChildren() * 10)
|
||||
|
||||
return panel
|
||||
end
|
||||
|
||||
function PANEL:Paint(width, height)
|
||||
self:PaintUnder()
|
||||
|
||||
derma.SkinFunc("PaintTooltipMinimalBackground", self, width, height)
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
end
|
||||
|
||||
function PANEL:SizeToContents()
|
||||
-- remove any panels that shouldn't be shown in a minimal tooltip
|
||||
for _, v in ipairs(self:GetChildren()) do
|
||||
if (v.bNoMinimal) then
|
||||
v:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
BaseClass.SizeToContents(self)
|
||||
self:SetPos(ScrW() * 0.5 - self:GetWide() * 0.5, ScrH() * 0.5 + self.mousePadding)
|
||||
|
||||
-- we create animation here since this is the only function that usually gets called after all the rows are populated
|
||||
local children = self:GetChildren()
|
||||
|
||||
-- sort by z index so we can animate them in order
|
||||
table.sort(children, function(a, b)
|
||||
return a:GetZPos() < b:GetZPos()
|
||||
end)
|
||||
|
||||
local i = 1
|
||||
local count = table.Count(children)
|
||||
|
||||
for _, v in ipairs(children) do
|
||||
v.ixAlpha = v.ixAlpha or 0
|
||||
|
||||
v:CreateAnimation((animationTime / count) * i, {
|
||||
easing = "inSine",
|
||||
target = {ixAlpha = 255},
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.ixAlpha)
|
||||
end
|
||||
})
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
DEFINE_BASECLASS("Panel")
|
||||
function PANEL:Remove()
|
||||
if (self.bClosing) then
|
||||
return
|
||||
end
|
||||
|
||||
self.bClosing = true
|
||||
|
||||
-- we create animation here since this is the only function that usually gets called after all the rows are populated
|
||||
local children = self:GetChildren()
|
||||
|
||||
-- sort by z index so we can animate them in order
|
||||
table.sort(children, function(a, b)
|
||||
return a:GetZPos() > b:GetZPos()
|
||||
end)
|
||||
|
||||
local duration = animationTime * 0.5
|
||||
local i = 1
|
||||
local count = table.Count(children)
|
||||
|
||||
for _, v in ipairs(children) do
|
||||
v.ixAlpha = v.ixAlpha or 255
|
||||
|
||||
v:CreateAnimation(duration / count * i, {
|
||||
target = {ixAlpha = 0},
|
||||
Think = function(animation, panel)
|
||||
panel:SetAlpha(panel.ixAlpha)
|
||||
end
|
||||
})
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
self:CreateAnimation(duration, {
|
||||
target = {fraction = 0},
|
||||
OnComplete = function(animation, panel)
|
||||
BaseClass.Remove(panel)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
vgui.Register("ixTooltipMinimal", PANEL, "ixTooltip")
|
||||
1052
gamemodes/helix/gamemode/core/hooks/cl_hooks.lua
Normal file
1052
gamemodes/helix/gamemode/core/hooks/cl_hooks.lua
Normal file
File diff suppressed because it is too large
Load Diff
672
gamemodes/helix/gamemode/core/hooks/sh_hooks.lua
Normal file
672
gamemodes/helix/gamemode/core/hooks/sh_hooks.lua
Normal file
@@ -0,0 +1,672 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
function GM:PlayerNoClip(client)
|
||||
return client:IsAdmin()
|
||||
end
|
||||
|
||||
-- luacheck: globals HOLDTYPE_TRANSLATOR
|
||||
HOLDTYPE_TRANSLATOR = {}
|
||||
HOLDTYPE_TRANSLATOR[""] = "normal"
|
||||
HOLDTYPE_TRANSLATOR["physgun"] = "smg"
|
||||
HOLDTYPE_TRANSLATOR["ar2"] = "smg"
|
||||
HOLDTYPE_TRANSLATOR["crossbow"] = "shotgun"
|
||||
HOLDTYPE_TRANSLATOR["rpg"] = "shotgun"
|
||||
HOLDTYPE_TRANSLATOR["slam"] = "normal"
|
||||
HOLDTYPE_TRANSLATOR["grenade"] = "grenade"
|
||||
HOLDTYPE_TRANSLATOR["fist"] = "normal"
|
||||
HOLDTYPE_TRANSLATOR["melee2"] = "melee"
|
||||
HOLDTYPE_TRANSLATOR["passive"] = "normal"
|
||||
HOLDTYPE_TRANSLATOR["knife"] = "melee"
|
||||
HOLDTYPE_TRANSLATOR["duel"] = "pistol"
|
||||
HOLDTYPE_TRANSLATOR["camera"] = "smg"
|
||||
HOLDTYPE_TRANSLATOR["magic"] = "normal"
|
||||
HOLDTYPE_TRANSLATOR["revolver"] = "pistol"
|
||||
|
||||
-- luacheck: globals PLAYER_HOLDTYPE_TRANSLATOR
|
||||
PLAYER_HOLDTYPE_TRANSLATOR = {}
|
||||
PLAYER_HOLDTYPE_TRANSLATOR[""] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["fist"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["pistol"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["grenade"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["melee"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["slam"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["melee2"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["passive"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["knife"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["duel"] = "normal"
|
||||
PLAYER_HOLDTYPE_TRANSLATOR["bugbait"] = "normal"
|
||||
|
||||
local PLAYER_HOLDTYPE_TRANSLATOR = PLAYER_HOLDTYPE_TRANSLATOR
|
||||
local HOLDTYPE_TRANSLATOR = HOLDTYPE_TRANSLATOR
|
||||
local animationFixOffset = Vector(16.5438, -0.1642, -20.5493)
|
||||
|
||||
function GM:TranslateActivity(client, act)
|
||||
local clientInfo = client:GetTable()
|
||||
local modelClass = clientInfo.ixAnimModelClass or "player"
|
||||
local bRaised = client:IsWepRaised()
|
||||
|
||||
if (modelClass == "player") then
|
||||
local weapon = client:GetActiveWeapon()
|
||||
local bAlwaysRaised = ix.config.Get("weaponAlwaysRaised")
|
||||
weapon = IsValid(weapon) and weapon or nil
|
||||
|
||||
if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then
|
||||
local model = string.lower(client:GetModel())
|
||||
|
||||
if (string.find(model, "zombie")) then
|
||||
local tree = ix.anim.zombie
|
||||
|
||||
if (string.find(model, "fast")) then
|
||||
tree = ix.anim.fastZombie
|
||||
end
|
||||
|
||||
if (tree[act]) then
|
||||
return tree[act]
|
||||
end
|
||||
end
|
||||
|
||||
local holdType = weapon and (weapon.HoldType or weapon:GetHoldType()) or "normal"
|
||||
|
||||
if (!bAlwaysRaised and weapon and !bRaised and client:OnGround()) then
|
||||
holdType = PLAYER_HOLDTYPE_TRANSLATOR[holdType] or "passive"
|
||||
end
|
||||
|
||||
local tree = ix.anim.player[holdType]
|
||||
|
||||
if (tree and tree[act]) then
|
||||
if (isstring(tree[act])) then
|
||||
clientInfo.CalcSeqOverride = client:LookupSequence(tree[act])
|
||||
|
||||
return
|
||||
else
|
||||
return tree[act]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self.BaseClass:TranslateActivity(client, act)
|
||||
end
|
||||
|
||||
if (clientInfo.ixAnimTable) then
|
||||
local glide = clientInfo.ixAnimGlide
|
||||
|
||||
if (client:InVehicle()) then
|
||||
act = clientInfo.ixAnimTable[1]
|
||||
|
||||
local fixVector = clientInfo.ixAnimTable[2]
|
||||
|
||||
if (isvector(fixVector)) then
|
||||
client:SetLocalPos(animationFixOffset)
|
||||
end
|
||||
|
||||
if (isstring(act)) then
|
||||
clientInfo.CalcSeqOverride = client:LookupSequence(act)
|
||||
else
|
||||
return act
|
||||
end
|
||||
elseif (client:OnGround()) then
|
||||
if (clientInfo.ixAnimTable[act]) then
|
||||
local act2 = clientInfo.ixAnimTable[act][bRaised and 2 or 1]
|
||||
|
||||
if (isstring(act2)) then
|
||||
clientInfo.CalcSeqOverride = client:LookupSequence(act2)
|
||||
else
|
||||
return act2
|
||||
end
|
||||
end
|
||||
elseif (glide) then
|
||||
if (isstring(glide)) then
|
||||
clientInfo.CalcSeqOverride = client:LookupSequence(glide)
|
||||
else
|
||||
return clientInfo.ixAnimGlide
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GM:CanPlayerUseBusiness(client, uniqueID)
|
||||
local itemTable = ix.item.list[uniqueID]
|
||||
|
||||
if (!client:GetCharacter()) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (itemTable.noBusiness) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (itemTable.factions) then
|
||||
local allowed = false
|
||||
|
||||
if (istable(itemTable.factions)) then
|
||||
for _, v in pairs(itemTable.factions) do
|
||||
if (client:Team() == v) then
|
||||
allowed = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif (client:Team() != itemTable.factions) then
|
||||
allowed = false
|
||||
end
|
||||
|
||||
if (!allowed) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if (itemTable.classes) then
|
||||
local allowed = false
|
||||
|
||||
if (istable(itemTable.classes)) then
|
||||
for _, v in pairs(itemTable.classes) do
|
||||
if (client:GetCharacter():GetClass() == v) then
|
||||
allowed = true
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif (client:GetCharacter():GetClass() == itemTable.classes) then
|
||||
allowed = true
|
||||
end
|
||||
|
||||
if (!allowed) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if (itemTable.flag) then
|
||||
if (!client:GetCharacter():HasFlags(itemTable.flag)) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function GM:DoAnimationEvent(client, event, data)
|
||||
local class = client.ixAnimModelClass
|
||||
|
||||
if (class == "player") then
|
||||
return self.BaseClass:DoAnimationEvent(client, event, data)
|
||||
else
|
||||
local weapon = client:GetActiveWeapon()
|
||||
|
||||
if (IsValid(weapon)) then
|
||||
local animation = client.ixAnimTable
|
||||
|
||||
if (event == PLAYERANIMEVENT_ATTACK_PRIMARY) then
|
||||
client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true)
|
||||
|
||||
return ACT_VM_PRIMARYATTACK
|
||||
elseif (event == PLAYERANIMEVENT_ATTACK_SECONDARY) then
|
||||
client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.attack or ACT_GESTURE_RANGE_ATTACK_SMG1, true)
|
||||
|
||||
return ACT_VM_SECONDARYATTACK
|
||||
elseif (event == PLAYERANIMEVENT_RELOAD) then
|
||||
client:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, animation.reload or ACT_GESTURE_RELOAD_SMG1, true)
|
||||
|
||||
return ACT_INVALID
|
||||
elseif (event == PLAYERANIMEVENT_JUMP) then
|
||||
client:AnimRestartMainSequence()
|
||||
|
||||
return ACT_INVALID
|
||||
elseif (event == PLAYERANIMEVENT_CANCEL_RELOAD) then
|
||||
client:AnimResetGestureSlot(GESTURE_SLOT_ATTACK_AND_RELOAD)
|
||||
|
||||
return ACT_INVALID
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ACT_INVALID
|
||||
end
|
||||
|
||||
function GM:EntityEmitSound(data)
|
||||
if (data.Entity.ixIsMuted) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function GM:EntityRemoved(entity)
|
||||
if (SERVER) then
|
||||
entity:ClearNetVars()
|
||||
elseif (entity:IsWeapon()) then
|
||||
local owner = entity:GetOwner()
|
||||
|
||||
-- GetActiveWeapon is the player's new weapon at this point so we'll assume
|
||||
-- that the player switched away from this weapon
|
||||
if (IsValid(owner) and owner:IsPlayer()) then
|
||||
hook.Run("PlayerWeaponChanged", owner, owner:GetActiveWeapon())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdatePlayerHoldType(client, weapon)
|
||||
weapon = weapon or client:GetActiveWeapon()
|
||||
local holdType = "normal"
|
||||
|
||||
if (IsValid(weapon)) then
|
||||
holdType = weapon.HoldType or weapon:GetHoldType()
|
||||
holdType = HOLDTYPE_TRANSLATOR[holdType] or holdType
|
||||
end
|
||||
|
||||
client.ixAnimHoldType = holdType
|
||||
end
|
||||
|
||||
local function UpdateAnimationTable(client, vehicle)
|
||||
local baseTable = ix.anim[client.ixAnimModelClass] or {}
|
||||
|
||||
if (IsValid(client) and IsValid(vehicle)) then
|
||||
local vehicleClass = vehicle:IsChair() and "chair" or vehicle:GetClass()
|
||||
|
||||
if (baseTable.vehicle and baseTable.vehicle[vehicleClass]) then
|
||||
client.ixAnimTable = baseTable.vehicle[vehicleClass]
|
||||
else
|
||||
client.ixAnimTable = baseTable.normal[ACT_MP_CROUCH_IDLE]
|
||||
end
|
||||
else
|
||||
client.ixAnimTable = baseTable[client.ixAnimHoldType]
|
||||
end
|
||||
|
||||
client.ixAnimGlide = baseTable["glide"]
|
||||
end
|
||||
|
||||
function GM:PlayerWeaponChanged(client, weapon)
|
||||
UpdatePlayerHoldType(client, weapon)
|
||||
UpdateAnimationTable(client)
|
||||
|
||||
if (CLIENT) then
|
||||
return
|
||||
end
|
||||
|
||||
-- update weapon raise state
|
||||
if (weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()]) then
|
||||
client:SetWepRaised(true, weapon)
|
||||
return
|
||||
elseif (weapon.IsAlwaysLowered or weapon.NeverRaised) then
|
||||
client:SetWepRaised(false, weapon)
|
||||
return
|
||||
end
|
||||
|
||||
-- If the player has been forced to have their weapon lowered.
|
||||
if (client:IsRestricted()) then
|
||||
client:SetWepRaised(false, weapon)
|
||||
return
|
||||
end
|
||||
|
||||
-- Let the config decide before actual results.
|
||||
if (ix.config.Get("weaponAlwaysRaised")) then
|
||||
client:SetWepRaised(true, weapon)
|
||||
return
|
||||
end
|
||||
|
||||
client:SetWepRaised(false, weapon)
|
||||
end
|
||||
|
||||
function GM:PlayerSwitchWeapon(client, oldWeapon, weapon)
|
||||
if (!IsFirstTimePredicted()) then
|
||||
return
|
||||
end
|
||||
|
||||
-- the player switched weapon themself (i.e not through SelectWeapon), so we have to network it here
|
||||
if (SERVER) then
|
||||
net.Start("PlayerSelectWeapon")
|
||||
net.WriteEntity(client)
|
||||
net.WriteString(weapon:GetClass())
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
hook.Run("PlayerWeaponChanged", client, weapon)
|
||||
end
|
||||
|
||||
function GM:PlayerModelChanged(client, model)
|
||||
client.ixAnimModelClass = ix.anim.GetModelClass(model)
|
||||
|
||||
UpdateAnimationTable(client)
|
||||
end
|
||||
|
||||
do
|
||||
local vectorAngle = FindMetaTable("Vector").Angle
|
||||
local normalizeAngle = math.NormalizeAngle
|
||||
|
||||
function GM:CalcMainActivity(client, velocity)
|
||||
local clientInfo = client:GetTable()
|
||||
local forcedSequence = client:GetNetVar("forcedSequence")
|
||||
|
||||
if (forcedSequence) then
|
||||
if (client:GetSequence() != forcedSequence) then
|
||||
client:SetCycle(0)
|
||||
end
|
||||
|
||||
return -1, forcedSequence
|
||||
end
|
||||
|
||||
client:SetPoseParameter("move_yaw", normalizeAngle(vectorAngle(velocity)[2] - client:EyeAngles()[2]))
|
||||
|
||||
local sequenceOverride = clientInfo.CalcSeqOverride
|
||||
clientInfo.CalcSeqOverride = -1
|
||||
clientInfo.CalcIdeal = ACT_MP_STAND_IDLE
|
||||
|
||||
-- we could call the baseclass function, but it's faster to do it this way
|
||||
local BaseClass = self.BaseClass
|
||||
|
||||
if (BaseClass:HandlePlayerNoClipping(client, velocity) or
|
||||
BaseClass:HandlePlayerDriving(client) or
|
||||
BaseClass:HandlePlayerVaulting(client, velocity) or
|
||||
BaseClass:HandlePlayerJumping(client, velocity) or
|
||||
BaseClass:HandlePlayerSwimming(client, velocity) or
|
||||
BaseClass:HandlePlayerDucking(client, velocity)) then -- luacheck: ignore 542
|
||||
else
|
||||
local length = velocity:Length2DSqr()
|
||||
|
||||
if (length > 22500) then
|
||||
clientInfo.CalcIdeal = ACT_MP_RUN
|
||||
elseif (length > 0.25) then
|
||||
clientInfo.CalcIdeal = ACT_MP_WALK
|
||||
end
|
||||
end
|
||||
|
||||
clientInfo.m_bWasOnGround = client:OnGround()
|
||||
clientInfo.m_bWasNoclipping = (client:GetMoveType() == MOVETYPE_NOCLIP and !client:InVehicle())
|
||||
|
||||
return clientInfo.CalcIdeal, sequenceOverride or clientInfo.CalcSeqOverride or -1
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local KEY_BLACKLIST = IN_ATTACK + IN_ATTACK2
|
||||
|
||||
function GM:StartCommand(client, command)
|
||||
if (!client:CanShootWeapon()) then
|
||||
command:RemoveKey(KEY_BLACKLIST)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GM:CharacterVarChanged(char, varName, oldVar, newVar)
|
||||
if (ix.char.varHooks[varName]) then
|
||||
for _, v in pairs(ix.char.varHooks[varName]) do
|
||||
v(char, oldVar, newVar)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GM:CanPlayerThrowPunch(client)
|
||||
if (!client:IsWepRaised()) then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function GM:OnCharacterCreated(client, character)
|
||||
local faction = ix.faction.Get(character:GetFaction())
|
||||
|
||||
if (faction and faction.OnCharacterCreated) then
|
||||
faction:OnCharacterCreated(client, character)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:GetDefaultCharacterName(client, faction)
|
||||
local info = ix.faction.indices[faction]
|
||||
|
||||
if (info and info.GetDefaultName) then
|
||||
return info:GetDefaultName(client)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:CanPlayerUseCharacter(client, character)
|
||||
local banned = character:GetData("banned")
|
||||
|
||||
if (banned) then
|
||||
if (isnumber(banned)) then
|
||||
if (banned < os.time()) then
|
||||
return
|
||||
end
|
||||
|
||||
return false, "@charBannedTemp"
|
||||
end
|
||||
|
||||
return false, "@charBanned"
|
||||
end
|
||||
|
||||
local bHasWhitelist = client:HasWhitelist(character:GetFaction())
|
||||
|
||||
if (!bHasWhitelist) then
|
||||
return false, "@noWhitelist"
|
||||
end
|
||||
end
|
||||
|
||||
function GM:CanProperty(client, property, entity)
|
||||
if (client:IsAdmin()) then
|
||||
return true
|
||||
end
|
||||
|
||||
if (CLIENT and (property == "remover" or property == "collision")) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function GM:PhysgunPickup(client, entity)
|
||||
local bPickup = self.BaseClass:PhysgunPickup(client, entity)
|
||||
|
||||
if (!bPickup and entity:IsPlayer() and (client:IsSuperAdmin() or client:IsAdmin() and !entity:IsSuperAdmin())) then
|
||||
bPickup = true
|
||||
end
|
||||
|
||||
if (bPickup) then
|
||||
if (entity:IsPlayer()) then
|
||||
entity:SetMoveType(MOVETYPE_NONE)
|
||||
elseif (!entity.ixCollisionGroup) then
|
||||
entity.ixCollisionGroup = entity:GetCollisionGroup()
|
||||
entity:SetCollisionGroup(COLLISION_GROUP_WEAPON)
|
||||
end
|
||||
end
|
||||
|
||||
return bPickup
|
||||
end
|
||||
|
||||
function GM:PhysgunDrop(client, entity)
|
||||
if (entity:IsPlayer()) then
|
||||
entity:SetMoveType(MOVETYPE_WALK)
|
||||
elseif (entity.ixCollisionGroup) then
|
||||
entity:SetCollisionGroup(entity.ixCollisionGroup)
|
||||
entity.ixCollisionGroup = nil
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local TOOL_DANGEROUS = {}
|
||||
TOOL_DANGEROUS["dynamite"] = true
|
||||
TOOL_DANGEROUS["duplicator"] = true
|
||||
|
||||
function GM:CanTool(client, trace, tool)
|
||||
if (client:IsAdmin()) then
|
||||
return true
|
||||
end
|
||||
|
||||
if (TOOL_DANGEROUS[tool]) then
|
||||
return false
|
||||
end
|
||||
|
||||
return self.BaseClass:CanTool(client, trace, tool)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:Move(client, moveData)
|
||||
local char = client:GetCharacter()
|
||||
|
||||
if (char) then
|
||||
if (client:GetNetVar("actEnterAngle")) then
|
||||
moveData:SetForwardSpeed(0)
|
||||
moveData:SetSideSpeed(0)
|
||||
moveData:SetVelocity(vector_origin)
|
||||
end
|
||||
|
||||
if (client:GetMoveType() == MOVETYPE_WALK and moveData:KeyDown(IN_WALK)) then
|
||||
local mf, ms = 0, 0
|
||||
local speed = client:GetWalkSpeed()
|
||||
local ratio = ix.config.Get("walkRatio")
|
||||
|
||||
if (moveData:KeyDown(IN_FORWARD)) then
|
||||
mf = ratio
|
||||
elseif (moveData:KeyDown(IN_BACK)) then
|
||||
mf = -ratio
|
||||
end
|
||||
|
||||
if (moveData:KeyDown(IN_MOVELEFT)) then
|
||||
ms = -ratio
|
||||
elseif (moveData:KeyDown(IN_MOVERIGHT)) then
|
||||
ms = ratio
|
||||
end
|
||||
|
||||
moveData:SetForwardSpeed(mf * speed)
|
||||
moveData:SetSideSpeed(ms * speed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- I'm sorry. ~Aspect
|
||||
--[[
|
||||
function GM:CanTransferItem(itemObject, curInv, inventory)
|
||||
if (SERVER) then
|
||||
local client = itemObject.GetOwner and itemObject:GetOwner() or nil
|
||||
|
||||
if (IsValid(client) and curInv.GetReceivers) then
|
||||
local bAuthorized = false
|
||||
|
||||
for _, v in ipairs(curInv:GetReceivers()) do
|
||||
if (client == v) then
|
||||
bAuthorized = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!bAuthorized) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- we can transfer anything that isn't a bag
|
||||
if (!itemObject or !itemObject.isBag) then
|
||||
return
|
||||
end
|
||||
|
||||
-- don't allow bags to be put inside bags
|
||||
if (inventory.id != 0 and curInv.id != inventory.id) then
|
||||
if (inventory.vars and inventory.vars.isBag) then
|
||||
local owner = itemObject:GetOwner()
|
||||
|
||||
if (IsValid(owner)) then
|
||||
owner:NotifyLocalized("nestedBags")
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
elseif (inventory.id != 0 and curInv.id == inventory.id) then
|
||||
-- we are simply moving items around if we're transferring to the same inventory
|
||||
return
|
||||
end
|
||||
|
||||
inventory = ix.item.inventories[itemObject:GetData("id")]
|
||||
|
||||
-- don't allow transferring items that are in use
|
||||
if (inventory) then
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v:GetData("equip") == true) then
|
||||
local owner = itemObject:GetOwner()
|
||||
|
||||
if (owner and IsValid(owner)) then
|
||||
owner:NotifyLocalized("equippedBag")
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
function GM:CanPlayerEquipItem(client, item)
|
||||
local slots = ix.plugin.list["inventoryslots"]
|
||||
if slots then return slots:CanEquipOrUnequip(client, item, true) end
|
||||
return item.invID == client:GetCharacter():GetInventory():GetID()
|
||||
end
|
||||
|
||||
function GM:CanPlayerUnequipItem(client, item)
|
||||
local slots = ix.plugin.list["inventoryslots"]
|
||||
if slots then return slots:CanEquipOrUnequip(client, item, false) end
|
||||
return item.invID == client:GetCharacter():GetInventory():GetID()
|
||||
end
|
||||
|
||||
function GM:OnItemTransferred(item, curInv, inventory)
|
||||
local bagInventory = item.GetInventory and item:GetInventory()
|
||||
|
||||
if (!bagInventory) then
|
||||
return
|
||||
end
|
||||
|
||||
-- we need to retain the receiver if the owner changed while viewing as storage
|
||||
if (inventory.storageInfo and isfunction(curInv.GetOwner)) then
|
||||
bagInventory:AddReceiver(curInv:GetOwner())
|
||||
end
|
||||
end
|
||||
|
||||
function GM:ShowHelp() end
|
||||
|
||||
function GM:PreGamemodeLoaded()
|
||||
hook.Remove("PostDrawEffects", "RenderWidgets")
|
||||
hook.Remove("PlayerTick", "TickWidgets")
|
||||
hook.Remove("RenderScene", "RenderStereoscopy")
|
||||
end
|
||||
|
||||
function GM:PostGamemodeLoaded()
|
||||
baseclass.Set("ix_character", ix.meta.character)
|
||||
baseclass.Set("ix_inventory", ix.meta.inventory)
|
||||
baseclass.Set("ix_item", ix.meta.item)
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("PlayerVehicle")
|
||||
|
||||
function GM:PlayerEnteredVehicle(client, vehicle, role)
|
||||
UpdateAnimationTable(client)
|
||||
|
||||
net.Start("PlayerVehicle")
|
||||
net.WriteEntity(client)
|
||||
net.WriteEntity(vehicle)
|
||||
net.WriteBool(true)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
function GM:PlayerLeaveVehicle(client, vehicle)
|
||||
UpdateAnimationTable(client)
|
||||
|
||||
net.Start("PlayerVehicle")
|
||||
net.WriteEntity(client)
|
||||
net.WriteEntity(vehicle)
|
||||
net.WriteBool(false)
|
||||
net.Broadcast()
|
||||
end
|
||||
else
|
||||
net.Receive("PlayerVehicle", function(length)
|
||||
local client = net.ReadEntity()
|
||||
local vehicle = net.ReadEntity()
|
||||
local bEntered = net.ReadBool()
|
||||
|
||||
UpdateAnimationTable(client, bEntered and vehicle or false)
|
||||
end)
|
||||
end
|
||||
951
gamemodes/helix/gamemode/core/hooks/sv_hooks.lua
Normal file
951
gamemodes/helix/gamemode/core/hooks/sv_hooks.lua
Normal file
@@ -0,0 +1,951 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
function GM:PlayerInitialSpawn(client)
|
||||
client.ixJoinTime = RealTime()
|
||||
|
||||
if (client:IsBot()) then
|
||||
local botID = os.time() + client:EntIndex()
|
||||
local index = math.random(1, table.Count(ix.faction.indices))
|
||||
local faction = ix.faction.indices[index]
|
||||
|
||||
local model = faction and table.Random(faction:GetModels(client)) or "models/gman.mdl"
|
||||
if (istable(model)) then
|
||||
model = table.Random(model)
|
||||
end
|
||||
|
||||
local character = ix.char.New({
|
||||
name = client:Name(),
|
||||
faction = faction and faction.uniqueID or "unknown",
|
||||
model = model,
|
||||
}, botID, client, client:SteamID64())
|
||||
character.isBot = true
|
||||
|
||||
local inventory = ix.inventory.Create(ix.config.Get("inventoryWidth"), ix.config.Get("inventoryHeight"), botID)
|
||||
inventory:SetOwner(botID)
|
||||
inventory.noSave = true
|
||||
|
||||
character.vars.inv = {inventory}
|
||||
|
||||
ix.char.loaded[botID] = character
|
||||
|
||||
character:Setup()
|
||||
client:Spawn()
|
||||
|
||||
ix.chat.Send(nil, "connect", client:SteamName())
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
ix.config.Send(client)
|
||||
ix.date.Send(client)
|
||||
|
||||
client:LoadData(function(data)
|
||||
if (!IsValid(client)) then return end
|
||||
|
||||
-- Don't use the character cache if they've connected to another server using the same database
|
||||
local address = ix.util.GetAddress()
|
||||
local bNoCache = client:GetData("lastIP", address) != address
|
||||
client:SetData("lastIP", address)
|
||||
|
||||
net.Start("ixDataSync")
|
||||
net.WriteTable(data or {})
|
||||
net.WriteUInt(client.ixPlayTime or 0, 32)
|
||||
net.Send(client)
|
||||
|
||||
ix.char.Restore(client, function(charList)
|
||||
if (!IsValid(client)) then return end
|
||||
|
||||
MsgN("Loaded (" .. table.concat(charList, ", ") .. ") for " .. client:Name())
|
||||
|
||||
for _, v in ipairs(charList) do
|
||||
ix.char.loaded[v]:Sync(client)
|
||||
end
|
||||
|
||||
client.ixCharList = charList
|
||||
|
||||
net.Start("ixCharacterMenu")
|
||||
net.WriteUInt(#charList, 6)
|
||||
|
||||
for _, v in ipairs(charList) do
|
||||
net.WriteUInt(v, 32)
|
||||
end
|
||||
|
||||
net.Send(client)
|
||||
|
||||
client.ixLoaded = true
|
||||
client:SetData("intro", true)
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetCharacter()) then
|
||||
v:GetCharacter():Sync(client)
|
||||
end
|
||||
end
|
||||
end, bNoCache)
|
||||
|
||||
ix.chat.Send(nil, "connect", client:SteamName())
|
||||
end)
|
||||
|
||||
client:SetNoDraw(true)
|
||||
client:SetNotSolid(true)
|
||||
client:Lock()
|
||||
client:SyncVars()
|
||||
|
||||
timer.Simple(1, function()
|
||||
if (!IsValid(client)) then
|
||||
return
|
||||
end
|
||||
|
||||
client:KillSilent()
|
||||
client:StripAmmo()
|
||||
end)
|
||||
end
|
||||
|
||||
function GM:PlayerUse(client, entity)
|
||||
if (client:IsRestricted() or (isfunction(entity.GetEntityMenu) and entity:GetClass() != "ix_item")) then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function GM:KeyPress(client, key)
|
||||
if (key == IN_RELOAD) then
|
||||
timer.Create("ixToggleRaise"..client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function()
|
||||
if (IsValid(client)) then
|
||||
client:ToggleWepRaised()
|
||||
end
|
||||
end)
|
||||
elseif (key == IN_USE) then
|
||||
local data = {}
|
||||
data.start = client:GetShootPos()
|
||||
data.endpos = data.start + client:GetAimVector() * 96
|
||||
data.filter = client
|
||||
local entity = util.TraceLine(data).Entity
|
||||
|
||||
if (IsValid(entity) and hook.Run("PlayerUse", client, entity)) then
|
||||
if (entity:IsDoor()) then
|
||||
local result = hook.Run("CanPlayerUseDoor", client, entity)
|
||||
|
||||
if (result != false) then
|
||||
hook.Run("PlayerUseDoor", client, entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GM:KeyRelease(client, key)
|
||||
if (key == IN_RELOAD) then
|
||||
timer.Remove("ixToggleRaise" .. client:SteamID())
|
||||
elseif (key == IN_USE) then
|
||||
timer.Remove("ixCharacterInteraction" .. client:SteamID())
|
||||
end
|
||||
end
|
||||
|
||||
function GM:CanPlayerInteractItem(client, action, item)
|
||||
if (client:IsRestricted()) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (IsValid(client.ixRagdoll)) then
|
||||
client:NotifyLocalized("notNow")
|
||||
return false
|
||||
end
|
||||
|
||||
if (action == "drop" and hook.Run("CanPlayerDropItem", client, item) == false) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (action == "take" and hook.Run("CanPlayerTakeItem", client, item) == false) then
|
||||
return false
|
||||
end
|
||||
|
||||
if (isentity(item) and item.ixSteamID and item.ixCharID
|
||||
and item.ixSteamID == client:SteamID() and item.ixCharID != client:GetCharacter():GetID()
|
||||
and !item:GetItemTable().bAllowMultiCharacterInteraction) then
|
||||
client:NotifyLocalized("itemOwned")
|
||||
return false
|
||||
end
|
||||
|
||||
return client:Alive()
|
||||
end
|
||||
|
||||
function GM:CanPlayerDropItem(client, item)
|
||||
|
||||
end
|
||||
|
||||
function GM:CanPlayerTakeItem(client, item)
|
||||
|
||||
end
|
||||
|
||||
function GM:PlayerShouldTakeDamage(client, attacker)
|
||||
return client:GetCharacter() != nil
|
||||
end
|
||||
|
||||
function GM:GetFallDamage(client, speed)
|
||||
return (speed - 580) * (100 / 444)
|
||||
end
|
||||
|
||||
function GM:EntityTakeDamage(entity, dmgInfo)
|
||||
local inflictor = dmgInfo:GetInflictor()
|
||||
|
||||
if (IsValid(inflictor) and inflictor:GetClass() == "ix_item") then
|
||||
dmgInfo:SetDamage(0)
|
||||
return
|
||||
end
|
||||
|
||||
if (IsValid(entity.ixPlayer)) then
|
||||
if (IsValid(entity.ixHeldOwner)) then
|
||||
dmgInfo:SetDamage(0)
|
||||
return
|
||||
end
|
||||
|
||||
if (dmgInfo:IsDamageType(DMG_CRUSH)) then
|
||||
if ((entity.ixFallGrace or 0) < CurTime()) then
|
||||
if (dmgInfo:GetDamage() <= 10) then
|
||||
dmgInfo:SetDamage(0)
|
||||
end
|
||||
|
||||
entity.ixFallGrace = CurTime() + 0.5
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
entity.ixPlayer:TakeDamageInfo(dmgInfo)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:PrePlayerLoadedCharacter(client, character, lastChar)
|
||||
-- Reset all bodygroups
|
||||
client:ResetBodygroups()
|
||||
|
||||
-- Remove all skins
|
||||
client:SetSkin(0)
|
||||
end
|
||||
|
||||
function GM:PlayerLoadedCharacter(client, character, lastChar)
|
||||
local query = mysql:Update("ix_characters")
|
||||
query:Where("id", character:GetID())
|
||||
query:Update("last_join_time", math.floor(os.time()))
|
||||
query:Execute()
|
||||
|
||||
if (lastChar) then
|
||||
local charEnts = lastChar:GetVar("charEnts") or {}
|
||||
|
||||
for _, v in ipairs(charEnts) do
|
||||
if (v and IsValid(v)) then
|
||||
v:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
lastChar:SetVar("charEnts", nil)
|
||||
end
|
||||
|
||||
if (character) then
|
||||
for _, v in pairs(ix.class.list) do
|
||||
if (v.faction == client:Team() and v.isDefault) then
|
||||
character:SetClass(v.index)
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (IsValid(client.ixRagdoll)) then
|
||||
client.ixRagdoll.ixNoReset = true
|
||||
client.ixRagdoll.ixIgnoreDelete = true
|
||||
client.ixRagdoll:Remove()
|
||||
end
|
||||
|
||||
local faction = ix.faction.indices[character:GetFaction()]
|
||||
local uniqueID = "ixSalary" .. client:SteamID64()
|
||||
|
||||
if (faction and faction.pay and faction.pay > 0) then
|
||||
timer.Create(uniqueID, faction.payTime or 300, 0, function()
|
||||
if (IsValid(client)) then
|
||||
if (hook.Run("CanPlayerEarnSalary", client, faction) != false) then
|
||||
local pay = hook.Run("GetSalaryAmount", client, faction) or faction.pay
|
||||
|
||||
character:GiveMoney(pay)
|
||||
client:NotifyLocalized("salary", ix.currency.Get(pay))
|
||||
end
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
elseif (timer.Exists(uniqueID)) then
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
|
||||
hook.Run("PlayerLoadout", client)
|
||||
end
|
||||
|
||||
function GM:CharacterLoaded(character)
|
||||
local client = character:GetPlayer()
|
||||
|
||||
if (IsValid(client)) then
|
||||
local uniqueID = "ixSaveChar"..client:SteamID()
|
||||
|
||||
timer.Create(uniqueID, ix.config.Get("saveInterval"), 0, function()
|
||||
if (IsValid(client) and client:GetCharacter()) then
|
||||
client:GetCharacter():Save()
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:PlayerSay(client, text)
|
||||
local chatType, message, anonymous = ix.chat.Parse(client, text, true)
|
||||
|
||||
if (chatType == "ic") then
|
||||
if (ix.command.Parse(client, message)) then
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
text = ix.chat.Send(client, chatType, message, anonymous)
|
||||
|
||||
if (isstring(text)) then
|
||||
ix.log.Add(client, "chat", chatType and chatType:utf8upper() or "??", text)
|
||||
end
|
||||
|
||||
hook.Run("PostPlayerSay", client, chatType, message, anonymous)
|
||||
return ""
|
||||
end
|
||||
|
||||
function GM:CanAutoFormatMessage(client, chatType, message)
|
||||
return chatType == "ic" or chatType == "w" or chatType == "y"
|
||||
end
|
||||
|
||||
function GM:PlayerSpawn(client)
|
||||
client:SetNoDraw(false)
|
||||
client:UnLock()
|
||||
client:SetNotSolid(false)
|
||||
client:SetMoveType(MOVETYPE_WALK)
|
||||
client:SetRagdolled(false)
|
||||
client:SetAction()
|
||||
client:SetDSP(1)
|
||||
|
||||
hook.Run("PlayerLoadout", client)
|
||||
end
|
||||
|
||||
-- Shortcuts for (super)admin only things.
|
||||
local function IsAdmin(_, client)
|
||||
return client:IsAdmin()
|
||||
end
|
||||
|
||||
-- Set the gamemode hooks to the appropriate shortcuts.
|
||||
GM.PlayerGiveSWEP = IsAdmin
|
||||
GM.PlayerSpawnEffect = IsAdmin
|
||||
GM.PlayerSpawnSENT = IsAdmin
|
||||
|
||||
function GM:PlayerSpawnNPC(client, npcType, weapon)
|
||||
return client:IsAdmin() or client:GetCharacter():HasFlags("n")
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnSWEP(client, weapon, info)
|
||||
return client:IsAdmin()
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnProp(client)
|
||||
if (client:GetCharacter() and client:GetCharacter():HasFlags("e")) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnRagdoll(client)
|
||||
if (client:GetCharacter() and client:GetCharacter():HasFlags("r")) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnVehicle(client, model, name, data)
|
||||
if (client:GetCharacter()) then
|
||||
if (data.Category == "Chairs") then
|
||||
return client:GetCharacter():HasFlags("c")
|
||||
else
|
||||
return client:GetCharacter():HasFlags("C")
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnedEffect(client, model, entity)
|
||||
entity:SetNetVar("owner", client:GetCharacter():GetID())
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnedNPC(client, entity)
|
||||
entity:SetNetVar("owner", client:GetCharacter():GetID())
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnedProp(client, model, entity)
|
||||
entity:SetNetVar("owner", client:GetCharacter():GetID())
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnedRagdoll(client, model, entity)
|
||||
entity:SetNetVar("owner", client:GetCharacter():GetID())
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnedSENT(client, entity)
|
||||
entity:SetNetVar("owner", client:GetCharacter():GetID())
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnedSWEP(client, entity)
|
||||
entity:SetNetVar("owner", client:GetCharacter():GetID())
|
||||
end
|
||||
|
||||
function GM:PlayerSpawnedVehicle(client, entity)
|
||||
entity:SetNetVar("owner", client:GetCharacter():GetID())
|
||||
end
|
||||
|
||||
ix.allowedHoldableClasses = {
|
||||
["ix_item"] = true,
|
||||
["ix_money"] = true,
|
||||
["ix_shipment"] = true,
|
||||
["prop_physics"] = true,
|
||||
["prop_physics_override"] = true,
|
||||
["prop_physics_multiplayer"] = true,
|
||||
["prop_ragdoll"] = true
|
||||
}
|
||||
|
||||
function GM:CanPlayerHoldObject(client, entity)
|
||||
if (ix.allowedHoldableClasses[entity:GetClass()]) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local voiceDistance = 360000
|
||||
local function CalcPlayerCanHearPlayersVoice(listener)
|
||||
if (!IsValid(listener)) then
|
||||
return
|
||||
end
|
||||
|
||||
listener.ixVoiceHear = listener.ixVoiceHear or {}
|
||||
|
||||
local eyePos = listener:EyePos()
|
||||
for _, speaker in ipairs(player.GetAll()) do
|
||||
local speakerEyePos = speaker:EyePos()
|
||||
listener.ixVoiceHear[speaker] = eyePos:DistToSqr(speakerEyePos) < voiceDistance
|
||||
end
|
||||
end
|
||||
|
||||
function GM:InitializedConfig()
|
||||
ix.date.Initialize()
|
||||
|
||||
voiceDistance = ix.config.Get("voiceDistance")
|
||||
voiceDistance = voiceDistance * voiceDistance
|
||||
end
|
||||
|
||||
function GM:VoiceToggled(bAllowVoice)
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
local uniqueID = v:SteamID64() .. "ixCanHearPlayersVoice"
|
||||
|
||||
if (bAllowVoice) then
|
||||
timer.Create(uniqueID, 0.5, 0, function()
|
||||
CalcPlayerCanHearPlayersVoice(v)
|
||||
end)
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
|
||||
v.ixVoiceHear = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GM:VoiceDistanceChanged(distance)
|
||||
voiceDistance = distance * distance
|
||||
end
|
||||
|
||||
-- Called when weapons should be given to a player.
|
||||
function GM:PlayerLoadout(client)
|
||||
if (client.ixSkipLoadout) then
|
||||
client.ixSkipLoadout = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
client:SetWeaponColor(Vector(client:GetInfo("cl_weaponcolor")))
|
||||
client:StripWeapons()
|
||||
client:StripAmmo()
|
||||
client:SetLocalVar("blur", nil)
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
-- Check if they have loaded a character.
|
||||
if (character) then
|
||||
client:SetupHands()
|
||||
-- Set their player model to the character's model.
|
||||
client:SetModel(character:GetModel())
|
||||
client:Give("ix_hands")
|
||||
client:SetWalkSpeed(ix.config.Get("walkSpeed"))
|
||||
client:SetRunSpeed(ix.config.Get("runSpeed"))
|
||||
client:SetJumpPower(ix.config.Get("jumpPower"))
|
||||
client:SetHealth(character:GetData("health", client:GetMaxHealth()))
|
||||
|
||||
local faction = ix.faction.indices[client:Team()]
|
||||
|
||||
if (faction) then
|
||||
-- If their faction wants to do something when the player spawns, let it.
|
||||
if (faction.OnSpawn) then
|
||||
faction:OnSpawn(client)
|
||||
end
|
||||
|
||||
-- @todo add docs for player:Give() failing if player already has weapon - which means if a player is given a weapon
|
||||
-- here due to the faction weapons table, the weapon's :Give call in the weapon base will fail since the player
|
||||
-- will already have it by then. This will cause issues for weapons that have pac data since the parts are applied
|
||||
-- only if the weapon returned by :Give() is valid
|
||||
|
||||
-- If the faction has default weapons, give them to the player.
|
||||
if (faction.weapons) then
|
||||
for _, v in ipairs(faction.weapons) do
|
||||
client:Give(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Ditto, but for classes.
|
||||
local class = ix.class.list[client:GetCharacter():GetClass()]
|
||||
|
||||
if (class) then
|
||||
if (class.OnSpawn) then
|
||||
class:OnSpawn(client)
|
||||
end
|
||||
|
||||
if (class.weapons) then
|
||||
for _, v in ipairs(class.weapons) do
|
||||
client:Give(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply any flags as needed.
|
||||
ix.flag.OnSpawn(client)
|
||||
ix.attributes.Setup(client)
|
||||
|
||||
hook.Run("PostPlayerLoadout", client)
|
||||
|
||||
client:SelectWeapon("ix_hands")
|
||||
else
|
||||
client:SetNoDraw(true)
|
||||
client:Lock()
|
||||
client:SetNotSolid(true)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:PostPlayerLoadout(client)
|
||||
-- Reload All Attrib Boosts
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character:GetInventory()) then
|
||||
for _, v in pairs(character:GetInventory():GetItems()) do
|
||||
v:Call("OnLoadout", client)
|
||||
|
||||
if (v:GetData("equip") and v.attribBoosts) then
|
||||
for attribKey, attribValue in pairs(v.attribBoosts) do
|
||||
character:AddBoost(v.uniqueID, attribKey, attribValue)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (ix.config.Get("allowVoice")) then
|
||||
timer.Create(client:SteamID64() .. "ixCanHearPlayersVoice", 0.5, 0, function()
|
||||
CalcPlayerCanHearPlayersVoice(client)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local deathSounds = {
|
||||
Sound("vo/npc/male01/pain07.wav"),
|
||||
Sound("vo/npc/male01/pain08.wav"),
|
||||
Sound("vo/npc/male01/pain09.wav")
|
||||
}
|
||||
|
||||
function GM:DoPlayerDeath(client, attacker, damageinfo)
|
||||
client:AddDeaths(1)
|
||||
|
||||
if (hook.Run("ShouldSpawnClientRagdoll", client) != false) then
|
||||
client:CreateRagdoll()
|
||||
end
|
||||
|
||||
if (IsValid(attacker) and attacker:IsPlayer()) then
|
||||
if (client == attacker) then
|
||||
attacker:AddFrags(-1)
|
||||
else
|
||||
attacker:AddFrags(1)
|
||||
end
|
||||
end
|
||||
|
||||
client:SetDSP(31)
|
||||
end
|
||||
|
||||
function GM:PlayerDeath(client, inflictor, attacker)
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
if (IsValid(client.ixRagdoll)) then
|
||||
client.ixRagdoll.ixIgnoreDelete = true
|
||||
client:SetLocalVar("blur", nil)
|
||||
|
||||
if (hook.Run("ShouldRemoveRagdollOnDeath", client) != false) then
|
||||
client.ixRagdoll:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
client:SetNetVar("deathStartTime", CurTime())
|
||||
client:SetNetVar("deathTime", CurTime() + ix.config.Get("spawnTime", 5))
|
||||
|
||||
character:SetData("health", nil)
|
||||
|
||||
local deathSound = hook.Run("GetPlayerDeathSound", client)
|
||||
|
||||
if (deathSound != false) then
|
||||
deathSound = deathSound or deathSounds[math.random(1, #deathSounds)]
|
||||
|
||||
if (client:IsFemale() and !deathSound:find("female")) then
|
||||
deathSound = deathSound:gsub("male", "female")
|
||||
end
|
||||
|
||||
client:EmitSound(deathSound)
|
||||
end
|
||||
|
||||
local weapon = attacker:IsPlayer() and attacker:GetActiveWeapon()
|
||||
|
||||
ix.log.Add(client, "playerDeath",
|
||||
attacker:GetName() ~= "" and attacker:GetName() or attacker:GetClass(), IsValid(weapon) and weapon:GetClass())
|
||||
end
|
||||
end
|
||||
|
||||
local painSounds = {
|
||||
Sound("vo/npc/male01/pain01.wav"),
|
||||
Sound("vo/npc/male01/pain02.wav"),
|
||||
Sound("vo/npc/male01/pain03.wav"),
|
||||
Sound("vo/npc/male01/pain04.wav"),
|
||||
Sound("vo/npc/male01/pain05.wav"),
|
||||
Sound("vo/npc/male01/pain06.wav")
|
||||
}
|
||||
|
||||
local drownSounds = {
|
||||
Sound("player/pl_drown1.wav"),
|
||||
Sound("player/pl_drown2.wav"),
|
||||
Sound("player/pl_drown3.wav"),
|
||||
}
|
||||
|
||||
function GM:GetPlayerPainSound(client)
|
||||
if (client:WaterLevel() >= 3) then
|
||||
return drownSounds[math.random(1, #drownSounds)]
|
||||
end
|
||||
end
|
||||
|
||||
function GM:PlayerHurt(client, attacker, health, damage)
|
||||
if ((client.ixNextPain or 0) < CurTime() and health > 0) then
|
||||
local painSound = hook.Run("GetPlayerPainSound", client) or painSounds[math.random(1, #painSounds)]
|
||||
|
||||
if (client:IsFemale() and !painSound:find("female")) then
|
||||
painSound = painSound:gsub("male", "female")
|
||||
end
|
||||
|
||||
client:EmitSound(painSound)
|
||||
client.ixNextPain = CurTime() + 0.33
|
||||
end
|
||||
|
||||
ix.log.Add(client, "playerHurt", damage, attacker:GetName() ~= "" and attacker:GetName() or attacker:GetClass())
|
||||
end
|
||||
|
||||
function GM:PlayerDeathThink(client)
|
||||
if (client:GetCharacter()) then
|
||||
local deathTime = client:GetNetVar("deathTime")
|
||||
|
||||
if (deathTime and deathTime <= CurTime()) then
|
||||
client:Spawn()
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function GM:PlayerDisconnected(client)
|
||||
client:SaveData()
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
local charEnts = character:GetVar("charEnts") or {}
|
||||
|
||||
for _, v in ipairs(charEnts) do
|
||||
if (v and IsValid(v)) then
|
||||
v:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("OnCharacterDisconnect", client, character)
|
||||
character:Save()
|
||||
ix.chat.Send(nil, "disconnect", client:SteamName())
|
||||
end
|
||||
|
||||
if (IsValid(client.ixRagdoll)) then
|
||||
client.ixRagdoll:Remove()
|
||||
end
|
||||
|
||||
client:ClearNetVars()
|
||||
|
||||
if (!client.ixVoiceHear) then
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (!v.ixVoiceHear) then
|
||||
continue
|
||||
end
|
||||
|
||||
v.ixVoiceHear[client] = nil
|
||||
end
|
||||
|
||||
timer.Remove(client:SteamID64() .. "ixCanHearPlayersVoice")
|
||||
end
|
||||
|
||||
function GM:InitPostEntity()
|
||||
local doors = ents.FindByClass("prop_door_rotating")
|
||||
|
||||
for _, v in ipairs(doors) do
|
||||
local parent = v:GetOwner()
|
||||
|
||||
if (IsValid(parent)) then
|
||||
v.ixPartner = parent
|
||||
parent.ixPartner = v
|
||||
else
|
||||
for _, v2 in ipairs(doors) do
|
||||
if (v2:GetOwner() == v) then
|
||||
v2.ixPartner = v
|
||||
v.ixPartner = v2
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
timer.Simple(2, function()
|
||||
ix.entityDataLoaded = true
|
||||
end)
|
||||
end
|
||||
|
||||
function GM:SaveData()
|
||||
ix.date.Save()
|
||||
end
|
||||
|
||||
function GM:ShutDown()
|
||||
ix.shuttingDown = true
|
||||
ix.config.Save()
|
||||
|
||||
hook.Run("SaveData")
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
v:SaveData()
|
||||
|
||||
if (v:GetCharacter()) then
|
||||
v:GetCharacter():Save()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GM:GetGameDescription()
|
||||
return "IX: "..(Schema and Schema.name or "Inconnu")
|
||||
end
|
||||
|
||||
function GM:OnPlayerUseBusiness(client, item)
|
||||
-- You can manipulate purchased items with this hook.
|
||||
-- does not requires any kind of return.
|
||||
-- ex) item:SetData("businessItem", true)
|
||||
-- then every purchased item will be marked as Business Item.
|
||||
end
|
||||
|
||||
function GM:PlayerDeathSound()
|
||||
return true
|
||||
end
|
||||
|
||||
function GM:InitializedSchema()
|
||||
game.ConsoleCommand("sbox_persist ix_"..Schema.folder.."\n")
|
||||
end
|
||||
|
||||
function GM:PlayerCanHearPlayersVoice(listener, speaker)
|
||||
if (!speaker:Alive()) then
|
||||
return false
|
||||
end
|
||||
|
||||
local bCanHear = listener.ixVoiceHear and listener.ixVoiceHear[speaker]
|
||||
return bCanHear, true
|
||||
end
|
||||
|
||||
function GM:PlayerCanPickupWeapon(client, weapon)
|
||||
local data = {}
|
||||
data.start = client:GetShootPos()
|
||||
data.endpos = data.start + client:GetAimVector() * 96
|
||||
data.filter = client
|
||||
local trace = util.TraceLine(data)
|
||||
|
||||
if (trace.Entity == weapon and client:KeyDown(IN_USE)) then
|
||||
return true
|
||||
end
|
||||
|
||||
return client.ixWeaponGive
|
||||
end
|
||||
|
||||
function GM:OnPhysgunFreeze(weapon, physObj, entity, client)
|
||||
-- Object is already frozen (!?)
|
||||
if (entity.scaledSize) then return true end
|
||||
if (!physObj:IsMoveable()) then return false end
|
||||
if (entity:GetUnFreezable()) then return false end
|
||||
|
||||
physObj:EnableMotion(false)
|
||||
|
||||
-- With the jeep we need to pause all of its physics objects
|
||||
-- to stop it spazzing out and killing the server.
|
||||
if (entity:GetClass() == "prop_vehicle_jeep") then
|
||||
local objects = entity:GetPhysicsObjectCount()
|
||||
|
||||
for i = 0, objects - 1 do
|
||||
entity:GetPhysicsObjectNum(i):EnableMotion(false)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add it to the player's frozen props
|
||||
client:AddFrozenPhysicsObject(entity, physObj)
|
||||
client:SendHint("PhysgunUnfreeze", 0.3)
|
||||
client:SuppressHint("PhysgunFreeze")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function GM:CanPlayerSuicide(client)
|
||||
return false
|
||||
end
|
||||
|
||||
function GM:AllowPlayerPickup(client, entity)
|
||||
return false
|
||||
end
|
||||
|
||||
function GM:PreCleanupMap()
|
||||
hook.Run("SaveData")
|
||||
hook.Run("PersistenceSave")
|
||||
end
|
||||
|
||||
function GM:PostCleanupMap()
|
||||
ix.plugin.RunLoadData()
|
||||
end
|
||||
|
||||
function GM:CharacterPreSave(character)
|
||||
local client = character:GetPlayer()
|
||||
|
||||
for _, v in pairs(character:GetInventory():GetItems()) do
|
||||
if (v.OnSave) then
|
||||
v:Call("OnSave", client)
|
||||
end
|
||||
end
|
||||
|
||||
character:SetData("health", client:Alive() and client:Health() or nil)
|
||||
end
|
||||
|
||||
timer.Create("ixLifeGuard", 1, 0, function()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetCharacter() and v:Alive() and hook.Run("ShouldPlayerDrowned", v) != false) then
|
||||
if (v:WaterLevel() >= 3) then
|
||||
if (!v.drowningTime) then
|
||||
v.drowningTime = CurTime() + 30
|
||||
v.nextDrowning = CurTime()
|
||||
v.drownDamage = v.drownDamage or 0
|
||||
end
|
||||
|
||||
if (v.drowningTime < CurTime()) then
|
||||
if (v.nextDrowning < CurTime()) then
|
||||
v:ScreenFade(1, Color(0, 0, 255, 100), 1, 0)
|
||||
v:TakeDamage(10)
|
||||
v.drownDamage = v.drownDamage + 10
|
||||
v.nextDrowning = CurTime() + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
if (v.drowningTime) then
|
||||
v.drowningTime = nil
|
||||
v.nextDrowning = nil
|
||||
v.nextRecover = CurTime() + 2
|
||||
end
|
||||
|
||||
if (v.nextRecover and v.nextRecover < CurTime() and v.drownDamage > 0) then
|
||||
v.drownDamage = v.drownDamage - 10
|
||||
v:SetHealth(math.Clamp(v:Health() + 10, 0, v:GetMaxHealth()))
|
||||
v.nextRecover = CurTime() + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixStringRequest", function(length, client)
|
||||
local time = net.ReadUInt(32)
|
||||
local text = net.ReadString()
|
||||
|
||||
if (client.ixStrReqs and client.ixStrReqs[time]) then
|
||||
client.ixStrReqs[time](text)
|
||||
client.ixStrReqs[time] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixConfirmationRequest", function(length, client)
|
||||
local time = net.ReadUInt(32)
|
||||
local confirmation = net.ReadBool()
|
||||
|
||||
if (client.ixConfReqs and client.ixConfReqs[time]) then
|
||||
client.ixConfReqs[time](confirmation)
|
||||
client.ixConfReqs[time] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
function GM:GetPreferredCarryAngles(entity)
|
||||
if (entity:GetClass() == "ix_item") then
|
||||
local itemTable = entity:GetItemTable()
|
||||
|
||||
if (itemTable) then
|
||||
local preferedAngle = itemTable.preferedAngle
|
||||
|
||||
if (preferedAngle) then -- I don't want to return something
|
||||
return preferedAngle
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GM:PluginShouldLoad(uniqueID)
|
||||
return !ix.plugin.unloaded[uniqueID]
|
||||
end
|
||||
|
||||
function GM:DatabaseConnected()
|
||||
-- Create the SQL tables if they do not exist.
|
||||
ix.db.LoadTables()
|
||||
ix.log.LoadTables()
|
||||
|
||||
MsgC(Color(0, 255, 0), "Database Type: " .. ix.db.config.adapter .. ".\n")
|
||||
|
||||
timer.Create("ixDatabaseThink", 0.5, 0, function()
|
||||
mysql:Think()
|
||||
end)
|
||||
|
||||
ix.plugin.RunLoadData()
|
||||
end
|
||||
131
gamemodes/helix/gamemode/core/libs/cl_bar.lua
Normal file
131
gamemodes/helix/gamemode/core/libs/cl_bar.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
ix.bar = ix.bar or {}
|
||||
ix.bar.list = {}
|
||||
ix.bar.delta = ix.bar.delta or {}
|
||||
ix.bar.actionText = ""
|
||||
ix.bar.actionStart = 0
|
||||
ix.bar.actionEnd = 0
|
||||
ix.bar.totalHeight = 0
|
||||
|
||||
-- luacheck: globals BAR_HEIGHT
|
||||
BAR_HEIGHT = 10
|
||||
|
||||
function ix.bar.Get(identifier)
|
||||
for _, v in ipairs(ix.bar.list) do
|
||||
if (v.identifier == identifier) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.bar.Remove(identifier)
|
||||
local bar = ix.bar.Get(identifier)
|
||||
|
||||
if (bar) then
|
||||
table.remove(ix.bar.list, bar.index)
|
||||
|
||||
if (IsValid(ix.gui.bars)) then
|
||||
ix.gui.bars:RemoveBar(bar.panel)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.bar.Add(getValue, color, priority, identifier)
|
||||
if (identifier) then
|
||||
ix.bar.Remove(identifier)
|
||||
end
|
||||
|
||||
local index = #ix.bar.list + 1
|
||||
|
||||
color = color or Color(math.random(150, 255), math.random(150, 255), math.random(150, 255))
|
||||
priority = priority or index
|
||||
|
||||
ix.bar.list[index] = {
|
||||
index = index,
|
||||
color = color,
|
||||
priority = priority,
|
||||
GetValue = getValue,
|
||||
identifier = identifier,
|
||||
panel = IsValid(ix.gui.bars) and ix.gui.bars:AddBar(index, color, priority)
|
||||
}
|
||||
|
||||
return priority
|
||||
end
|
||||
|
||||
local gradientD = ix.util.GetMaterial("vgui/gradient-d")
|
||||
|
||||
local TEXT_COLOR = Color(240, 240, 240)
|
||||
local SHADOW_COLOR = Color(20, 20, 20)
|
||||
|
||||
function ix.bar.DrawAction()
|
||||
local start, finish = ix.bar.actionStart, ix.bar.actionEnd
|
||||
local curTime = CurTime()
|
||||
local scrW, scrH = ScrW(), ScrH()
|
||||
|
||||
if (finish > curTime) then
|
||||
local fraction = 1 - math.TimeFraction(start, finish, curTime)
|
||||
local alpha = fraction * 255
|
||||
|
||||
if (alpha > 0) then
|
||||
local w, h = scrW * 0.35, 28
|
||||
local x, y = (scrW * 0.5) - (w * 0.5), (scrH * 0.725) - (h * 0.5)
|
||||
|
||||
ix.util.DrawBlurAt(x, y, w, h)
|
||||
|
||||
surface.SetDrawColor(35, 35, 35, 100)
|
||||
surface.DrawRect(x, y, w, h)
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, 120)
|
||||
surface.DrawOutlinedRect(x, y, w, h)
|
||||
|
||||
surface.SetDrawColor(ix.config.Get("color"))
|
||||
surface.DrawRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8)
|
||||
|
||||
surface.SetDrawColor(200, 200, 200, 20)
|
||||
surface.SetMaterial(gradientD)
|
||||
surface.DrawTexturedRect(x + 4, y + 4, math.max(w * fraction, 8) - 8, h - 8)
|
||||
|
||||
draw.SimpleText(ix.bar.actionText, "ixMediumFont", x + 2, y - 22, SHADOW_COLOR)
|
||||
draw.SimpleText(ix.bar.actionText, "ixMediumFont", x, y - 24, TEXT_COLOR)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
ix.bar.Add(function()
|
||||
return math.max(LocalPlayer():Health() / LocalPlayer():GetMaxHealth(), 0)
|
||||
end, Color(200, 50, 40), nil, "health")
|
||||
|
||||
ix.bar.Add(function()
|
||||
return math.min(LocalPlayer():Armor() / 100, 1)
|
||||
end, Color(30, 70, 180), nil, "armor")
|
||||
end
|
||||
|
||||
net.Receive("ixActionBar", function()
|
||||
local start, finish = net.ReadFloat(), net.ReadFloat()
|
||||
local text = net.ReadString()
|
||||
|
||||
if (text:sub(1, 1) == "@") then
|
||||
text = L2(text:sub(2)) or text
|
||||
end
|
||||
|
||||
ix.bar.actionStart = start
|
||||
ix.bar.actionEnd = finish
|
||||
ix.bar.actionText = text:utf8upper()
|
||||
end)
|
||||
|
||||
net.Receive("ixActionBarReset", function()
|
||||
ix.bar.actionStart = 0
|
||||
ix.bar.actionEnd = 0
|
||||
ix.bar.actionText = ""
|
||||
end)
|
||||
146
gamemodes/helix/gamemode/core/libs/cl_hud.lua
Normal file
146
gamemodes/helix/gamemode/core/libs/cl_hud.lua
Normal file
@@ -0,0 +1,146 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
ix.hud = {}
|
||||
|
||||
local owner, w, h, ceil, ft, clmp
|
||||
ceil = math.ceil
|
||||
clmp = math.Clamp
|
||||
local aprg, aprg2 = 0, 0
|
||||
|
||||
function ix.hud.DrawDeath()
|
||||
owner = LocalPlayer()
|
||||
ft = FrameTime()
|
||||
w, h = ScrW(), ScrH()
|
||||
|
||||
if (owner:GetCharacter()) then
|
||||
if (owner:Alive()) then
|
||||
if (aprg != 0) then
|
||||
aprg2 = clmp(aprg2 - ft*1.3, 0, 1)
|
||||
if (aprg2 == 0) then
|
||||
aprg = clmp(aprg - ft*.7, 0, 1)
|
||||
end
|
||||
end
|
||||
else
|
||||
if (aprg2 != 1) then
|
||||
aprg = clmp(aprg + ft*.5, 0, 1)
|
||||
if (aprg == 1) then
|
||||
aprg2 = clmp(aprg2 + ft*.4, 0, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.characterMenu) and ix.gui.characterMenu:IsVisible() or !owner:GetCharacter()) then
|
||||
return
|
||||
end
|
||||
|
||||
surface.SetDrawColor(0, 0, 0, ceil((aprg^.5) * 255))
|
||||
surface.DrawRect(-1, -1, w+2, h+2)
|
||||
|
||||
ix.util.DrawText(
|
||||
string.utf8upper(L"youreDead"), w/2, h/2, ColorAlpha(color_white, aprg2 * 255), 1, 1, "ixMenuButtonHugeFont", aprg2 * 255
|
||||
)
|
||||
end
|
||||
|
||||
function ix.hud.DrawItemPickup()
|
||||
local pickupTime = ix.config.Get("itemPickupTime", 0.5)
|
||||
|
||||
if (pickupTime == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
local client = LocalPlayer()
|
||||
local entity = client.ixInteractionTarget
|
||||
local startTime = client.ixInteractionStartTime
|
||||
|
||||
if (IsValid(entity) and startTime) then
|
||||
local sysTime = SysTime()
|
||||
local endTime = startTime + pickupTime
|
||||
|
||||
if (sysTime >= endTime or client:GetEyeTrace().Entity != entity) then
|
||||
client.ixInteractionTarget = nil
|
||||
client.ixInteractionStartTime = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local fraction = math.min((endTime - sysTime) / pickupTime, 1)
|
||||
local x, y = ScrW() / 2, ScrH() / 2
|
||||
local radius, thickness = 32, 6
|
||||
local startAngle = 90
|
||||
local endAngle = startAngle + (1 - fraction) * 360
|
||||
local color = ColorAlpha(color_white, fraction * 255)
|
||||
|
||||
ix.util.DrawArc(x, y, radius, thickness, startAngle, endAngle, 2, color)
|
||||
end
|
||||
end
|
||||
|
||||
function ix.hud.PopulateItemTooltip(tooltip, item)
|
||||
local name = tooltip:AddRow("name")
|
||||
name:SetImportant()
|
||||
name:SetText(item.GetName and item:GetName() or L(item.name))
|
||||
name:SetMaxWidth(math.max(name:GetMaxWidth(), ScrW() * 0.5))
|
||||
name:SizeToContents()
|
||||
|
||||
local description = tooltip:AddRow("description")
|
||||
description:SetText(item:GetDescription() or "")
|
||||
description:SizeToContents()
|
||||
|
||||
if (item.PopulateTooltip) then
|
||||
item:PopulateTooltip(tooltip)
|
||||
end
|
||||
|
||||
hook.Run("PopulateItemTooltip", tooltip, item)
|
||||
end
|
||||
|
||||
function ix.hud.PopulatePlayerTooltip(tooltip, client)
|
||||
local name = tooltip:AddRow("name")
|
||||
name:SetImportant()
|
||||
name:SetText(client:SteamName())
|
||||
name:SetBackgroundColor(team.GetColor(client:Team()))
|
||||
name:SizeToContents()
|
||||
|
||||
local nameHeight = name:GetTall()
|
||||
name:SetTextInset(nameHeight + 4, 0)
|
||||
name:SetWide(name:GetWide() + nameHeight + 4)
|
||||
|
||||
local avatar = name:Add("AvatarImage")
|
||||
avatar:Dock(LEFT)
|
||||
avatar:SetPlayer(client, nameHeight)
|
||||
avatar:SetSize(name:GetTall(), name:GetTall())
|
||||
|
||||
local currentPing = client:Ping()
|
||||
|
||||
local ping = tooltip:AddRow("ping")
|
||||
ping:SetText(L("ping", currentPing))
|
||||
ping.Paint = function(_, width, height)
|
||||
surface.SetDrawColor(ColorAlpha(derma.GetColor(
|
||||
currentPing < 110 and "Success" or (currentPing < 165 and "Warning" or "Error")
|
||||
, tooltip), 22))
|
||||
surface.DrawRect(0, 0, width, height)
|
||||
end
|
||||
ping:SizeToContents()
|
||||
|
||||
hook.Run("PopulatePlayerTooltip", client, tooltip)
|
||||
end
|
||||
|
||||
hook.Add("GetCrosshairAlpha", "ixCrosshair", function(alpha)
|
||||
return alpha * (1 - aprg)
|
||||
end)
|
||||
|
||||
function ix.hud.DrawAll(postHook)
|
||||
if (postHook) then
|
||||
ix.hud.DrawDeath()
|
||||
end
|
||||
|
||||
ix.hud.DrawItemPickup()
|
||||
end
|
||||
506
gamemodes/helix/gamemode/core/libs/cl_markup.lua
Normal file
506
gamemodes/helix/gamemode/core/libs/cl_markup.lua
Normal file
@@ -0,0 +1,506 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- luacheck: ignore
|
||||
ix.markup = ix.markup or {}
|
||||
|
||||
-- Temporary information used when building text frames.
|
||||
local colour_stack = { {r=255,g=255,b=255,a=255} }
|
||||
local font_stack = { "DermaDefault" }
|
||||
local curtag = nil
|
||||
local blocks = {}
|
||||
|
||||
local colourmap = {
|
||||
-- it's all black and white
|
||||
["black"] = { r=0, g=0, b=0, a=255 },
|
||||
["white"] = { r=255, g=255, b=255, a=255 },
|
||||
-- it's greys
|
||||
["dkgrey"] = { r=64, g=64, b=64, a=255 },
|
||||
["grey"] = { r=128, g=128, b=128, a=255 },
|
||||
["ltgrey"] = { r=192, g=192, b=192, a=255 },
|
||||
-- account for speeling mistakes
|
||||
["dkgray"] = { r=64, g=64, b=64, a=255 },
|
||||
["gray"] = { r=128, g=128, b=128, a=255 },
|
||||
["ltgray"] = { r=192, g=192, b=192, a=255 },
|
||||
-- normal colours
|
||||
["red"] = { r=255, g=0, b=0, a=255 },
|
||||
["green"] = { r=0, g=255, b=0, a=255 },
|
||||
["blue"] = { r=0, g=0, b=255, a=255 },
|
||||
["yellow"] = { r=255, g=255, b=0, a=255 },
|
||||
["purple"] = { r=255, g=0, b=255, a=255 },
|
||||
["cyan"] = { r=0, g=255, b=255, a=255 },
|
||||
["turq"] = { r=0, g=255, b=255, a=255 },
|
||||
-- dark variations
|
||||
["dkred"] = { r=128, g=0, b=0, a=255 },
|
||||
["dkgreen"] = { r=0, g=128, b=0, a=255 },
|
||||
["dkblue"] = { r=0, g=0, b=128, a=255 },
|
||||
["dkyellow"] = { r=128, g=128, b=0, a=255 },
|
||||
["dkpurple"] = { r=128, g=0, b=128, a=255 },
|
||||
["dkcyan"] = { r=0, g=128, b=128, a=255 },
|
||||
["dkturq"] = { r=0, g=128, b=128, a=255 },
|
||||
-- light variations
|
||||
["ltred"] = { r=255, g=128, b=128, a=255 },
|
||||
["ltgreen"] = { r=128, g=255, b=128, a=255 },
|
||||
["ltblue"] = { r=128, g=128, b=255, a=255 },
|
||||
["ltyellow"] = { r=255, g=255, b=128, a=255 },
|
||||
["ltpurple"] = { r=255, g=128, b=255, a=255 },
|
||||
["ltcyan"] = { r=128, g=255, b=255, a=255 },
|
||||
["ltturq"] = { r=128, g=255, b=255, a=255 },
|
||||
}
|
||||
|
||||
--[[
|
||||
Name: colourMatch(c)
|
||||
Desc: Match a colour name to an rgb value.
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function colourMatch(c)
|
||||
c = string.lower(c)
|
||||
|
||||
return colourmap[c]
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: ExtractParams(p1,p2,p3)
|
||||
Desc: This function is used to extract the tag information.
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function ExtractParams(p1,p2,p3)
|
||||
|
||||
if (string.utf8sub(p1, 1, 1) == "/") then
|
||||
|
||||
local tag = string.utf8sub(p1, 2)
|
||||
|
||||
if (tag == "color" or tag == "colour") then
|
||||
table.remove(colour_stack)
|
||||
elseif (tag == "font" or tag == "face") then
|
||||
table.remove(font_stack)
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
if (p1 == "color" or p1 == "colour") then
|
||||
|
||||
local rgba = colourMatch(p2)
|
||||
|
||||
if (rgba == nil) then
|
||||
rgba = {}
|
||||
local x = { "r", "g", "b", "a" }
|
||||
n = 1
|
||||
for k, v in string.gmatch(p2, "(%d+),?") do
|
||||
rgba[ x[n] ] = k
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(colour_stack, rgba)
|
||||
|
||||
elseif (p1 == "font" or p1 == "face") then
|
||||
|
||||
table.insert(font_stack, tostring(p2))
|
||||
elseif (p1 == "img" and p2) then
|
||||
local exploded = string.Explode(",", p2)
|
||||
local material = exploded[1] or p2
|
||||
local p3 = exploded[2]
|
||||
|
||||
local found = file.Find("materials/"..material..".*", "GAME")
|
||||
|
||||
if (found[1] and found[1]:find("%.png")) then
|
||||
material = material..".png"
|
||||
end
|
||||
|
||||
local texture = Material(material)
|
||||
local sizeData = string.Explode("x", p3 or "16x16")
|
||||
w = tonumber(sizeData[1]) or 16
|
||||
h = tonumber(sizeData[2]) or 16
|
||||
|
||||
if (texture) then
|
||||
table.insert(blocks, {
|
||||
texture = texture,
|
||||
w = w,
|
||||
h = h
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: CheckTextOrTag(p)
|
||||
Desc: This function places data in the "blocks" table
|
||||
depending of if p is a tag, or some text
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function CheckTextOrTag(p)
|
||||
if (p == "") then return end
|
||||
if (p == nil) then return end
|
||||
|
||||
if (string.utf8sub(p, 1, 1) == "<") then
|
||||
string.gsub(p, "<([/%a]*)=?([^>]*)", ExtractParams)
|
||||
else
|
||||
|
||||
local text_block = {}
|
||||
text_block.text = p
|
||||
text_block.colour = colour_stack[#colour_stack]
|
||||
text_block.font = font_stack[#font_stack]
|
||||
table.insert(blocks, text_block)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: ProcessMatches(p1,p2,p3)
|
||||
Desc: CheckTextOrTag for 3 parameters. Called by string.gsub
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
local function ProcessMatches(p1,p2,p3)
|
||||
if (p1) then CheckTextOrTag(p1) end
|
||||
if (p2) then CheckTextOrTag(p2) end
|
||||
if (p3) then CheckTextOrTag(p3) end
|
||||
end
|
||||
|
||||
local MarkupObject = {}
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:Create()
|
||||
Desc: Called by Parse. Creates a new table, and setups the
|
||||
metatable.
|
||||
Usage: ** INTERNAL ** Do not use!
|
||||
]]
|
||||
function MarkupObject:create()
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
|
||||
return o
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:GetWidth()
|
||||
Desc: Returns the width of a markup block
|
||||
Usage: ml:GetWidth()
|
||||
]]
|
||||
function MarkupObject:GetWidth()
|
||||
return self.totalWidth
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:GetHeight()
|
||||
Desc: Returns the height of a markup block
|
||||
Usage: ml:GetHeight()
|
||||
]]
|
||||
function MarkupObject:GetHeight()
|
||||
return self.totalHeight
|
||||
end
|
||||
|
||||
function MarkupObject:size()
|
||||
return self.totalWidth, self.totalHeight
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: MarkupObject:Draw(xOffset, yOffset, halign, valign, alphaoverride)
|
||||
Desc: Draw the markup text to the screen as position
|
||||
xOffset, yOffset. Halign and Valign can be used
|
||||
to align the text. Alphaoverride can be used to override
|
||||
the alpha value of the text-colour.
|
||||
Usage: MarkupObject:Draw(100, 100)
|
||||
]]
|
||||
function MarkupObject:draw(xOffset, yOffset, halign, valign, alphaoverride)
|
||||
for i = 1, #self.blocks do
|
||||
local blk = self.blocks[i]
|
||||
|
||||
if (blk.texture) then
|
||||
local y = yOffset + blk.offset.y
|
||||
local x = xOffset + blk.offset.x
|
||||
|
||||
if (halign == TEXT_ALIGN_CENTER) then
|
||||
x = x - (self.totalWidth * 0.5)
|
||||
elseif (halign == TEXT_ALIGN_RIGHT) then
|
||||
x = x - (self.totalWidth)
|
||||
end
|
||||
|
||||
surface.SetDrawColor(blk.colour.r, blk.colour.g, blk.colour.b, alphaoverride or blk.colour.a or 255)
|
||||
surface.SetMaterial(blk.texture)
|
||||
surface.DrawTexturedRect(x, y, blk.w, blk.h)
|
||||
else
|
||||
local y = yOffset + (blk.height - blk.thisY) + blk.offset.y
|
||||
local x = xOffset
|
||||
|
||||
if (halign == TEXT_ALIGN_CENTER) then x = x - (self.totalWidth / 2)
|
||||
elseif (halign == TEXT_ALIGN_RIGHT) then x = x - (self.totalWidth)
|
||||
end
|
||||
|
||||
x = x + blk.offset.x
|
||||
|
||||
if (self.onDrawText) then
|
||||
self.onDrawText(blk.text, blk.font, x, y, blk.colour, halign, valign, alphaoverride, blk)
|
||||
else
|
||||
if (valign == TEXT_ALIGN_CENTER) then y = y - (self.totalHeight / 2)
|
||||
elseif (valign == TEXT_ALIGN_BOTTOM) then y = y - (self.totalHeight)
|
||||
end
|
||||
|
||||
local alpha = blk.colour.a
|
||||
if (alphaoverride) then alpha = alphaoverride end
|
||||
|
||||
surface.SetFont( blk.font )
|
||||
surface.SetTextColor( blk.colour.r, blk.colour.g, blk.colour.b, alpha )
|
||||
surface.SetTextPos( x, y )
|
||||
surface.DrawText( blk.text )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
Name: Parse(ml, maxwidth)
|
||||
Desc: Parses the pseudo-html markup language, and creates a
|
||||
MarkupObject, which can be used to the draw the
|
||||
text to the screen. Valid tags are: font and colour.
|
||||
\n and \t are also available to move to the next line,
|
||||
or insert a tab character.
|
||||
Maxwidth can be used to make the text wrap to a specific
|
||||
width.
|
||||
Usage: markup.Parse("<font=Default>changed font</font>\n<colour=255,0,255,255>changed colour</colour>")
|
||||
]]
|
||||
function ix.markup.Parse(ml, maxwidth)
|
||||
|
||||
ml = utf8.force(ml)
|
||||
|
||||
colour_stack = { {r=255,g=255,b=255,a=255} }
|
||||
font_stack = { "DermaDefault" }
|
||||
blocks = {}
|
||||
|
||||
if (not string.find(ml, "<")) then
|
||||
ml = ml .. "<nop>"
|
||||
end
|
||||
|
||||
string.gsub(ml, "([^<>]*)(<[^>]+.)([^<>]*)", ProcessMatches)
|
||||
|
||||
local xOffset = 0
|
||||
local yOffset = 0
|
||||
local xSize = 0
|
||||
local xMax = 0
|
||||
local thisMaxY = 0
|
||||
local new_block_list = {}
|
||||
local ymaxes = {}
|
||||
local texOffset = 0
|
||||
|
||||
local lineHeight = 0
|
||||
for i = 1, #blocks do
|
||||
local block = blocks[i]
|
||||
|
||||
if (block.text) then
|
||||
surface.SetFont(block.font)
|
||||
|
||||
local thisY = 0
|
||||
local curString = ""
|
||||
block.text = string.gsub(block.text, ">", ">")
|
||||
block.text = string.gsub(block.text, "<", "<")
|
||||
block.text = string.gsub(block.text, "&", "&")
|
||||
|
||||
for j=1,string.utf8len(block.text) do
|
||||
local ch = string.utf8sub(block.text,j,j)
|
||||
|
||||
if (ch == "\n") then
|
||||
if (thisY == 0) then
|
||||
thisY = lineHeight + texOffset;
|
||||
thisMaxY = lineHeight + texOffset;
|
||||
else
|
||||
lineHeight = thisY + texOffset
|
||||
end
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
end
|
||||
|
||||
xOffset = 0
|
||||
xSize = 0
|
||||
yOffset = yOffset + thisMaxY;
|
||||
thisY = 0
|
||||
curString = ""
|
||||
thisMaxY = 0
|
||||
elseif (ch == "\t") then
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
end
|
||||
|
||||
local xOldSize = xSize
|
||||
xSize = 0
|
||||
curString = ""
|
||||
local xOldOffset = xOffset
|
||||
xOffset = math.ceil( (xOffset + xOldSize) / 50 ) * 50
|
||||
|
||||
if (xOffset == xOldOffset) then
|
||||
xOffset = xOffset + 50
|
||||
end
|
||||
else
|
||||
local x,y = surface.GetTextSize(ch)
|
||||
|
||||
if (x == nil) then return end
|
||||
|
||||
if (maxwidth and maxwidth > x) then
|
||||
if (xOffset + xSize + x >= maxwidth) then
|
||||
|
||||
-- need to: find the previous space in the curString
|
||||
-- if we can't find one, take off the last character
|
||||
-- and add a -. add the character to ch
|
||||
-- and insert as a new block, incrementing the y etc
|
||||
|
||||
local lastSpacePos = string.utf8len(curString)
|
||||
for k=1,string.utf8len(curString) do
|
||||
local chspace = string.utf8sub(curString,k,k)
|
||||
if (chspace == " ") then
|
||||
lastSpacePos = k
|
||||
end
|
||||
end
|
||||
|
||||
if (lastSpacePos == string.utf8len(curString)) then
|
||||
ch = string.utf8sub(curString,lastSpacePos,lastSpacePos) .. ch
|
||||
j = lastSpacePos
|
||||
curString = string.utf8sub(curString, 1, lastSpacePos-1)
|
||||
else
|
||||
ch = string.utf8sub(curString,lastSpacePos+1) .. ch
|
||||
j = lastSpacePos+1
|
||||
curString = string.utf8sub(curString, 1, lastSpacePos)
|
||||
end
|
||||
|
||||
local m = 1
|
||||
while string.utf8sub(ch, m, m) == " " and m <= string.utf8len(ch) do
|
||||
m = m + 1
|
||||
end
|
||||
ch = string.utf8sub(ch, m)
|
||||
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
if (y1 > thisMaxY) then thisMaxY = y1; ymaxes[yOffset] = thisMaxY; lineHeight = y1; end
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
|
||||
xOffset = 0
|
||||
xSize = 0
|
||||
x,y = surface.GetTextSize(ch)
|
||||
yOffset = yOffset + thisMaxY;
|
||||
thisY = 0
|
||||
curString = ""
|
||||
thisMaxY = 0
|
||||
end
|
||||
end
|
||||
|
||||
curString = curString .. ch
|
||||
|
||||
thisY = y
|
||||
xSize = xSize + x
|
||||
|
||||
if (y > thisMaxY) then thisMaxY = y; ymaxes[yOffset] = thisMaxY; lineHeight = y; end
|
||||
end
|
||||
end
|
||||
|
||||
if (string.utf8len(curString) > 0) then
|
||||
|
||||
local x1,y1 = surface.GetTextSize(curString)
|
||||
|
||||
local new_block = {}
|
||||
new_block.text = curString
|
||||
new_block.font = block.font
|
||||
new_block.colour = block.colour
|
||||
new_block.thisY = thisY
|
||||
new_block.thisX = x1
|
||||
new_block.offset = {}
|
||||
new_block.offset.x = xOffset
|
||||
new_block.offset.y = yOffset
|
||||
table.insert(new_block_list, new_block)
|
||||
|
||||
lineHeight = thisY
|
||||
|
||||
if (xOffset + x1 > xMax) then
|
||||
xMax = xOffset + x1
|
||||
end
|
||||
xOffset = xOffset + x1
|
||||
end
|
||||
xSize = 0
|
||||
elseif (block.texture) then
|
||||
local newBlock = table.Copy(block)
|
||||
newBlock.colour = block.colour or {r = 255, g = 255, b = 255, a = 255}
|
||||
newBlock.thisX = block.w
|
||||
newBlock.thisY = block.h
|
||||
newBlock.offset = {
|
||||
x = xOffset,
|
||||
y = 0
|
||||
}
|
||||
|
||||
table.insert(new_block_list, newBlock)
|
||||
xOffset = xOffset + block.w + 1
|
||||
texOffset = block.h / 2
|
||||
end
|
||||
end
|
||||
|
||||
local totalHeight = 0
|
||||
for i = 1, #new_block_list do
|
||||
local block = new_block_list[i]
|
||||
block.height = ymaxes[block.offset.y]
|
||||
|
||||
if (block.texture) then
|
||||
block.offset.y = ymaxes[0] * 0.5 - block.h * 0.5
|
||||
end
|
||||
|
||||
if (block.height and block.offset.y + block.height > totalHeight) then
|
||||
totalHeight = block.offset.y + block.height
|
||||
end
|
||||
end
|
||||
|
||||
local newObject = MarkupObject:create()
|
||||
newObject.totalHeight = totalHeight
|
||||
newObject.totalWidth = xMax
|
||||
newObject.blocks = new_block_list
|
||||
return newObject
|
||||
end
|
||||
68
gamemodes/helix/gamemode/core/libs/cl_networking.lua
Normal file
68
gamemodes/helix/gamemode/core/libs/cl_networking.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local entityMeta = FindMetaTable("Entity")
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
ix.net = ix.net or {}
|
||||
ix.net.globals = ix.net.globals or {}
|
||||
|
||||
net.Receive("ixGlobalVarSet", function()
|
||||
ix.net.globals[net.ReadString()] = net.ReadType()
|
||||
end)
|
||||
|
||||
net.Receive("ixNetVarSet", function()
|
||||
local index = net.ReadUInt(16)
|
||||
|
||||
ix.net[index] = ix.net[index] or {}
|
||||
ix.net[index][net.ReadString()] = net.ReadType()
|
||||
end)
|
||||
|
||||
net.Receive("ixNetStatics", function()
|
||||
for _ = 1, net.ReadUInt(16) do
|
||||
local id = net.ReadUInt(16)
|
||||
if (id == 0) then continue end
|
||||
ix.net[id] = ix.net[id] or {}
|
||||
ix.net[id].Persistent = true
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixNetVarDelete", function()
|
||||
ix.net[net.ReadUInt(16)] = nil
|
||||
end)
|
||||
|
||||
net.Receive("ixLocalVarSet", function()
|
||||
local key = net.ReadString()
|
||||
local var = net.ReadType()
|
||||
|
||||
ix.net[LocalPlayer():EntIndex()] = ix.net[LocalPlayer():EntIndex()] or {}
|
||||
ix.net[LocalPlayer():EntIndex()][key] = var
|
||||
|
||||
hook.Run("OnLocalVarSet", key, var)
|
||||
end)
|
||||
|
||||
function GetNetVar(key, default) -- luacheck: globals GetNetVar
|
||||
local value = ix.net.globals[key]
|
||||
|
||||
return value != nil and value or default
|
||||
end
|
||||
|
||||
function entityMeta:GetNetVar(key, default)
|
||||
local index = self:EntIndex()
|
||||
|
||||
if (ix.net[index] and ix.net[index][key] != nil) then
|
||||
return ix.net[index][key]
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
playerMeta.GetLocalVar = entityMeta.GetNetVar
|
||||
160
gamemodes/helix/gamemode/core/libs/sh_animation.lua
Normal file
160
gamemodes/helix/gamemode/core/libs/sh_animation.lua
Normal file
@@ -0,0 +1,160 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
function ix.util.InstallAnimationMethods(meta)
|
||||
local function TweenAnimationThink(object)
|
||||
for k, v in pairs(object.tweenAnimations) do
|
||||
if (!v.bShouldPlay) then
|
||||
continue
|
||||
end
|
||||
|
||||
local bComplete = v:update(FrameTime())
|
||||
|
||||
if (v.Think) then
|
||||
v:Think(object)
|
||||
end
|
||||
|
||||
if (bComplete) then
|
||||
v.bShouldPlay = nil
|
||||
|
||||
v:ForceComplete()
|
||||
|
||||
if (v.OnComplete) then
|
||||
v:OnComplete(object)
|
||||
end
|
||||
|
||||
if (v.bRemoveOnComplete) then
|
||||
object.tweenAnimations[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function meta:GetTweenAnimation(index, bNoPlay)
|
||||
-- if we don't need to check if the animation is playing we can just return the animation
|
||||
if (bNoPlay) then
|
||||
return self.tweenAnimations[index]
|
||||
else
|
||||
for k, v in pairs(self.tweenAnimations or {}) do
|
||||
if (k == index and v.bShouldPlay) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function meta:IsPlayingTweenAnimation(index)
|
||||
for k, v in pairs(self.tweenAnimations or {}) do
|
||||
if (v.bShouldPlay and index == k) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function meta:StopAnimations(bRemove)
|
||||
for k, v in pairs(self.tweenAnimations or {}) do
|
||||
if (v.bShouldPlay) then
|
||||
v:ForceComplete()
|
||||
|
||||
if (bRemove) then
|
||||
self.tweenAnimations[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function meta:CreateAnimation(length, data)
|
||||
local animations = self.tweenAnimations or {}
|
||||
self.tweenAnimations = animations
|
||||
|
||||
if (self.SetAnimationEnabled) then
|
||||
self:SetAnimationEnabled(true)
|
||||
end
|
||||
|
||||
local index = data.index or 1
|
||||
local bCancelPrevious = data.bCancelPrevious == nil and false or data.bCancelPrevious
|
||||
local bIgnoreConfig = SERVER or (data.bIgnoreConfig == nil and false or data.bIgnoreConfig)
|
||||
|
||||
if (bCancelPrevious and self:IsPlayingTweenAnimation()) then
|
||||
for _, v in pairs(animations) do
|
||||
v:set(v.duration)
|
||||
end
|
||||
end
|
||||
|
||||
local animation = ix.tween.new(
|
||||
((length == 0 and 1 or length) or 1) * (bIgnoreConfig and 1 or ix.option.Get("animationScale", 1)),
|
||||
data.subject or self,
|
||||
data.target or {},
|
||||
data.easing or "linear"
|
||||
)
|
||||
|
||||
animation.index = index
|
||||
animation.bIgnoreConfig = bIgnoreConfig
|
||||
animation.bAutoFire = (data.bAutoFire == nil and true or data.bAutoFire)
|
||||
animation.bRemoveOnComplete = (data.bRemoveOnComplete == nil and true or data.bRemoveOnComplete)
|
||||
animation.Think = data.Think
|
||||
animation.OnComplete = data.OnComplete
|
||||
|
||||
animation.ForceComplete = function(anim)
|
||||
anim:set(anim.duration)
|
||||
end
|
||||
|
||||
-- @todo don't use ridiculous method chaining
|
||||
animation.CreateAnimation = function(currentAnimation, newLength, newData)
|
||||
newData.bAutoFire = false
|
||||
newData.index = currentAnimation.index + 1
|
||||
|
||||
local oldOnComplete = currentAnimation.OnComplete
|
||||
local newAnimation = currentAnimation.subject:CreateAnimation(newLength, newData)
|
||||
|
||||
currentAnimation.OnComplete = function(...)
|
||||
if (oldOnComplete) then
|
||||
oldOnComplete(...)
|
||||
end
|
||||
|
||||
newAnimation:Fire()
|
||||
end
|
||||
|
||||
return newAnimation
|
||||
end
|
||||
|
||||
if (length == 0 or (!animation.bIgnoreConfig and ix.option.Get("disableAnimations", false))) then
|
||||
animation.Fire = function(anim)
|
||||
anim:set(anim.duration)
|
||||
anim.bShouldPlay = true
|
||||
end
|
||||
else
|
||||
animation.Fire = function(anim)
|
||||
anim:set(0)
|
||||
anim.bShouldPlay = true
|
||||
end
|
||||
end
|
||||
|
||||
-- we can assume if we're using this library, we're not going to use the built-in
|
||||
-- AnimationTo functions, so override AnimationThink with our own
|
||||
self.AnimationThink = TweenAnimationThink
|
||||
|
||||
-- fire right away if autofire is enabled
|
||||
if (animation.bAutoFire) then
|
||||
animation:Fire()
|
||||
end
|
||||
|
||||
self.tweenAnimations[index] = animation
|
||||
return animation
|
||||
end
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
local panelMeta = FindMetaTable("Panel")
|
||||
ix.util.InstallAnimationMethods(panelMeta)
|
||||
end
|
||||
571
gamemodes/helix/gamemode/core/libs/sh_anims.lua
Normal file
571
gamemodes/helix/gamemode/core/libs/sh_anims.lua
Normal file
@@ -0,0 +1,571 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Player model animation.
|
||||
|
||||
Helix comes with support for using NPC animations/models as regular player models by manually translating animations. There are
|
||||
a few standard animation sets that are built-in that should cover most non-player models:
|
||||
citizen_male
|
||||
citizen_female
|
||||
metrocop
|
||||
overwatch
|
||||
vortigaunt
|
||||
player
|
||||
zombie
|
||||
fastZombie
|
||||
|
||||
If you find that your models are T-posing when they work elsewhere, you'll probably need to set the model class for your
|
||||
model with `ix.anim.SetModelClass` in order for the correct animations to be used. If you'd like to add your own animation
|
||||
class, simply add to the `ix.anim` table with a model class name and the required animation translation table.
|
||||
]]
|
||||
-- @module ix.anim
|
||||
|
||||
ix.anim = ix.anim or {}
|
||||
ix.anim.citizen_male = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
|
||||
reload = ACT_GESTURE_RELOAD_SMG1
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_RANGE_ATTACK_THROW
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = {
|
||||
["prop_vehicle_prisoner_pod"] = {"podpose", Vector(-3, 0, 0)},
|
||||
["prop_vehicle_jeep"] = {ACT_BUSY_SIT_CHAIR, Vector(14, 0, -14)},
|
||||
["prop_vehicle_airboat"] = {ACT_BUSY_SIT_CHAIR, Vector(8, 0, -20)},
|
||||
chair = {ACT_BUSY_SIT_CHAIR, Vector(1, 0, -23)}
|
||||
},
|
||||
}
|
||||
|
||||
ix.anim.citizen_female = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
|
||||
reload = ACT_GESTURE_RELOAD_SMG1
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_RANGE_ATTACK_THROW
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = ix.anim.citizen_male.vehicle
|
||||
}
|
||||
ix.anim.metrocop = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_PISTOL, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN_PISTOL, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_GESTURE_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_SMG1_LOW, ACT_COVER_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_COMBINE_THROW_GRENADE
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_MELEE},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_PISTOL_LOW, ACT_COVER_PISTOL_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_ANGRY},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING_GESTURE
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = {
|
||||
chair = {ACT_COVER_PISTOL_LOW, Vector(5, 0, -5)},
|
||||
["prop_vehicle_airboat"] = {ACT_COVER_PISTOL_LOW, Vector(10, 0, 0)},
|
||||
["prop_vehicle_jeep"] = {ACT_COVER_PISTOL_LOW, Vector(18, -2, 4)},
|
||||
["prop_vehicle_prisoner_pod"] = {ACT_IDLE, Vector(-4, -0.5, 0)}
|
||||
}
|
||||
}
|
||||
|
||||
ix.anim.metrocop_female = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_PISTOL, ACT_IDLE_ANGRY_PISTOL},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
reload = ACT_RELOAD_PISTOL
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SMG1,
|
||||
reload = ACT_GESTURE_RELOAD_SMG1
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SHOTGUN_RELAXED, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE_RELAXED, ACT_WALK_AIM_RIFLE_STIMULATED},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE_RELAXED, ACT_RUN_AIM_RIFLE_STIMULATED},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_GESTURE_RANGE_ATTACK_SHOTGUN
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_RANGE_AIM_SMG1_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_PISTOL},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_AIM_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM_PISTOL},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_RANGE_ATTACK_THROW
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_MANNEDGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_COVER_LOW, ACT_COVER_LOW},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING
|
||||
},
|
||||
glide = ACT_GLIDE,
|
||||
vehicle = ix.anim.citizen_male.vehicle
|
||||
}
|
||||
|
||||
ix.anim.overwatch = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
pistol = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
smg = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SMG1},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
shotgun = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE_SMG1, ACT_IDLE_ANGRY_SHOTGUN},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK_RIFLE, ACT_WALK_AIM_SHOTGUN},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_RIFLE, ACT_RUN_AIM_SHOTGUN},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
grenade = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
melee = {
|
||||
[ACT_MP_STAND_IDLE] = {"idle_unarmed", ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_CROUCHIDLE},
|
||||
[ACT_MP_WALK] = {"walkunarmed_all", ACT_WALK_RIFLE},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH_RIFLE, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_RUN] = {ACT_RUN_AIM_RIFLE, ACT_RUN_AIM_RIFLE},
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET},
|
||||
attack = ACT_MELEE_ATTACK_SWING_GESTURE
|
||||
},
|
||||
glide = ACT_GLIDE
|
||||
}
|
||||
ix.anim.vortigaunt = {
|
||||
melee = {
|
||||
["attack"] = ACT_MELEE_ATTACK1,
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
},
|
||||
grenade = {
|
||||
["attack"] = ACT_MELEE_ATTACK1,
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "ActionIdle"},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM}
|
||||
},
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
["attack"] = ACT_MELEE_ATTACK1
|
||||
},
|
||||
pistol = { -- beam
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
["attack"] = ACT_GESTURE_RANGE_ATTACK_PISTOL,
|
||||
["reload"] = ACT_IDLE,
|
||||
["glide"] = {ACT_RUN, ACT_RUN}
|
||||
},
|
||||
shotgun = { -- broom
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, "sweep_idle"},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {"Walk_all_HoldBroom", "Walk_all_HoldBroom"},
|
||||
["attack"] = ACT_IDLE,
|
||||
["reload"] = ACT_IDLE,
|
||||
["glide"] = {ACT_RUN, ACT_RUN}
|
||||
},
|
||||
smg = { -- heal
|
||||
[ACT_MP_STAND_IDLE] = {ACT_IDLE, ACT_IDLE_ANGRY},
|
||||
[ACT_MP_CROUCH_IDLE] = {ACT_CROUCHIDLE, ACT_RANGE_ATTACK_PISTOL_LOW},
|
||||
[ACT_MP_RUN] = {ACT_RUN, ACT_RUN_AIM},
|
||||
[ACT_MP_CROUCHWALK] = {ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE},
|
||||
[ACT_MP_WALK] = {ACT_WALK, ACT_WALK_AIM},
|
||||
["attack"] = ACT_IDLE,
|
||||
["reload"] = ACT_IDLE,
|
||||
["glide"] = {ACT_RUN, ACT_RUN}
|
||||
},
|
||||
glide = "jump_holding_glide"
|
||||
}
|
||||
ix.anim.player = {
|
||||
normal = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE,
|
||||
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
},
|
||||
passive = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_PASSIVE,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK_PASSIVE,
|
||||
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_PASSIVE,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN_PASSIVE,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
}
|
||||
}
|
||||
ix.anim.zombie = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_IDLE_ZOMBIE,
|
||||
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE,
|
||||
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_01,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_02,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
}
|
||||
ix.anim.fastZombie = {
|
||||
[ACT_MP_STAND_IDLE] = ACT_HL2MP_WALK_ZOMBIE,
|
||||
[ACT_MP_CROUCH_IDLE] = ACT_HL2MP_IDLE_CROUCH_ZOMBIE,
|
||||
[ACT_MP_CROUCHWALK] = ACT_HL2MP_WALK_CROUCH_ZOMBIE_05,
|
||||
[ACT_MP_WALK] = ACT_HL2MP_WALK_ZOMBIE_06,
|
||||
[ACT_MP_RUN] = ACT_HL2MP_RUN_ZOMBIE_FAST,
|
||||
[ACT_LAND] = {ACT_RESET, ACT_RESET}
|
||||
}
|
||||
|
||||
local translations = {}
|
||||
|
||||
--- Sets a model's animation class.
|
||||
-- @realm shared
|
||||
-- @string model Model name to set the animation class for
|
||||
-- @string class Animation class to assign to the model
|
||||
-- @usage ix.anim.SetModelClass("models/police.mdl", "metrocop")
|
||||
function ix.anim.SetModelClass(model, class)
|
||||
if (!ix.anim[class]) then
|
||||
error("'" .. tostring(class) .. "' n'est pas une classe d'animation valide !")
|
||||
end
|
||||
|
||||
translations[model:lower()] = class
|
||||
end
|
||||
|
||||
--- Gets a model's animation class.
|
||||
-- @realm shared
|
||||
-- @string model Model to get the animation class for
|
||||
-- @treturn[1] string Animation class of the model
|
||||
-- @treturn[2] nil If there was no animation associated with the given model
|
||||
-- @usage ix.anim.GetModelClass("models/police.mdl")
|
||||
-- > metrocop
|
||||
function ix.anim.GetModelClass(model)
|
||||
model = string.lower(model)
|
||||
local class = translations[model]
|
||||
|
||||
if (!class and string.find(model, "/player")) then
|
||||
return "player"
|
||||
end
|
||||
|
||||
class = class or "citizen_male"
|
||||
|
||||
if (class == "citizen_male" and (
|
||||
string.find(model, "female") or
|
||||
string.find(model, "alyx") or
|
||||
string.find(model, "mossman"))) then
|
||||
class = "citizen_female"
|
||||
end
|
||||
|
||||
return class
|
||||
end
|
||||
|
||||
ix.anim.SetModelClass("models/police.mdl", "metrocop")
|
||||
ix.anim.SetModelClass("models/combine_super_soldier.mdl", "overwatch")
|
||||
ix.anim.SetModelClass("models/combine_soldier_prisonGuard.mdl", "overwatch")
|
||||
ix.anim.SetModelClass("models/combine_soldier.mdl", "overwatch")
|
||||
ix.anim.SetModelClass("models/vortigaunt.mdl", "vortigaunt")
|
||||
ix.anim.SetModelClass("models/vortigaunt_blue.mdl", "vortigaunt")
|
||||
ix.anim.SetModelClass("models/vortigaunt_doctor.mdl", "vortigaunt")
|
||||
ix.anim.SetModelClass("models/vortigaunt_slave.mdl", "vortigaunt")
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixSequenceSet")
|
||||
util.AddNetworkString("ixSequenceReset")
|
||||
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
--- Player anim methods
|
||||
-- @classmod Player
|
||||
|
||||
--- Forces this player's model to play an animation sequence. It also prevents the player from firing their weapon while the
|
||||
-- animation is playing.
|
||||
-- @realm server
|
||||
-- @string sequence Name of the animation sequence to play
|
||||
-- @func[opt=nil] callback Function to call when the animation finishes. This is also called immediately if the animation
|
||||
-- fails to play
|
||||
-- @number[opt=nil] time How long to play the animation for. This defaults to the duration of the animation
|
||||
-- @bool[opt=false] bNoFreeze Whether or not to avoid freezing this player in place while the animation is playing
|
||||
-- @see LeaveSequence
|
||||
function playerMeta:ForceSequence(sequence, callback, time, bNoFreeze)
|
||||
hook.Run("PlayerEnterSequence", self, sequence, callback, time, bNoFreeze)
|
||||
|
||||
if (!sequence) then
|
||||
net.Start("ixSequenceReset")
|
||||
net.WriteEntity(self)
|
||||
net.Broadcast()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
sequence = self:LookupSequence(tostring(sequence))
|
||||
|
||||
if (sequence and sequence > 0) then
|
||||
time = time or self:SequenceDuration(sequence)
|
||||
|
||||
self.ixCouldShoot = self:GetNetVar("canShoot", false)
|
||||
self.ixSeqCallback = callback
|
||||
self:SetCycle(0)
|
||||
self:SetPlaybackRate(1)
|
||||
self:SetNetVar("forcedSequence", sequence)
|
||||
self:SetNetVar("canShoot", false)
|
||||
|
||||
if (!bNoFreeze) then
|
||||
self:SetMoveType(MOVETYPE_NONE)
|
||||
end
|
||||
|
||||
if (time > 0) then
|
||||
timer.Create("ixSeq"..self:EntIndex(), time, 1, function()
|
||||
if (IsValid(self)) then
|
||||
self:LeaveSequence()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
net.Start("ixSequenceSet")
|
||||
net.WriteEntity(self)
|
||||
net.Broadcast()
|
||||
|
||||
return time
|
||||
elseif (callback) then
|
||||
callback()
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Forcefully stops this player's model from playing an animation that was started by `ForceSequence`.
|
||||
-- @realm server
|
||||
function playerMeta:LeaveSequence()
|
||||
hook.Run("PlayerLeaveSequence", self)
|
||||
|
||||
net.Start("ixSequenceReset")
|
||||
net.WriteEntity(self)
|
||||
net.Broadcast()
|
||||
|
||||
self:SetNetVar("canShoot", self.ixCouldShoot)
|
||||
self:SetNetVar("forcedSequence", nil)
|
||||
self:SetMoveType(MOVETYPE_WALK)
|
||||
self.ixCouldShoot = nil
|
||||
|
||||
if (self.ixSeqCallback) then
|
||||
self:ixSeqCallback()
|
||||
end
|
||||
end
|
||||
else
|
||||
net.Receive("ixSequenceSet", function()
|
||||
local entity = net.ReadEntity()
|
||||
|
||||
if (IsValid(entity)) then
|
||||
hook.Run("PlayerEnterSequence", entity)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixSequenceReset", function()
|
||||
local entity = net.ReadEntity()
|
||||
|
||||
if (IsValid(entity)) then
|
||||
hook.Run("PlayerLeaveSequence", entity)
|
||||
end
|
||||
end)
|
||||
end
|
||||
201
gamemodes/helix/gamemode/core/libs/sh_attribs.lua
Normal file
201
gamemodes/helix/gamemode/core/libs/sh_attribs.lua
Normal file
@@ -0,0 +1,201 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- @module ix.attributes
|
||||
|
||||
if (!ix.char) then
|
||||
include("sh_character.lua")
|
||||
end
|
||||
|
||||
ix.attributes = ix.attributes or {}
|
||||
ix.attributes.list = ix.attributes.list or {}
|
||||
|
||||
function ix.attributes.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
|
||||
local niceName = v:sub(4, -5)
|
||||
|
||||
ATTRIBUTE = ix.attributes.list[niceName] or {}
|
||||
if (PLUGIN) then
|
||||
ATTRIBUTE.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
ix.util.Include(directory.."/"..v)
|
||||
|
||||
ATTRIBUTE.name = ATTRIBUTE.name or "Inconnu"
|
||||
ATTRIBUTE.description = ATTRIBUTE.description or "Aucune description n'est disponible."
|
||||
|
||||
ix.attributes.list[niceName] = ATTRIBUTE
|
||||
ATTRIBUTE = nil
|
||||
end
|
||||
end
|
||||
|
||||
function ix.attributes.Setup(client)
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
for k, v in pairs(ix.attributes.list) do
|
||||
if (v.OnSetup) then
|
||||
v:OnSetup(client, character:GetAttribute(k, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
--- Character attribute methods
|
||||
-- @classmod Character
|
||||
local charMeta = ix.meta.character
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixAttributeUpdate")
|
||||
|
||||
--- Increments one of this character's attributes by the given amount.
|
||||
-- @realm server
|
||||
-- @string key Name of the attribute to update
|
||||
-- @number value Amount to add to the attribute
|
||||
function charMeta:UpdateAttrib(key, value)
|
||||
local attribute = ix.attributes.list[key]
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (attribute) then
|
||||
local attrib = self:GetAttributes()
|
||||
|
||||
attrib[key] = math.min((attrib[key] or 0) + value, attribute.maxValue or ix.config.Get("maxAttributes", 100))
|
||||
|
||||
if (IsValid(client)) then
|
||||
net.Start("ixAttributeUpdate")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteFloat(attrib[key])
|
||||
net.Send(client)
|
||||
|
||||
if (attribute.Setup) then
|
||||
attribute.Setup(attrib[key])
|
||||
end
|
||||
end
|
||||
|
||||
self:SetAttributes(attrib)
|
||||
end
|
||||
|
||||
hook.Run("CharacterAttributeUpdated", client, self, key, value)
|
||||
end
|
||||
|
||||
--- Sets the value of an attribute for this character.
|
||||
-- @realm server
|
||||
-- @string key Name of the attribute to update
|
||||
-- @number value New value for the attribute
|
||||
function charMeta:SetAttrib(key, value)
|
||||
local attribute = ix.attributes.list[key]
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (attribute) then
|
||||
local attrib = self:GetAttributes()
|
||||
|
||||
attrib[key] = value
|
||||
|
||||
if (IsValid(client)) then
|
||||
net.Start("ixAttributeUpdate")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteFloat(attrib[key])
|
||||
net.Send(client)
|
||||
|
||||
if (attribute.Setup) then
|
||||
attribute.Setup(attrib[key])
|
||||
end
|
||||
end
|
||||
|
||||
self:SetAttributes(attrib)
|
||||
end
|
||||
|
||||
hook.Run("CharacterAttributeUpdated", client, self, key, value)
|
||||
end
|
||||
|
||||
--- Temporarily increments one of this character's attributes. Useful for things like consumable items.
|
||||
-- @realm server
|
||||
-- @string boostID Unique ID to use for the boost to remove it later
|
||||
-- @string attribID Name of the attribute to boost
|
||||
-- @number boostAmount Amount to increase the attribute by
|
||||
function charMeta:AddBoost(boostID, attribID, boostAmount)
|
||||
local boosts = self:GetVar("boosts", {})
|
||||
|
||||
boosts[attribID] = boosts[attribID] or {}
|
||||
boosts[attribID][boostID] = boostAmount
|
||||
|
||||
hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, boostAmount)
|
||||
|
||||
return self:SetVar("boosts", boosts, nil, self:GetPlayer())
|
||||
end
|
||||
|
||||
--- Removes a temporary boost from this character.
|
||||
-- @realm server
|
||||
-- @string boostID Unique ID of the boost to remove
|
||||
-- @string attribID Name of the attribute that was boosted
|
||||
function charMeta:RemoveBoost(boostID, attribID)
|
||||
local boosts = self:GetVar("boosts", {})
|
||||
|
||||
boosts[attribID] = boosts[attribID] or {}
|
||||
boosts[attribID][boostID] = nil
|
||||
|
||||
hook.Run("CharacterAttributeBoosted", self:GetPlayer(), self, attribID, boostID, true)
|
||||
|
||||
return self:SetVar("boosts", boosts, nil, self:GetPlayer())
|
||||
end
|
||||
else
|
||||
net.Receive("ixAttributeUpdate", function()
|
||||
local id = net.ReadUInt(32)
|
||||
local character = ix.char.loaded[id]
|
||||
|
||||
if (character) then
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadFloat()
|
||||
|
||||
character:GetAttributes()[key] = value
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Returns all boosts that this character has for the given attribute. This is only valid on the server and owning client.
|
||||
-- @realm shared
|
||||
-- @string attribID Name of the attribute to find boosts for
|
||||
-- @treturn[1] table Table of boosts that this character has for the attribute
|
||||
-- @treturn[2] nil If the character has no boosts for the given attribute
|
||||
function charMeta:GetBoost(attribID)
|
||||
local boosts = self:GetBoosts()
|
||||
|
||||
return boosts[attribID]
|
||||
end
|
||||
|
||||
--- Returns all boosts that this character has. This is only valid on the server and owning client.
|
||||
-- @realm shared
|
||||
-- @treturn table Table of boosts this character has
|
||||
function charMeta:GetBoosts()
|
||||
return self:GetVar("boosts", {})
|
||||
end
|
||||
|
||||
--- Returns the current value of an attribute. This is only valid on the server and owning client.
|
||||
-- @realm shared
|
||||
-- @string key Name of the attribute to get
|
||||
-- @number default Value to return if the attribute doesn't exist
|
||||
-- @treturn number Value of the attribute
|
||||
function charMeta:GetAttribute(key, default)
|
||||
local att = self:GetAttributes()[key] or default
|
||||
local boosts = self:GetBoosts()[key]
|
||||
|
||||
if (boosts) then
|
||||
for _, v in pairs(boosts) do
|
||||
att = att + v
|
||||
end
|
||||
end
|
||||
|
||||
return att
|
||||
end
|
||||
end
|
||||
161
gamemodes/helix/gamemode/core/libs/sh_business.lua
Normal file
161
gamemodes/helix/gamemode/core/libs/sh_business.lua
Normal file
@@ -0,0 +1,161 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixBusinessBuy")
|
||||
util.AddNetworkString("ixBusinessResponse")
|
||||
util.AddNetworkString("ixShipmentUse")
|
||||
util.AddNetworkString("ixShipmentOpen")
|
||||
util.AddNetworkString("ixShipmentClose")
|
||||
|
||||
net.Receive("ixBusinessBuy", function(length, client)
|
||||
if (client.ixNextBusiness and client.ixNextBusiness > CurTime()) then
|
||||
client:NotifyLocalized("businessTooFast")
|
||||
return
|
||||
end
|
||||
|
||||
local char = client:GetCharacter()
|
||||
|
||||
if (!char) then
|
||||
return
|
||||
end
|
||||
|
||||
local indicies = net.ReadUInt(8)
|
||||
local items = {}
|
||||
|
||||
for _ = 1, indicies do
|
||||
items[net.ReadString()] = net.ReadUInt(8)
|
||||
end
|
||||
|
||||
if (table.IsEmpty(items)) then
|
||||
return
|
||||
end
|
||||
|
||||
local cost = 0
|
||||
|
||||
for k, v in pairs(items) do
|
||||
local itemTable = ix.item.list[k]
|
||||
|
||||
if (itemTable and hook.Run("CanPlayerUseBusiness", client, k) != false) then
|
||||
local amount = math.Clamp(tonumber(v) or 0, 0, 10)
|
||||
items[k] = amount
|
||||
|
||||
if (amount == 0) then
|
||||
items[k] = nil
|
||||
else
|
||||
cost = cost + (amount * (itemTable.price or 0))
|
||||
end
|
||||
else
|
||||
items[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if (table.IsEmpty(items)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (char:HasMoney(cost)) then
|
||||
char:TakeMoney(cost)
|
||||
|
||||
local entity = ents.Create("ix_shipment")
|
||||
entity:Spawn()
|
||||
entity:SetPos(client:GetItemDropPos(entity))
|
||||
entity:SetItems(items)
|
||||
entity:SetNetVar("owner", char:GetID())
|
||||
|
||||
local shipments = char:GetVar("charEnts") or {}
|
||||
table.insert(shipments, entity)
|
||||
char:SetVar("charEnts", shipments, true)
|
||||
|
||||
net.Start("ixBusinessResponse")
|
||||
net.Send(client)
|
||||
|
||||
hook.Run("CreateShipment", client, entity)
|
||||
|
||||
client.ixNextBusiness = CurTime() + 0.5
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixShipmentUse", function(length, client)
|
||||
local uniqueID = net.ReadString()
|
||||
local drop = net.ReadBool()
|
||||
|
||||
local entity = client.ixShipment
|
||||
local itemTable = ix.item.list[uniqueID]
|
||||
|
||||
if (itemTable and IsValid(entity)) then
|
||||
if (entity:GetPos():Distance(client:GetPos()) > 128) then
|
||||
client.ixShipment = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local amount = entity.items[uniqueID]
|
||||
|
||||
if (amount and amount > 0) then
|
||||
if (entity.items[uniqueID] <= 0) then
|
||||
entity.items[uniqueID] = nil
|
||||
end
|
||||
|
||||
if (drop) then
|
||||
ix.item.Spawn(uniqueID, entity:GetPos() + Vector(0, 0, 16), function(item, itemEntity)
|
||||
if (IsValid(client)) then
|
||||
itemEntity.ixSteamID = client:SteamID()
|
||||
itemEntity.ixCharID = client:GetCharacter():GetID()
|
||||
end
|
||||
end)
|
||||
else
|
||||
if entity.itemData then
|
||||
local item, _ = client:GetCharacter():GetInventory():Add(uniqueID, 1, {
|
||||
data = entity.itemData
|
||||
})
|
||||
|
||||
if (!item) then
|
||||
return client:NotifyLocalized("noFit")
|
||||
end
|
||||
else
|
||||
local status, _ = client:GetCharacter():GetInventory():Add(uniqueID)
|
||||
|
||||
if (!status) then
|
||||
return client:NotifyLocalized("noFit")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("ShipmentItemTaken", client, uniqueID, amount)
|
||||
|
||||
entity.items[uniqueID] = entity.items[uniqueID] - 1
|
||||
|
||||
if (entity:GetItemCount() < 1) then
|
||||
entity:GibBreakServer(Vector(0, 0, 0.5))
|
||||
entity:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixShipmentClose", function(length, client)
|
||||
local entity = client.ixShipment
|
||||
|
||||
if (IsValid(entity)) then
|
||||
entity.ixInteractionDirty = false
|
||||
client.ixShipment = nil
|
||||
end
|
||||
end)
|
||||
else
|
||||
net.Receive("ixShipmentOpen", function()
|
||||
local entity = net.ReadEntity()
|
||||
local items = net.ReadTable()
|
||||
|
||||
ix.gui.shipment = vgui.Create("ixShipment")
|
||||
ix.gui.shipment:SetItems(entity, items)
|
||||
end)
|
||||
end
|
||||
1282
gamemodes/helix/gamemode/core/libs/sh_character.lua
Normal file
1282
gamemodes/helix/gamemode/core/libs/sh_character.lua
Normal file
File diff suppressed because it is too large
Load Diff
758
gamemodes/helix/gamemode/core/libs/sh_chatbox.lua
Normal file
758
gamemodes/helix/gamemode/core/libs/sh_chatbox.lua
Normal file
@@ -0,0 +1,758 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Chat manipulation and helper functions.
|
||||
|
||||
Chat messages are a core part of the framework - it's takes up a good chunk of the gameplay, and is also used to interact with
|
||||
the framework. Chat messages can have types or "classes" that describe how the message should be interpreted. All chat messages
|
||||
will have some type of class: `ic` for regular in-character speech, `me` for actions, `ooc` for out-of-character, etc. These
|
||||
chat classes can affect how the message is displayed in each player's chatbox. See `ix.chat.Register` and `ChatClassStructure`
|
||||
to create your own chat classes.
|
||||
]]
|
||||
-- @module ix.chat
|
||||
|
||||
ix.chat = ix.chat or {}
|
||||
|
||||
--- List of all chat classes that have been registered by the framework, where each key is the name of the chat class, and value
|
||||
-- is the chat class data. Accessing a chat class's data is useful for when you want to copy some functionality or properties
|
||||
-- to use in your own. Note that if you're accessing this table, you should do so inside of the `InitializedChatClasses` hook.
|
||||
-- @realm shared
|
||||
-- @table ix.chat.classes
|
||||
-- @usage print(ix.chat.classes.ic.format)
|
||||
-- > "%s says \"%s\""
|
||||
ix.chat.classes = ix.chat.classes or {}
|
||||
|
||||
if (!ix.command) then
|
||||
include("sh_command.lua")
|
||||
end
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Bypass OOC Timer",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - OOC See IC Name",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Icon Incognito Mode",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
|
||||
-- note we can't use commas in the "color" field's default value since the metadata is separated by commas which will break the
|
||||
-- formatting for that field
|
||||
|
||||
--- Chat messages can have different classes or "types" of messages that have different properties. This can include how the
|
||||
-- text is formatted, color, hearing distance, etc.
|
||||
-- @realm shared
|
||||
-- @table ChatClassStructure
|
||||
-- @see ix.chat.Register
|
||||
-- @field[type=string] prefix What the player must type before their message in order to use this chat class. For example,
|
||||
-- having a prefix of `/Y` will require to type `/Y I am yelling` in order to send a message with this chat class. This can also
|
||||
-- be a table of strings if you want to allow multiple prefixes, such as `{"//", "/OOC"}`.
|
||||
--
|
||||
-- **NOTE:** the prefix should usually start with a `/` to be consistent with the rest of the framework. However, you are able
|
||||
-- to use something different like the `LOOC` chat class where the prefixes are `.//`, `[[`, and `/LOOC`.
|
||||
-- @field[type=bool,opt=false] noSpaceAfter Whether or not the `prefix` can be used without a space after it. For example, the
|
||||
-- `OOC` chat class allows you to type `//my message` instead of `// my message`. **NOTE:** this only works if the last
|
||||
-- character in the prefix is non-alphanumeric (i.e `noSpaceAfter` with `/Y` will not work, but `/!` will).
|
||||
-- @field[type=string,opt] description Description to show to the user in the chatbox when they're using this chat class
|
||||
-- @field[type=string,opt="%s: \"%s\""] format How to format a message with this chat class. The first `%s` will be the speaking
|
||||
-- player's name, and the second one will be their message
|
||||
-- @field[type=color,opt=Color(242 230 160)] color Color to use when displaying a message with this chat class
|
||||
-- @field[type=string,opt="chatTyping"] indicator Language phrase to use when displaying the typing indicator above the
|
||||
-- speaking player's head
|
||||
-- @field[type=bool,opt=false] bNoIndicator Whether or not to avoid showing the typing indicator above the speaking player's
|
||||
-- head
|
||||
-- @field[type=string,opt=ixChatFont] font Font to use for displaying a message with this chat class
|
||||
-- @field[type=bool,opt=false] deadCanChat Whether or not a dead player can send a message with this chat class
|
||||
-- @field[type=number] CanHear This can be either a `number` representing how far away another player can hear this message.
|
||||
-- IC messages will use the `chatRange` config, for example. This can also be a function, which returns `true` if the given
|
||||
-- listener can hear the message emitted from a speaker.
|
||||
-- -- message can be heard by any player 1000 units away from the speaking player
|
||||
-- CanHear = 1000
|
||||
-- OR
|
||||
-- CanHear = function(self, speaker, listener)
|
||||
-- -- the speaking player will be heard by everyone
|
||||
-- return true
|
||||
-- end
|
||||
-- @field[type=function,opt] CanSay Function to run to check whether or not a player can send a message with this chat class.
|
||||
-- By default, it will return `false` if the player is dead and `deadCanChat` is `false`. Overriding this function will prevent
|
||||
-- `deadCanChat` from working, and you must implement this functionality manually.
|
||||
-- CanSay = function(self, speaker, text)
|
||||
-- -- the speaker will never be able to send a message with this chat class
|
||||
-- return false
|
||||
-- end
|
||||
-- @field[type=function,opt] GetColor Function to run to set the color of a message with this chat class. You should generally
|
||||
-- stick to using `color`, but this is useful for when you want the color of the message to change with some criteria.
|
||||
-- GetColor = function(self, speaker, text)
|
||||
-- -- each message with this chat class will be colored a random shade of red
|
||||
-- return Color(math.random(120, 200), 0, 0)
|
||||
-- end
|
||||
-- @field[type=function,opt] OnChatAdd Function to run when a message with this chat class should be added to the chatbox. If
|
||||
-- using this function, make sure you end the function by calling `chat.AddText` in order for the text to show up.
|
||||
--
|
||||
-- **NOTE:** using your own `OnChatAdd` function will prevent `color`, `GetColor`, or `format` from being used since you'll be
|
||||
-- overriding the base function that uses those properties. In such cases you'll need to add that functionality back in
|
||||
-- manually. In general, you should avoid overriding this function where possible. The `data` argument in the function is
|
||||
-- whatever is passed into the same `data` argument in `ix.chat.Send`.
|
||||
--
|
||||
-- OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
-- -- adds white text in the form of "Player Name: Message contents"
|
||||
-- chat.AddText(color_white, speaker:GetName(), ": ", text)
|
||||
-- end
|
||||
|
||||
--- Registers a new chat type with the information provided. Chat classes should usually be created inside of the
|
||||
-- `InitializedChatClasses` hook.
|
||||
-- @realm shared
|
||||
-- @string chatType Name of the chat type
|
||||
-- @tparam ChatClassStructure data Properties and functions to assign to this chat class
|
||||
-- @usage -- this is the "me" chat class taken straight from the framework as an example
|
||||
-- ix.chat.Register("me", {
|
||||
-- format = "** %s %s",
|
||||
-- color = Color(255, 50, 50),
|
||||
-- CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
-- prefix = {"/Me", "/Action"},
|
||||
-- description = "@cmdMe",
|
||||
-- indicator = "chatPerforming",
|
||||
-- deadCanChat = true
|
||||
-- })
|
||||
-- @see ChatClassStructure
|
||||
function ix.chat.Register(chatType, data)
|
||||
chatType = string.lower(chatType)
|
||||
|
||||
if (!data.CanHear) then
|
||||
-- Have a substitute if the canHear property is not found.
|
||||
function data:CanHear(speaker, listener)
|
||||
-- The speaker will be heard by everyone.
|
||||
return true
|
||||
end
|
||||
elseif (isnumber(data.CanHear)) then
|
||||
-- Use the value as a range and create a function to compare distances.
|
||||
local range = data.CanHear * data.CanHear
|
||||
data.range = range
|
||||
|
||||
function data:CanHear(speaker, listener)
|
||||
-- Length2DSqr is faster than Length2D, so just check the squares.
|
||||
return (speaker:GetPos() - listener:GetPos()):LengthSqr() <= self.range
|
||||
end
|
||||
end
|
||||
|
||||
-- Allow players to use this chat type by default.
|
||||
if (!data.CanSay) then
|
||||
function data:CanSay(speaker, text)
|
||||
if (!self.deadCanChat and !speaker:Alive()) then
|
||||
speaker:NotifyLocalized("noPerm")
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- Chat text color.
|
||||
data.color = data.color or Color(242, 230, 160)
|
||||
|
||||
if (!data.OnChatAdd) then
|
||||
data.format = data.format or "%s: \"%s\""
|
||||
data.icon = data.icon or nil
|
||||
|
||||
function data:OnChatAdd(speaker, text, anonymous, info)
|
||||
local color = self.color
|
||||
local name = anonymous and
|
||||
L"someone" or hook.Run("GetCharacterName", speaker, chatType) or
|
||||
(IsValid(speaker) and speaker:Name() or "Console")
|
||||
|
||||
if (self.GetColor) then
|
||||
color = self:GetColor(speaker, text, info)
|
||||
end
|
||||
|
||||
if self.bigfont then
|
||||
local oldFont = self.font
|
||||
local font = hook.Run("GetSpeakerYellFont", speaker)
|
||||
self.font = font
|
||||
elseif self.specialfont then
|
||||
local oldFont = self.font
|
||||
local font = hook.Run("GetSpeakerFont", speaker)
|
||||
self.font = font
|
||||
else
|
||||
local oldFont = self.font
|
||||
local font = "ixChatFont"
|
||||
self.font = font
|
||||
end
|
||||
|
||||
local translated = L2(chatType.."Format", name, text)
|
||||
|
||||
if self.icon and ix.option.Get("standardIconsEnabled") then
|
||||
chat.AddText(ix.util.GetMaterial(self.icon), color, translated or string.format(self.format, name, text))
|
||||
else
|
||||
chat.AddText(color, translated or string.format(self.format, name, text))
|
||||
end
|
||||
|
||||
if self.font then
|
||||
self.font = oldFont
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (CLIENT and data.prefix) then
|
||||
if (istable(data.prefix)) then
|
||||
for _, v in ipairs(data.prefix) do
|
||||
if (v:utf8sub(1, 1) == "/") then
|
||||
ix.command.Add(v:utf8sub(2), {
|
||||
description = data.description,
|
||||
arguments = ix.type.text,
|
||||
indicator = data.indicator,
|
||||
bNoIndicator = data.bNoIndicator,
|
||||
chatClass = data,
|
||||
OnCheckAccess = function() return true end,
|
||||
OnRun = function(self, client, message) end
|
||||
})
|
||||
end
|
||||
end
|
||||
else
|
||||
ix.command.Add(isstring(data.prefix) and data.prefix:utf8sub(2) or chatType, {
|
||||
description = data.description,
|
||||
arguments = ix.type.text,
|
||||
indicator = data.indicator,
|
||||
bNoIndicator = data.bNoIndicator,
|
||||
chatClass = data,
|
||||
OnCheckAccess = function() return true end,
|
||||
OnRun = function(self, client, message) end
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
data.uniqueID = chatType
|
||||
ix.chat.classes[chatType] = data
|
||||
end
|
||||
|
||||
--- Identifies which chat mode should be used.
|
||||
-- @realm shared
|
||||
-- @player client Player who is speaking
|
||||
-- @string message Message to parse
|
||||
-- @bool[opt=false] bNoSend Whether or not to send the chat message after parsing
|
||||
-- @treturn string Name of the chat type
|
||||
-- @treturn string Message that was parsed
|
||||
-- @treturn bool Whether or not the speaker should be anonymous
|
||||
function ix.chat.Parse(client, message, bNoSend)
|
||||
local anonymous = false
|
||||
local chatType = "ic"
|
||||
|
||||
-- Loop through all chat classes and see if the message contains their prefix.
|
||||
for k, v in pairs(ix.chat.classes) do
|
||||
local isChosen = false
|
||||
local chosenPrefix = ""
|
||||
local noSpaceAfter = v.noSpaceAfter
|
||||
|
||||
-- Check through all prefixes if the chat type has more than one.
|
||||
if (istable(v.prefix)) then
|
||||
for _, prefix in ipairs(v.prefix) do
|
||||
prefix = prefix:utf8lower()
|
||||
local fullPrefix = prefix .. (noSpaceAfter and "" or " ")
|
||||
|
||||
-- Checking if the start of the message has the prefix.
|
||||
if (message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()) then
|
||||
isChosen = true
|
||||
chosenPrefix = fullPrefix
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Otherwise the prefix itself is checked.
|
||||
elseif (isstring(v.prefix)) then
|
||||
local prefix = v.prefix:utf8lower()
|
||||
local fullPrefix = prefix .. (noSpaceAfter and "" or " ")
|
||||
|
||||
isChosen = message:utf8sub(1, prefix:utf8len() + (noSpaceAfter and 0 or 1)):utf8lower() == fullPrefix:utf8lower()
|
||||
chosenPrefix = fullPrefix
|
||||
end
|
||||
|
||||
-- If the checks say we have the proper chat type, then the chat type is the chosen one!
|
||||
-- If this is not chosen, the loop continues. If the loop doesn't find the correct chat
|
||||
-- type, then it falls back to IC chat as seen by the chatType variable above.
|
||||
if (isChosen) then
|
||||
-- Set the chat type to the chosen one.
|
||||
chatType = k
|
||||
-- Remove the prefix from the chat type so it does not show in the message.
|
||||
message = message:utf8sub(chosenPrefix:utf8len() + 1)
|
||||
|
||||
if (ix.chat.classes[k].noSpaceAfter and message:utf8sub(1, 1):match("%s")) then
|
||||
message = message:utf8sub(2)
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!message:find("%S")) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Only send if needed.
|
||||
if (SERVER and !bNoSend) then
|
||||
-- Send the correct chat type out so other player see the message.
|
||||
ix.chat.Send(client, chatType, hook.Run("PlayerMessageSend", client, chatType, message, anonymous) or message, anonymous)
|
||||
end
|
||||
|
||||
-- Return the chosen chat type and the message that was sent if needed for some reason.
|
||||
-- This would be useful if you want to send the message on your own.
|
||||
return chatType, message, anonymous
|
||||
end
|
||||
|
||||
--- Formats a string to fix basic grammar - removing extra spacing at the beginning and end, capitalizing the first character,
|
||||
-- and making sure it ends in punctuation.
|
||||
-- @realm shared
|
||||
-- @string text String to format
|
||||
-- @treturn string Formatted string
|
||||
-- @usage print(ix.chat.Format("hello"))
|
||||
-- > Hello.
|
||||
-- @usage print(ix.chat.Format("wow!"))
|
||||
-- > Wow!
|
||||
function ix.chat.Format(text)
|
||||
text = string.Trim(text)
|
||||
local last = text:utf8sub(-1)
|
||||
|
||||
if (last != "." and last != "?" and last != "!" and last != "-" and last != "\"") then
|
||||
text = text .. "."
|
||||
end
|
||||
|
||||
return text:utf8sub(1, 1):utf8upper() .. text:utf8sub(2)
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixChatMessage")
|
||||
|
||||
--- Send a chat message using the specified chat type.
|
||||
-- @realm server
|
||||
-- @player speaker Player who is speaking
|
||||
-- @string chatType Name of the chat type
|
||||
-- @string text Message to send
|
||||
-- @bool[opt=false] bAnonymous Whether or not the speaker should be anonymous
|
||||
-- @tab[opt=nil] receivers The players to replicate send the message to
|
||||
-- @tab[opt=nil] data Additional data for this chat message
|
||||
function ix.chat.Send(speaker, chatType, text, bAnonymous, receivers, data)
|
||||
if (!chatType) then
|
||||
return
|
||||
end
|
||||
|
||||
data = data or {}
|
||||
chatType = string.lower(chatType)
|
||||
|
||||
if (IsValid(speaker) and hook.Run("PrePlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, data) == false) then
|
||||
return
|
||||
end
|
||||
|
||||
local class = ix.chat.classes[chatType]
|
||||
|
||||
if (class and class:CanSay(speaker, text, data) != false) then
|
||||
if (class.CanHear and !receivers) then
|
||||
receivers = {}
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetCharacter() and class:CanHear(speaker, v, data) != false) then
|
||||
receivers[#receivers + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
if (#receivers == 0) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Format the message if needed before we run the hook.
|
||||
local rawText = text
|
||||
local maxLength = ix.config.Get("chatMax")
|
||||
|
||||
if (text:utf8len() > maxLength) then
|
||||
text = text:utf8sub(0, maxLength)
|
||||
end
|
||||
|
||||
if (ix.config.Get("chatAutoFormat") and hook.Run("CanAutoFormatMessage", speaker, chatType, text)) then
|
||||
text = ix.chat.Format(text)
|
||||
end
|
||||
|
||||
local iconIncognitoMode = false
|
||||
if speaker and IsValid(speaker) and !speaker:IsBot() then
|
||||
iconIncognitoMode = ix.option.Get(speaker, "iconIncognitoMode", false)
|
||||
end
|
||||
|
||||
text = hook.Run("PlayerMessageSend", speaker, chatType, text, bAnonymous, receivers, rawText, data) or text
|
||||
|
||||
net.Start("ixChatMessage")
|
||||
net.WriteEntity(speaker)
|
||||
net.WriteString(chatType)
|
||||
net.WriteString(text)
|
||||
net.WriteBool(bAnonymous or false)
|
||||
net.WriteBool(iconIncognitoMode)
|
||||
net.WriteTable(data or {})
|
||||
net.Send(receivers)
|
||||
|
||||
return text
|
||||
end
|
||||
end
|
||||
else
|
||||
function ix.chat.Send(speaker, chatType, text, anonymous, data, iconIncognitoMode)
|
||||
local class = ix.chat.classes[chatType]
|
||||
|
||||
if (class) then
|
||||
-- luacheck: globals CHAT_CLASS
|
||||
CHAT_CLASS = class
|
||||
class:OnChatAdd(speaker, text, anonymous, data, iconIncognitoMode)
|
||||
CHAT_CLASS = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Call OnChatAdd for the appropriate chatType.
|
||||
net.Receive("ixChatMessage", function()
|
||||
local client = net.ReadEntity()
|
||||
local chatType = net.ReadString()
|
||||
local text = net.ReadString()
|
||||
local anonymous = net.ReadBool()
|
||||
local iconIncognitoMode = net.ReadBool()
|
||||
local data = net.ReadTable()
|
||||
local info = {
|
||||
chatType = chatType,
|
||||
text = text,
|
||||
anonymous = anonymous,
|
||||
iconIncognitoMode = iconIncognitoMode,
|
||||
data = data
|
||||
}
|
||||
|
||||
if (IsValid(client)) then
|
||||
hook.Run("MessageReceived", client, info)
|
||||
ix.chat.Send(client, info.chatType or chatType, info.text or text, info.anonymous or anonymous, info.data,
|
||||
info.iconIncognitoMode)
|
||||
else
|
||||
if client and client.IsWorld and client:IsWorld() then
|
||||
hook.Run("MessageReceived", nil, info)
|
||||
end
|
||||
|
||||
ix.chat.Send(nil, chatType, text, anonymous, data)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Add the default chat types here.
|
||||
do
|
||||
-- Load the chat types after the configs so we can access changed configs.
|
||||
hook.Add("InitializedConfig", "ixChatTypes", function()
|
||||
-- The default in-character chat.
|
||||
ix.chat.Register("ic", {
|
||||
format = "%s says \"%s\"",
|
||||
icon = "willardnetworks/chat/message_icon.png",
|
||||
indicator = "chatTalking",
|
||||
color = Color(255, 254, 153, 255),
|
||||
CanHear = ix.config.Get("chatRange", 280)
|
||||
})
|
||||
|
||||
ix.option.Add("iconIncognitoMode", ix.type.bool, false, {
|
||||
bNetworked = true,
|
||||
category = "Chat",
|
||||
hidden = function()
|
||||
return !CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Icon Incognito Mode")
|
||||
end
|
||||
})
|
||||
|
||||
if (CLIENT) then
|
||||
ix.option.Add("standardIconsEnabled", ix.type.bool, true, {
|
||||
category = "Chat"
|
||||
})
|
||||
|
||||
ix.option.Add("seeGlobalOOC", ix.type.bool, true, {
|
||||
category = "Chat"
|
||||
})
|
||||
|
||||
ix.option.Add("enablePrivateMessageSound", ix.type.bool, true, {
|
||||
category = "Chat"
|
||||
})
|
||||
end
|
||||
|
||||
-- Actions and such.
|
||||
ix.chat.Register("me", {
|
||||
format = "*** %s %s",
|
||||
color = Color(214, 254, 137, 255),
|
||||
CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
prefix = {"/Me", "/Action"},
|
||||
description = "@cmdMe",
|
||||
indicator = "chatPerforming",
|
||||
CanSay = function(self, speaker, text)
|
||||
if (!speaker:Alive() and speaker.lastMeExpended) then
|
||||
speaker:NotifyLocalized("noPerm")
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Actions and such.
|
||||
ix.chat.Register("it", {
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
chat.AddText(ix.config.Get("chatColor"), "***' "..text)
|
||||
end,
|
||||
CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
prefix = {"/It"},
|
||||
description = "@cmdIt",
|
||||
indicator = "chatPerforming",
|
||||
deadCanChat = true
|
||||
})
|
||||
|
||||
-- Whisper chat.
|
||||
ix.chat.Register("w", {
|
||||
format = "%s chuchotte \"%s\"",
|
||||
icon = "willardnetworks/chat/whisper_icon.png",
|
||||
color = Color(158, 162, 191, 255),
|
||||
CanHear = ix.config.Get("chatRange", 280) * 0.25,
|
||||
prefix = {"/W", "/Whisper"},
|
||||
description = "@cmdW",
|
||||
indicator = "chatWhispering",
|
||||
specialfont = true
|
||||
})
|
||||
|
||||
-- Yelling out loud.
|
||||
ix.chat.Register("y", {
|
||||
format = "%s hurle \"%s\"",
|
||||
color = Color(254, 171, 103, 255),
|
||||
icon = "willardnetworks/chat/yell_icon.png",
|
||||
CanHear = ix.config.Get("chatRange", 280) * 2,
|
||||
prefix = {"/Y", "/Yell"},
|
||||
description = "@cmdY",
|
||||
indicator = "chatYelling",
|
||||
bigfont = true
|
||||
})
|
||||
|
||||
-- REMEMBER TO UPDATE THESE WHEN WE CHANGE RANKS IN SAM
|
||||
local chatIconMap = {
|
||||
["willardnetworks/chat/star.png"] = {
|
||||
["community_manager"] = true,
|
||||
["head_of_staff"] = true,
|
||||
["server_council"] = true,
|
||||
["willard_management"] = true,
|
||||
["superadmin"] = true
|
||||
},
|
||||
["willardnetworks/chat/hammer.png"] = {
|
||||
["senior_admin"] = true,
|
||||
["server_admin"] = true,
|
||||
["trial_admin"] = true,
|
||||
["admin"] = true
|
||||
},
|
||||
["willardnetworks/chat/paintbrush.png"] = {
|
||||
["gamemaster"] = true
|
||||
},
|
||||
["willardnetworks/chat/wrench.png"] = {
|
||||
["developer"] = true
|
||||
},
|
||||
["willardnetworks/chat/leaf.png"] = {
|
||||
["mentor"] = true
|
||||
},
|
||||
["willardnetworks/chat/heart.png"] = {
|
||||
["premium1"] = true,
|
||||
["premium2"] = true,
|
||||
["premium3"] = true
|
||||
}
|
||||
}
|
||||
local function GetIcon(speaker, iconIncognitoMode)
|
||||
local icon = "willardnetworks/chat/ooc_icon.png"
|
||||
if !speaker or speaker and !IsValid(speaker) then
|
||||
return ix.util.GetMaterial(icon)
|
||||
end
|
||||
|
||||
if iconIncognitoMode then return ix.util.GetMaterial(icon) end
|
||||
|
||||
for mat, rankGroup in pairs(chatIconMap) do
|
||||
if (rankGroup[speaker:GetUserGroup()]) then
|
||||
icon = mat
|
||||
end
|
||||
end
|
||||
|
||||
return ix.util.GetMaterial(hook.Run("GetPlayerIcon", speaker) or icon)
|
||||
end
|
||||
-- Out of character.
|
||||
ix.chat.Register("ooc", {
|
||||
CanSay = function(self, speaker, text)
|
||||
if (!ix.config.Get("allowGlobalOOC")) then
|
||||
speaker:NotifyLocalized("GOOCIsDisabled")
|
||||
return false
|
||||
else
|
||||
local delay = ix.config.Get("oocDelay", 10)
|
||||
|
||||
-- Only need to check the time if they have spoken in OOC chat before.
|
||||
if (delay > 0 and speaker.ixLastOOC) then
|
||||
local lastOOC = CurTime() - speaker.ixLastOOC
|
||||
|
||||
-- Use this method of checking time in case the oocDelay config changes.
|
||||
if (lastOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then
|
||||
speaker:NotifyLocalized("oocDelay", delay - math.ceil(lastOOC))
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the last time they spoke in OOC.
|
||||
speaker.ixLastOOC = CurTime()
|
||||
end
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text, _, _, iconIncognitoMode)
|
||||
-- @todo remove and fix actual cause of speaker being nil
|
||||
if (!IsValid(speaker) or !ix.option.Get("seeGlobalOOC", true)) then
|
||||
return
|
||||
end
|
||||
|
||||
local icon = GetIcon(speaker, iconIncognitoMode)
|
||||
if (CAMI.PlayerHasAccess(LocalPlayer(), "Helix - OOC See IC Name")) then
|
||||
chat.AddText(icon, Color(255, 66, 66), "[OOC] ", Color(192, 192, 196), speaker:SteamName()
|
||||
, " (", speaker:Name(), ")", color_white, ": ", text)
|
||||
else
|
||||
chat.AddText(icon, Color(255, 66, 66), "[OOC] ", Color(192, 192, 196), speaker:SteamName(), color_white, ": ", text)
|
||||
end
|
||||
end,
|
||||
prefix = {"//", "/OOC"},
|
||||
description = "@cmdOOC",
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
-- Local out of character.
|
||||
ix.chat.Register("looc", {
|
||||
CanSay = function(self, speaker, text)
|
||||
local delay = ix.config.Get("loocDelay", 0)
|
||||
|
||||
-- Only need to check the time if they have spoken in OOC chat before.
|
||||
if (delay > 0 and speaker.ixLastLOOC) then
|
||||
local lastLOOC = CurTime() - speaker.ixLastLOOC
|
||||
|
||||
-- Use this method of checking time in case the oocDelay config changes.
|
||||
if (lastLOOC <= delay and !CAMI.PlayerHasAccess(speaker, "Helix - Bypass OOC Timer", nil)) then
|
||||
speaker:NotifyLocalized("loocDelay", delay - math.ceil(lastLOOC))
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the last time they spoke in OOC.
|
||||
speaker.ixLastLOOC = CurTime()
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text, _, _, iconIncognitoMode)
|
||||
local icon = GetIcon(speaker, iconIncognitoMode)
|
||||
local name = hook.Run("GetCharacterName", speaker, "ic") or
|
||||
(IsValid(speaker) and speaker:Name() or "Console")
|
||||
|
||||
chat.AddText(icon, Color(255, 66, 66), "[LOOC] ", Color(255, 254, 153, 255), name..": "..text)
|
||||
end,
|
||||
CanHear = ix.config.Get("chatRange", 280),
|
||||
prefix = {".//", "[[", "/LOOC"},
|
||||
description = "@cmdLOOC",
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
-- Roll information in chat.
|
||||
ix.chat.Register("roll", {
|
||||
format = "*** %s has rolled %s out of %s.",
|
||||
color = Color(155, 111, 176),
|
||||
CanHear = ix.config.Get("chatRange", 280),
|
||||
deadCanChat = true,
|
||||
OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
local max = data.max or 100
|
||||
local translated = L2(self.uniqueID.."Format", speaker:Name(), text, max)
|
||||
|
||||
chat.AddText(self.color, translated and "*** "..translated or string.format(self.format,
|
||||
speaker:Name(), text, max
|
||||
))
|
||||
end
|
||||
})
|
||||
|
||||
-- run a hook after we add the basic chat classes so schemas/plugins can access their info as soon as possible if needed
|
||||
hook.Run("InitializedChatClasses")
|
||||
end)
|
||||
end
|
||||
|
||||
-- Private messages between players.
|
||||
ix.chat.Register("pm", {
|
||||
format = "%s (%s): %s",
|
||||
color = Color(255, 255, 239, 61),
|
||||
deadCanChat = true,
|
||||
|
||||
OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
local client = LocalPlayer()
|
||||
|
||||
if (ix.option.Get("standardIconsEnabled")) then
|
||||
if (speaker and client == speaker) then
|
||||
if !data.target or data.target and !IsValid(data.target) then return end
|
||||
|
||||
chat.AddText(ix.util.GetMaterial("willardnetworks/chat/pm_icon.png"), Color(254, 238, 60), "[PM] »"
|
||||
, self.color, string.format(self.format, data.target:GetName(), data.target:SteamName(), text))
|
||||
else
|
||||
chat.AddText(ix.util.GetMaterial("willardnetworks/chat/pm_icon.png"), Color(254, 238, 60), "[PM] "
|
||||
, self.color, string.format(self.format, speaker:GetName(), speaker:SteamName(), text))
|
||||
end
|
||||
else
|
||||
if (client == speaker) then
|
||||
chat.AddText(Color(254, 238, 60), "[PM] »"
|
||||
, self.color, string.format(self.format, data.target:GetName(), data.target:SteamName(), text))
|
||||
else
|
||||
chat.AddText(Color(254, 238, 60), "[PM] "
|
||||
, self.color, string.format(self.format, speaker:GetName(), speaker:SteamName(), text))
|
||||
end
|
||||
end
|
||||
|
||||
if (client != speaker and ix.option.Get("enablePrivateMessageSound", true)) then
|
||||
surface.PlaySound("hl1/fvox/bell.wav")
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
-- Global events.
|
||||
ix.chat.Register("event", {
|
||||
CanHear = 1000000,
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
chat.AddText(Color(254, 138, 0), text)
|
||||
end,
|
||||
indicator = "chatPerforming"
|
||||
})
|
||||
|
||||
ix.chat.Register("connect", {
|
||||
CanSay = function(self, speaker, text)
|
||||
return !IsValid(speaker)
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local icon = ix.util.GetMaterial("willardnetworks/chat/connected_icon.png")
|
||||
|
||||
chat.AddText(icon, Color(151, 153, 152), L("playerConnected", text))
|
||||
end,
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
ix.chat.Register("disconnect", {
|
||||
CanSay = function(self, speaker, text)
|
||||
return !IsValid(speaker)
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text)
|
||||
local icon = ix.util.GetMaterial("willardnetworks/chat/disconnected_icon.png")
|
||||
|
||||
chat.AddText(icon, Color(151, 153, 152), L("playerDisconnected", text))
|
||||
end,
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
ix.chat.Register("notice", {
|
||||
CanSay = function(self, speaker, text)
|
||||
return !IsValid(speaker)
|
||||
end,
|
||||
OnChatAdd = function(self, speaker, text, bAnonymous, data)
|
||||
local icon = ix.util.GetMaterial(data.bError and "icon16/comment_delete.png" or "icon16/comment.png")
|
||||
chat.AddText(icon, data.bError and Color(200, 175, 200, 255) or Color(175, 200, 255), text)
|
||||
end,
|
||||
noSpaceAfter = true
|
||||
})
|
||||
|
||||
-- Why does ULX even have a /me command?
|
||||
hook.Remove("PlayerSay", "ULXMeCheck")
|
||||
212
gamemodes/helix/gamemode/core/libs/sh_class.lua
Normal file
212
gamemodes/helix/gamemode/core/libs/sh_class.lua
Normal file
@@ -0,0 +1,212 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Helper library for loading/getting class information.
|
||||
|
||||
Classes are temporary assignments for characters - analogous to a "job" in a faction. For example, you may have a police faction
|
||||
in your schema, and have "police recruit" and "police chief" as different classes in your faction. Anyone can join a class in
|
||||
their faction by default, but you can restrict this as you need with `CLASS.CanSwitchTo`.
|
||||
]]
|
||||
-- @module ix.class
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixClassUpdate")
|
||||
end
|
||||
|
||||
ix.class = ix.class or {}
|
||||
ix.class.list = {}
|
||||
|
||||
local charMeta = ix.meta.character
|
||||
|
||||
--- Loads classes from a directory.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @string directory The path to the class files.
|
||||
function ix.class.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
|
||||
-- Get the name without the "sh_" prefix and ".lua" suffix.
|
||||
local niceName = v:sub(4, -5)
|
||||
-- Determine a numeric identifier for this class.
|
||||
local index = #ix.class.list + 1
|
||||
local halt
|
||||
|
||||
for _, v2 in ipairs(ix.class.list) do
|
||||
if (v2.uniqueID == niceName) then
|
||||
halt = true
|
||||
end
|
||||
end
|
||||
|
||||
if (halt == true) then
|
||||
continue
|
||||
end
|
||||
|
||||
-- Set up a global table so the file has access to the class table.
|
||||
CLASS = {index = index, uniqueID = niceName}
|
||||
CLASS.name = "Unknown"
|
||||
CLASS.description = "No description available."
|
||||
CLASS.limit = 0
|
||||
|
||||
-- For future use with plugins.
|
||||
if (PLUGIN) then
|
||||
CLASS.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
ix.util.Include(directory.."/"..v, "shared")
|
||||
|
||||
-- Why have a class without a faction?
|
||||
if (!CLASS.faction or !team.Valid(CLASS.faction)) then
|
||||
ErrorNoHalt("Class '"..niceName.."' does not have a valid faction!\n")
|
||||
CLASS = nil
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
-- Allow classes to be joinable by default.
|
||||
if (!CLASS.CanSwitchTo) then
|
||||
CLASS.CanSwitchTo = function(client)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
ix.class.list[index] = CLASS
|
||||
CLASS = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Determines if a player is allowed to join a specific class.
|
||||
-- @realm shared
|
||||
-- @player client Player to check
|
||||
-- @number class Index of the class
|
||||
-- @treturn bool Whether or not the player can switch to the class
|
||||
function ix.class.CanSwitchTo(client, class)
|
||||
-- Get the class table by its numeric identifier.
|
||||
local info = ix.class.list[class]
|
||||
|
||||
-- See if the class exists.
|
||||
if (!info) then
|
||||
return false, "no info"
|
||||
end
|
||||
|
||||
-- If the player's faction matches the class's faction.
|
||||
if (client:Team() != info.faction) then
|
||||
return false, "not correct team"
|
||||
end
|
||||
|
||||
if (client:GetCharacter():GetClass() == class) then
|
||||
return false, "same class request"
|
||||
end
|
||||
|
||||
if (info.limit > 0) then
|
||||
if (#ix.class.GetPlayers(info.index) >= info.limit) then
|
||||
return false, "class is full"
|
||||
end
|
||||
end
|
||||
|
||||
if (hook.Run("CanPlayerJoinClass", client, class, info) == false) then
|
||||
return false
|
||||
end
|
||||
|
||||
-- See if the class allows the player to join it.
|
||||
return info:CanSwitchTo(client)
|
||||
end
|
||||
|
||||
--- Retrieves a class table.
|
||||
-- @realm shared
|
||||
-- @number identifier Index of the class
|
||||
-- @treturn table Class table
|
||||
function ix.class.Get(identifier)
|
||||
return ix.class.list[identifier]
|
||||
end
|
||||
|
||||
--- Retrieves the players in a class
|
||||
-- @realm shared
|
||||
-- @number class Index of the class
|
||||
-- @treturn table Table of players in the class
|
||||
function ix.class.GetPlayers(class)
|
||||
local players = {}
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
local char = v:GetCharacter()
|
||||
|
||||
if (char and char:GetClass() == class) then
|
||||
table.insert(players, v)
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Character class methods
|
||||
-- @classmod Character
|
||||
|
||||
--- Makes this character join a class. This automatically calls `KickClass` for you.
|
||||
-- @realm server
|
||||
-- @number class Index of the class to join
|
||||
-- @treturn bool Whether or not the character has successfully joined the class
|
||||
function charMeta:JoinClass(class)
|
||||
if (!class) then
|
||||
self:KickClass()
|
||||
return false
|
||||
end
|
||||
|
||||
local oldClass = self:GetClass()
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (ix.class.CanSwitchTo(client, class)) then
|
||||
self:SetClass(class)
|
||||
hook.Run("PlayerJoinedClass", client, class, oldClass)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Kicks this character out of the class they are currently in.
|
||||
-- @realm server
|
||||
function charMeta:KickClass()
|
||||
local client = self:GetPlayer()
|
||||
if (!client) then return end
|
||||
|
||||
local goClass
|
||||
|
||||
for k, v in pairs(ix.class.list) do
|
||||
if (v.faction == client:Team() and v.isDefault) then
|
||||
goClass = k
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self:JoinClass(goClass)
|
||||
|
||||
hook.Run("PlayerJoinedClass", client, goClass)
|
||||
end
|
||||
|
||||
function GM:PlayerJoinedClass(client, class, oldClass)
|
||||
local info = ix.class.list[class]
|
||||
local info2 = ix.class.list[oldClass]
|
||||
|
||||
if (info.OnSet) then
|
||||
info:OnSet(client)
|
||||
end
|
||||
|
||||
if (info2 and info2.OnLeave) then
|
||||
info2:OnLeave(client)
|
||||
end
|
||||
|
||||
net.Start("ixClassUpdate")
|
||||
net.WriteEntity(client)
|
||||
net.Broadcast()
|
||||
end
|
||||
end
|
||||
636
gamemodes/helix/gamemode/core/libs/sh_command.lua
Normal file
636
gamemodes/helix/gamemode/core/libs/sh_command.lua
Normal file
@@ -0,0 +1,636 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Registration, parsing, and handling of commands.
|
||||
|
||||
Commands can be ran through the chat with slash commands or they can be executed through the console. Commands can be manually
|
||||
restricted to certain usergroups using a [CAMI](https://github.com/glua/CAMI)-compliant admin mod.
|
||||
]]
|
||||
-- @module ix.command
|
||||
|
||||
--- When registering commands with `ix.command.Add`, you'll need to pass in a valid command structure. This is simply a table
|
||||
-- with various fields defined to describe the functionality of the command.
|
||||
-- @realm shared
|
||||
-- @table CommandStructure
|
||||
-- @field[type=function] OnRun This function is called when the command has passed all the checks and can execute. The first two
|
||||
-- arguments will be the running command table and the calling player. If the arguments field has been specified, the arguments
|
||||
-- will be passed as regular function parameters rather than in a table.
|
||||
--
|
||||
-- When the arguments field is defined: `OnRun(self, client, target, length, message)`
|
||||
--
|
||||
-- When the arguments field is NOT defined: `OnRun(self, client, arguments)`
|
||||
-- @field[type=string,opt="@noDesc"] description The help text that appears when the user types in the command. If the string is
|
||||
-- prefixed with `"@"`, it will use a language phrase.
|
||||
-- @field[type=table,opt=nil] argumentNames An array of strings corresponding to each argument of the command. This ignores the
|
||||
-- name that's specified in the `OnRun` function arguments and allows you to use any string to change the text that displays
|
||||
-- in the command's syntax help. When using this field, make sure that the amount is equal to the amount of arguments, as such:
|
||||
-- COMMAND.arguments = {ix.type.character, ix.type.number}
|
||||
-- COMMAND.argumentNames = {"target char", "cash (1-1000)"}
|
||||
-- @field[type=table,opt] arguments If this field is defined, then additional checks will be performed to ensure that the
|
||||
-- arguments given to the command are valid. This removes extra boilerplate code since all the passed arguments are guaranteed
|
||||
-- to be valid. See `CommandArgumentsStructure` for more information.
|
||||
-- @field[type=boolean,opt=false] adminOnly Provides an additional check to see if the user is an admin before running.
|
||||
-- @field[type=boolean,opt=false] superAdminOnly Provides an additional check to see if the user is a superadmin before running.
|
||||
-- @field[type=string,opt=nil] privilege Manually specify a privilege name for this command. It will always be prefixed with
|
||||
-- `"Helix - "`. This is used in the case that you want to group commands under the same privilege, or use a privilege that
|
||||
-- you've already defined (i.e grouping `/CharBan` and `/CharUnban` into the `Helix - Ban Character` privilege).
|
||||
-- @field[type=function,opt=nil] OnCheckAccess This callback checks whether or not the player is allowed to run the command.
|
||||
-- This callback should **NOT** be used in conjunction with `adminOnly` or `superAdminOnly`, as populating those
|
||||
-- fields create a custom a `OnCheckAccess` callback for you internally. This is used in cases where you want more fine-grained
|
||||
-- access control for your command.
|
||||
--
|
||||
-- Keep in mind that this is a **SHARED** callback; the command will not show up the client if the callback returns `false`.
|
||||
|
||||
--- Rather than checking the validity for arguments in your command's `OnRun` function, you can have Helix do it for you to
|
||||
-- reduce the amount of boilerplate code that needs to be written. This can be done by populating the `arguments` field.
|
||||
--
|
||||
-- When using the `arguments` field in your command, you are specifying specific types that you expect to receive when the
|
||||
-- command is ran successfully. This means that before `OnRun` is called, the arguments passed to the command from a user will
|
||||
-- be verified to be valid. Each argument is an `ix.type` entry that specifies the expected type for that argument. Optional
|
||||
-- arguments can be specified by using a bitwise OR with the special `ix.type.optional` type. When specified as optional, the
|
||||
-- argument can be `nil` if the user has not entered anything for that argument - otherwise it will be valid.
|
||||
--
|
||||
-- Note that optional arguments must always be at the end of a list of arguments - or rather, they must not follow a required
|
||||
-- argument. The `syntax` field will be automatically populated when using strict arguments, which means you shouldn't fill out
|
||||
-- the `syntax` field yourself. The arguments you specify will have the same names as the arguments in your OnRun function.
|
||||
--
|
||||
-- Consider this example command:
|
||||
-- ix.command.Add("CharSlap", {
|
||||
-- description = "Slaps a character with a large trout.",
|
||||
-- adminOnly = true,
|
||||
-- arguments = {
|
||||
-- ix.type.character,
|
||||
-- bit.bor(ix.type.number, ix.type.optional)
|
||||
-- },
|
||||
-- OnRun = function(self, client, target, damage)
|
||||
-- -- WHAM!
|
||||
-- end
|
||||
-- })
|
||||
-- Here, we've specified the first argument called `target` to be of type `character`, and the second argument called `damage`
|
||||
-- to be of type `number`. The `damage` argument is optional, meaning that the command will still run if the user has not
|
||||
-- specified any value for the damage. In this case, we'll need to check if it was specified by doing a simple
|
||||
-- `if (damage) then`. The syntax field will be automatically populated with the value `"<target: character> [damage: number]"`.
|
||||
-- @realm shared
|
||||
-- @table CommandArgumentsStructure
|
||||
|
||||
ix.command = ix.command or {}
|
||||
ix.command.list = ix.command.list or {}
|
||||
|
||||
local COMMAND_PREFIX = "/"
|
||||
|
||||
local function ArgumentCheckStub(command, client, given)
|
||||
local arguments = command.arguments
|
||||
local result = {}
|
||||
|
||||
for i = 1, #arguments do
|
||||
local bOptional = bit.band(arguments[i], ix.type.optional) == ix.type.optional
|
||||
local argType = bOptional and bit.bxor(arguments[i], ix.type.optional) or arguments[i]
|
||||
local argument = given[i]
|
||||
|
||||
if (!argument and !bOptional) then
|
||||
return L("invalidArg", client, i)
|
||||
end
|
||||
|
||||
if (argType == ix.type.string) then
|
||||
if (!argument and bOptional) then
|
||||
result[#result + 1] = nil
|
||||
else
|
||||
result[#result + 1] = tostring(argument)
|
||||
end
|
||||
elseif (argType == ix.type.text) then
|
||||
result[#result + 1] = table.concat(given, " ", i) or ""
|
||||
break
|
||||
elseif (argType == ix.type.number) then
|
||||
local value = tonumber(argument)
|
||||
|
||||
if (!bOptional and !value) then
|
||||
return L("invalidArg", client, i)
|
||||
end
|
||||
|
||||
result[#result + 1] = value
|
||||
elseif (argType == ix.type.player or argType == ix.type.character) then
|
||||
local bPlayer = argType == ix.type.player
|
||||
local value
|
||||
|
||||
if argument == "^" then
|
||||
value = client
|
||||
else
|
||||
value = ix.util.FindPlayer(argument)
|
||||
end
|
||||
|
||||
-- FindPlayer emits feedback for us
|
||||
if (!value and !bOptional) then
|
||||
return L(bPlayer and "plyNoExist" or "charNoExist", client)
|
||||
end
|
||||
|
||||
-- check for the character if we're using the character type
|
||||
if (!bPlayer) then
|
||||
local character = value:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return L("charNoExist", client)
|
||||
end
|
||||
|
||||
value = character
|
||||
end
|
||||
|
||||
result[#result + 1] = value
|
||||
elseif (argType == ix.type.steamid) then
|
||||
local value = argument:match("STEAM_(%d+):(%d+):(%d+)")
|
||||
|
||||
if (!value and bOptional) then
|
||||
return L("invalidArg", client, i)
|
||||
end
|
||||
|
||||
result[#result + 1] = value
|
||||
elseif (argType == ix.type.bool) then
|
||||
if (argument == nil and bOptional) then
|
||||
result[#result + 1] = nil
|
||||
else
|
||||
result[#result + 1] = tobool(argument)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Creates a new command.
|
||||
-- @realm shared
|
||||
-- @string command Name of the command (recommended in UpperCamelCase)
|
||||
-- @tparam CommandStructure data Data describing the command
|
||||
-- @see CommandStructure
|
||||
-- @see CommandArgumentsStructure
|
||||
function ix.command.Add(command, data)
|
||||
data.name = string.gsub(command, "%s", "")
|
||||
data.description = data.description or ""
|
||||
|
||||
command = command:lower()
|
||||
data.uniqueID = command
|
||||
|
||||
-- Why bother adding a command if it doesn't do anything.
|
||||
if (!data.OnRun) then
|
||||
return ErrorNoHalt("Command '"..command.."' does not have a callback, not adding!\n")
|
||||
end
|
||||
|
||||
-- Add a function to get the description that can be overridden.
|
||||
if (!data.GetDescription) then
|
||||
-- Check if the description is using a language string.
|
||||
if (data.description:sub(1, 1) == "@") then
|
||||
function data:GetDescription()
|
||||
return L(self.description:sub(2))
|
||||
end
|
||||
else
|
||||
-- Otherwise just return the raw description.
|
||||
function data:GetDescription()
|
||||
return self.description
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- OnCheckAccess by default will rely on CAMI for access information with adminOnly/superAdminOnly being fallbacks
|
||||
if (!data.OnCheckAccess) then
|
||||
if (data.group) then
|
||||
ErrorNoHalt("Command '" .. data.name .. "' tried to use the deprecated field 'group'!\n")
|
||||
return
|
||||
end
|
||||
|
||||
local privilege = "Helix - " .. (isstring(data.privilege) and data.privilege or data.name)
|
||||
|
||||
-- we could be using a previously-defined privilege
|
||||
if (!CAMI.GetPrivilege(privilege)) then
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = privilege,
|
||||
MinAccess = data.superAdminOnly and "superadmin" or (data.adminOnly and "admin" or "user"),
|
||||
Description = data.description
|
||||
})
|
||||
end
|
||||
|
||||
function data:OnCheckAccess(client)
|
||||
local bHasAccess, _ = CAMI.PlayerHasAccess(client, privilege, nil)
|
||||
return bHasAccess
|
||||
end
|
||||
end
|
||||
|
||||
-- if we have an arguments table, then we're using the new command format
|
||||
if (data.arguments) then
|
||||
local bFirst = true
|
||||
local bLastOptional = false
|
||||
local bHasArgumentNames = istable(data.argumentNames)
|
||||
|
||||
data.syntax = "" -- @todo deprecate this in favour of argumentNames
|
||||
data.argumentNames = bHasArgumentNames and data.argumentNames or {}
|
||||
|
||||
-- if one argument is supplied by itself, put it into a table
|
||||
if (!istable(data.arguments)) then
|
||||
data.arguments = {data.arguments}
|
||||
end
|
||||
|
||||
if (bHasArgumentNames and #data.argumentNames != #data.arguments) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' doesn't have argument names that correspond to each argument\n", command
|
||||
))
|
||||
end
|
||||
|
||||
-- check the arguments table to see if its entries are valid
|
||||
for i = 1, #data.arguments do
|
||||
local argument = data.arguments[i]
|
||||
local argumentName = debug.getlocal(data.OnRun, 2 + i)
|
||||
|
||||
if (argument == ix.type.optional) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an optional argument for #%d without specifying type\n", command, i
|
||||
))
|
||||
elseif (!isnumber(argument)) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an invalid type for argument #%d\n", command, i
|
||||
))
|
||||
elseif (argument == ix.type.array or bit.band(argument, ix.type.array) > 0) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an unsupported type 'array' for argument #%d\n", command, i
|
||||
))
|
||||
end
|
||||
|
||||
local bOptional = bit.band(argument, ix.type.optional) > 0
|
||||
argument = bOptional and bit.bxor(argument, ix.type.optional) or argument
|
||||
|
||||
if (!ix.type[argument]) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an invalid type for argument #%d\n", command, i
|
||||
))
|
||||
elseif (!isstring(argumentName)) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' is missing function argument for command argument #%d\n", command, i
|
||||
))
|
||||
elseif (argument == ix.type.text and i != #data.arguments) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use a text argument outside of the last argument\n", command
|
||||
))
|
||||
elseif (!bOptional and bLastOptional) then
|
||||
return ErrorNoHalt(string.format(
|
||||
"Command '%s' tried to use an required argument after an optional one\n", command
|
||||
))
|
||||
end
|
||||
|
||||
-- text is always optional and will return an empty string if nothing is specified, rather than nil
|
||||
if (argument == ix.type.text) then
|
||||
data.arguments[i] = bit.bor(ix.type.text, ix.type.optional)
|
||||
bOptional = true
|
||||
end
|
||||
|
||||
if (!bHasArgumentNames) then
|
||||
data.argumentNames[i] = argumentName
|
||||
end
|
||||
|
||||
data.syntax = data.syntax .. (bFirst and "" or " ") ..
|
||||
string.format((bOptional and "[%s: %s]" or "<%s: %s>"), argumentName, ix.type[argument])
|
||||
|
||||
bFirst = false
|
||||
bLastOptional = bOptional
|
||||
end
|
||||
|
||||
if (data.syntax:utf8len() == 0) then
|
||||
data.syntax = "<none>"
|
||||
end
|
||||
else
|
||||
data.syntax = data.syntax or "<none>"
|
||||
end
|
||||
|
||||
-- Add the command to the list of commands.
|
||||
local alias = data.alias
|
||||
|
||||
if (alias) then
|
||||
if (istable(alias)) then
|
||||
for _, v in ipairs(alias) do
|
||||
ix.command.list[v:lower()] = data
|
||||
end
|
||||
elseif (isstring(alias)) then
|
||||
ix.command.list[alias:lower()] = data
|
||||
end
|
||||
end
|
||||
|
||||
ix.command.list[command] = data
|
||||
end
|
||||
|
||||
--- Returns true if a player is allowed to run a certain command.
|
||||
-- @realm shared
|
||||
-- @player client Player to check access for
|
||||
-- @string command Name of the command to check access for
|
||||
-- @treturn bool Whether or not the player is allowed to run the command
|
||||
function ix.command.HasAccess(client, command)
|
||||
command = ix.command.list[command:lower()]
|
||||
|
||||
if (command) then
|
||||
if (command.OnCheckAccess) then
|
||||
return command:OnCheckAccess(client)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns a table of arguments from a given string.
|
||||
-- Words separated by spaces will be considered one argument. To have an argument containing multiple words, they must be
|
||||
-- contained within quotation marks.
|
||||
-- @realm shared
|
||||
-- @string text String to extract arguments from
|
||||
-- @treturn table Arguments extracted from string
|
||||
-- @usage PrintTable(ix.command.ExtractArgs("these are \"some arguments\""))
|
||||
-- > 1 = these
|
||||
-- > 2 = are
|
||||
-- > 3 = some arguments
|
||||
function ix.command.ExtractArgs(text)
|
||||
local skip = 0
|
||||
local arguments = {}
|
||||
local curString = ""
|
||||
|
||||
for i = 1, text:utf8len() do
|
||||
if (i <= skip) then continue end
|
||||
|
||||
local c = text:utf8sub(i, i)
|
||||
|
||||
if (c == "\"") then
|
||||
local match = text:utf8sub(i):match("%b\"\"")
|
||||
|
||||
if (match) then
|
||||
curString = ""
|
||||
skip = i + match:utf8len()
|
||||
arguments[#arguments + 1] = match:utf8sub(2, -2)
|
||||
else
|
||||
curString = curString..c
|
||||
end
|
||||
elseif (c == " " and curString != "") then
|
||||
arguments[#arguments + 1] = curString
|
||||
curString = ""
|
||||
else
|
||||
if (c == " " and curString == "") then
|
||||
continue
|
||||
end
|
||||
|
||||
curString = curString..c
|
||||
end
|
||||
end
|
||||
|
||||
if (curString != "") then
|
||||
arguments[#arguments + 1] = curString
|
||||
end
|
||||
|
||||
return arguments
|
||||
end
|
||||
|
||||
--- Returns an array of potential commands by unique id.
|
||||
-- When bSorted is true, the commands will be sorted by name. When bReorganize is true, it will move any exact match to the top
|
||||
-- of the array. When bRemoveDupes is true, it will remove any commands that have the same NAME.
|
||||
-- @realm shared
|
||||
-- @string identifier Search query
|
||||
-- @bool[opt=false] bSorted Whether or not to sort the commands by name
|
||||
-- @bool[opt=false] bReorganize Whether or not any exact match will be moved to the top of the array
|
||||
-- @bool[opt=false] bRemoveDupes Whether or not to remove any commands that have the same name
|
||||
-- @treturn table Array of command tables whose name partially or completely matches the search query
|
||||
function ix.command.FindAll(identifier, bSorted, bReorganize, bRemoveDupes)
|
||||
local result = {}
|
||||
local iterator = bSorted and SortedPairs or pairs
|
||||
local fullMatch
|
||||
|
||||
identifier = identifier:lower()
|
||||
|
||||
if (identifier == "/") then
|
||||
-- we don't simply copy because we need numeric indices
|
||||
for _, v in iterator(ix.command.list) do
|
||||
result[#result + 1] = v
|
||||
end
|
||||
|
||||
return result
|
||||
elseif (identifier:utf8sub(1, 1) == "/") then
|
||||
identifier = identifier:utf8sub(2)
|
||||
end
|
||||
|
||||
for k, v in iterator(ix.command.list) do
|
||||
if (k:match(identifier)) then
|
||||
local index = #result + 1
|
||||
result[index] = v
|
||||
|
||||
if (k == identifier) then
|
||||
fullMatch = index
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (bReorganize and fullMatch and fullMatch != 1) then
|
||||
result[1], result[fullMatch] = result[fullMatch], result[1]
|
||||
end
|
||||
|
||||
if (bRemoveDupes) then
|
||||
local commandNames = {}
|
||||
|
||||
-- using pairs intead of ipairs because we might remove from array
|
||||
for k, v in pairs(result) do
|
||||
if (commandNames[v.name]) then
|
||||
table.remove(result, k)
|
||||
end
|
||||
|
||||
commandNames[v.name] = true
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixCommand")
|
||||
|
||||
--- Attempts to find a player by an identifier. If unsuccessful, a notice will be displayed to the specified player. The
|
||||
-- search criteria is derived from `ix.util.FindPlayer`.
|
||||
-- @realm server
|
||||
-- @player client Player to give a notification to if the player could not be found
|
||||
-- @string name Search query
|
||||
-- @treturn[1] player Player that matches the given search query
|
||||
-- @treturn[2] nil If a player could not be found
|
||||
-- @see ix.util.FindPlayer
|
||||
function ix.command.FindPlayer(client, name)
|
||||
local target = isstring(name) and ix.util.FindPlayer(name) or NULL
|
||||
|
||||
if (IsValid(target)) then
|
||||
return target
|
||||
else
|
||||
client:NotifyLocalized("plyNoExist")
|
||||
end
|
||||
end
|
||||
|
||||
--- Forces a player to execute a command by name.
|
||||
-- @realm server
|
||||
-- @player client Player who is executing the command
|
||||
-- @string command Full name of the command to be executed. This string gets lowered, but it's good practice to stick with
|
||||
-- the exact name of the command
|
||||
-- @tab arguments Array of arguments to be passed to the command
|
||||
-- @usage ix.command.Run(player.GetByID(1), "Roll", {10})
|
||||
function ix.command.Run(client, command, arguments)
|
||||
if ((client.ixCommandCooldown or 0) > RealTime()) then
|
||||
return
|
||||
end
|
||||
|
||||
command = ix.command.list[tostring(command):lower()]
|
||||
|
||||
if (!command) then
|
||||
return
|
||||
end
|
||||
|
||||
-- we throw it into a table since arguments get unpacked and only
|
||||
-- the arguments table gets passed in by default
|
||||
local argumentsTable = arguments
|
||||
arguments = {argumentsTable}
|
||||
|
||||
-- if feedback is non-nil, we can assume that the command failed
|
||||
-- and is a phrase string
|
||||
local feedback
|
||||
|
||||
-- check for group access
|
||||
if (command.OnCheckAccess) then
|
||||
local bSuccess, phrase = command:OnCheckAccess(client)
|
||||
feedback = !bSuccess and L(phrase and phrase or "noPerm", client) or nil
|
||||
end
|
||||
|
||||
-- check for strict arguments
|
||||
if (!feedback and command.arguments) then
|
||||
arguments = ArgumentCheckStub(command, client, argumentsTable)
|
||||
|
||||
if (isstring(arguments)) then
|
||||
feedback = arguments
|
||||
end
|
||||
end
|
||||
|
||||
-- run the command if all the checks passed
|
||||
if (!feedback) then
|
||||
local results = {command:OnRun(client, unpack(arguments))}
|
||||
local phrase = results[1]
|
||||
|
||||
-- check to see if the command has returned a phrase string and display it
|
||||
if (isstring(phrase)) then
|
||||
if (IsValid(client)) then
|
||||
if (phrase:sub(1, 1) == "@") then
|
||||
client:NotifyLocalized(phrase:sub(2), unpack(results, 2))
|
||||
else
|
||||
client:Notify(phrase)
|
||||
end
|
||||
else
|
||||
-- print message since we're running from the server console
|
||||
print(phrase)
|
||||
end
|
||||
end
|
||||
|
||||
client.ixCommandCooldown = RealTime() + 0.5
|
||||
|
||||
if (IsValid(client)) then
|
||||
ix.log.Add(client, "command", COMMAND_PREFIX .. command.name, argumentsTable and table.concat(argumentsTable, " "))
|
||||
end
|
||||
else
|
||||
client:Notify(feedback)
|
||||
end
|
||||
end
|
||||
|
||||
--- Parses a chat string and runs the command if one is found. Specifically, it checks for commands in a string with the
|
||||
-- format `/CommandName some arguments`
|
||||
-- @realm server
|
||||
-- @player client Player who is executing the command
|
||||
-- @string text Input string to search for the command format
|
||||
-- @string[opt] realCommand Specific command to check for. If this is specified, it will not try to run any command that's
|
||||
-- found at the beginning - only if it matches `realCommand`
|
||||
-- @tab[opt] arguments Array of arguments to pass to the command. If not specified, it will try to extract it from the
|
||||
-- string specified in `text` using `ix.command.ExtractArgs`
|
||||
-- @treturn bool Whether or not a command has been found
|
||||
-- @usage ix.command.Parse(player.GetByID(1), "/roll 10")
|
||||
function ix.command.Parse(client, text, realCommand, arguments)
|
||||
if (realCommand or text:utf8sub(1, 1) == COMMAND_PREFIX) then
|
||||
-- See if the string contains a command.
|
||||
local match = realCommand or text:utf8lower():match(COMMAND_PREFIX.."([_%w]+)")
|
||||
|
||||
-- is it unicode text?
|
||||
if (!match) then
|
||||
local post = string.Explode(" ", text)
|
||||
local len = string.len(post[1])
|
||||
|
||||
match = post[1]:utf8sub(2, len)
|
||||
end
|
||||
|
||||
match = match:utf8lower()
|
||||
|
||||
local command = ix.command.list[match]
|
||||
-- We have a valid, registered command.
|
||||
if (command) then
|
||||
-- Get the arguments like a console command.
|
||||
if (!arguments) then
|
||||
arguments = ix.command.ExtractArgs(text:utf8sub(match:utf8len() + 3))
|
||||
end
|
||||
|
||||
-- Runs the actual command.
|
||||
ix.command.Run(client, match, arguments)
|
||||
else
|
||||
if (IsValid(client)) then
|
||||
client:NotifyLocalized("cmdNoExist")
|
||||
else
|
||||
print("Sorry, that command does not exist.")
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
concommand.Add("ix", function(client, _, arguments)
|
||||
local command = arguments[1]
|
||||
table.remove(arguments, 1)
|
||||
|
||||
ix.command.Parse(client, nil, command or "", arguments)
|
||||
end)
|
||||
|
||||
net.Receive("ixCommand", function(length, client)
|
||||
if ((client.ixNextCmd or 0) < CurTime()) then
|
||||
local command = net.ReadString()
|
||||
local indices = net.ReadUInt(4)
|
||||
local arguments = {}
|
||||
|
||||
for _ = 1, indices do
|
||||
local value = net.ReadType()
|
||||
|
||||
if (isstring(value) or isnumber(value)) then
|
||||
arguments[#arguments + 1] = tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
ix.command.Parse(client, nil, command, arguments)
|
||||
client.ixNextCmd = CurTime() + 0.2
|
||||
end
|
||||
end)
|
||||
else
|
||||
--- Request the server to run a command. This mimics similar functionality to the client typing `/CommandName` in the chatbox.
|
||||
-- @realm client
|
||||
-- @string command Unique ID of the command
|
||||
-- @param ... Arguments to pass to the command
|
||||
-- @usage ix.command.Send("roll", 10)
|
||||
function ix.command.Send(command, ...)
|
||||
local arguments = {...}
|
||||
|
||||
net.Start("ixCommand")
|
||||
net.WriteString(command)
|
||||
net.WriteUInt(#arguments, 4)
|
||||
|
||||
for _, v in ipairs(arguments) do
|
||||
net.WriteType(v)
|
||||
end
|
||||
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
166
gamemodes/helix/gamemode/core/libs/sh_compnettable.lua
Normal file
166
gamemodes/helix/gamemode/core/libs/sh_compnettable.lua
Normal file
@@ -0,0 +1,166 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
ix.compnettable = ix.compnettable or {}
|
||||
-- size of chunks
|
||||
ix.compnettable.chunkSize = 60000
|
||||
|
||||
do
|
||||
if (CLIENT) then
|
||||
ix.compnettable.doExpoBackOff = true
|
||||
-- expo backoff and jitter vars
|
||||
ix.compnettable.maxBackOffTime = 20 -- tenth of a second
|
||||
ix.compnettable.baseBackOffTime = 1 -- tenth of a second
|
||||
ix.compnettable.attemptsInWindow = 0
|
||||
ix.compnettable.windowSize = 1 -- seconds
|
||||
ix.compnettable.lastRequest = CurTime()
|
||||
else
|
||||
-- don't do backoffs on the server (that'd break a lot of shit)
|
||||
ix.compnettable.doExpobackOff = false
|
||||
end
|
||||
end
|
||||
|
||||
-- writes a compressed and chunked table with the following mechanism:
|
||||
--[[
|
||||
1) sends an int of the number of chunks to expect
|
||||
2) writes the chunks as compressed strings
|
||||
3) returns number of chunks to caller
|
||||
|
||||
IMPORTANT
|
||||
THIS MUST BE USED IN THE CORRECT CONTEXT. EX:
|
||||
|
||||
net.Send("YourNetHook", function()
|
||||
ix.compnettable:Write(myTable)
|
||||
end)
|
||||
net.Receive("YourNetHook", function()
|
||||
local yourTable = ix.compnettable:Read()
|
||||
end)
|
||||
|
||||
OTHERWISE IT WILL BREAK YOUR SHIT
|
||||
]]
|
||||
function ix.compnettable:Write(tbl)
|
||||
if (!istable(tbl)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
local str = util.TableToJSON(tbl)
|
||||
local basestrlen = string.len(str)
|
||||
|
||||
if (!str or basestrlen < 1) then
|
||||
error("Provided table could not be converted to JSON!")
|
||||
end
|
||||
|
||||
local nchunks = math.ceil(basestrlen / self.chunkSize)
|
||||
net.WriteInt(nchunks, 17)
|
||||
|
||||
for i=0, nchunks + 1, 1 do
|
||||
local stringslice = string.sub(
|
||||
str,
|
||||
i * self.chunkSize,
|
||||
(i + 1) *self.chunkSize
|
||||
)
|
||||
local chunk = util.Compress(stringslice)
|
||||
|
||||
if (!chunk) then
|
||||
error(
|
||||
"Chunk "
|
||||
..tostring(i)
|
||||
.." cannot be compressed! Attempted to compress: "
|
||||
..stringslice
|
||||
)
|
||||
end
|
||||
|
||||
net.WriteInt(#chunk, 17)
|
||||
net.WriteData(chunk, #chunk)
|
||||
end
|
||||
|
||||
return nchunks
|
||||
end
|
||||
|
||||
|
||||
-- attempts to read a table compressed by ix.compnettable.Write()
|
||||
--[[ IMPORTANT
|
||||
THIS MUST BE USED IN THE CORRECT CONTEXT. EX:
|
||||
|
||||
net.Send("YourNetHook", function()
|
||||
ix.compnettable:Write(myTable)
|
||||
end)
|
||||
net.Receive("YourNetHook", function()
|
||||
local yourTable = ix.compnettable:Read()
|
||||
end)
|
||||
|
||||
OTHERWISE IT WILL BREAK YOUR SHIT
|
||||
]]
|
||||
function ix.compnettable:Read()
|
||||
local nchunks = net.ReadInt(17)
|
||||
if (nchunks < 1) then
|
||||
error("No chunks to read!")
|
||||
end
|
||||
|
||||
local fullstr = ""
|
||||
for i=0, nchunks + 1, 1 do
|
||||
local chunksize = net.ReadInt(17)
|
||||
local strcomp = net.ReadData(chunksize)
|
||||
if (#strcomp == 1 and strcomp[0] == 0) then
|
||||
error("Expected substring at idx ("..tostring(i)..") out of ("..tostring(nchunks)..") is empty or nil!")
|
||||
end
|
||||
|
||||
local strchunk = util.Decompress(strcomp)
|
||||
if (!strchunk) then
|
||||
error("Substring ("..tostring(i)..") out of ("..tostring(nchunks)..") failed to decompress!")
|
||||
end
|
||||
fullstr = fullstr..strchunk
|
||||
end
|
||||
|
||||
return util.JSONToTable(fullstr)
|
||||
end
|
||||
|
||||
-- reads with a brief backoff to help with overflow issues
|
||||
-- https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
|
||||
-- ^^ a cheap (and wildly simplified) version of this:
|
||||
function ix.compnettable:ReadWithBackoff()
|
||||
if (CLIENT) then
|
||||
local backOffTime = 0
|
||||
local reqStart = SysTime()
|
||||
|
||||
if (self.doExpoBackOff) then
|
||||
if ((reqStart - self.lastRequest) < self.windowSize) then
|
||||
self.attemptsInWindow = self.attemptsInWindow + 1
|
||||
local minBackOffTime = math.min(
|
||||
math.pow(
|
||||
self.baseBackOffTime,
|
||||
self.attemptsInWindow
|
||||
),
|
||||
self.maxBackOffTime
|
||||
)
|
||||
local maxBackOffTime = minBackOffTime + (self.baseBackOffTime * 10)
|
||||
backOffTime = math.Rand(minBackOffTime, maxBackOffTime) / 10
|
||||
else
|
||||
self.attemptsInWindow = 0
|
||||
end
|
||||
end
|
||||
|
||||
if (backOffTime > 0) then
|
||||
print("Backing off for "..tostring(backOffTime).." seconds...")
|
||||
-- ^^ leave this in for now just in case ;)
|
||||
end
|
||||
|
||||
local sec = tonumber(SysTime() + backOffTime);
|
||||
while (SysTime() < sec) do
|
||||
-- wait for backOffTime in seconds...
|
||||
-- NOTE: This might cause some freezing and things
|
||||
-- BUT A FREEZE IS BETTER THAN AN OVERFLOW!!!!!!!!
|
||||
end
|
||||
|
||||
self.lastRequest = reqStart
|
||||
end
|
||||
|
||||
return self:Read()
|
||||
end
|
||||
121
gamemodes/helix/gamemode/core/libs/sh_currency.lua
Normal file
121
gamemodes/helix/gamemode/core/libs/sh_currency.lua
Normal file
@@ -0,0 +1,121 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--- A library representing the server's currency system.
|
||||
-- @module ix.currency
|
||||
|
||||
ix.currency = ix.currency or {}
|
||||
ix.currency.symbol = ix.currency.symbol or "$"
|
||||
ix.currency.singular = ix.currency.singular or "dollar"
|
||||
ix.currency.plural = ix.currency.plural or "dollars"
|
||||
ix.currency.model = ix.currency.model or "models/props_lab/box01a.mdl"
|
||||
|
||||
--- Sets the currency type.
|
||||
-- @realm shared
|
||||
-- @string symbol The symbol of the currency.
|
||||
-- @string singular The name of the currency in it's singular form.
|
||||
-- @string plural The name of the currency in it's plural form.
|
||||
-- @string model The model of the currency entity.
|
||||
function ix.currency.Set(symbol, singular, plural, model)
|
||||
ix.currency.symbol = symbol
|
||||
ix.currency.singular = singular
|
||||
ix.currency.plural = plural
|
||||
ix.currency.model = model
|
||||
end
|
||||
|
||||
--- Returns a formatted string according to the current currency.
|
||||
-- @realm shared
|
||||
-- @number amount The amount of cash being formatted.
|
||||
-- @treturn string The formatted string.
|
||||
function ix.currency.Get(amount)
|
||||
if (amount == 1) then
|
||||
return ix.currency.symbol.."1 "..ix.currency.singular
|
||||
else
|
||||
return ix.currency.symbol..amount.." "..ix.currency.plural
|
||||
end
|
||||
end
|
||||
|
||||
--- Spawns an amount of cash at a specific location on the map.
|
||||
-- @realm shared
|
||||
-- @vector pos The position of the money to be spawned.
|
||||
-- @number amount The amount of cash being spawned.
|
||||
-- @angle[opt=angle_zero] angle The angle of the entity being spawned.
|
||||
-- @treturn entity The spawned money entity.
|
||||
function ix.currency.Spawn(pos, amount, angle)
|
||||
if (!amount or amount < 0) then
|
||||
print("[Helix] Impossible de créer une entité monétaire : Montant invalide de la monnaie")
|
||||
return
|
||||
end
|
||||
|
||||
local money = ents.Create("ix_money")
|
||||
money:Spawn()
|
||||
|
||||
if (IsValid(pos) and pos:IsPlayer()) then
|
||||
pos = pos:GetItemDropPos(money)
|
||||
elseif (!isvector(pos)) then
|
||||
print("[Helix] Impossible de créer une entité monétaire : Position invalide")
|
||||
|
||||
money:Remove()
|
||||
return
|
||||
end
|
||||
|
||||
money:SetPos(pos)
|
||||
-- double check for negative.
|
||||
money:SetAmount(math.Round(math.abs(amount)))
|
||||
money:SetAngles(angle or angle_zero)
|
||||
money:Activate()
|
||||
|
||||
return money
|
||||
end
|
||||
|
||||
function GM:OnPickupMoney(client, moneyEntity)
|
||||
if (IsValid(moneyEntity)) then
|
||||
local amount = moneyEntity:GetAmount()
|
||||
|
||||
client:GetCharacter():GiveMoney(amount)
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local character = ix.meta.character
|
||||
|
||||
function character:HasMoney(amount)
|
||||
if (amount < 0) then
|
||||
print("Chèque négatif reçu.")
|
||||
end
|
||||
|
||||
return self:GetMoney() >= amount
|
||||
end
|
||||
|
||||
function character:GiveMoney(amount, bNoLog)
|
||||
amount = math.abs(amount)
|
||||
|
||||
if (!bNoLog) then
|
||||
ix.log.Add(self:GetPlayer(), "money", amount)
|
||||
end
|
||||
|
||||
self:SetMoney(self:GetMoney() + amount)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function character:TakeMoney(amount, bNoLog)
|
||||
amount = math.abs(amount)
|
||||
|
||||
if (!bNoLog) then
|
||||
ix.log.Add(self:GetPlayer(), "money", -amount)
|
||||
end
|
||||
|
||||
self:SetMoney(self:GetMoney() - amount)
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
156
gamemodes/helix/gamemode/core/libs/sh_date.lua
Normal file
156
gamemodes/helix/gamemode/core/libs/sh_date.lua
Normal file
@@ -0,0 +1,156 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Persistent date and time handling.
|
||||
|
||||
All of Lua's time functions are dependent on the Unix epoch, which means we can't have dates that go further than 1970. This
|
||||
library remedies this problem. Time/date is represented by a `date` object that is queried, instead of relying on the seconds
|
||||
since the epoch.
|
||||
|
||||
## Futher documentation
|
||||
This library makes use of a third-party date library found at https://github.com/Tieske/date - you can find all documentation
|
||||
regarding the `date` object and its methods there.
|
||||
]]
|
||||
-- @module ix.date
|
||||
|
||||
ix.date = ix.date or {}
|
||||
ix.date.lib = ix.date.lib or include("thirdparty/sh_date.lua")
|
||||
ix.date.timeScale = ix.date.timeScale or ix.config.Get("secondsPerMinute", 60) -- seconds per minute
|
||||
ix.date.current = ix.date.current or ix.date.lib() -- current in-game date/time
|
||||
ix.date.start = ix.date.start or CurTime() -- arbitrary start time for calculating date/time offset
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixDateSync")
|
||||
|
||||
--- Loads the date from disk.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
function ix.date.Initialize()
|
||||
local currentDate = ix.data.Get("date", nil, false, true)
|
||||
|
||||
-- construct new starting date if we don't have it saved already
|
||||
if (!currentDate) then
|
||||
currentDate = {
|
||||
year = ix.config.Get("year"),
|
||||
month = ix.config.Get("month"),
|
||||
day = ix.config.Get("day"),
|
||||
hour = tonumber(os.date("%H")) or 0,
|
||||
min = tonumber(os.date("%M")) or 0,
|
||||
sec = tonumber(os.date("%S")) or 0
|
||||
}
|
||||
|
||||
currentDate = ix.date.lib.serialize(ix.date.lib(currentDate))
|
||||
ix.data.Set("date", currentDate, false, true)
|
||||
end
|
||||
|
||||
ix.date.timeScale = ix.config.Get("secondsPerMinute", 60)
|
||||
ix.date.current = ix.date.lib.construct(currentDate)
|
||||
end
|
||||
|
||||
--- Updates the internal in-game date/time representation and resets the offset.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
function ix.date.ResolveOffset()
|
||||
ix.date.current = ix.date.Get()
|
||||
ix.date.start = CurTime()
|
||||
end
|
||||
|
||||
--- Updates the time scale of the in-game date/time. The time scale is given in seconds per minute (i.e how many real life
|
||||
-- seconds it takes for an in-game minute to pass). You should avoid using this function and use the in-game config menu to
|
||||
-- change the time scale instead.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @number secondsPerMinute New time scale
|
||||
function ix.date.UpdateTimescale(secondsPerMinute)
|
||||
ix.date.ResolveOffset()
|
||||
ix.date.timeScale = secondsPerMinute
|
||||
end
|
||||
|
||||
--- Sends the current date to a player. This is done automatically when the player joins the server.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player[opt=nil] client Player to send the date to, or `nil` to send to everyone
|
||||
function ix.date.Send(client)
|
||||
net.Start("ixDateSync")
|
||||
|
||||
net.WriteFloat(ix.date.timeScale)
|
||||
net.WriteTable(ix.date.current)
|
||||
net.WriteFloat(ix.date.start)
|
||||
|
||||
if (client) then
|
||||
net.Send(client)
|
||||
else
|
||||
net.Broadcast()
|
||||
end
|
||||
end
|
||||
|
||||
--- Saves the current in-game date to disk.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
function ix.date.Save()
|
||||
ix.date.bSaving = true
|
||||
|
||||
ix.date.ResolveOffset() -- resolve offset so we save the actual time to disk
|
||||
ix.data.Set("date", ix.date.lib.serialize(ix.date.current), false, true)
|
||||
|
||||
-- update config to reflect current saved date
|
||||
ix.config.Set("year", ix.date.current:getyear())
|
||||
ix.config.Set("month", ix.date.current:getmonth())
|
||||
ix.config.Set("day", ix.date.current:getday())
|
||||
|
||||
ix.date.bSaving = nil
|
||||
end
|
||||
else
|
||||
net.Receive("ixDateSync", function()
|
||||
local timeScale = net.ReadFloat()
|
||||
local currentDate = ix.date.lib.construct(net.ReadTable())
|
||||
local startTime = net.ReadFloat()
|
||||
|
||||
ix.date.timeScale = timeScale
|
||||
ix.date.current = currentDate
|
||||
ix.date.start = startTime
|
||||
end)
|
||||
end
|
||||
|
||||
--- Returns the currently set date.
|
||||
-- @realm shared
|
||||
-- @treturn date Current in-game date
|
||||
function ix.date.Get()
|
||||
local minutesSinceStart = (CurTime() - ix.date.start) / ix.date.timeScale
|
||||
|
||||
return ix.date.current:copy():addminutes(minutesSinceStart)
|
||||
end
|
||||
|
||||
--- Returns a string formatted version of a date.
|
||||
-- @realm shared
|
||||
-- @string format Format string
|
||||
-- @date[opt=nil] currentDate Date to format. If nil, it will use the currently set date
|
||||
-- @treturn string Formatted date
|
||||
function ix.date.GetFormatted(format, currentDate)
|
||||
return (currentDate or ix.date.Get()):fmt(format)
|
||||
end
|
||||
|
||||
--- Returns a serialized version of a date. This is useful when you need to network a date to clients, or save a date to disk.
|
||||
-- @realm shared
|
||||
-- @date[opt=nil] currentDate Date to serialize. If nil, it will use the currently set date
|
||||
-- @treturn table Serialized date
|
||||
function ix.date.GetSerialized(currentDate)
|
||||
return ix.date.lib.serialize(currentDate or ix.date.Get())
|
||||
end
|
||||
|
||||
--- Returns a date object from a table or serialized date.
|
||||
-- @realm shared
|
||||
-- @param currentDate Date to construct
|
||||
-- @treturn date Constructed date object
|
||||
function ix.date.Construct(currentDate)
|
||||
return ix.date.lib.construct(currentDate)
|
||||
end
|
||||
135
gamemodes/helix/gamemode/core/libs/sh_faction.lua
Normal file
135
gamemodes/helix/gamemode/core/libs/sh_faction.lua
Normal file
@@ -0,0 +1,135 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--- Helper library for loading/getting faction information.
|
||||
-- @module ix.faction
|
||||
|
||||
ix.faction = ix.faction or {}
|
||||
ix.faction.teams = ix.faction.teams or {}
|
||||
ix.faction.indices = ix.faction.indices or {}
|
||||
|
||||
local CITIZEN_MODELS = {
|
||||
"models/humans/group01/male_01.mdl",
|
||||
"models/humans/group01/male_02.mdl",
|
||||
"models/humans/group01/male_04.mdl",
|
||||
"models/humans/group01/male_05.mdl",
|
||||
"models/humans/group01/male_06.mdl",
|
||||
"models/humans/group01/male_07.mdl",
|
||||
"models/humans/group01/male_08.mdl",
|
||||
"models/humans/group01/male_09.mdl",
|
||||
"models/humans/group02/male_01.mdl",
|
||||
"models/humans/group02/male_03.mdl",
|
||||
"models/humans/group02/male_05.mdl",
|
||||
"models/humans/group02/male_07.mdl",
|
||||
"models/humans/group02/male_09.mdl",
|
||||
"models/humans/group01/female_01.mdl",
|
||||
"models/humans/group01/female_02.mdl",
|
||||
"models/humans/group01/female_03.mdl",
|
||||
"models/humans/group01/female_06.mdl",
|
||||
"models/humans/group01/female_07.mdl",
|
||||
"models/humans/group02/female_01.mdl",
|
||||
"models/humans/group02/female_03.mdl",
|
||||
"models/humans/group02/female_06.mdl",
|
||||
"models/humans/group01/female_04.mdl"
|
||||
}
|
||||
|
||||
--- Loads factions from a directory.
|
||||
-- @realm shared
|
||||
-- @string directory The path to the factions files.
|
||||
function ix.faction.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/*.lua", "LUA")) do
|
||||
local niceName = v:sub(4, -5)
|
||||
|
||||
FACTION = ix.faction.teams[niceName] or {index = table.Count(ix.faction.teams) + 1, isDefault = false}
|
||||
if (PLUGIN) then
|
||||
FACTION.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
ix.util.Include(directory.."/"..v, "shared")
|
||||
|
||||
if (!FACTION.name) then
|
||||
FACTION.name = "Unknown"
|
||||
ErrorNoHalt("Faction '"..niceName.."' is missing a name. You need to add a FACTION.name = \"Name\"\n")
|
||||
end
|
||||
|
||||
if (!FACTION.color) then
|
||||
FACTION.color = Color(150, 150, 150)
|
||||
ErrorNoHalt("Faction '"..niceName.."' is missing a color. You need to add FACTION.color = Color(1, 2, 3)\n")
|
||||
end
|
||||
|
||||
team.SetUp(FACTION.index, FACTION.name or "Unknown", FACTION.color or Color(125, 125, 125))
|
||||
|
||||
FACTION.models = FACTION.models or CITIZEN_MODELS
|
||||
FACTION.uniqueID = FACTION.uniqueID or niceName
|
||||
|
||||
for _, v2 in pairs(FACTION.models) do
|
||||
if (isstring(v2)) then
|
||||
util.PrecacheModel(v2)
|
||||
elseif (istable(v2)) then
|
||||
util.PrecacheModel(v2[1])
|
||||
end
|
||||
end
|
||||
|
||||
if (!FACTION.GetModels) then
|
||||
function FACTION:GetModels(client)
|
||||
return self.models
|
||||
end
|
||||
end
|
||||
|
||||
ix.faction.indices[FACTION.index] = FACTION
|
||||
ix.faction.teams[niceName] = FACTION
|
||||
FACTION = nil
|
||||
end
|
||||
end
|
||||
|
||||
--- Retrieves a faction table.
|
||||
-- @realm shared
|
||||
-- @param identifier Index or name of the faction
|
||||
-- @treturn table Faction table
|
||||
-- @usage print(ix.faction.Get(Entity(1):Team()).name)
|
||||
-- > "Citizen"
|
||||
function ix.faction.Get(identifier)
|
||||
return ix.faction.indices[identifier] or ix.faction.teams[identifier]
|
||||
end
|
||||
|
||||
--- Retrieves a faction index.
|
||||
-- @realm shared
|
||||
-- @string uniqueID Unique ID of the faction
|
||||
-- @treturn number Faction index
|
||||
function ix.faction.GetIndex(uniqueID)
|
||||
for k, v in ipairs(ix.faction.indices) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
--- Returns true if a faction requires a whitelist.
|
||||
-- @realm client
|
||||
-- @number faction Index of the faction
|
||||
-- @treturn bool Whether or not the faction requires a whitelist
|
||||
function ix.faction.HasWhitelist(faction)
|
||||
local data = ix.faction.indices[faction]
|
||||
|
||||
if (data) then
|
||||
if (data.isDefault) then
|
||||
return true
|
||||
end
|
||||
|
||||
local ixData = ix.localData and ix.localData.whitelists or {}
|
||||
|
||||
return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
213
gamemodes/helix/gamemode/core/libs/sh_flag.lua
Normal file
213
gamemodes/helix/gamemode/core/libs/sh_flag.lua
Normal file
@@ -0,0 +1,213 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Grants abilities to characters.
|
||||
|
||||
Flags are a simple way of adding/removing certain abilities to players on a per-character basis. Helix comes with a few flags
|
||||
by default, for example to restrict spawning of props, usage of the physgun, etc. All flags will be listed in the
|
||||
`Flags` section of the `Help` menu. Flags are usually used when server validation is required to allow a player to do something
|
||||
on their character. However, it's usually preferable to use in-character methods over flags when possible (i.e restricting
|
||||
the business menu to characters that have a permit item, rather than using flags to determine availability).
|
||||
|
||||
Flags are a single alphanumeric character that can be checked on the server. Serverside callbacks can be used to provide
|
||||
functionality whenever the flag is added or removed. For example:
|
||||
ix.flag.Add("z", "Access to some cool stuff.", function(client, bGiven)
|
||||
print("z flag given:", bGiven)
|
||||
end)
|
||||
|
||||
Entity(1):GetCharacter():GiveFlags("z")
|
||||
> z flag given: true
|
||||
|
||||
Entity(1):GetCharacter():TakeFlags("z")
|
||||
> z flag given: false
|
||||
|
||||
print(Entity(1):GetCharacter():HasFlags("z"))
|
||||
> false
|
||||
|
||||
Check out `Character:GiveFlags` and `Character:TakeFlags` for additional info.
|
||||
]]
|
||||
-- @module ix.flag
|
||||
|
||||
ix.flag = ix.flag or {}
|
||||
ix.flag.list = ix.flag.list or {}
|
||||
|
||||
--- Creates a flag. This should be called shared in order for the client to be aware of the flag's existence.
|
||||
-- @realm shared
|
||||
-- @string flag Alphanumeric character to use for the flag
|
||||
-- @string description Description of the flag
|
||||
-- @func callback Function to call when the flag is given or taken from a player
|
||||
function ix.flag.Add(flag, description, callback)
|
||||
ix.flag.list[flag] = {
|
||||
description = description,
|
||||
callback = callback
|
||||
}
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
-- Called to apply flags when a player has spawned.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to setup flags for
|
||||
function ix.flag.OnSpawn(client)
|
||||
-- Check if they have a valid character.
|
||||
if (client:GetCharacter()) then
|
||||
-- Get all of the character's flags.
|
||||
local flags = client:GetCharacter():GetFlags()
|
||||
|
||||
for i = 1, #flags do
|
||||
-- Get each individual flag.
|
||||
local flag = flags[i]
|
||||
local info = ix.flag.list[flag]
|
||||
|
||||
-- Check if the flag has a callback.
|
||||
if (info and info.callback) then
|
||||
-- Run the callback, passing the player and true so they get whatever benefits.
|
||||
info.callback(client, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local character = ix.meta.character
|
||||
|
||||
if (SERVER) then
|
||||
--- Flag util functions for character
|
||||
-- @classmod Character
|
||||
|
||||
--- Sets this character's accessible flags. Note that this method overwrites **all** flags instead of adding them.
|
||||
-- @realm server
|
||||
-- @string flags Flag(s) this charater is allowed to have
|
||||
-- @see GiveFlags
|
||||
function character:SetFlags(flags)
|
||||
self:SetData("f", flags)
|
||||
end
|
||||
|
||||
--- Adds a flag to the list of this character's accessible flags. This does not overwrite existing flags.
|
||||
-- @realm server
|
||||
-- @string flags Flag(s) this character should be given
|
||||
-- @usage character:GiveFlags("pet")
|
||||
-- -- gives p, e, and t flags to the character
|
||||
-- @see HasFlags
|
||||
function character:GiveFlags(flags)
|
||||
local addedFlags = ""
|
||||
|
||||
-- Get the individual flags within the flag string.
|
||||
for i = 1, #flags do
|
||||
local flag = flags[i]
|
||||
local info = ix.flag.list[flag]
|
||||
|
||||
if (info) then
|
||||
if (!self:HasFlags(flag)) then
|
||||
addedFlags = addedFlags..flag
|
||||
end
|
||||
|
||||
if (info.callback) then
|
||||
-- Pass the player and true (true for the flag being given.)
|
||||
info.callback(self:GetPlayer(), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Only change the flag string if it is different.
|
||||
if (addedFlags != "") then
|
||||
self:SetFlags(self:GetFlags()..addedFlags)
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes this character's access to the given flags.
|
||||
-- @realm server
|
||||
-- @string flags Flag(s) to remove from this character
|
||||
-- @usage -- for a character with "pet" flags
|
||||
-- character:TakeFlags("p")
|
||||
-- -- character now has e, and t flags
|
||||
function character:TakeFlags(flags)
|
||||
local oldFlags = self:GetFlags()
|
||||
local newFlags = oldFlags
|
||||
|
||||
-- Get the individual flags within the flag string.
|
||||
for i = 1, #flags do
|
||||
local flag = flags[i]
|
||||
local info = ix.flag.list[flag]
|
||||
|
||||
-- Call the callback if the flag has been registered.
|
||||
if (info and info.callback) then
|
||||
-- Pass the player and false (false since the flag is being taken)
|
||||
info.callback(self:GetPlayer(), false)
|
||||
end
|
||||
|
||||
newFlags = newFlags:gsub(flag, "")
|
||||
end
|
||||
|
||||
if (newFlags != oldFlags) then
|
||||
self:SetFlags(newFlags)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns all of the flags this character has.
|
||||
-- @realm shared
|
||||
-- @treturn string Flags this character has represented as one string. You can access individual flags by iterating through
|
||||
-- the string letter by letter
|
||||
function character:GetFlags()
|
||||
return self:GetData("f", "")
|
||||
end
|
||||
|
||||
--- Returns `true` if the character has the given flag(s).
|
||||
-- @realm shared
|
||||
-- @string flags Flag(s) to check access for
|
||||
-- @treturn bool Whether or not this character has access to the given flag(s)
|
||||
function character:HasFlags(flags)
|
||||
local bHasFlag = hook.Run("CharacterHasFlags", self, flags)
|
||||
|
||||
if (bHasFlag == true) then
|
||||
return true
|
||||
end
|
||||
|
||||
local flagList = self:GetFlags()
|
||||
|
||||
for i = 1, #flags do
|
||||
if (flagList:find(flags[i], 1, true)) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
ix.flag.Add("p", "Accès au physgun.", function(client, isGiven)
|
||||
if (isGiven) then
|
||||
client:Give("weapon_physgun")
|
||||
--client:SelectWeapon("weapon_physgun")
|
||||
else
|
||||
client:StripWeapon("weapon_physgun")
|
||||
end
|
||||
end)
|
||||
|
||||
ix.flag.Add("t", "Accès au toolgun.", function(client, isGiven)
|
||||
if (isGiven) then
|
||||
client:Give("gmod_tool")
|
||||
--client:SelectWeapon("gmod_tool")
|
||||
else
|
||||
client:StripWeapon("gmod_tool")
|
||||
end
|
||||
end)
|
||||
|
||||
ix.flag.Add("c", "Accès au spawn des chaises.")
|
||||
ix.flag.Add("C", "Accès au spawn des véhicules.")
|
||||
ix.flag.Add("r", "Accès au spawn des ragdolls.")
|
||||
ix.flag.Add("e", "Accès au spawn des accessoires.")
|
||||
ix.flag.Add("n", "Accès à la création de NPCs.")
|
||||
end
|
||||
186
gamemodes/helix/gamemode/core/libs/sh_inventory.lua
Normal file
186
gamemodes/helix/gamemode/core/libs/sh_inventory.lua
Normal file
@@ -0,0 +1,186 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Inventory manipulation and helper functions.
|
||||
]]
|
||||
-- @module ix.inventory
|
||||
|
||||
ix.inventory = ix.inventory or {}
|
||||
|
||||
ix.util.Include("helix/gamemode/core/meta/sh_inventory.lua")
|
||||
|
||||
--- Retrieves an inventory table.
|
||||
-- @realm shared
|
||||
-- @number invID Index of the inventory
|
||||
-- @treturn Inventory Inventory table
|
||||
function ix.inventory.Get(invID)
|
||||
return ix.item.inventories[invID]
|
||||
end
|
||||
|
||||
function ix.inventory.Create(width, height, id)
|
||||
local inventory = ix.meta.inventory:New(id, width, height)
|
||||
ix.item.inventories[id] = inventory
|
||||
return inventory
|
||||
end
|
||||
|
||||
--- Loads an inventory and associated items from the database into memory. If you are passing a table into `invID`, it
|
||||
-- requires a table where the key is the inventory ID, and the value is a table of the width and height values. See below
|
||||
-- for an example.
|
||||
-- @realm server
|
||||
-- @param invID Inventory ID or table of inventory IDs
|
||||
-- @number width Width of inventory (this is not used when passing a table to `invID`)
|
||||
-- @number height Height of inventory (this is not used when passing a table to `invID`)
|
||||
-- @func callback Function to call when inventory is restored
|
||||
-- @usage ix.inventory.Restore({
|
||||
-- [10] = {5, 5},
|
||||
-- [11] = {7, 4}
|
||||
-- })
|
||||
-- -- inventories 10 and 11 with sizes (5, 5) and (7, 4) will be loaded
|
||||
function ix.inventory.Restore(invID, width, height, callback)
|
||||
local inventories = {}
|
||||
|
||||
if (!istable(invID)) then
|
||||
if (!isnumber(invID) or invID < 0) then
|
||||
error("Attempt to restore inventory with an invalid ID!")
|
||||
end
|
||||
|
||||
inventories[invID] = {width, height}
|
||||
ix.inventory.Create(width, height, invID)
|
||||
else
|
||||
for k, v in pairs(invID) do
|
||||
inventories[k] = {v[1], v[2]}
|
||||
ix.inventory.Create(v[1], v[2], k)
|
||||
end
|
||||
end
|
||||
|
||||
local query = mysql:Select("ix_items")
|
||||
query:Select("item_id")
|
||||
query:Select("inventory_id")
|
||||
query:Select("unique_id")
|
||||
query:Select("data")
|
||||
query:Select("character_id")
|
||||
query:Select("player_id")
|
||||
query:Select("x")
|
||||
query:Select("y")
|
||||
query:WhereIn("inventory_id", table.GetKeys(inventories))
|
||||
query:Callback(function(result)
|
||||
if (istable(result) and #result > 0) then
|
||||
local invSlots = {}
|
||||
|
||||
for _, item in ipairs(result) do
|
||||
local itemInvID = tonumber(item.inventory_id)
|
||||
local invInfo = inventories[itemInvID]
|
||||
|
||||
if (!itemInvID or !invInfo) then
|
||||
-- don't restore items with an invalid inventory id or type
|
||||
continue
|
||||
end
|
||||
|
||||
local inventory = ix.item.inventories[itemInvID]
|
||||
local x, y = tonumber(item.x), tonumber(item.y)
|
||||
local itemID = tonumber(item.item_id)
|
||||
local data = util.JSONToTable(item.data or "[]")
|
||||
local characterID, playerID = tonumber(item.character_id), tostring(item.player_id)
|
||||
|
||||
if (x and y and itemID) then
|
||||
if (x <= inventory.w and x > 0 and y <= inventory.h and y > 0) then
|
||||
local item2 = ix.item.New(item.unique_id, itemID)
|
||||
|
||||
if (item2) then
|
||||
invSlots[itemInvID] = invSlots[itemInvID] or {}
|
||||
local slots = invSlots[itemInvID]
|
||||
|
||||
item2.data = {}
|
||||
|
||||
if (data) then
|
||||
item2.data = data
|
||||
end
|
||||
|
||||
item2.gridX = x
|
||||
item2.gridY = y
|
||||
item2.invID = itemInvID
|
||||
item2.characterID = characterID
|
||||
item2.playerID = (playerID == "" or playerID == "NULL") and nil or playerID
|
||||
|
||||
for x2 = 0, item2.width - 1 do
|
||||
for y2 = 0, item2.height - 1 do
|
||||
slots[x + x2] = slots[x + x2] or {}
|
||||
slots[x + x2][y + y2] = item2
|
||||
end
|
||||
end
|
||||
|
||||
if (item2.OnRestored) then
|
||||
item2:OnRestored(item2, itemInvID)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(invSlots) do
|
||||
ix.item.inventories[k].slots = v
|
||||
end
|
||||
end
|
||||
|
||||
if (callback) then
|
||||
for k, _ in pairs(inventories) do
|
||||
callback(ix.item.inventories[k])
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.inventory.New(owner, invType, callback)
|
||||
local invData = ix.item.inventoryTypes[invType] or {w = 1, h = 1}
|
||||
|
||||
local query = mysql:Insert("ix_inventories")
|
||||
query:Insert("inventory_type", invType)
|
||||
query:Insert("character_id", owner)
|
||||
query:Callback(function(result, status, lastID)
|
||||
local inventory = ix.inventory.Create(invData.w, invData.h, lastID)
|
||||
|
||||
if (invType) then
|
||||
if invType == "equipInventory" then
|
||||
inventory.vars.isBag = false
|
||||
else
|
||||
inventory.vars.isBag = invType
|
||||
end
|
||||
end
|
||||
|
||||
if (isnumber(owner) and owner > 0) then
|
||||
local character = ix.char.loaded[owner]
|
||||
local client = character:GetPlayer()
|
||||
|
||||
inventory:SetOwner(owner)
|
||||
|
||||
if (IsValid(client)) then
|
||||
inventory:Sync(client)
|
||||
end
|
||||
end
|
||||
|
||||
if (callback) then
|
||||
callback(inventory)
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.inventory.Register(invType, w, h, isBag)
|
||||
ix.item.inventoryTypes[invType] = {w = w, h = h}
|
||||
|
||||
if (isBag) then
|
||||
ix.item.inventoryTypes[invType].isBag = invType
|
||||
end
|
||||
|
||||
return ix.item.inventoryTypes[invType]
|
||||
end
|
||||
879
gamemodes/helix/gamemode/core/libs/sh_item.lua
Normal file
879
gamemodes/helix/gamemode/core/libs/sh_item.lua
Normal file
@@ -0,0 +1,879 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[--
|
||||
Item manipulation and helper functions.
|
||||
]]
|
||||
-- @module ix.item
|
||||
|
||||
ix.item = ix.item or {}
|
||||
ix.item.list = ix.item.list or {}
|
||||
ix.item.base = ix.item.base or {}
|
||||
ix.item.instances = ix.item.instances or {}
|
||||
ix.item.inventories = ix.item.inventories or {
|
||||
[0] = {}
|
||||
}
|
||||
ix.item.inventoryTypes = ix.item.inventoryTypes or {}
|
||||
|
||||
ix.util.Include("helix/gamemode/core/meta/sh_item.lua")
|
||||
|
||||
-- Declare some supports for logic inventory
|
||||
local zeroInv = ix.item.inventories[0]
|
||||
|
||||
function zeroInv:GetID()
|
||||
return 0
|
||||
end
|
||||
|
||||
function zeroInv:OnCheckAccess(client)
|
||||
return true
|
||||
end
|
||||
|
||||
-- WARNING: You have to manually sync the data to client if you're trying to use item in the logical inventory in the vgui.
|
||||
function zeroInv:Add(uniqueID, quantity, data, x, y)
|
||||
quantity = quantity or 1
|
||||
|
||||
if (quantity > 0) then
|
||||
if (!isnumber(uniqueID)) then
|
||||
if (quantity > 1) then
|
||||
for _ = 1, quantity do
|
||||
self:Add(uniqueID, 1, data)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local itemTable = ix.item.list[uniqueID]
|
||||
|
||||
if (!itemTable) then
|
||||
return false, "invalidItem"
|
||||
end
|
||||
|
||||
ix.item.Instance(0, uniqueID, data, x, y, function(item)
|
||||
self[item:GetID()] = item
|
||||
end)
|
||||
|
||||
return nil, nil, 0
|
||||
end
|
||||
else
|
||||
return false, "notValid"
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.Instance(index, uniqueID, itemData, x, y, callback, characterID, playerID)
|
||||
if (!uniqueID or ix.item.list[uniqueID]) then
|
||||
itemData = istable(itemData) and itemData or {}
|
||||
|
||||
local query = mysql:Insert("ix_items")
|
||||
query:Insert("inventory_id", index)
|
||||
query:Insert("unique_id", uniqueID)
|
||||
query:Insert("data", util.TableToJSON(itemData))
|
||||
query:Insert("x", x)
|
||||
query:Insert("y", y)
|
||||
|
||||
if (characterID) then
|
||||
query:Insert("character_id", characterID)
|
||||
end
|
||||
|
||||
if (playerID) then
|
||||
query:Insert("player_id", playerID)
|
||||
end
|
||||
|
||||
query:Callback(function(result, status, lastID)
|
||||
local item = ix.item.New(uniqueID, lastID)
|
||||
|
||||
if (item) then
|
||||
item.data = table.Copy(itemData)
|
||||
item.invID = index
|
||||
item.characterID = characterID
|
||||
item.playerID = playerID
|
||||
|
||||
if (callback) then
|
||||
callback(item)
|
||||
end
|
||||
|
||||
if (item.OnInstanced) then
|
||||
item:OnInstanced(index, x, y, item)
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
else
|
||||
ErrorNoHalt("[Helix] Attempt to give an invalid item! (" .. (uniqueID or "nil") .. ")\n")
|
||||
end
|
||||
end
|
||||
|
||||
--- Retrieves an item table.
|
||||
-- @realm shared
|
||||
-- @string identifier Unique ID of the item
|
||||
-- @treturn item Item table
|
||||
-- @usage print(ix.item.Get("example"))
|
||||
-- > "item[example][0]"
|
||||
function ix.item.Get(identifier)
|
||||
return ix.item.base[identifier] or ix.item.list[identifier]
|
||||
end
|
||||
|
||||
function ix.item.Load(path, baseID, isBaseItem)
|
||||
local uniqueID = path:match("sh_([_%w]+)%.lua")
|
||||
|
||||
if (uniqueID) then
|
||||
uniqueID = (isBaseItem and "base_" or "")..uniqueID
|
||||
ix.item.Register(uniqueID, baseID, isBaseItem, path)
|
||||
else
|
||||
if (!path:find(".txt")) then
|
||||
ErrorNoHalt("[Helix] Item at '"..path.."' follows invalid naming convention!\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.Register(uniqueID, baseID, isBaseItem, path, luaGenerated)
|
||||
local meta = ix.meta.item
|
||||
|
||||
if (uniqueID) then
|
||||
ITEM = (isBaseItem and ix.item.base or ix.item.list)[uniqueID] or setmetatable({}, meta)
|
||||
ITEM.uniqueID = uniqueID
|
||||
ITEM.base = baseID or ITEM.base
|
||||
ITEM.isBase = isBaseItem
|
||||
ITEM.hooks = ITEM.hooks or {}
|
||||
ITEM.postHooks = ITEM.postHooks or {}
|
||||
ITEM.functions = ITEM.functions or {}
|
||||
ITEM.functions.drop = ITEM.functions.drop or {
|
||||
tip = "dropTip",
|
||||
icon = "icon16/world.png",
|
||||
OnRun = function(item)
|
||||
local bSuccess, error = item:Transfer(nil, nil, nil, item.player)
|
||||
|
||||
if (!bSuccess and isstring(error)) then
|
||||
item.player:NotifyLocalized(error)
|
||||
else
|
||||
item.player:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1)
|
||||
end
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return !IsValid(item.entity) and !item.noDrop
|
||||
end
|
||||
}
|
||||
ITEM.functions.take = ITEM.functions.take or {
|
||||
tip = "takeTip",
|
||||
icon = "icon16/box.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local bSuccess, error = item:Transfer(client:GetCharacter():GetInventory():GetID(), nil, nil, client)
|
||||
|
||||
if (!bSuccess) then
|
||||
client:NotifyLocalized(error or "unknownError")
|
||||
return false
|
||||
else
|
||||
client:EmitSound("npc/zombie/foot_slide" .. math.random(1, 3) .. ".wav", 75, math.random(90, 120), 1)
|
||||
|
||||
if (item.data) then -- I don't like it but, meh...
|
||||
for k, v in pairs(item.data) do
|
||||
item:SetData(k, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return IsValid(item.entity)
|
||||
end
|
||||
}
|
||||
ITEM.functions.removeLabel = ITEM.functions.removeLabel or {
|
||||
name = "Remove Label",
|
||||
icon = "icon16/tag_blue_delete.png",
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local inventory = client:GetCharacter():GetInventory()
|
||||
inventory:Add("itemlabel", 1, {
|
||||
labelInfoName = item:GetData("labelName"),
|
||||
labelInfoDesc = item:GetData("labelDescription")
|
||||
})
|
||||
|
||||
item:SetData("labelName", false)
|
||||
item:SetData("labelDescription", false)
|
||||
|
||||
client:Notify("You remove the label from the " .. item.name .. ".")
|
||||
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
return item:GetData("labelName", false) or item:GetData("labelDescription", false)
|
||||
end
|
||||
}
|
||||
|
||||
local oldBase = ITEM.base
|
||||
|
||||
if (ITEM.base) then
|
||||
local baseTable = ix.item.base[ITEM.base]
|
||||
|
||||
if (baseTable) then
|
||||
for k, v in pairs(baseTable) do
|
||||
if (ITEM[k] == nil) then
|
||||
ITEM[k] = v
|
||||
end
|
||||
|
||||
ITEM.baseTable = baseTable
|
||||
end
|
||||
|
||||
local mergeTable = table.Copy(baseTable)
|
||||
ITEM = table.Merge(mergeTable, ITEM)
|
||||
else
|
||||
ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n")
|
||||
end
|
||||
end
|
||||
|
||||
if (PLUGIN) then
|
||||
ITEM.plugin = PLUGIN.uniqueID
|
||||
end
|
||||
|
||||
if (!luaGenerated and path) then
|
||||
ix.util.Include(path)
|
||||
end
|
||||
|
||||
if (ITEM.base and oldBase != ITEM.base) then
|
||||
local baseTable = ix.item.base[ITEM.base]
|
||||
|
||||
if (baseTable) then
|
||||
for k, v in pairs(baseTable) do
|
||||
if (ITEM[k] == nil) then
|
||||
ITEM[k] = v
|
||||
end
|
||||
|
||||
ITEM.baseTable = baseTable
|
||||
end
|
||||
|
||||
local mergeTable = table.Copy(baseTable)
|
||||
ITEM = table.Merge(mergeTable, ITEM)
|
||||
else
|
||||
ErrorNoHalt("[Helix] Item '"..ITEM.uniqueID.."' has a non-existent base! ("..ITEM.base..")\n")
|
||||
end
|
||||
end
|
||||
|
||||
ITEM.description = ITEM.description or "noDesc"
|
||||
ITEM.width = ITEM.width or 1
|
||||
ITEM.height = ITEM.height or 1
|
||||
ITEM.category = ITEM.category or "misc"
|
||||
|
||||
if (ITEM.OnRegistered) then
|
||||
ITEM:OnRegistered()
|
||||
end
|
||||
|
||||
(isBaseItem and ix.item.base or ix.item.list)[ITEM.uniqueID] = ITEM
|
||||
|
||||
if (IX_RELOADED) then
|
||||
-- we don't know which item was actually edited, so we'll refresh all of them
|
||||
for _, v in pairs(ix.item.instances) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
table.Merge(v, ITEM)
|
||||
end
|
||||
end
|
||||
end
|
||||
if (luaGenerated) then
|
||||
return ITEM
|
||||
else
|
||||
ITEM = nil
|
||||
end
|
||||
else
|
||||
ErrorNoHalt("[Helix] You tried to register an item without uniqueID!\n")
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.LoadFromDir(directory)
|
||||
local files, folders
|
||||
|
||||
files = file.Find(directory.."/base/*.lua", "LUA")
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
ix.item.Load(directory.."/base/"..v, nil, true)
|
||||
end
|
||||
|
||||
files, folders = file.Find(directory.."/*", "LUA")
|
||||
|
||||
for _, v in ipairs(folders) do
|
||||
if (v == "base") then
|
||||
continue
|
||||
end
|
||||
|
||||
for _, v2 in ipairs(file.Find(directory.."/"..v.."/*.lua", "LUA")) do
|
||||
ix.item.Load(directory.."/"..v .. "/".. v2, "base_"..v)
|
||||
end
|
||||
end
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
ix.item.Load(directory.."/"..v)
|
||||
end
|
||||
end
|
||||
|
||||
function ix.item.New(uniqueID, id)
|
||||
if (ix.item.instances[id] and ix.item.instances[id].uniqueID == uniqueID) then
|
||||
return ix.item.instances[id]
|
||||
end
|
||||
|
||||
local stockItem = ix.item.list[uniqueID]
|
||||
|
||||
if (stockItem) then
|
||||
local item = setmetatable({id = id, data = {}}, {
|
||||
__index = stockItem,
|
||||
__eq = stockItem.__eq,
|
||||
__tostring = stockItem.__tostring
|
||||
})
|
||||
|
||||
ix.item.instances[id] = item
|
||||
|
||||
return item
|
||||
else
|
||||
ErrorNoHalt("[Helix] Attempt to index unknown item '"..uniqueID.."'\n")
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
function ix.item.GetInv(invID)
|
||||
ErrorNoHalt("ix.item.GetInv is deprecated. Use ix.inventory.Get instead!\n")
|
||||
return ix.inventory.Get(invID)
|
||||
end
|
||||
|
||||
function ix.item.RegisterInv(invType, w, h, isBag)
|
||||
ErrorNoHalt("ix.item.RegisterInv is deprecated. Use ix.inventory.Register instead!\n")
|
||||
return ix.inventory.Register(invType, w, h, isBag)
|
||||
end
|
||||
|
||||
function ix.item.NewInv(owner, invType, callback)
|
||||
ErrorNoHalt("ix.item.NewInv is deprecated. Use ix.inventory.New instead!\n")
|
||||
return ix.inventory.New(owner, invType, callback)
|
||||
end
|
||||
|
||||
function ix.item.CreateInv(width, height, id)
|
||||
ErrorNoHalt("ix.item.CreateInv is deprecated. Use ix.inventory.Create instead!\n")
|
||||
return ix.inventory.Create(width, height, id)
|
||||
end
|
||||
|
||||
function ix.item.RestoreInv(invID, width, height, callback)
|
||||
ErrorNoHalt("ix.item.RestoreInv is deprecated. Use ix.inventory.Restore instead!\n")
|
||||
return ix.inventory.Restore(invID, width, height, callback)
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
net.Receive("ixInventorySync", function()
|
||||
local slots = net.ReadTable()
|
||||
local id = net.ReadUInt(32)
|
||||
local w, h = net.ReadUInt(6), net.ReadUInt(6)
|
||||
local owner = net.ReadType()
|
||||
local vars = net.ReadTable()
|
||||
|
||||
if (!LocalPlayer():GetCharacter()) then
|
||||
return
|
||||
end
|
||||
|
||||
local character = owner and ix.char.loaded[owner]
|
||||
local inventory = ix.inventory.Create(w, h, id)
|
||||
inventory.slots = {}
|
||||
inventory.vars = vars
|
||||
|
||||
local x, y
|
||||
|
||||
for _, v in ipairs(slots) do
|
||||
x, y = v[1], v[2]
|
||||
|
||||
inventory.slots[x] = inventory.slots[x] or {}
|
||||
|
||||
local item = ix.item.New(v[3], v[4])
|
||||
|
||||
item.data = {}
|
||||
if (v[5]) then
|
||||
item.data = v[5]
|
||||
end
|
||||
|
||||
item.invID = item.invID or id
|
||||
inventory.slots[x][y] = item
|
||||
end
|
||||
|
||||
if (character) then
|
||||
inventory:SetOwner(character:GetID())
|
||||
character.vars.inv = character.vars.inv or {}
|
||||
|
||||
for k, v in ipairs(character:GetInventory(true)) do
|
||||
if (v:GetID() == id) then
|
||||
character:GetInventory(true)[k] = inventory
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(character.vars.inv, inventory)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryData", function()
|
||||
local id = net.ReadUInt(32)
|
||||
local item = ix.item.instances[id]
|
||||
|
||||
if (item) then
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
|
||||
item.data = item.data or {}
|
||||
item.data[key] = value
|
||||
|
||||
local invID = item.invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or item.invID
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
if (panel and panel.panels) then
|
||||
local icon = panel.panels[id]
|
||||
|
||||
if (icon) then
|
||||
icon:SetHelixTooltip(function(tooltip)
|
||||
ix.hud.PopulateItemTooltip(tooltip, item)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventorySet", function()
|
||||
local invID = net.ReadUInt(32)
|
||||
local x, y = net.ReadUInt(6), net.ReadUInt(6)
|
||||
local uniqueID = net.ReadString()
|
||||
local id = net.ReadUInt(32)
|
||||
local owner = net.ReadUInt(32)
|
||||
local data = net.ReadTable()
|
||||
|
||||
local character = owner != 0 and ix.char.loaded[owner] or LocalPlayer():GetCharacter()
|
||||
|
||||
if (character) then
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (inventory) then
|
||||
local item = (uniqueID != "" and id != 0) and ix.item.New(uniqueID, id) or nil
|
||||
item.invID = invID
|
||||
item.data = {}
|
||||
|
||||
if (data) then
|
||||
item.data = data
|
||||
end
|
||||
|
||||
inventory.slots[x] = inventory.slots[x] or {}
|
||||
inventory.slots[x][y] = item
|
||||
|
||||
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
|
||||
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
if (IsValid(panel)) then
|
||||
local icon = panel:AddIcon(item, item:GetModel() or "models/props_junk/popcan01a.mdl",
|
||||
x, y, item.width, item.height, item:GetSkin(), item:GetModelBodygroups(), item.color, item.material, item.rotate, item.OnInventoryDraw)
|
||||
|
||||
if (IsValid(icon)) then
|
||||
icon:SetHelixTooltip(function(tooltip)
|
||||
ix.hud.PopulateItemTooltip(tooltip, item)
|
||||
end)
|
||||
|
||||
icon.itemID = item.id
|
||||
panel.panels[item.id] = icon
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryMove", function()
|
||||
local invID = net.ReadUInt(32)
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (!inventory) then
|
||||
return
|
||||
end
|
||||
|
||||
local itemID = net.ReadUInt(32)
|
||||
local oldX = net.ReadUInt(6)
|
||||
local oldY = net.ReadUInt(6)
|
||||
local x = net.ReadUInt(6)
|
||||
local y = net.ReadUInt(6)
|
||||
|
||||
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
|
||||
|
||||
local item = ix.item.instances[itemID]
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
-- update inventory UI if it's open
|
||||
if (IsValid(panel)) then
|
||||
local icon = panel.panels[itemID]
|
||||
|
||||
if (IsValid(icon)) then
|
||||
icon:Move(x, y, panel, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- update inventory slots
|
||||
if (item) then
|
||||
inventory.slots[oldX][oldY] = nil
|
||||
|
||||
inventory.slots[x] = inventory.slots[x] or {}
|
||||
inventory.slots[x][y] = item
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryRemove", function()
|
||||
local id = net.ReadUInt(32)
|
||||
local invID = net.ReadUInt(32)
|
||||
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (!inventory) then
|
||||
return
|
||||
end
|
||||
|
||||
inventory:Remove(id)
|
||||
|
||||
invID = invID == LocalPlayer():GetCharacter():GetInventory():GetID() and 1 or invID
|
||||
local panel = ix.gui["inv" .. invID]
|
||||
|
||||
if (IsValid(panel)) then
|
||||
local icon = panel.panels[id]
|
||||
|
||||
if (IsValid(icon)) then
|
||||
for _, v in ipairs(icon.slots or {}) do
|
||||
if (v.item == icon) then
|
||||
v.item = nil
|
||||
end
|
||||
end
|
||||
|
||||
icon:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
local item = ix.item.instances[id]
|
||||
|
||||
if (!item) then
|
||||
return
|
||||
end
|
||||
|
||||
-- we need to close any bag windows that are open because of this item
|
||||
if (item.isBag) then
|
||||
local itemInv = item:GetInventory()
|
||||
|
||||
if (itemInv) then
|
||||
local frame = ix.gui["inv" .. itemInv:GetID()]
|
||||
|
||||
if (IsValid(frame)) then
|
||||
frame:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
util.AddNetworkString("ixInventorySync")
|
||||
util.AddNetworkString("ixInventorySet")
|
||||
util.AddNetworkString("ixInventoryMove")
|
||||
util.AddNetworkString("ixInventoryRemove")
|
||||
util.AddNetworkString("ixInventoryData")
|
||||
util.AddNetworkString("ixInventoryAction")
|
||||
|
||||
function ix.item.LoadItemByID(itemIndex, recipientFilter, callback)
|
||||
local query = mysql:Select("ix_items")
|
||||
query:Select("item_id")
|
||||
query:Select("unique_id")
|
||||
query:Select("data")
|
||||
query:Select("character_id")
|
||||
query:Select("player_id")
|
||||
query:WhereIn("item_id", itemIndex)
|
||||
query:Callback(function(result)
|
||||
if (istable(result)) then
|
||||
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]
|
||||
local characterID = tonumber(v.character_id)
|
||||
local playerID = tostring(v.player_id)
|
||||
|
||||
if (itemTable and itemID) then
|
||||
local item = ix.item.New(uniqueID, itemID)
|
||||
|
||||
item.data = data or {}
|
||||
item.invID = 0
|
||||
item.characterID = characterID
|
||||
item.playerID = (playerID == "" or playerID == "NULL") and nil or playerID
|
||||
|
||||
if (callback) then
|
||||
callback(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.item.PerformInventoryAction(client, action, item, invID, data)
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return
|
||||
end
|
||||
|
||||
local inventory = ix.item.inventories[invID or 0]
|
||||
|
||||
if (hook.Run("CanPlayerInteractItem", client, action, item, data) == false) then
|
||||
return
|
||||
end
|
||||
|
||||
if (!inventory:OnCheckAccess(client)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (isentity(item)) then
|
||||
if (IsValid(item)) then
|
||||
local entity = item
|
||||
local itemID = item.ixItemID
|
||||
item = ix.item.instances[itemID]
|
||||
|
||||
if (!item) then
|
||||
return
|
||||
end
|
||||
|
||||
item.entity = entity
|
||||
item.player = client
|
||||
else
|
||||
return
|
||||
end
|
||||
elseif (isnumber(item)) then
|
||||
item = ix.item.instances[item]
|
||||
|
||||
if (!item) then
|
||||
return
|
||||
end
|
||||
|
||||
item.player = client
|
||||
end
|
||||
|
||||
if (item.entity) then
|
||||
if (item.entity:GetPos():Distance(client:GetPos()) > 96) then
|
||||
return
|
||||
end
|
||||
elseif (!inventory:GetItemByID(item.id)) then
|
||||
return
|
||||
end
|
||||
|
||||
if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then
|
||||
local itemPlayerID = item:GetPlayerID()
|
||||
local itemCharacterID = item:GetCharacterID()
|
||||
local playerID = client:SteamID64()
|
||||
local characterID = client:GetCharacter():GetID()
|
||||
|
||||
if (itemPlayerID and itemCharacterID and itemPlayerID == playerID and itemCharacterID != characterID) then
|
||||
client:NotifyLocalized("itemOwned")
|
||||
|
||||
item.player = nil
|
||||
item.entity = nil
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local callback = item.functions[action]
|
||||
|
||||
if (callback) then
|
||||
if (callback.OnCanRun and callback.OnCanRun(item, data) == false) then
|
||||
item.entity = nil
|
||||
item.player = nil
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
hook.Run("PlayerInteractItem", client, action, item)
|
||||
|
||||
local entity = item.entity
|
||||
local result
|
||||
|
||||
if (item.hooks[action]) then
|
||||
result = item.hooks[action](item, data)
|
||||
end
|
||||
|
||||
if (result == nil) then
|
||||
result = callback.OnRun(item, data)
|
||||
end
|
||||
|
||||
if (item.postHooks[action]) then
|
||||
-- Posthooks shouldn't override the result from OnRun
|
||||
item.postHooks[action](item, result, data)
|
||||
end
|
||||
|
||||
if (result != false) then
|
||||
if (IsValid(entity)) then
|
||||
entity.ixIsSafe = true
|
||||
entity:Remove()
|
||||
else
|
||||
item:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
item.entity = nil
|
||||
item.player = nil
|
||||
|
||||
return result != false
|
||||
end
|
||||
end
|
||||
|
||||
local function NetworkInventoryMove(receiver, invID, itemID, oldX, oldY, x, y)
|
||||
net.Start("ixInventoryMove")
|
||||
net.WriteUInt(invID, 32)
|
||||
net.WriteUInt(itemID, 32)
|
||||
net.WriteUInt(oldX, 6)
|
||||
net.WriteUInt(oldY, 6)
|
||||
net.WriteUInt(x, 6)
|
||||
net.WriteUInt(y, 6)
|
||||
net.Send(receiver)
|
||||
end
|
||||
|
||||
net.Receive("ixInventoryMove", function(length, client)
|
||||
local oldX, oldY, x, y = net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6), net.ReadUInt(6)
|
||||
local invID, newInvID = net.ReadUInt(32), net.ReadUInt(32)
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
local inventory = ix.item.inventories[invID]
|
||||
|
||||
if (!inventory or inventory == nil) then
|
||||
inventory:Sync(client)
|
||||
end
|
||||
|
||||
if ((!inventory.owner or (inventory.owner and inventory.owner == character:GetID())) or
|
||||
inventory:OnCheckAccess(client)) then
|
||||
local item = inventory:GetItemAt(oldX, oldY)
|
||||
|
||||
if (item) then
|
||||
if (newInvID and invID != newInvID) then
|
||||
local inventory2 = ix.item.inventories[newInvID]
|
||||
|
||||
if (inventory2) then
|
||||
local bStatus, error = item:Transfer(newInvID, x, y, client)
|
||||
|
||||
if (!bStatus) then
|
||||
NetworkInventoryMove(
|
||||
client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
|
||||
)
|
||||
|
||||
client:NotifyLocalized(error or "unknownError")
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if hook.Run("CanMoveItemSameInv", item, inventory, x, y) == false then return end
|
||||
|
||||
if (inventory:CanItemFit(x, y, item.width, item.height, item)) then
|
||||
item.gridX = x
|
||||
item.gridY = y
|
||||
|
||||
for x2 = 0, item.width - 1 do
|
||||
for y2 = 0, item.height - 1 do
|
||||
local previousX = inventory.slots[oldX + x2]
|
||||
|
||||
if (previousX) then
|
||||
previousX[oldY + y2] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for x2 = 0, item.width - 1 do
|
||||
for y2 = 0, item.height - 1 do
|
||||
inventory.slots[x + x2] = inventory.slots[x + x2] or {}
|
||||
inventory.slots[x + x2][y + y2] = item
|
||||
end
|
||||
end
|
||||
|
||||
local receivers = inventory:GetReceivers()
|
||||
|
||||
if (istable(receivers)) then
|
||||
local filtered = {}
|
||||
|
||||
for _, v in ipairs(receivers) do
|
||||
if (v != client) then
|
||||
filtered[#filtered + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
if (#filtered > 0) then
|
||||
NetworkInventoryMove(
|
||||
filtered, invID, item:GetID(), oldX, oldY, x, y
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if (!inventory.noSave) then
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("x", x)
|
||||
query:Update("y", y)
|
||||
query:Where("item_id", item.id)
|
||||
query:Execute()
|
||||
end
|
||||
else
|
||||
NetworkInventoryMove(
|
||||
client, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
local item = inventory:GetItemAt(oldX, oldY)
|
||||
|
||||
if (item) then
|
||||
NetworkInventoryMove(
|
||||
client, item.invID, item.invID, item:GetID(), item.gridX, item.gridY, item.gridX, item.gridY
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixInventoryAction", function(length, client)
|
||||
ix.item.PerformInventoryAction(client, net.ReadString(), net.ReadUInt(32), net.ReadUInt(32), net.ReadTable())
|
||||
end)
|
||||
end
|
||||
|
||||
--- Instances and spawns a given item type.
|
||||
-- @realm server
|
||||
-- @string uniqueID Unique ID of the item
|
||||
-- @vector position The position in which the item's entity will be spawned
|
||||
-- @func[opt=nil] callback Function to call when the item entity is created
|
||||
-- @angle[opt=angle_zero] angles The angles at which the item's entity will spawn
|
||||
-- @tab[opt=nil] data Additional data for this item instance
|
||||
function ix.item.Spawn(uniqueID, position, callback, angles, data)
|
||||
ix.item.Instance(0, uniqueID, data or {}, 1, 1, function(item)
|
||||
local entity = item:Spawn(position, angles)
|
||||
|
||||
if (callback) then
|
||||
callback(item, entity)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- Inventory util functions for character
|
||||
-- @classmod Character
|
||||
|
||||
--- Returns this character's associated `Inventory` object.
|
||||
-- @function GetInventory
|
||||
-- @realm shared
|
||||
-- @treturn Inventory This character's inventory
|
||||
ix.char.RegisterVar("Inventory", {
|
||||
bNoNetworking = true,
|
||||
bNoDisplay = true,
|
||||
OnGet = function(character, index)
|
||||
if (index and !isnumber(index)) then
|
||||
return character.vars.inv or {}
|
||||
end
|
||||
|
||||
return character.vars.inv and character.vars.inv[index or 1]
|
||||
end,
|
||||
alias = "Inv"
|
||||
})
|
||||
138
gamemodes/helix/gamemode/core/libs/sh_language.lua
Normal file
138
gamemodes/helix/gamemode/core/libs/sh_language.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Multi-language phrase support.
|
||||
|
||||
Helix has support for multiple languages, and you can easily leverage this system for use in your own schema, plugins, etc.
|
||||
Languages will be loaded from the schema and any plugins in `languages/sh_languagename.lua`, where `languagename` is the id of a
|
||||
language (`english` for English, `french` for French, etc). The structure of a language file is a table of phrases with the key
|
||||
as its phrase ID and the value as its translation for that language. For example, in `plugins/area/sh_english.lua`:
|
||||
LANGUAGE = {
|
||||
area = "Area",
|
||||
areas = "Areas",
|
||||
areaEditMode = "Area Edit Mode",
|
||||
-- etc.
|
||||
}
|
||||
|
||||
The phrases defined in these language files can be used with the `L` global function:
|
||||
print(L("areaEditMode"))
|
||||
> Area Edit Mode
|
||||
|
||||
All phrases are formatted with `string.format`, so if you wish to add some info in a phrase you can use standard Lua string
|
||||
formatting arguments:
|
||||
print(L("areaDeleteConfirm", "Test"))
|
||||
> Are you sure you want to delete the area "Test"?
|
||||
|
||||
Phrases are also usable on the server, but only when trying to localize a phrase based on a client's preferences. The server
|
||||
does not have a set language. An example:
|
||||
Entity(1):ChatPrint(L("areaEditMode"))
|
||||
> -- "Area Edit Mode" will print in the player's chatbox
|
||||
]]
|
||||
-- @module ix.lang
|
||||
|
||||
ix.lang = ix.lang or {}
|
||||
ix.lang.stored = ix.lang.stored or {}
|
||||
ix.lang.names = ix.lang.names or {}
|
||||
|
||||
--- Loads language files from a directory.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @string directory Directory to load language files from
|
||||
function ix.lang.LoadFromDir(directory)
|
||||
for _, v in ipairs(file.Find(directory.."/sh_*.lua", "LUA")) do
|
||||
local niceName = v:sub(4, -5):lower()
|
||||
|
||||
ix.util.Include(directory.."/"..v, "shared")
|
||||
|
||||
if (LANGUAGE) then
|
||||
if (NAME) then
|
||||
ix.lang.names[niceName] = NAME
|
||||
NAME = nil
|
||||
end
|
||||
|
||||
ix.lang.AddTable(niceName, LANGUAGE)
|
||||
LANGUAGE = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Adds phrases to a language. This is used when you aren't adding entries through the files in the `languages/` folder. A
|
||||
-- common use case is adding language phrases in a single-file plugin.
|
||||
-- @realm shared
|
||||
-- @string language The ID of the language
|
||||
-- @tab data Language data to add to the given language
|
||||
-- @usage ix.lang.AddTable("english", {
|
||||
-- myPhrase = "My Phrase"
|
||||
-- })
|
||||
function ix.lang.AddTable(language, data)
|
||||
language = tostring(language):lower()
|
||||
ix.lang.stored[language] = table.Merge(ix.lang.stored[language] or {}, data)
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
-- luacheck: globals L
|
||||
function L(key, arg, ...)
|
||||
local defaultLang = ix.config.language or "french"
|
||||
local languages = ix.lang.stored
|
||||
|
||||
if (IsEntity(arg) and arg:IsPlayer()) then
|
||||
local langKey = ix.option.Get(arg, "language", defaultLang)
|
||||
local info = languages[langKey] or languages[defaultLang]
|
||||
|
||||
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, ...)
|
||||
else
|
||||
local info = languages[defaultLang]
|
||||
|
||||
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, arg, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- luacheck: globals L2
|
||||
function L2(key, arg, ...)
|
||||
local defaultLang = ix.config.language or "french"
|
||||
local languages = ix.lang.stored
|
||||
|
||||
if (IsEntity(arg) and arg:IsPlayer()) then
|
||||
local langKey = ix.option.Get(arg, "language", defaultLang)
|
||||
local info = languages[langKey] or languages[defaultLang]
|
||||
|
||||
if (info and info[key]) then
|
||||
return string.format(info[key], ...)
|
||||
end
|
||||
else
|
||||
local info = ix.lang.stored[defaultLang]
|
||||
|
||||
if (info and info[key]) then
|
||||
return string.format(info[key], arg, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
function L(key, ...)
|
||||
local defaultLang = ix.config.language or "french"
|
||||
local languages = ix.lang.stored
|
||||
local langKey = ix.option.Get("language", defaultLang)
|
||||
local info = languages[langKey] or languages[defaultLang]
|
||||
|
||||
return string.format(info and info[key] or languages[defaultLang][key] or languages.english[key] or key, ...)
|
||||
end
|
||||
|
||||
function L2(key, ...)
|
||||
local defaultLang = ix.config.language or "french"
|
||||
local langKey = ix.option.Get("language", defaultLang)
|
||||
local info = ix.lang.stored[langKey]
|
||||
|
||||
if (info and info[key]) then
|
||||
return string.format(info[key], ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
169
gamemodes/helix/gamemode/core/libs/sh_log.lua
Normal file
169
gamemodes/helix/gamemode/core/libs/sh_log.lua
Normal file
@@ -0,0 +1,169 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Logging helper functions.
|
||||
|
||||
Predefined flags:
|
||||
FLAG_NORMAL
|
||||
FLAG_SUCCESS
|
||||
FLAG_WARNING
|
||||
FLAG_DANGER
|
||||
FLAG_SERVER
|
||||
FLAG_DEV
|
||||
]]
|
||||
-- @module ix.log
|
||||
|
||||
-- luacheck: globals FLAG_NORMAL FLAG_SUCCESS FLAG_WARNING FLAG_DANGER FLAG_SERVER FLAG_DEV
|
||||
FLAG_NORMAL = 0
|
||||
FLAG_SUCCESS = 1
|
||||
FLAG_WARNING = 2
|
||||
FLAG_DANGER = 3
|
||||
FLAG_SERVER = 4
|
||||
FLAG_DEV = 5
|
||||
|
||||
ix.log = ix.log or {}
|
||||
ix.log.color = {
|
||||
[FLAG_NORMAL] = Color(200, 200, 200),
|
||||
[FLAG_SUCCESS] = Color(50, 200, 50),
|
||||
[FLAG_WARNING] = Color(255, 255, 0),
|
||||
[FLAG_DANGER] = Color(255, 50, 50),
|
||||
[FLAG_SERVER] = Color(200, 200, 220),
|
||||
[FLAG_DEV] = Color(200, 200, 220),
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Logs",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
|
||||
local consoleColor = Color(50, 200, 50)
|
||||
|
||||
if (SERVER) then
|
||||
if (!ix.db) then
|
||||
include("sv_database.lua")
|
||||
end
|
||||
|
||||
util.AddNetworkString("ixLogStream")
|
||||
|
||||
function ix.log.LoadTables()
|
||||
ix.log.CallHandler("Load")
|
||||
end
|
||||
|
||||
ix.log.types = ix.log.types or {}
|
||||
|
||||
--- Adds a log type
|
||||
-- @realm server
|
||||
-- @string logType Log category
|
||||
-- @string format The string format that log messages should use
|
||||
-- @number flag Log level
|
||||
function ix.log.AddType(logType, format, flag)
|
||||
ix.log.types[logType] = {format = format, flag = flag}
|
||||
end
|
||||
|
||||
function ix.log.Parse(client, logType, ...)
|
||||
local info = ix.log.types[logType]
|
||||
|
||||
if (!info) then
|
||||
ErrorNoHalt("attempted to add entry to non-existent log type \"" .. tostring(logType) .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
local text = info and info.format
|
||||
|
||||
if (text) then
|
||||
if (isfunction(text)) then
|
||||
text = text(client, ...)
|
||||
end
|
||||
else
|
||||
text = -1
|
||||
end
|
||||
|
||||
return text, info.flag
|
||||
end
|
||||
|
||||
function ix.log.AddRaw(logString, bNoSave)
|
||||
CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers)
|
||||
ix.log.Send(receivers, logString)
|
||||
end)
|
||||
|
||||
Msg("[LOG] ", logString .. "\n")
|
||||
|
||||
if (!bNoSave) then
|
||||
ix.log.CallHandler("Write", nil, logString)
|
||||
end
|
||||
end
|
||||
|
||||
--- Add a log message
|
||||
-- @realm server
|
||||
-- @player client Player who instigated the log
|
||||
-- @string logType Log category
|
||||
-- @param ... Arguments to pass to the log
|
||||
function ix.log.Add(client, logType, ...)
|
||||
local logString, logFlag = ix.log.Parse(client, logType, ...)
|
||||
if (logString == -1) then return end
|
||||
|
||||
CAMI.GetPlayersWithAccess("Helix - Logs", function(receivers)
|
||||
ix.log.Send(receivers, logString, logFlag)
|
||||
end)
|
||||
|
||||
Msg("[LOG] ", logString .. "\n")
|
||||
|
||||
ix.log.CallHandler("Write", client, logString, logFlag, logType, {...})
|
||||
end
|
||||
|
||||
function ix.log.Send(client, logString, flag)
|
||||
net.Start("ixLogStream")
|
||||
net.WriteString(logString)
|
||||
net.WriteUInt(flag or 0, 4)
|
||||
net.Send(client)
|
||||
end
|
||||
|
||||
ix.log.handlers = ix.log.handlers or {}
|
||||
function ix.log.CallHandler(event, ...)
|
||||
for _, v in pairs(ix.log.handlers) do
|
||||
if (isfunction(v[event])) then
|
||||
v[event](...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function ix.log.RegisterHandler(name, data)
|
||||
data.name = string.gsub(name, "%s", "")
|
||||
name = name:lower()
|
||||
data.uniqueID = name
|
||||
|
||||
ix.log.handlers[name] = data
|
||||
end
|
||||
|
||||
do
|
||||
local HANDLER = {}
|
||||
|
||||
function HANDLER.Load()
|
||||
file.CreateDir("helix/logs")
|
||||
end
|
||||
|
||||
function HANDLER.Write(client, message)
|
||||
file.Append("helix/logs/" .. os.date("%x"):gsub("/", "-") .. ".txt", "[" .. os.date("%X") .. "]\t" .. message .. "\r\n")
|
||||
end
|
||||
|
||||
ix.log.RegisterHandler("File", HANDLER)
|
||||
end
|
||||
else
|
||||
net.Receive("ixLogStream", function(length)
|
||||
local logString = net.ReadString()
|
||||
local flag = net.ReadUInt(4)
|
||||
|
||||
if (isstring(logString) and isnumber(flag)) then
|
||||
MsgC(consoleColor, "[SERVEUR] ", ix.log.color[flag], logString .. "\n")
|
||||
end
|
||||
end)
|
||||
end
|
||||
119
gamemodes/helix/gamemode/core/libs/sh_menu.lua
Normal file
119
gamemodes/helix/gamemode/core/libs/sh_menu.lua
Normal file
@@ -0,0 +1,119 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Entity menu manipulation.
|
||||
|
||||
The `menu` library allows you to open up a context menu of arbitrary options whose callbacks will be ran when they are selected
|
||||
from the panel that shows up for the player.
|
||||
]]
|
||||
-- @module ix.menu
|
||||
|
||||
--- You'll need to pass a table of options to `ix.menu.Open` to populate the menu. This table consists of strings as its keys
|
||||
-- and functions as its values. These correspond to the text displayed in the menu and the callback to run, respectively.
|
||||
--
|
||||
-- Example usage:
|
||||
-- ix.menu.Open({
|
||||
-- Drink = function()
|
||||
-- print("Drink option selected!")
|
||||
-- end,
|
||||
-- Take = function()
|
||||
-- print("Take option selected!")
|
||||
-- end
|
||||
-- }, ents.GetByIndex(1))
|
||||
-- This opens a menu with the options `"Drink"` and `"Take"` which will print a message when you click on either of the options.
|
||||
-- @realm client
|
||||
-- @table MenuOptionsStructure
|
||||
|
||||
ix.menu = ix.menu or {}
|
||||
|
||||
if (CLIENT) then
|
||||
--- Opens up a context menu for the given entity.
|
||||
-- @realm client
|
||||
-- @tparam MenuOptionsStructure options Data describing what options to display
|
||||
-- @entity[opt] entity Entity to send commands to
|
||||
-- @treturn boolean Whether or not the menu opened successfully. It will fail when there is already a menu open.
|
||||
function ix.menu.Open(options, entity)
|
||||
if (IsValid(ix.menu.panel)) then
|
||||
return false
|
||||
end
|
||||
|
||||
local panel = vgui.Create("ixEntityMenu")
|
||||
panel:SetEntity(entity)
|
||||
panel:SetOptions(options)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Checks whether or not an entity menu is currently open.
|
||||
-- @realm client
|
||||
-- @treturn boolean Whether or not an entity menu is open
|
||||
function ix.menu.IsOpen()
|
||||
return IsValid(ix.menu.panel)
|
||||
end
|
||||
|
||||
--- Notifies the server of an option that was chosen for the given entity.
|
||||
-- @realm client
|
||||
-- @entity entity Entity to call option on
|
||||
-- @string choice Option that was chosen
|
||||
-- @param data Extra data to send to the entity
|
||||
function ix.menu.NetworkChoice(entity, choice, data)
|
||||
if (IsValid(entity)) then
|
||||
net.Start("ixEntityMenuSelect")
|
||||
net.WriteEntity(entity)
|
||||
net.WriteString(choice)
|
||||
net.WriteType(data)
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
else
|
||||
util.AddNetworkString("ixEntityMenuSelect")
|
||||
|
||||
net.Receive("ixEntityMenuSelect", function(length, client)
|
||||
local entity = net.ReadEntity()
|
||||
local option = net.ReadString()
|
||||
local data = net.ReadType()
|
||||
|
||||
if (!IsValid(entity) or !isstring(option) or
|
||||
hook.Run("CanPlayerInteractEntity", client, entity, option, data) == false or
|
||||
entity:GetPos():Distance(client:GetPos()) > 96) then
|
||||
return
|
||||
end
|
||||
|
||||
hook.Run("PlayerInteractEntity", client, entity, option, data)
|
||||
|
||||
local callbackName = "OnSelect" .. option:gsub("%s", "")
|
||||
|
||||
if (entity[callbackName]) then
|
||||
entity[callbackName](entity, client, data)
|
||||
else
|
||||
entity:OnOptionSelected(client, option, data)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
do
|
||||
local PLAYER = FindMetaTable("Player")
|
||||
|
||||
if (CLIENT) then
|
||||
function PLAYER:GetEntityMenu()
|
||||
local options = {}
|
||||
|
||||
hook.Run("GetPlayerEntityMenu", self, options)
|
||||
return options
|
||||
end
|
||||
else
|
||||
function PLAYER:OnOptionSelected(client, option)
|
||||
hook.Run("OnPlayerOptionSelected", self, client, option)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
154
gamemodes/helix/gamemode/core/libs/sh_notice.lua
Normal file
154
gamemodes/helix/gamemode/core/libs/sh_notice.lua
Normal file
@@ -0,0 +1,154 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--- Notification helper functions
|
||||
-- @module ix.notice
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixNotify")
|
||||
util.AddNetworkString("ixNotifyLocalized")
|
||||
|
||||
--- Sends a notification to a specified recipient.
|
||||
-- @realm server
|
||||
-- @string message Message to notify
|
||||
-- @player[opt=nil] recipient Player to be notified
|
||||
function ix.util.Notify(message, recipient)
|
||||
net.Start("ixNotify")
|
||||
net.WriteString(message)
|
||||
|
||||
if (recipient == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(recipient)
|
||||
end
|
||||
end
|
||||
|
||||
--- Sends a translated notification to a specified recipient.
|
||||
-- @realm server
|
||||
-- @string message Message to notify
|
||||
-- @player[opt=nil] recipient Player to be notified
|
||||
-- @param ... Arguments to pass to the translated message
|
||||
function ix.util.NotifyLocalized(message, recipient, ...)
|
||||
net.Start("ixNotifyLocalized")
|
||||
net.WriteString(message)
|
||||
net.WriteTable({...})
|
||||
|
||||
if (recipient == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(recipient)
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
--- Notification util functions for players
|
||||
-- @classmod Player
|
||||
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
--- Displays a prominent notification in the top-right of this player's screen.
|
||||
-- @realm shared
|
||||
-- @string message Text to display in the notification
|
||||
function playerMeta:Notify(message)
|
||||
ix.util.Notify(message, self)
|
||||
end
|
||||
|
||||
--- Displays a notification for this player with the given language phrase.
|
||||
-- @realm shared
|
||||
-- @string message ID of the phrase to display to the player
|
||||
-- @param ... Arguments to pass to the phrase
|
||||
-- @usage client:NotifyLocalized("mapRestarting", 10)
|
||||
-- -- displays "The map will restart in 10 seconds!" if the player's language is set to English
|
||||
-- @see ix.lang
|
||||
function playerMeta:NotifyLocalized(message, ...)
|
||||
ix.util.NotifyLocalized(message, self, ...)
|
||||
end
|
||||
|
||||
--- Displays a notification for this player in the chatbox.
|
||||
-- @realm shared
|
||||
-- @string message Text to display in the notification
|
||||
function playerMeta:ChatNotify(message)
|
||||
ix.chat.Send(nil, "notice", message, false, {self})
|
||||
end
|
||||
|
||||
--- Displays a notification for this player in the chatbox with the given language phrase.
|
||||
-- @realm shared
|
||||
-- @string message ID of the phrase to display to the player
|
||||
-- @param ... Arguments to pass to the phrase
|
||||
-- @see NotifyLocalized
|
||||
function playerMeta:ChatNotifyLocalized(message, ...)
|
||||
ix.chat.Send(nil, "notice", L(message, self, ...), false, {self})
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Create a notification panel.
|
||||
function ix.util.Notify(message)
|
||||
if (ix.option.Get("chatNotices", false)) then
|
||||
local messageLength = message:utf8len()
|
||||
|
||||
ix.chat.Send(LocalPlayer(), "notice", message, false, {
|
||||
bError = message:utf8sub(messageLength, messageLength) == "!"
|
||||
})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.notices)) then
|
||||
ix.gui.notices:AddNotice(message)
|
||||
end
|
||||
|
||||
MsgC(Color(0, 255, 255), message .. "\n")
|
||||
end
|
||||
|
||||
-- Creates a translated notification.
|
||||
function ix.util.NotifyLocalized(message, ...)
|
||||
ix.util.Notify(L(message, ...))
|
||||
end
|
||||
|
||||
-- shortcut notify functions
|
||||
do
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
function playerMeta:Notify(message)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.util.Notify(message)
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:NotifyLocalized(message, ...)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.util.NotifyLocalized(message, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:ChatNotify(message)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.chat.Send(LocalPlayer(), "notice", message)
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:ChatNotifyLocalized(message, ...)
|
||||
if (self == LocalPlayer()) then
|
||||
ix.chat.Send(LocalPlayer(), "notice", L(message, ...))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Receives a notification from the server.
|
||||
net.Receive("ixNotify", function()
|
||||
ix.util.Notify(net.ReadString())
|
||||
end)
|
||||
|
||||
-- Receives a notification from the server.
|
||||
net.Receive("ixNotifyLocalized", function()
|
||||
ix.util.NotifyLocalized(net.ReadString(), unpack(net.ReadTable()))
|
||||
end)
|
||||
end
|
||||
381
gamemodes/helix/gamemode/core/libs/sh_option.lua
Normal file
381
gamemodes/helix/gamemode/core/libs/sh_option.lua
Normal file
@@ -0,0 +1,381 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Client-side configuration management.
|
||||
|
||||
The `option` library provides a cleaner way to manage any arbitrary data on the client without the hassle of managing CVars. It
|
||||
is analagous to the `ix.config` library, but it only deals with data that needs to be stored on the client.
|
||||
|
||||
To get started, you'll need to define an option in a client realm so the framework can be aware of its existence. This can be
|
||||
done in the `cl_init.lua` file of your schema, or in an `if (CLIENT) then` statement in the `sh_plugin.lua` file of your plugin:
|
||||
ix.option.Add("headbob", ix.type.bool, true)
|
||||
|
||||
If you need to get the value of an option on the server, you'll need to specify `true` for the `bNetworked` argument in
|
||||
`ix.option.Add`. *NOTE:* You also need to define your option in a *shared* realm, since the server now also needs to be aware
|
||||
of its existence. This makes it so that the client will send that option's value to the server whenever it changes, which then
|
||||
means that the server can now retrieve the value that the client has the option set to. For example, if you need to get what
|
||||
language a client is using, you can simply do the following:
|
||||
ix.option.Get(player.GetByID(1), "language", "english")
|
||||
|
||||
This will return the language of the player, or `"english"` if one isn't found. Note that `"language"` is a networked option
|
||||
that is already defined in the framework, so it will always be available. All options will show up in the options menu on the
|
||||
client, unless `hidden` returns `true` when using `ix.option.Add`.
|
||||
|
||||
Note that the labels for each option in the menu will use a language phrase to show the name. For example, if your option is
|
||||
named `headbob`, then you'll need to define a language phrase called `optHeadbob` that will be used as the option title.
|
||||
]]
|
||||
-- @module ix.option
|
||||
|
||||
ix.option = ix.option or {}
|
||||
ix.option.stored = ix.option.stored or {}
|
||||
ix.option.categories = ix.option.categories or {}
|
||||
|
||||
--- Creates a client-side configuration option with the given information.
|
||||
-- @realm shared
|
||||
-- @string key Unique ID for this option
|
||||
-- @ixtype optionType Type of this option
|
||||
-- @param default Default value that this option will have - this can be nil if needed
|
||||
-- @tparam OptionStructure data Additional settings for this option
|
||||
-- @usage ix.option.Add("animationScale", ix.type.number, 1, {
|
||||
-- category = "appearance",
|
||||
-- min = 0.3,
|
||||
-- max = 2,
|
||||
-- decimals = 1
|
||||
-- })
|
||||
function ix.option.Add(key, optionType, default, data)
|
||||
assert(isstring(key) and key:find("%S"), "expected a non-empty string for the key")
|
||||
|
||||
data = data or {}
|
||||
|
||||
local categories = ix.option.categories
|
||||
local category = data.category or "misc"
|
||||
local upperName = key:sub(1, 1):upper() .. key:sub(2)
|
||||
|
||||
categories[category] = categories[category] or {}
|
||||
categories[category][key] = true
|
||||
|
||||
--- You can specify additional optional arguments for `ix.option.Add` by passing in a table of specific fields as the fourth
|
||||
-- argument.
|
||||
-- @table OptionStructure
|
||||
-- @realm shared
|
||||
-- @field[type=string,opt="opt" .. key] phrase The phrase to use when displaying in the UI. The default value is your option
|
||||
-- key in UpperCamelCase, prefixed with `"opt"`. For example, if your key is `"exampleOption"`, the default phrase will be
|
||||
-- `"optExampleOption"`.
|
||||
-- @field[type=string,opt="optd" .. key] description The phrase to use in the tooltip when hovered in the UI. The default
|
||||
-- value is your option key in UpperCamelCase, prefixed with `"optd"`. For example, if your key is `"exampleOption"`, the
|
||||
-- default phrase will be `"optdExampleOption"`.
|
||||
-- @field[type=string,opt="misc"] category The category that this option should reside in. This is purely for
|
||||
-- aesthetic reasons when displaying the options in the options menu. When displayed in the UI, it will take the form of
|
||||
-- `L("category name")`. This means that you must create a language phrase for the category name - otherwise it will only
|
||||
-- show as the exact string you've specified. If no category is set, it will default to `"misc"`.
|
||||
-- @field[type=number,opt=0] min The minimum allowed amount when setting this option. This field is not
|
||||
-- applicable to any type other than `ix.type.number`.
|
||||
-- @field[type=number,opt=10] max The maximum allowed amount when setting this option. This field is not
|
||||
-- applicable to any type other than `ix.type.number`.
|
||||
-- @field[type=number,opt=0] decimals How many decimals to constrain to when using a number type. This field is not
|
||||
-- applicable to any type other than `ix.type.number`.
|
||||
-- @field[type=boolean,opt=false] bNetworked Whether or not the server should be aware of this option for each client.
|
||||
-- @field[type=function,opt] OnChanged The function to run when this option is changed - this includes whether it was set
|
||||
-- by the player, or through code using `ix.option.Set`.
|
||||
-- OnChanged = function(oldValue, value)
|
||||
-- print("new value is", value)
|
||||
-- end
|
||||
-- @field[type=function,opt] hidden The function to check whether the option should be hidden from the options menu.
|
||||
-- @field[type=function,opt] populate The function to run when the option needs to be added to the menu. This is a required
|
||||
-- field for any array options. It should return a table of entries where the key is the value to set in `ix.option.Set`,
|
||||
-- and the value is the display name for the entry. An example:
|
||||
--
|
||||
-- populate = function()
|
||||
-- return {
|
||||
-- ["english"] = "English",
|
||||
-- ["french"] = "French",
|
||||
-- ["spanish"] = "Spanish"
|
||||
-- }
|
||||
-- end
|
||||
ix.option.stored[key] = {
|
||||
key = key,
|
||||
phrase = "opt" .. upperName,
|
||||
description = "optd" .. upperName,
|
||||
type = optionType,
|
||||
default = default,
|
||||
min = data.min or 0,
|
||||
max = data.max or 10,
|
||||
decimals = data.decimals or 0,
|
||||
category = data.category or "misc",
|
||||
bNetworked = data.bNetworked and true or false,
|
||||
hidden = data.hidden or nil,
|
||||
populate = data.populate or nil,
|
||||
OnChanged = data.OnChanged or nil
|
||||
}
|
||||
end
|
||||
|
||||
--- Loads all saved options from disk.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
function ix.option.Load()
|
||||
ix.util.Include("helix/gamemode/config/sh_options.lua")
|
||||
|
||||
if (CLIENT) then
|
||||
local options = ix.data.Get("options", nil, true, true)
|
||||
|
||||
if (options) then
|
||||
for k, v in pairs(options) do
|
||||
ix.option.client[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
ix.option.Sync()
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns all of the available options. Note that this does contain the actual values of the options, just their properties.
|
||||
-- @realm shared
|
||||
-- @treturn table Table of all options
|
||||
-- @usage PrintTable(ix.option.GetAll())
|
||||
-- > language:
|
||||
-- > bNetworked = true
|
||||
-- > default = english
|
||||
-- > type = 512
|
||||
-- -- etc.
|
||||
function ix.option.GetAll()
|
||||
return ix.option.stored
|
||||
end
|
||||
|
||||
|
||||
--- Returns all of the available options grouped by their categories. The returned table contains category tables, that contain
|
||||
-- all the options in that category as an array (this is so you can sort them if you'd like).
|
||||
-- @realm shared
|
||||
-- @bool[opt=false] bRemoveHidden Remove entries that are marked as hidden
|
||||
-- @treturn table Table of all options
|
||||
-- @usage PrintTable(ix.option.GetAllByCategories())
|
||||
-- > general:
|
||||
-- > 1:
|
||||
-- > key = language
|
||||
-- > bNetworked = true
|
||||
-- > default = english
|
||||
-- > type = 512
|
||||
-- -- etc.
|
||||
function ix.option.GetAllByCategories(bRemoveHidden)
|
||||
local result = {}
|
||||
|
||||
for k, v in pairs(ix.option.categories) do
|
||||
for k2, _ in pairs(v) do
|
||||
local option = ix.option.stored[k2]
|
||||
|
||||
if (bRemoveHidden and isfunction(option.hidden) and option.hidden()) then
|
||||
continue
|
||||
end
|
||||
|
||||
-- we create the category table here because it could contain all hidden options which makes the table empty
|
||||
result[k] = result[k] or {}
|
||||
result[k][#result[k] + 1] = option
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
if (CLIENT) then
|
||||
ix.option.client = ix.option.client or {}
|
||||
|
||||
--- Sets an option value for the local player.
|
||||
-- This function will error when an invalid key is passed.
|
||||
-- @realm client
|
||||
-- @string key Unique ID of the option
|
||||
-- @param value New value to assign to the option
|
||||
-- @bool[opt=false] bNoSave Whether or not to avoid saving
|
||||
-- @bool[opt=false] bNoNetwork Whether or not to avoid networking regardless of whether the option is networked or not
|
||||
function ix.option.Set(key, value, bNoSave, bNoNetwork)
|
||||
local option = assert(ix.option.stored[key], "invalid option key \"" .. tostring(key) .. "\"")
|
||||
|
||||
if (option.type == ix.type.number) then
|
||||
value = math.Clamp(math.Round(value, option.decimals), option.min, option.max)
|
||||
end
|
||||
|
||||
local oldValue = ix.option.client[key]
|
||||
ix.option.client[key] = value
|
||||
|
||||
if (option.bNetworked and !bNoNetwork) then
|
||||
net.Start("ixOptionSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
if (!bNoSave) then
|
||||
ix.option.Save()
|
||||
end
|
||||
|
||||
if (isfunction(option.OnChanged)) then
|
||||
option.OnChanged(oldValue, value)
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("ixOptionSet", function()
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
local bNoSave = net.ReadBool()
|
||||
|
||||
ix.option.Set(key, value, bNoSave, true)
|
||||
end)
|
||||
|
||||
--- Retrieves an option value for the local player. If it is not set, it'll return the default that you've specified.
|
||||
-- @realm client
|
||||
-- @string key Unique ID of the option
|
||||
-- @param default Default value to return if the option is not set
|
||||
-- @return[1] Value associated with the key
|
||||
-- @return[2] The given default if the option is not set
|
||||
function ix.option.Get(key, default)
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
local localValue = ix.option.client[key]
|
||||
|
||||
if (localValue != nil) then
|
||||
return localValue
|
||||
end
|
||||
|
||||
return option.default
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Saves all options to disk.
|
||||
-- @realm client
|
||||
-- @internal
|
||||
function ix.option.Save()
|
||||
ix.data.Set("options", ix.option.client, true, true)
|
||||
end
|
||||
|
||||
--- Syncs all networked options to the server.
|
||||
-- @realm client
|
||||
function ix.option.Sync()
|
||||
local options = {}
|
||||
|
||||
for k, v in pairs(ix.option.stored) do
|
||||
if (v.bNetworked) then
|
||||
options[#options + 1] = {k, ix.option.client[k]}
|
||||
end
|
||||
end
|
||||
|
||||
if (#options > 0) then
|
||||
net.Start("ixOptionSync")
|
||||
net.WriteUInt(#options, 8)
|
||||
|
||||
for _, v in ipairs(options) do
|
||||
net.WriteString(v[1])
|
||||
net.WriteType(v[2])
|
||||
end
|
||||
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
else
|
||||
util.AddNetworkString("ixOptionSet")
|
||||
util.AddNetworkString("ixOptionSync")
|
||||
|
||||
ix.option.clients = ix.option.clients or {}
|
||||
|
||||
--- Retrieves an option value from the specified player. If it is not set, it'll return the default that you've specified.
|
||||
-- This function will error when an invalid player is passed.
|
||||
-- @realm server
|
||||
-- @player client Player to retrieve option value from
|
||||
-- @string key Unique ID of the option
|
||||
-- @param default Default value to return if the option is not set
|
||||
-- @return[1] Value associated with the key
|
||||
-- @return[2] The given default if the option is not set
|
||||
function ix.option.Get(client, key, default)
|
||||
assert(IsValid(client) and client:IsPlayer(), "expected valid player for argument #1")
|
||||
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
local clientOptions = ix.option.clients[client:SteamID64()]
|
||||
|
||||
if (clientOptions) then
|
||||
local clientOption = clientOptions[key]
|
||||
|
||||
if (clientOption != nil) then
|
||||
return clientOption
|
||||
end
|
||||
end
|
||||
|
||||
return option.default
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
function ix.option.Set(client, key, value, bNoSave)
|
||||
local steamID = client:SteamID64()
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
if (option.bNetworked) then
|
||||
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
|
||||
ix.option.clients[steamID][key] = value
|
||||
end
|
||||
|
||||
net.Start("ixOptionSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.WriteBool(bNoSave)
|
||||
net.Send(client)
|
||||
else
|
||||
ErrorNoHalt(string.format("Attempted to set option with invalid key '%s' for '%s'\n", key, tostring(client) .. client:SteamID()))
|
||||
end
|
||||
end
|
||||
|
||||
-- sent whenever a client's networked option has changed
|
||||
net.Receive("ixOptionSet", function(length, client)
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
|
||||
local steamID = client:SteamID64()
|
||||
local option = ix.option.stored[key]
|
||||
|
||||
if (option) then
|
||||
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
|
||||
ix.option.clients[steamID][key] = value
|
||||
else
|
||||
ErrorNoHalt(string.format(
|
||||
"'%s' attempted to set option with invalid key '%s'\n", tostring(client) .. client:SteamID(), key
|
||||
))
|
||||
end
|
||||
end)
|
||||
|
||||
-- sent on first load to sync all networked option values
|
||||
net.Receive("ixOptionSync", function(length, client)
|
||||
local indices = net.ReadUInt(8)
|
||||
local data = {}
|
||||
|
||||
for _ = 1, indices do
|
||||
data[net.ReadString()] = net.ReadType()
|
||||
end
|
||||
|
||||
local steamID = client:SteamID64()
|
||||
ix.option.clients[steamID] = ix.option.clients[steamID] or {}
|
||||
|
||||
for k, v in pairs(data) do
|
||||
local option = ix.option.stored[k]
|
||||
|
||||
if (option) then
|
||||
ix.option.clients[steamID][k] = v
|
||||
else
|
||||
return ErrorNoHalt(string.format(
|
||||
"'%s' attempted to sync option with invalid key '%s'\n", tostring(client) .. client:SteamID(), k
|
||||
))
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
153
gamemodes/helix/gamemode/core/libs/sh_player.lua
Normal file
153
gamemodes/helix/gamemode/core/libs/sh_player.lua
Normal file
@@ -0,0 +1,153 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
-- ixData information for the player.
|
||||
do
|
||||
if (SERVER) then
|
||||
function playerMeta:GetData(key, default)
|
||||
if (key == true) then
|
||||
return self.ixData
|
||||
end
|
||||
|
||||
local data = self.ixData and self.ixData[key]
|
||||
|
||||
if (data == nil) then
|
||||
return default
|
||||
else
|
||||
return data
|
||||
end
|
||||
end
|
||||
else
|
||||
function playerMeta:GetData(key, default)
|
||||
local data = ix.localData and ix.localData[key]
|
||||
|
||||
if (data == nil) then
|
||||
return default
|
||||
else
|
||||
return data
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("ixDataSync", function()
|
||||
ix.localData = net.ReadTable()
|
||||
ix.playTime = net.ReadUInt(32)
|
||||
end)
|
||||
|
||||
net.Receive("ixData", function()
|
||||
ix.localData = ix.localData or {}
|
||||
ix.localData[net.ReadString()] = net.ReadType()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- Whitelist networking information here.
|
||||
do
|
||||
function playerMeta:HasWhitelist(faction)
|
||||
local data = ix.faction.indices[faction]
|
||||
|
||||
if (data) then
|
||||
if (data.isDefault) then
|
||||
return true
|
||||
end
|
||||
|
||||
local ixData = self:GetData("whitelists", {})
|
||||
|
||||
return ixData[Schema.folder] and ixData[Schema.folder][data.uniqueID] == true or false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function playerMeta:GetItems()
|
||||
local char = self:GetCharacter()
|
||||
|
||||
if (char) then
|
||||
local inv = char:GetInventory()
|
||||
|
||||
if (inv) then
|
||||
return inv:GetItems()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function playerMeta:GetClassData()
|
||||
local char = self:GetCharacter()
|
||||
|
||||
if (char) then
|
||||
local class = char:GetClass()
|
||||
|
||||
if (class) then
|
||||
local classData = ix.class.list[class]
|
||||
|
||||
return classData
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("PlayerModelChanged")
|
||||
util.AddNetworkString("PlayerSelectWeapon")
|
||||
|
||||
local entityMeta = FindMetaTable("Entity")
|
||||
|
||||
entityMeta.ixSetModel = entityMeta.ixSetModel or entityMeta.SetModel
|
||||
playerMeta.ixSelectWeapon = playerMeta.ixSelectWeapon or playerMeta.SelectWeapon
|
||||
|
||||
function entityMeta:SetModel(model)
|
||||
local oldModel = self:GetModel()
|
||||
|
||||
if (self:IsPlayer()) then
|
||||
hook.Run("PlayerModelChanged", self, model, oldModel)
|
||||
|
||||
net.Start("PlayerModelChanged")
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(model)
|
||||
net.WriteString(oldModel)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
return self:ixSetModel(model)
|
||||
end
|
||||
|
||||
function playerMeta:SelectWeapon(className)
|
||||
net.Start("PlayerSelectWeapon")
|
||||
net.WriteEntity(self)
|
||||
net.WriteString(className)
|
||||
net.Broadcast()
|
||||
|
||||
return self:ixSelectWeapon(className)
|
||||
end
|
||||
else
|
||||
net.Receive("PlayerModelChanged", function(length)
|
||||
hook.Run("PlayerModelChanged", net.ReadEntity(), net.ReadString(), net.ReadString())
|
||||
end)
|
||||
|
||||
net.Receive("PlayerSelectWeapon", function(length)
|
||||
local client = net.ReadEntity()
|
||||
local className = net.ReadString()
|
||||
|
||||
if (!IsValid(client)) then
|
||||
hook.Run("PlayerWeaponChanged", client, NULL)
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in ipairs(client:GetWeapons()) do
|
||||
if (v:GetClass() == className) then
|
||||
hook.Run("PlayerWeaponChanged", client, v)
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
526
gamemodes/helix/gamemode/core/libs/sh_plugin.lua
Normal file
526
gamemodes/helix/gamemode/core/libs/sh_plugin.lua
Normal file
@@ -0,0 +1,526 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
ix.plugin = ix.plugin or {}
|
||||
ix.plugin.list = ix.plugin.list or {}
|
||||
ix.plugin.unloaded = ix.plugin.unloaded or {}
|
||||
|
||||
ix.util.Include("helix/gamemode/core/meta/sh_tool.lua")
|
||||
|
||||
-- luacheck: globals HOOKS_CACHE
|
||||
HOOKS_CACHE = {}
|
||||
|
||||
local function insertSorted(tbl, plugin, func, priority)
|
||||
if (IX_RELOADED) then
|
||||
-- Clean out the old function from the table
|
||||
for i = 1, #tbl do
|
||||
if (tbl[i][1] == plugin) then
|
||||
table.remove(tbl, i)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Attempt to insert into an empty table or at the end first
|
||||
if (#tbl == 0 or tbl[#tbl][3] >= priority) then
|
||||
tbl[#tbl + 1] = {plugin, func, priority}
|
||||
return
|
||||
end
|
||||
|
||||
-- Find where to insert
|
||||
for i = #tbl - 1, 1, -1 do
|
||||
if (tbl[i][3] >= priority) then
|
||||
table.insert(tbl, i + 1, {plugin, func, priority})
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Insert at the front
|
||||
table.insert(tbl, 1, {plugin, func, priority})
|
||||
end
|
||||
|
||||
function ix.plugin.Load(uniqueID, path, isSingleFile, variable)
|
||||
if (hook.Run("PluginShouldLoad", uniqueID) == false) then return end
|
||||
|
||||
variable = variable or "PLUGIN"
|
||||
|
||||
-- Plugins within plugins situation?
|
||||
local oldPlugin = PLUGIN
|
||||
local PLUGIN = {
|
||||
folder = path,
|
||||
plugin = oldPlugin,
|
||||
uniqueID = uniqueID,
|
||||
name = "Unknown",
|
||||
description = "Description not available",
|
||||
author = "Anonymous"
|
||||
}
|
||||
|
||||
if (uniqueID == "schema") then
|
||||
if (Schema) then
|
||||
PLUGIN = Schema
|
||||
end
|
||||
|
||||
variable = "Schema"
|
||||
PLUGIN.folder = engine.ActiveGamemode()
|
||||
elseif (ix.plugin.list[uniqueID]) then
|
||||
PLUGIN = ix.plugin.list[uniqueID]
|
||||
end
|
||||
|
||||
_G[variable] = PLUGIN
|
||||
PLUGIN.loading = true
|
||||
|
||||
if (!isSingleFile) then
|
||||
ix.lang.LoadFromDir(path.."/languages")
|
||||
ix.util.IncludeDir(path.."/libs", true)
|
||||
ix.attributes.LoadFromDir(path.."/attributes")
|
||||
ix.faction.LoadFromDir(path.."/factions")
|
||||
ix.class.LoadFromDir(path.."/classes")
|
||||
ix.item.LoadFromDir(path.."/items")
|
||||
ix.plugin.LoadFromDir(path.."/plugins")
|
||||
ix.util.IncludeDir(path.."/derma", true)
|
||||
ix.plugin.LoadEntities(path.."/entities")
|
||||
|
||||
hook.Run("DoPluginIncludes", path, PLUGIN)
|
||||
end
|
||||
|
||||
ix.util.Include(isSingleFile and path or path.."/sh_"..variable:lower()..".lua", "shared")
|
||||
PLUGIN.loading = false
|
||||
|
||||
local uniqueID2 = uniqueID
|
||||
|
||||
if (uniqueID2 == "schema") then
|
||||
uniqueID2 = PLUGIN.name
|
||||
end
|
||||
|
||||
function PLUGIN:SetData(value, global, ignoreMap)
|
||||
ix.data.Set(uniqueID2, value, global, ignoreMap)
|
||||
end
|
||||
|
||||
function PLUGIN:GetData(default, global, ignoreMap, refresh)
|
||||
return ix.data.Get(uniqueID2, default, global, ignoreMap, refresh) or {}
|
||||
end
|
||||
|
||||
hook.Run("PluginLoaded", uniqueID, PLUGIN)
|
||||
|
||||
if (uniqueID != "schema") then
|
||||
PLUGIN.name = PLUGIN.name or "Unknown"
|
||||
PLUGIN.description = PLUGIN.description or "Aucune description."
|
||||
|
||||
PLUGIN.hookCallPriority = PLUGIN.hookCallPriority or 1000
|
||||
for k, v in pairs(PLUGIN) do
|
||||
if (isfunction(v)) then
|
||||
HOOKS_CACHE[k] = HOOKS_CACHE[k] or {}
|
||||
insertSorted(HOOKS_CACHE[k], PLUGIN, v,
|
||||
(PLUGIN.GetHookCallPriority and PLUGIN:GetHookCallPriority(k))
|
||||
or PLUGIN.hookCallPriority)
|
||||
end
|
||||
end
|
||||
|
||||
ix.plugin.list[uniqueID] = PLUGIN
|
||||
_G[variable] = nil
|
||||
end
|
||||
|
||||
if (PLUGIN.OnLoaded) then
|
||||
PLUGIN:OnLoaded()
|
||||
end
|
||||
end
|
||||
|
||||
function ix.plugin.GetHook(pluginName, hookName)
|
||||
local h = HOOKS_CACHE[hookName]
|
||||
|
||||
if (h) then
|
||||
local p = ix.plugin.list[pluginName]
|
||||
|
||||
if (p) then
|
||||
for _, v in ipairs(h) do
|
||||
if (v[1] == p) then
|
||||
return v[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
function ix.plugin.LoadEntities(path)
|
||||
local bLoadedTools
|
||||
local files, folders
|
||||
|
||||
local function IncludeFiles(path2, bClientOnly)
|
||||
if (SERVER and !bClientOnly) then
|
||||
if (file.Exists(path2.."init.lua", "LUA")) then
|
||||
ix.util.Include(path2.."init.lua", "server")
|
||||
elseif (file.Exists(path2.."shared.lua", "LUA")) then
|
||||
ix.util.Include(path2.."shared.lua")
|
||||
end
|
||||
|
||||
if (file.Exists(path2.."cl_init.lua", "LUA")) then
|
||||
ix.util.Include(path2.."cl_init.lua", "client")
|
||||
end
|
||||
elseif (file.Exists(path2.."cl_init.lua", "LUA")) then
|
||||
ix.util.Include(path2.."cl_init.lua", "client")
|
||||
elseif (file.Exists(path2.."shared.lua", "LUA")) then
|
||||
ix.util.Include(path2.."shared.lua")
|
||||
end
|
||||
end
|
||||
|
||||
local function HandleEntityInclusion(folder, variable, register, default, clientOnly, create, complete)
|
||||
files, folders = file.Find(path.."/"..folder.."/*", "LUA")
|
||||
default = default or {}
|
||||
|
||||
for _, v in ipairs(folders) do
|
||||
local path2 = path.."/"..folder.."/"..v.."/"
|
||||
v = ix.util.StripRealmPrefix(v)
|
||||
|
||||
_G[variable] = table.Copy(default)
|
||||
|
||||
if (!isfunction(create)) then
|
||||
_G[variable].ClassName = v
|
||||
else
|
||||
create(v)
|
||||
end
|
||||
|
||||
IncludeFiles(path2, clientOnly)
|
||||
|
||||
if (clientOnly) then
|
||||
if (CLIENT) then
|
||||
register(_G[variable], v)
|
||||
end
|
||||
else
|
||||
register(_G[variable], v)
|
||||
end
|
||||
|
||||
if (isfunction(complete)) then
|
||||
complete(_G[variable])
|
||||
end
|
||||
|
||||
_G[variable] = nil
|
||||
end
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
local niceName = ix.util.StripRealmPrefix(string.StripExtension(v))
|
||||
|
||||
_G[variable] = table.Copy(default)
|
||||
|
||||
if (!isfunction(create)) then
|
||||
_G[variable].ClassName = niceName
|
||||
else
|
||||
create(niceName)
|
||||
end
|
||||
|
||||
ix.util.Include(path.."/"..folder.."/"..v, clientOnly and "client" or "shared")
|
||||
|
||||
if (clientOnly) then
|
||||
if (CLIENT) then
|
||||
register(_G[variable], niceName)
|
||||
end
|
||||
else
|
||||
register(_G[variable], niceName)
|
||||
end
|
||||
|
||||
if (isfunction(complete)) then
|
||||
complete(_G[variable])
|
||||
end
|
||||
|
||||
_G[variable] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function RegisterTool(tool, className)
|
||||
local gmodTool = weapons.GetStored("gmod_tool")
|
||||
|
||||
if (className:sub(1, 3) == "sh_") then
|
||||
className = className:sub(4)
|
||||
end
|
||||
|
||||
if (gmodTool) then
|
||||
gmodTool.Tool[className] = tool
|
||||
else
|
||||
-- this should never happen
|
||||
ErrorNoHalt(string.format("attempted to register tool '%s' with invalid gmod_tool weapon", className))
|
||||
end
|
||||
|
||||
bLoadedTools = true
|
||||
end
|
||||
|
||||
-- Include entities.
|
||||
HandleEntityInclusion("entities", "ENT", scripted_ents.Register, {
|
||||
Type = "anim",
|
||||
Base = "base_gmodentity",
|
||||
Spawnable = true
|
||||
}, false, nil, function(ent)
|
||||
if (SERVER and ent.Holdable == true) then
|
||||
ix.allowedHoldableClasses[ent.ClassName] = true
|
||||
end
|
||||
end)
|
||||
|
||||
-- Include weapons.
|
||||
HandleEntityInclusion("weapons", "SWEP", weapons.Register, {
|
||||
Primary = {},
|
||||
Secondary = {},
|
||||
Base = "weapon_base"
|
||||
})
|
||||
|
||||
HandleEntityInclusion("tools", "TOOL", RegisterTool, {}, false, function(className)
|
||||
if (className:sub(1, 3) == "sh_") then
|
||||
className = className:sub(4)
|
||||
end
|
||||
|
||||
TOOL = ix.meta.tool:Create()
|
||||
TOOL.Mode = className
|
||||
TOOL:CreateConVars()
|
||||
end)
|
||||
|
||||
-- Include effects.
|
||||
HandleEntityInclusion("effects", "EFFECT", effects and effects.Register, nil, true)
|
||||
|
||||
-- only reload spawn menu if any new tools were registered
|
||||
if (CLIENT and bLoadedTools) then
|
||||
RunConsoleCommand("spawnmenu_reload")
|
||||
end
|
||||
end
|
||||
|
||||
function ix.plugin.Initialize()
|
||||
ix.plugin.unloaded = ix.data.Get("unloaded", {}, true, true)
|
||||
|
||||
ix.plugin.LoadFromDir("helix/plugins")
|
||||
|
||||
ix.plugin.Load("schema", engine.ActiveGamemode().."/schema")
|
||||
hook.Run("InitializedSchema")
|
||||
|
||||
ix.plugin.LoadFromDir(engine.ActiveGamemode().."/plugins")
|
||||
hook.Run("InitializedPlugins")
|
||||
end
|
||||
|
||||
function ix.plugin.Get(identifier)
|
||||
return ix.plugin.list[identifier]
|
||||
end
|
||||
|
||||
function ix.plugin.LoadFromDir(directory)
|
||||
local files, folders = file.Find(directory.."/*", "LUA")
|
||||
|
||||
for _, v in ipairs(folders) do
|
||||
ix.plugin.Load(v, directory.."/"..v)
|
||||
end
|
||||
|
||||
for _, v in ipairs(files) do
|
||||
ix.plugin.Load(string.StripExtension(v), directory.."/"..v, true)
|
||||
end
|
||||
end
|
||||
|
||||
function ix.plugin.SetUnloaded(uniqueID, state, bNoSave)
|
||||
local plugin = ix.plugin.list[uniqueID]
|
||||
|
||||
if (state) then
|
||||
if (plugin and plugin.OnUnload) then
|
||||
plugin:OnUnload()
|
||||
end
|
||||
|
||||
ix.plugin.unloaded[uniqueID] = true
|
||||
elseif (ix.plugin.unloaded[uniqueID]) then
|
||||
ix.plugin.unloaded[uniqueID] = nil
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
if (SERVER and !bNoSave) then
|
||||
local status
|
||||
|
||||
if (state) then
|
||||
status = true
|
||||
end
|
||||
|
||||
local unloaded = ix.data.Get("unloaded", {}, true, true)
|
||||
unloaded[uniqueID] = status
|
||||
ix.data.Set("unloaded", unloaded, true, true)
|
||||
end
|
||||
|
||||
if (state) then
|
||||
hook.Run("PluginUnloaded", uniqueID)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Runs the `LoadData` and `PostLoadData` hooks for the gamemode, schema, and plugins. Any plugins that error during the
|
||||
-- hook will have their `SaveData` and `PostLoadData` hooks removed to prevent them from saving junk data.
|
||||
-- @internal
|
||||
-- @realm server
|
||||
function ix.plugin.RunLoadData()
|
||||
local errors = hook.SafeRun("LoadData")
|
||||
|
||||
-- remove the SaveData and PostLoadData hooks for any plugins that error during LoadData since they would probably be
|
||||
-- saving bad data. this doesn't prevent plugins from saving data via other means, but there's only so much we can do
|
||||
for _, v in pairs(errors or {}) do
|
||||
if (v.plugin) then
|
||||
local plugin = ix.plugin.Get(v.plugin)
|
||||
|
||||
if (plugin) then
|
||||
local saveDataHooks = HOOKS_CACHE["SaveData"] or {}
|
||||
saveDataHooks[plugin] = nil
|
||||
|
||||
local postLoadDataHooks = HOOKS_CACHE["PostLoadData"] or {}
|
||||
postLoadDataHooks[plugin] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("PostLoadData")
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
|
||||
-- luacheck: globals hook
|
||||
hook.ixCall = hook.ixCall or hook.Call
|
||||
local hookCall
|
||||
if (CLIENT) then
|
||||
hookCall = function(name, gm, ...)
|
||||
local cache = HOOKS_CACHE[name]
|
||||
|
||||
if (cache) then
|
||||
for i = 1, #cache do
|
||||
local a, b, c, d, e, f = cache[i][2](cache[i][1], ...)
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (Schema and Schema[name]) then
|
||||
local a, b, c, d, e, f = Schema[name](Schema, ...)
|
||||
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
|
||||
--luacheck: ignore global SwiftAC__N
|
||||
if (SwiftAC__N) then
|
||||
SwiftAC__N.hook.Call(name, gm, ...)
|
||||
else
|
||||
return hook.ixCall(name, gm, ...)
|
||||
end
|
||||
end
|
||||
|
||||
--SwiftAC compatibility
|
||||
hook.Run = function(name, ...)
|
||||
return hookCall(name, gmod and gmod.GetGamemode(), ...)
|
||||
end
|
||||
else
|
||||
hookCall = function(name, gm, ...)
|
||||
local cache = HOOKS_CACHE[name]
|
||||
|
||||
if (cache) then
|
||||
for i = 1, #cache do
|
||||
local a, b, c, d, e, f = cache[i][2](cache[i][1], ...)
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (Schema and Schema[name]) then
|
||||
local a, b, c, d, e, f = Schema[name](Schema, ...)
|
||||
|
||||
if (a != nil) then
|
||||
return a, b, c, d, e, f
|
||||
end
|
||||
end
|
||||
|
||||
return hook.ixCall(name, gm, ...)
|
||||
end
|
||||
end
|
||||
hook.Call = hookCall
|
||||
|
||||
|
||||
|
||||
--- Runs the given hook in a protected call so that the calling function will continue executing even if any errors occur
|
||||
-- while running the hook. This function is much more expensive to call than `hook.Run`, so you should avoid using it unless
|
||||
-- you absolutely need to avoid errors from stopping the execution of your function.
|
||||
-- @internal
|
||||
-- @realm shared
|
||||
-- @string name Name of the hook to run
|
||||
-- @param ... Arguments to pass to the hook functions
|
||||
-- @treturn[1] table Table of error data if an error occurred while running
|
||||
-- @treturn[1] ... Any arguments returned by the hook functions
|
||||
-- @usage local errors, bCanSpray = hook.SafeRun("PlayerSpray", Entity(1))
|
||||
-- if (!errors) then
|
||||
-- -- do stuff with bCanSpray
|
||||
-- else
|
||||
-- PrintTable(errors)
|
||||
-- end
|
||||
function hook.SafeRun(name, ...)
|
||||
local errors = {}
|
||||
local gm = gmod and gmod.GetGamemode() or nil
|
||||
local cache = HOOKS_CACHE[name]
|
||||
|
||||
if (cache) then
|
||||
for i = 1, #cache do
|
||||
local bSuccess, a, b, c, d, e, f = pcall(cache[i][2], cache[i][1], ...)
|
||||
|
||||
if (bSuccess) then
|
||||
if (a != nil) then
|
||||
return errors, a, b, c, d, e, f
|
||||
end
|
||||
else
|
||||
local plugin = cache[i][1]
|
||||
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for plugin hook \"%s:%s\":\n\t%s\n%s\n",
|
||||
tostring(plugin and plugin.uniqueID or nil), tostring(name), tostring(a), debug.traceback()))
|
||||
|
||||
errors[#errors + 1] = {
|
||||
name = name,
|
||||
plugin = plugin and plugin.uniqueID or nil,
|
||||
errorMessage = tostring(a)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (Schema and Schema[name]) then
|
||||
local bSuccess, a, b, c, d, e, f = pcall(Schema[name], Schema, ...)
|
||||
|
||||
if (bSuccess) then
|
||||
if (a != nil) then
|
||||
return errors, a, b, c, d, e, f
|
||||
end
|
||||
else
|
||||
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for schema hook \"%s\":\n\t%s\n%s\n",
|
||||
tostring(name), tostring(a), debug.traceback()))
|
||||
|
||||
errors[#errors + 1] = {
|
||||
name = name,
|
||||
schema = Schema.name,
|
||||
errorMessage = tostring(a)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local bSuccess, a, b, c, d, e, f = pcall(hook.ixCall, name, gm, ...)
|
||||
|
||||
if (bSuccess) then
|
||||
return errors, a, b, c, d, e, f
|
||||
else
|
||||
ErrorNoHalt(string.format("[Helix] hook.SafeRun error for gamemode hook \"%s\":\n\t%s\n%s\n",
|
||||
tostring(name), tostring(a), debug.traceback()))
|
||||
|
||||
errors[#errors + 1] = {
|
||||
name = name,
|
||||
gamemode = "gamemode",
|
||||
errorMessage = tostring(a)
|
||||
}
|
||||
|
||||
return errors
|
||||
end
|
||||
end
|
||||
end
|
||||
491
gamemodes/helix/gamemode/core/libs/sh_storage.lua
Normal file
491
gamemodes/helix/gamemode/core/libs/sh_storage.lua
Normal file
@@ -0,0 +1,491 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Player manipulation of inventories.
|
||||
|
||||
This library provides an easy way for players to manipulate other inventories. The only functions that you should need are
|
||||
`ix.storage.Open` and `ix.storage.Close`. When opening an inventory as a storage item, it will display both the given inventory
|
||||
and the player's inventory in the player's UI, which allows them to drag items to and from the given inventory.
|
||||
|
||||
Example usage:
|
||||
ix.storage.Open(client, inventory, {
|
||||
name = "Filing Cabinet",
|
||||
entity = ents.GetByIndex(3),
|
||||
bMultipleUsers = true,
|
||||
searchText = "Rummaging...",
|
||||
searchTime = 4
|
||||
})
|
||||
]]
|
||||
-- @module ix.storage
|
||||
|
||||
--- There are some parameters you can customize when opening an inventory as a storage object with `ix.storage.Open`.
|
||||
-- @realm server
|
||||
-- @table StorageInfoStructure
|
||||
-- @field[type=entity] entity Entity to "attach" the inventory to. This is used to provide a location for the inventory for
|
||||
-- things like making sure the player doesn't move too far away from the inventory, etc. This can also be a `player` object.
|
||||
-- @field[type=number,opt=inventory id] id The ID of the nventory. This defaults to the inventory passed into `ix.Storage.Open`.
|
||||
-- @field[type=string,opt="Storage"] name Title to display in the UI when the inventory is open.
|
||||
-- @field[type=boolean,opt=false] bMultipleUsers Whether or not multiple players are allowed to view this inventory at the
|
||||
-- same time.
|
||||
-- @field[type=number,opt=0] searchTime How long the player has to wait before the inventory is opened.
|
||||
-- @field[type=string,opt="@storageSearching"] text Text to display to the user while opening the inventory. If prefixed with
|
||||
-- `"@"`, it will display a language phrase.
|
||||
-- @field[type=function,opt] OnPlayerClose Called when a player who was accessing the inventory has closed it. The
|
||||
-- argument passed to the callback is the player who closed it.
|
||||
-- @field[type=table,opt={}] data Table of arbitrary data to send to the client when the inventory has been opened.
|
||||
|
||||
ix.storage = ix.storage or {}
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixStorageOpen")
|
||||
util.AddNetworkString("ixStorageClose")
|
||||
util.AddNetworkString("ixStorageExpired")
|
||||
util.AddNetworkString("ixStorageMoneyTake")
|
||||
util.AddNetworkString("ixStorageMoneyGive")
|
||||
util.AddNetworkString("ixStorageMoneyUpdate")
|
||||
|
||||
--- Returns whether or not the given inventory has a storage context and is being looked at by other players.
|
||||
-- @realm server
|
||||
-- @inventory inventory Inventory to check
|
||||
-- @treturn bool Whether or not `inventory` is in use
|
||||
function ix.storage.InUse(inventory)
|
||||
if (inventory.storageInfo) then
|
||||
for _, v in pairs(inventory:GetReceivers()) do
|
||||
if (IsValid(v) and v:IsPlayer() and v != inventory.storageInfo.entity) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns whether or not an inventory is in use by a specific player.
|
||||
-- @realm server
|
||||
-- @inventory inventory Inventory to check
|
||||
-- @player client Player to check
|
||||
-- @treturn bool Whether or not the player is using the given `inventory`
|
||||
function ix.storage.InUseBy(inventory, client)
|
||||
if (inventory.storageInfo) then
|
||||
for _, v in pairs(inventory:GetReceivers()) do
|
||||
if (IsValid(v) and v:IsPlayer() and v == client) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Creates a storage context on the given inventory.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @inventory inventory Inventory to create a storage context for
|
||||
-- @tab info Information to store on the context
|
||||
function ix.storage.CreateContext(inventory, info)
|
||||
info = info or {}
|
||||
|
||||
info.id = inventory:GetID()
|
||||
info.name = info.name or "Storage"
|
||||
info.entity = assert(IsValid(info.entity), "expected valid entity in info table") and info.entity
|
||||
info.bMultipleUsers = info.bMultipleUsers == nil and false or info.bMultipleUsers
|
||||
info.searchTime = tonumber(info.searchTime) or 0
|
||||
info.searchText = info.searchText or "@storageSearching"
|
||||
info.data = info.data or {}
|
||||
|
||||
inventory.storageInfo = info
|
||||
|
||||
-- remove context from any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
ix.storage.CreateContext(v:GetInventory(), table.Copy(info))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes a storage context from an inventory if it exists.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @inventory inventory Inventory to remove a storage context from
|
||||
function ix.storage.RemoveContext(inventory)
|
||||
inventory.storageInfo = nil
|
||||
|
||||
-- remove context from any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
ix.storage.RemoveContext(v:GetInventory())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Synchronizes an inventory with a storage context to the given client.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to sync storage for
|
||||
-- @inventory inventory Inventory to sync storage for
|
||||
function ix.storage.Sync(client, inventory)
|
||||
local info = inventory.storageInfo
|
||||
|
||||
-- we'll retrieve the money value as we're syncing because it may have changed while
|
||||
-- we were waiting for the timer to finish
|
||||
if (info.entity.GetMoney) then
|
||||
info.data.money = info.entity:GetMoney()
|
||||
elseif (info.entity:IsPlayer() and info.entity:GetCharacter()) then
|
||||
info.data.money = info.entity:GetCharacter():GetMoney()
|
||||
end
|
||||
|
||||
-- bags are automatically sync'd when the owning inventory is sync'd
|
||||
inventory:Sync(client)
|
||||
|
||||
net.Start("ixStorageOpen")
|
||||
net.WriteUInt(info.id, 32)
|
||||
net.WriteEntity(info.entity)
|
||||
net.WriteString(info.name)
|
||||
net.WriteTable(info.data)
|
||||
net.Send(client)
|
||||
end
|
||||
|
||||
--- Adds a receiver to a given inventory with a storage context.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to sync storage for
|
||||
-- @inventory inventory Inventory to sync storage for
|
||||
-- @bool bDontSync Whether or not to skip syncing the storage to the client. If this is `true`, the storage panel will not
|
||||
-- show up for the player
|
||||
function ix.storage.AddReceiver(client, inventory, bDontSync)
|
||||
local info = inventory.storageInfo
|
||||
|
||||
if (info) then
|
||||
inventory:AddReceiver(client)
|
||||
client.ixOpenStorage = inventory
|
||||
|
||||
-- update receivers for any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
v:GetInventory():AddReceiver(client)
|
||||
end
|
||||
end
|
||||
|
||||
if (isfunction(info.OnPlayerOpen)) then
|
||||
info.OnPlayerOpen(client)
|
||||
end
|
||||
|
||||
if (!bDontSync) then
|
||||
ix.storage.Sync(client, inventory)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Removes a storage receiver and removes the context if there are no more receivers.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @player client Player to remove from receivers
|
||||
-- @inventory inventory Inventory with storage context to remove receiver from
|
||||
-- @bool bDontRemove Whether or not to skip removing the storage context if there are no more receivers
|
||||
function ix.storage.RemoveReceiver(client, inventory, bDontRemove)
|
||||
local info = inventory.storageInfo
|
||||
|
||||
if (info) then
|
||||
inventory:RemoveReceiver(client)
|
||||
|
||||
-- update receivers for any bags this inventory might have
|
||||
for _, v in pairs(inventory:GetItems()) do
|
||||
if (v.isBag and v:GetInventory()) then
|
||||
v:GetInventory():RemoveReceiver(client)
|
||||
end
|
||||
end
|
||||
|
||||
if (isfunction(info.OnPlayerClose)) then
|
||||
info.OnPlayerClose(client)
|
||||
end
|
||||
|
||||
if (!bDontRemove and !ix.storage.InUse(inventory)) then
|
||||
ix.storage.RemoveContext(inventory)
|
||||
end
|
||||
|
||||
client.ixOpenStorage = nil
|
||||
|
||||
hook.Run("OnStorageReceiverRemoved", client, inventory)
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Makes a player open an inventory that they can interact with. This can be called multiple times on the same inventory,
|
||||
-- if the info passed allows for multiple users.
|
||||
-- @realm server
|
||||
-- @player client Player to open the inventory for
|
||||
-- @inventory inventory Inventory to open
|
||||
-- @tab info `StorageInfoStructure` describing the storage properties
|
||||
function ix.storage.Open(client, inventory, info)
|
||||
assert(IsValid(client) and client:IsPlayer(), "expected valid player")
|
||||
assert(type(inventory) == "table" and inventory:IsInstanceOf(ix.meta.inventory), "expected valid inventory")
|
||||
|
||||
-- create storage context if one isn't already created
|
||||
if (!inventory.storageInfo) then
|
||||
info = info or {}
|
||||
ix.storage.CreateContext(inventory, info)
|
||||
end
|
||||
|
||||
local storageInfo = inventory.storageInfo
|
||||
|
||||
-- add the client to the list of receivers if we're allowed to have multiple users
|
||||
-- or if nobody else is occupying this inventory, otherwise nag the player
|
||||
if (storageInfo.bMultipleUsers or !ix.storage.InUse(inventory)) then
|
||||
ix.storage.AddReceiver(client, inventory, true)
|
||||
else
|
||||
client:NotifyLocalized("storageInUse")
|
||||
return
|
||||
end
|
||||
|
||||
if (storageInfo.searchTime > 0) then
|
||||
client:SetAction(storageInfo.searchText, storageInfo.searchTime)
|
||||
client:DoStaredAction(storageInfo.entity, function()
|
||||
if (IsValid(client) and IsValid(storageInfo.entity) and inventory.storageInfo) then
|
||||
ix.storage.Sync(client, inventory)
|
||||
end
|
||||
end, storageInfo.searchTime, function()
|
||||
if (IsValid(client)) then
|
||||
ix.storage.RemoveReceiver(client, inventory)
|
||||
client:SetAction()
|
||||
end
|
||||
end)
|
||||
else
|
||||
ix.storage.Sync(client, inventory)
|
||||
end
|
||||
end
|
||||
|
||||
--- Forcefully makes clients close this inventory if they have it open.
|
||||
-- @realm server
|
||||
-- @inventory inventory Inventory to close
|
||||
function ix.storage.Close(inventory)
|
||||
local receivers = inventory:GetReceivers()
|
||||
|
||||
if (#receivers > 0) then
|
||||
net.Start("ixStorageExpired")
|
||||
net.WriteUInt(inventory.storageInfo.id, 32)
|
||||
net.Send(receivers)
|
||||
end
|
||||
|
||||
ix.storage.RemoveContext(inventory)
|
||||
end
|
||||
|
||||
net.Receive("ixStorageClose", function(length, client)
|
||||
local inventory = client.ixOpenStorage
|
||||
|
||||
if (inventory) then
|
||||
ix.storage.RemoveReceiver(client, inventory)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageMoneyTake", function(length, client)
|
||||
if (CurTime() < (client.ixStorageMoneyTimer or 0)) then
|
||||
return
|
||||
end
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return
|
||||
end
|
||||
|
||||
local storageID = net.ReadUInt(32)
|
||||
local amount = net.ReadUInt(32)
|
||||
|
||||
local inventory = client.ixOpenStorage
|
||||
|
||||
if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = inventory.storageInfo.entity
|
||||
if (hook.Run("CanTakeMoney", client, entity, amount) == false) then
|
||||
client:NotifyLocalized("notAllowed")
|
||||
|
||||
return
|
||||
end
|
||||
local total
|
||||
if (inventory.storageInfo.OnMoneyTake) then
|
||||
amount, total = inventory.storageInfo.OnMoneyTake(client, inventory, amount)
|
||||
|
||||
if (!amount) then
|
||||
return
|
||||
end
|
||||
else
|
||||
if (!IsValid(entity) or
|
||||
(!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or
|
||||
(entity:IsPlayer() and !entity:GetCharacter())) then
|
||||
return
|
||||
end
|
||||
|
||||
entity = entity:IsPlayer() and entity:GetCharacter() or entity
|
||||
amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, entity:GetMoney())
|
||||
|
||||
if (amount == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
character:SetMoney(character:GetMoney() + amount)
|
||||
|
||||
total = entity:GetMoney() - amount
|
||||
entity:SetMoney(total)
|
||||
|
||||
ix.log.Add(client, "storageMoneyTake", entity, amount, total)
|
||||
end
|
||||
|
||||
net.Start("ixStorageMoneyUpdate")
|
||||
net.WriteUInt(storageID, 32)
|
||||
net.WriteUInt(total, 32)
|
||||
net.Send(inventory:GetReceivers())
|
||||
|
||||
client.ixStorageMoneyTimer = CurTime() + 0.5
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageMoneyGive", function(length, client)
|
||||
if (CurTime() < (client.ixStorageMoneyTimer or 0)) then
|
||||
return
|
||||
end
|
||||
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (!character) then
|
||||
return
|
||||
end
|
||||
|
||||
local storageID = net.ReadUInt(32)
|
||||
local amount = net.ReadUInt(32)
|
||||
|
||||
local inventory = client.ixOpenStorage
|
||||
|
||||
if (!inventory or !inventory.storageInfo or storageID != inventory:GetID()) then
|
||||
return
|
||||
end
|
||||
|
||||
local entity = inventory.storageInfo.entity
|
||||
if (hook.Run("CanGiveMoney", client, entity, amount) == false) then
|
||||
client:NotifyLocalized("notAllowed")
|
||||
|
||||
return
|
||||
end
|
||||
local total
|
||||
if (inventory.storageInfo.OnMoneyGive) then
|
||||
amount, total = inventory.storageInfo.OnMoneyGive(client, inventory, amount)
|
||||
|
||||
if (!amount) then
|
||||
return
|
||||
end
|
||||
else
|
||||
if (!IsValid(entity) or
|
||||
(!entity:IsPlayer() and (!isfunction(entity.GetMoney) or !isfunction(entity.SetMoney))) or
|
||||
(entity:IsPlayer() and !entity:GetCharacter())) then
|
||||
return
|
||||
end
|
||||
|
||||
entity = entity:IsPlayer() and entity:GetCharacter() or entity
|
||||
amount = math.Clamp(math.Round(tonumber(amount) or 0), 0, character:GetMoney())
|
||||
|
||||
if (amount == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
character:SetMoney(character:GetMoney() - amount)
|
||||
|
||||
total = entity:GetMoney() + amount
|
||||
entity:SetMoney(total)
|
||||
|
||||
ix.log.Add(client, "storageMoneyGive", entity, amount, total)
|
||||
end
|
||||
|
||||
net.Start("ixStorageMoneyUpdate")
|
||||
net.WriteUInt(storageID, 32)
|
||||
net.WriteUInt(total, 32)
|
||||
net.Send(inventory:GetReceivers())
|
||||
|
||||
client.ixStorageMoneyTimer = CurTime() + 0.5
|
||||
end)
|
||||
else
|
||||
net.Receive("ixStorageOpen", function()
|
||||
if (IsValid(ix.gui.menu)) then
|
||||
net.Start("ixStorageClose")
|
||||
net.SendToServer()
|
||||
return
|
||||
end
|
||||
|
||||
local id = net.ReadUInt(32)
|
||||
local entity = net.ReadEntity()
|
||||
local name = net.ReadString()
|
||||
local data = net.ReadTable()
|
||||
|
||||
local inventory = ix.item.inventories[id]
|
||||
|
||||
if (IsValid(entity) and inventory and inventory.slots) then
|
||||
local char = LocalPlayer().GetCharacter and LocalPlayer():GetCharacter()
|
||||
local localInventory = char and char:GetInventory()
|
||||
local equipInvID = char and char.GetEquipInventory and char:GetEquipInventory()
|
||||
local equipInv = equipInvID and ix.item.inventories[equipInvID]
|
||||
|
||||
local panel = vgui.Create("ixStorageView")
|
||||
panel:CreateContents(inventory, entity)
|
||||
|
||||
if (localInventory) then
|
||||
panel:SetLocalInventory(localInventory)
|
||||
end
|
||||
|
||||
if (equipInv) then
|
||||
panel:SetEquipInv(equipInv)
|
||||
end
|
||||
|
||||
panel:SetStorageID(id)
|
||||
panel:SetStorageTitle(name)
|
||||
panel:SetStorageInventory(inventory)
|
||||
|
||||
if (data.money) then
|
||||
if (localInventory) then
|
||||
panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney())
|
||||
end
|
||||
|
||||
panel:SetStorageMoney(data.money)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageExpired", function()
|
||||
if (IsValid(ix.gui.openedStorage)) then
|
||||
ix.gui.openedStorage:Remove()
|
||||
end
|
||||
|
||||
local id = net.ReadUInt(32)
|
||||
|
||||
if (id != 0) then
|
||||
ix.item.inventories[id] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixStorageMoneyUpdate", function()
|
||||
local storageID = net.ReadUInt(32)
|
||||
local amount = net.ReadUInt(32)
|
||||
|
||||
local panel = ix.gui.openedStorage
|
||||
|
||||
if (!IsValid(panel) or panel:GetStorageID() != storageID) then
|
||||
return
|
||||
end
|
||||
|
||||
panel:SetStorageMoney(amount)
|
||||
panel:SetLocalMoney(LocalPlayer():GetCharacter():GetMoney())
|
||||
end)
|
||||
end
|
||||
204
gamemodes/helix/gamemode/core/libs/sv_database.lua
Normal file
204
gamemodes/helix/gamemode/core/libs/sv_database.lua
Normal file
@@ -0,0 +1,204 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
ix.db = ix.db or {
|
||||
schema = {},
|
||||
schemaQueue = {},
|
||||
type = {
|
||||
-- TODO: more specific types, lengths, and defaults
|
||||
-- i.e INT(11) UNSIGNED, SMALLINT(4), LONGTEXT, VARCHAR(350), NOT NULL, DEFAULT NULL, etc
|
||||
[ix.type.string] = "VARCHAR(255)",
|
||||
[ix.type.text] = "TEXT",
|
||||
[ix.type.number] = "INT(11)",
|
||||
[ix.type.steamid] = "VARCHAR(20)",
|
||||
[ix.type.bool] = "TINYINT(1)"
|
||||
}
|
||||
}
|
||||
|
||||
ix.db.config = ix.config.server.database or {}
|
||||
|
||||
function ix.db.Connect()
|
||||
ix.db.config.adapter = ix.db.config.adapter or "sqlite"
|
||||
|
||||
local dbmodule = ix.db.config.adapter
|
||||
local hostname = ix.db.config.hostname
|
||||
local username = ix.db.config.username
|
||||
local password = ix.db.config.password
|
||||
local database = ix.db.config.database
|
||||
local port = ix.db.config.port
|
||||
|
||||
mysql:SetModule(dbmodule)
|
||||
mysql:Connect(hostname, username, password, database, port)
|
||||
end
|
||||
|
||||
function ix.db.AddToSchema(schemaType, field, fieldType)
|
||||
if (!ix.db.type[fieldType]) then
|
||||
error(string.format("attempted to add field in schema with invalid type '%s'", fieldType))
|
||||
return
|
||||
end
|
||||
|
||||
if (!mysql:IsConnected() or !ix.db.schema[schemaType]) then
|
||||
ix.db.schemaQueue[#ix.db.schemaQueue + 1] = {schemaType, field, fieldType}
|
||||
return
|
||||
end
|
||||
|
||||
ix.db.InsertSchema(schemaType, field, fieldType)
|
||||
end
|
||||
|
||||
-- this is only ever used internally
|
||||
function ix.db.InsertSchema(schemaType, field, fieldType)
|
||||
local schema = ix.db.schema[schemaType]
|
||||
|
||||
if (!schema) then
|
||||
error(string.format("attempted to insert into schema with invalid schema type '%s'", schemaType))
|
||||
return
|
||||
end
|
||||
|
||||
if (!schema[field]) then
|
||||
schema[field] = true
|
||||
|
||||
local query = mysql:Update("ix_schema")
|
||||
query:Update("columns", util.TableToJSON(schema))
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Alter(schemaType)
|
||||
query:Add(field, ix.db.type[fieldType])
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
function ix.db.LoadTables()
|
||||
local query
|
||||
|
||||
query = mysql:Create("ix_schema")
|
||||
query:Create("table", "VARCHAR(64) NOT NULL")
|
||||
query:Create("columns", "TEXT NOT NULL")
|
||||
query:PrimaryKey("table")
|
||||
query:Execute()
|
||||
|
||||
-- table structure will be populated with more fields when vars
|
||||
-- are registered using ix.char.RegisterVar
|
||||
query = mysql:Create("ix_characters")
|
||||
query:Create("id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
|
||||
query:PrimaryKey("id")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_characters_data")
|
||||
query:Create("id", "INT(11) UNSIGNED NOT NULL")
|
||||
query:Create("key", "VARCHAR(60) NOT NULL")
|
||||
query:Create("data", "TEXT DEFAULT NULL")
|
||||
query:PrimaryKey("id`,`key")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_inventories")
|
||||
query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
|
||||
query:Create("character_id", "INT(11) UNSIGNED NOT NULL")
|
||||
query:Create("inventory_type", "VARCHAR(150) DEFAULT NULL")
|
||||
query:PrimaryKey("inventory_id")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_items")
|
||||
query:Create("item_id", "INT(11) UNSIGNED NOT NULL AUTO_INCREMENT")
|
||||
query:Create("inventory_id", "INT(11) UNSIGNED NOT NULL")
|
||||
query:Create("unique_id", "VARCHAR(60) NOT NULL")
|
||||
query:Create("character_id", "INT(11) UNSIGNED DEFAULT NULL")
|
||||
query:Create("player_id", "VARCHAR(20) DEFAULT NULL")
|
||||
query:Create("data", "TEXT DEFAULT NULL")
|
||||
query:Create("x", "SMALLINT(4) NOT NULL")
|
||||
query:Create("y", "SMALLINT(4) NOT NULL")
|
||||
query:PrimaryKey("item_id")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Create("ix_players")
|
||||
query:Create("steamid", "VARCHAR(20) NOT NULL")
|
||||
query:Create("steam_name", "VARCHAR(32) NOT NULL")
|
||||
query:Create("play_time", "INT(11) UNSIGNED DEFAULT NULL")
|
||||
query:Create("address", "VARCHAR(15) DEFAULT NULL")
|
||||
query:Create("last_join_time", "INT(11) UNSIGNED DEFAULT NULL")
|
||||
query:Create("data", "TEXT")
|
||||
query:PrimaryKey("steamid")
|
||||
query:Execute()
|
||||
|
||||
-- populate schema table if rows don't exist
|
||||
query = mysql:InsertIgnore("ix_schema")
|
||||
query:Insert("table", "ix_characters")
|
||||
query:Insert("columns", util.TableToJSON({}))
|
||||
query:Execute()
|
||||
|
||||
-- load schema from database
|
||||
query = mysql:Select("ix_schema")
|
||||
query:Callback(function(result)
|
||||
if (!istable(result)) then
|
||||
return
|
||||
end
|
||||
|
||||
for _, v in pairs(result) do
|
||||
ix.db.schema[v.table] = util.JSONToTable(v.columns)
|
||||
end
|
||||
|
||||
-- update schema if needed
|
||||
for i = 1, #ix.db.schemaQueue do
|
||||
local entry = ix.db.schemaQueue[i]
|
||||
ix.db.InsertSchema(entry[1], entry[2], entry[3])
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function ix.db.WipeTables(callback)
|
||||
local query
|
||||
|
||||
query = mysql:Drop("ix_schema")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_characters")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_inventories")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_items")
|
||||
query:Execute()
|
||||
|
||||
query = mysql:Drop("ix_players")
|
||||
query:Callback(callback)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
hook.Add("InitPostEntity", "ixDatabaseConnect", function()
|
||||
-- Connect to the database using SQLite, mysqoo, or tmysql4.
|
||||
ix.db.Connect()
|
||||
end)
|
||||
|
||||
local resetCalled = 0
|
||||
|
||||
concommand.Add("ix_wipedb", function(client, cmd, arguments)
|
||||
-- can only be ran through the server's console
|
||||
if (!IsValid(client)) then
|
||||
if (resetCalled < RealTime()) then
|
||||
resetCalled = RealTime() + 3
|
||||
|
||||
MsgC(Color(255, 0, 0),
|
||||
"[Helix] WIPING THE DATABASE WILL PERMENANTLY REMOVE ALL PLAYER, CHARACTER, ITEM, AND INVENTORY DATA.\n")
|
||||
MsgC(Color(255, 0, 0), "[Helix] THE SERVER WILL RESTART TO APPLY THESE CHANGES WHEN COMPLETED.\n")
|
||||
MsgC(Color(255, 0, 0), "[Helix] TO CONFIRM DATABASE RESET, RUN 'ix_wipedb' AGAIN WITHIN 3 SECONDS.\n")
|
||||
else
|
||||
resetCalled = 0
|
||||
MsgC(Color(255, 0, 0), "[Helix] DATABASE WIPE IN PROGRESS...\n")
|
||||
|
||||
hook.Run("OnWipeTables")
|
||||
ix.db.WipeTables(function()
|
||||
MsgC(Color(255, 255, 0), "[Helix] DATABASE WIPE COMPLETED!\n")
|
||||
RunConsoleCommand("changelevel", game.GetMap())
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
245
gamemodes/helix/gamemode/core/libs/sv_networking.lua
Normal file
245
gamemodes/helix/gamemode/core/libs/sv_networking.lua
Normal file
@@ -0,0 +1,245 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
-- @module ix.net
|
||||
|
||||
local entityMeta = FindMetaTable("Entity")
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
ix.net = ix.net or {}
|
||||
ix.net.list = ix.net.list or {}
|
||||
ix.net.locals = ix.net.locals or {}
|
||||
ix.net.globals = ix.net.globals or {}
|
||||
|
||||
util.AddNetworkString("ixGlobalVarSet")
|
||||
util.AddNetworkString("ixLocalVarSet")
|
||||
util.AddNetworkString("ixNetVarSet")
|
||||
util.AddNetworkString("ixNetStatics")
|
||||
util.AddNetworkString("ixNetVarDelete")
|
||||
|
||||
-- Check if there is an attempt to send a function. Can't send those.
|
||||
local function CheckBadType(name, object)
|
||||
if (isfunction(object)) then
|
||||
ErrorNoHalt("Net var '" .. name .. "' contains a bad object type!")
|
||||
|
||||
return true
|
||||
elseif (istable(object)) then
|
||||
for k, v in pairs(object) do
|
||||
-- Check both the key and the value for tables, and has recursion.
|
||||
if (CheckBadType(name, k) or CheckBadType(name, v)) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function GetNetVar(key, default) -- luacheck: globals GetNetVar
|
||||
local value = ix.net.globals[key]
|
||||
|
||||
return value != nil and value or default
|
||||
end
|
||||
|
||||
function SetNetVar(key, value, receiver) -- luacheck: globals SetNetVar
|
||||
if (CheckBadType(key, value)) then return end
|
||||
if (GetNetVar(key) == value) then return end
|
||||
|
||||
ix.net.globals[key] = value
|
||||
|
||||
net.Start("ixGlobalVarSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
|
||||
if (receiver == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
--- Player networked variable functions
|
||||
-- @classmod Player
|
||||
|
||||
--- Synchronizes networked variables to the client.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
local division = 100
|
||||
function playerMeta:SyncVars()
|
||||
for k, v in pairs(ix.net.globals) do
|
||||
net.Start("ixGlobalVarSet")
|
||||
net.WriteString(k)
|
||||
net.WriteType(v)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
for k, v in pairs(ix.net.locals[self] or {}) do
|
||||
net.Start("ixLocalVarSet")
|
||||
net.WriteString(k)
|
||||
net.WriteType(v)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
local statics = {}
|
||||
local toWrite = {}
|
||||
for entity, data in pairs(ix.net.list) do
|
||||
if (IsValid(entity)) then
|
||||
local index = entity:EntIndex()
|
||||
|
||||
if (table.Count(data) == 1 and data.Persistent == true) then
|
||||
statics[#statics + 1] = index
|
||||
continue
|
||||
end
|
||||
|
||||
for k, v in pairs(data) do
|
||||
toWrite[#toWrite + 1] = {index, k, v}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = 0, math.floor(#toWrite / division) do
|
||||
timer.Simple(i * 0.5, function()
|
||||
if (!IsValid(self)) then return end
|
||||
|
||||
for j = 0, division - 1 do
|
||||
if (!toWrite[i * division + j]) then continue end
|
||||
net.Start("ixNetVarSet")
|
||||
net.WriteUInt(toWrite[i * division + j][1], 16)
|
||||
net.WriteString(toWrite[i * division + j][2])
|
||||
net.WriteType(toWrite[i * division + j][3])
|
||||
net.Send(self)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
timer.Simple(30, function()
|
||||
if (!IsValid(self)) then return end
|
||||
|
||||
net.Start("ixNetStatics")
|
||||
net.WriteUInt(#statics, 16)
|
||||
for _, v in ipairs(statics) do
|
||||
local entity = Entity(v)
|
||||
if (IsValid(entity) and ix.net.list[entity] and ix.net.list[entity].Persistent == true) then
|
||||
net.WriteUInt(v, 16)
|
||||
else
|
||||
net.WriteUInt(0, 16)
|
||||
end
|
||||
end
|
||||
net.Send(self)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Retrieves a local networked variable. If it is not set, it'll return the default that you've specified.
|
||||
-- Locally networked variables can only be retrieved from the owning player when used from the client.
|
||||
-- @realm shared
|
||||
-- @string key Identifier of the local variable
|
||||
-- @param default Default value to return if the local variable is not set
|
||||
-- @return Value associated with the key, or the default that was given if it doesn't exist
|
||||
-- @usage print(client:GetLocalVar("secret"))
|
||||
-- > 12345678
|
||||
-- @see SetLocalVar
|
||||
function playerMeta:GetLocalVar(key, default)
|
||||
if (ix.net.locals[self] and ix.net.locals[self][key] != nil) then
|
||||
return ix.net.locals[self][key]
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Sets the value of a local networked variable.
|
||||
-- @realm server
|
||||
-- @string key Identifier of the local variable
|
||||
-- @param value New value to assign to the local variable
|
||||
-- @usage client:SetLocalVar("secret", 12345678)
|
||||
-- @see GetLocalVar
|
||||
function playerMeta:SetLocalVar(key, value)
|
||||
if (CheckBadType(key, value)) then return end
|
||||
|
||||
ix.net.locals[self] = ix.net.locals[self] or {}
|
||||
ix.net.locals[self][key] = value
|
||||
|
||||
net.Start("ixLocalVarSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
--- Entity networked variable functions
|
||||
-- @classmod Entity
|
||||
|
||||
--- Retrieves a networked variable. If it is not set, it'll return the default that you've specified.
|
||||
-- @realm shared
|
||||
-- @string key Identifier of the networked variable
|
||||
-- @param default Default value to return if the networked variable is not set
|
||||
-- @return Value associated with the key, or the default that was given if it doesn't exist
|
||||
-- @usage print(client:GetNetVar("example"))
|
||||
-- > Hello World!
|
||||
-- @see SetNetVar
|
||||
function entityMeta:GetNetVar(key, default)
|
||||
if (ix.net.list[self] and ix.net.list[self][key] != nil) then
|
||||
return ix.net.list[self][key]
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Sets the value of a networked variable.
|
||||
-- @realm server
|
||||
-- @string key Identifier of the networked variable
|
||||
-- @param value New value to assign to the networked variable
|
||||
-- @tab[opt=nil] receiver The players to send the networked variable to
|
||||
-- @usage client:SetNetVar("example", "Hello World!")
|
||||
-- @see GetNetVar
|
||||
function entityMeta:SetNetVar(key, value, receiver)
|
||||
if (CheckBadType(key, value)) then return end
|
||||
|
||||
ix.net.list[self] = ix.net.list[self] or {}
|
||||
|
||||
if (ix.net.list[self][key] != value) then
|
||||
ix.net.list[self][key] = value
|
||||
end
|
||||
|
||||
self:SendNetVar(key, receiver)
|
||||
end
|
||||
|
||||
--- Sends a networked variable.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @string key Identifier of the networked variable
|
||||
-- @tab[opt=nil] receiver The players to send the networked variable to
|
||||
function entityMeta:SendNetVar(key, receiver)
|
||||
net.Start("ixNetVarSet")
|
||||
net.WriteUInt(self:EntIndex(), 16)
|
||||
net.WriteString(key)
|
||||
net.WriteType(ix.net.list[self] and ix.net.list[self][key])
|
||||
|
||||
if (receiver == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
--- Clears all of the networked variables.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @tab[opt=nil] receiver The players to clear the networked variable for
|
||||
function entityMeta:ClearNetVars(receiver)
|
||||
ix.net.list[self] = nil
|
||||
ix.net.locals[self] = nil
|
||||
|
||||
net.Start("ixNetVarDelete")
|
||||
net.WriteUInt(self:EntIndex(), 16)
|
||||
|
||||
if (receiver == nil) then
|
||||
net.Broadcast()
|
||||
else
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
125
gamemodes/helix/gamemode/core/libs/sv_player.lua
Normal file
125
gamemodes/helix/gamemode/core/libs/sv_player.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local playerMeta = FindMetaTable("Player")
|
||||
|
||||
-- Player data (outside of characters) handling.
|
||||
do
|
||||
util.AddNetworkString("ixData")
|
||||
util.AddNetworkString("ixDataSync")
|
||||
|
||||
function playerMeta:LoadData(callback)
|
||||
local name = self:SteamName()
|
||||
local steamID64 = self:SteamID64()
|
||||
local timestamp = math.floor(os.time())
|
||||
local ip = self:IPAddress():match("%d+%.%d+%.%d+%.%d+")
|
||||
|
||||
local query = mysql:Select("ix_players")
|
||||
query:Select("data")
|
||||
query:Select("play_time")
|
||||
query:Where("steamid", steamID64)
|
||||
query:Callback(function(result)
|
||||
if (IsValid(self) and istable(result) and #result > 0 and result[1].data) then
|
||||
local updateQuery = mysql:Update("ix_players")
|
||||
updateQuery:Update("last_join_time", timestamp)
|
||||
updateQuery:Update("address", ip)
|
||||
updateQuery:Where("steamid", steamID64)
|
||||
updateQuery:Execute()
|
||||
|
||||
self.ixPlayTime = tonumber(result[1].play_time) or 0
|
||||
self.ixData = util.JSONToTable(result[1].data)
|
||||
|
||||
hook.Run("PlayerDataRestored", self)
|
||||
|
||||
if (callback) then
|
||||
callback(self.ixData)
|
||||
end
|
||||
else
|
||||
local insertQuery = mysql:Insert("ix_players")
|
||||
insertQuery:Insert("steamid", steamID64)
|
||||
insertQuery:Insert("steam_name", name)
|
||||
insertQuery:Insert("play_time", 0)
|
||||
insertQuery:Insert("address", ip)
|
||||
insertQuery:Insert("last_join_time", timestamp)
|
||||
insertQuery:Insert("data", util.TableToJSON({}))
|
||||
insertQuery:Execute()
|
||||
|
||||
hook.Run("PlayerDataRestored", self)
|
||||
|
||||
if (callback) then
|
||||
callback({})
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function playerMeta:SaveData()
|
||||
local name = self:SteamName()
|
||||
local steamID64 = self:SteamID64()
|
||||
|
||||
local query = mysql:Update("ix_players")
|
||||
query:Update("steam_name", name)
|
||||
query:Update("play_time", math.floor((self.ixPlayTime or 0) + (RealTime() - (self.ixJoinTime or RealTime() - 1))))
|
||||
query:Update("data", util.TableToJSON(self.ixData))
|
||||
query:Where("steamid", steamID64)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
function playerMeta:SetData(key, value, bNoNetworking)
|
||||
self.ixData = self.ixData or {}
|
||||
self.ixData[key] = value
|
||||
|
||||
if (!bNoNetworking) then
|
||||
net.Start("ixData")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Whitelisting information for the player.
|
||||
do
|
||||
function playerMeta:SetWhitelisted(faction, whitelisted)
|
||||
if (!whitelisted) then
|
||||
whitelisted = nil
|
||||
end
|
||||
|
||||
local data = ix.faction.indices[faction]
|
||||
|
||||
if (data) then
|
||||
local whitelists = self:GetData("whitelists", {})
|
||||
whitelists[Schema.folder] = whitelists[Schema.folder] or {}
|
||||
whitelists[Schema.folder][data.uniqueID] = whitelisted and true or nil
|
||||
|
||||
self:SetData("whitelists", whitelists)
|
||||
self:SaveData()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
playerMeta.ixGive = playerMeta.ixGive or playerMeta.Give
|
||||
|
||||
function playerMeta:Give(className, bNoAmmo)
|
||||
local weapon
|
||||
|
||||
self.ixWeaponGive = true
|
||||
weapon = self:ixGive(className, bNoAmmo)
|
||||
self.ixWeaponGive = nil
|
||||
|
||||
return weapon
|
||||
end
|
||||
end
|
||||
362
gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua
vendored
Normal file
362
gamemodes/helix/gamemode/core/libs/thirdparty/cl_ikon.lua
vendored
Normal file
@@ -0,0 +1,362 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
BLACK TEA ICON LIBRARY FOR NUTSCRIPT 1.1
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017, Kyu Yeon Lee(Black Tea Za rebel1324)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so, subject
|
||||
to the following conditions:
|
||||
|
||||
The above copyright notice and thispermission notice shall be included in all copies
|
||||
or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
TL;DR: https://tldrlegal.com/license/mit-license
|
||||
OK -
|
||||
Commercial Use
|
||||
Modify
|
||||
Distribute
|
||||
Sublicense
|
||||
Private Use
|
||||
|
||||
NOT OK -
|
||||
Hold Liable
|
||||
|
||||
MUST -
|
||||
Include Copyright
|
||||
Include License
|
||||
]]--
|
||||
|
||||
--[[
|
||||
Default Tables.
|
||||
]]--
|
||||
|
||||
ikon = ikon or {}
|
||||
ikon.cache = ikon.cache or {}
|
||||
ikon.requestList = ikon.requestList or {}
|
||||
ikon.dev = false
|
||||
ikon.maxSize = 8 -- 8x8 (512^2) is max icon size.
|
||||
|
||||
IKON_BUSY = 1
|
||||
IKON_PROCESSING = 0
|
||||
IKON_SOMETHINGWRONG = -1
|
||||
|
||||
local schemaName = schemaName or (Schema and Schema.folder)
|
||||
|
||||
--[[
|
||||
Initialize hooks and RT Screens.
|
||||
returns nothing
|
||||
]]--
|
||||
function ikon:init()
|
||||
if (self.dev) then
|
||||
hook.Add("HUDPaint", "ikon_dev2", ikon.showResult)
|
||||
else
|
||||
hook.Remove("HUDPaint", "ikon_dev2")
|
||||
end
|
||||
|
||||
--[[
|
||||
Being good at gmod is knowing all of stinky hacks
|
||||
- Black Tea (2017)
|
||||
]]--
|
||||
ikon.haloAdd = ikon.haloAdd or halo.Add
|
||||
function halo.Add(...)
|
||||
if (ikon.rendering != true) then
|
||||
ikon.haloAdd(...)
|
||||
end
|
||||
end
|
||||
|
||||
ikon.haloRender = ikon.haloRender or halo.Render
|
||||
function halo.Render(...)
|
||||
if (ikon.rendering != true) then
|
||||
ikon.haloRender(...)
|
||||
end
|
||||
end
|
||||
|
||||
file.CreateDir("helix/icons")
|
||||
file.CreateDir("helix/icons/" .. schemaName)
|
||||
end
|
||||
|
||||
--[[
|
||||
IKON Library Essential Material/Texture Declare
|
||||
]]--
|
||||
|
||||
local TEXTURE_FLAGS_CLAMP_S = 0x0004
|
||||
local TEXTURE_FLAGS_CLAMP_T = 0x0008
|
||||
|
||||
ikon.max = ikon.maxSize * 64
|
||||
ikon.RT = GetRenderTargetEx("ixIconRendered",
|
||||
ikon.max,
|
||||
ikon.max,
|
||||
RT_SIZE_NO_CHANGE,
|
||||
MATERIAL_RT_DEPTH_SHARED,
|
||||
bit.bor(TEXTURE_FLAGS_CLAMP_S, TEXTURE_FLAGS_CLAMP_T),
|
||||
CREATERENDERTARGETFLAGS_UNFILTERABLE_OK,
|
||||
IMAGE_FORMAT_RGBA8888
|
||||
)
|
||||
|
||||
local tex_effect = GetRenderTarget("ixIconRenderedOutline", ikon.max, ikon.max)
|
||||
local mat_outline = CreateMaterial("ixIconRenderedTemp", "UnlitGeneric", {
|
||||
["$basetexture"] = tex_effect:GetName(),
|
||||
["$translucent"] = 1
|
||||
})
|
||||
|
||||
local lightPositions = {
|
||||
BOX_TOP = Color(255, 255, 255),
|
||||
BOX_FRONT = Color(255, 255, 255),
|
||||
}
|
||||
function ikon:renderHook()
|
||||
local entity = ikon.renderEntity
|
||||
|
||||
if (halo.RenderedEntity() == entity) then
|
||||
return
|
||||
end
|
||||
|
||||
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
|
||||
local x, y = 0, 0
|
||||
local tab
|
||||
|
||||
if (ikon.info) then
|
||||
tab = {
|
||||
origin = ikon.info.pos,
|
||||
angles = ikon.info.ang,
|
||||
fov = ikon.info.fov,
|
||||
outline = ikon.info.outline,
|
||||
outCol = ikon.info.outlineColor
|
||||
}
|
||||
|
||||
if (!tab.origin and !tab.angles and !tab.fov) then
|
||||
table.Merge(tab, PositionSpawnIcon(entity, entity:GetPos(), true))
|
||||
end
|
||||
else
|
||||
tab = PositionSpawnIcon(entity, entity:GetPos(), true)
|
||||
end
|
||||
|
||||
-- Taking MDave's Tip
|
||||
xpcall(function()
|
||||
render.OverrideAlphaWriteEnable(true, true) -- some playermodel eyeballs will not render without this
|
||||
render.SetWriteDepthToDestAlpha(false)
|
||||
render.OverrideBlend(true, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD, BLEND_ONE, BLEND_ONE, BLENDFUNC_ADD)
|
||||
render.SuppressEngineLighting(true)
|
||||
render.Clear(0, 0, 0, 0, true, true)
|
||||
|
||||
render.SetLightingOrigin(vector_origin)
|
||||
render.ResetModelLighting(200 / 255, 200 / 255, 200 / 255)
|
||||
render.SetColorModulation(1, 1, 1)
|
||||
|
||||
for i = 0, 6 do
|
||||
local col = lightPositions[i]
|
||||
|
||||
if (col) then
|
||||
render.SetModelLighting(i, col.r / 255, col.g / 255, col.b / 255)
|
||||
end
|
||||
end
|
||||
|
||||
if (tab.outline) then
|
||||
ix.util.ResetStencilValues()
|
||||
render.SetStencilEnable(true)
|
||||
render.SetStencilWriteMask(137) -- yeah random number to avoid confliction
|
||||
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS)
|
||||
render.SetStencilPassOperation(STENCILOPERATION_REPLACE)
|
||||
render.SetStencilFailOperation(STENCILOPERATION_REPLACE)
|
||||
end
|
||||
|
||||
--[[
|
||||
Add more effects on the Models!
|
||||
]]--
|
||||
if (ikon.info and ikon.info.drawHook) then
|
||||
ikon.info.drawHook(entity)
|
||||
end
|
||||
|
||||
cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h)
|
||||
render.SetBlend(1)
|
||||
entity:DrawModel()
|
||||
cam.End3D()
|
||||
|
||||
if (tab.outline) then
|
||||
render.PushRenderTarget(tex_effect)
|
||||
render.Clear(0, 0, 0, 0)
|
||||
render.ClearDepth()
|
||||
cam.Start2D()
|
||||
cam.Start3D(tab.origin, tab.angles, tab.fov, 0, 0, w, h)
|
||||
render.SetBlend(0)
|
||||
entity:DrawModel()
|
||||
|
||||
render.SetStencilWriteMask(138) -- could you please?
|
||||
render.SetStencilTestMask(1)
|
||||
render.SetStencilReferenceValue(1)
|
||||
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL)
|
||||
render.SetStencilPassOperation(STENCILOPERATION_KEEP)
|
||||
render.SetStencilFailOperation(STENCILOPERATION_KEEP)
|
||||
cam.Start2D()
|
||||
surface.SetDrawColor(tab.outCol or color_white)
|
||||
surface.DrawRect(0, 0, ScrW(), ScrH())
|
||||
cam.End2D()
|
||||
cam.End3D()
|
||||
cam.End2D()
|
||||
render.PopRenderTarget()
|
||||
|
||||
render.SetBlend(1)
|
||||
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NOTEQUAL)
|
||||
|
||||
--[[
|
||||
Thanks for Noiwex
|
||||
NxServ.eu
|
||||
]]--
|
||||
cam.Start2D()
|
||||
surface.SetMaterial(mat_outline)
|
||||
surface.DrawTexturedRectUV(-2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max)
|
||||
surface.DrawTexturedRectUV(2, 0, w, h, 0, 0, w / ikon.max, h / ikon.max)
|
||||
surface.DrawTexturedRectUV(0, 2, w, h, 0, 0, w / ikon.max, h / ikon.max)
|
||||
surface.DrawTexturedRectUV(0, -2, w, h, 0, 0, w / ikon.max, h / ikon.max)
|
||||
cam.End2D()
|
||||
|
||||
render.SetStencilEnable(false)
|
||||
end
|
||||
|
||||
render.SuppressEngineLighting(false)
|
||||
render.SetWriteDepthToDestAlpha(true)
|
||||
render.OverrideAlphaWriteEnable(false)
|
||||
end, function(message)
|
||||
print(message)
|
||||
end)
|
||||
end
|
||||
|
||||
function ikon:showResult()
|
||||
local x, y = ScrW() / 2, ScrH() / 2
|
||||
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
|
||||
|
||||
surface.SetDrawColor(255, 255, 255, 255)
|
||||
surface.DrawOutlinedRect(x, 0, w, h)
|
||||
|
||||
surface.SetMaterial(mat_outline)
|
||||
surface.DrawTexturedRect(x, 0, w, h)
|
||||
end
|
||||
|
||||
--[[
|
||||
Renders the Icon with given arguments.
|
||||
returns nothing
|
||||
]]--
|
||||
function ikon:renderIcon(name, w, h, mdl, camInfo, updateCache)
|
||||
if (#ikon.requestList > 0) then return IKON_BUSY end
|
||||
if (ikon.requestList[name]) then return IKON_PROCESSING end
|
||||
if (!w or !h or !mdl) then return IKON_SOMETHINGWRONG end
|
||||
|
||||
local capturedIcon
|
||||
ikon.curWidth = w or 1
|
||||
ikon.curHeight = h or 1
|
||||
ikon.renderModel = mdl
|
||||
|
||||
if (camInfo) then
|
||||
ikon.info = camInfo
|
||||
end
|
||||
|
||||
local w, h = ikon.curWidth * 64, ikon.curHeight * 64
|
||||
local sw, sh = ScrW(), ScrH()
|
||||
|
||||
if (ikon.renderModel) then
|
||||
if (!IsValid(ikon.renderEntity)) then
|
||||
ikon.renderEntity = ClientsideModel(ikon.renderModel, RENDERGROUP_BOTH)
|
||||
ikon.renderEntity:SetNoDraw(true)
|
||||
end
|
||||
end
|
||||
|
||||
ikon.renderEntity:SetModel(ikon.renderModel)
|
||||
|
||||
local bone = ikon.renderEntity:LookupBone("ValveBiped.Bip01_Head1")
|
||||
|
||||
if (bone) then
|
||||
ikon.renderEntity:SetEyeTarget(ikon.renderEntity:GetBonePosition(bone) + ikon.renderEntity:GetForward() * 32)
|
||||
end
|
||||
|
||||
local oldRT = render.GetRenderTarget()
|
||||
render.PushRenderTarget(ikon.RT)
|
||||
|
||||
ikon.rendering = true
|
||||
ikon:renderHook()
|
||||
ikon.rendering = nil
|
||||
|
||||
capturedIcon = render.Capture({
|
||||
format = "png",
|
||||
alpha = true,
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = w,
|
||||
h = h
|
||||
})
|
||||
|
||||
file.Write("helix/icons/" .. schemaName .. "/" .. name .. ".png", capturedIcon)
|
||||
ikon.info = nil
|
||||
render.PopRenderTarget()
|
||||
|
||||
if (updateCache) then
|
||||
local materialID = tostring(os.time())
|
||||
file.Write(materialID .. ".png", capturedIcon)
|
||||
|
||||
timer.Simple(0, function()
|
||||
local material = Material("../data/".. materialID ..".png")
|
||||
|
||||
ikon.cache[name] = material
|
||||
file.Delete(materialID .. ".png")
|
||||
end)
|
||||
end
|
||||
|
||||
ikon.requestList[name] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
Gets rendered icon with given unique name.
|
||||
returns IMaterial
|
||||
]]--
|
||||
function ikon:GetIcon(name)
|
||||
if (ikon.cache[name]) then
|
||||
return ikon.cache[name] -- yeah return cache
|
||||
end
|
||||
|
||||
if (file.Exists("helix/icons/" .. schemaName .. "/" .. name .. ".png", "DATA")) then
|
||||
ikon.cache[name] = Material("../data/helix/icons/" .. schemaName .. "/".. name ..".png")
|
||||
return ikon.cache[name] -- yeah return cache
|
||||
else
|
||||
return false -- retryd
|
||||
end
|
||||
end
|
||||
|
||||
concommand.Add("ix_flushicon", function()
|
||||
local root = "helix/icons/" .. schemaName
|
||||
|
||||
for _, v in pairs(file.Find(root .. "/*.png", "DATA")) do
|
||||
file.Delete(root .. "/" .. v)
|
||||
end
|
||||
|
||||
ikon.cache = {}
|
||||
end)
|
||||
|
||||
hook.Add("InitializedSchema", "updatePath", function()
|
||||
schemaName = Schema.folder
|
||||
ikon:init()
|
||||
end)
|
||||
|
||||
if (schemaName) then
|
||||
ikon:init()
|
||||
end
|
||||
1870
gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua
vendored
Normal file
1870
gamemodes/helix/gamemode/core/libs/thirdparty/data/sh_utf8_casemap.lua
vendored
Normal file
File diff suppressed because it is too large
Load Diff
560
gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua
vendored
Normal file
560
gamemodes/helix/gamemode/core/libs/thirdparty/sh_cami.lua
vendored
Normal file
@@ -0,0 +1,560 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
CAMI - Common Admin Mod Interface.
|
||||
Makes admin mods intercompatible and provides an abstract privilege interface
|
||||
for third party addons.
|
||||
|
||||
Follows the specification on this page:
|
||||
https://docs.google.com/document/d/1QIRVcAgZfAYf1aBl_dNV_ewR6P25wze2KmUVzlbFgMI
|
||||
|
||||
Structures:
|
||||
CAMI_USERGROUP, defines the charactaristics of a usergroup:
|
||||
{
|
||||
Name
|
||||
string
|
||||
The name of the usergroup
|
||||
Inherits
|
||||
string
|
||||
The name of the usergroup this usergroup inherits from
|
||||
}
|
||||
|
||||
CAMI_PRIVILEGE, defines the charactaristics of a privilege:
|
||||
{
|
||||
Name
|
||||
string
|
||||
The name of the privilege
|
||||
MinAccess
|
||||
string
|
||||
One of the following three: user/admin/superadmin
|
||||
Description
|
||||
string
|
||||
optional
|
||||
A text describing the purpose of the privilege
|
||||
HasAccess
|
||||
function(
|
||||
privilege :: CAMI_PRIVILEGE,
|
||||
actor :: Player,
|
||||
target :: Player
|
||||
) :: bool
|
||||
optional
|
||||
Function that decides whether a player can execute this privilege,
|
||||
optionally on another player (target).
|
||||
}
|
||||
]]
|
||||
|
||||
-- Version number in YearMonthDay format.
|
||||
local version = 20190102
|
||||
|
||||
if CAMI and CAMI.Version >= version then return end
|
||||
|
||||
CAMI = CAMI or {}
|
||||
CAMI.Version = version
|
||||
|
||||
--[[
|
||||
usergroups
|
||||
Contains the registered CAMI_USERGROUP usergroup structures.
|
||||
Indexed by usergroup name.
|
||||
]]
|
||||
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
|
||||
user = {
|
||||
Name = "user",
|
||||
Inherits = "user"
|
||||
},
|
||||
admin = {
|
||||
Name = "admin",
|
||||
Inherits = "user"
|
||||
},
|
||||
superadmin = {
|
||||
Name = "superadmin",
|
||||
Inherits = "admin"
|
||||
}
|
||||
}
|
||||
|
||||
--[[
|
||||
privileges
|
||||
Contains the registered CAMI_PRIVILEGE privilege structures.
|
||||
Indexed by privilege name.
|
||||
]]
|
||||
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
|
||||
|
||||
--[[
|
||||
CAMI.RegisterUsergroup
|
||||
Registers a usergroup with CAMI.
|
||||
|
||||
Parameters:
|
||||
usergroup
|
||||
CAMI_USERGROUP
|
||||
(see CAMI_USERGROUP structure)
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
Use this to make sure CAMI.RegisterUsergroup function and the
|
||||
CAMI.OnUsergroupRegistered hook don't cause an infinite loop
|
||||
|
||||
|
||||
|
||||
Return value:
|
||||
CAMI_USERGROUP
|
||||
The usergroup given as argument.
|
||||
]]
|
||||
function CAMI.RegisterUsergroup(usergroup, source)
|
||||
usergroups[usergroup.Name] = usergroup
|
||||
|
||||
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
|
||||
return usergroup
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.UnregisterUsergroup
|
||||
Unregisters a usergroup from CAMI. This will call a hook that will notify
|
||||
all other admin mods of the removal.
|
||||
|
||||
Call only when the usergroup is to be permanently removed.
|
||||
|
||||
Parameters:
|
||||
usergroupName
|
||||
string
|
||||
The name of the usergroup.
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
Use this to make sure CAMI.UnregisterUsergroup function and the
|
||||
CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
|
||||
|
||||
Return value:
|
||||
bool
|
||||
Whether the unregistering succeeded.
|
||||
]]
|
||||
function CAMI.UnregisterUsergroup(usergroupName, source)
|
||||
if not usergroups[usergroupName] then return false end
|
||||
|
||||
local usergroup = usergroups[usergroupName]
|
||||
usergroups[usergroupName] = nil
|
||||
|
||||
hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetUsergroups
|
||||
Retrieves all registered usergroups.
|
||||
|
||||
Return value:
|
||||
Table of CAMI_USERGROUP, indexed by their names.
|
||||
]]
|
||||
function CAMI.GetUsergroups()
|
||||
return usergroups
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetUsergroup
|
||||
Receives information about a usergroup.
|
||||
|
||||
Return value:
|
||||
CAMI_USERGROUP
|
||||
Returns nil when the usergroup does not exist.
|
||||
]]
|
||||
function CAMI.GetUsergroup(usergroupName)
|
||||
return usergroups[usergroupName]
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.UsergroupInherits
|
||||
Returns true when usergroupName1 inherits usergroupName2.
|
||||
Note that usergroupName1 does not need to be a direct child.
|
||||
Every usergroup trivially inherits itself.
|
||||
|
||||
Parameters:
|
||||
usergroupName1
|
||||
string
|
||||
The name of the usergroup that is queried.
|
||||
usergroupName2
|
||||
string
|
||||
The name of the usergroup of which is queried whether usergroupName
|
||||
inherits from.
|
||||
|
||||
Return value:
|
||||
bool
|
||||
Whether usergroupName1 inherits usergroupName2.
|
||||
]]
|
||||
function CAMI.UsergroupInherits(usergroupName1, usergroupName2)
|
||||
repeat
|
||||
if usergroupName1 == usergroupName2 then return true end
|
||||
|
||||
usergroupName1 = usergroups[usergroupName1] and
|
||||
usergroups[usergroupName1].Inherits or
|
||||
usergroupName1
|
||||
until not usergroups[usergroupName1] or
|
||||
usergroups[usergroupName1].Inherits == usergroupName1
|
||||
|
||||
-- One can only be sure the usergroup inherits from user if the
|
||||
-- usergroup isn't registered.
|
||||
return usergroupName1 == usergroupName2 or usergroupName2 == "user"
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.InheritanceRoot
|
||||
All usergroups must eventually inherit either user, admin or superadmin.
|
||||
Regardless of what inheritance mechism an admin may or may not have, this
|
||||
always applies.
|
||||
|
||||
This method always returns either user, admin or superadmin, based on what
|
||||
usergroups eventually inherit.
|
||||
|
||||
Parameters:
|
||||
usergroupName
|
||||
string
|
||||
The name of the usergroup of which the root of inheritance is
|
||||
requested
|
||||
|
||||
Return value:
|
||||
string
|
||||
The name of the root usergroup (either user, admin or superadmin)
|
||||
]]
|
||||
function CAMI.InheritanceRoot(usergroupName)
|
||||
if not usergroups[usergroupName] then return end
|
||||
|
||||
local inherits = usergroups[usergroupName].Inherits
|
||||
while inherits ~= usergroups[usergroupName].Inherits do
|
||||
usergroupName = usergroups[usergroupName].Inherits
|
||||
end
|
||||
|
||||
return usergroupName
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.RegisterPrivilege
|
||||
Registers a privilege with CAMI.
|
||||
Note: do NOT register all your admin mod's privileges with this function!
|
||||
This function is for third party addons to register privileges
|
||||
with admin mods, not for admin mods sharing the privileges amongst one
|
||||
another.
|
||||
|
||||
Parameters:
|
||||
privilege
|
||||
CAMI_PRIVILEGE
|
||||
See CAMI_PRIVILEGE structure.
|
||||
|
||||
Return value:
|
||||
CAMI_PRIVILEGE
|
||||
The privilege given as argument.
|
||||
]]
|
||||
function CAMI.RegisterPrivilege(privilege)
|
||||
privileges[privilege.Name] = privilege
|
||||
|
||||
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
|
||||
|
||||
return privilege
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.UnregisterPrivilege
|
||||
Unregisters a privilege from CAMI. This will call a hook that will notify
|
||||
all other admin mods of the removal.
|
||||
|
||||
Call only when the privilege is to be permanently removed.
|
||||
|
||||
Parameters:
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
|
||||
Return value:
|
||||
bool
|
||||
Whether the unregistering succeeded.
|
||||
]]
|
||||
function CAMI.UnregisterPrivilege(privilegeName)
|
||||
if not privileges[privilegeName] then return false end
|
||||
|
||||
local privilege = privileges[privilegeName]
|
||||
privileges[privilegeName] = nil
|
||||
|
||||
hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetPrivileges
|
||||
Retrieves all registered privileges.
|
||||
|
||||
Return value:
|
||||
Table of CAMI_PRIVILEGE, indexed by their names.
|
||||
]]
|
||||
function CAMI.GetPrivileges()
|
||||
return privileges
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetPrivilege
|
||||
Receives information about a privilege.
|
||||
|
||||
Return value:
|
||||
CAMI_PRIVILEGE when the privilege exists.
|
||||
nil when the privilege does not exist.
|
||||
]]
|
||||
function CAMI.GetPrivilege(privilegeName)
|
||||
return privileges[privilegeName]
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.PlayerHasAccess
|
||||
Queries whether a certain player has the right to perform a certain action.
|
||||
|
||||
Parameters:
|
||||
actorPly
|
||||
Player
|
||||
The player of which is requested whether they have the privilege.
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
callback
|
||||
function(bool, string) or nil
|
||||
This function will be called with the answer. The bool signifies the
|
||||
yes or no answer as to whether the player is allowed. The string
|
||||
will optionally give a reason.
|
||||
|
||||
Give an explicit nil here to get an answer immediately
|
||||
Important note: May throw an error when the admin mod doesn't
|
||||
give an answer immediately!
|
||||
targetPly
|
||||
Optional.
|
||||
The player on which the privilege is executed.
|
||||
extraInfoTbl
|
||||
Optional.
|
||||
Table containing extra information.
|
||||
Officially supported members:
|
||||
Fallback
|
||||
string
|
||||
Either of user/admin/superadmin. When no admin mod replies,
|
||||
the decision is based on the admin status of the user.
|
||||
Defaults to admin if not given.
|
||||
IgnoreImmunity
|
||||
bool
|
||||
Ignore any immunity mechanisms an admin mod might have.
|
||||
CommandArguments
|
||||
table
|
||||
Extra arguments that were given to the privilege command.
|
||||
|
||||
Return value:
|
||||
If callback is specified:
|
||||
None
|
||||
Otherwise:
|
||||
hasAccess
|
||||
bool
|
||||
Whether the player has access
|
||||
reason
|
||||
Optional.
|
||||
The reason why a player does or does not have access.
|
||||
]]
|
||||
-- Default access handler
|
||||
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
|
||||
function(_, actorPly, privilegeName, callback, _, extraInfoTbl)
|
||||
-- The server always has access in the fallback
|
||||
if not IsValid(actorPly) then return callback(true, "Fallback.") end
|
||||
|
||||
local priv = privileges[privilegeName]
|
||||
|
||||
local fallback = extraInfoTbl and (
|
||||
not extraInfoTbl.Fallback and actorPly:IsAdmin() or
|
||||
extraInfoTbl.Fallback == "user" and true or
|
||||
extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or
|
||||
extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin())
|
||||
|
||||
|
||||
if not priv then return callback(fallback, "Fallback.") end
|
||||
|
||||
callback(
|
||||
priv.MinAccess == "user" or
|
||||
priv.MinAccess == "admin" and actorPly:IsAdmin() or
|
||||
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
|
||||
, "Fallback.")
|
||||
end,
|
||||
["CAMI.SteamIDHasAccess"] =
|
||||
function(_, _, _, callback)
|
||||
callback(false, "No information available.")
|
||||
end
|
||||
}
|
||||
function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly,
|
||||
extraInfoTbl)
|
||||
local hasAccess, reason = nil, nil
|
||||
local callback_ = callback or function(hA, r) hasAccess, reason = hA, r end
|
||||
|
||||
hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly,
|
||||
privilegeName, callback_, targetPly, extraInfoTbl)
|
||||
|
||||
if callback ~= nil then return end
|
||||
|
||||
if hasAccess == nil then
|
||||
local err = [[The function CAMI.PlayerHasAccess was used to find out
|
||||
whether Player %s has privilege "%s", but an admin mod did not give an
|
||||
immediate answer!]]
|
||||
error(string.format(err,
|
||||
actorPly:IsPlayer() and actorPly:Nick() or tostring(actorPly),
|
||||
privilegeName))
|
||||
end
|
||||
|
||||
return hasAccess, reason
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.GetPlayersWithAccess
|
||||
Finds the list of currently joined players who have the right to perform a
|
||||
certain action.
|
||||
NOTE: this function will NOT return an immediate result!
|
||||
The result is in the callback!
|
||||
|
||||
Parameters:
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
callback
|
||||
function(players)
|
||||
This function will be called with the list of players with access.
|
||||
targetPly
|
||||
Optional.
|
||||
The player on which the privilege is executed.
|
||||
extraInfoTbl
|
||||
Optional.
|
||||
Table containing extra information.
|
||||
Officially supported members:
|
||||
Fallback
|
||||
string
|
||||
Either of user/admin/superadmin. When no admin mod replies,
|
||||
the decision is based on the admin status of the user.
|
||||
Defaults to admin if not given.
|
||||
IgnoreImmunity
|
||||
bool
|
||||
Ignore any immunity mechanisms an admin mod might have.
|
||||
CommandArguments
|
||||
table
|
||||
Extra arguments that were given to the privilege command.
|
||||
]]
|
||||
function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly,
|
||||
extraInfoTbl)
|
||||
local allowedPlys = {}
|
||||
local allPlys = player.GetAll()
|
||||
local countdown = #allPlys
|
||||
|
||||
local function onResult(ply, hasAccess, _)
|
||||
countdown = countdown - 1
|
||||
|
||||
if hasAccess then table.insert(allowedPlys, ply) end
|
||||
if countdown == 0 then callback(allowedPlys) end
|
||||
end
|
||||
|
||||
for _, ply in ipairs(allPlys) do
|
||||
CAMI.PlayerHasAccess(ply, privilegeName,
|
||||
function(...) onResult(ply, ...) end,
|
||||
targetPly, extraInfoTbl)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.SteamIDHasAccess
|
||||
Queries whether a player with a steam ID has the right to perform a certain
|
||||
action.
|
||||
Note: the player does not need to be in the server for this to
|
||||
work.
|
||||
|
||||
Note: this function does NOT return an immediate result!
|
||||
The result is in the callback!
|
||||
|
||||
Parameters:
|
||||
actorSteam
|
||||
Player
|
||||
The SteamID of the player of which is requested whether they have
|
||||
the privilege.
|
||||
privilegeName
|
||||
string
|
||||
The name of the privilege.
|
||||
callback
|
||||
function(bool, string)
|
||||
This function will be called with the answer. The bool signifies the
|
||||
yes or no answer as to whether the player is allowed. The string
|
||||
will optionally give a reason.
|
||||
targetSteam
|
||||
Optional.
|
||||
The SteamID of the player on which the privilege is executed.
|
||||
extraInfoTbl
|
||||
Optional.
|
||||
Table containing extra information.
|
||||
Officially supported members:
|
||||
IgnoreImmunity
|
||||
bool
|
||||
Ignore any immunity mechanisms an admin mod might have.
|
||||
CommandArguments
|
||||
table
|
||||
Extra arguments that were given to the privilege command.
|
||||
|
||||
Return value:
|
||||
None, the answer is given in the callback function in order to allow
|
||||
for the admin mod to perform e.g. a database lookup.
|
||||
]]
|
||||
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
|
||||
targetSteam, extraInfoTbl)
|
||||
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
|
||||
privilegeName, callback, targetSteam, extraInfoTbl)
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.SignalUserGroupChanged
|
||||
Signify that your admin mod has changed the usergroup of a player. This
|
||||
function communicates to other admin mods what it thinks the usergroup
|
||||
of a player should be.
|
||||
|
||||
Listen to the hook to receive the usergroup changes of other admin mods.
|
||||
|
||||
Parameters:
|
||||
ply
|
||||
Player
|
||||
The player for which the usergroup is changed
|
||||
old
|
||||
string
|
||||
The previous usergroup of the player.
|
||||
new
|
||||
string
|
||||
The new usergroup of the player.
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
]]
|
||||
function CAMI.SignalUserGroupChanged(ply, old, new, source)
|
||||
hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source)
|
||||
end
|
||||
|
||||
--[[
|
||||
CAMI.SignalSteamIDUserGroupChanged
|
||||
Signify that your admin mod has changed the usergroup of a disconnected
|
||||
player. This communicates to other admin mods what it thinks the usergroup
|
||||
of a player should be.
|
||||
|
||||
Listen to the hook to receive the usergroup changes of other admin mods.
|
||||
|
||||
Parameters:
|
||||
ply
|
||||
string
|
||||
The steam ID of the player for which the usergroup is changed
|
||||
old
|
||||
string
|
||||
The previous usergroup of the player.
|
||||
new
|
||||
string
|
||||
The new usergroup of the player.
|
||||
source
|
||||
any
|
||||
Identifier for your own admin mod. Can be anything.
|
||||
]]
|
||||
function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source)
|
||||
hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source)
|
||||
end
|
||||
791
gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua
vendored
Normal file
791
gamemodes/helix/gamemode/core/libs/thirdparty/sh_date.lua
vendored
Normal file
@@ -0,0 +1,791 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
---------------------------------------------------------------------------------------
|
||||
-- Module for date and time calculations
|
||||
--
|
||||
-- Version 2.1.1
|
||||
-- Copyright (C) 2006, by Jas Latrix (jastejada@yahoo.com)
|
||||
-- Copyright (C) 2013-2014, by Thijs Schreijer
|
||||
-- Licensed under MIT, http://opensource.org/licenses/MIT
|
||||
-- https://github.com/Tieske/date
|
||||
|
||||
--[[
|
||||
The MIT License (MIT) http://opensource.org/licenses/MIT
|
||||
|
||||
Copyright (c) 2013-2017 Thijs Schreijer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
]]
|
||||
|
||||
--[[ CONSTANTS ]]--
|
||||
local HOURPERDAY = 24
|
||||
local MINPERHOUR = 60
|
||||
local MINPERDAY = 1440 -- 24*60
|
||||
local SECPERMIN = 60
|
||||
local SECPERHOUR = 3600 -- 60*60
|
||||
local SECPERDAY = 86400 -- 24*60*60
|
||||
local TICKSPERSEC = 1000000
|
||||
local TICKSPERDAY = 86400000000
|
||||
local TICKSPERHOUR = 3600000000
|
||||
local TICKSPERMIN = 60000000
|
||||
local DAYNUM_MAX = 365242500 -- Sat Jan 01 1000000 00:00:00
|
||||
local DAYNUM_MIN = -365242500 -- Mon Jan 01 1000000 BCE 00:00:00
|
||||
local DAYNUM_DEF = 0 -- Mon Jan 01 0001 00:00:00
|
||||
local _;
|
||||
--[[ LOCAL ARE FASTER ]]--
|
||||
local type = type
|
||||
local pairs = pairs
|
||||
local error = error
|
||||
local assert = assert
|
||||
local tonumber = tonumber
|
||||
local tostring = tostring
|
||||
local string = string
|
||||
local math = math
|
||||
local os = os
|
||||
local unpack = unpack or table.unpack
|
||||
local pack = table.pack or function(...) return { n = select('#', ...), ... } end
|
||||
local setmetatable = setmetatable
|
||||
local getmetatable = getmetatable
|
||||
--[[ EXTRA FUNCTIONS ]]--
|
||||
local fmt = string.format
|
||||
local lwr = string.lower
|
||||
local upr = string.upper
|
||||
local rep = string.rep
|
||||
local len = string.len
|
||||
local sub = string.sub
|
||||
local gsub = string.gsub
|
||||
local gmatch = string.gmatch or string.gfind
|
||||
local find = string.find
|
||||
local ostime = os.time
|
||||
local osdate = os.date
|
||||
local floor = math.floor
|
||||
local ceil = math.ceil
|
||||
local abs = math.abs
|
||||
-- removes the decimal part of a number
|
||||
local function fix(n) n = tonumber(n) return n and ((n > 0 and floor or ceil)(n)) end
|
||||
-- returns the modulo n % d;
|
||||
local function mod(n,d) return n - d*floor(n/d) end
|
||||
-- rounds a number;
|
||||
local function round(n, d) d=d^10 return floor((n*d)+.5)/d end
|
||||
-- rounds a number to whole;
|
||||
local function whole(n)return floor(n+.5)end
|
||||
-- is `str` in string list `tbl`, `ml` is the minimun len
|
||||
local function inlist(str, tbl, ml, tn)
|
||||
local sl = len(str)
|
||||
if sl < (ml or 0) then return nil end
|
||||
str = lwr(str)
|
||||
for k, v in pairs(tbl) do
|
||||
if str == lwr(sub(v, 1, sl)) then
|
||||
if tn then tn[0] = k end
|
||||
return k
|
||||
end
|
||||
end
|
||||
end
|
||||
local function fnil() end
|
||||
local function fret(x)return x;end
|
||||
--[[ DATE FUNCTIONS ]]--
|
||||
local DATE_EPOCH -- to be set later
|
||||
local sl_weekdays = {
|
||||
[0]="Dimanche",[1]="Lundi",[2]="Mardi",[3]="Mercredi",[4]="Jeudi",[5]="Vendredi",[6]="Samedi",
|
||||
[7]="Dim",[8]="Lun",[9]="Mar",[10]="Mer",[11]="Jeu",[12]="Ven",[13]="Sam",
|
||||
}
|
||||
local sl_meridian = {[-1]="AM", [1]="PM"}
|
||||
local sl_months = {
|
||||
[00]="Janvier", [01]="Février", [02]="Mars",
|
||||
[03]="Avril", [04]="Mai", [05]="Juin",
|
||||
[06]="Juillet", [07]="Août", [08]="Septembre",
|
||||
[09]="Octobre", [10]="Novembre", [11]="Décembre",
|
||||
[12]="Jan", [13]="Fev", [14]="Mar",
|
||||
[15]="Avr", [16]="Mai", [17]="Jui",
|
||||
[18]="Jul", [19]="Aou", [20]="Sep",
|
||||
[21]="Oct", [22]="Nov", [23]="Dec",
|
||||
}
|
||||
-- added the '.2' to avoid collision, use `fix` to remove
|
||||
local sl_timezone = {
|
||||
[000]="utc", [0.2]="gmt",
|
||||
[300]="est", [240]="edt",
|
||||
[360]="cst", [300.2]="cdt",
|
||||
[420]="mst", [360.2]="mdt",
|
||||
[480]="pst", [420.2]="pdt",
|
||||
}
|
||||
-- set the day fraction resolution
|
||||
local function setticks(t)
|
||||
TICKSPERSEC = t;
|
||||
TICKSPERDAY = SECPERDAY*TICKSPERSEC
|
||||
TICKSPERHOUR= SECPERHOUR*TICKSPERSEC
|
||||
TICKSPERMIN = SECPERMIN*TICKSPERSEC
|
||||
end
|
||||
-- is year y leap year?
|
||||
local function isleapyear(y) -- y must be int!
|
||||
return (mod(y, 4) == 0 and (mod(y, 100) ~= 0 or mod(y, 400) == 0))
|
||||
end
|
||||
-- day since year 0
|
||||
local function dayfromyear(y) -- y must be int!
|
||||
return 365*y + floor(y/4) - floor(y/100) + floor(y/400)
|
||||
end
|
||||
-- day number from date, month is zero base
|
||||
local function makedaynum(y, m, d)
|
||||
local mm = mod(mod(m,12) + 10, 12)
|
||||
return dayfromyear(y + floor(m/12) - floor(mm/10)) + floor((mm*306 + 5)/10) + d - 307
|
||||
--local yy = y + floor(m/12) - floor(mm/10)
|
||||
--return dayfromyear(yy) + floor((mm*306 + 5)/10) + (d - 1)
|
||||
end
|
||||
-- date from day number, month is zero base
|
||||
local function breakdaynum(g)
|
||||
local g = g + 306
|
||||
local y = floor((10000*g + 14780)/3652425)
|
||||
local d = g - dayfromyear(y)
|
||||
if d < 0 then y = y - 1; d = g - dayfromyear(y) end
|
||||
local mi = floor((100*d + 52)/3060)
|
||||
return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1)
|
||||
end
|
||||
--[[ for floats or int32 Lua Number data type
|
||||
local function breakdaynum2(g)
|
||||
local g, n = g + 306;
|
||||
local n400 = floor(g/DI400Y);n = mod(g,DI400Y);
|
||||
local n100 = floor(n/DI100Y);n = mod(n,DI100Y);
|
||||
local n004 = floor(n/DI4Y); n = mod(n,DI4Y);
|
||||
local n001 = floor(n/365); n = mod(n,365);
|
||||
local y = (n400*400) + (n100*100) + (n004*4) + n001 - ((n001 == 4 or n100 == 4) and 1 or 0)
|
||||
local d = g - dayfromyear(y)
|
||||
local mi = floor((100*d + 52)/3060)
|
||||
return (floor((mi + 2)/12) + y), mod(mi + 2,12), (d - floor((mi*306 + 5)/10) + 1)
|
||||
end
|
||||
]]
|
||||
-- day fraction from time
|
||||
local function makedayfrc(h,r,s,t)
|
||||
return ((h*60 + r)*60 + s)*TICKSPERSEC + t
|
||||
end
|
||||
-- time from day fraction
|
||||
local function breakdayfrc(df)
|
||||
return
|
||||
mod(floor(df/TICKSPERHOUR),HOURPERDAY),
|
||||
mod(floor(df/TICKSPERMIN ),MINPERHOUR),
|
||||
mod(floor(df/TICKSPERSEC ),SECPERMIN),
|
||||
mod(df,TICKSPERSEC)
|
||||
end
|
||||
-- weekday sunday = 0, monday = 1 ...
|
||||
local function weekday(dn) return mod(dn + 1, 7) end
|
||||
-- yearday 0 based ...
|
||||
local function yearday(dn)
|
||||
return dn - dayfromyear((breakdaynum(dn))-1)
|
||||
end
|
||||
-- parse v as a month
|
||||
local function getmontharg(v)
|
||||
local m = tonumber(v);
|
||||
return (m and fix(m - 1)) or inlist(tostring(v) or "", sl_months, 2)
|
||||
end
|
||||
-- get daynum of isoweek one of year y
|
||||
local function isow1(y)
|
||||
local f = makedaynum(y, 0, 4) -- get the date for the 4-Jan of year `y`
|
||||
local d = weekday(f)
|
||||
d = d == 0 and 7 or d -- get the ISO day number, 1 == Monday, 7 == Sunday
|
||||
return f + (1 - d)
|
||||
end
|
||||
local function isowy(dn)
|
||||
local w1;
|
||||
local y = (breakdaynum(dn))
|
||||
if dn >= makedaynum(y, 11, 29) then
|
||||
w1 = isow1(y + 1);
|
||||
if dn < w1 then
|
||||
w1 = isow1(y);
|
||||
else
|
||||
y = y + 1;
|
||||
end
|
||||
else
|
||||
w1 = isow1(y);
|
||||
if dn < w1 then
|
||||
w1 = isow1(y-1)
|
||||
y = y - 1
|
||||
end
|
||||
end
|
||||
return floor((dn-w1)/7)+1, y
|
||||
end
|
||||
local function isoy(dn)
|
||||
local y = (breakdaynum(dn))
|
||||
return y + (((dn >= makedaynum(y, 11, 29)) and (dn >= isow1(y + 1))) and 1 or (dn < isow1(y) and -1 or 0))
|
||||
end
|
||||
local function makedaynum_isoywd(y,w,d)
|
||||
return isow1(y) + 7*w + d - 8 -- simplified: isow1(y) + ((w-1)*7) + (d-1)
|
||||
end
|
||||
--[[ THE DATE MODULE ]]--
|
||||
local fmtstr = "%x %X";
|
||||
--#if not DATE_OBJECT_AFX then
|
||||
local date = {}
|
||||
setmetatable(date, date)
|
||||
-- Version: VMMMRRRR; V-Major, M-Minor, R-Revision; e.g. 5.45.321 == 50450321
|
||||
date.version = 20010001 -- 2.1.1
|
||||
--#end -- not DATE_OBJECT_AFX
|
||||
--[[ THE DATE OBJECT ]]--
|
||||
local dobj = {}
|
||||
dobj.__index = dobj
|
||||
dobj.__metatable = dobj
|
||||
-- shout invalid arg
|
||||
local function date_error_arg() return error("invalid argument(s)",0) end
|
||||
-- create new date object
|
||||
local function date_new(dn, df)
|
||||
return setmetatable({daynum=dn, dayfrc=df}, dobj)
|
||||
end
|
||||
-- is `v` a date object?
|
||||
local function date_isdobj(v)
|
||||
return (istable(v) and getmetatable(v) == dobj) and v
|
||||
end
|
||||
|
||||
--#if not NO_LOCAL_TIME_SUPPORT then
|
||||
-- magic year table
|
||||
local date_epoch, yt;
|
||||
local function getequivyear(y)
|
||||
assert(not yt)
|
||||
yt = {}
|
||||
local de, dw, dy = date_epoch:copy()
|
||||
for i = 0, 3000 do
|
||||
de:setyear(de:getyear() + 1, 1, 1)
|
||||
dy = de:getyear()
|
||||
dw = de:getweekday() * (isleapyear(dy) and -1 or 1)
|
||||
if not yt[dw] then yt[dw] = dy end --print(de)
|
||||
if yt[1] and yt[2] and yt[3] and yt[4] and yt[5] and yt[6] and yt[7] and yt[-1] and yt[-2] and yt[-3] and yt[-4] and yt[-5] and yt[-6] and yt[-7] then
|
||||
getequivyear = function(y) return yt[ (weekday(makedaynum(y, 0, 1)) + 1) * (isleapyear(y) and -1 or 1) ] end
|
||||
return getequivyear(y)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- TimeValue from daynum and dayfrc
|
||||
local function dvtotv(dn, df)
|
||||
return fix(dn - DATE_EPOCH) * SECPERDAY + (df/1000)
|
||||
end
|
||||
-- TimeValue from date and time
|
||||
local function totv(y,m,d,h,r,s)
|
||||
return (makedaynum(y, m, d) - DATE_EPOCH) * SECPERDAY + ((h*60 + r)*60 + s)
|
||||
end
|
||||
-- TimeValue from TimeTable
|
||||
local function tmtotv(tm)
|
||||
return tm and totv(tm.year, tm.month - 1, tm.day, tm.hour, tm.min, tm.sec)
|
||||
end
|
||||
-- Returns the bias in seconds of utc time daynum and dayfrc
|
||||
local function getbiasutc2(self)
|
||||
local y,m,d = breakdaynum(self.daynum)
|
||||
local h,r,s = breakdayfrc(self.dayfrc)
|
||||
local tvu = totv(y,m,d,h,r,s) -- get the utc TimeValue of date and time
|
||||
local tml = osdate("*t", tvu) -- get the local TimeTable of tvu
|
||||
if (not tml) or (tml.year > (y+1) or tml.year < (y-1)) then -- failed try the magic
|
||||
y = getequivyear(y)
|
||||
tvu = totv(y,m,d,h,r,s)
|
||||
tml = osdate("*t", tvu)
|
||||
end
|
||||
local tvl = tmtotv(tml)
|
||||
if tvu and tvl then
|
||||
return tvu - tvl, tvu, tvl
|
||||
else
|
||||
return error("failed to get bias from utc time")
|
||||
end
|
||||
end
|
||||
-- Returns the bias in seconds of local time daynum and dayfrc
|
||||
local function getbiasloc2(daynum, dayfrc)
|
||||
local tvu
|
||||
-- extract date and time
|
||||
local y,m,d = breakdaynum(daynum)
|
||||
local h,r,s = breakdayfrc(dayfrc)
|
||||
-- get equivalent TimeTable
|
||||
local tml = {year=y, month=m+1, day=d, hour=h, min=r, sec=s}
|
||||
-- get equivalent TimeValue
|
||||
local tvl = tmtotv(tml)
|
||||
|
||||
local function chkutc()
|
||||
tml.isdst = nil; local tvug = ostime(tml) if tvug and (tvl == tmtotv(osdate("*t", tvug))) then tvu = tvug return end
|
||||
tml.isdst = true; local tvud = ostime(tml) if tvud and (tvl == tmtotv(osdate("*t", tvud))) then tvu = tvud return end
|
||||
tvu = tvud or tvug
|
||||
end
|
||||
chkutc()
|
||||
if not tvu then
|
||||
tml.year = getequivyear(y)
|
||||
tvl = tmtotv(tml)
|
||||
chkutc()
|
||||
end
|
||||
return ((tvu and tvl) and (tvu - tvl)) or error("failed to get bias from local time"), tvu, tvl
|
||||
end
|
||||
--#end -- not NO_LOCAL_TIME_SUPPORT
|
||||
|
||||
--#if not DATE_OBJECT_AFX then
|
||||
-- the date parser
|
||||
local strwalker = {} -- ^Lua regular expression is not as powerful as Perl$
|
||||
strwalker.__index = strwalker
|
||||
local function newstrwalker(s)return setmetatable({s=s, i=1, e=1, c=len(s)}, strwalker) end
|
||||
function strwalker:aimchr() return "\n" .. self.s .. "\n" .. rep(".",self.e-1) .. "^" end
|
||||
function strwalker:finish() return self.i > self.c end
|
||||
function strwalker:back() self.i = self.e return self end
|
||||
function strwalker:restart() self.i, self.e = 1, 1 return self end
|
||||
function strwalker:match(s) return (find(self.s, s, self.i)) end
|
||||
function strwalker:__call(s, f)-- print("strwalker:__call "..s..self:aimchr())
|
||||
local is, ie; is, ie, self[1], self[2], self[3], self[4], self[5] = find(self.s, s, self.i)
|
||||
if is then self.e, self.i = self.i, 1+ie; if f then f(unpack(self)) end return self end
|
||||
end
|
||||
local function date_parse(str)
|
||||
local y,m,d, h,r,s, z, w,u, j, e, k, x,v,c, chkfin, dn,df;
|
||||
local sw = newstrwalker(gsub(gsub(str, "(%b())", ""),"^(%s*)","")) -- remove comment, trim leading space
|
||||
--local function error_out() print(y,m,d,h,r,s) end
|
||||
local function error_dup(q) --[[error_out()]] error("duplicate value: " .. (q or "") .. sw:aimchr()) end
|
||||
local function error_syn(q) --[[error_out()]] error("syntax error: " .. (q or "") .. sw:aimchr()) end
|
||||
local function error_inv(q) --[[error_out()]] error("invalid date: " .. (q or "") .. sw:aimchr()) end
|
||||
local function sety(q) y = y and error_dup() or tonumber(q); end
|
||||
local function setm(q) m = (m or w or j) and error_dup(m or w or j) or tonumber(q) end
|
||||
local function setd(q) d = d and error_dup() or tonumber(q) end
|
||||
local function seth(q) h = h and error_dup() or tonumber(q) end
|
||||
local function setr(q) r = r and error_dup() or tonumber(q) end
|
||||
local function sets(q) s = s and error_dup() or tonumber(q) end
|
||||
local function adds(q) s = s + tonumber(q) end
|
||||
local function setj(q) j = (m or w or j) and error_dup() or tonumber(q); end
|
||||
local function setz(q) z = (z ~= 0 and z) and error_dup() or q end
|
||||
local function setzn(zs,zn) zn = tonumber(zn); setz( ((zn<24) and (zn*60) or (mod(zn,100) + floor(zn/100) * 60))*( zs=='+' and -1 or 1) ) end
|
||||
local function setzc(zs,zh,zm) setz( ((tonumber(zh)*60) + tonumber(zm))*( zs=='+' and -1 or 1) ) end
|
||||
|
||||
if not (sw("^(%d%d%d%d)",sety) and (sw("^(%-?)(%d%d)%1(%d%d)",function(_,a,b) setm(tonumber(a)); setd(tonumber(b)) end) or sw("^(%-?)[Ww](%d%d)%1(%d?)",function(_,a,b) w, u = tonumber(a), tonumber(b or 1) end) or sw("^%-?(%d%d%d)",setj) or sw("^%-?(%d%d)",function(a) setm(a);setd(1) end))
|
||||
and ((sw("^%s*[Tt]?(%d%d):?",seth) and sw("^(%d%d):?",setr) and sw("^(%d%d)",sets) and sw("^(%.%d+)",adds))
|
||||
or sw:finish() or (sw"^%s*$" or sw"^%s*[Zz]%s*$" or sw("^%s-([%+%-])(%d%d):?(%d%d)%s*$",setzc) or sw("^%s*([%+%-])(%d%d)%s*$",setzn))
|
||||
) )
|
||||
then --print(y,m,d,h,r,s,z,w,u,j)
|
||||
sw:restart(); y,m,d,h,r,s,z,w,u,j = nil;
|
||||
repeat -- print(sw:aimchr())
|
||||
if sw("^[tT:]?%s*(%d%d?):",seth) then --print("$Time")
|
||||
_ = sw("^%s*(%d%d?)",setr) and sw("^%s*:%s*(%d%d?)",sets) and sw("^(%.%d+)",adds)
|
||||
elseif sw("^(%d+)[/\\%s,-]?%s*") then --print("$Digits")
|
||||
x, c = tonumber(sw[1]), len(sw[1])
|
||||
if (x >= 70) or (m and d and (not y)) or (c > 3) then
|
||||
sety( x + ((x >= 100 or c>3)and 0 or 1900) )
|
||||
else
|
||||
if m then setd(x) else m = x end
|
||||
end
|
||||
elseif sw("^(%a+)[/\\%s,-]?%s*") then --print("$Words")
|
||||
x = sw[1]
|
||||
if inlist(x, sl_months, 2, sw) then
|
||||
if m and (not d) and (not y) then d, m = m, false end
|
||||
setm(mod(sw[0],12)+1)
|
||||
elseif inlist(x, sl_timezone, 2, sw) then
|
||||
c = fix(sw[0]) -- ignore gmt and utc
|
||||
if c ~= 0 then setz(c, x) end
|
||||
elseif inlist(x, sl_weekdays, 2, sw) then
|
||||
k = sw[0]
|
||||
else
|
||||
sw:back()
|
||||
-- am pm bce ad ce bc
|
||||
if sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*[Ee]%s*(%2)%s*") or sw("^([bB])%s*(%.?)%s*[Cc]%s*(%2)%s*") then
|
||||
e = e and error_dup() or -1
|
||||
elseif sw("^([aA])%s*(%.?)%s*[Dd]%s*(%2)%s*") or sw("^([cC])%s*(%.?)%s*[Ee]%s*(%2)%s*") then
|
||||
e = e and error_dup() or 1
|
||||
elseif sw("^([PApa])%s*(%.?)%s*[Mm]?%s*(%2)%s*") then
|
||||
x = lwr(sw[1]) -- there should be hour and it must be correct
|
||||
if (not h) or (h > 12) or (h < 0) then return error_inv() end
|
||||
if x == 'a' and h == 12 then h = 0 end -- am
|
||||
if x == 'p' and h ~= 12 then h = h + 12 end -- pm
|
||||
else error_syn() end
|
||||
end
|
||||
elseif not(sw("^([+-])(%d%d?):(%d%d)",setzc) or sw("^([+-])(%d+)",setzn) or sw("^[Zz]%s*$")) then -- sw{"([+-])",{"(%d%d?):(%d%d)","(%d+)"}}
|
||||
error_syn("?")
|
||||
end
|
||||
sw("^%s*") until sw:finish()
|
||||
--else print("$Iso(Date|Time|Zone)")
|
||||
end
|
||||
-- if date is given, it must be complete year, month & day
|
||||
if (not y and not h) or ((m and not d) or (d and not m)) or ((m and w) or (m and j) or (j and w)) then return error_inv("!") end
|
||||
-- fix month
|
||||
if m then m = m - 1 end
|
||||
-- fix year if we are on BCE
|
||||
if e and e < 0 and y > 0 then y = 1 - y end
|
||||
-- create date object
|
||||
dn = (y and ((w and makedaynum_isoywd(y,w,u)) or (j and makedaynum(y, 0, j)) or makedaynum(y, m, d))) or DAYNUM_DEF
|
||||
df = makedayfrc(h or 0, r or 0, s or 0, 0) + ((z or 0)*TICKSPERMIN)
|
||||
--print("Zone",h,r,s,z,m,d,y,df)
|
||||
return date_new(dn, df) -- no need to :normalize();
|
||||
end
|
||||
local function date_fromtable(v)
|
||||
local y, m, d = fix(v.year), getmontharg(v.month), fix(v.day)
|
||||
local h, r, s, t = tonumber(v.hour), tonumber(v.min), tonumber(v.sec), tonumber(v.ticks)
|
||||
-- atleast there is time or complete date
|
||||
if (y or m or d) and (not(y and m and d)) then return error("incomplete table") end
|
||||
return (y or h or r or s or t) and date_new(y and makedaynum(y, m, d) or DAYNUM_DEF, makedayfrc(h or 0, r or 0, s or 0, t or 0))
|
||||
end
|
||||
local tmap = {
|
||||
['number'] = function(v) return date_epoch:copy():addseconds(v) end,
|
||||
['string'] = function(v) return date_parse(v) end,
|
||||
['boolean']= function(v) return date_fromtable(osdate(v and "!*t" or "*t")) end,
|
||||
['table'] = function(v) local ref = getmetatable(v) == dobj; return ref and v or date_fromtable(v), ref end
|
||||
}
|
||||
local function date_getdobj(v)
|
||||
local o, r = (tmap[type(v)] or fnil)(v);
|
||||
return (o and o:normalize() or error"invalid date time value"), r -- if r is true then o is a reference to a date obj
|
||||
end
|
||||
--#end -- not DATE_OBJECT_AFX
|
||||
local function date_from(...)
|
||||
local arg = pack(...)
|
||||
local y, m, d = fix(arg[1]), getmontharg(arg[2]), fix(arg[3])
|
||||
local h, r, s, t = tonumber(arg[4] or 0), tonumber(arg[5] or 0), tonumber(arg[6] or 0), tonumber(arg[7] or 0)
|
||||
if y and m and d and h and r and s and t then
|
||||
return date_new(makedaynum(y, m, d), makedayfrc(h, r, s, t)):normalize()
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
|
||||
--[[ THE DATE OBJECT METHODS ]]--
|
||||
function dobj:normalize()
|
||||
local dn, df = fix(self.daynum), self.dayfrc
|
||||
self.daynum, self.dayfrc = dn + floor(df/TICKSPERDAY), mod(df, TICKSPERDAY)
|
||||
return (dn >= DAYNUM_MIN and dn <= DAYNUM_MAX) and self or error("date beyond imposed limits:"..self)
|
||||
end
|
||||
|
||||
function dobj:getdate() local y, m, d = breakdaynum(self.daynum) return y, m+1, d end
|
||||
function dobj:gettime() return breakdayfrc(self.dayfrc) end
|
||||
|
||||
function dobj:getclockhour() local h = self:gethours() return h>12 and mod(h,12) or (h==0 and 12 or h) end
|
||||
|
||||
function dobj:getyearday() return yearday(self.daynum) + 1 end
|
||||
function dobj:getweekday() return weekday(self.daynum) + 1 end -- in lua weekday is sunday = 1, monday = 2 ...
|
||||
|
||||
function dobj:getyear() local r,_,_ = breakdaynum(self.daynum) return r end
|
||||
function dobj:getmonth() local _,r,_ = breakdaynum(self.daynum) return r+1 end-- in lua month is 1 base
|
||||
function dobj:getday() local _,_,r = breakdaynum(self.daynum) return r end
|
||||
function dobj:gethours() return mod(floor(self.dayfrc/TICKSPERHOUR),HOURPERDAY) end
|
||||
function dobj:getminutes() return mod(floor(self.dayfrc/TICKSPERMIN), MINPERHOUR) end
|
||||
function dobj:getseconds() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN) end
|
||||
function dobj:getfracsec() return mod(floor(self.dayfrc/TICKSPERSEC ),SECPERMIN)+(mod(self.dayfrc,TICKSPERSEC)/TICKSPERSEC) end
|
||||
function dobj:getticks(u) local x = mod(self.dayfrc,TICKSPERSEC) return u and ((x*u)/TICKSPERSEC) or x end
|
||||
|
||||
function dobj:getweeknumber(wdb)
|
||||
local wd, yd = weekday(self.daynum), yearday(self.daynum)
|
||||
if wdb then
|
||||
wdb = tonumber(wdb)
|
||||
if wdb then
|
||||
wd = mod(wd-(wdb-1),7)-- shift the week day base
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
return (yd < wd and 0) or (floor(yd/7) + ((mod(yd, 7)>=wd) and 1 or 0))
|
||||
end
|
||||
|
||||
function dobj:getisoweekday() return mod(weekday(self.daynum)-1,7)+1 end -- sunday = 7, monday = 1 ...
|
||||
function dobj:getisoweeknumber() return (isowy(self.daynum)) end
|
||||
function dobj:getisoyear() return isoy(self.daynum) end
|
||||
function dobj:getisodate()
|
||||
local w, y = isowy(self.daynum)
|
||||
return y, w, self:getisoweekday()
|
||||
end
|
||||
function dobj:setisoyear(y, w, d)
|
||||
local cy, cw, cd = self:getisodate()
|
||||
if y then cy = fix(tonumber(y))end
|
||||
if w then cw = fix(tonumber(w))end
|
||||
if d then cd = fix(tonumber(d))end
|
||||
if cy and cw and cd then
|
||||
self.daynum = makedaynum_isoywd(cy, cw, cd)
|
||||
return self:normalize()
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
|
||||
function dobj:setisoweekday(d) return self:setisoyear(nil, nil, d) end
|
||||
function dobj:setisoweeknumber(w,d) return self:setisoyear(nil, w, d) end
|
||||
|
||||
function dobj:setyear(y, m, d)
|
||||
local cy, cm, cd = breakdaynum(self.daynum)
|
||||
if y then cy = fix(tonumber(y))end
|
||||
if m then cm = getmontharg(m) end
|
||||
if d then cd = fix(tonumber(d))end
|
||||
if cy and cm and cd then
|
||||
self.daynum = makedaynum(cy, cm, cd)
|
||||
return self:normalize()
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
|
||||
function dobj:setmonth(m, d)return self:setyear(nil, m, d) end
|
||||
function dobj:setday(d) return self:setyear(nil, nil, d) end
|
||||
|
||||
function dobj:sethours(h, m, s, t)
|
||||
local ch,cm,cs,ck = breakdayfrc(self.dayfrc)
|
||||
ch, cm, cs, ck = tonumber(h or ch), tonumber(m or cm), tonumber(s or cs), tonumber(t or ck)
|
||||
if ch and cm and cs and ck then
|
||||
self.dayfrc = makedayfrc(ch, cm, cs, ck)
|
||||
return self:normalize()
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
|
||||
function dobj:setminutes(m,s,t) return self:sethours(nil, m, s, t) end
|
||||
function dobj:setseconds(s, t) return self:sethours(nil, nil, s, t) end
|
||||
function dobj:setticks(t) return self:sethours(nil, nil, nil, t) end
|
||||
|
||||
function dobj:spanticks() return (self.daynum*TICKSPERDAY + self.dayfrc) end
|
||||
function dobj:spanseconds() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERSEC end
|
||||
function dobj:spanminutes() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERMIN end
|
||||
function dobj:spanhours() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERHOUR end
|
||||
function dobj:spandays() return (self.daynum*TICKSPERDAY + self.dayfrc)/TICKSPERDAY end
|
||||
|
||||
function dobj:addyears(y, m, d)
|
||||
local cy, cm, cd = breakdaynum(self.daynum)
|
||||
if y then y = fix(tonumber(y))else y = 0 end
|
||||
if m then m = fix(tonumber(m))else m = 0 end
|
||||
if d then d = fix(tonumber(d))else d = 0 end
|
||||
if y and m and d then
|
||||
self.daynum = makedaynum(cy+y, cm+m, cd+d)
|
||||
return self:normalize()
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
|
||||
function dobj:addmonths(m, d)
|
||||
return self:addyears(nil, m, d)
|
||||
end
|
||||
|
||||
local function dobj_adddayfrc(self,n,pt,pd)
|
||||
n = tonumber(n)
|
||||
if n then
|
||||
local x = floor(n/pd);
|
||||
self.daynum = self.daynum + x;
|
||||
self.dayfrc = self.dayfrc + (n-x*pd)*pt;
|
||||
return self:normalize()
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
function dobj:adddays(n) return dobj_adddayfrc(self,n,TICKSPERDAY,1) end
|
||||
function dobj:addhours(n) return dobj_adddayfrc(self,n,TICKSPERHOUR,HOURPERDAY) end
|
||||
function dobj:addminutes(n) return dobj_adddayfrc(self,n,TICKSPERMIN,MINPERDAY) end
|
||||
function dobj:addseconds(n) return dobj_adddayfrc(self,n,TICKSPERSEC,SECPERDAY) end
|
||||
function dobj:addticks(n) return dobj_adddayfrc(self,n,1,TICKSPERDAY) end
|
||||
local tvspec = {
|
||||
-- Abbreviated weekday name (Sun)
|
||||
['%a']=function(self) return sl_weekdays[weekday(self.daynum) + 7] end,
|
||||
-- Full weekday name (Sunday)
|
||||
['%A']=function(self) return sl_weekdays[weekday(self.daynum)] end,
|
||||
-- Abbreviated month name (Dec)
|
||||
['%b']=function(self) return sl_months[self:getmonth() - 1 + 12] end,
|
||||
-- Full month name (December)
|
||||
['%B']=function(self) return sl_months[self:getmonth() - 1] end,
|
||||
-- Year/100 (19, 20, 30)
|
||||
['%C']=function(self) return fmt("%.2d", fix(self:getyear()/100)) end,
|
||||
-- The day of the month as a number (range 1 - 31)
|
||||
['%d']=function(self) return fmt("%.2d", self:getday()) end,
|
||||
-- year for ISO 8601 week, from 00 (79)
|
||||
['%g']=function(self) return fmt("%.2d", mod(self:getisoyear() ,100)) end,
|
||||
-- year for ISO 8601 week, from 0000 (1979)
|
||||
['%G']=function(self) return fmt("%.4d", self:getisoyear()) end,
|
||||
-- same as %b
|
||||
['%h']=function(self) return self:fmt0("%b") end,
|
||||
-- hour of the 24-hour day, from 00 (06)
|
||||
['%H']=function(self) return fmt("%.2d", self:gethours()) end,
|
||||
-- The hour as a number using a 12-hour clock (01 - 12)
|
||||
['%I']=function(self) return fmt("%.2d", self:getclockhour()) end,
|
||||
-- The day of the year as a number (001 - 366)
|
||||
['%j']=function(self) return fmt("%.3d", self:getyearday()) end,
|
||||
-- Month of the year, from 01 to 12
|
||||
['%m']=function(self) return fmt("%.2d", self:getmonth()) end,
|
||||
-- Minutes after the hour 55
|
||||
['%M']=function(self) return fmt("%.2d", self:getminutes())end,
|
||||
-- AM/PM indicator (AM)
|
||||
['%p']=function(self) return sl_meridian[self:gethours() > 11 and 1 or -1] end, --AM/PM indicator (AM)
|
||||
-- The second as a number (59, 20 , 01)
|
||||
['%S']=function(self) return fmt("%.2d", self:getseconds()) end,
|
||||
-- ISO 8601 day of the week, to 7 for Sunday (7, 1)
|
||||
['%u']=function(self) return self:getisoweekday() end,
|
||||
-- Sunday week of the year, from 00 (48)
|
||||
['%U']=function(self) return fmt("%.2d", self:getweeknumber()) end,
|
||||
-- ISO 8601 week of the year, from 01 (48)
|
||||
['%V']=function(self) return fmt("%.2d", self:getisoweeknumber()) end,
|
||||
-- The day of the week as a decimal, Sunday being 0
|
||||
['%w']=function(self) return self:getweekday() - 1 end,
|
||||
-- Monday week of the year, from 00 (48)
|
||||
['%W']=function(self) return fmt("%.2d", self:getweeknumber(2)) end,
|
||||
-- The year as a number without a century (range 00 to 99)
|
||||
['%y']=function(self) return fmt("%.2d", mod(self:getyear() ,100)) end,
|
||||
-- Year with century (2000, 1914, 0325, 0001)
|
||||
['%Y']=function(self) return fmt("%.4d", self:getyear()) end,
|
||||
-- Time zone offset, the date object is assumed local time (+1000, -0230)
|
||||
['%z']=function(self) local b = -self:getbias(); local x = abs(b); return fmt("%s%.4d", b < 0 and "-" or "+", fix(x/60)*100 + floor(mod(x,60))) end,
|
||||
-- Time zone name, the date object is assumed local time
|
||||
['%Z']=function(self) return self:gettzname() end,
|
||||
-- Misc --
|
||||
-- Year, if year is in BCE, prints the BCE Year representation, otherwise result is similar to "%Y" (1 BCE, 40 BCE)
|
||||
['%\b']=function(self) local x = self:getyear() return fmt("%.4d%s", x>0 and x or (-x+1), x>0 and "" or " BCE") end,
|
||||
-- Seconds including fraction (59.998, 01.123)
|
||||
['%\f']=function(self) local x = self:getfracsec() return fmt("%s%.9f",x >= 10 and "" or "0", x) end,
|
||||
-- percent character %
|
||||
['%%']=function(self) return "%" end,
|
||||
-- Group Spec --
|
||||
-- 12-hour time, from 01:00:00 AM (06:55:15 AM); same as "%I:%M:%S %p"
|
||||
['%r']=function(self) return self:fmt0("%I:%M:%S %p") end,
|
||||
-- hour:minute, from 01:00 (06:55); same as "%I:%M"
|
||||
['%R']=function(self) return self:fmt0("%I:%M") end,
|
||||
-- 24-hour time, from 00:00:00 (06:55:15); same as "%H:%M:%S"
|
||||
['%T']=function(self) return self:fmt0("%H:%M:%S") end,
|
||||
-- month/day/year from 01/01/00 (12/02/79); same as "%m/%d/%y"
|
||||
['%D']=function(self) return self:fmt0("%m/%d/%y") end,
|
||||
-- year-month-day (1979-12-02); same as "%Y-%m-%d"
|
||||
['%F']=function(self) return self:fmt0("%Y-%m-%d") end,
|
||||
-- The preferred date and time representation; same as "%x %X"
|
||||
['%c']=function(self) return self:fmt0("%x %X") end,
|
||||
-- The preferred date representation, same as "%a %b %d %\b"
|
||||
['%x']=function(self) return self:fmt0("%a %b %d %\b") end,
|
||||
-- The preferred time representation, same as "%H:%M:%\f"
|
||||
['%X']=function(self) return self:fmt0("%H:%M:%\f") end,
|
||||
-- GroupSpec --
|
||||
-- Iso format, same as "%Y-%m-%dT%T"
|
||||
['${iso}'] = function(self) return self:fmt0("%Y-%m-%dT%T") end,
|
||||
-- http format, same as "%a, %d %b %Y %T GMT"
|
||||
['${http}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end,
|
||||
-- ctime format, same as "%a %b %d %T GMT %Y"
|
||||
['${ctime}'] = function(self) return self:fmt0("%a %b %d %T GMT %Y") end,
|
||||
-- RFC850 format, same as "%A, %d-%b-%y %T GMT"
|
||||
['${rfc850}'] = function(self) return self:fmt0("%A, %d-%b-%y %T GMT") end,
|
||||
-- RFC1123 format, same as "%a, %d %b %Y %T GMT"
|
||||
['${rfc1123}'] = function(self) return self:fmt0("%a, %d %b %Y %T GMT") end,
|
||||
-- asctime format, same as "%a %b %d %T %Y"
|
||||
['${asctime}'] = function(self) return self:fmt0("%a %b %d %T %Y") end,
|
||||
}
|
||||
function dobj:fmt0(str) return (gsub(str, "%%[%a%%\b\f]", function(x) local f = tvspec[x];return (f and f(self)) or x end)) end
|
||||
function dobj:fmt(str)
|
||||
str = str or self.fmtstr or fmtstr
|
||||
return self:fmt0((gmatch(str, "${%w+}")) and (gsub(str, "${%w+}", function(x)local f=tvspec[x];return (f and f(self)) or x end)) or str)
|
||||
end
|
||||
|
||||
dobj.format = dobj.fmt
|
||||
|
||||
function dobj.__lt(a, b) if (a.daynum == b.daynum) then return (a.dayfrc < b.dayfrc) else return (a.daynum < b.daynum) end end
|
||||
function dobj.__le(a, b) if (a.daynum == b.daynum) then return (a.dayfrc <= b.dayfrc) else return (a.daynum <= b.daynum) end end
|
||||
function dobj.__eq(a, b)return (a.daynum == b.daynum) and (a.dayfrc == b.dayfrc) end
|
||||
function dobj.__sub(a,b)
|
||||
local d1, d2 = date_getdobj(a), date_getdobj(b)
|
||||
local d0 = d1 and d2 and date_new(d1.daynum - d2.daynum, d1.dayfrc - d2.dayfrc)
|
||||
return d0 and d0:normalize()
|
||||
end
|
||||
function dobj.__add(a,b)
|
||||
local d1, d2 = date_getdobj(a), date_getdobj(b)
|
||||
local d0 = d1 and d2 and date_new(d1.daynum + d2.daynum, d1.dayfrc + d2.dayfrc)
|
||||
return d0 and d0:normalize()
|
||||
end
|
||||
function dobj.__concat(a, b) return tostring(a) .. tostring(b) end
|
||||
function dobj:__tostring() return self:fmt() end
|
||||
|
||||
function dobj:copy() return date_new(self.daynum, self.dayfrc) end
|
||||
|
||||
--[[ THE LOCAL DATE OBJECT METHODS ]]--
|
||||
function dobj:tolocal()
|
||||
local dn,df = self.daynum, self.dayfrc
|
||||
local bias = getbiasutc2(self)
|
||||
if bias then
|
||||
-- utc = local + bias; local = utc - bias
|
||||
self.daynum = dn
|
||||
self.dayfrc = df - bias*TICKSPERSEC
|
||||
return self:normalize()
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function dobj:toutc()
|
||||
local dn,df = self.daynum, self.dayfrc
|
||||
local bias = getbiasloc2(dn, df)
|
||||
if bias then
|
||||
-- utc = local + bias;
|
||||
self.daynum = dn
|
||||
self.dayfrc = df + bias*TICKSPERSEC
|
||||
return self:normalize()
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function dobj:getbias() return (getbiasloc2(self.daynum, self.dayfrc))/SECPERMIN end
|
||||
|
||||
function dobj:gettzname()
|
||||
local _, tvu, _ = getbiasloc2(self.daynum, self.dayfrc)
|
||||
return tvu and osdate("%Z",tvu) or ""
|
||||
end
|
||||
|
||||
--#if not DATE_OBJECT_AFX then
|
||||
function date.time(h, r, s, t)
|
||||
h, r, s, t = tonumber(h or 0), tonumber(r or 0), tonumber(s or 0), tonumber(t or 0)
|
||||
if h and r and s and t then
|
||||
return date_new(DAYNUM_DEF, makedayfrc(h, r, s, t))
|
||||
else
|
||||
return date_error_arg()
|
||||
end
|
||||
end
|
||||
|
||||
function date:__call(...)
|
||||
local arg = pack(...)
|
||||
if arg.n > 1 then return (date_from(...))
|
||||
elseif arg.n == 0 then return (date_getdobj(false))
|
||||
else local o, r = date_getdobj(arg[1]); return r and o:copy() or o end
|
||||
end
|
||||
|
||||
date.diff = dobj.__sub
|
||||
|
||||
function date.isleapyear(v)
|
||||
local y = fix(v);
|
||||
if not y then
|
||||
y = date_getdobj(v)
|
||||
y = y and y:getyear()
|
||||
end
|
||||
return isleapyear(y+0)
|
||||
end
|
||||
|
||||
function date.epoch() return date_epoch:copy() end
|
||||
|
||||
function date.isodate(y,w,d) return date_new(makedaynum_isoywd(y + 0, w and (w+0) or 1, d and (d+0) or 1), 0) end
|
||||
|
||||
-- Internal functions
|
||||
function date.fmt(str) if str then fmtstr = str end; return fmtstr end
|
||||
function date.daynummin(n) DAYNUM_MIN = (n and n < DAYNUM_MAX) and n or DAYNUM_MIN return n and DAYNUM_MIN or date_new(DAYNUM_MIN, 0):normalize()end
|
||||
function date.daynummax(n) DAYNUM_MAX = (n and n > DAYNUM_MIN) and n or DAYNUM_MAX return n and DAYNUM_MAX or date_new(DAYNUM_MAX, 0):normalize()end
|
||||
function date.ticks(t) if t then setticks(t) end return TICKSPERSEC end
|
||||
--#end -- not DATE_OBJECT_AFX
|
||||
|
||||
local tm = osdate("!*t", 0);
|
||||
if tm then
|
||||
date_epoch = date_new(makedaynum(tm.year, tm.month - 1, tm.day), makedayfrc(tm.hour, tm.min, tm.sec, 0))
|
||||
-- the distance from our epoch to os epoch in daynum
|
||||
DATE_EPOCH = date_epoch and date_epoch:spandays()
|
||||
else -- error will be raise only if called!
|
||||
date_epoch = setmetatable({},{__index = function() error("failed to get the epoch date") end})
|
||||
end
|
||||
|
||||
function date.serialize(object)
|
||||
return {tostring(object.daynum), tostring(object.dayfrc)}
|
||||
end
|
||||
|
||||
function date.construct(object)
|
||||
return date_isdobj(object) or (object.daynum and date_new(object.daynum, object.dayfrc) or date_new(object[1], object[2]))
|
||||
end
|
||||
|
||||
--#if not DATE_OBJECT_AFX then
|
||||
return date
|
||||
--#else
|
||||
--$return date_from
|
||||
--#end
|
||||
193
gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua
vendored
Normal file
193
gamemodes/helix/gamemode/core/libs/thirdparty/sh_middleclass.lua
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local middleclass = {
|
||||
_VERSION = 'middleclass v4.1.1',
|
||||
_DESCRIPTION = 'Object Orientation for Lua',
|
||||
_URL = 'https://github.com/kikito/middleclass',
|
||||
_LICENSE = [[
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2011 Enrique García Cota
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
}
|
||||
|
||||
local function _createIndexWrapper(aClass, f)
|
||||
if f == nil then
|
||||
return aClass.__instanceDict
|
||||
else
|
||||
return function(self, name)
|
||||
local value = aClass.__instanceDict[name]
|
||||
|
||||
if value ~= nil then
|
||||
return value
|
||||
elseif type(f) == "function" then
|
||||
return (f(self, name))
|
||||
else
|
||||
return f[name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _propagateInstanceMethod(aClass, name, f)
|
||||
f = name == "__index" and _createIndexWrapper(aClass, f) or f
|
||||
aClass.__instanceDict[name] = f
|
||||
|
||||
for subclass in pairs(aClass.subclasses) do
|
||||
if rawget(subclass.__declaredMethods, name) == nil then
|
||||
_propagateInstanceMethod(subclass, name, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _declareInstanceMethod(aClass, name, f)
|
||||
aClass.__declaredMethods[name] = f
|
||||
|
||||
if f == nil and aClass.super then
|
||||
f = aClass.super.__instanceDict[name]
|
||||
end
|
||||
|
||||
_propagateInstanceMethod(aClass, name, f)
|
||||
end
|
||||
|
||||
local function _tostring(self) return "class " .. self.name end
|
||||
local function _call(self, ...) return self:New(...) end
|
||||
|
||||
local function _createClass(name, super)
|
||||
local dict = {}
|
||||
dict.__index = dict
|
||||
|
||||
local aClass = { name = name, super = super, static = {},
|
||||
__instanceDict = dict, __declaredMethods = {},
|
||||
subclasses = setmetatable({}, {__mode='k'}) }
|
||||
|
||||
if super then
|
||||
setmetatable(aClass.static, {
|
||||
__index = function(_,k)
|
||||
local result = rawget(dict,k)
|
||||
if result == nil then
|
||||
return super.static[k]
|
||||
end
|
||||
return result
|
||||
end
|
||||
})
|
||||
else
|
||||
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
|
||||
end
|
||||
|
||||
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
|
||||
__call = _call, __newindex = _declareInstanceMethod })
|
||||
|
||||
return aClass
|
||||
end
|
||||
|
||||
local function _includeMixin(aClass, mixin)
|
||||
assert(type(mixin) == 'table', "mixin must be a table")
|
||||
|
||||
for name,method in pairs(mixin) do
|
||||
if name ~= "Included" and name ~= "static" then aClass[name] = method end
|
||||
end
|
||||
|
||||
for name,method in pairs(mixin.static or {}) do
|
||||
aClass.static[name] = method
|
||||
end
|
||||
|
||||
if type(mixin.Included)=="function" then mixin:Included(aClass) end
|
||||
return aClass
|
||||
end
|
||||
|
||||
local DefaultMixin = {
|
||||
__tostring = function(self) return "instance of " .. tostring(self.class) end,
|
||||
|
||||
Initialize = function(self, ...) end,
|
||||
|
||||
IsInstanceOf = function(self, aClass)
|
||||
return type(aClass) == 'table'
|
||||
and type(self) == 'table'
|
||||
and (self.class == aClass
|
||||
or type(self.class) == 'table'
|
||||
and type(self.class.IsSubclassOf) == 'function'
|
||||
and self.class:IsSubclassOf(aClass))
|
||||
end,
|
||||
|
||||
static = {
|
||||
Allocate = function(self)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:Allocate' instead of 'Class.Allocate'")
|
||||
return setmetatable({ class = self }, self.__instanceDict)
|
||||
end,
|
||||
|
||||
New = function(self, ...)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:New' instead of 'Class.New'")
|
||||
local instance = self:Allocate()
|
||||
instance:Initialize(...)
|
||||
return instance
|
||||
end,
|
||||
|
||||
Subclass = function(self, name)
|
||||
assert(type(self) == 'table', "Make sure that you are using 'Class:Subclass' instead of 'Class.Subclass'")
|
||||
assert(type(name) == "string", "You must provide a name(string) for your class")
|
||||
|
||||
local subclass = _createClass(name, self)
|
||||
|
||||
for methodName, f in pairs(self.__instanceDict) do
|
||||
_propagateInstanceMethod(subclass, methodName, f)
|
||||
end
|
||||
subclass.Initialize = function(instance, ...) return self.Initialize(instance, ...) end
|
||||
|
||||
self.subclasses[subclass] = true
|
||||
self:Subclassed(subclass)
|
||||
|
||||
return subclass
|
||||
end,
|
||||
|
||||
Subclassed = function(self, other) end,
|
||||
|
||||
IsSubclassOf = function(self, other)
|
||||
return type(other) == 'table' and
|
||||
type(self.super) == 'table' and
|
||||
( self.super == other or self.super:IsSubclassOf(other) )
|
||||
end,
|
||||
|
||||
Include = function(self, ...)
|
||||
assert(type(self) == 'table', "Make sure you that you are using 'Class:Include' instead of 'Class.Include'")
|
||||
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
|
||||
return self
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
function middleclass.class(name, super)
|
||||
assert(type(name) == 'string', "A name (string) is needed for the new class")
|
||||
return super and super:Subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
|
||||
end
|
||||
|
||||
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
|
||||
|
||||
ix.middleclass = middleclass
|
||||
174
gamemodes/helix/gamemode/core/libs/thirdparty/sh_netstream2.lua
vendored
Normal file
174
gamemodes/helix/gamemode/core/libs/thirdparty/sh_netstream2.lua
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
NetStream - 2.0.0
|
||||
|
||||
Alexander Grist-Hucker
|
||||
http://www.revotech.org
|
||||
|
||||
Credits to:
|
||||
thelastpenguin for pON.
|
||||
https://github.com/thelastpenguin/gLUA-Library/tree/master/pON
|
||||
--]]
|
||||
|
||||
|
||||
AddCSLuaFile();
|
||||
|
||||
local _player = player
|
||||
|
||||
netstream = netstream or {};
|
||||
netstream.stored = netstream.stored or {};
|
||||
|
||||
-- A function to split data for a data stream.
|
||||
function netstream.Split(data)
|
||||
local index = 1;
|
||||
local result = {};
|
||||
local buffer = {};
|
||||
|
||||
for i = 0, string.len(data) do
|
||||
buffer[#buffer + 1] = string.sub(data, i, i);
|
||||
|
||||
if (#buffer == 32768) then
|
||||
result[#result + 1] = table.concat(buffer);
|
||||
index = index + 1;
|
||||
buffer = {};
|
||||
end;
|
||||
end;
|
||||
|
||||
result[#result + 1] = table.concat(buffer);
|
||||
|
||||
return result;
|
||||
end;
|
||||
|
||||
-- A function to hook a data stream.
|
||||
function netstream.Hook(name, Callback)
|
||||
netstream.stored[name] = Callback;
|
||||
end;
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("NetStreamDS");
|
||||
|
||||
-- A function to start a net stream.
|
||||
function netstream.Start(player, name, ...)
|
||||
local recipients = {};
|
||||
local bShouldSend = false;
|
||||
local bSendPVS = false;
|
||||
|
||||
if (type(player) != "table") then
|
||||
if (!player) then
|
||||
player = _player.GetAll();
|
||||
elseif (type(player) == "Vector") then
|
||||
bSendPVS = true;
|
||||
else
|
||||
player = {player};
|
||||
end;
|
||||
end;
|
||||
|
||||
if (type(player) != "Vector") then
|
||||
for k, v in pairs(player) do
|
||||
if (type(v) == "Player") then
|
||||
recipients[#recipients + 1] = v;
|
||||
|
||||
bShouldSend = true;
|
||||
elseif (type(k) == "Player") then
|
||||
recipients[#recipients + 1] = k;
|
||||
|
||||
bShouldSend = true;
|
||||
end;
|
||||
end;
|
||||
else
|
||||
bShouldSend = true;
|
||||
end;
|
||||
|
||||
local dataTable = {...};
|
||||
local encodedData = pon.encode(dataTable);
|
||||
|
||||
if (encodedData and #encodedData > 0 and bShouldSend) then
|
||||
net.Start("NetStreamDS");
|
||||
net.WriteString(name);
|
||||
net.WriteUInt(#encodedData, 32);
|
||||
net.WriteData(encodedData, #encodedData);
|
||||
if (bSendPVS) then
|
||||
net.SendPVS(player);
|
||||
else
|
||||
net.Send(recipients);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
net.Receive("NetStreamDS", function(length, player)
|
||||
local NS_DS_NAME = net.ReadString();
|
||||
local NS_DS_LENGTH = net.ReadUInt(32);
|
||||
local NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
|
||||
|
||||
if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
|
||||
player.nsDataStreamName = NS_DS_NAME;
|
||||
player.nsDataStreamData = "";
|
||||
|
||||
if (player.nsDataStreamName and player.nsDataStreamData) then
|
||||
player.nsDataStreamData = NS_DS_DATA;
|
||||
|
||||
if (netstream.stored[player.nsDataStreamName]) then
|
||||
local bStatus, value = pcall(pon.decode, player.nsDataStreamData);
|
||||
|
||||
if (bStatus) then
|
||||
netstream.stored[player.nsDataStreamName](player, unpack(value));
|
||||
else
|
||||
ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n");
|
||||
end;
|
||||
else
|
||||
ErrorNoHalt("NetStream: Undefined hook for '"..NS_DS_NAME.."'\n")
|
||||
end;
|
||||
|
||||
player.nsDataStreamName = nil;
|
||||
player.nsDataStreamData = nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
|
||||
end);
|
||||
else
|
||||
-- A function to start a net stream.
|
||||
function netstream.Start(name, ...)
|
||||
local dataTable = {...};
|
||||
local encodedData = pon.encode(dataTable);
|
||||
|
||||
if (encodedData and #encodedData > 0) then
|
||||
net.Start("NetStreamDS");
|
||||
net.WriteString(name);
|
||||
net.WriteUInt(#encodedData, 32);
|
||||
net.WriteData(encodedData, #encodedData);
|
||||
net.SendToServer();
|
||||
end;
|
||||
end;
|
||||
|
||||
net.Receive("NetStreamDS", function(length)
|
||||
local NS_DS_NAME = net.ReadString();
|
||||
local NS_DS_LENGTH = net.ReadUInt(32);
|
||||
local NS_DS_DATA = net.ReadData(NS_DS_LENGTH);
|
||||
|
||||
if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then
|
||||
if (netstream.stored[NS_DS_NAME]) then
|
||||
local bStatus, value = pcall(pon.decode, NS_DS_DATA);
|
||||
|
||||
if (bStatus) then
|
||||
netstream.stored[NS_DS_NAME](unpack(value));
|
||||
else
|
||||
ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n");
|
||||
end;
|
||||
else
|
||||
ErrorNoHalt("NetSteam: Undefined hook for '"..NS_DS_NAME.."'\n")
|
||||
end;
|
||||
end;
|
||||
|
||||
NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil;
|
||||
end);
|
||||
end;
|
||||
411
gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua
vendored
Normal file
411
gamemodes/helix/gamemode/core/libs/thirdparty/sh_pon.lua
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
DEVELOPMENTAL VERSION;
|
||||
VERSION 1.2.2
|
||||
Copyright thelastpenguin™
|
||||
You may use this for any purpose as long as:
|
||||
- You don't remove this copyright notice.
|
||||
- You don't claim this to be your own.
|
||||
- You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this.
|
||||
If you modify the code for any purpose, the above still applies to the modified code.
|
||||
The author is not held responsible for any damages incured from the use of pon, you use it at your own risk.
|
||||
DATA TYPES SUPPORTED:
|
||||
- tables - k,v - pointers
|
||||
- strings - k,v - pointers
|
||||
- numbers - k,v
|
||||
- booleans- k,v
|
||||
- Vectors - k,v
|
||||
- Angles - k,v
|
||||
- Entities- k,v
|
||||
- Players - k,v
|
||||
CHANGE LOG
|
||||
V 1.1.0
|
||||
- Added Vehicle, NPC, NextBot, Player, Weapon
|
||||
V 1.2.0
|
||||
- Added custom handling for k,v tables without any array component.
|
||||
V 1.2.1
|
||||
- fixed deserialization bug.
|
||||
THANKS TO...
|
||||
- VERCAS for the inspiration.
|
||||
]]
|
||||
|
||||
|
||||
local pon = {};
|
||||
_G.pon = pon;
|
||||
|
||||
local type, count = type, table.Count ;
|
||||
local tonumber = tonumber ;
|
||||
local format = string.format;
|
||||
do
|
||||
local type, count = type, table.Count ;
|
||||
local tonumber = tonumber ;
|
||||
local format = string.format;
|
||||
|
||||
local encode = {};
|
||||
|
||||
local tryCache ;
|
||||
|
||||
local cacheSize = 0;
|
||||
|
||||
encode['table'] = function( self, tbl, output, cache )
|
||||
|
||||
if( cache[ tbl ] )then
|
||||
output[ #output + 1 ] = format('(%x)', cache[tbl] );
|
||||
return ;
|
||||
else
|
||||
cacheSize = cacheSize + 1;
|
||||
cache[ tbl ] = cacheSize;
|
||||
end
|
||||
|
||||
|
||||
local first = next(tbl, nil)
|
||||
local predictedNumeric = 1
|
||||
local lastKey = nil
|
||||
-- starts with a numeric dealio
|
||||
if first == 1 then
|
||||
output[#output + 1] = '{'
|
||||
|
||||
for k,v in next, tbl do
|
||||
if k == predictedNumeric then
|
||||
predictedNumeric = predictedNumeric + 1
|
||||
|
||||
local tv = type(v)
|
||||
if tv == 'string' then
|
||||
local pid = cache[v]
|
||||
if pid then
|
||||
output[#output + 1] = format('(%x)', pid)
|
||||
else
|
||||
cacheSize = cacheSize + 1
|
||||
cache[v] = cacheSize
|
||||
self.string(self, v, output, cache)
|
||||
end
|
||||
else
|
||||
self[tv](self, v, output, cache)
|
||||
end
|
||||
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
predictedNumeric = predictedNumeric - 1
|
||||
else
|
||||
predictedNumeric = nil
|
||||
end
|
||||
|
||||
if predictedNumeric == nil then
|
||||
output[#output + 1] = '[' -- no array component
|
||||
else
|
||||
output[#output + 1] = '~' -- array component came first so shit needs to happen
|
||||
end
|
||||
|
||||
for k, v in next, tbl, predictedNumeric do
|
||||
local tk, tv = type(k), type(v)
|
||||
|
||||
-- WRITE KEY
|
||||
if tk == 'string' then
|
||||
local pid = cache[ k ];
|
||||
if( pid )then
|
||||
output[ #output + 1 ] = format('(%x)', pid );
|
||||
else
|
||||
cacheSize = cacheSize + 1;
|
||||
cache[ k ] = cacheSize;
|
||||
|
||||
self.string( self, k, output, cache );
|
||||
end
|
||||
else
|
||||
self[tk](self, k, output, cache)
|
||||
end
|
||||
|
||||
-- WRITE VALUE
|
||||
if( tv == 'string' )then
|
||||
local pid = cache[ v ];
|
||||
if( pid )then
|
||||
output[ #output + 1 ] = format('(%x)', pid );
|
||||
else
|
||||
cacheSize = cacheSize + 1;
|
||||
cache[ v ] = cacheSize;
|
||||
|
||||
self.string( self, v, output, cache );
|
||||
end
|
||||
else
|
||||
self[ tv ]( self, v, output, cache );
|
||||
end
|
||||
end
|
||||
|
||||
output[#output + 1] = '}'
|
||||
end
|
||||
-- ENCODE STRING
|
||||
local gsub = string.gsub ;
|
||||
encode['string'] = function( self, str, output )
|
||||
--if tryCache( str, output ) then return end
|
||||
local estr, count = gsub( str, ";", "\\;");
|
||||
if( count == 0 )then
|
||||
output[ #output + 1 ] = '\''..str..';';
|
||||
else
|
||||
output[ #output + 1 ] = '"'..estr..'";';
|
||||
end
|
||||
end
|
||||
-- ENCODE NUMBER
|
||||
encode['number'] = function( self, num, output )
|
||||
if num%1 == 0 then
|
||||
if num < 0 then
|
||||
output[ #output + 1 ] = format( 'x%x;', -num );
|
||||
else
|
||||
output[ #output + 1 ] = format('X%x;', num );
|
||||
end
|
||||
else
|
||||
output[ #output + 1 ] = tonumber( num )..';';
|
||||
end
|
||||
end
|
||||
-- ENCODE BOOLEAN
|
||||
encode['boolean'] = function( self, val, output )
|
||||
output[ #output + 1 ] = val and 't' or 'f'
|
||||
end
|
||||
-- ENCODE VECTOR
|
||||
encode['Vector'] = function( self, val, output )
|
||||
output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';');
|
||||
end
|
||||
-- ENCODE ANGLE
|
||||
encode['Angle'] = function( self, val, output )
|
||||
output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';');
|
||||
end
|
||||
encode['Entity'] = function( self, val, output )
|
||||
output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#');
|
||||
end
|
||||
encode['Player'] = encode['Entity'];
|
||||
encode['Vehicle'] = encode['Entity'];
|
||||
encode['Weapon'] = encode['Entity'];
|
||||
encode['NPC'] = encode['Entity'];
|
||||
encode['NextBot'] = encode['Entity'];
|
||||
encode['PhysObj'] = encode['Entity'];
|
||||
|
||||
encode['nil'] = function()
|
||||
output[ #output + 1 ] = '?';
|
||||
end
|
||||
encode.__index = function( key )
|
||||
ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.');
|
||||
return encode['nil'];
|
||||
end
|
||||
|
||||
do
|
||||
local empty, concat = table.Empty, table.concat ;
|
||||
function pon.encode( tbl )
|
||||
local output = {};
|
||||
cacheSize = 0;
|
||||
encode[ 'table' ]( encode, tbl, output, {} );
|
||||
local res = concat( output );
|
||||
|
||||
return res;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local tonumber = tonumber ;
|
||||
local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ;
|
||||
local Vector, Angle, Entity = Vector, Angle, Entity ;
|
||||
|
||||
local decode = {};
|
||||
decode['{'] = function( self, index, str, cache )
|
||||
|
||||
local cur = {};
|
||||
cache[ #cache + 1 ] = cur;
|
||||
|
||||
local k, v, tk, tv = 1, nil, nil, nil;
|
||||
while( true )do
|
||||
tv = sub( str, index, index );
|
||||
if( not tv or tv == '~' )then
|
||||
index = index + 1;
|
||||
break ;
|
||||
end
|
||||
if( tv == '}' )then
|
||||
return index + 1, cur;
|
||||
end
|
||||
|
||||
-- READ THE VALUE
|
||||
index = index + 1;
|
||||
index, v = self[ tv ]( self, index, str, cache );
|
||||
cur[ k ] = v;
|
||||
|
||||
k = k + 1;
|
||||
end
|
||||
|
||||
while( true )do
|
||||
tk = sub( str, index, index );
|
||||
if( not tk or tk == '}' )then
|
||||
index = index + 1;
|
||||
break ;
|
||||
end
|
||||
|
||||
-- READ THE KEY
|
||||
|
||||
index = index + 1;
|
||||
index, k = self[ tk ]( self, index, str, cache );
|
||||
|
||||
-- READ THE VALUE
|
||||
tv = sub( str, index, index );
|
||||
index = index + 1;
|
||||
index, v = self[ tv ]( self, index, str, cache );
|
||||
|
||||
cur[ k ] = v;
|
||||
end
|
||||
|
||||
return index, cur;
|
||||
end
|
||||
decode['['] = function( self, index, str, cache )
|
||||
|
||||
local cur = {};
|
||||
cache[ #cache + 1 ] = cur;
|
||||
|
||||
local k, v, tk, tv = 1, nil, nil, nil;
|
||||
while( true )do
|
||||
tk = sub( str, index, index );
|
||||
if( not tk or tk == '}' )then
|
||||
index = index + 1;
|
||||
break ;
|
||||
end
|
||||
|
||||
-- READ THE KEY
|
||||
index = index + 1;
|
||||
index, k = self[ tk ]( self, index, str, cache );
|
||||
if not k then continue end
|
||||
|
||||
-- READ THE VALUE
|
||||
tv = sub( str, index, index );
|
||||
index = index + 1;
|
||||
if not self[tv] then
|
||||
print('did not find type: '..tv)
|
||||
end
|
||||
index, v = self[ tv ]( self, index, str, cache );
|
||||
|
||||
cur[ k ] = v;
|
||||
end
|
||||
|
||||
return index, cur;
|
||||
end
|
||||
|
||||
-- STRING
|
||||
decode['"'] = function( self, index, str, cache )
|
||||
local finish = find( str, '";', index, true );
|
||||
local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' );
|
||||
index = finish + 2;
|
||||
|
||||
cache[ #cache + 1 ] = res;
|
||||
return index, res;
|
||||
end
|
||||
-- STRING NO ESCAPING NEEDED
|
||||
decode['\''] = function( self, index, str, cache )
|
||||
local finish = find( str, ';', index, true );
|
||||
local res = sub( str, index, finish - 1 )
|
||||
index = finish + 1;
|
||||
|
||||
cache[ #cache + 1 ] = res;
|
||||
return index, res;
|
||||
end
|
||||
|
||||
-- NUMBER
|
||||
decode['n'] = function( self, index, str, cache )
|
||||
index = index - 1;
|
||||
local finish = find( str, ';', index, true );
|
||||
local num = tonumber( sub( str, index, finish - 1 ) );
|
||||
index = finish + 1;
|
||||
return index, num;
|
||||
end
|
||||
decode['0'] = decode['n'];
|
||||
decode['1'] = decode['n'];
|
||||
decode['2'] = decode['n'];
|
||||
decode['3'] = decode['n'];
|
||||
decode['4'] = decode['n'];
|
||||
decode['5'] = decode['n'];
|
||||
decode['6'] = decode['n'];
|
||||
decode['7'] = decode['n'];
|
||||
decode['8'] = decode['n'];
|
||||
decode['9'] = decode['n'];
|
||||
decode['-'] = decode['n'];
|
||||
-- positive hex
|
||||
decode['X'] = function( self, index, str, cache )
|
||||
local finish = find( str, ';', index, true );
|
||||
local num = tonumber( sub( str, index, finish - 1), 16 );
|
||||
index = finish + 1;
|
||||
return index, num;
|
||||
end
|
||||
-- negative hex
|
||||
decode['x'] = function( self, index, str, cache )
|
||||
local finish = find( str, ';', index, true );
|
||||
local num = -tonumber( sub( str, index, finish - 1), 16 );
|
||||
index = finish + 1;
|
||||
return index, num;
|
||||
end
|
||||
|
||||
-- POINTER
|
||||
decode['('] = function( self, index, str, cache )
|
||||
local finish = find( str, ')', index, true );
|
||||
local num = tonumber( sub( str, index, finish - 1), 16 );
|
||||
index = finish + 1;
|
||||
return index, cache[ num ];
|
||||
end
|
||||
|
||||
-- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO.
|
||||
decode[ 't' ] = function( self, index )
|
||||
return index, true;
|
||||
end
|
||||
decode[ 'f' ] = function( self, index )
|
||||
return index, false;
|
||||
end
|
||||
|
||||
-- VECTOR
|
||||
decode[ 'v' ] = function( self, index, str, cache )
|
||||
local finish = find( str, ';', index, true );
|
||||
local vecStr = sub( str, index, finish - 1 );
|
||||
index = finish + 1; -- update the index.
|
||||
local segs = Explode( ',', vecStr, false );
|
||||
return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
|
||||
end
|
||||
-- ANGLE
|
||||
decode[ 'a' ] = function( self, index, str, cache )
|
||||
local finish = find( str, ';', index, true );
|
||||
local angStr = sub( str, index, finish - 1 );
|
||||
index = finish + 1; -- update the index.
|
||||
local segs = Explode( ',', angStr, false );
|
||||
return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) );
|
||||
end
|
||||
-- ENTITY
|
||||
decode[ 'E' ] = function( self, index, str, cache )
|
||||
if( str[index] == '#' )then
|
||||
index = index + 1;
|
||||
return index, NULL ;
|
||||
else
|
||||
local finish = find( str, ';', index, true );
|
||||
local num = tonumber( sub( str, index, finish - 1 ) );
|
||||
index = finish + 1;
|
||||
return index, Entity( num );
|
||||
end
|
||||
end
|
||||
-- PLAYER
|
||||
decode[ 'P' ] = function( self, index, str, cache )
|
||||
local finish = find( str, ';', index, true );
|
||||
local num = tonumber( sub( str, index, finish - 1 ) );
|
||||
index = finish + 1;
|
||||
return index, Entity( num ) or NULL;
|
||||
end
|
||||
-- NIL
|
||||
decode['?'] = function( self, index, str, cache )
|
||||
return index + 1, nil;
|
||||
end
|
||||
|
||||
|
||||
function pon.decode( data )
|
||||
local _, res = decode[sub(data,1,1)]( decode, 2, data, {});
|
||||
return res;
|
||||
end
|
||||
end
|
||||
385
gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua
vendored
Normal file
385
gamemodes/helix/gamemode/core/libs/thirdparty/sh_tween.lua
vendored
Normal file
@@ -0,0 +1,385 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
local tween = {
|
||||
_VERSION = 'tween 2.1.1',
|
||||
_DESCRIPTION = 'tweening for lua',
|
||||
_URL = 'https://github.com/kikito/tween.lua',
|
||||
_LICENSE = [[
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
}
|
||||
|
||||
-- easing
|
||||
|
||||
-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
|
||||
-- For all easing functions:
|
||||
-- t = time == how much time has to pass for the tweening to complete
|
||||
-- b = begin == starting property value
|
||||
-- c = change == ending - beginning
|
||||
-- d = duration == running time. How much time has passed *right now*
|
||||
|
||||
local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
|
||||
|
||||
-- linear
|
||||
local function linear(t, b, c, d) return c * t / d + b end
|
||||
|
||||
-- quad
|
||||
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
|
||||
local function outQuad(t, b, c, d)
|
||||
t = t / d
|
||||
return -c * t * (t - 2) + b
|
||||
end
|
||||
local function inOutQuad(t, b, c, d)
|
||||
t = t / d * 2
|
||||
if t < 1 then return c / 2 * pow(t, 2) + b end
|
||||
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
|
||||
end
|
||||
local function outInQuad(t, b, c, d)
|
||||
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
|
||||
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
-- cubic
|
||||
local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end
|
||||
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
|
||||
local function inOutCubic(t, b, c, d)
|
||||
t = t / d * 2
|
||||
if t < 1 then return c / 2 * t * t * t + b end
|
||||
t = t - 2
|
||||
return c / 2 * (t * t * t + 2) + b
|
||||
end
|
||||
local function outInCubic(t, b, c, d)
|
||||
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
|
||||
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
-- quart
|
||||
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
|
||||
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
|
||||
local function inOutQuart(t, b, c, d)
|
||||
t = t / d * 2
|
||||
if t < 1 then return c / 2 * pow(t, 4) + b end
|
||||
return -c / 2 * (pow(t - 2, 4) - 2) + b
|
||||
end
|
||||
local function outInQuart(t, b, c, d)
|
||||
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
|
||||
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
-- quint
|
||||
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
|
||||
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
|
||||
local function inOutQuint(t, b, c, d)
|
||||
t = t / d * 2
|
||||
if t < 1 then return c / 2 * pow(t, 5) + b end
|
||||
return c / 2 * (pow(t - 2, 5) + 2) + b
|
||||
end
|
||||
local function outInQuint(t, b, c, d)
|
||||
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
|
||||
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
-- sine
|
||||
local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
|
||||
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
|
||||
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
|
||||
local function outInSine(t, b, c, d)
|
||||
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
|
||||
return inSine((t * 2) -d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
-- expo
|
||||
local function inExpo(t, b, c, d)
|
||||
if t == 0 then return b end
|
||||
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
|
||||
end
|
||||
local function outExpo(t, b, c, d)
|
||||
if t == d then return b + c end
|
||||
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
|
||||
end
|
||||
local function inOutExpo(t, b, c, d)
|
||||
if t == 0 then return b end
|
||||
if t == d then return b + c end
|
||||
t = t / d * 2
|
||||
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
|
||||
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
|
||||
end
|
||||
local function outInExpo(t, b, c, d)
|
||||
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
|
||||
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
-- circ
|
||||
local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
|
||||
local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end
|
||||
local function inOutCirc(t, b, c, d)
|
||||
t = t / d * 2
|
||||
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
|
||||
t = t - 2
|
||||
return c / 2 * (sqrt(1 - t * t) + 1) + b
|
||||
end
|
||||
local function outInCirc(t, b, c, d)
|
||||
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
|
||||
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
-- elastic
|
||||
local function calculatePAS(p,a,c,d)
|
||||
p, a = p or d * 0.3, a or 0
|
||||
if a < abs(c) then return p, c, p / 4 end -- p, a, s
|
||||
return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
|
||||
end
|
||||
local function inElastic(t, b, c, d, a, p)
|
||||
local s
|
||||
if t == 0 then return b end
|
||||
t = t / d
|
||||
if t == 1 then return b + c end
|
||||
p,a,s = calculatePAS(p,a,c,d)
|
||||
t = t - 1
|
||||
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
|
||||
end
|
||||
local function outElastic(t, b, c, d, a, p)
|
||||
local s
|
||||
if t == 0 then return b end
|
||||
t = t / d
|
||||
if t == 1 then return b + c end
|
||||
p,a,s = calculatePAS(p,a,c,d)
|
||||
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
|
||||
end
|
||||
local function inOutElastic(t, b, c, d, a, p)
|
||||
local s
|
||||
if t == 0 then return b end
|
||||
t = t / d * 2
|
||||
if t == 2 then return b + c end
|
||||
p,a,s = calculatePAS(p,a,c,d)
|
||||
t = t - 1
|
||||
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
|
||||
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
|
||||
end
|
||||
local function outInElastic(t, b, c, d, a, p)
|
||||
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
|
||||
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
|
||||
end
|
||||
|
||||
-- back
|
||||
local function inBack(t, b, c, d, s)
|
||||
s = s or 1.70158
|
||||
t = t / d
|
||||
return c * t * t * ((s + 1) * t - s) + b
|
||||
end
|
||||
local function outBack(t, b, c, d, s)
|
||||
s = s or 1.70158
|
||||
t = t / d - 1
|
||||
return c * (t * t * ((s + 1) * t + s) + 1) + b
|
||||
end
|
||||
local function inOutBack(t, b, c, d, s)
|
||||
s = (s or 1.70158) * 1.525
|
||||
t = t / d * 2
|
||||
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
|
||||
t = t - 2
|
||||
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
|
||||
end
|
||||
local function outInBack(t, b, c, d, s)
|
||||
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
|
||||
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
|
||||
end
|
||||
|
||||
-- bounce
|
||||
local function outBounce(t, b, c, d)
|
||||
t = t / d
|
||||
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
|
||||
if t < 2 / 2.75 then
|
||||
t = t - (1.5 / 2.75)
|
||||
return c * (7.5625 * t * t + 0.75) + b
|
||||
elseif t < 2.5 / 2.75 then
|
||||
t = t - (2.25 / 2.75)
|
||||
return c * (7.5625 * t * t + 0.9375) + b
|
||||
end
|
||||
t = t - (2.625 / 2.75)
|
||||
return c * (7.5625 * t * t + 0.984375) + b
|
||||
end
|
||||
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
|
||||
local function inOutBounce(t, b, c, d)
|
||||
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
|
||||
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
|
||||
end
|
||||
local function outInBounce(t, b, c, d)
|
||||
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
|
||||
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
|
||||
end
|
||||
|
||||
tween.easing = {
|
||||
linear = linear,
|
||||
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
|
||||
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
|
||||
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
|
||||
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
|
||||
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
|
||||
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
|
||||
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
|
||||
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
|
||||
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
|
||||
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- private stuff
|
||||
|
||||
local function copyTables(destination, keysTable, valuesTable)
|
||||
valuesTable = valuesTable or keysTable
|
||||
local mt = getmetatable(keysTable)
|
||||
if mt and getmetatable(destination) == nil then
|
||||
setmetatable(destination, mt)
|
||||
end
|
||||
for k,v in pairs(keysTable) do
|
||||
if type(v) == 'table' then
|
||||
destination[k] = copyTables({}, v, valuesTable[k])
|
||||
else
|
||||
destination[k] = valuesTable[k]
|
||||
end
|
||||
end
|
||||
return destination
|
||||
end
|
||||
|
||||
local function checkSubjectAndTargetRecursively(subject, target, path)
|
||||
path = path or {}
|
||||
local newPath
|
||||
for k,targetValue in pairs(target) do
|
||||
newPath = copyTables({}, path)
|
||||
table.insert(newPath, tostring(k))
|
||||
if isnumber(targetValue) then
|
||||
assert(isnumber(subject[k]), "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
|
||||
elseif istable(targetValue) then
|
||||
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
|
||||
else
|
||||
assert(isnumber(targetValue), "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function checkNewParams(duration, subject, target, easing)
|
||||
assert(isnumber(duration) and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
|
||||
assert(istable(target), "target must be a table. Was " .. tostring(target))
|
||||
assert(isfunction(easing), "easing must be a function. Was " .. tostring(easing))
|
||||
checkSubjectAndTargetRecursively(subject, target)
|
||||
end
|
||||
|
||||
local function getEasingFunction(easing)
|
||||
easing = easing or "linear"
|
||||
if isstring(easing) then
|
||||
local name = easing
|
||||
easing = tween.easing[name]
|
||||
if not isfunction(easing) then
|
||||
error("The easing function name '" .. name .. "' is invalid")
|
||||
end
|
||||
end
|
||||
return easing
|
||||
end
|
||||
|
||||
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
|
||||
local t,b,c,d
|
||||
for k,v in pairs(target) do
|
||||
if istable(v) then
|
||||
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
|
||||
else
|
||||
t,b,c,d = clock, initial[k], v - initial[k], duration
|
||||
subject[k] = easing(t,b,c,d)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function applyValues(subject, target)
|
||||
for k, v in pairs(target) do
|
||||
if (istable(v)) then
|
||||
applyValues(subject[k], v)
|
||||
else
|
||||
subject[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Tween methods
|
||||
|
||||
local Tween = {}
|
||||
local Tween_mt = {__index = Tween}
|
||||
|
||||
function Tween:set(clock)
|
||||
assert(isnumber(clock), "clock must be a positive number or 0")
|
||||
|
||||
self.initial = self.initial or copyTables({}, self.target, self.subject)
|
||||
self.clock = clock
|
||||
|
||||
if self.clock <= 0 then
|
||||
|
||||
self.clock = 0
|
||||
applyValues(self.subject, self.initial)
|
||||
|
||||
elseif self.clock >= self.duration then -- the tween has expired
|
||||
|
||||
self.clock = self.duration
|
||||
applyValues(self.subject, self.target)
|
||||
|
||||
else
|
||||
|
||||
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
|
||||
|
||||
end
|
||||
|
||||
return self.clock >= self.duration
|
||||
end
|
||||
|
||||
function Tween:reset()
|
||||
return self:set(0)
|
||||
end
|
||||
|
||||
function Tween:update(dt)
|
||||
assert(isnumber(dt), "dt must be a number")
|
||||
return self:set(self.clock + dt)
|
||||
end
|
||||
|
||||
|
||||
-- Public interface
|
||||
|
||||
function tween.new(duration, subject, target, easing)
|
||||
easing = getEasingFunction(easing)
|
||||
checkNewParams(duration, subject, target, easing)
|
||||
return setmetatable({
|
||||
duration = duration,
|
||||
subject = subject,
|
||||
target = target,
|
||||
easing = easing,
|
||||
clock = 0
|
||||
}, Tween_mt)
|
||||
end
|
||||
|
||||
ix.tween = tween
|
||||
338
gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua
vendored
Normal file
338
gamemodes/helix/gamemode/core/libs/thirdparty/sh_utf8.lua
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
-- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $
|
||||
--
|
||||
-- Provides UTF-8 aware string functions implemented in pure lua:
|
||||
-- * string.utf8len(s)
|
||||
-- * string.utf8sub(s, i, j)
|
||||
-- * string.utf8reverse(s)
|
||||
--
|
||||
-- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these
|
||||
-- additional functions are available:
|
||||
-- * string.utf8upper(s)
|
||||
-- * string.utf8lower(s)
|
||||
--
|
||||
-- All functions behave as their non UTF-8 aware counterparts with the exception
|
||||
-- that UTF-8 characters are used instead of bytes for all units.
|
||||
|
||||
--[[
|
||||
Copyright (c) 2006-2007, Kyle Smith
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
--]]
|
||||
|
||||
-- ABNF from RFC 3629
|
||||
--
|
||||
-- UTF8-octets = *( UTF8-char )
|
||||
-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
|
||||
-- UTF8-1 = %x00-7F
|
||||
-- UTF8-2 = %xC2-DF UTF8-tail
|
||||
-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
|
||||
-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
|
||||
-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
|
||||
-- %xF4 %x80-8F 2( UTF8-tail )
|
||||
-- UTF8-tail = %x80-BF
|
||||
--
|
||||
|
||||
ix.util.Include("data/sh_utf8_casemap.lua")
|
||||
|
||||
-- returns the number of bytes used by the UTF-8 character at byte i in s
|
||||
-- also doubles as a UTF-8 character validator
|
||||
local function utf8charbytes (s, i)
|
||||
-- argument defaults
|
||||
i = i or 1
|
||||
|
||||
-- argument checking
|
||||
if not isstring(s) then
|
||||
error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")")
|
||||
end
|
||||
if not isnumber(i) then
|
||||
error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")")
|
||||
end
|
||||
|
||||
local c = s:byte(i)
|
||||
|
||||
-- determine bytes needed for character, based on RFC 3629
|
||||
-- validate byte 1
|
||||
if c > 0 and c <= 127 then
|
||||
-- UTF8-1
|
||||
return 1
|
||||
|
||||
elseif c >= 194 and c <= 223 then
|
||||
-- UTF8-2
|
||||
local c2 = s:byte(i + 1)
|
||||
|
||||
if not c2 then
|
||||
error("UTF-8 string terminated early")
|
||||
end
|
||||
|
||||
-- validate byte 2
|
||||
if c2 < 128 or c2 > 191 then
|
||||
error("Invalid UTF-8 character")
|
||||
end
|
||||
|
||||
return 2
|
||||
|
||||
elseif c >= 224 and c <= 239 then
|
||||
-- UTF8-3
|
||||
local c2 = s:byte(i + 1)
|
||||
local c3 = s:byte(i + 2)
|
||||
|
||||
if not c2 or not c3 then
|
||||
error("UTF-8 string terminated early")
|
||||
end
|
||||
|
||||
-- validate byte 2
|
||||
if c == 224 and (c2 < 160 or c2 > 191) then
|
||||
error("Invalid UTF-8 character")
|
||||
elseif c == 237 and (c2 < 128 or c2 > 159) then
|
||||
error("Invalid UTF-8 character")
|
||||
elseif c2 < 128 or c2 > 191 then
|
||||
error("Invalid UTF-8 character")
|
||||
end
|
||||
|
||||
-- validate byte 3
|
||||
if c3 < 128 or c3 > 191 then
|
||||
error("Invalid UTF-8 character")
|
||||
end
|
||||
|
||||
return 3
|
||||
|
||||
elseif c >= 240 and c <= 244 then
|
||||
-- UTF8-4
|
||||
local c2 = s:byte(i + 1)
|
||||
local c3 = s:byte(i + 2)
|
||||
local c4 = s:byte(i + 3)
|
||||
|
||||
if not c2 or not c3 or not c4 then
|
||||
error("UTF-8 string terminated early")
|
||||
end
|
||||
|
||||
-- validate byte 2
|
||||
if c == 240 and (c2 < 144 or c2 > 191) then
|
||||
error("Invalid UTF-8 character")
|
||||
elseif c == 244 and (c2 < 128 or c2 > 143) then
|
||||
error("Invalid UTF-8 character")
|
||||
elseif c2 < 128 or c2 > 191 then
|
||||
error("Invalid UTF-8 character")
|
||||
end
|
||||
|
||||
-- validate byte 3
|
||||
if c3 < 128 or c3 > 191 then
|
||||
error("Invalid UTF-8 character")
|
||||
end
|
||||
|
||||
-- validate byte 4
|
||||
if c4 < 128 or c4 > 191 then
|
||||
error("Invalid UTF-8 character")
|
||||
end
|
||||
|
||||
return 4
|
||||
|
||||
else
|
||||
error("Invalid UTF-8 character")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- returns the number of characters in a UTF-8 string
|
||||
local function utf8len (s)
|
||||
-- argument checking
|
||||
if not isstring(s) then
|
||||
error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")")
|
||||
end
|
||||
|
||||
local pos = 1
|
||||
local bytes = s:len()
|
||||
local len = 0
|
||||
|
||||
while pos <= bytes do
|
||||
len = len + 1
|
||||
pos = pos + utf8charbytes(s, pos)
|
||||
end
|
||||
|
||||
return len
|
||||
end
|
||||
|
||||
-- install in the string library
|
||||
if not string.utf8bytes then
|
||||
string.utf8bytes = utf8charbytes
|
||||
end
|
||||
|
||||
-- install in the string library
|
||||
if not string.utf8len then
|
||||
string.utf8len = utf8len
|
||||
end
|
||||
|
||||
|
||||
-- functions identically to string.sub except that i and j are UTF-8 characters
|
||||
-- instead of bytes
|
||||
local function utf8sub (s, i, j)
|
||||
-- argument defaults
|
||||
j = j or -1
|
||||
|
||||
-- argument checking
|
||||
if not isstring(s) then
|
||||
error("bad argument #1 to 'utf8sub' (string expected, got ".. type(s).. ")")
|
||||
end
|
||||
if not isnumber(i) then
|
||||
error("bad argument #2 to 'utf8sub' (number expected, got ".. type(i).. ")")
|
||||
end
|
||||
if not isnumber(j) then
|
||||
error("bad argument #3 to 'utf8sub' (number expected, got ".. type(j).. ")")
|
||||
end
|
||||
|
||||
local pos = 1
|
||||
local bytes = s:len()
|
||||
local len = 0
|
||||
|
||||
-- only set l if i or j is negative
|
||||
local l = (i >= 0 and j >= 0) or s:utf8len()
|
||||
local startChar = (i >= 0) and i or l + i + 1
|
||||
local endChar = (j >= 0) and j or l + j + 1
|
||||
|
||||
-- can't have start before end!
|
||||
if startChar > endChar then
|
||||
return ""
|
||||
end
|
||||
|
||||
-- byte offsets to pass to string.sub
|
||||
local startByte, endByte = 1, bytes
|
||||
|
||||
while pos <= bytes do
|
||||
len = len + 1
|
||||
|
||||
if len == startChar then
|
||||
startByte = pos
|
||||
end
|
||||
|
||||
pos = pos + utf8charbytes(s, pos)
|
||||
|
||||
if len == endChar then
|
||||
endByte = pos - 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return s:sub(startByte, endByte)
|
||||
end
|
||||
|
||||
-- install in the string library
|
||||
if not string.utf8sub then
|
||||
string.utf8sub = utf8sub
|
||||
end
|
||||
|
||||
|
||||
-- replace UTF-8 characters based on a mapping table
|
||||
local function utf8replace (s, mapping)
|
||||
-- argument checking
|
||||
if not isstring(s) then
|
||||
error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")")
|
||||
end
|
||||
if not istable(mapping) then
|
||||
error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")")
|
||||
end
|
||||
|
||||
local pos = 1
|
||||
local bytes = s:len()
|
||||
local charbytes
|
||||
local newstr = ""
|
||||
|
||||
while pos <= bytes do
|
||||
charbytes = utf8charbytes(s, pos)
|
||||
local c = s:sub(pos, pos + charbytes - 1)
|
||||
|
||||
newstr = newstr .. (mapping[c] or c)
|
||||
|
||||
pos = pos + charbytes
|
||||
end
|
||||
|
||||
return newstr
|
||||
end
|
||||
|
||||
|
||||
-- identical to string.upper except it knows about unicode simple case conversions
|
||||
local function utf8upper (s)
|
||||
return utf8replace(s, utf8_lc_uc)
|
||||
end
|
||||
|
||||
-- install in the string library
|
||||
if not string.utf8upper and utf8_lc_uc then
|
||||
string.utf8upper = utf8upper
|
||||
end
|
||||
|
||||
|
||||
-- identical to string.lower except it knows about unicode simple case conversions
|
||||
local function utf8lower (s)
|
||||
return utf8replace(s, utf8_uc_lc)
|
||||
end
|
||||
|
||||
-- install in the string library
|
||||
if not string.utf8lower and utf8_uc_lc then
|
||||
string.utf8lower = utf8lower
|
||||
end
|
||||
|
||||
|
||||
-- identical to string.reverse except that it supports UTF-8
|
||||
local function utf8reverse (s)
|
||||
-- argument checking
|
||||
if not isstring(s) then
|
||||
error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")")
|
||||
end
|
||||
|
||||
local bytes = s:len()
|
||||
local pos = bytes
|
||||
local charbytes
|
||||
local newstr = ""
|
||||
|
||||
while pos > 0 do
|
||||
c = s:byte(pos)
|
||||
while c >= 128 and c <= 191 do
|
||||
pos = pos - 1
|
||||
c = s:byte(pos)
|
||||
end
|
||||
|
||||
charbytes = utf8charbytes(s, pos)
|
||||
|
||||
newstr = newstr .. s:sub(pos, pos + charbytes - 1)
|
||||
|
||||
pos = pos - 1
|
||||
end
|
||||
|
||||
return newstr
|
||||
end
|
||||
|
||||
-- install in the string library
|
||||
if not string.utf8reverse then
|
||||
string.utf8reverse = utf8reverse
|
||||
end
|
||||
615
gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua
vendored
Normal file
615
gamemodes/helix/gamemode/core/libs/thirdparty/sh_yaml.lua
vendored
Normal file
@@ -0,0 +1,615 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2017 Dominic Letz dominicletz@exosite.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
--]]
|
||||
|
||||
local table_print_value
|
||||
table_print_value = function(value, indent, done)
|
||||
indent = indent or 0
|
||||
done = done or {}
|
||||
if istable(value) and not done [value] then
|
||||
done [value] = true
|
||||
|
||||
local list = {}
|
||||
for key in pairs (value) do
|
||||
list[#list + 1] = key
|
||||
end
|
||||
table.sort(list, function(a, b) return tostring(a) < tostring(b) end)
|
||||
local last = list[#list]
|
||||
|
||||
local rep = "{\n"
|
||||
local comma
|
||||
for _, key in ipairs (list) do
|
||||
if key == last then
|
||||
comma = ''
|
||||
else
|
||||
comma = ','
|
||||
end
|
||||
local keyRep
|
||||
if isnumber(key) then
|
||||
keyRep = key
|
||||
else
|
||||
keyRep = string.format("%q", tostring(key))
|
||||
end
|
||||
rep = rep .. string.format(
|
||||
"%s[%s] = %s%s\n",
|
||||
string.rep(" ", indent + 2),
|
||||
keyRep,
|
||||
table_print_value(value[key], indent + 2, done),
|
||||
comma
|
||||
)
|
||||
end
|
||||
|
||||
rep = rep .. string.rep(" ", indent) -- indent it
|
||||
rep = rep .. "}"
|
||||
|
||||
done[value] = false
|
||||
return rep
|
||||
elseif isstring(value) then
|
||||
return string.format("%q", value)
|
||||
else
|
||||
return tostring(value)
|
||||
end
|
||||
end
|
||||
|
||||
local table_print = function(tt)
|
||||
print('return '..table_print_value(tt))
|
||||
end
|
||||
|
||||
local table_clone = function(t)
|
||||
local clone = {}
|
||||
for k,v in pairs(t) do
|
||||
clone[k] = v
|
||||
end
|
||||
return clone
|
||||
end
|
||||
|
||||
local string_trim = function(s, what)
|
||||
what = what or " "
|
||||
return s:gsub("^[" .. what .. "]*(.-)["..what.."]*$", "%1")
|
||||
end
|
||||
|
||||
local push = function(stack, item)
|
||||
stack[#stack + 1] = item
|
||||
end
|
||||
|
||||
local pop = function(stack)
|
||||
local item = stack[#stack]
|
||||
stack[#stack] = nil
|
||||
return item
|
||||
end
|
||||
|
||||
local context = function (str)
|
||||
if not isstring(str) then
|
||||
return ""
|
||||
end
|
||||
|
||||
str = str:sub(0,25):gsub("\n","\\n"):gsub("\"","\\\"");
|
||||
return ", near \"" .. str .. "\""
|
||||
end
|
||||
|
||||
local Parser = {}
|
||||
function Parser.new (self, tokens)
|
||||
self.tokens = tokens
|
||||
self.parse_stack = {}
|
||||
self.refs = {}
|
||||
self.current = 0
|
||||
return self
|
||||
end
|
||||
|
||||
local exports = {version = "1.2"}
|
||||
|
||||
local word = function(w) return "^("..w..")([%s$%c])" end
|
||||
|
||||
local tokens = {
|
||||
{"comment", "^#[^\n]*"},
|
||||
{"indent", "^\n( *)"},
|
||||
{"space", "^ +"},
|
||||
{"true", word("enabled"), const = true, value = true},
|
||||
{"true", word("true"), const = true, value = true},
|
||||
{"true", word("yes"), const = true, value = true},
|
||||
{"true", word("on"), const = true, value = true},
|
||||
{"false", word("disabled"), const = true, value = false},
|
||||
{"false", word("false"), const = true, value = false},
|
||||
{"false", word("no"), const = true, value = false},
|
||||
{"false", word("off"), const = true, value = false},
|
||||
{"null", word("null"), const = true, value = nil},
|
||||
{"null", word("Null"), const = true, value = nil},
|
||||
{"null", word("NULL"), const = true, value = nil},
|
||||
{"null", word("~"), const = true, value = nil},
|
||||
{"id", "^\"([^\"]-)\" *(:[%s%c])"},
|
||||
{"id", "^'([^']-)' *(:[%s%c])"},
|
||||
{"string", "^\"([^\"]-)\"", force_text = true},
|
||||
{"string", "^'([^']-)'", force_text = true},
|
||||
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?):(%d%d)"},
|
||||
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)%s+(%-?%d%d?)"},
|
||||
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d):(%d%d)"},
|
||||
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?):(%d%d)"},
|
||||
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)%s+(%d%d?)"},
|
||||
{"timestamp", "^(%d%d%d%d)-(%d%d?)-(%d%d?)"},
|
||||
{"doc", "^%-%-%-[^%c]*"},
|
||||
{",", "^,"},
|
||||
{"string", "^%b{} *[^,%c]+", noinline = true},
|
||||
{"{", "^{"},
|
||||
{"}", "^}"},
|
||||
{"string", "^%b[] *[^,%c]+", noinline = true},
|
||||
{"[", "^%["},
|
||||
{"]", "^%]"},
|
||||
{"-", "^%-"},
|
||||
{":", "^:"},
|
||||
{"pipe", "^(|)(%d*[+%-]?)", sep = "\n"},
|
||||
{"pipe", "^(>)(%d*[+%-]?)", sep = " "},
|
||||
{"id", "^([%w][%w %-_]*)(:[%s%c])"},
|
||||
{"string", "^[^%c]+", noinline = true},
|
||||
{"string", "^[^,%c ]+"}
|
||||
};
|
||||
exports.tokenize = function (str)
|
||||
local token
|
||||
local row = 0
|
||||
local ignore
|
||||
local indents = 0
|
||||
local lastIndents
|
||||
local stack = {}
|
||||
local indentAmount = 0
|
||||
local inline = false
|
||||
str = str:gsub("\r\n","\010")
|
||||
|
||||
while #str > 0 do
|
||||
for i in ipairs(tokens) do
|
||||
local captures = {}
|
||||
if not inline or tokens[i].noinline == nil then
|
||||
captures = {str:match(tokens[i][2])}
|
||||
end
|
||||
|
||||
if #captures > 0 then
|
||||
captures.input = str:sub(0, 25)
|
||||
token = table_clone(tokens[i])
|
||||
token[2] = captures
|
||||
local str2 = str:gsub(tokens[i][2], "", 1)
|
||||
token.raw = str:sub(1, #str - #str2)
|
||||
str = str2
|
||||
|
||||
if token[1] == "{" or token[1] == "[" then
|
||||
inline = true
|
||||
elseif token.const then
|
||||
-- Since word pattern contains last char we're re-adding it
|
||||
str = token[2][2] .. str
|
||||
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
|
||||
elseif token[1] == "id" then
|
||||
-- Since id pattern contains last semi-colon we're re-adding it
|
||||
str = token[2][2] .. str
|
||||
token.raw = token.raw:sub(1, #token.raw - #token[2][2])
|
||||
-- Trim
|
||||
token[2][1] = string_trim(token[2][1])
|
||||
elseif token[1] == "string" then
|
||||
-- Finding numbers
|
||||
local snip = token[2][1]
|
||||
if not token.force_text then
|
||||
if snip:match("^(%d+%.%d+)$") or snip:match("^(%d+)$") then
|
||||
token[1] = "number"
|
||||
end
|
||||
end
|
||||
|
||||
elseif token[1] == "comment" then
|
||||
ignore = true;
|
||||
elseif token[1] == "indent" then
|
||||
row = row + 1
|
||||
inline = false
|
||||
lastIndents = indents
|
||||
if indentAmount == 0 then
|
||||
indentAmount = #token[2][1]
|
||||
end
|
||||
|
||||
if indentAmount ~= 0 then
|
||||
indents = (#token[2][1] / indentAmount);
|
||||
else
|
||||
indents = 0
|
||||
end
|
||||
|
||||
if indents == lastIndents then
|
||||
ignore = true;
|
||||
elseif indents > lastIndents + 2 then
|
||||
error("SyntaxError: invalid indentation, got " .. tostring(indents)
|
||||
.. " instead of " .. tostring(lastIndents) .. context(token[2].input))
|
||||
elseif indents > lastIndents + 1 then
|
||||
push(stack, token)
|
||||
elseif indents < lastIndents then
|
||||
local input = token[2].input
|
||||
token = {"dedent", {"", input = ""}}
|
||||
token.input = input
|
||||
while lastIndents > indents + 1 do
|
||||
lastIndents = lastIndents - 1
|
||||
push(stack, token)
|
||||
end
|
||||
end
|
||||
end -- if token[1] == XXX
|
||||
token.row = row
|
||||
break
|
||||
end -- if #captures > 0
|
||||
end
|
||||
|
||||
if not ignore then
|
||||
if token then
|
||||
push(stack, token)
|
||||
token = nil
|
||||
else
|
||||
error("SyntaxError " .. context(str))
|
||||
end
|
||||
end
|
||||
|
||||
ignore = false;
|
||||
end
|
||||
|
||||
return stack
|
||||
end
|
||||
|
||||
Parser.peek = function (self, offset)
|
||||
offset = offset or 1
|
||||
return self.tokens[offset + self.current]
|
||||
end
|
||||
|
||||
Parser.advance = function (self)
|
||||
self.current = self.current + 1
|
||||
return self.tokens[self.current]
|
||||
end
|
||||
|
||||
Parser.advanceValue = function (self)
|
||||
return self:advance()[2][1]
|
||||
end
|
||||
|
||||
Parser.accept = function (self, type)
|
||||
if self:peekType(type) then
|
||||
return self:advance()
|
||||
end
|
||||
end
|
||||
|
||||
Parser.expect = function (self, type, msg)
|
||||
return self:accept(type) or
|
||||
error(msg .. context(self:peek()[1].input))
|
||||
end
|
||||
|
||||
Parser.expectDedent = function (self, msg)
|
||||
return self:accept("dedent") or (self:peek() == nil) or
|
||||
error(msg .. context(self:peek()[2].input))
|
||||
end
|
||||
|
||||
Parser.peekType = function (self, val, offset)
|
||||
return self:peek(offset) and self:peek(offset)[1] == val
|
||||
end
|
||||
|
||||
Parser.ignore = function (self, items)
|
||||
local advanced
|
||||
repeat
|
||||
advanced = false
|
||||
for _,v in pairs(items) do
|
||||
if self:peekType(v) then
|
||||
self:advance()
|
||||
advanced = true
|
||||
end
|
||||
end
|
||||
until advanced == false
|
||||
end
|
||||
|
||||
Parser.ignoreSpace = function (self)
|
||||
self:ignore{"space"}
|
||||
end
|
||||
|
||||
Parser.ignoreWhitespace = function (self)
|
||||
self:ignore{"space", "indent", "dedent"}
|
||||
end
|
||||
|
||||
Parser.parse = function (self)
|
||||
|
||||
local ref = nil
|
||||
if self:peekType("string") and not self:peek().force_text then
|
||||
local char = self:peek()[2][1]:sub(1,1)
|
||||
if char == "&" then
|
||||
ref = self:peek()[2][1]:sub(2)
|
||||
self:advanceValue()
|
||||
self:ignoreSpace()
|
||||
elseif char == "*" then
|
||||
ref = self:peek()[2][1]:sub(2)
|
||||
return self.refs[ref]
|
||||
end
|
||||
end
|
||||
|
||||
local result
|
||||
local c = {
|
||||
indent = self:accept("indent") and 1 or 0,
|
||||
token = self:peek()
|
||||
}
|
||||
push(self.parse_stack, c)
|
||||
|
||||
if c.token[1] == "doc" then
|
||||
result = self:parseDoc()
|
||||
elseif c.token[1] == "-" then
|
||||
result = self:parseList()
|
||||
elseif c.token[1] == "{" then
|
||||
result = self:parseInlineHash()
|
||||
elseif c.token[1] == "[" then
|
||||
result = self:parseInlineList()
|
||||
elseif c.token[1] == "id" then
|
||||
result = self:parseHash()
|
||||
elseif c.token[1] == "string" then
|
||||
result = self:parseString("\n")
|
||||
elseif c.token[1] == "timestamp" then
|
||||
result = self:parseTimestamp()
|
||||
elseif c.token[1] == "number" then
|
||||
result = tonumber(self:advanceValue())
|
||||
elseif c.token[1] == "pipe" then
|
||||
result = self:parsePipe()
|
||||
elseif c.token.const == true then
|
||||
self:advanceValue();
|
||||
result = c.token.value
|
||||
else
|
||||
error("ParseError: unexpected token '" .. c.token[1] .. "'" .. context(c.token.input))
|
||||
end
|
||||
|
||||
pop(self.parse_stack)
|
||||
while c.indent > 0 do
|
||||
c.indent = c.indent - 1
|
||||
local term = "term "..c.token[1]..": '"..c.token[2][1].."'"
|
||||
self:expectDedent("last ".. term .." is not properly dedented")
|
||||
end
|
||||
|
||||
if ref then
|
||||
self.refs[ref] = result
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
Parser.parseDoc = function (self)
|
||||
self:accept("doc")
|
||||
return self:parse()
|
||||
end
|
||||
|
||||
Parser.inline = function (self)
|
||||
local current = self:peek(0)
|
||||
if not current then
|
||||
return {}, 0
|
||||
end
|
||||
|
||||
local inline = {}
|
||||
local i = 0
|
||||
|
||||
while self:peek(i) and not self:peekType("indent", i) and current.row == self:peek(i).row do
|
||||
inline[self:peek(i)[1]] = true
|
||||
i = i - 1
|
||||
end
|
||||
return inline, -i
|
||||
end
|
||||
|
||||
Parser.isInline = function (self)
|
||||
local _, i = self:inline()
|
||||
return i > 0
|
||||
end
|
||||
|
||||
Parser.parent = function(self, level)
|
||||
level = level or 1
|
||||
return self.parse_stack[#self.parse_stack - level]
|
||||
end
|
||||
|
||||
Parser.parentType = function(self, type, level)
|
||||
return self:parent(level) and self:parent(level).token[1] == type
|
||||
end
|
||||
|
||||
Parser.parseString = function (self)
|
||||
if self:isInline() then
|
||||
local result = self:advanceValue()
|
||||
|
||||
--[[
|
||||
- a: this looks
|
||||
flowing: but is
|
||||
no: string
|
||||
--]]
|
||||
local types = self:inline()
|
||||
if types["id"] and types["-"] then
|
||||
if not self:peekType("indent") or not self:peekType("indent", 2) then
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
a: 1
|
||||
b: this is
|
||||
a flowing string
|
||||
example
|
||||
c: 3
|
||||
--]]
|
||||
if self:peekType("indent") then
|
||||
self:expect("indent", "text block needs to start with indent")
|
||||
local addtl = self:accept("indent")
|
||||
|
||||
result = result .. "\n" .. self:parseTextBlock("\n")
|
||||
|
||||
self:expectDedent("text block ending dedent missing")
|
||||
if addtl then
|
||||
self:expectDedent("text block ending dedent missing")
|
||||
end
|
||||
end
|
||||
return result
|
||||
else
|
||||
--[[
|
||||
a: 1
|
||||
b:
|
||||
this is also
|
||||
a flowing string
|
||||
example
|
||||
c: 3
|
||||
--]]
|
||||
return self:parseTextBlock("\n")
|
||||
end
|
||||
end
|
||||
|
||||
Parser.parsePipe = function (self)
|
||||
local pipe = self:expect("pipe")
|
||||
self:expect("indent", "text block needs to start with indent")
|
||||
local result = self:parseTextBlock(pipe.sep)
|
||||
self:expectDedent("text block ending dedent missing")
|
||||
return result
|
||||
end
|
||||
|
||||
Parser.parseTextBlock = function (self, sep)
|
||||
local token = self:advance()
|
||||
local result = string_trim(token.raw, "\n")
|
||||
local indents = 0
|
||||
while self:peek() ~= nil and ( indents > 0 or not self:peekType("dedent") ) do
|
||||
local newtoken = self:advance()
|
||||
while token.row < newtoken.row do
|
||||
result = result .. sep
|
||||
token.row = token.row + 1
|
||||
end
|
||||
if newtoken[1] == "indent" then
|
||||
indents = indents + 1
|
||||
elseif newtoken[1] == "dedent" then
|
||||
indents = indents - 1
|
||||
else
|
||||
result = result .. string_trim(newtoken.raw, "\n")
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
Parser.parseHash = function (self, hash)
|
||||
hash = hash or {}
|
||||
local indents = 0
|
||||
|
||||
if self:isInline() then
|
||||
local id = self:advanceValue()
|
||||
self:expect(":", "expected semi-colon after id")
|
||||
self:ignoreSpace()
|
||||
if self:accept("indent") then
|
||||
indents = indents + 1
|
||||
hash[id] = self:parse()
|
||||
else
|
||||
hash[id] = self:parse()
|
||||
if self:accept("indent") then
|
||||
indents = indents + 1
|
||||
end
|
||||
end
|
||||
self:ignoreSpace();
|
||||
end
|
||||
|
||||
while self:peekType("id") do
|
||||
local id = self:advanceValue()
|
||||
self:expect(":","expected semi-colon after id")
|
||||
self:ignoreSpace()
|
||||
hash[id] = self:parse()
|
||||
self:ignoreSpace();
|
||||
end
|
||||
|
||||
while indents > 0 do
|
||||
self:expectDedent("expected dedent")
|
||||
indents = indents - 1
|
||||
end
|
||||
|
||||
return hash
|
||||
end
|
||||
|
||||
Parser.parseInlineHash = function (self)
|
||||
local id
|
||||
local hash = {}
|
||||
local i = 0
|
||||
|
||||
self:accept("{")
|
||||
while not self:accept("}") do
|
||||
self:ignoreSpace()
|
||||
if i > 0 then
|
||||
self:expect(",","expected comma")
|
||||
end
|
||||
|
||||
self:ignoreWhitespace()
|
||||
if self:peekType("id") then
|
||||
id = self:advanceValue()
|
||||
if id then
|
||||
self:expect(":","expected semi-colon after id")
|
||||
self:ignoreSpace()
|
||||
hash[id] = self:parse()
|
||||
self:ignoreWhitespace()
|
||||
end
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
end
|
||||
return hash
|
||||
end
|
||||
|
||||
Parser.parseList = function (self)
|
||||
local list = {}
|
||||
while self:accept("-") do
|
||||
self:ignoreSpace()
|
||||
list[#list + 1] = self:parse()
|
||||
|
||||
self:ignoreSpace()
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
Parser.parseInlineList = function (self)
|
||||
local list = {}
|
||||
local i = 0
|
||||
self:accept("[")
|
||||
while not self:accept("]") do
|
||||
self:ignoreSpace()
|
||||
if i > 0 then
|
||||
self:expect(",","expected comma")
|
||||
end
|
||||
|
||||
self:ignoreSpace()
|
||||
list[#list + 1] = self:parse()
|
||||
self:ignoreSpace()
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
Parser.parseTimestamp = function (self)
|
||||
local capture = self:advance()[2]
|
||||
|
||||
return os.time{
|
||||
year = capture[1],
|
||||
month = capture[2],
|
||||
day = capture[3],
|
||||
hour = capture[4] or 0,
|
||||
min = capture[5] or 0,
|
||||
sec = capture[6] or 0
|
||||
}
|
||||
end
|
||||
|
||||
exports.Eval = function (str)
|
||||
return Parser:new(exports.tokenize(str)):parse()
|
||||
end
|
||||
|
||||
exports.Read = function(file_name)
|
||||
if file.Exists(file_name, 'GAME') then
|
||||
local local_name = file_name:gsub('%.y([a]?)ml', '.local.y%1ml')
|
||||
|
||||
if file.Exists(local_name, 'GAME') then
|
||||
file_name = local_name
|
||||
end
|
||||
|
||||
return Parser:new(exports.tokenize(file.Read(file_name, 'GAME'))):parse()
|
||||
end
|
||||
end
|
||||
|
||||
exports.Dump = table_print
|
||||
|
||||
ix.yaml = exports
|
||||
663
gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua
vendored
Normal file
663
gamemodes/helix/gamemode/core/libs/thirdparty/sv_mysql.lua
vendored
Normal file
@@ -0,0 +1,663 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
--[[
|
||||
mysql - 1.0.3
|
||||
A simple MySQL wrapper for Garry's Mod.
|
||||
|
||||
Alexander Grist-Hucker
|
||||
http://www.alexgrist.com
|
||||
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alex Grist-Hucker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
--]]
|
||||
|
||||
mysql = mysql or {
|
||||
module = "sqlite"
|
||||
}
|
||||
|
||||
local QueueTable = {}
|
||||
local tostring = tostring
|
||||
local table = table
|
||||
|
||||
--[[
|
||||
Replacement tables
|
||||
--]]
|
||||
|
||||
local Replacements = {
|
||||
sqlite = {
|
||||
Create = {
|
||||
{"UNSIGNED ", ""},
|
||||
{"NOT NULL AUTO_INCREMENT", ""}, -- assuming primary key
|
||||
{"AUTO_INCREMENT", ""},
|
||||
{"INT%(%d*%)", "INTEGER"},
|
||||
{"INT ", "INTEGER"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--[[
|
||||
Phrases
|
||||
--]]
|
||||
|
||||
local MODULE_NOT_EXIST = "[mysql] The %s module does not exist!\n"
|
||||
|
||||
--[[
|
||||
Begin Query Class.
|
||||
--]]
|
||||
|
||||
local QUERY_CLASS = {}
|
||||
QUERY_CLASS.__index = QUERY_CLASS
|
||||
|
||||
function QUERY_CLASS:New(tableName, queryType)
|
||||
local newObject = setmetatable({}, QUERY_CLASS)
|
||||
newObject.queryType = queryType
|
||||
newObject.tableName = tableName
|
||||
newObject.selectList = {}
|
||||
newObject.insertList = {}
|
||||
newObject.updateList = {}
|
||||
newObject.createList = {}
|
||||
newObject.whereList = {}
|
||||
newObject.orderByList = {}
|
||||
return newObject
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Escape(text)
|
||||
return mysql:Escape(tostring(text))
|
||||
end
|
||||
|
||||
function QUERY_CLASS:ForTable(tableName)
|
||||
self.tableName = tableName
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Where(key, value)
|
||||
self:WhereEqual(key, value)
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereEqual(key, value)
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` = '"..self:Escape(value).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereNotEqual(key, value)
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` != '"..self:Escape(value).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereLike(key, value, format)
|
||||
format = format or "%%%s%%"
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` LIKE '"..string.format(format, self:Escape(value)).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereNotLike(key, value, format)
|
||||
format = format or "%%%s%%"
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` NOT LIKE '"..string.format(format, self:Escape(value)).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereGT(key, value)
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` > '"..self:Escape(value).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereLT(key, value)
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` < '"..self:Escape(value).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereGTE(key, value)
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` >= '"..self:Escape(value).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereLTE(key, value)
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` <= '"..self:Escape(value).."'"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:WhereIn(key, value)
|
||||
value = istable(value) and value or {value}
|
||||
|
||||
local values = ""
|
||||
local bFirst = true
|
||||
|
||||
for k, v in pairs(value) do
|
||||
values = values .. (bFirst and "" or ", ") .. "'" .. self:Escape(v) .. "'"
|
||||
bFirst = false
|
||||
end
|
||||
|
||||
self.whereList[#self.whereList + 1] = "`"..key.."` IN ("..values..")"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:OrderByDesc(key)
|
||||
self.orderByList[#self.orderByList + 1] = "`"..key.."` DESC"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:OrderByAsc(key)
|
||||
self.orderByList[#self.orderByList + 1] = "`"..key.."` ASC"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Callback(queryCallback)
|
||||
self.callback = queryCallback
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Select(fieldName)
|
||||
self.selectList[#self.selectList + 1] = "`"..fieldName.."`"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Insert(key, value)
|
||||
self.insertList[#self.insertList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"}
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Update(key, value)
|
||||
self.updateList[#self.updateList + 1] = {"`"..key.."`", "'"..self:Escape(value).."'"}
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Create(key, value)
|
||||
self.createList[#self.createList + 1] = {"`"..key.."`", value}
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Add(key, value)
|
||||
self.add = {"`"..key.."`", value}
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Drop(key)
|
||||
self.drop = "`"..key.."`"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:PrimaryKey(key)
|
||||
self.primaryKey = "`"..key.."`"
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Limit(value)
|
||||
self.limit = value
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Offset(value)
|
||||
self.offset = value
|
||||
end
|
||||
|
||||
local function ApplyQueryReplacements(mode, query)
|
||||
if (!Replacements[mysql.module]) then
|
||||
return query
|
||||
end
|
||||
|
||||
local result = query
|
||||
local entries = Replacements[mysql.module][mode]
|
||||
|
||||
for i = 1, #entries do
|
||||
result = string.gsub(result, entries[i][1], entries[i][2])
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local function BuildSelectQuery(queryObj)
|
||||
local queryString = {"SELECT"}
|
||||
|
||||
if (!istable(queryObj.selectList) or #queryObj.selectList == 0) then
|
||||
queryString[#queryString + 1] = " *"
|
||||
else
|
||||
queryString[#queryString + 1] = " "..table.concat(queryObj.selectList, ", ")
|
||||
end
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " FROM `"..queryObj.tableName.."` "
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
|
||||
queryString[#queryString + 1] = " WHERE "
|
||||
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
|
||||
end
|
||||
|
||||
if (istable(queryObj.orderByList) and #queryObj.orderByList > 0) then
|
||||
queryString[#queryString + 1] = " ORDER BY "
|
||||
queryString[#queryString + 1] = table.concat(queryObj.orderByList, ", ")
|
||||
end
|
||||
|
||||
if (isnumber(queryObj.limit)) then
|
||||
queryString[#queryString + 1] = " LIMIT "
|
||||
queryString[#queryString + 1] = queryObj.limit
|
||||
|
||||
if (isnumber(queryObj.offset)) then
|
||||
queryString[#queryString + 1] = " OFFSET "
|
||||
queryString[#queryString + 1] = queryObj.offset
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
local function BuildInsertQuery(queryObj, bIgnore)
|
||||
local suffix = (bIgnore and (mysql.module == "sqlite" and "INSERT OR IGNORE INTO" or "INSERT IGNORE INTO") or "INSERT INTO")
|
||||
local queryString = {suffix}
|
||||
local keyList = {}
|
||||
local valueList = {}
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
for i = 1, #queryObj.insertList do
|
||||
keyList[#keyList + 1] = queryObj.insertList[i][1]
|
||||
valueList[#valueList + 1] = queryObj.insertList[i][2]
|
||||
end
|
||||
|
||||
if (#keyList == 0) then
|
||||
return
|
||||
end
|
||||
|
||||
queryString[#queryString + 1] = " ("..table.concat(keyList, ", ")..")"
|
||||
queryString[#queryString + 1] = " VALUES ("..table.concat(valueList, ", ")..")"
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
local function BuildUpdateQuery(queryObj)
|
||||
local queryString = {"UPDATE"}
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
if (istable(queryObj.updateList) and #queryObj.updateList > 0) then
|
||||
local updateList = {}
|
||||
|
||||
queryString[#queryString + 1] = " SET"
|
||||
|
||||
for i = 1, #queryObj.updateList do
|
||||
updateList[#updateList + 1] = queryObj.updateList[i][1].." = "..queryObj.updateList[i][2]
|
||||
end
|
||||
|
||||
queryString[#queryString + 1] = " "..table.concat(updateList, ", ")
|
||||
end
|
||||
|
||||
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
|
||||
queryString[#queryString + 1] = " WHERE "
|
||||
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
|
||||
end
|
||||
|
||||
if (isnumber(queryObj.offset)) then
|
||||
queryString[#queryString + 1] = " OFFSET "
|
||||
queryString[#queryString + 1] = queryObj.offset
|
||||
end
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
local function BuildDeleteQuery(queryObj)
|
||||
local queryString = {"DELETE FROM"}
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
if (istable(queryObj.whereList) and #queryObj.whereList > 0) then
|
||||
queryString[#queryString + 1] = " WHERE "
|
||||
queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND ")
|
||||
end
|
||||
|
||||
if (isnumber(queryObj.limit)) then
|
||||
queryString[#queryString + 1] = " LIMIT "
|
||||
queryString[#queryString + 1] = queryObj.limit
|
||||
end
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
local function BuildDropQuery(queryObj)
|
||||
local queryString = {"DROP TABLE"}
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
local function BuildTruncateQuery(queryObj)
|
||||
local queryString = {"TRUNCATE TABLE"}
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
local function BuildCreateQuery(queryObj)
|
||||
local queryString = {"CREATE TABLE IF NOT EXISTS"}
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
queryString[#queryString + 1] = " ("
|
||||
|
||||
if (istable(queryObj.createList) and #queryObj.createList > 0) then
|
||||
local createList = {}
|
||||
|
||||
for i = 1, #queryObj.createList do
|
||||
if (mysql.module == "sqlite") then
|
||||
createList[#createList + 1] = queryObj.createList[i][1].." "..ApplyQueryReplacements("Create", queryObj.createList[i][2])
|
||||
else
|
||||
createList[#createList + 1] = queryObj.createList[i][1].." "..queryObj.createList[i][2]
|
||||
end
|
||||
end
|
||||
|
||||
queryString[#queryString + 1] = " "..table.concat(createList, ", ")
|
||||
end
|
||||
|
||||
if (isstring(queryObj.primaryKey)) then
|
||||
queryString[#queryString + 1] = ", PRIMARY KEY"
|
||||
queryString[#queryString + 1] = " ("..queryObj.primaryKey..")"
|
||||
end
|
||||
|
||||
queryString[#queryString + 1] = " )"
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
local function BuildAlterQuery(queryObj)
|
||||
local queryString = {"ALTER TABLE"}
|
||||
|
||||
if (isstring(queryObj.tableName)) then
|
||||
queryString[#queryString + 1] = " `"..queryObj.tableName.."`"
|
||||
else
|
||||
ErrorNoHalt("[mysql] No table name specified!\n")
|
||||
return
|
||||
end
|
||||
|
||||
if (istable(queryObj.add)) then
|
||||
queryString[#queryString + 1] = " ADD "..queryObj.add[1].." "..ApplyQueryReplacements("Create", queryObj.add[2])
|
||||
elseif (isstring(queryObj.drop)) then
|
||||
if (mysql.module == "sqlite") then
|
||||
ErrorNoHalt("[mysql] Cannot drop columns in sqlite!\n")
|
||||
return
|
||||
end
|
||||
|
||||
queryString[#queryString + 1] = " DROP COLUMN "..queryObj.drop
|
||||
end
|
||||
|
||||
return table.concat(queryString)
|
||||
end
|
||||
|
||||
function QUERY_CLASS:Execute(bQueueQuery)
|
||||
local queryString = nil
|
||||
local queryType = string.lower(self.queryType)
|
||||
|
||||
if (queryType == "select") then
|
||||
queryString = BuildSelectQuery(self)
|
||||
elseif (queryType == "insert") then
|
||||
queryString = BuildInsertQuery(self)
|
||||
elseif (queryType == "insert ignore") then
|
||||
queryString = BuildInsertQuery(self, true)
|
||||
elseif (queryType == "update") then
|
||||
queryString = BuildUpdateQuery(self)
|
||||
elseif (queryType == "delete") then
|
||||
queryString = BuildDeleteQuery(self)
|
||||
elseif (queryType == "drop") then
|
||||
queryString = BuildDropQuery(self)
|
||||
elseif (queryType == "truncate") then
|
||||
queryString = BuildTruncateQuery(self)
|
||||
elseif (queryType == "create") then
|
||||
queryString = BuildCreateQuery(self)
|
||||
elseif (queryType == "alter") then
|
||||
queryString = BuildAlterQuery(self)
|
||||
end
|
||||
|
||||
if (isstring(queryString)) then
|
||||
if (!bQueueQuery) then
|
||||
return mysql:RawQuery(queryString, self.callback)
|
||||
else
|
||||
return mysql:Queue(queryString, self.callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
End Query Class.
|
||||
--]]
|
||||
|
||||
function mysql:Select(tableName)
|
||||
return QUERY_CLASS:New(tableName, "SELECT")
|
||||
end
|
||||
|
||||
function mysql:Insert(tableName)
|
||||
return QUERY_CLASS:New(tableName, "INSERT")
|
||||
end
|
||||
|
||||
function mysql:InsertIgnore(tableName)
|
||||
return QUERY_CLASS:New(tableName, "INSERT IGNORE")
|
||||
end
|
||||
|
||||
function mysql:Update(tableName)
|
||||
return QUERY_CLASS:New(tableName, "UPDATE")
|
||||
end
|
||||
|
||||
function mysql:Delete(tableName)
|
||||
return QUERY_CLASS:New(tableName, "DELETE")
|
||||
end
|
||||
|
||||
function mysql:Drop(tableName)
|
||||
return QUERY_CLASS:New(tableName, "DROP")
|
||||
end
|
||||
|
||||
function mysql:Truncate(tableName)
|
||||
return QUERY_CLASS:New(tableName, "TRUNCATE")
|
||||
end
|
||||
|
||||
function mysql:Create(tableName)
|
||||
return QUERY_CLASS:New(tableName, "CREATE")
|
||||
end
|
||||
|
||||
function mysql:Alter(tableName)
|
||||
return QUERY_CLASS:New(tableName, "ALTER")
|
||||
end
|
||||
|
||||
local UTF8MB4 = "ALTER DATABASE %s CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci"
|
||||
|
||||
-- A function to connect to the MySQL database.
|
||||
function mysql:Connect(host, username, password, database, port, socket, flags)
|
||||
port = port or 3306
|
||||
|
||||
if (self.module == "mysqloo") then
|
||||
if (!istable(mysqloo)) then
|
||||
require("mysqloo")
|
||||
end
|
||||
|
||||
if (mysqloo) then
|
||||
if (self.connection and self.connection:ping()) then
|
||||
return
|
||||
end
|
||||
|
||||
local clientFlag = flags or 0
|
||||
|
||||
if (!isstring(socket)) then
|
||||
self.connection = mysqloo.connect(host, username, password, database, port)
|
||||
else
|
||||
self.connection = mysqloo.connect(host, username, password, database, port, socket, clientFlag)
|
||||
end
|
||||
|
||||
self.connection.onConnected = function(connection)
|
||||
local success, error_message = connection:setCharacterSet("utf8mb4")
|
||||
|
||||
if (!success) then
|
||||
ErrorNoHalt("Failed to set MySQL encoding!\n")
|
||||
ErrorNoHalt(error_message .. "\n")
|
||||
else
|
||||
self:RawQuery(string.format(UTF8MB4, database))
|
||||
end
|
||||
|
||||
mysql:OnConnected()
|
||||
end
|
||||
|
||||
self.connection.onConnectionFailed = function(database, errorText)
|
||||
mysql:OnConnectionFailed(errorText)
|
||||
end
|
||||
|
||||
self.connection:connect()
|
||||
|
||||
timer.Create("mysql.KeepAlive", 300, 0, function()
|
||||
self.connection:ping()
|
||||
end)
|
||||
else
|
||||
ErrorNoHalt(string.format(MODULE_NOT_EXIST, self.module))
|
||||
end
|
||||
elseif (self.module == "sqlite") then
|
||||
timer.Simple(0, function()
|
||||
mysql:OnConnected()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- A function to query the MySQL database.
|
||||
function mysql:RawQuery(query, callback, flags, ...)
|
||||
if (self.module == "mysqloo") then
|
||||
local queryObj = self.connection:query(query)
|
||||
|
||||
queryObj:setOption(mysqloo.OPTION_NAMED_FIELDS)
|
||||
|
||||
queryObj.onSuccess = function(queryObj, result)
|
||||
if (callback) then
|
||||
local bStatus, value = pcall(callback, result, true, tonumber(queryObj:lastInsert()))
|
||||
|
||||
if (!bStatus) then
|
||||
error(string.format("[mysql] MySQL Callback Error!\n%s\n", value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
queryObj.onError = function(queryObj, errorText)
|
||||
ErrorNoHalt(string.format("[mysql] MySQL Query Error!\nQuery: %s\n%s\n", query, errorText))
|
||||
end
|
||||
|
||||
queryObj:start()
|
||||
elseif (self.module == "sqlite") then
|
||||
local result = sql.Query(query)
|
||||
|
||||
if (result == false) then
|
||||
error(string.format("[mysql] SQL Query Error!\nQuery: %s\n%s\n", query, sql.LastError()))
|
||||
else
|
||||
if (callback) then
|
||||
local bStatus, value = pcall(callback, result, true, tonumber(sql.QueryValue("SELECT last_insert_rowid()")))
|
||||
|
||||
if (!bStatus) then
|
||||
error(string.format("[mysql] SQL Callback Error!\n%s\n", value))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
ErrorNoHalt(string.format("[mysql] Unsupported module \"%s\"!\n", self.module))
|
||||
end
|
||||
end
|
||||
|
||||
-- A function to add a query to the queue.
|
||||
function mysql:Queue(queryString, callback)
|
||||
if (isstring(queryString)) then
|
||||
QueueTable[#QueueTable + 1] = {queryString, callback}
|
||||
end
|
||||
end
|
||||
|
||||
-- A function to escape a string for MySQL.
|
||||
function mysql:Escape(text)
|
||||
if (self.connection) then
|
||||
if (self.module == "mysqloo") then
|
||||
return self.connection:escape(text)
|
||||
end
|
||||
else
|
||||
return sql.SQLStr(text, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- A function to disconnect from the MySQL database.
|
||||
function mysql:Disconnect()
|
||||
if (self.connection) then
|
||||
if (self.module == "mysqloo") then
|
||||
self.connection:disconnect(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mysql:Think()
|
||||
if (#QueueTable > 0) then
|
||||
if (istable(QueueTable[1])) then
|
||||
local queueObj = QueueTable[1]
|
||||
local queryString = queueObj[1]
|
||||
local callback = queueObj[2]
|
||||
|
||||
if (isstring(queryString)) then
|
||||
self:RawQuery(queryString, callback)
|
||||
end
|
||||
|
||||
table.remove(QueueTable, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- A function to set the module that should be used.
|
||||
function mysql:SetModule(moduleName)
|
||||
self.module = moduleName
|
||||
end
|
||||
|
||||
-- Called when the database connects sucessfully.
|
||||
function mysql:OnConnected()
|
||||
MsgC(Color(25, 235, 25), "[mysql] Connected to the database!\n")
|
||||
|
||||
hook.Run("DatabaseConnected")
|
||||
end
|
||||
|
||||
-- Called when the database connection fails.
|
||||
function mysql:OnConnectionFailed(errorText)
|
||||
ErrorNoHalt(string.format("[mysql] Unable to connect to the database!\n%s\n", errorText))
|
||||
|
||||
hook.Run("DatabaseConnectionFailed", errorText)
|
||||
end
|
||||
|
||||
-- A function to check whether or not the module is connected to a database.
|
||||
function mysql:IsConnected()
|
||||
return self.module == "mysqloo" and (self.connection and self.connection:ping()) or self.module == "sqlite"
|
||||
end
|
||||
|
||||
return mysql
|
||||
397
gamemodes/helix/gamemode/core/meta/sh_character.lua
Normal file
397
gamemodes/helix/gamemode/core/meta/sh_character.lua
Normal file
@@ -0,0 +1,397 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Contains information about a player's current game state.
|
||||
|
||||
Characters are a fundamental object type in Helix. They are distinct from players, where players are the representation of a
|
||||
person's existence in the server that owns a character, and their character is their currently selected persona. All the
|
||||
characters that a player owns will be loaded into memory once they connect to the server. Characters are saved during a regular
|
||||
interval, and during specific events (e.g when the owning player switches away from one character to another).
|
||||
|
||||
They contain all information that is not persistent with the player; names, descriptions, model, currency, etc. For the most
|
||||
part, you'll want to keep all information stored on the character since it will probably be different or change if the
|
||||
player switches to another character. An easy way to do this is to use `ix.char.RegisterVar` to easily create accessor functions
|
||||
for variables that automatically save to the character object.
|
||||
]]
|
||||
-- @classmod Character
|
||||
|
||||
local CHAR = ix.meta.character or {}
|
||||
CHAR.__index = CHAR
|
||||
CHAR.id = CHAR.id or 0
|
||||
CHAR.vars = CHAR.vars or {}
|
||||
|
||||
-- @todo not this
|
||||
if (!ix.db) then
|
||||
ix.util.Include("../libs/sv_database.lua")
|
||||
end
|
||||
|
||||
--- Returns a string representation of this character
|
||||
-- @realm shared
|
||||
-- @treturn string String representation
|
||||
-- @usage print(ix.char.loaded[1])
|
||||
-- > "character[1]"
|
||||
function CHAR:__tostring()
|
||||
return "character["..(self.id or 0).."]"
|
||||
end
|
||||
|
||||
--- Returns true if this character is equal to another character. Internally, this checks character IDs.
|
||||
-- @realm shared
|
||||
-- @char other Character to compare to
|
||||
-- @treturn bool Whether or not this character is equal to the given character
|
||||
-- @usage print(ix.char.loaded[1] == ix.char.loaded[2])
|
||||
-- > false
|
||||
function CHAR:__eq(other)
|
||||
return self:GetID() == other:GetID()
|
||||
end
|
||||
|
||||
--- Returns this character's database ID. This is guaranteed to be unique.
|
||||
-- @realm shared
|
||||
-- @treturn number Unique ID of character
|
||||
function CHAR:GetID()
|
||||
return self.id
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Saves this character's info to the database.
|
||||
-- @realm server
|
||||
-- @func[opt=nil] callback Function to call when the save has completed.
|
||||
-- @usage ix.char.loaded[1]:Save(function()
|
||||
-- print("done!")
|
||||
-- end)
|
||||
-- > done! -- after a moment
|
||||
function CHAR:Save(callback)
|
||||
-- Do not save if the character is for a bot.
|
||||
if (self.isBot) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Let plugins/schema determine if the character should be saved.
|
||||
local shouldSave = hook.Run("CharacterPreSave", self)
|
||||
|
||||
if (shouldSave != false) then
|
||||
-- Run a query to save the character to the database.
|
||||
local query = mysql:Update("ix_characters")
|
||||
-- update all character vars
|
||||
for k, v in pairs(ix.char.vars) do
|
||||
if (v.field and self.vars[k] != nil and !v.bSaveLoadInitialOnly) then
|
||||
if (type(v.default) == "table") then
|
||||
local tblQuery = mysql:Update("ix_characters_data")
|
||||
tblQuery:Update("data", util.TableToJSON(self.vars[k]) or "NULL")
|
||||
tblQuery:Where("id", self:GetID())
|
||||
tblQuery:Where("key", v.field)
|
||||
tblQuery:Execute()
|
||||
else
|
||||
local value = self.vars[k]
|
||||
|
||||
if (v.fieldType == ix.type.bool) then
|
||||
value = value and 1 or 0
|
||||
end
|
||||
query:Update(v.field, tostring(value))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
query:Where("id", self:GetID())
|
||||
query:Callback(function()
|
||||
if (callback) then
|
||||
callback()
|
||||
end
|
||||
|
||||
hook.Run("CharacterPostSave", self)
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
--- Networks this character's information to make the given player aware of this character's existence. If the receiver is
|
||||
-- not the owner of this character, it will only be sent a limited amount of data (as it does not need anything else).
|
||||
-- This is done automatically by the framework.
|
||||
-- @internal
|
||||
-- @realm server
|
||||
-- @player[opt=nil] receiver Player to send the information to. This will sync to all connected players if set to `nil`.
|
||||
function CHAR:Sync(receiver)
|
||||
-- Broadcast the character information if receiver is not set.
|
||||
if (receiver == nil) then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
self:Sync(v)
|
||||
end
|
||||
-- Send all character information if the receiver is the character's owner.
|
||||
elseif (receiver == self.player) then
|
||||
local data = {}
|
||||
|
||||
for k, v in pairs(self.vars) do
|
||||
if (ix.char.vars[k] != nil and !ix.char.vars[k].bNoNetworking) then
|
||||
data[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("ixCharacterInfo")
|
||||
ix.compnettable:Write(data)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(self.player:EntIndex(), 8)
|
||||
net.Send(self.player)
|
||||
else
|
||||
local data = {}
|
||||
|
||||
for k, v in pairs(ix.char.vars) do
|
||||
if (!v.bNoNetworking and !v.isLocal) then
|
||||
data[k] = self.vars[k]
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("ixCharacterInfo")
|
||||
ix.compnettable:Write(data)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(self.player:EntIndex(), 8)
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
-- Sets up the "appearance" related inforomation for the character.
|
||||
--- Applies the character's appearance and synchronizes information to the owning player.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @bool[opt] bNoNetworking Whether or not to sync the character info to other players
|
||||
function CHAR:Setup(bNoNetworking)
|
||||
local client = self:GetPlayer()
|
||||
|
||||
if (IsValid(client)) then
|
||||
-- Set the faction, model, and character index for the player.
|
||||
local model = self:GetModel()
|
||||
|
||||
client:SetNetVar("char", self:GetID())
|
||||
client:SetTeam(self:GetFaction())
|
||||
client:SetModel(istable(model) and model[1] or model)
|
||||
|
||||
-- Apply saved body groups.
|
||||
for k, v in pairs(self:GetData("groups", {})) do
|
||||
client:SetBodygroup(k, v)
|
||||
end
|
||||
|
||||
-- Apply a saved skin.
|
||||
client:SetSkin(self:GetData("skin", 0))
|
||||
|
||||
-- Synchronize the character if we should.
|
||||
if (!bNoNetworking) then
|
||||
if (client:IsBot()) then
|
||||
timer.Simple(0.33, function()
|
||||
self:Sync()
|
||||
end)
|
||||
else
|
||||
self:Sync()
|
||||
end
|
||||
|
||||
for _, v in ipairs(self:GetInventory(true)) do
|
||||
if (istable(v)) then
|
||||
v:AddReceiver(client)
|
||||
v:Sync(client)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local id = self:GetID()
|
||||
|
||||
hook.Run("CharacterLoaded", ix.char.loaded[id])
|
||||
|
||||
net.Start("ixCharacterLoaded")
|
||||
net.WriteUInt(id, 32)
|
||||
net.Send(client)
|
||||
|
||||
self.firstTimeLoaded = true
|
||||
end
|
||||
end
|
||||
|
||||
--- Forces a player off their current character, and sends them to the character menu to select a character.
|
||||
-- @realm server
|
||||
function CHAR:Kick()
|
||||
-- Kill the player so they are not standing anywhere.
|
||||
local client = self:GetPlayer()
|
||||
client:KillSilent()
|
||||
|
||||
hook.Run("OnCharacterKicked", self)
|
||||
|
||||
local steamID = client:SteamID64()
|
||||
local id = self:GetID()
|
||||
local isCurrentChar = self and self:GetID() == id
|
||||
|
||||
-- Return the player to the character menu.
|
||||
if (self and self.steamID == steamID) then
|
||||
net.Start("ixCharacterKick")
|
||||
net.WriteBool(isCurrentChar)
|
||||
net.Send(client)
|
||||
|
||||
if (isCurrentChar) then
|
||||
client:SetNetVar("char", nil)
|
||||
client:Spawn()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Forces a player off their current character, and prevents them from using the character for the specified amount of time.
|
||||
-- @realm server
|
||||
-- @number[opt] time Amount of seconds to ban the character for. If left as `nil`, the character will be banned permanently
|
||||
function CHAR:Ban(time)
|
||||
time = tonumber(time)
|
||||
|
||||
hook.Run("OnCharacterBanned", self, time or true)
|
||||
|
||||
if (time) then
|
||||
-- If time is provided, adjust it so it becomes the un-ban time.
|
||||
time = os.time() + math.max(math.ceil(time), 60)
|
||||
end
|
||||
|
||||
-- Mark the character as banned and kick the character back to menu.
|
||||
self:SetData("banned", time or true)
|
||||
self:Kick()
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the player that owns this character.
|
||||
-- @realm shared
|
||||
-- @treturn player Player that owns this character
|
||||
function CHAR:GetPlayer()
|
||||
-- Set the player from entity index.
|
||||
if (isnumber(self.player)) then
|
||||
local client = Entity(self.player)
|
||||
|
||||
if (IsValid(client)) then
|
||||
self.player = client
|
||||
|
||||
return client
|
||||
end
|
||||
-- Return the player from cache.
|
||||
elseif (IsValid(self.player)) then
|
||||
return self.player
|
||||
-- Search for which player owns this character.
|
||||
elseif (self.steamID) then
|
||||
local steamID = self.steamID
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:SteamID64() == steamID) then
|
||||
self.player = v
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function CHAR:GetFactionVar(variable, default)
|
||||
local faction = ix.faction.Get(self:GetFaction())
|
||||
if (!faction) then return end
|
||||
|
||||
return faction[variable] or default
|
||||
end
|
||||
|
||||
-- Sets up a new character variable.
|
||||
function ix.char.RegisterVar(key, data)
|
||||
-- Store information for the variable.
|
||||
ix.char.vars[key] = data
|
||||
data.index = data.index or table.Count(ix.char.vars)
|
||||
|
||||
local upperName = key:sub(1, 1):upper() .. key:sub(2)
|
||||
|
||||
if (SERVER) then
|
||||
if (data.field) then
|
||||
if (type(data.default) != "table") then
|
||||
ix.db.AddToSchema("ix_characters", data.field, data.fieldType or ix.type.string)
|
||||
end
|
||||
end
|
||||
|
||||
-- Provide functions to change the variable if allowed.
|
||||
if (!data.bNotModifiable) then
|
||||
-- Overwrite the set function if desired.
|
||||
if (data.OnSet) then
|
||||
CHAR["Set"..upperName] = data.OnSet
|
||||
-- Have the set function only set on the server if no networking.
|
||||
elseif (data.bNoNetworking) then
|
||||
CHAR["Set"..upperName] = function(self, value)
|
||||
self.vars[key] = value
|
||||
end
|
||||
-- If the variable is a local one, only send the variable to the local player.
|
||||
elseif (data.isLocal) then
|
||||
CHAR["Set"..upperName] = function(self, value)
|
||||
local oldVar = self.vars[key]
|
||||
self.vars[key] = value
|
||||
|
||||
net.Start("ixCharacterVarChanged")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(self.player)
|
||||
|
||||
hook.Run("CharacterVarChanged", self, key, oldVar, value)
|
||||
end
|
||||
-- Otherwise network the variable to everyone.
|
||||
else
|
||||
CHAR["Set"..upperName] = function(self, value)
|
||||
local oldVar = self.vars[key]
|
||||
self.vars[key] = value
|
||||
|
||||
net.Start("ixCharacterVarChanged")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Broadcast()
|
||||
|
||||
hook.Run("CharacterVarChanged", self, key, oldVar, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- The get functions are shared.
|
||||
-- Overwrite the get function if desired.
|
||||
if (data.OnGet) then
|
||||
CHAR["Get"..upperName] = data.OnGet
|
||||
-- Otherwise return the character variable or default if it does not exist.
|
||||
else
|
||||
CHAR["Get"..upperName] = function(self, default)
|
||||
local value = self.vars[key]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
end
|
||||
|
||||
if (default == nil) then
|
||||
return ix.char.vars[key] and (istable(ix.char.vars[key].default) and table.Copy(ix.char.vars[key].default)
|
||||
or ix.char.vars[key].default)
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
end
|
||||
|
||||
local alias = data.alias
|
||||
|
||||
if (alias) then
|
||||
if (istable(alias)) then
|
||||
for _, v in ipairs(alias) do
|
||||
local aliasName = v:sub(1, 1):upper()..v:sub(2)
|
||||
|
||||
CHAR["Get"..aliasName] = CHAR["Get"..upperName]
|
||||
CHAR["Set"..aliasName] = CHAR["Set"..upperName]
|
||||
end
|
||||
elseif (isstring(alias)) then
|
||||
local aliasName = alias:sub(1, 1):upper()..alias:sub(2)
|
||||
|
||||
CHAR["Get"..aliasName] = CHAR["Get"..upperName]
|
||||
CHAR["Set"..aliasName] = CHAR["Set"..upperName]
|
||||
end
|
||||
end
|
||||
|
||||
-- Add the variable default to the character object.
|
||||
CHAR.vars[key] = data.default
|
||||
end
|
||||
|
||||
-- Allows access to the character metatable using ix.meta.character
|
||||
ix.meta.character = CHAR
|
||||
227
gamemodes/helix/gamemode/core/meta/sh_entity.lua
Normal file
227
gamemodes/helix/gamemode/core/meta/sh_entity.lua
Normal file
@@ -0,0 +1,227 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Physical object in the game world.
|
||||
|
||||
Entities are physical representations of objects in the game world. Helix extends the functionality of entities to interface
|
||||
between Helix's own classes, and to reduce boilerplate code.
|
||||
|
||||
See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Entity) for all other methods that the `Player` class has.
|
||||
]]
|
||||
-- @classmod Entity
|
||||
|
||||
local meta = FindMetaTable("Entity")
|
||||
local CHAIR_CACHE = {}
|
||||
|
||||
-- Add chair models to the cache by checking if its vehicle category is a class.
|
||||
for _, v in pairs(list.Get("Vehicles")) do
|
||||
if (v.Category == "Chairs") then
|
||||
CHAIR_CACHE[v.Model] = true
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns `true` if this entity is a chair.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not this entity is a chair
|
||||
function meta:IsChair()
|
||||
return CHAIR_CACHE[self:GetModel()]
|
||||
end
|
||||
|
||||
--- Returns `true` if this entity is a door. Internally, this checks to see if the entity's class has `door` in its name.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the entity is a door
|
||||
function meta:IsDoor()
|
||||
local class = self:GetClass()
|
||||
|
||||
return (class and class:find("door") != nil)
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Returns `true` if the given entity is a button or door and is locked.
|
||||
-- @realm server
|
||||
-- @treturn bool Whether or not this entity is locked; `false` if this entity cannot be locked at all
|
||||
-- (e.g not a button or door)
|
||||
function meta:IsLocked()
|
||||
if (self:IsVehicle()) then
|
||||
local datatable = self:GetSaveTable()
|
||||
|
||||
if (datatable) then
|
||||
return datatable.VehicleLocked
|
||||
end
|
||||
else
|
||||
local datatable = self:GetSaveTable()
|
||||
|
||||
if (datatable) then
|
||||
return datatable.m_bLocked
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns the neighbouring door entity for double doors.
|
||||
-- @realm shared
|
||||
-- @treturn[1] Entity This door's partner
|
||||
-- @treturn[2] nil If the door does not have a partner
|
||||
function meta:GetDoorPartner()
|
||||
return self.ixPartner
|
||||
end
|
||||
|
||||
--- Returns the entity that is blocking this door from opening.
|
||||
-- @realm server
|
||||
-- @treturn[1] Entity Entity that is blocking this door
|
||||
-- @treturn[2] nil If this entity is not a door, or there is no blocking entity
|
||||
function meta:GetBlocker()
|
||||
local datatable = self:GetSaveTable()
|
||||
|
||||
return datatable.pBlocker
|
||||
end
|
||||
|
||||
--- Blasts a door off its hinges. Internally, this hides the door entity, spawns a physics prop with the same model, and
|
||||
-- applies force to the prop.
|
||||
-- @realm server
|
||||
-- @vector velocity Velocity to apply to the door
|
||||
-- @number lifeTime How long to wait in seconds before the door is put back on its hinges
|
||||
-- @bool bIgnorePartner Whether or not to ignore the door's partner in the case of double doors
|
||||
-- @treturn[1] Entity The physics prop created for the door
|
||||
-- @treturn nil If the entity is not a door
|
||||
function meta:BlastDoor(velocity, lifeTime, bIgnorePartner)
|
||||
if (!self:IsDoor()) then
|
||||
return
|
||||
end
|
||||
|
||||
if (IsValid(self.ixDummy)) then
|
||||
self.ixDummy:Remove()
|
||||
end
|
||||
|
||||
velocity = velocity or VectorRand()*100
|
||||
lifeTime = lifeTime or 120
|
||||
|
||||
local partner = self:GetDoorPartner()
|
||||
|
||||
if (IsValid(partner) and !bIgnorePartner) then
|
||||
partner:BlastDoor(velocity, lifeTime, true)
|
||||
end
|
||||
|
||||
local color = self:GetColor()
|
||||
|
||||
local dummy = ents.Create("prop_physics")
|
||||
dummy:SetModel(self:GetModel())
|
||||
dummy:SetPos(self:GetPos())
|
||||
dummy:SetAngles(self:GetAngles())
|
||||
dummy:Spawn()
|
||||
dummy:SetColor(color)
|
||||
dummy:SetMaterial(self:GetMaterial())
|
||||
dummy:SetSkin(self:GetSkin() or 0)
|
||||
dummy:SetRenderMode(RENDERMODE_TRANSALPHA)
|
||||
dummy:CallOnRemove("restoreDoor", function()
|
||||
if (IsValid(self)) then
|
||||
self:SetNotSolid(false)
|
||||
self:SetNoDraw(false)
|
||||
self:DrawShadow(true)
|
||||
self.ignoreUse = false
|
||||
self.ixIsMuted = false
|
||||
|
||||
for _, v in ipairs(ents.GetAll()) do
|
||||
if (v:GetParent() == self) then
|
||||
v:SetNotSolid(false)
|
||||
v:SetNoDraw(false)
|
||||
|
||||
if (v.OnDoorRestored) then
|
||||
v:OnDoorRestored(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
dummy:SetOwner(self)
|
||||
dummy:SetCollisionGroup(COLLISION_GROUP_WEAPON)
|
||||
|
||||
self:Fire("unlock")
|
||||
self:Fire("open")
|
||||
self:SetNotSolid(true)
|
||||
self:SetNoDraw(true)
|
||||
self:DrawShadow(false)
|
||||
self.ignoreUse = true
|
||||
self.ixDummy = dummy
|
||||
self.ixIsMuted = true
|
||||
self:DeleteOnRemove(dummy)
|
||||
|
||||
for _, v in ipairs(self:GetBodyGroups() or {}) do
|
||||
dummy:SetBodygroup(v.id, self:GetBodygroup(v.id))
|
||||
end
|
||||
|
||||
for _, v in ipairs(ents.GetAll()) do
|
||||
if (v:GetParent() == self) then
|
||||
v:SetNotSolid(true)
|
||||
v:SetNoDraw(true)
|
||||
|
||||
if (v.OnDoorBlasted) then
|
||||
v:OnDoorBlasted(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dummy:GetPhysicsObject():SetVelocity(velocity)
|
||||
|
||||
local uniqueID = "doorRestore"..self:EntIndex()
|
||||
local uniqueID2 = "doorOpener"..self:EntIndex()
|
||||
|
||||
timer.Create(uniqueID2, 1, 0, function()
|
||||
if (IsValid(self) and IsValid(self.ixDummy)) then
|
||||
self:Fire("open")
|
||||
else
|
||||
timer.Remove(uniqueID2)
|
||||
end
|
||||
end)
|
||||
|
||||
timer.Create(uniqueID, lifeTime, 1, function()
|
||||
if (IsValid(self) and IsValid(dummy)) then
|
||||
uniqueID = "dummyFade"..dummy:EntIndex()
|
||||
local alpha = 255
|
||||
|
||||
timer.Create(uniqueID, 0.1, 255, function()
|
||||
if (IsValid(dummy)) then
|
||||
alpha = alpha - 1
|
||||
dummy:SetColor(ColorAlpha(color, alpha))
|
||||
|
||||
if (alpha <= 0) then
|
||||
dummy:Remove()
|
||||
end
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
return dummy
|
||||
end
|
||||
|
||||
else
|
||||
-- Returns the door's slave entity.
|
||||
function meta:GetDoorPartner()
|
||||
local owner = self:GetOwner() or self.ixDoorOwner
|
||||
|
||||
if (IsValid(owner) and owner:IsDoor()) then
|
||||
return owner
|
||||
end
|
||||
|
||||
for _, v in ipairs(ents.FindByClass("prop_door_rotating")) do
|
||||
if (v:GetOwner() == self) then
|
||||
self.ixDoorOwner = v
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
953
gamemodes/helix/gamemode/core/meta/sh_inventory.lua
Normal file
953
gamemodes/helix/gamemode/core/meta/sh_inventory.lua
Normal file
@@ -0,0 +1,953 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Holds items within a grid layout.
|
||||
|
||||
Inventories are an object that contains `Item`s in a grid layout. Every `Character` will have exactly one inventory attached to
|
||||
it, which is the only inventory that is allowed to hold bags - any item that has its own inventory (i.e a suitcase). Inventories
|
||||
can be owned by a character, or it can be individually interacted with as a standalone object. For example, the container plugin
|
||||
attaches inventories to props, allowing for items to be stored outside of any character inventories and remain "in the world".
|
||||
|
||||
|
||||
You may be looking for the following common functions:
|
||||
|
||||
`Add` Which adds an item to the inventory.
|
||||
|
||||
`GetItems` Which gets all of the items inside the inventory.
|
||||
|
||||
`GetItemByID` Which gets an item in the inventory by it's item ID.
|
||||
|
||||
`GetItemAt` Which gets an item in the inventory by it's x and y
|
||||
|
||||
`GetID` Which gets the inventory's ID.
|
||||
|
||||
`HasItem` Which checks if the inventory has an item.
|
||||
]]
|
||||
-- @classmod Inventory
|
||||
|
||||
local META = ix.meta.inventory or ix.middleclass("ix_inventory")
|
||||
|
||||
META.slots = META.slots or {}
|
||||
META.w = META.w or 4
|
||||
META.h = META.h or 4
|
||||
META.vars = META.vars or {}
|
||||
META.receivers = META.receivers or {}
|
||||
|
||||
--- Returns a string representation of this inventory
|
||||
-- @realm shared
|
||||
-- @treturn string String representation
|
||||
-- @usage print(ix.item.inventories[1])
|
||||
-- > "inventory[1]"
|
||||
function META:__tostring()
|
||||
return "inventory["..(self.id or 0).."]"
|
||||
end
|
||||
|
||||
--- Initializes the inventory with the provided arguments.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @number id The `Inventory`'s database ID.
|
||||
-- @number width The inventory's width.
|
||||
-- @number height The inventory's height.
|
||||
function META:Initialize(id, width, height)
|
||||
self.id = id
|
||||
self.w = width
|
||||
self.h = height
|
||||
|
||||
self.slots = {}
|
||||
self.vars = {}
|
||||
self.receivers = {}
|
||||
end
|
||||
|
||||
--- Returns this inventory's database ID. This is guaranteed to be unique.
|
||||
-- @realm shared
|
||||
-- @treturn number Unique ID of inventory
|
||||
function META:GetID()
|
||||
return self.id or 0
|
||||
end
|
||||
|
||||
--- Sets the grid size of this inventory.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @number width New width of inventory
|
||||
-- @number height New height of inventory
|
||||
function META:SetSize(width, height)
|
||||
self.w = width
|
||||
self.h = height
|
||||
end
|
||||
|
||||
--- Returns the grid size of this inventory.
|
||||
-- @realm shared
|
||||
-- @treturn number Width of inventory
|
||||
-- @treturn number Height of inventory
|
||||
function META:GetSize()
|
||||
return self.w, self.h
|
||||
end
|
||||
|
||||
-- this is pretty good to debug/develop function to use.
|
||||
function META:Print(printPos)
|
||||
for k, v in pairs(self:GetItems()) do
|
||||
local str = k .. ": " .. v.name
|
||||
|
||||
if (printPos) then
|
||||
str = str .. " (" .. v.gridX .. ", " .. v.gridY .. ")"
|
||||
end
|
||||
|
||||
print(str)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Searches the inventory to find any stacked items.
|
||||
-- A common problem with developing, is that items will sometimes error out, or get corrupt.
|
||||
-- Sometimes, the server knows things you don't while developing live
|
||||
-- This function can be helpful for getting rid of those pesky errors.
|
||||
-- @realm shared
|
||||
function META:FindError()
|
||||
for _, v in pairs(self:GetItems()) do
|
||||
if (v.width == 1 and v.height == 1) then
|
||||
continue
|
||||
end
|
||||
|
||||
print("Finding error: " .. v.name)
|
||||
print("Item Position: " .. v.gridX, v.gridY)
|
||||
|
||||
for x = v.gridX, v.gridX + v.width - 1 do
|
||||
for y = v.gridY, v.gridY + v.height - 1 do
|
||||
local item = self.slots[x][y]
|
||||
|
||||
if (item and item.id != v.id) then
|
||||
print("Error Found: ".. item.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Prints out the id, width, height, slots and each item in each slot of an `Inventory`, used for debugging.
|
||||
-- @realm shared
|
||||
function META:PrintAll()
|
||||
print("------------------------")
|
||||
print("INVID", self:GetID())
|
||||
print("INVSIZE", self:GetSize())
|
||||
|
||||
if (self.slots) then
|
||||
for x = 1, self.w do
|
||||
for y = 1, self.h do
|
||||
local item = self.slots[x] and self.slots[x][y]
|
||||
if (item and item.id) then
|
||||
print(item.name .. "(" .. item.id .. ")", x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print("INVVARS")
|
||||
PrintTable(self.vars or {})
|
||||
print("------------------------")
|
||||
end
|
||||
|
||||
--- Returns the player that owns this inventory.
|
||||
-- @realm shared
|
||||
-- @treturn[1] Player Owning player
|
||||
-- @treturn[2] nil If no connected player owns this inventory
|
||||
function META:GetOwner()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetCharacter() and v:GetCharacter().id == self.owner) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the player that owns this inventory.
|
||||
-- @realm shared
|
||||
-- @player owner The player to take control over the inventory.
|
||||
-- @bool fullUpdate Whether or not to update the inventory immediately to the new owner.
|
||||
function META:SetOwner(owner, fullUpdate)
|
||||
if (type(owner) == "Player" and owner:GetNetVar("char")) then
|
||||
owner = owner:GetNetVar("char")
|
||||
else
|
||||
owner = tonumber(owner) or 0 // previously if owner variable wasn't a number, function ended, which caused bags to have and owner all the time
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
if (fullUpdate) then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (v:GetNetVar("char") == owner) then
|
||||
self:Sync(v, true)
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local query = mysql:Update("ix_inventories")
|
||||
query:Update("character_id", owner)
|
||||
query:Where("inventory_id", self:GetID())
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
self.owner = owner
|
||||
end
|
||||
|
||||
--- Checks whether a player has access to an inventory
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @player client Player to check access for
|
||||
-- @treturn bool Whether or not the player has access to the inventory
|
||||
function META:OnCheckAccess(client)
|
||||
local bAccess = false
|
||||
|
||||
for _, v in ipairs(self:GetReceivers()) do
|
||||
if (v == client) then
|
||||
bAccess = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return bAccess
|
||||
end
|
||||
|
||||
--- Checks whether or not an `Item` can fit into the `Inventory` starting from `x` and `y`.
|
||||
-- Internally used by FindEmptySlot, in most cases you are better off using that.
|
||||
-- This function will search if all of the slots within `x + width` and `y + width` are empty,
|
||||
-- ignoring any space the `Item` itself already occupies.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
-- @number x The beginning x coordinate to search for.
|
||||
-- @number y The beginning y coordiate to search for.
|
||||
-- @number w The `Item`'s width.
|
||||
-- @number h The `Item`'s height.
|
||||
-- @item[opt=nil] item2 An `Item`, if any, to ignore when searching.
|
||||
function META:CanItemFit(x, y, w, h, item2)
|
||||
local canFit = true
|
||||
|
||||
for x2 = 0, w - 1 do
|
||||
for y2 = 0, h - 1 do
|
||||
local item = (self.slots[x + x2] or {})[y + y2]
|
||||
|
||||
if ((x + x2) > self.w or item) then
|
||||
if (item2) then
|
||||
if (item and item.id == item2.id) then
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
canFit = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!canFit) then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return canFit
|
||||
end
|
||||
|
||||
|
||||
--- Returns the amount of slots currently filled in the Inventory.
|
||||
-- @realm shared
|
||||
-- @treturn number The amount of slots currently filled.
|
||||
function META:GetFilledSlotCount()
|
||||
local count = 0
|
||||
|
||||
for x = 1, self.w do
|
||||
for y = 1, self.h do
|
||||
if ((self.slots[x] or {})[y]) then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
--- Finds an empty slot of a specified width and height.
|
||||
-- In most cases, to check if an `Item` can actually fit in the `Inventory`,
|
||||
-- as if it can't, it will just return `nil`.
|
||||
--
|
||||
-- FindEmptySlot will loop through all the slots for you, as opposed to `CanItemFit`
|
||||
-- which you specify an `x` and `y` for.
|
||||
-- this will call CanItemFit anyway.
|
||||
-- If you need to check if an item will fit *exactly* at a position, you want CanItemFit instead.
|
||||
-- @realm shared
|
||||
-- @number w The width of the `Item` you are trying to fit.
|
||||
-- @number h The height of the `Item` you are trying to fit.
|
||||
-- @bool onlyMain Whether or not to search any bags connected to this `Inventory`
|
||||
-- @treturn[1] number x The `x` coordinate that the `Item` can fit into.
|
||||
-- @treturn[1] number y The `y` coordinate that the `Item` can fit into.
|
||||
-- @treturn[2] number x The `x` coordinate that the `Item` can fit into.
|
||||
-- @treturn[2] number y The `y` coordinate that the `Item` can fit into.
|
||||
-- @treturn[2] Inventory bagInv If the item was in a bag, it will return the inventory it was in.
|
||||
-- @see CanItemFit
|
||||
function META:FindEmptySlot(w, h, onlyMain)
|
||||
w = w or 1
|
||||
h = h or 1
|
||||
|
||||
if (w > self.w or h > self.h) then
|
||||
return
|
||||
end
|
||||
|
||||
for y = 1, self.h - (h - 1) do
|
||||
for x = 1, self.w - (w - 1) do
|
||||
if (self:CanItemFit(x, y, w, h)) then
|
||||
return x, y
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (onlyMain != true) then
|
||||
local bags = self:GetBags()
|
||||
|
||||
if (#bags > 0) then
|
||||
for _, invID in ipairs(bags) do
|
||||
local bagInv = ix.item.inventories[invID]
|
||||
|
||||
if (bagInv) then
|
||||
local x, y = bagInv:FindEmptySlot(w, h)
|
||||
|
||||
if (x and y) then
|
||||
return x, y, bagInv
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the item that currently exists within `x` and `y` in the `Inventory`.
|
||||
-- Items that have a width or height greater than 0 occupy more than 1 x and y.
|
||||
-- @realm shared
|
||||
-- @number x The `x` coordindate to search in.
|
||||
-- @number y The `y` coordinate to search in.
|
||||
-- @treturn number x The `x` coordinate that the `Item` is located at.
|
||||
-- @treturn number y The `y` coordinate that the `Item` is located at.
|
||||
function META:GetItemAt(x, y)
|
||||
if (self.slots and self.slots[x]) then
|
||||
return self.slots[x][y]
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes an item from the inventory.
|
||||
-- @realm shared
|
||||
-- @number id The item instance ID to remove
|
||||
-- @bool[opt=false] bNoReplication Whether or not the item's removal should not be replicated
|
||||
-- @bool[opt=false] bNoDelete Whether or not the item should not be fully deleted
|
||||
-- @bool[opt=false] bTransferring Whether or not the item is being transferred to another inventory
|
||||
-- @treturn number The X position that the item was removed from
|
||||
-- @treturn number The Y position that the item was removed from
|
||||
function META:Remove(id, bNoReplication, bNoDelete, bTransferring)
|
||||
local x2, y2
|
||||
|
||||
for x = 1, self.w do
|
||||
if (self.slots[x]) then
|
||||
for y = 1, self.h do
|
||||
local item = self.slots[x][y]
|
||||
|
||||
if (item and item.id == id) then
|
||||
self.slots[x][y] = nil
|
||||
|
||||
x2 = x2 or x
|
||||
y2 = y2 or y
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (SERVER and !bNoReplication) then
|
||||
local receivers = self:GetReceivers()
|
||||
|
||||
if (istable(receivers)) then
|
||||
net.Start("ixInventoryRemove")
|
||||
net.WriteUInt(id, 32)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.Send(receivers)
|
||||
end
|
||||
|
||||
-- we aren't removing the item - we're transferring it to another inventory
|
||||
if (!bTransferring) then
|
||||
hook.Run("InventoryItemRemoved", self, ix.item.instances[id])
|
||||
end
|
||||
|
||||
if (!bNoDelete) then
|
||||
local item = ix.item.instances[id]
|
||||
|
||||
if (item and item.OnRemoved) then
|
||||
item:OnRemoved()
|
||||
end
|
||||
|
||||
local query = mysql:Delete("ix_items")
|
||||
query:Where("item_id", id)
|
||||
query:Execute()
|
||||
|
||||
ix.item.instances[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return x2, y2
|
||||
end
|
||||
|
||||
--- Adds a player as a receiver on this `Inventory`
|
||||
-- Receivers are players who will be networked the items inside the inventory.
|
||||
--
|
||||
-- Calling this will *not* automatically sync it's current contents to the client.
|
||||
-- All future contents will be synced, but not anything that was not synced before this is called.
|
||||
--
|
||||
-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error.
|
||||
-- @realm shared
|
||||
-- @player client The player to add as a receiver.
|
||||
function META:AddReceiver(client)
|
||||
self.receivers[client] = true
|
||||
end
|
||||
|
||||
--- The opposite of `AddReceiver`.
|
||||
-- This function does not check the validity of `client`, therefore if `client` doesn't exist, it will error.
|
||||
-- @realm shared
|
||||
-- @player client The player to remove from the receiver list.
|
||||
function META:RemoveReceiver(client)
|
||||
self.receivers[client] = nil
|
||||
end
|
||||
|
||||
--- Get all of the receivers this `Inventory` has.
|
||||
-- Receivers are players who will be networked the items inside the inventory.
|
||||
--
|
||||
-- This function will automatically sort out invalid players for you.
|
||||
-- @realm shared
|
||||
-- @treturn table result The players who are on the server and allowed to see this table.
|
||||
function META:GetReceivers()
|
||||
local result = {}
|
||||
|
||||
if (self.receivers) then
|
||||
for k, _ in pairs(self.receivers) do
|
||||
if (IsValid(k) and k:IsPlayer()) then
|
||||
result[#result + 1] = k
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Returns a count of a *specific* `Item`s in the `Inventory`
|
||||
-- @realm shared
|
||||
-- @string uniqueID The Unique ID of the item.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn number The amount of `Item`s this inventory has.
|
||||
-- @usage local curHighest, winner = 0, false
|
||||
-- for client, character in ix.util.GetCharacters() do
|
||||
-- local itemCount = character:GetInventory():GetItemCount('water', false)
|
||||
-- if itemCount > curHighest then
|
||||
-- curHighest = itemCount
|
||||
-- winner = character
|
||||
-- end
|
||||
-- end
|
||||
-- -- Finds the thirstiest character on the server and returns their Character ID or false if no character has water.
|
||||
function META:GetItemCount(uniqueID, onlyMain)
|
||||
local i = 0
|
||||
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
return i
|
||||
end
|
||||
|
||||
--- Returns a table of all `Item`s in the `Inventory` by their Unique ID.
|
||||
-- Not to be confused with `GetItemsByID` or `GetItemByID` which take in an Item Instance's ID instead.
|
||||
-- @realm shared
|
||||
-- @string uniqueID The Unique ID of the item.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn number The table of specified `Item`s this inventory has.
|
||||
function META:GetItemsByUniqueID(uniqueID, onlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.uniqueID == uniqueID) then
|
||||
items[#items + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
--- Returns a table of `Item`s by their base.
|
||||
-- @realm shared
|
||||
-- @string baseID The base to search for.
|
||||
-- @bool bOnlyMain Whether or not to exclude bags that are present from the search.
|
||||
function META:GetItemsByBase(baseID, bOnlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self:GetItems(bOnlyMain)) do
|
||||
if (v.base == baseID) then
|
||||
items[#items + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
--- Get an item by it's specific Database ID.
|
||||
-- @realm shared
|
||||
-- @number id The ID to search for.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn item The item if it exists.
|
||||
function META:GetItemByID(id, onlyMain)
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.id == id) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Get a table of `Item`s by their specific Database ID.
|
||||
-- It's important to note that while in 99% of cases,
|
||||
-- items will have a unique Database ID, developers or random GMod weirdness could
|
||||
-- cause a second item with the same ID to appear, even though, `ix.item.instances` will only store one of those.
|
||||
-- The inventory only stores a reference to the `ix.item.instance` ID, not the memory reference itself.
|
||||
-- @realm shared
|
||||
-- @number id The ID to search for.
|
||||
-- @bool onlyMain Whether or not to exclude bags that are present from the search.
|
||||
-- @treturn item The item if it exists.
|
||||
function META:GetItemsByID(id, onlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self:GetItems(onlyMain)) do
|
||||
if (v.id == id) then
|
||||
items[#items + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
-- This function may pretty heavy.
|
||||
|
||||
--- Returns a table of all the items that an `Inventory` has.
|
||||
-- @realm shared
|
||||
-- @bool onlyMain Whether or not to exclude bags from this search.
|
||||
-- @treturn table The items this `Inventory` has.
|
||||
function META:GetItems(onlyMain)
|
||||
local items = {}
|
||||
|
||||
for _, v in pairs(self.slots) do
|
||||
for _, v2 in pairs(v) do
|
||||
if (istable(v2) and !items[v2.id]) then
|
||||
items[v2.id] = v2
|
||||
|
||||
v2.data = v2.data or {}
|
||||
local isBag = v2.data.id
|
||||
if (isBag and isBag != self:GetID() and onlyMain != true) then
|
||||
local bagInv = ix.item.inventories[isBag]
|
||||
|
||||
if (bagInv) then
|
||||
local bagItems = bagInv:GetItems()
|
||||
|
||||
table.Merge(items, bagItems)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
-- This function may pretty heavy.
|
||||
--- Returns a table of all the items that an `Inventory` has.
|
||||
-- @realm shared
|
||||
-- @bool onlyMain Whether or not to exclude bags from this search.
|
||||
-- @treturn table The items this `Inventory` has.
|
||||
function META:GetBags()
|
||||
local invs = {}
|
||||
for _, v in pairs(self.slots) do
|
||||
for _, v2 in pairs(v) do
|
||||
if (istable(v2) and v2.data) then
|
||||
local isBag = (((v2.base == "base_bags") or v2.isBag) and v2.data.id)
|
||||
|
||||
if (!table.HasValue(invs, isBag)) then
|
||||
if (isBag and isBag != self:GetID()) then
|
||||
invs[#invs + 1] = isBag
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return invs
|
||||
end
|
||||
--- Returns the item with the given unique ID (e.g `"handheld_radio"`) if it exists in this inventory.
|
||||
-- This method checks both
|
||||
-- this inventory, and any bags that this inventory has inside of it.
|
||||
-- @realm shared
|
||||
-- @string targetID Unique ID of the item to look for
|
||||
-- @tab[opt] data Item data to check for
|
||||
-- @treturn[1] Item Item that belongs to this inventory with the given criteria
|
||||
-- @treturn[2] bool `false` if the item does not exist
|
||||
-- @see HasItems
|
||||
-- @see HasItemOfBase
|
||||
-- @usage local item = inventory:HasItem("handheld_radio")
|
||||
--
|
||||
-- if (item) then
|
||||
-- -- do something with the item table
|
||||
-- end
|
||||
function META:HasItem(targetID, data)
|
||||
local items = self:GetItems()
|
||||
|
||||
for _, v in pairs(items) do
|
||||
if (v.uniqueID == targetID) then
|
||||
if (data) then
|
||||
local itemData = v.data
|
||||
local bFound = true
|
||||
|
||||
for dataKey, dataVal in pairs(data) do
|
||||
if (itemData[dataKey] != dataVal) then
|
||||
bFound = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!bFound) then
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Checks whether or not the `Inventory` has a table of items.
|
||||
-- This function takes a table with **no** keys and runs in order of first item > last item,
|
||||
--this is due to the usage of the `#` operator in the function.
|
||||
--
|
||||
-- @realm shared
|
||||
-- @tab targetIDs A table of `Item` Unique ID's.
|
||||
-- @treturn[1] bool true Whether or not the `Inventory` has all of the items.
|
||||
-- @treturn[1] table targetIDs Your provided targetIDs table, but it will be empty.
|
||||
-- @treturn[2] bool false
|
||||
-- @treturn[2] table targetIDs Table consisting of the items the `Inventory` did **not** have.
|
||||
-- @usage local itemFilter = {'water', 'water_sparkling'}
|
||||
-- if not Entity(1):GetCharacter():GetInventory():HasItems(itemFilter) then return end
|
||||
-- -- Filters out if this player has both a water, and a sparkling water.
|
||||
function META:HasItems(targetIDs)
|
||||
local items = self:GetItems()
|
||||
local count = #targetIDs -- assuming array
|
||||
targetIDs = table.Copy(targetIDs)
|
||||
|
||||
for _, v in pairs(items) do
|
||||
for k, targetID in ipairs(targetIDs) do
|
||||
if (v.uniqueID == targetID) then
|
||||
table.remove(targetIDs, k)
|
||||
count = count - 1
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return count <= 0, targetIDs
|
||||
end
|
||||
|
||||
--- Whether or not an `Inventory` has an item of a base, optionally with specified data.
|
||||
-- This function has an optional `data` argument, which will take a `table`.
|
||||
-- it will match if the data of the item is correct or not.
|
||||
--
|
||||
-- Items which are a base will automatically have base_ prefixed to their Unique ID, if you are having
|
||||
-- trouble finding your base, that is probably why.
|
||||
-- @realm shared
|
||||
-- @string baseID The Item Base's Unique ID.
|
||||
-- @tab[opt] data The Item's data to compare against.
|
||||
-- @treturn[1] item The first `Item` of `baseID` that is found and there is no `data` argument or `data` was matched.
|
||||
-- @treturn[2] false If no `Item`s of `baseID` is found or the `data` argument, if specified didn't match.
|
||||
-- @usage local bHasWeaponEquipped = Entity(1):GetCharacter():GetInventory():HasItemOfBase('base_weapons', {['equip'] = true})
|
||||
-- if bHasWeaponEquipped then
|
||||
-- Entity(1):Notify('One gun is fun, two guns is Woo-tastic.')
|
||||
-- end
|
||||
-- -- Notifies the player that they should get some more guns.
|
||||
function META:HasItemOfBase(baseID, data)
|
||||
local items = self:GetItems()
|
||||
|
||||
for _, v in pairs(items) do
|
||||
if (v.base == baseID) then
|
||||
if (data) then
|
||||
local itemData = v.data
|
||||
local bFound = true
|
||||
|
||||
for dataKey, dataVal in pairs(data) do
|
||||
if (itemData[dataKey] != dataVal) then
|
||||
bFound = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if (!bFound) then
|
||||
continue
|
||||
end
|
||||
end
|
||||
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Sends a specific slot to a character.
|
||||
-- This will *not* send all of the slots of the `Item` to the character, items can occupy multiple slots.
|
||||
--
|
||||
-- This will call `OnSendData` on the Item using all of the `Inventory`'s receivers.
|
||||
--
|
||||
-- This function should *not* be used to sync an entire inventory, if you need to do that, use `AddReceiver` and `Sync`.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
-- @number x The Inventory x position to send.
|
||||
-- @number y The Inventory y position to send.
|
||||
-- @item[opt] item The item to send, if any.
|
||||
-- @see AddReceiver
|
||||
-- @see Sync
|
||||
function META:SendSlot(x, y, item)
|
||||
local receivers = self:GetReceivers()
|
||||
local sendData = item and item.data and !table.IsEmpty(item.data) and item.data or {}
|
||||
|
||||
net.Start("ixInventorySet")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(x, 6)
|
||||
net.WriteUInt(y, 6)
|
||||
net.WriteString(item and item.uniqueID or "")
|
||||
net.WriteUInt(item and item.id or 0, 32)
|
||||
net.WriteUInt(self.owner or 0, 32)
|
||||
net.WriteTable(sendData)
|
||||
net.Send(receivers)
|
||||
|
||||
if (item) then
|
||||
for _, v in pairs(receivers) do
|
||||
item:Call("OnSendData", v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets whether or not an `Inventory` should save.
|
||||
-- This will prevent an `Inventory` from updating in the Database, if the inventory is already saved,
|
||||
-- it will not be deleted when unloaded.
|
||||
-- @realm server
|
||||
-- @bool bNoSave Whether or not the Inventory should save.
|
||||
function META:SetShouldSave(bNoSave)
|
||||
self.noSave = bNoSave
|
||||
end
|
||||
|
||||
--- Gets whether or not an `Inventory` should save.
|
||||
-- Inventories that are marked to not save will not update in the Database, if they inventory is already saved,
|
||||
-- it will not be deleted when unloaded.
|
||||
-- @realm server
|
||||
-- @treturn[1] bool Returns the field `noSave`.
|
||||
-- @treturn[2] bool Returns true if the field `noSave` is not registered to this inventory.
|
||||
function META:GetShouldSave()
|
||||
return self.noSave or true
|
||||
end
|
||||
|
||||
--- Add an item to the inventory.
|
||||
-- @realm server
|
||||
-- @param uniqueID The item unique ID (e.g `"handheld_radio"`) or instance ID (e.g `1024`) to add to the inventory
|
||||
-- @number[opt=1] quantity The quantity of the item to add
|
||||
-- @tab data Item data to add to the item
|
||||
-- @number[opt=nil] x The X position for the item
|
||||
-- @number[opt=nil] y The Y position for the item
|
||||
-- @bool[opt=false] noReplication Whether or not the item's addition should not be replicated
|
||||
-- @treturn[1] bool Whether the add was successful or not
|
||||
-- @treturn[1] string The error, if applicable
|
||||
-- @treturn[2] number The X position that the item was added to
|
||||
-- @treturn[2] number The Y position that the item was added to
|
||||
-- @treturn[2] number The inventory ID that the item was added to
|
||||
function META:Add(uniqueID, quantity, data, x, y, noReplication)
|
||||
quantity = quantity or 1
|
||||
|
||||
if (quantity < 1) then
|
||||
return false, "noOwner"
|
||||
end
|
||||
|
||||
if (!isnumber(uniqueID) and quantity > 1) then
|
||||
for _ = 1, quantity do
|
||||
local bSuccess, error = self:Add(uniqueID, 1, data)
|
||||
|
||||
if (!bSuccess) then
|
||||
return false, error
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local client = self.GetOwner and self:GetOwner() or nil
|
||||
local item = isnumber(uniqueID) and ix.item.instances[uniqueID] or ix.item.list[uniqueID]
|
||||
local targetInv = self
|
||||
local bagInv
|
||||
|
||||
if (!item) then
|
||||
return false, "invalidItem"
|
||||
end
|
||||
|
||||
if (isnumber(uniqueID)) then
|
||||
local oldInvID = item.invID
|
||||
|
||||
if (!x and !y) then
|
||||
x, y, bagInv = self:FindEmptySlot(item.width, item.height)
|
||||
end
|
||||
|
||||
if (bagInv) then
|
||||
targetInv = bagInv
|
||||
end
|
||||
|
||||
-- we need to check for owner since the item instance already exists
|
||||
if (!item.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter() and
|
||||
item:GetPlayerID() == client:SteamID64() and item:GetCharacterID() != client:GetCharacter():GetID()) then
|
||||
return false, "itemOwned"
|
||||
end
|
||||
|
||||
if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv, x, y) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
if (x and y) then
|
||||
targetInv.slots[x] = targetInv.slots[x] or {}
|
||||
targetInv.slots[x][y] = true
|
||||
|
||||
item.gridX = x
|
||||
item.gridY = y
|
||||
item.invID = targetInv:GetID()
|
||||
|
||||
for x2 = 0, item.width - 1 do
|
||||
local index = x + x2
|
||||
|
||||
for y2 = 0, item.height - 1 do
|
||||
targetInv.slots[index] = targetInv.slots[index] or {}
|
||||
targetInv.slots[index][y + y2] = item
|
||||
end
|
||||
end
|
||||
|
||||
if (!noReplication) then
|
||||
targetInv:SendSlot(x, y, item)
|
||||
end
|
||||
|
||||
if (!self.noSave) then
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("inventory_id", targetInv:GetID())
|
||||
query:Update("x", x)
|
||||
query:Update("y", y)
|
||||
query:Where("item_id", item.id)
|
||||
query:Execute()
|
||||
end
|
||||
|
||||
hook.Run("InventoryItemAdded", ix.item.inventories[oldInvID], targetInv, item)
|
||||
|
||||
return x, y, targetInv:GetID()
|
||||
else
|
||||
return false, "noFit"
|
||||
end
|
||||
else
|
||||
if (!x and !y) then
|
||||
x, y, bagInv = self:FindEmptySlot(item.width, item.height)
|
||||
end
|
||||
|
||||
if (bagInv) then
|
||||
targetInv = bagInv
|
||||
end
|
||||
|
||||
if (hook.Run("CanTransferItem", item, ix.item.inventories[0], targetInv, x, y) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
if (x and y) then
|
||||
for x2 = 0, item.width - 1 do
|
||||
local index = x + x2
|
||||
|
||||
for y2 = 0, item.height - 1 do
|
||||
targetInv.slots[index] = targetInv.slots[index] or {}
|
||||
targetInv.slots[index][y + y2] = true
|
||||
end
|
||||
end
|
||||
|
||||
local characterID
|
||||
local playerID
|
||||
|
||||
if (self.owner) then
|
||||
local character = ix.char.loaded[self.owner]
|
||||
|
||||
if (character) then
|
||||
characterID = character.id
|
||||
playerID = character.steamID
|
||||
end
|
||||
end
|
||||
|
||||
ix.item.Instance(targetInv:GetID(), uniqueID, data, x, y, function(newItem)
|
||||
newItem.gridX = x
|
||||
newItem.gridY = y
|
||||
|
||||
for x2 = 0, newItem.width - 1 do
|
||||
local index = x + x2
|
||||
|
||||
for y2 = 0, newItem.height - 1 do
|
||||
targetInv.slots[index] = targetInv.slots[index] or {}
|
||||
targetInv.slots[index][y + y2] = newItem
|
||||
end
|
||||
end
|
||||
|
||||
if (!noReplication) then
|
||||
targetInv:SendSlot(x, y, newItem)
|
||||
end
|
||||
|
||||
hook.Run("InventoryItemAdded", nil, targetInv, newItem)
|
||||
end, characterID, playerID)
|
||||
|
||||
return x, y, targetInv:GetID()
|
||||
else
|
||||
return false, "noFit"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Syncs the `Inventory` to the receiver.
|
||||
-- This will call Item.OnSendData on every item in the `Inventory`.
|
||||
-- @realm server
|
||||
-- @player receiver The player to
|
||||
function META:Sync(receiver)
|
||||
local slots = {}
|
||||
|
||||
for x, items in pairs(self.slots) do
|
||||
for y, item in pairs(items) do
|
||||
if (istable(item) and item.gridX == x and item.gridY == y) then
|
||||
slots[#slots + 1] = {x, y, item.uniqueID, item.id, item.data}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
net.Start("ixInventorySync")
|
||||
net.WriteTable(slots)
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteUInt(self.w, 6)
|
||||
net.WriteUInt(self.h, 6)
|
||||
net.WriteType(self.owner)
|
||||
net.WriteTable(self.vars or {})
|
||||
net.Send(receiver)
|
||||
|
||||
for _, v in pairs(self:GetItems()) do
|
||||
v:Call("OnSendData", receiver)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.meta.inventory = META
|
||||
709
gamemodes/helix/gamemode/core/meta/sh_item.lua
Normal file
709
gamemodes/helix/gamemode/core/meta/sh_item.lua
Normal file
@@ -0,0 +1,709 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Interactable entities that can be held in inventories.
|
||||
|
||||
Items are objects that are contained inside of an `Inventory`, or as standalone entities if they are dropped in the world. They
|
||||
usually have functionality that provides more gameplay aspects to the schema. For example, the zipties in the HL2 RP schema
|
||||
allow a player to tie up and search a player.
|
||||
|
||||
For an item to have an actual presence, they need to be instanced (usually with `ix.item.Instance`). Items describe the
|
||||
properties, while instances are a clone of these properties that can have their own unique data (e.g an ID card will have the
|
||||
same name but different numerical IDs). You can think of items as the class, while instances are objects of the `Item` class.
|
||||
|
||||
## Creating item classes (`ItemStructure`)
|
||||
Item classes are defined in their own file inside of your schema or plugin's `items/` folder. In these item class files you
|
||||
specify how instances of the item behave. This includes default values for basic things like the item's name and description,
|
||||
to more advanced things by overriding extra methods from an item base. See `ItemStructure` for information on how to define
|
||||
a basic item class.
|
||||
|
||||
Item classes in this folder are automatically loaded by Helix when the server starts up.
|
||||
|
||||
## Item bases
|
||||
If many items share the same functionality (i.e a can of soda and a bottle of water can both be consumed), then you might want
|
||||
to consider using an item base to reduce the amount of duplication for these items. Item bases are defined the same way as
|
||||
regular item classes, but they are placed in the `items/base/` folder in your schema or plugin. For example, a `consumables`
|
||||
base would be in `items/base/sh_consumables.lua`.
|
||||
|
||||
Any items that you want to use this base must be placed in a subfolder that has the name of the base you want that item to use.
|
||||
For example, for a bottled water item to use the consumable base, it must be placed in `items/consumables/sh_bottled_water.lua`.
|
||||
This also means that you cannot place items into subfolders as you wish, since the framework will try to use an item base that
|
||||
doesn't exist.
|
||||
|
||||
The default item bases that come with Helix are:
|
||||
|
||||
- `ammo` - provides ammo to any items with the `weapons` base
|
||||
- `bags` - holds an inventory that other items can be stored inside of
|
||||
- `outfit` - changes the appearance of the player that wears it
|
||||
- `pacoutfit` - changes the appearance of the player that wears it using PAC3
|
||||
- `weapons` - makes any SWEP into an item that can be equipped
|
||||
|
||||
These item bases usually come with extra values and methods that you can define/override in order to change their functionality.
|
||||
You should take a look at the source code for these bases to see their capabilities.
|
||||
|
||||
## Item functions (`ItemFunctionStructure`)
|
||||
Requiring players to interact with items in order for them to do something is quite common. As such, there is already a built-in
|
||||
mechanism to allow players to right-click items and show a list of available options. Item functions are defined in your item
|
||||
class file in the `ITEM.functions` table. See `ItemFunctionStructure` on how to define them.
|
||||
|
||||
Helix comes with `drop`, `take`, and `combine` item functions by default that allows items to be dropped from a player's
|
||||
inventory, picked up from the world, and combining items together. These can be overridden by defining an item function
|
||||
in your item class file with the same name. See the `bags` base for example usage of the `combine` item function.
|
||||
|
||||
## Item icons (`ItemIconStructure`)
|
||||
Icons for items sometimes don't line up quite right, in which case you can modify an item's `iconCam` value and line up the
|
||||
rendered model as needed. See `ItemIconStructure` for more details.
|
||||
]]
|
||||
-- @classmod Item
|
||||
|
||||
--[[--
|
||||
All item functions live inside of an item's `functions` table. An item function entry includes a few methods and fields you can
|
||||
use to customize the functionality and appearance of the item function. An example item function is below:
|
||||
|
||||
-- this item function's unique ID is "MyFunction"
|
||||
ITEM.functions.MyFunction = {
|
||||
name = "myFunctionPhrase", -- uses the "myFunctionPhrase" language phrase when displaying in the UI
|
||||
tip = "myFunctionDescription", -- uses the "myFunctionDescription" language phrase when displaying in the UI
|
||||
icon = "icon16/add.png", -- path to the icon material
|
||||
OnRun = function(item)
|
||||
local client = item.player
|
||||
local entity = item.entity -- only set if this is function is being ran while the item is in the world
|
||||
|
||||
if (IsValid(client)) then
|
||||
client:ChatPrint("This is a test.")
|
||||
|
||||
if (IsValid(entity)) then
|
||||
client:ChatPrint(entity:GetName())
|
||||
end
|
||||
end
|
||||
|
||||
-- do not remove this item from the owning player's inventory
|
||||
return false
|
||||
end,
|
||||
OnCanRun = function(item)
|
||||
-- only allow admins to run this item function
|
||||
local client = item.player
|
||||
return IsValid(client) and client:IsAdmin()
|
||||
end
|
||||
}
|
||||
]]
|
||||
-- @table ItemFunctionStructure
|
||||
-- @realm shared
|
||||
-- @field[type=string,opt] name Language phrase to use when displaying this item function's name in the UI. If not specified,
|
||||
-- then it will use the unique ID of the item function
|
||||
-- @field[type=string,opt] tip Language phrase to use when displaying this item function's detailed description in the UI
|
||||
-- @field[type=string,opt] icon Path to the material to use when displaying this item function's icon
|
||||
-- @field[type=function] OnRun Function to call when the item function is ran. This function is **ONLY** ran on the server.
|
||||
--
|
||||
-- The only argument passed into this function is the instance of the item being called. The instance will have its `player`
|
||||
-- field set if the item function is being ran by a player (which it should be most of the time). It will also have its `entity`
|
||||
-- field set if the item function is being ran while the item is in the world, and not in a player's inventory.
|
||||
--
|
||||
-- The item will be removed after the item function is ran. If you want to prevent this behaviour, then you can return `false`
|
||||
-- in this function. See the example above.
|
||||
-- @field[type=function] OnCanRun Function to call when checking whether or not this item function can be ran. This function is
|
||||
-- ran **BOTH** on the client and server.
|
||||
--
|
||||
-- The arguments are the same as `OnCanRun`, and the `player` and `entity` fields will be set on the item instance accordingly.
|
||||
-- Returning `true` will allow the item function to be ran. Returning `false` will prevent it from running and additionally
|
||||
-- hide it from the UI. See the example above.
|
||||
-- @field[type=function,opt] OnClick This function is called when the player clicks on this item function's entry in the UI.
|
||||
-- This function is ran **ONLY** on the client, and is only ran if `OnCanRun` succeeds.
|
||||
--
|
||||
-- The same arguments from `OnCanRun` and `OnRun` apply to this function.
|
||||
|
||||
--[[--
|
||||
Changing the way an item's icon is rendered is done by modifying the location and angle of the model, as well as the FOV of the
|
||||
camera. You can tweak the values in code, or use the `ix_dev_icon` console command to visually position the model and camera. An
|
||||
example entry for an item's icon is below:
|
||||
|
||||
ITEM.iconCam = {
|
||||
pos = Vector(0, 0, 60),
|
||||
ang = Angle(90, 0, 0),
|
||||
fov = 45
|
||||
}
|
||||
|
||||
Note that this will probably not work for your item's specific model, since every model has a different size, origin, etc. All
|
||||
item icons need to be tweaked individually.
|
||||
]]
|
||||
-- @table ItemIconStructure
|
||||
-- @realm client
|
||||
-- @field[type=vector] pos Location of the model relative to the camera. +X is forward, +Z is up
|
||||
-- @field[type=angle] ang Angle of the model
|
||||
-- @field[type=number] fov FOV of the camera
|
||||
|
||||
--[[--
|
||||
When creating an item class, the file will have a global table `ITEM` set that you use to define the item's values/methods. An
|
||||
example item class is below:
|
||||
|
||||
`items/sh_brick.lua`
|
||||
ITEM.name = "Brick"
|
||||
ITEM.description = "A brick. Pretty self-explanatory. You can eat it but you'll probably lose some teeth."
|
||||
ITEM.model = Model("models/props_debris/concrete_cynderblock001.mdl")
|
||||
ITEM.width = 1
|
||||
ITEM.height = 1
|
||||
ITEM.price = 25
|
||||
|
||||
Note that the below list only includes the default fields available for *all* items, and not special ones defined in custom
|
||||
item bases.
|
||||
]]
|
||||
-- @table ItemStructure
|
||||
-- @realm shared
|
||||
-- @field[type=string] name Display name of the item
|
||||
-- @field[type=string] description Detailed description of the item
|
||||
-- @field[type=string] model Model to use for the item's icon and when it's dropped in the world
|
||||
-- @field[type=number,opt=1] width Width of the item in grid cells
|
||||
-- @field[type=number,opt=1] height Height of the item in grid cells
|
||||
-- @field[type=number,opt=0] price How much money it costs to purchase this item in the business menu
|
||||
-- @field[type=string,opt] category Name of the category this item belongs to - mainly used for the business menu
|
||||
-- @field[type=boolean,opt=false] noBusiness Whether or not to disallow purchasing this item in the business menu
|
||||
-- @field[type=table,opt] factions List of factions allowed to purchase this item in the business menu
|
||||
-- @field[type=table,opt] classes List of character classes allowed to purchase this item in the business menu. Classes are
|
||||
-- checked after factions, so the character must also be in an allowed faction
|
||||
-- @field[type=string,opt] flag List of flags (as a string - e.g `"a"` or `"abc"`) allowed to purchase this item in the
|
||||
-- business menu. Flags are checked last, so the character must also be in an allowed faction and class
|
||||
-- @field[type=ItemIconStructure,opt] iconCam How to render this item's icon
|
||||
-- @field[type=table,opt] functions List of all item functions that this item has. See `ItemFunctionStructure` on how to define
|
||||
-- new item functions
|
||||
|
||||
local ITEM = ix.meta.item or {}
|
||||
ITEM.__index = ITEM
|
||||
ITEM.name = "Undefined"
|
||||
ITEM.description = ITEM.description or "An item that is undefined."
|
||||
ITEM.id = ITEM.id or 0
|
||||
ITEM.uniqueID = "undefined"
|
||||
|
||||
--- Returns a string representation of this item.
|
||||
-- @realm shared
|
||||
-- @treturn string String representation
|
||||
-- @usage print(ix.item.instances[1])
|
||||
-- > "item[1]"
|
||||
function ITEM:__tostring()
|
||||
return "item["..self.uniqueID.."]["..self.id.."]"
|
||||
end
|
||||
|
||||
--- Returns true if this item is equal to another item. Internally, this checks item IDs.
|
||||
-- @realm shared
|
||||
-- @item other Item to compare to
|
||||
-- @treturn bool Whether or not this item is equal to the given item
|
||||
-- @usage print(ix.item.instances[1] == ix.item.instances[2])
|
||||
-- > false
|
||||
function ITEM:__eq(other)
|
||||
return self:GetID() == other:GetID()
|
||||
end
|
||||
|
||||
--- Returns this item's database ID. This is guaranteed to be unique.
|
||||
-- @realm shared
|
||||
-- @treturn number Unique ID of item
|
||||
function ITEM:GetID()
|
||||
return self.id
|
||||
end
|
||||
|
||||
--- Returns the name of the item.
|
||||
-- @realm shared
|
||||
-- @treturn string The name of the item
|
||||
function ITEM:GetName()
|
||||
return (CLIENT and L(self.name) or self.name)
|
||||
end
|
||||
|
||||
--- Returns the description of the item.
|
||||
-- @realm shared
|
||||
-- @treturn string The description of the item
|
||||
function ITEM:GetDescription()
|
||||
if (!self.description) then return "ERROR" end
|
||||
|
||||
return L(self.description or "noDesc")
|
||||
end
|
||||
|
||||
--- Returns the model of the item.
|
||||
-- @realm shared
|
||||
-- @treturn string The model of the item
|
||||
function ITEM:GetModel()
|
||||
return self.model
|
||||
end
|
||||
|
||||
--- Returns the skin of the item.
|
||||
-- @realm shared
|
||||
-- @treturn number The skin of the item
|
||||
function ITEM:GetSkin()
|
||||
return self.skin or 0
|
||||
end
|
||||
|
||||
--- Returns the bodygroup of the item model.
|
||||
-- @realm shared
|
||||
-- @return The body groups to set. Each single-digit number in the string represents a separate bodygroup.
|
||||
|
||||
function ITEM:GetModelBodygroups()
|
||||
return self.modelBodygroups or ""
|
||||
end
|
||||
|
||||
function ITEM:GetMaterial()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Returns the ID of the owning character, if one exists.
|
||||
-- @realm shared
|
||||
-- @treturn number The owning character's ID
|
||||
function ITEM:GetCharacterID()
|
||||
return self.characterID
|
||||
end
|
||||
|
||||
--- Returns the SteamID64 of the owning player, if one exists.
|
||||
-- @realm shared
|
||||
-- @treturn number The owning player's SteamID64
|
||||
function ITEM:GetPlayerID()
|
||||
return self.playerID
|
||||
end
|
||||
|
||||
--- A utility function which prints the item's details.
|
||||
-- @realm shared
|
||||
-- @bool[opt=false] detail Whether additional detail should be printed or not(Owner, X position, Y position)
|
||||
function ITEM:Print(detail)
|
||||
if (detail == true) then
|
||||
print(Format("%s[%s]: >> [%s](%s,%s)", self.uniqueID, self.id, self.owner, self.gridX, self.gridY))
|
||||
else
|
||||
print(Format("%s[%s]", self.uniqueID, self.id))
|
||||
end
|
||||
end
|
||||
|
||||
--- A utility function printing the item's stored data.
|
||||
-- @realm shared
|
||||
function ITEM:PrintData()
|
||||
self:Print(true)
|
||||
print("ITEM DATA:")
|
||||
for k, v in pairs(self.data) do
|
||||
print(Format("[%s] = %s", k, v))
|
||||
end
|
||||
end
|
||||
|
||||
--- Calls one of the item's methods.
|
||||
-- @realm shared
|
||||
-- @string method The method to be called
|
||||
-- @player client The client to pass when calling the method, if applicable
|
||||
-- @entity entity The eneity to pass when calling the method, if applicable
|
||||
-- @param ... Arguments to pass to the method
|
||||
-- @return The values returned by the method
|
||||
function ITEM:Call(method, client, entity, ...)
|
||||
local oldPlayer, oldEntity = self.player, self.entity
|
||||
|
||||
self.player = client or self.player
|
||||
self.entity = entity or self.entity
|
||||
|
||||
if (isfunction(self[method])) then
|
||||
local results = {self[method](self, ...)}
|
||||
|
||||
self.player = nil
|
||||
self.entity = nil
|
||||
|
||||
return unpack(results)
|
||||
end
|
||||
|
||||
self.player = oldPlayer
|
||||
self.entity = oldEntity
|
||||
end
|
||||
|
||||
--- Returns the player that owns this item.
|
||||
-- @realm shared
|
||||
-- @treturn player Player owning this item
|
||||
function ITEM:GetOwner()
|
||||
local inventory = ix.item.inventories[self.invID]
|
||||
|
||||
if (inventory) then
|
||||
return inventory.GetOwner and inventory:GetOwner()
|
||||
end
|
||||
|
||||
local id = self:GetID()
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
local character = v:GetCharacter()
|
||||
|
||||
if (character and character:GetInventory():GetItemByID(id)) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets a key within the item's data.
|
||||
-- @realm shared
|
||||
-- @string key The key to store the value within
|
||||
-- @param[opt=nil] value The value to store within the key
|
||||
-- @tab[opt=nil] receivers The players to replicate the data on
|
||||
-- @bool[opt=false] noSave Whether to disable saving the data on the database or not
|
||||
-- @bool[opt=false] noCheckEntity Whether to disable setting the data on the entity, if applicable
|
||||
function ITEM:SetData(key, value, receivers, noSave, noCheckEntity)
|
||||
self.data = self.data or {}
|
||||
self.data[key] = value
|
||||
|
||||
if (SERVER) then
|
||||
if (!noCheckEntity) then
|
||||
local ent = self:GetEntity()
|
||||
|
||||
if (IsValid(ent)) then
|
||||
local data = ent:GetNetVar("data", {})
|
||||
data[key] = value
|
||||
|
||||
ent:SetNetVar("data", data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (receivers != false and (receivers or self:GetOwner())) then
|
||||
net.Start("ixInventoryData")
|
||||
net.WriteUInt(self:GetID(), 32)
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Send(receivers or self:GetOwner())
|
||||
end
|
||||
|
||||
if (!noSave and ix.db) then
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("data", util.TableToJSON(self.data))
|
||||
query:Where("item_id", self:GetID())
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the value stored on a key within the item's data.
|
||||
-- @realm shared
|
||||
-- @string key The key in which the value is stored
|
||||
-- @param[opt=nil] default The value to return in case there is no value stored in the key
|
||||
-- @return The value stored within the key
|
||||
function ITEM:GetData(key, default)
|
||||
self.data = self.data or {}
|
||||
|
||||
if (self.data) then
|
||||
if (key == true) then
|
||||
return self.data
|
||||
end
|
||||
|
||||
local value = self.data[key]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
elseif (IsValid(self.entity)) then
|
||||
local data = self.entity:GetNetVar("data", {})
|
||||
value = data[key]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
end
|
||||
end
|
||||
else
|
||||
self.data = {}
|
||||
end
|
||||
|
||||
if (default != nil) then
|
||||
return default
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
--- Changes the function called on specific events for the item.
|
||||
-- @realm shared
|
||||
-- @string name The name of the hook
|
||||
-- @func func The function to call once the event occurs
|
||||
function ITEM:Hook(name, func)
|
||||
if (name) then
|
||||
self.hooks[name] = func
|
||||
end
|
||||
end
|
||||
|
||||
--- Changes the function called after hooks for specific events for the item.
|
||||
-- @realm shared
|
||||
-- @string name The name of the hook
|
||||
-- @func func The function to call after the original hook was called
|
||||
function ITEM:PostHook(name, func)
|
||||
if (name) then
|
||||
self.postHooks[name] = func
|
||||
end
|
||||
end
|
||||
|
||||
--- Removes the item.
|
||||
-- @realm shared
|
||||
-- @bool bNoReplication Whether or not the item's removal should not be replicated.
|
||||
-- @bool bNoDelete Whether or not the item should not be fully deleted
|
||||
-- @treturn bool Whether the item was successfully deleted or not
|
||||
function ITEM:Remove(bNoReplication, bNoDelete)
|
||||
local inv = ix.item.inventories[self.invID]
|
||||
|
||||
if (self.invID > 0 and inv) then
|
||||
local failed = false
|
||||
|
||||
for x = self.gridX, self.gridX + (self.width - 1) do
|
||||
if (inv.slots[x]) then
|
||||
for y = self.gridY, self.gridY + (self.height - 1) do
|
||||
local item = inv.slots[x][y]
|
||||
|
||||
if (item and item.id == self.id) then
|
||||
inv.slots[x][y] = nil
|
||||
else
|
||||
failed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (failed) then
|
||||
local items = inv:GetItems()
|
||||
|
||||
inv.slots = {}
|
||||
for _, v in pairs(items) do
|
||||
if (v.invID == inv:GetID()) then
|
||||
for x = self.gridX, self.gridX + (self.width - 1) do
|
||||
for y = self.gridY, self.gridY + (self.height - 1) do
|
||||
inv.slots[x][y] = v.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (IsValid(inv.owner) and inv.owner:IsPlayer()) then
|
||||
inv:Sync(inv.owner, true)
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
else
|
||||
-- @todo definition probably isn't needed
|
||||
inv = ix.item.inventories[self.invID]
|
||||
|
||||
if (inv) then
|
||||
ix.item.inventories[self.invID][self.id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
if (SERVER and !bNoReplication) then
|
||||
local entity = self:GetEntity()
|
||||
|
||||
if (IsValid(entity)) then
|
||||
entity:Remove()
|
||||
end
|
||||
|
||||
local receivers = inv.GetReceivers and inv:GetReceivers()
|
||||
|
||||
if (self.invID != 0 and istable(receivers)) then
|
||||
net.Start("ixInventoryRemove")
|
||||
net.WriteUInt(self.id, 32)
|
||||
net.WriteUInt(self.invID, 32)
|
||||
net.Send(receivers)
|
||||
end
|
||||
|
||||
if (!bNoDelete) then
|
||||
local item = ix.item.instances[self.id]
|
||||
|
||||
if (item and item.OnRemoved) then
|
||||
item:OnRemoved()
|
||||
end
|
||||
|
||||
local query = mysql:Delete("ix_items")
|
||||
query:Where("item_id", self.id)
|
||||
query:Execute()
|
||||
|
||||
ix.item.instances[self.id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
--- Returns the item's entity.
|
||||
-- @realm server
|
||||
-- @treturn entity The entity of the item
|
||||
function ITEM:GetEntity()
|
||||
local id = self:GetID()
|
||||
|
||||
for _, v in ipairs(ents.FindByClass("ix_item")) do
|
||||
if (v.ixItemID == id) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Spawn an item entity based off the item table.
|
||||
-- @realm server
|
||||
-- @param[type=vector] position The position in which the item's entity will be spawned
|
||||
-- @param[type=angle] angles The angles at which the item's entity will spawn
|
||||
-- @treturn entity The spawned entity
|
||||
function ITEM:Spawn(position, angles)
|
||||
-- Check if the item has been created before.
|
||||
if (ix.item.instances[self.id]) then
|
||||
local client
|
||||
|
||||
-- Spawn the actual item entity.
|
||||
local entity = ents.Create("ix_item")
|
||||
entity:Spawn()
|
||||
entity:SetAngles(angles or Angle(0, 0, 0))
|
||||
entity:SetItem(self.id)
|
||||
|
||||
-- If the first argument is a player, then we will find a position to drop
|
||||
-- the item based off their aim.
|
||||
if (type(position) == "Player") then
|
||||
client = position
|
||||
position = position:GetItemDropPos(entity)
|
||||
end
|
||||
|
||||
entity:SetPos(position)
|
||||
|
||||
if (IsValid(client)) then
|
||||
entity.ixSteamID = client:SteamID()
|
||||
entity.ixCharID = client:GetCharacter():GetID()
|
||||
entity:SetNetVar("owner", entity.ixCharID)
|
||||
end
|
||||
|
||||
hook.Run("OnItemSpawned", entity)
|
||||
return entity
|
||||
end
|
||||
end
|
||||
|
||||
--- Transfers an item to a specific inventory.
|
||||
-- @realm server
|
||||
-- @number invID The inventory to transfer the item to
|
||||
-- @number x The X position to which the item should be transferred on the new inventory
|
||||
-- @number y The Y position to which the item should be transferred on the new inventory
|
||||
-- @player client The player to which the item is being transferred
|
||||
-- @bool noReplication Whether there should be no replication of the transferral
|
||||
-- @bool isLogical Whether or not an entity should spawn if the item is transferred to the world
|
||||
-- @treturn[1] bool Whether the transfer was successful or not
|
||||
-- @treturn[1] string The error, if applicable
|
||||
function ITEM:Transfer(invID, x, y, client, noReplication, isLogical)
|
||||
invID = invID or 0
|
||||
|
||||
if (self.invID == invID) then
|
||||
return false, "same inv"
|
||||
end
|
||||
|
||||
local inventory = ix.item.inventories[invID]
|
||||
local curInv = ix.item.inventories[self.invID or 0]
|
||||
|
||||
if (curInv and !IsValid(client)) then
|
||||
client = curInv.GetOwner and curInv:GetOwner() or nil
|
||||
end
|
||||
|
||||
-- check if this item doesn't belong to another one of this player's characters
|
||||
local itemPlayerID = self:GetPlayerID()
|
||||
local itemCharacterID = self:GetCharacterID()
|
||||
|
||||
if (!self.bAllowMultiCharacterInteraction and IsValid(client) and client:GetCharacter()) then
|
||||
local playerID = client:SteamID64()
|
||||
local characterID = client:GetCharacter():GetID()
|
||||
|
||||
if (itemPlayerID and itemCharacterID) then
|
||||
if (itemPlayerID == playerID and itemCharacterID != characterID) then
|
||||
return false, "itemOwned"
|
||||
end
|
||||
else
|
||||
self.characterID = characterID
|
||||
self.playerID = playerID
|
||||
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("character_id", characterID)
|
||||
query:Update("player_id", playerID)
|
||||
query:Where("item_id", self:GetID())
|
||||
query:Execute()
|
||||
end
|
||||
end
|
||||
|
||||
if (hook.Run("CanTransferItem", self, curInv, inventory, x, y) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
local authorized = false
|
||||
|
||||
if (inventory and inventory.OnAuthorizeTransfer and inventory:OnAuthorizeTransfer(client, curInv, self)) then
|
||||
authorized = true
|
||||
end
|
||||
|
||||
if (!authorized and self.CanTransfer and self:CanTransfer(curInv, inventory) == false) then
|
||||
return false, "notAllowed"
|
||||
end
|
||||
|
||||
if (curInv) then
|
||||
if (invID and invID > 0 and inventory) then
|
||||
local targetInv = inventory
|
||||
local bagInv
|
||||
|
||||
if (!x and !y) then
|
||||
x, y, bagInv = inventory:FindEmptySlot(self.width, self.height)
|
||||
end
|
||||
|
||||
if (bagInv) then
|
||||
targetInv = bagInv
|
||||
end
|
||||
|
||||
if (!x or !y) then
|
||||
return false, "noFit"
|
||||
end
|
||||
|
||||
local prevID = self.invID
|
||||
local status, result = targetInv:Add(self.id, nil, nil, x, y, noReplication)
|
||||
|
||||
if (status) then
|
||||
if (self.invID > 0 and prevID != 0) then
|
||||
-- we are transferring this item from one inventory to another
|
||||
curInv:Remove(self.id, false, true, true)
|
||||
|
||||
if (self.OnTransferred) then
|
||||
self:OnTransferred(curInv, inventory)
|
||||
end
|
||||
|
||||
hook.Run("OnItemTransferred", self, curInv, inventory)
|
||||
return true
|
||||
elseif (self.invID > 0 and prevID == 0) then
|
||||
-- we are transferring this item from the world to an inventory
|
||||
ix.item.inventories[0][self.id] = nil
|
||||
|
||||
if (self.OnTransferred) then
|
||||
self:OnTransferred(curInv, inventory)
|
||||
end
|
||||
|
||||
hook.Run("OnItemTransferred", self, curInv, inventory)
|
||||
return true
|
||||
end
|
||||
else
|
||||
return false, result
|
||||
end
|
||||
elseif (IsValid(client)) then
|
||||
-- we are transferring this item from an inventory to the world
|
||||
self.invID = 0
|
||||
curInv:Remove(self.id, false, true)
|
||||
|
||||
local query = mysql:Update("ix_items")
|
||||
query:Update("inventory_id", 0)
|
||||
query:Where("item_id", self.id)
|
||||
query:Execute()
|
||||
|
||||
inventory = ix.item.inventories[0]
|
||||
inventory[self:GetID()] = self
|
||||
|
||||
if (self.OnTransferred) then
|
||||
self:OnTransferred(curInv, inventory)
|
||||
end
|
||||
|
||||
hook.Run("OnItemTransferred", self, curInv, inventory)
|
||||
|
||||
if (!isLogical) then
|
||||
return self:Spawn(client)
|
||||
end
|
||||
|
||||
return true
|
||||
else
|
||||
return false, "noOwner"
|
||||
end
|
||||
else
|
||||
return false, "invalidInventory"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.meta.item = ITEM
|
||||
690
gamemodes/helix/gamemode/core/meta/sh_player.lua
Normal file
690
gamemodes/helix/gamemode/core/meta/sh_player.lua
Normal file
@@ -0,0 +1,690 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--[[--
|
||||
Physical representation of connected player.
|
||||
|
||||
`Player`s are a type of `Entity`. They are a physical representation of a `Character` - and can possess at most one `Character`
|
||||
object at a time that you can interface with.
|
||||
|
||||
See the [Garry's Mod Wiki](https://wiki.garrysmod.com/page/Category:Player) for all other methods that the `Player` class has.
|
||||
]]
|
||||
-- @classmod Player
|
||||
|
||||
local meta = FindMetaTable("Player")
|
||||
|
||||
if (SERVER) then
|
||||
--- Returns the amount of time the player has played on the server.
|
||||
-- @realm shared
|
||||
-- @treturn number Number of seconds the player has played on the server
|
||||
function meta:GetPlayTime()
|
||||
return self.ixPlayTime + (RealTime() - (self.ixJoinTime or RealTime()))
|
||||
end
|
||||
else
|
||||
ix.playTime = ix.playTime or 0
|
||||
|
||||
function meta:GetPlayTime()
|
||||
return ix.playTime + (RealTime() - ix.joinTime or 0)
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns `true` if the player has their weapon raised.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player has their weapon raised
|
||||
function meta:IsWepRaised()
|
||||
return self:GetNetVar("raised", false)
|
||||
end
|
||||
|
||||
--- Returns `true` if the player is restricted - that is to say that they are considered "bound" and cannot interact with
|
||||
-- objects normally (e.g hold weapons, use items, etc). An example of this would be a player in handcuffs.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player is restricted
|
||||
function meta:IsRestricted()
|
||||
return self:GetNetVar("restricted", false)
|
||||
end
|
||||
|
||||
--- Returns `true` if the player is able to shoot their weapon.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player can shoot their weapon
|
||||
function meta:CanShootWeapon()
|
||||
return self:GetNetVar("canShoot", true)
|
||||
end
|
||||
|
||||
local vectorLength2D = FindMetaTable("Vector").Length2D
|
||||
|
||||
--- Returns `true` if the player is running. Running in this case means that their current speed is greater than their
|
||||
-- regularly set walk speed.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player is running
|
||||
function meta:IsRunning()
|
||||
return vectorLength2D(self:GetVelocity()) > (self:GetWalkSpeed() + 10)
|
||||
end
|
||||
|
||||
--- Returns `true` if the player currently has a female model. This checks if the model has `female`, `alyx` or `mossman` in its
|
||||
-- name, or if the player's model class is `citizen_female`.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not the player has a female model
|
||||
function meta:IsFemale()
|
||||
local model = self:GetModel():lower()
|
||||
|
||||
return (model:find("female") or model:find("alyx") or model:find("mossman")) != nil or
|
||||
ix.anim.GetModelClass(model) == "citizen_female"
|
||||
end
|
||||
|
||||
--- Whether or not this player is stuck and cannot move.
|
||||
-- @realm shared
|
||||
-- @treturn bool Whether or not this player is stuck
|
||||
function meta:IsStuck()
|
||||
return util.TraceEntity({
|
||||
start = self:GetPos(),
|
||||
endpos = self:GetPos(),
|
||||
filter = self
|
||||
}, self).StartSolid
|
||||
end
|
||||
|
||||
--- Returns a good position in front of the player for an entity to be placed. This is usually used for item entities.
|
||||
-- @realm shared
|
||||
-- @entity entity Entity to get a position for
|
||||
-- @treturn vector Best guess for a good drop position in front of the player
|
||||
-- @usage local position = client:GetItemDropPos(entity)
|
||||
-- entity:SetPos(position)
|
||||
function meta:GetItemDropPos(entity)
|
||||
local data = {}
|
||||
local trace
|
||||
|
||||
data.start = self:GetShootPos()
|
||||
data.endpos = self:GetShootPos() + self:GetAimVector() * 86
|
||||
data.filter = self
|
||||
|
||||
if (IsValid(entity)) then
|
||||
-- use a hull trace if there's a valid entity to avoid collisions
|
||||
local mins, maxs = entity:GetRotatedAABB(entity:OBBMins(), entity:OBBMaxs())
|
||||
|
||||
data.mins = mins
|
||||
data.maxs = maxs
|
||||
data.filter = {entity, self}
|
||||
trace = util.TraceHull(data)
|
||||
else
|
||||
-- trace along the normal for a few units so we can attempt to avoid a collision
|
||||
trace = util.TraceLine(data)
|
||||
|
||||
data.start = trace.HitPos
|
||||
data.endpos = data.start + trace.HitNormal * 48
|
||||
trace = util.TraceLine(data)
|
||||
end
|
||||
|
||||
return trace.HitPos
|
||||
end
|
||||
|
||||
--- Performs a time-delay action that requires this player to look at an entity. If this player looks away from the entity
|
||||
-- before the action timer completes, the action is cancelled. This is usually used in conjunction with `SetAction` to display
|
||||
-- progress to the player.
|
||||
-- @realm shared
|
||||
-- @entity entity that this player must look at
|
||||
-- @func callback Function to call when the timer completes
|
||||
-- @number time How much time in seconds this player must look at the entity for
|
||||
-- @func[opt=nil] onCancel Function to call when the timer has been cancelled
|
||||
-- @number[opt=96] distance Maximum distance a player can move away from the entity before the action is cancelled
|
||||
-- @see SetAction
|
||||
-- @usage client:SetAction("Searching...", 4) -- for displaying the progress bar
|
||||
-- client:DoStaredAction(entity, function()
|
||||
-- print("hello!")
|
||||
-- end)
|
||||
-- -- prints "hello!" after looking at the entity for 4 seconds
|
||||
function meta:DoStaredAction(entity, callback, time, onCancel, distance)
|
||||
local uniqueID = "ixStare"..self:SteamID64()
|
||||
local data = {}
|
||||
data.filter = self
|
||||
|
||||
timer.Create(uniqueID, 0.1, time / 0.1, function()
|
||||
if (IsValid(self) and IsValid(entity)) then
|
||||
data.start = self:GetShootPos()
|
||||
data.endpos = data.start + self:GetAimVector()*(distance or 96)
|
||||
|
||||
if (util.TraceLine(data).Entity != entity) then
|
||||
timer.Remove(uniqueID)
|
||||
|
||||
if (onCancel) then
|
||||
onCancel()
|
||||
end
|
||||
elseif (callback and timer.RepsLeft(uniqueID) == 0) then
|
||||
callback()
|
||||
end
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
|
||||
if (onCancel) then
|
||||
onCancel()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Resets all bodygroups this player's model has to their defaults (`0`).
|
||||
-- @realm shared
|
||||
function meta:ResetBodygroups()
|
||||
for i = 0, (self:GetNumBodyGroups() - 1) do
|
||||
self:SetBodygroup(i, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function meta:GetFactionVar(variable, default)
|
||||
if (!self:GetCharacter()) then return end
|
||||
|
||||
local faction = ix.faction.Get(self:Team())
|
||||
if (!faction) then return end
|
||||
|
||||
return faction[variable] or default
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixActionBar")
|
||||
util.AddNetworkString("ixActionBarReset")
|
||||
util.AddNetworkString("ixStringRequest")
|
||||
util.AddNetworkString("ixConfirmationRequest")
|
||||
|
||||
--- Sets whether or not this player's current weapon is raised.
|
||||
-- @realm server
|
||||
-- @bool bState Whether or not the raise the weapon
|
||||
-- @entity[opt=GetActiveWeapon()] weapon Weapon to raise or lower. You should pass this argument if you already have a
|
||||
-- reference to this player's current weapon to avoid an expensive lookup for this player's current weapon.
|
||||
function meta:SetWepRaised(bState, weapon)
|
||||
weapon = weapon or self:GetActiveWeapon()
|
||||
|
||||
if (IsValid(weapon)) then
|
||||
local bCanShoot = !bState and weapon.FireWhenLowered or bState
|
||||
self:SetNetVar("raised", bState)
|
||||
|
||||
if (bCanShoot) then
|
||||
-- delay shooting while the raise animation is playing
|
||||
timer.Create("ixWeaponRaise" .. self:SteamID64(), 1, 1, function()
|
||||
if (IsValid(self)) then
|
||||
self:SetNetVar("canShoot", true)
|
||||
end
|
||||
end)
|
||||
else
|
||||
timer.Remove("ixWeaponRaise" .. self:SteamID64())
|
||||
self:SetNetVar("canShoot", false)
|
||||
end
|
||||
else
|
||||
timer.Remove("ixWeaponRaise" .. self:SteamID64())
|
||||
self:SetNetVar("raised", false)
|
||||
self:SetNetVar("canShoot", false)
|
||||
end
|
||||
end
|
||||
|
||||
--- Inverts this player's weapon raised state. You should use `SetWepRaised` instead of this if you already have a reference
|
||||
-- to this player's current weapon.
|
||||
-- @realm server
|
||||
function meta:ToggleWepRaised()
|
||||
local weapon = self:GetActiveWeapon()
|
||||
|
||||
if (!IsValid(weapon) or
|
||||
weapon.IsAlwaysRaised or ALWAYS_RAISED[weapon:GetClass()] or
|
||||
weapon.IsAlwaysLowered or weapon.NeverRaised) then
|
||||
return
|
||||
end
|
||||
|
||||
self:SetWepRaised(!self:IsWepRaised(), weapon)
|
||||
|
||||
if (self:IsWepRaised() and weapon.OnRaised) then
|
||||
weapon:OnRaised()
|
||||
elseif (!self:IsWepRaised() and weapon.OnLowered) then
|
||||
weapon:OnLowered()
|
||||
end
|
||||
end
|
||||
|
||||
--- Performs a delayed action that requires this player to hold use on an entity. This is displayed to this player as a
|
||||
-- closing ring over their crosshair.
|
||||
-- @realm server
|
||||
-- @number time How much time in seconds this player has to hold use for
|
||||
-- @entity entity Entity that this player must be looking at
|
||||
-- @func callback Function to run when the timer completes. It will be ran right away if `time` is `0`. Returning `false` in
|
||||
-- the callback will not mark this interaction as dirty if you're managing the interaction state manually.
|
||||
function meta:PerformInteraction(time, entity, callback)
|
||||
if (!IsValid(entity) or entity.ixInteractionDirty) then
|
||||
return
|
||||
end
|
||||
|
||||
if (time > 0) then
|
||||
self.ixInteractionTarget = entity
|
||||
self.ixInteractionCharacter = self:GetCharacter():GetID()
|
||||
|
||||
timer.Create("ixCharacterInteraction" .. self:SteamID(), time, 1, function()
|
||||
if (IsValid(self) and IsValid(entity) and IsValid(self.ixInteractionTarget) and
|
||||
self.ixInteractionCharacter == self:GetCharacter():GetID()) then
|
||||
local data = {}
|
||||
data.start = self:GetShootPos()
|
||||
data.endpos = data.start + self:GetAimVector() * 96
|
||||
data.filter = self
|
||||
local traceEntity = util.TraceLine(data).Entity
|
||||
|
||||
if (IsValid(traceEntity) and traceEntity == self.ixInteractionTarget and !traceEntity.ixInteractionDirty) then
|
||||
if (callback(self) != false) then
|
||||
traceEntity.ixInteractionDirty = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
else
|
||||
if (callback(self) != false) then
|
||||
entity.ixInteractionDirty = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Displays a progress bar for this player that takes the given amount of time to complete.
|
||||
-- @realm server
|
||||
-- @string text Text to display above the progress bar
|
||||
-- @number[opt=5] time How much time in seconds to wait before the timer completes
|
||||
-- @func callback Function to run once the timer completes
|
||||
-- @number[opt=CurTime()] startTime Game time in seconds that the timer started. If you are using `time`, then you shouldn't
|
||||
-- use this argument
|
||||
-- @number[opt=startTime + time] finishTime Game time in seconds that the timer should complete at. If you are using `time`,
|
||||
-- then you shouldn't use this argument
|
||||
function meta:SetAction(text, time, callback, startTime, finishTime)
|
||||
if (time and time <= 0) then
|
||||
if (callback) then
|
||||
callback(self)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Default the time to five seconds.
|
||||
time = time or 5
|
||||
startTime = startTime or CurTime()
|
||||
finishTime = finishTime or (startTime + time)
|
||||
|
||||
if (text == false) then
|
||||
timer.Remove("ixAct"..self:SteamID64())
|
||||
|
||||
net.Start("ixActionBarReset")
|
||||
net.Send(self)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if (!text) then
|
||||
net.Start("ixActionBarReset")
|
||||
net.Send(self)
|
||||
else
|
||||
net.Start("ixActionBar")
|
||||
net.WriteFloat(startTime)
|
||||
net.WriteFloat(finishTime)
|
||||
net.WriteString(text)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
-- If we have provided a callback, run it delayed.
|
||||
if (callback) then
|
||||
-- Create a timer that runs once with a delay.
|
||||
timer.Create("ixAct"..self:SteamID64(), time, 1, function()
|
||||
-- Call the callback if the player is still valid.
|
||||
if (IsValid(self)) then
|
||||
callback(self)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
--- Opens up a text box on this player's screen for input and returns the result. Remember to sanitize the user's input if
|
||||
-- it's needed!
|
||||
-- @realm server
|
||||
-- @string title Title to display on the panel
|
||||
-- @string subTitle Subtitle to display on the panel
|
||||
-- @func callback Function to run when this player enters their input. Callback is ran with the user's input string.
|
||||
-- @string[opt=nil] default Default value to put in the text box.
|
||||
-- @usage client:RequestString("Hello", "Please enter your name", function(text)
|
||||
-- client:ChatPrint("Hello, " .. text)
|
||||
-- end)
|
||||
-- -- prints "Hello, <text>" in the player's chat
|
||||
function meta:RequestString(title, subTitle, callback, default)
|
||||
local time = math.floor(os.time())
|
||||
|
||||
self.ixStrReqs = self.ixStrReqs or {}
|
||||
self.ixStrReqs[time] = callback
|
||||
|
||||
net.Start("ixStringRequest")
|
||||
net.WriteUInt(time, 32)
|
||||
net.WriteString(title)
|
||||
net.WriteString(subTitle)
|
||||
net.WriteString(default)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
--- Opens up a confirmation box on this player's screen for input and returns the result.
|
||||
-- @realm server
|
||||
-- @string title Title to display on the panel
|
||||
-- @string subTitle Subtitle to display on the panel
|
||||
-- @func callback Function to run when this player enters their input.
|
||||
-- @usage client:RequestString("Confirm", "Are you sure you want to do this?", function(confirmation)
|
||||
-- client:ChatPrint(confirmation and "Confirmed." or "Cancelled.")
|
||||
-- end)
|
||||
-- -- prints whether the player confirmed or cancelled.
|
||||
function meta:RequestConfirmation(title, subTitle, callback)
|
||||
local time = math.floor(os.time())
|
||||
|
||||
self.ixConfReqs = self.ixConfReqs or {}
|
||||
self.ixConfReqs[time] = callback
|
||||
|
||||
net.Start("ixConfirmationRequest")
|
||||
net.WriteUInt(time, 32)
|
||||
net.WriteString(title)
|
||||
net.WriteString(subTitle)
|
||||
net.Send(self)
|
||||
end
|
||||
|
||||
--- Sets this player's restricted status.
|
||||
-- @realm server
|
||||
-- @bool bState Whether or not to restrict this player
|
||||
-- @bool bNoMessage Whether or not to suppress the restriction notification
|
||||
function meta:SetRestricted(bState, bNoMessage)
|
||||
if (bState) then
|
||||
self:SetNetVar("restricted", true)
|
||||
|
||||
if (bNoMessage) then
|
||||
self:SetLocalVar("restrictNoMsg", true)
|
||||
end
|
||||
|
||||
self.ixRestrictWeps = self.ixRestrictWeps or {}
|
||||
|
||||
for _, v in ipairs(self:GetWeapons()) do
|
||||
self.ixRestrictWeps[#self.ixRestrictWeps + 1] = v:GetClass()
|
||||
v:Remove()
|
||||
end
|
||||
|
||||
hook.Run("OnPlayerRestricted", self)
|
||||
else
|
||||
self:SetNetVar("restricted")
|
||||
|
||||
if (self:GetLocalVar("restrictNoMsg")) then
|
||||
self:SetLocalVar("restrictNoMsg")
|
||||
end
|
||||
|
||||
if (self.ixRestrictWeps) then
|
||||
for _, v in ipairs(self.ixRestrictWeps) do
|
||||
self:Give(v)
|
||||
end
|
||||
|
||||
self.ixRestrictWeps = nil
|
||||
end
|
||||
|
||||
hook.Run("OnPlayerUnRestricted", self)
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates a ragdoll entity of this player that will be synced with clients. This does **not** affect the player like
|
||||
-- `SetRagdolled` does.
|
||||
-- @realm server
|
||||
-- @bool[opt=false] bDontSetPlayer Whether or not to avoid setting the ragdoll's owning player
|
||||
-- @treturn entity Created ragdoll entity
|
||||
function meta:CreateServerRagdoll(bDontSetPlayer)
|
||||
local entity = ents.Create("prop_ragdoll")
|
||||
entity:SetPos(self:GetPos())
|
||||
entity:SetAngles(self:EyeAngles())
|
||||
entity:SetModel(self:GetModel())
|
||||
entity:SetSkin(self:GetSkin())
|
||||
|
||||
for i = 0, (self:GetNumBodyGroups() - 1) do
|
||||
entity:SetBodygroup(i, self:GetBodygroup(i))
|
||||
end
|
||||
|
||||
entity:Spawn()
|
||||
|
||||
if (!bDontSetPlayer) then
|
||||
entity:SetNetVar("player", self)
|
||||
end
|
||||
|
||||
entity:SetCollisionGroup(COLLISION_GROUP_WEAPON)
|
||||
entity:Activate()
|
||||
|
||||
local velocity = self:GetVelocity()
|
||||
|
||||
for i = 0, entity:GetPhysicsObjectCount() - 1 do
|
||||
local physObj = entity:GetPhysicsObjectNum(i)
|
||||
|
||||
if (IsValid(physObj)) then
|
||||
physObj:SetVelocity(velocity)
|
||||
|
||||
local index = entity:TranslatePhysBoneToBone(i)
|
||||
|
||||
if (index) then
|
||||
local position, angles = self:GetBonePosition(index)
|
||||
|
||||
physObj:SetPos(position)
|
||||
physObj:SetAngles(angles)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return entity
|
||||
end
|
||||
|
||||
--- Sets this player's ragdoll status.
|
||||
-- @realm server
|
||||
-- @bool bState Whether or not to ragdoll this player
|
||||
-- @number[opt=0] time How long this player should stay ragdolled for. Set to `0` if they should stay ragdolled until they
|
||||
-- get back up manually
|
||||
-- @number[opt=5] getUpGrace How much time in seconds to wait before the player is able to get back up manually. Set to
|
||||
-- the same number as `time` to disable getting up manually entirely
|
||||
function meta:SetRagdolled(bState, time, getUpGrace)
|
||||
if (!self:Alive()) then
|
||||
return
|
||||
end
|
||||
|
||||
getUpGrace = getUpGrace or time or 5
|
||||
|
||||
if (bState) then
|
||||
if (IsValid(self.ixRagdoll)) then
|
||||
self.ixRagdoll:Remove()
|
||||
end
|
||||
|
||||
local entity = self:CreateServerRagdoll()
|
||||
|
||||
entity:CallOnRemove("fixer", function()
|
||||
if (IsValid(self)) then
|
||||
self:SetLocalVar("blur", nil)
|
||||
self:SetLocalVar("ragdoll", nil)
|
||||
|
||||
if (!entity.ixNoReset) then
|
||||
self:SetPos(entity:GetPos())
|
||||
end
|
||||
|
||||
self:SetNoDraw(false)
|
||||
self:SetNotSolid(false)
|
||||
self:SetMoveType(MOVETYPE_WALK)
|
||||
self:SetLocalVelocity(IsValid(entity) and entity.ixLastVelocity or vector_origin)
|
||||
end
|
||||
|
||||
if (IsValid(self) and !entity.ixIgnoreDelete) then
|
||||
if (entity.ixWeapons) then
|
||||
local active
|
||||
for _, v in ipairs(entity.ixWeapons) do
|
||||
if (v.active) then
|
||||
active = v
|
||||
elseif (v.class) then
|
||||
local weapon = self:Give(v.class, true)
|
||||
if (IsValid(weapon)) then
|
||||
if (v.item) then
|
||||
weapon.ixItem = v.item
|
||||
end
|
||||
|
||||
self:SetAmmo(v.ammo, weapon:GetPrimaryAmmoType())
|
||||
weapon:SetClip1(v.clip)
|
||||
end
|
||||
elseif (v.item and v.invID == v.item.invID) then
|
||||
v.item:Equip(self, true, true)
|
||||
self:SetAmmo(v.ammo, self.carryWeapons[v.item.weaponCategory]:GetPrimaryAmmoType())
|
||||
end
|
||||
end
|
||||
|
||||
if (active) then
|
||||
if (active.class) then
|
||||
local weapon = self:Give(active.class, true)
|
||||
if (IsValid(weapon)) then
|
||||
if (active.item) then
|
||||
weapon.ixItem = active.item
|
||||
end
|
||||
|
||||
self:SetAmmo(active.ammo, weapon:GetPrimaryAmmoType())
|
||||
weapon:SetClip1(active.clip)
|
||||
end
|
||||
elseif (active.item and active.invID == active.item.invID) then
|
||||
active.item:Equip(self, true, true)
|
||||
self:SetAmmo(active.ammo, self.carryWeapons[active.item.weaponCategory]:GetPrimaryAmmoType())
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
if (entity.ixActiveWeapon) then
|
||||
local weapon = entity.ixActiveWeapon
|
||||
timer.Simple(1, function()
|
||||
if (self:HasWeapon(weapon)) then
|
||||
self:SetActiveWeapon(self:GetWeapon(weapon))
|
||||
else
|
||||
local weapons = self:GetWeapons()
|
||||
if (#weapons > 0) then
|
||||
self:SetActiveWeapon(weapons[1])
|
||||
end
|
||||
end
|
||||
end)
|
||||
timer.Simple(0.5, function()
|
||||
if (self:HasWeapon("ix_hands")) then
|
||||
self:SetActiveWeapon(self:GetWeapon("ix_hands"))
|
||||
end
|
||||
end)
|
||||
end
|
||||
--]]
|
||||
|
||||
if (self:IsStuck()) then
|
||||
entity:DropToFloor()
|
||||
self:SetPos(entity:GetPos() + Vector(0, 0, 16))
|
||||
|
||||
local positions = ix.util.FindEmptySpace(self, {entity, self})
|
||||
|
||||
for _, v in ipairs(positions) do
|
||||
self:SetPos(v)
|
||||
|
||||
if (!self:IsStuck()) then
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
self:SetLocalVar("blur", 25)
|
||||
self.ixRagdoll = entity
|
||||
|
||||
entity.ixWeapons = {}
|
||||
entity.ixPlayer = self
|
||||
|
||||
if (getUpGrace) then
|
||||
entity.ixGrace = CurTime() + getUpGrace
|
||||
end
|
||||
|
||||
if (time and time > 0) then
|
||||
entity.ixStart = CurTime()
|
||||
entity.ixFinish = entity.ixStart + time
|
||||
|
||||
self:SetAction("@wakingUp", nil, nil, entity.ixStart, entity.ixFinish)
|
||||
end
|
||||
|
||||
if (IsValid(self:GetActiveWeapon())) then
|
||||
entity.ixActiveWeapon = self:GetActiveWeapon():GetClass()
|
||||
end
|
||||
|
||||
for _, v in ipairs(self:GetWeapons()) do
|
||||
if (v.ixItem and v.ixItem.Equip and v.ixItem.Unequip) then
|
||||
entity.ixWeapons[#entity.ixWeapons + 1] = {
|
||||
item = v.ixItem,
|
||||
invID = v.ixItem.invID,
|
||||
ammo = self:GetAmmoCount(v:GetPrimaryAmmoType()),
|
||||
active = v:GetClass() == entity.ixActiveWeapon
|
||||
}
|
||||
v.ixItem:Unequip(self, false)
|
||||
else
|
||||
local clip = v:Clip1()
|
||||
local reserve = self:GetAmmoCount(v:GetPrimaryAmmoType())
|
||||
entity.ixWeapons[#entity.ixWeapons + 1] = {
|
||||
class = v:GetClass(),
|
||||
item = v.ixItem,
|
||||
clip = clip,
|
||||
ammo = reserve,
|
||||
active = v:GetClass() == entity.ixActiveWeapon
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
self:GodDisable()
|
||||
self:StripWeapons()
|
||||
self:SetMoveType(MOVETYPE_OBSERVER)
|
||||
self:SetNoDraw(true)
|
||||
self:SetNotSolid(true)
|
||||
|
||||
local uniqueID = "ixUnRagdoll" .. self:SteamID()
|
||||
|
||||
if (time) then
|
||||
timer.Create(uniqueID, 0.33, 0, function()
|
||||
if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then
|
||||
local velocity = entity:GetVelocity()
|
||||
entity.ixLastVelocity = velocity
|
||||
|
||||
self:SetPos(entity:GetPos())
|
||||
|
||||
if (velocity:Length2D() >= 8) then
|
||||
if (!entity.ixPausing) then
|
||||
self:SetAction()
|
||||
entity.ixPausing = true
|
||||
end
|
||||
|
||||
return
|
||||
elseif (entity.ixPausing) then
|
||||
self:SetAction("@wakingUp", time)
|
||||
entity.ixPausing = false
|
||||
end
|
||||
|
||||
time = time - 0.33
|
||||
|
||||
if (time <= 0) then
|
||||
entity:Remove()
|
||||
end
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
else
|
||||
timer.Create(uniqueID, 0.33, 0, function()
|
||||
if (IsValid(entity) and IsValid(self) and self.ixRagdoll == entity) then
|
||||
self:SetPos(entity:GetPos())
|
||||
else
|
||||
timer.Remove(uniqueID)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
self:SetLocalVar("ragdoll", entity:EntIndex())
|
||||
hook.Run("OnCharacterFallover", self, entity, true)
|
||||
elseif (IsValid(self.ixRagdoll)) then
|
||||
-- Anti-Exploit measure. Just silently fail if near a door.
|
||||
for _, entity in ipairs(ents.FindInSphere(self:GetPos(), 50)) do
|
||||
if (entity:GetClass() == "func_door" or entity:GetClass() == "func_door_rotating" or entity:GetClass() == "prop_door_rotating") then return end
|
||||
end
|
||||
|
||||
self.ixRagdoll:Remove()
|
||||
|
||||
hook.Run("OnCharacterFallover", self, nil, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
140
gamemodes/helix/gamemode/core/meta/sh_tool.lua
Normal file
140
gamemodes/helix/gamemode/core/meta/sh_tool.lua
Normal file
@@ -0,0 +1,140 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
local TOOL = ix.meta.tool or {}
|
||||
|
||||
-- code replicated from gamemodes/sandbox/entities/weapons/gmod_tool/stool.lua
|
||||
function TOOL:Create()
|
||||
local object = {}
|
||||
|
||||
setmetatable(object, self)
|
||||
self.__index = self
|
||||
|
||||
object.Mode = nil
|
||||
object.SWEP = nil
|
||||
object.Owner = nil
|
||||
object.ClientConVar = {}
|
||||
object.ServerConVar = {}
|
||||
object.Objects = {}
|
||||
object.Stage = 0
|
||||
object.Message = "start"
|
||||
object.LastMessage = 0
|
||||
object.AllowedCVar = 0
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
function TOOL:CreateConVars()
|
||||
local mode = self:GetMode()
|
||||
|
||||
if (CLIENT) then
|
||||
for cvar, default in pairs(self.ClientConVar) do
|
||||
CreateClientConVar(mode .. "_" .. cvar, default, true, true)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Note: I changed this from replicated because replicated convars don't work when they're created via Lua.
|
||||
if (SERVER) then
|
||||
self.AllowedCVar = CreateConVar("toolmode_allow_" .. mode, 1, FCVAR_NOTIFY)
|
||||
end
|
||||
end
|
||||
|
||||
function TOOL:GetServerInfo(property)
|
||||
local mode = self:GetMode()
|
||||
return GetConVarString(mode .. "_" .. property)
|
||||
end
|
||||
|
||||
function TOOL:BuildConVarList()
|
||||
local mode = self:GetMode()
|
||||
local convars = {}
|
||||
|
||||
for k, v in pairs(self.ClientConVar) do
|
||||
convars[mode .. "_" .. k] = v
|
||||
end
|
||||
|
||||
return convars
|
||||
end
|
||||
|
||||
function TOOL:GetClientInfo(property)
|
||||
return self:GetOwner():GetInfo(self:GetMode() .. "_" .. property)
|
||||
end
|
||||
|
||||
function TOOL:GetClientNumber(property, default)
|
||||
return self:GetOwner():GetInfoNum(self:GetMode() .. "_" .. property, tonumber(default) or 0)
|
||||
end
|
||||
|
||||
function TOOL:Allowed()
|
||||
if (CLIENT) then
|
||||
return true
|
||||
end
|
||||
|
||||
return self.AllowedCVar:GetBool()
|
||||
end
|
||||
|
||||
-- Now for all the TOOL redirects
|
||||
function TOOL:Init()
|
||||
end
|
||||
|
||||
function TOOL:GetMode()
|
||||
return self.Mode
|
||||
end
|
||||
|
||||
function TOOL:GetSWEP()
|
||||
return self.SWEP
|
||||
end
|
||||
|
||||
function TOOL:GetOwner()
|
||||
return self:GetSWEP().Owner or self.Owner
|
||||
end
|
||||
|
||||
function TOOL:GetWeapon()
|
||||
return self:GetSWEP().Weapon or self.Weapon
|
||||
end
|
||||
|
||||
function TOOL:LeftClick()
|
||||
return false
|
||||
end
|
||||
|
||||
function TOOL:RightClick()
|
||||
return false
|
||||
end
|
||||
|
||||
function TOOL:Reload()
|
||||
self:ClearObjects()
|
||||
end
|
||||
|
||||
function TOOL:Deploy()
|
||||
self:ReleaseGhostEntity()
|
||||
return
|
||||
end
|
||||
|
||||
function TOOL:Holster()
|
||||
self:ReleaseGhostEntity()
|
||||
return
|
||||
end
|
||||
|
||||
function TOOL:Think()
|
||||
self:ReleaseGhostEntity()
|
||||
end
|
||||
|
||||
-- Checks the objects before any action is taken
|
||||
-- This is to make sure that the entities haven't been removed
|
||||
function TOOL:CheckObjects()
|
||||
for _, v in pairs(self.Objects) do
|
||||
if (!v.Ent:IsWorld() and !v.Ent:IsValid()) then
|
||||
self:ClearObjects()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.meta.tool = TOOL
|
||||
823
gamemodes/helix/gamemode/core/sh_commands.lua
Normal file
823
gamemodes/helix/gamemode/core/sh_commands.lua
Normal file
@@ -0,0 +1,823 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
ix.command.Add("Roll", {
|
||||
description = "@cmdRoll",
|
||||
arguments = bit.bor(ix.type.number, ix.type.optional),
|
||||
OnRun = function(self, client, maximum)
|
||||
maximum = math.Clamp(maximum or 100, 0, 1000000)
|
||||
|
||||
local value = math.random(0, maximum)
|
||||
|
||||
ix.chat.Send(client, "roll", tostring(value), nil, nil, {
|
||||
max = maximum
|
||||
})
|
||||
|
||||
ix.log.Add(client, "roll", value, maximum)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("Event", {
|
||||
description = "@cmdEvent",
|
||||
arguments = ix.type.text,
|
||||
superAdminOnly = true,
|
||||
OnRun = function(self, client, text)
|
||||
ix.chat.Send(client, "event", text)
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("PM", {
|
||||
description = "@cmdPM",
|
||||
arguments = {
|
||||
ix.type.player,
|
||||
ix.type.text
|
||||
},
|
||||
OnRun = function(self, client, target, message)
|
||||
local voiceMail = target:GetData("vm")
|
||||
|
||||
if (voiceMail and voiceMail:find("%S")) then
|
||||
return target:GetName()..": "..voiceMail
|
||||
end
|
||||
|
||||
if ((client.ixNextPM or 0) < CurTime()) then
|
||||
ix.chat.Send(client, "pm", message, false, {client, target}, {target = target})
|
||||
|
||||
client.ixNextPM = CurTime() + 0.5
|
||||
target.ixLastPM = client
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("Reply", {
|
||||
description = "@cmdReply",
|
||||
arguments = ix.type.text,
|
||||
OnRun = function(self, client, message)
|
||||
local target = client.ixLastPM
|
||||
|
||||
if (IsValid(target) and (client.ixNextPM or 0) < CurTime()) then
|
||||
ix.chat.Send(client, "pm", message, false, {client, target}, {target = target})
|
||||
client.ixNextPM = CurTime() + 0.5
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("SetVoicemail", {
|
||||
description = "@cmdSetVoicemail",
|
||||
arguments = bit.bor(ix.type.text, ix.type.optional),
|
||||
OnRun = function(self, client, message)
|
||||
if (isstring(message) and message:find("%S")) then
|
||||
client:SetData("vm", message:utf8sub(1, 240))
|
||||
return "@vmSet"
|
||||
else
|
||||
client:SetData("vm")
|
||||
return "@vmRem"
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharGiveFlag", {
|
||||
description = "@cmdCharGiveFlag",
|
||||
privilege = "Manage Character Flags",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
bit.bor(ix.type.string, ix.type.optional)
|
||||
},
|
||||
OnRun = function(self, client, target, flags)
|
||||
-- show string request if no flags are specified
|
||||
if (!flags) then
|
||||
local available = ""
|
||||
|
||||
-- sort and display flags the character already has
|
||||
for k, _ in SortedPairs(ix.flag.list) do
|
||||
if (!target:HasFlags(k)) then
|
||||
available = available .. k
|
||||
end
|
||||
end
|
||||
|
||||
return client:RequestString("@flagGiveTitle", "@cmdCharGiveFlag", function(text)
|
||||
ix.command.Run(client, "CharGiveFlag", {target:GetName(), text})
|
||||
end, available)
|
||||
end
|
||||
|
||||
target:GiveFlags(flags)
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("flagGive", client:GetName(), target:GetName(), flags)
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharTakeFlag", {
|
||||
description = "@cmdCharTakeFlag",
|
||||
privilege = "Manage Character Flags",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
bit.bor(ix.type.string, ix.type.optional)
|
||||
},
|
||||
OnRun = function(self, client, target, flags)
|
||||
if (!flags) then
|
||||
return client:RequestString("@flagTakeTitle", "@cmdCharTakeFlag", function(text)
|
||||
ix.command.Run(client, "CharTakeFlag", {target:GetName(), text})
|
||||
end, target:GetFlags())
|
||||
end
|
||||
|
||||
target:TakeFlags(flags)
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("flagTake", client:GetName(), flags, target:GetName())
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("ToggleRaise", {
|
||||
description = "@cmdToggleRaise",
|
||||
OnRun = function(self, client, arguments)
|
||||
if (!timer.Exists("ixToggleRaise" .. client:SteamID())) then
|
||||
timer.Create("ixToggleRaise" .. client:SteamID(), ix.config.Get("weaponRaiseTime"), 1, function()
|
||||
client:ToggleWepRaised()
|
||||
end)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharSetModel", {
|
||||
description = "@cmdCharSetModel",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.string
|
||||
},
|
||||
OnRun = function(self, client, target, model)
|
||||
target:SetModel(model)
|
||||
target:GetPlayer():SetupHands()
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("cChangeModel", client:GetName(), target:GetName(), model)
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharSetSkin", {
|
||||
description = "@cmdCharSetSkin",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
bit.bor(ix.type.number, ix.type.optional)
|
||||
},
|
||||
OnRun = function(self, client, target, skin)
|
||||
target:SetData("skin", skin)
|
||||
target:GetPlayer():SetSkin(skin or 0)
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("cChangeSkin", client:GetName(), target:GetName(), skin or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharSetBodygroup", {
|
||||
description = "@cmdCharSetBodygroup",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.string,
|
||||
bit.bor(ix.type.number, ix.type.optional)
|
||||
},
|
||||
OnRun = function(self, client, target, bodygroup, value)
|
||||
local index = target:GetPlayer():FindBodygroupByName(bodygroup)
|
||||
|
||||
if (index > -1) then
|
||||
if (value and value < 1) then
|
||||
value = nil
|
||||
end
|
||||
|
||||
local groups = target:GetData("groups", {})
|
||||
groups[index] = value
|
||||
target:SetData("groups", groups)
|
||||
target:GetPlayer():SetBodygroup(index, value or 0)
|
||||
|
||||
ix.util.NotifyLocalized("cChangeGroups", nil, client:GetName(), target:GetName(), bodygroup, value or 0)
|
||||
else
|
||||
return "@invalidArg", 2
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharSetAttribute", {
|
||||
description = "@cmdCharSetAttribute",
|
||||
privilege = "Manage Character Attributes",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.string,
|
||||
ix.type.number
|
||||
},
|
||||
OnRun = function(self, client, target, attributeName, level)
|
||||
for k, v in pairs(ix.attributes.list) do
|
||||
if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then
|
||||
target:SetAttrib(k, math.abs(level))
|
||||
return "@attributeSet", target:GetName(), L(v.name, client), math.abs(level)
|
||||
end
|
||||
end
|
||||
|
||||
return "@attributeNotFound"
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharAddAttribute", {
|
||||
description = "@cmdCharAddAttribute",
|
||||
privilege = "Manage Character Attributes",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.string,
|
||||
ix.type.number
|
||||
},
|
||||
OnRun = function(self, client, target, attributeName, level)
|
||||
for k, v in pairs(ix.attributes.list) do
|
||||
if (ix.util.StringMatches(L(v.name, client), attributeName) or ix.util.StringMatches(k, attributeName)) then
|
||||
target:UpdateAttrib(k, math.abs(level))
|
||||
return "@attributeUpdate", target:GetName(), L(v.name, client), math.abs(level)
|
||||
end
|
||||
end
|
||||
|
||||
return "@attributeNotFound"
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharSetName", {
|
||||
description = "@cmdCharSetName",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
bit.bor(ix.type.text, ix.type.optional)
|
||||
},
|
||||
OnRun = function(self, client, target, newName)
|
||||
-- display string request panel if no name was specified
|
||||
if (newName:len() == 0) then
|
||||
return client:RequestString("@chgName", "@chgNameDesc", function(text)
|
||||
ix.command.Run(client, "CharSetName", {target:GetName(), text})
|
||||
end, target:GetName())
|
||||
end
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("cChangeName", client:GetName(), target:GetName(), newName)
|
||||
end
|
||||
end
|
||||
|
||||
target:SetName(newName:gsub("#", "#"))
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharGiveItem", {
|
||||
description = "@cmdCharGiveItem",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.string,
|
||||
bit.bor(ix.type.number, ix.type.optional)
|
||||
},
|
||||
OnRun = function(self, client, target, item, amount)
|
||||
local uniqueID = item:lower()
|
||||
|
||||
if (!ix.item.list[uniqueID]) then
|
||||
for k, v in SortedPairs(ix.item.list) do
|
||||
if (ix.util.StringMatches(v.name, uniqueID)) then
|
||||
uniqueID = k
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
amount = amount or 1
|
||||
local bSuccess, error = target:GetInventory():Add(uniqueID, amount)
|
||||
|
||||
hook.Run("PlayerGaveItem", client, target, uniqueID, amount)
|
||||
|
||||
if (bSuccess) then
|
||||
target:GetPlayer():NotifyLocalized("itemCreated")
|
||||
|
||||
if (target != client:GetCharacter()) then
|
||||
return "@itemCreated"
|
||||
end
|
||||
else
|
||||
return "@" .. tostring(error)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharKick", {
|
||||
description = "@cmdCharKick",
|
||||
adminOnly = true,
|
||||
arguments = ix.type.character,
|
||||
OnRun = function(self, client, target)
|
||||
target:Save(function()
|
||||
target:Kick()
|
||||
end)
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("charKick", client:GetName(), target:GetName())
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharBan", {
|
||||
description = "@cmdCharBan",
|
||||
privilege = "Ban Character",
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
bit.bor(ix.type.number, ix.type.optional)
|
||||
},
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client, target, minutes)
|
||||
if (minutes) then
|
||||
minutes = minutes * 60
|
||||
end
|
||||
|
||||
target:Ban(minutes)
|
||||
target:Save()
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("charBan", client:GetName(), target:GetName())
|
||||
end
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharUnban", {
|
||||
description = "@cmdCharUnban",
|
||||
privilege = "Ban Character",
|
||||
arguments = ix.type.text,
|
||||
adminOnly = true,
|
||||
OnRun = function(self, client, name)
|
||||
if ((client.ixNextSearch or 0) >= CurTime()) then
|
||||
return L("charSearching", client)
|
||||
end
|
||||
|
||||
for _, v in pairs(ix.char.loaded) do
|
||||
if (ix.util.StringMatches(v:GetName(), name)) then
|
||||
if (v:GetData("banned")) then
|
||||
v:SetData("banned")
|
||||
else
|
||||
return "@charNotBanned"
|
||||
end
|
||||
|
||||
return ix.util.NotifyLocalized("charUnBan", nil, client:GetName(), v:GetName())
|
||||
end
|
||||
end
|
||||
|
||||
client.ixNextSearch = CurTime() + 15
|
||||
|
||||
local query = mysql:Select("ix_characters")
|
||||
query:Select("id")
|
||||
query:Select("name")
|
||||
query:WhereLike("name", name)
|
||||
query:Where("schema", Schema and Schema.folder or "helix")
|
||||
query:Limit(1)
|
||||
query:Callback(function(result)
|
||||
if (istable(result) and #result > 0) then
|
||||
local dataQuery = mysql:Select("ix_characters_data")
|
||||
dataQuery:Select("data")
|
||||
dataQuery:Where("id", tonumber(result[1].id))
|
||||
dataQuery:Where("key", "data")
|
||||
dataQuery:Callback(function(dataResult)
|
||||
if (istable(dataResult) and #dataResult > 0) then
|
||||
local characterID = tonumber(result[1].id)
|
||||
local data = util.JSONToTable(dataResult[1].data or "")
|
||||
if !istable(data) then return end
|
||||
|
||||
name = result[1].name
|
||||
|
||||
client.ixNextSearch = 0
|
||||
|
||||
if (!data.banned) then
|
||||
return client:NotifyLocalized("charNotBanned")
|
||||
end
|
||||
|
||||
data.banned = nil
|
||||
|
||||
local updateQuery = mysql:Update("ix_characters_data")
|
||||
updateQuery:Update("data", util.TableToJSON(data))
|
||||
updateQuery:Where("id", characterID)
|
||||
updateQuery:Where("key", "data")
|
||||
updateQuery:Execute()
|
||||
|
||||
ix.util.NotifyLocalized("charUnBan", nil, client:GetName(), name)
|
||||
end
|
||||
end)
|
||||
dataQuery:Execute()
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
})
|
||||
|
||||
do
|
||||
hook.Add("InitializedConfig", "ixMoneyCommands", function()
|
||||
local MONEY_NAME = string.gsub(ix.util.ExpandCamelCase(ix.currency.plural), "%s", "")
|
||||
|
||||
ix.command.Add("Give" .. MONEY_NAME, {
|
||||
alias = {"GiveMoney"},
|
||||
description = "@cmdGiveMoney",
|
||||
arguments = ix.type.number,
|
||||
OnRun = function(self, client, amount)
|
||||
amount = math.floor(amount)
|
||||
|
||||
if (amount <= 0) then
|
||||
return L("invalidArg", client, 1)
|
||||
end
|
||||
|
||||
local data = {}
|
||||
data.start = client:GetShootPos()
|
||||
data.endpos = data.start + client:GetAimVector() * 96
|
||||
data.filter = client
|
||||
local target = util.TraceLine(data).Entity
|
||||
|
||||
if (IsValid(target) and target:IsPlayer() and target:GetCharacter()) then
|
||||
if (!client:GetCharacter():HasMoney(amount)) then
|
||||
return
|
||||
end
|
||||
|
||||
target:GetCharacter():GiveMoney(amount)
|
||||
client:GetCharacter():TakeMoney(amount)
|
||||
|
||||
target:NotifyLocalized("moneyTaken", ix.currency.Get(amount))
|
||||
client:NotifyLocalized("moneyGiven", ix.currency.Get(amount))
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharSet" .. MONEY_NAME, {
|
||||
alias = {"CharSetMoney"},
|
||||
description = "@cmdCharSetMoney",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.number
|
||||
},
|
||||
OnRun = function(self, client, target, amount)
|
||||
amount = math.Round(amount)
|
||||
|
||||
if (amount <= 0) then
|
||||
return "@invalidArg", 2
|
||||
end
|
||||
|
||||
target:SetMoney(amount)
|
||||
client:NotifyLocalized("setMoney", target:GetName(), ix.currency.Get(amount))
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("Drop" .. MONEY_NAME, {
|
||||
alias = {"DropMoney"},
|
||||
description = "@cmdDropMoney",
|
||||
arguments = ix.type.number,
|
||||
OnRun = function(self, client, amount)
|
||||
amount = math.Round(amount)
|
||||
|
||||
if (amount <= 0) then
|
||||
return "@invalidArg", 1
|
||||
end
|
||||
|
||||
if (!client:GetCharacter():HasMoney(amount)) then
|
||||
return "@insufficientMoney"
|
||||
end
|
||||
|
||||
client:GetCharacter():TakeMoney(amount)
|
||||
|
||||
local money = ix.currency.Spawn(client, amount)
|
||||
money.ixCharID = client:GetCharacter():GetID()
|
||||
money.ixSteamID = client:SteamID()
|
||||
end
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
ix.command.Add("PlyWhitelist", {
|
||||
description = "@cmdPlyWhitelist",
|
||||
privilege = "Manage Character Whitelist",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.player,
|
||||
ix.type.text
|
||||
},
|
||||
OnRun = function(self, client, target, name)
|
||||
if (name == "") then
|
||||
return "@invalidArg", 2
|
||||
end
|
||||
|
||||
local faction = ix.faction.teams[name]
|
||||
|
||||
if (!faction) then
|
||||
for _, v in ipairs(ix.faction.indices) do
|
||||
if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then
|
||||
faction = v
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (faction) then
|
||||
if (target:SetWhitelisted(faction.index, true)) then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target) then
|
||||
v:NotifyLocalized("whitelist", client:GetName(), target:GetName(), L(faction.name, v))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return "@invalidFaction"
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharGetUp", {
|
||||
description = "@cmdCharGetUp",
|
||||
OnRun = function(self, client, arguments)
|
||||
local entity = client.ixRagdoll
|
||||
|
||||
if (IsValid(entity) and entity.ixGrace and entity.ixGrace < CurTime() and
|
||||
entity:GetVelocity():Length2D() < 8 and !entity.ixWakingUp) then
|
||||
entity.ixWakingUp = true
|
||||
entity:CallOnRemove("CharGetUp", function()
|
||||
client:SetAction()
|
||||
end)
|
||||
|
||||
client:SetAction("@gettingUp", 5, function()
|
||||
if (!IsValid(entity)) then
|
||||
return
|
||||
end
|
||||
|
||||
hook.Run("OnCharacterGetup", client, entity)
|
||||
entity:Remove()
|
||||
end)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("PlyUnwhitelist", {
|
||||
description = "@cmdPlyUnwhitelist",
|
||||
privilege = "Manage Character Whitelist",
|
||||
superAdminOnly = true,
|
||||
arguments = {
|
||||
ix.type.string,
|
||||
ix.type.text
|
||||
},
|
||||
OnRun = function(self, client, target, name)
|
||||
local faction = ix.faction.teams[name]
|
||||
|
||||
if (!faction) then
|
||||
for _, v in ipairs(ix.faction.indices) do
|
||||
if (ix.util.StringMatches(L(v.name, client), name) or ix.util.StringMatches(v.uniqueID, name)) then
|
||||
faction = v
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (faction) then
|
||||
local targetPlayer = ix.util.FindPlayer(target)
|
||||
|
||||
if (IsValid(targetPlayer) and targetPlayer:SetWhitelisted(faction.index, false)) then
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == targetPlayer) then
|
||||
v:NotifyLocalized("unwhitelist", client:GetName(), targetPlayer:GetName(), L(faction.name, v))
|
||||
end
|
||||
end
|
||||
else
|
||||
local steamID64 = util.SteamIDTo64(target)
|
||||
local query = mysql:Select("ix_players")
|
||||
query:Select("data")
|
||||
query:Where("steamid", steamID64)
|
||||
query:Limit(1)
|
||||
query:Callback(function(result)
|
||||
if (istable(result) and #result > 0) then
|
||||
local data = util.JSONToTable(result[1].data or "[]")
|
||||
local whitelists = data.whitelists and data.whitelists[Schema.folder]
|
||||
|
||||
if (!whitelists or !whitelists[faction.uniqueID]) then
|
||||
return
|
||||
end
|
||||
|
||||
whitelists[faction.uniqueID] = nil
|
||||
|
||||
local updateQuery = mysql:Update("ix_players")
|
||||
updateQuery:Update("data", util.TableToJSON(data))
|
||||
updateQuery:Where("steamid", steamID64)
|
||||
updateQuery:Execute()
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v)) then
|
||||
v:NotifyLocalized("unwhitelist", client:GetName(), target, L(faction.name, v))
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
query:Execute()
|
||||
end
|
||||
else
|
||||
return "@invalidFaction"
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharFallOver", {
|
||||
description = "@cmdCharFallOver",
|
||||
arguments = bit.bor(ix.type.number, ix.type.optional),
|
||||
OnRun = function(self, client, time)
|
||||
if (!client:Alive() or client:GetMoveType() == MOVETYPE_NOCLIP) then
|
||||
return "@notNow"
|
||||
end
|
||||
|
||||
if (time and time > 0) then
|
||||
time = math.Clamp(time, 1, 60)
|
||||
end
|
||||
|
||||
if (!IsValid(client.ixRagdoll)) then
|
||||
client:SetRagdolled(true, time)
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("BecomeClass", {
|
||||
description = "@cmdBecomeClass",
|
||||
arguments = ix.type.text,
|
||||
OnRun = function(self, client, class)
|
||||
local character = client:GetCharacter()
|
||||
|
||||
if (character) then
|
||||
local num = isnumber(tonumber(class)) and tonumber(class) or -1
|
||||
|
||||
if (ix.class.list[num]) then
|
||||
local v = ix.class.list[num]
|
||||
|
||||
if (character:JoinClass(num)) then
|
||||
return "@becomeClass", L(v.name, client)
|
||||
else
|
||||
return "@becomeClassFail", L(v.name, client)
|
||||
end
|
||||
else
|
||||
for k, v in ipairs(ix.class.list) do
|
||||
if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(L(v.name, client), class)) then
|
||||
if (character:JoinClass(k)) then
|
||||
return "@becomeClass", L(v.name, client)
|
||||
else
|
||||
return "@becomeClassFail", L(v.name, client)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return "@invalid", L("class", client)
|
||||
else
|
||||
return "@illegalAccess"
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharDesc", {
|
||||
description = "@cmdCharDesc",
|
||||
arguments = bit.bor(ix.type.text, ix.type.optional),
|
||||
OnRun = function(self, client, description)
|
||||
if (!description:find("%S")) then
|
||||
if (hook.Run("OnCharDescRequested", client) != false) then
|
||||
return client:RequestString("@cmdCharDescTitle", "@cmdCharDescDescription", function(text)
|
||||
ix.command.Run(client, "CharDesc", {text})
|
||||
end, client:GetCharacter():GetDescription())
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local info = ix.char.vars.description
|
||||
local result, fault, count = info:OnValidate(description)
|
||||
|
||||
if (result == false) then
|
||||
return "@" .. fault, count
|
||||
end
|
||||
|
||||
client:GetCharacter():SetDescription(description)
|
||||
return "@descChanged"
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("PlyTransfer", {
|
||||
description = "@cmdPlyTransfer",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.text
|
||||
},
|
||||
OnRun = function(self, client, target, name)
|
||||
local faction = ix.faction.teams[name]
|
||||
|
||||
if (!faction) then
|
||||
for _, v in pairs(ix.faction.indices) do
|
||||
if (ix.util.StringMatches(L(v.name, client), name)) then
|
||||
faction = v
|
||||
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (faction) then
|
||||
local bHasWhitelist = target:GetPlayer():HasWhitelist(faction.index)
|
||||
|
||||
if (bHasWhitelist) then
|
||||
target.vars.faction = faction.uniqueID
|
||||
target:SetFaction(faction.index)
|
||||
|
||||
if (faction.OnTransferred) then
|
||||
faction:OnTransferred(target)
|
||||
end
|
||||
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if (self:OnCheckAccess(v) or v == target:GetPlayer()) then
|
||||
v:NotifyLocalized("cChangeFaction", client:GetName(), target:GetName(), L(faction.name, v))
|
||||
end
|
||||
end
|
||||
else
|
||||
return "@charNotWhitelisted", target:GetName(), L(faction.name, client)
|
||||
end
|
||||
else
|
||||
return "@invalidFaction"
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("CharSetClass", {
|
||||
description = "@cmdCharSetClass",
|
||||
adminOnly = true,
|
||||
arguments = {
|
||||
ix.type.character,
|
||||
ix.type.text
|
||||
},
|
||||
OnRun = function(self, client, target, class)
|
||||
local classTable
|
||||
|
||||
for _, v in ipairs(ix.class.list) do
|
||||
if (ix.util.StringMatches(v.uniqueID, class) or ix.util.StringMatches(v.name, class)) then
|
||||
classTable = v
|
||||
end
|
||||
end
|
||||
|
||||
if (classTable) then
|
||||
local oldClass = target:GetClass()
|
||||
local targetPlayer = target:GetPlayer()
|
||||
|
||||
if (targetPlayer:Team() == classTable.faction) then
|
||||
target:SetClass(classTable.index)
|
||||
hook.Run("PlayerJoinedClass", targetPlayer, classTable.index, oldClass)
|
||||
|
||||
targetPlayer:NotifyLocalized("becomeClass", L(classTable.name, targetPlayer))
|
||||
|
||||
-- only send second notification if the character isn't setting their own class
|
||||
if (client != targetPlayer) then
|
||||
return "@setClass", target:GetName(), L(classTable.name, client)
|
||||
end
|
||||
else
|
||||
return "@invalidClassFaction"
|
||||
end
|
||||
else
|
||||
return "@invalidClass"
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
ix.command.Add("MapRestart", {
|
||||
description = "@cmdMapRestart",
|
||||
adminOnly = true,
|
||||
arguments = bit.bor(ix.type.number, ix.type.optional),
|
||||
OnRun = function(self, client, delay)
|
||||
delay = delay or 10
|
||||
ix.util.NotifyLocalized("mapRestarting", nil, delay)
|
||||
|
||||
timer.Simple(delay, function()
|
||||
RunConsoleCommand("changelevel", game.GetMap())
|
||||
end)
|
||||
end
|
||||
})
|
||||
383
gamemodes/helix/gamemode/core/sh_config.lua
Normal file
383
gamemodes/helix/gamemode/core/sh_config.lua
Normal file
@@ -0,0 +1,383 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--- Helper library for creating/setting config options.
|
||||
-- @module ix.config
|
||||
|
||||
ix.config = ix.config or {}
|
||||
ix.config.stored = ix.config.stored or {}
|
||||
|
||||
if (SERVER) then
|
||||
util.AddNetworkString("ixConfigList")
|
||||
util.AddNetworkString("ixConfigSet")
|
||||
util.AddNetworkString("ixConfigRequestUnloadedList")
|
||||
util.AddNetworkString("ixConfigUnloadedList")
|
||||
util.AddNetworkString("ixConfigPluginToggle")
|
||||
|
||||
ix.config.server = ix.yaml.Read("gamemodes/helix/helix.yml") or {}
|
||||
end
|
||||
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Manage Config",
|
||||
MinAccess = "superadmin"
|
||||
})
|
||||
|
||||
--- Creates a config option with the given information.
|
||||
-- @realm shared
|
||||
-- @string key Unique ID of the config
|
||||
-- @param value Default value that this config will have
|
||||
-- @string description Description of the config
|
||||
-- @func[opt=nil] callback Function to call when config is changed
|
||||
-- @tab[opt=nil] data Additional settings for this config option
|
||||
-- @bool[opt=false] bNoNetworking Whether or not to prevent networking the config
|
||||
-- @bool[opt=false] bSchemaOnly Whether or not the config is for the schema only
|
||||
function ix.config.Add(key, value, description, callback, data, bNoNetworking, bSchemaOnly)
|
||||
data = istable(data) and data or {}
|
||||
|
||||
local oldConfig = ix.config.stored[key]
|
||||
local type = data.type or ix.util.GetTypeFromValue(value)
|
||||
|
||||
if (!type) then
|
||||
ErrorNoHalt("attempted to add config with invalid type\n")
|
||||
return
|
||||
end
|
||||
|
||||
local default = value
|
||||
data.type = nil
|
||||
|
||||
-- using explicit nil comparisons so we don't get caught by a config's value being `false`
|
||||
if (oldConfig != nil) then
|
||||
if (oldConfig.value != nil) then
|
||||
value = oldConfig.value
|
||||
end
|
||||
|
||||
if (oldConfig.default != nil) then
|
||||
default = oldConfig.default
|
||||
end
|
||||
end
|
||||
|
||||
ix.config.stored[key] = {
|
||||
type = type,
|
||||
data = data,
|
||||
value = value,
|
||||
default = default,
|
||||
description = description,
|
||||
bNoNetworking = bNoNetworking,
|
||||
global = !bSchemaOnly,
|
||||
callback = callback,
|
||||
hidden = data.hidden or nil,
|
||||
populate = data.populate or nil,
|
||||
}
|
||||
end
|
||||
|
||||
--- Sets the default value for a config option.
|
||||
-- @realm shared
|
||||
-- @string key Unique ID of the config
|
||||
-- @param value Default value for the config option
|
||||
function ix.config.SetDefault(key, value)
|
||||
local config = ix.config.stored[key]
|
||||
|
||||
if (config) then
|
||||
config.default = value
|
||||
else
|
||||
-- set up dummy config if we're setting default of config that doesn't exist yet (i.e schema setting framework default)
|
||||
ix.config.stored[key] = {
|
||||
value = value,
|
||||
default = value
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function ix.config.ForceSet(key, value, noSave)
|
||||
local config = ix.config.stored[key]
|
||||
|
||||
if (config) then
|
||||
config.value = value
|
||||
end
|
||||
|
||||
if (noSave) then
|
||||
ix.config.Save()
|
||||
end
|
||||
end
|
||||
|
||||
--- Sets the value of a config option.
|
||||
-- @realm shared
|
||||
-- @string key Unique ID of the config
|
||||
-- @param value New value to assign to the config
|
||||
function ix.config.Set(key, value)
|
||||
local config = ix.config.stored[key]
|
||||
|
||||
if (config) then
|
||||
local oldValue = value
|
||||
config.value = value
|
||||
|
||||
if (SERVER) then
|
||||
if (!config.bNoNetworking) then
|
||||
net.Start("ixConfigSet")
|
||||
net.WriteString(key)
|
||||
net.WriteType(value)
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
if (config.callback) then
|
||||
config.callback(oldValue, value)
|
||||
end
|
||||
|
||||
ix.config.Save()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Retrieves a value of a config option. If it is not set, it'll return the default that you've specified.
|
||||
-- @realm shared
|
||||
-- @string key Unique ID of the config
|
||||
-- @param default Default value to return if the config is not set
|
||||
-- @return Value associated with the key, or the default that was given if it doesn't exist
|
||||
function ix.config.Get(key, default)
|
||||
local config = ix.config.stored[key]
|
||||
|
||||
-- ensure we aren't accessing a dummy value
|
||||
if (config and config.type) then
|
||||
if (config.value != nil) then
|
||||
return config.value
|
||||
elseif (config.default != nil) then
|
||||
return config.default
|
||||
end
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Loads all saved config options from disk.
|
||||
-- @realm shared
|
||||
-- @internal
|
||||
function ix.config.Load()
|
||||
if (SERVER) then
|
||||
local globals = ix.data.Get("config", nil, true, true)
|
||||
local data = ix.data.Get("config", nil, false, true)
|
||||
|
||||
if (globals) then
|
||||
for k, v in pairs(globals) do
|
||||
ix.config.stored[k] = ix.config.stored[k] or {}
|
||||
ix.config.stored[k].value = v
|
||||
end
|
||||
end
|
||||
|
||||
if (data) then
|
||||
for k, v in pairs(data) do
|
||||
ix.config.stored[k] = ix.config.stored[k] or {}
|
||||
ix.config.stored[k].value = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ix.util.Include("helix/gamemode/config/sh_config.lua")
|
||||
|
||||
if (SERVER or !IX_RELOADED) then
|
||||
hook.Run("InitializedConfig")
|
||||
end
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
function ix.config.GetChangedValues()
|
||||
local data = {}
|
||||
|
||||
for k, v in pairs(ix.config.stored) do
|
||||
if (v.default != v.value) then
|
||||
data[k] = v.value
|
||||
end
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function ix.config.Send(client)
|
||||
net.Start("ixConfigList")
|
||||
net.WriteTable(ix.config.GetChangedValues())
|
||||
net.Send(client)
|
||||
end
|
||||
|
||||
--- Saves all config options to disk.
|
||||
-- @realm server
|
||||
-- @internal
|
||||
function ix.config.Save()
|
||||
local globals = {}
|
||||
local data = {}
|
||||
|
||||
for k, v in pairs(ix.config.GetChangedValues()) do
|
||||
if (ix.config.stored[k].global) then
|
||||
globals[k] = v
|
||||
else
|
||||
data[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
-- Global and schema data set respectively.
|
||||
ix.data.Set("config", globals, true, true)
|
||||
ix.data.Set("config", data, false, true)
|
||||
end
|
||||
|
||||
net.Receive("ixConfigSet", function(length, client)
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
|
||||
if (CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil) and
|
||||
type(ix.config.stored[key].default) == type(value)) then
|
||||
ix.config.Set(key, value)
|
||||
|
||||
if (ix.util.IsColor(value)) then
|
||||
value = string.format("[%d, %d, %d]", value.r, value.g, value.b)
|
||||
elseif (istable(value)) then
|
||||
local value2 = "["
|
||||
local count = table.Count(value)
|
||||
local i = 1
|
||||
|
||||
for _, v in SortedPairs(value) do
|
||||
value2 = value2 .. v .. (i == count and "]" or ", ")
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
value = value2
|
||||
elseif (isstring(value)) then
|
||||
value = string.format("\"%s\"", tostring(value))
|
||||
elseif (isbool(value)) then
|
||||
value = string.format("[%s]", tostring(value))
|
||||
end
|
||||
|
||||
ix.util.NotifyLocalized("cfgSet", nil, client:Name(), key, tostring(value))
|
||||
ix.log.Add(client, "cfgSet", key, value)
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixConfigRequestUnloadedList", function(length, client)
|
||||
if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then
|
||||
return
|
||||
end
|
||||
|
||||
net.Start("ixConfigUnloadedList")
|
||||
net.WriteTable(ix.plugin.unloaded)
|
||||
net.Send(client)
|
||||
end)
|
||||
|
||||
net.Receive("ixConfigPluginToggle", function(length, client)
|
||||
if (!CAMI.PlayerHasAccess(client, "Helix - Manage Config", nil)) then
|
||||
return
|
||||
end
|
||||
|
||||
local uniqueID = net.ReadString()
|
||||
local bUnloaded = !!ix.plugin.unloaded[uniqueID]
|
||||
local bShouldEnable = net.ReadBool()
|
||||
|
||||
if ((bShouldEnable and bUnloaded) or (!bShouldEnable and !bUnloaded)) then
|
||||
ix.plugin.SetUnloaded(uniqueID, !bShouldEnable) -- flip bool since we're setting unloaded, not enabled
|
||||
|
||||
ix.util.NotifyLocalized(bShouldEnable and "pluginLoaded" or "pluginUnloaded", nil, client:GetName(), uniqueID)
|
||||
ix.log.Add(client, bShouldEnable and "pluginLoaded" or "pluginUnloaded", uniqueID)
|
||||
|
||||
net.Start("ixConfigPluginToggle")
|
||||
net.WriteString(uniqueID)
|
||||
net.WriteBool(bShouldEnable)
|
||||
net.Broadcast()
|
||||
end
|
||||
end)
|
||||
else
|
||||
net.Receive("ixConfigList", function()
|
||||
local data = net.ReadTable()
|
||||
|
||||
for k, v in pairs(data) do
|
||||
if (ix.config.stored[k]) then
|
||||
ix.config.stored[k].value = v
|
||||
end
|
||||
end
|
||||
|
||||
hook.Run("InitializedConfig", data)
|
||||
end)
|
||||
|
||||
net.Receive("ixConfigSet", function()
|
||||
local key = net.ReadString()
|
||||
local value = net.ReadType()
|
||||
local config = ix.config.stored[key]
|
||||
|
||||
if (config) then
|
||||
if (config.callback) then
|
||||
config.callback(config.value, value)
|
||||
end
|
||||
|
||||
config.value = value
|
||||
|
||||
local properties = ix.gui.properties
|
||||
|
||||
if (IsValid(properties)) then
|
||||
local row = properties:GetCategory(L(config.data and config.data.category or "misc")):GetRow(key)
|
||||
|
||||
if (IsValid(row)) then
|
||||
if (istable(value) and value.r and value.g and value.b) then
|
||||
value = Vector(value.r / 255, value.g / 255, value.b / 255)
|
||||
end
|
||||
|
||||
row:SetValue(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixConfigUnloadedList", function()
|
||||
ix.plugin.unloaded = net.ReadTable()
|
||||
ix.gui.bReceivedUnloadedPlugins = true
|
||||
|
||||
if (IsValid(ix.gui.pluginManager)) then
|
||||
ix.gui.pluginManager:UpdateUnloaded()
|
||||
end
|
||||
end)
|
||||
|
||||
net.Receive("ixConfigPluginToggle", function()
|
||||
local uniqueID = net.ReadString()
|
||||
local bEnabled = net.ReadBool()
|
||||
|
||||
if (bEnabled) then
|
||||
ix.plugin.unloaded[uniqueID] = nil
|
||||
else
|
||||
ix.plugin.unloaded[uniqueID] = true
|
||||
end
|
||||
|
||||
if (IsValid(ix.gui.pluginManager)) then
|
||||
ix.gui.pluginManager:UpdatePlugin(uniqueID, bEnabled)
|
||||
end
|
||||
end)
|
||||
|
||||
hook.Add("CreateMenuButtons", "ixConfig", function(tabs)
|
||||
if (!CAMI.PlayerHasAccess(LocalPlayer(), "Helix - Manage Config", nil)) then
|
||||
return
|
||||
end
|
||||
|
||||
tabs["config"] = {
|
||||
Create = function(info, container)
|
||||
container.panel = container:Add("ixConfigManager")
|
||||
end,
|
||||
|
||||
OnSelected = function(info, container)
|
||||
container.panel.searchEntry:RequestFocus()
|
||||
end,
|
||||
|
||||
Sections = {
|
||||
plugins = {
|
||||
Create = function(info, container)
|
||||
ix.gui.pluginManager = container:Add("ixPluginManager")
|
||||
end,
|
||||
|
||||
OnSelected = function(info, container)
|
||||
ix.gui.pluginManager.searchEntry:RequestFocus()
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
end)
|
||||
end
|
||||
128
gamemodes/helix/gamemode/core/sh_data.lua
Normal file
128
gamemodes/helix/gamemode/core/sh_data.lua
Normal file
@@ -0,0 +1,128 @@
|
||||
--[[
|
||||
| This file was obtained through the combined efforts
|
||||
| of Madbluntz & Plymouth Antiquarian Society.
|
||||
|
|
||||
| Credits: lifestorm, Gregory Wayne Rossel JR.,
|
||||
| Maloy, DrPepper10 @ RIP, Atle!
|
||||
|
|
||||
| Visit for more: https://plymouth.thetwilightzone.ru/
|
||||
--]]
|
||||
|
||||
|
||||
--- Helper library for reading/writing files to the data folder.
|
||||
-- @module ix.data
|
||||
|
||||
ix.data = ix.data or {}
|
||||
ix.data.stored = ix.data.stored or {}
|
||||
|
||||
-- Create a folder to store data in.
|
||||
file.CreateDir("helix")
|
||||
|
||||
--- Populates a file in the `data/helix` folder with some serialized data.
|
||||
-- @realm shared
|
||||
-- @string key Name of the file to save
|
||||
-- @param value Some sort of data to save
|
||||
-- @bool[opt=false] bGlobal Whether or not to write directly to the `data/helix` folder, or the `data/helix/schema` folder,
|
||||
-- where `schema` is the name of the current schema.
|
||||
-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and save in the schema folder, rather than
|
||||
-- `data/helix/schema/map`, where `map` is the name of the current map.
|
||||
function ix.data.Set(key, value, bGlobal, bIgnoreMap)
|
||||
-- Get the base path to write to.
|
||||
local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/")
|
||||
|
||||
-- Create the schema folder if the data is not global.
|
||||
if (!bGlobal) then
|
||||
file.CreateDir("helix/" .. Schema.folder .. "/")
|
||||
end
|
||||
|
||||
-- If we're not ignoring the map, create a folder for the map.
|
||||
file.CreateDir(path)
|
||||
-- Write the data using JSON encoding.
|
||||
file.Write(path .. key .. ".txt", util.TableToJSON({value}))
|
||||
|
||||
-- Cache the data value here.
|
||||
ix.data.stored[key] = value
|
||||
|
||||
return path
|
||||
end
|
||||
|
||||
--- Retrieves the contents of a saved file in the `data/helix` folder.
|
||||
-- @realm shared
|
||||
-- @string key Name of the file to load
|
||||
-- @param default Value to return if the file could not be loaded successfully
|
||||
-- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder,
|
||||
-- where `schema` is the name of the current schema.
|
||||
-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and load from the schema folder, rather than
|
||||
-- `data/helix/schema/map`, where `map` is the name of the current map.
|
||||
-- @bool[opt=false] bRefresh Whether or not to skip the cache and forcefully load from disk.
|
||||
-- @return Value associated with the key, or the default that was given if it doesn't exists
|
||||
function ix.data.Get(key, default, bGlobal, bIgnoreMap, bRefresh)
|
||||
-- If it exists in the cache, return the cached value so it is faster.
|
||||
if (!bRefresh) then
|
||||
local stored = ix.data.stored[key]
|
||||
|
||||
if (stored != nil) then
|
||||
return stored
|
||||
end
|
||||
end
|
||||
|
||||
-- Get the path to read from.
|
||||
local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/")
|
||||
-- Read the data from a local file.
|
||||
local contents = file.Read(path .. key .. ".txt", "DATA")
|
||||
|
||||
if (contents and contents != "") then
|
||||
local status, decoded = pcall(util.JSONToTable, contents)
|
||||
|
||||
if (status and decoded) then
|
||||
local value = decoded[1]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
-- Backwards compatibility.
|
||||
-- This may be removed in the future.
|
||||
status, decoded = pcall(pon.decode, contents)
|
||||
|
||||
if (status and decoded) then
|
||||
local value = decoded[1]
|
||||
|
||||
if (value != nil) then
|
||||
return value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
--- Deletes the contents of a saved file in the `data/helix` folder.
|
||||
-- @realm shared
|
||||
-- @string key Name of the file to delete
|
||||
-- @bool[opt=false] bGlobal Whether or not the data is in the `data/helix` folder, or the `data/helix/schema` folder,
|
||||
-- where `schema` is the name of the current schema.
|
||||
-- @bool[opt=false] bIgnoreMap Whether or not to ignore the map and delete from the schema folder, rather than
|
||||
-- `data/helix/schema/map`, where `map` is the name of the current map.
|
||||
-- @treturn bool Whether or not the deletion has succeeded
|
||||
function ix.data.Delete(key, bGlobal, bIgnoreMap)
|
||||
-- Get the path to read from.
|
||||
local path = "helix/" .. (bGlobal and "" or Schema.folder .. "/") .. (bIgnoreMap and "" or game.GetMap() .. "/")
|
||||
-- Read the data from a local file.
|
||||
local contents = file.Read(path .. key .. ".txt", "DATA")
|
||||
|
||||
if (contents and contents != "") then
|
||||
file.Delete(path .. key .. ".txt")
|
||||
ix.data.stored[key] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if (SERVER) then
|
||||
timer.Create("ixSaveData", 600, 0, function()
|
||||
hook.Run("SaveData")
|
||||
end)
|
||||
end
|
||||
1166
gamemodes/helix/gamemode/core/sh_util.lua
Normal file
1166
gamemodes/helix/gamemode/core/sh_util.lua
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user