add sborka

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

View File

@@ -0,0 +1,199 @@
local PLUGIN = PLUGIN
surface.CreateFont("ixAdminShopTitle", { font = "Montserrat", size = 28, weight = 900, antialias = true })
surface.CreateFont("ixAdminShopTab", { font = "Montserrat", size = 18, weight = 700, antialias = true })
surface.CreateFont("ixAdminShopItem", { font = "Montserrat", size = 20, weight = 800, antialias = true })
surface.CreateFont("ixAdminShopDesc", { font = "Montserrat", size = 14, weight = 500, antialias = true })
surface.CreateFont("ixAdminShopPrice", { font = "Montserrat", size = 18, weight = 900, antialias = true })
surface.CreateFont("ixAdminShopSmall", { font = "Montserrat", size = 14, weight = 600, antialias = true })
surface.CreateFont("ixAdminShopBold", { font = "Montserrat", size = 16, weight = 800, antialias = true })
-- Modern Color Palette #00431c
local COLOR_BASE = Color(0, 67, 28)
local COLOR_BG = Color(15, 18, 16, 250)
local COLOR_CARD = Color(22, 26, 24, 255)
local COLOR_CARD_LIGHT = Color(30, 36, 32, 255)
local COLOR_TEXT = Color(240, 245, 240)
local COLOR_TEXT_DIM = Color(140, 150, 145)
local COLOR_ACCENT = Color(0, 140, 60)
local COLOR_DANGER = Color(200, 50, 50)
local COLOR_WARN = Color(200, 150, 0)
function PLUGIN:WrapText(text, width, font)
surface.SetFont(font)
local words = string.Explode(" ", text)
local lines = {}
local currentLine = ""
for _, word in ipairs(words) do
local testLine = currentLine == "" and word or currentLine .. " " .. word
local w, _ = surface.GetTextSize(testLine)
if w > width then
table.insert(lines, currentLine)
currentLine = word
else
currentLine = testLine
end
end
if currentLine != "" then
table.insert(lines, currentLine)
end
return lines
end
local PANEL = {}
function PANEL:Init()
self:SetSize(ScrW() * 0.7, ScrH() * 0.75)
self:Center()
self:MakePopup()
self:SetTitle("")
self:ShowCloseButton(false)
self.currentView = "shop"
self:SetAlpha(0)
self:AlphaTo(255, 0.2, 0)
self.startTime = SysTime()
self.Paint = function(s, w, h)
Derma_DrawBackgroundBlur(s, s.startTime)
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG)
surface.SetDrawColor(COLOR_BASE.r, COLOR_BASE.g, COLOR_BASE.b, 150)
surface.SetMaterial(Material("vgui/gradient-u"))
surface.DrawTexturedRect(0, 0, w, 80)
surface.SetDrawColor(COLOR_BASE)
surface.DrawRect(0, 80, w, 2)
local title = self.currentView == "shop" and "АДМИН МАГАЗИН" or (self.currentView == "inventory" and "МОЙ ИНВЕНТАРЬ" or "УПРАВЛЕНИЕ АДМИНИСТРАТОРАМИ")
draw.SimpleText(title, "ixAdminShopTitle", 40, 40, COLOR_TEXT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
local points = LocalPlayer():GetAdminPoints()
draw.SimpleText("Текущий баланс:", "ixAdminShopSmall", w * 0.45, 40, COLOR_TEXT_DIM, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
draw.SimpleText(points .. " Очков", "ixAdminShopItem", w * 0.45 + 10, 40, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
local closeBtn = self:Add("DButton")
closeBtn:SetSize(40, 40)
closeBtn:SetPos(self:GetWide() - 50, 20)
closeBtn:SetText("")
closeBtn:SetFont("ixAdminShopTitle")
closeBtn:SetTextColor(COLOR_TEXT_DIM)
closeBtn.Paint = nil
closeBtn.DoClick = function()
self:AlphaTo(0, 0.2, 0, function() self:Close() end)
end
closeBtn.OnCursorEntered = function(s) s:SetTextColor(COLOR_DANGER) end
closeBtn.OnCursorExited = function(s) s:SetTextColor(COLOR_TEXT_DIM) end
self.nav = self:Add("Panel")
self.nav:SetSize(450, 40)
self.nav:SetPos(self:GetWide() - 520, 20)
local function CreateNavBtn(text, view, x, activeColor)
local btn = self.nav:Add("DButton")
btn:SetSize(140, 40)
btn:SetPos(x, 0)
btn:SetText(text)
btn:SetFont("ixAdminShopBold")
btn:SetTextColor(COLOR_TEXT)
btn.Paint = function(s, w, h)
if self.currentView == view then
draw.RoundedBox(4, 0, 0, w, h, activeColor)
elseif s:IsHovered() then
draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 10))
end
end
btn.DoClick = function()
surface.PlaySound("ui/buttonclick.wav")
self.currentView = view
self:Refresh()
end
return btn
end
CreateNavBtn("Магазин", "shop", 0, COLOR_BASE)
self.content = self:Add("DScrollPanel")
self.content:Dock(FILL)
self.content:DockMargin(40, 100, 40, 40)
local sbar = self.content:GetVBar()
sbar:SetWide(8)
sbar:SetHideButtons(true)
sbar.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, Color(0, 0, 0, 100))
end
sbar.btnGrip.Paint = function(s, w, h)
draw.RoundedBox(4, 2, 0, w-4, h, s:IsHovered() and COLOR_ACCENT or COLOR_BASE)
end
self:Refresh()
end
function PANEL:Refresh()
self.content:Clear()
if (self.currentView == "shop") then
local layout = self.content:Add("DIconLayout")
layout:Dock(TOP)
layout:SetSpaceX(20)
layout:SetSpaceY(20)
for id, item in pairs(PLUGIN.Items) do
local card = layout:Add("DPanel")
card:SetSize(250, 340)
card.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, COLOR_CARD)
surface.SetDrawColor(COLOR_CARD_LIGHT)
surface.SetMaterial(Material("vgui/gradient-d"))
surface.DrawTexturedRect(0, 0, w, 140)
draw.SimpleText(item.name, "ixAdminShopItem", w/2, 160, COLOR_TEXT, TEXT_ALIGN_CENTER)
local descLines = PLUGIN:WrapText(item.desc, w - 30, "ixAdminShopDesc")
for i, line in ipairs(descLines) do
if (i > 4) then break end
draw.SimpleText(line, "ixAdminShopDesc", w/2, 185 + (i-1)*18, COLOR_TEXT_DIM, TEXT_ALIGN_CENTER)
end
end
local icon = card:Add("DPanel")
icon:SetSize(60, 60)
icon:SetPos(250/2 - 30, 40)
icon.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG)
surface.SetDrawColor(item.color or COLOR_ACCENT)
surface.DrawOutlinedRect(0, 0, w, h, 2)
end
local buy = card:Add("DButton")
buy:SetSize(210, 45)
buy:SetPos(20, 275)
buy:SetText("Купить за " .. item.price .. " Очков")
buy:SetFont("ixAdminShopBold")
buy:SetTextColor(COLOR_TEXT)
buy.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, s:IsHovered() and COLOR_ACCENT or COLOR_BASE)
end
buy.DoClick = function()
surface.PlaySound("ui/buttonclick.wav")
net.Start("ixAdminShopBuy")
net.WriteString(id)
net.SendToServer()
end
end
end
end
vgui.Register("ixAdminShop", PANEL, "DFrame")
net.Receive("ixAdminShopOpen", function()
if (IsValid(ixAdminShop)) then
ixAdminShop:Close()
end
ixAdminShop = vgui.Create("ixAdminShop")
end)

View File

@@ -0,0 +1,75 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Admin Shop"
PLUGIN.author = "Scripty"
PLUGIN.description = "Adds an admin shop with a unique currency 'Points', inventory, and admin panel."
PLUGIN.Items = {
["remove_warn"] = {
name = "Снятие выговора",
desc = "Снимает один активный выговор с вашего аккаунта. Выговор будет удален из базы данных, и это отобразится в истории.",
price = 150,
category = "Other",
icon = "icon16/error_delete.png",
oneTime = true
},
["tacrp_ex_m4a1"] = {
name = "Оружие: HK416",
desc = "Мощная штурмовая винтовка HK416. Доступ на 10 дней. Оружие выдается через инвентарь.",
price = 50,
category = "Weapons",
duration = 10,
class = "tacrp_ex_m4a1",
color = Color(30, 144, 255)
},
["tacrp_mg4"] = {
name = "Оружие: M249",
desc = "Тяжелый пулемет M249. Доступ на 15 дней. Оружие выдается через инвентарь.",
price = 60,
category = "Weapons",
duration = 15,
class = "tacrp_mg4",
color = Color(255, 140, 0)
},
["tacrp_ex_hecate"] = {
name = "Оружие: MSR",
desc = "Снайперская винтовка MSR. Доступ на 13 дней. Оружие выдается через инвентарь.",
price = 60,
category = "Weapons",
duration = 13,
class = "tacrp_ex_hecate",
color = Color(138, 43, 226)
},
["tacrp_ak_ak12"] = {
name = "Оружие: AK-12",
desc = "Современная штурмовая винтовка АК-12. Доступ на 5 дней. Оружие выдается через инвентарь.",
price = 45,
category = "Weapons",
duration = 5,
class = "tacrp_ak_ak12",
color = Color(255, 215, 0)
},
["tacrp_sd_aac_hb"] = {
name = "Оружие: LVO-AC",
desc = "Превосходная штурмовая винтовка TFA LVO-AC. Доступ на 15 дней. Оружие выдается через инвентарь.",
price = 50,
category = "Weapons",
duration = 15,
class = "tacrp_sd_aac_hb",
color = Color(255, 165, 0)
}
}
-- Point utility functions
local playerMeta = debug.getregistry().Player
function playerMeta:GetAdminPoints()
return self:GetNetVar("adminPoints", 0)
end
function playerMeta:GetAdminInventory()
return self:GetNetVar("adminInventory", {})
end
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")

View File

@@ -0,0 +1,184 @@
local PLUGIN = PLUGIN
util.AddNetworkString("ixAdminShopOpen")
util.AddNetworkString("ixAdminShopBuy")
util.AddNetworkString("ixAdminShopRetrieve")
util.AddNetworkString("ixAdminShopAdminSync")
util.AddNetworkString("ixAdminShopAdminAction")
function PLUGIN:Initialize()
self.shopData = ix.data.Get("adminShop", {})
end
function PLUGIN:SaveShopData()
ix.data.Set("adminShop", self.shopData)
end
function PLUGIN:PlayerLoaded(client)
local steamID = client:SteamID()
self.shopData[steamID] = self.shopData[steamID] or {points = 0, inventory = {}}
client:SetNetVar("adminPoints", self.shopData[steamID].points)
client:SetNetVar("adminInventory", self.shopData[steamID].inventory)
end
local playerMeta = debug.getregistry().Player
function playerMeta:SetAdminPoints(amount)
amount = math.max(0, amount)
self:SetNetVar("adminPoints", amount)
local steamID = self:SteamID()
PLUGIN.shopData[steamID] = PLUGIN.shopData[steamID] or {points = 0, inventory = {}}
PLUGIN.shopData[steamID].points = amount
PLUGIN:SaveShopData()
end
function playerMeta:GiveAdminPoints(amount)
self:SetAdminPoints(self:GetAdminPoints() + amount)
end
function playerMeta:TakeAdminPoints(amount)
self:SetAdminPoints(self:GetAdminPoints() - amount)
end
function playerMeta:SetAdminInventory(data)
self:SetNetVar("adminInventory", data)
local steamID = self:SteamID()
PLUGIN.shopData[steamID] = PLUGIN.shopData[steamID] or {points = 0, inventory = {}}
PLUGIN.shopData[steamID].inventory = data
PLUGIN:SaveShopData()
end
ix.command.Add("AdminShop", {
description = "Открыть магазин администратора.",
OnRun = function(self, client)
if (!client:IsAdmin() and !client:IsSuperAdmin() and !IsAdminRank(client:GetUserGroup())) then
client:Notify("Эта команда доступна только администраторам.")
return
end
net.Start("ixAdminShopOpen")
net.Send(client)
end
})
net.Receive("ixAdminShopBuy", function(len, client)
local itemID = net.ReadString()
local item = PLUGIN.Items[itemID]
if (!item) then return end
local points = client:GetAdminPoints()
if (points < item.price) then
client:Notify("У вас недостаточно очков!")
return
end
if (itemID == "remove_warn") then
local warnPlugin = ix.plugin.Get("warn")
if (warnPlugin) then
local steamID = client:SteamID()
local warns = ix.data.Get("warns", {})
local targetData = warns[steamID]
if (targetData and targetData.count > 0) then
targetData.count = targetData.count - 1
table.insert(targetData.history, {
admin = "Admin Shop",
adminSteamID = "STEAM_0:0:0",
reason = "Покупка в магазине",
time = os.time(),
type = "remove"
})
ix.data.Set("warns", warns)
warnPlugin.warns = warns
client:TakeAdminPoints(item.price)
client:Notify("Вы успешно сняли выговор!")
else
client:Notify("У вас нет активных выговоров для снятия!")
end
else
client:Notify("Система выговоров не найдена!")
end
else
-- Immediate delivery if class exists
if (item.class) then
client:GiveItem(item.class)
client:TakeAdminPoints(item.price)
client:Notify("Вы купили " .. item.name .. ". Предмет выдан!")
else
-- Fallback to inventory just in case (hidden)
local inv = client:GetAdminInventory()
local expireTime = 0
if (item.duration) then
expireTime = os.time() + (item.duration * 24 * 60 * 60)
end
table.insert(inv, {
id = itemID,
buyTime = os.time(),
expireTime = expireTime,
name = item.name,
class = item.class
})
client:SetAdminInventory(inv)
client:TakeAdminPoints(item.price)
client:Notify("Вы купили " .. item.name .. ". Предмет добавлен в инвентарь (скрыто).")
end
end
end)
net.Receive("ixAdminShopRetrieve", function(len, client)
local index = net.ReadUInt(16)
local inv = client:GetAdminInventory()
local itemData = inv[index]
if (!itemData) then return end
if (itemData.expireTime > 0 and os.time() > itemData.expireTime) then
client:Notify("Срок действия предмета истек!")
table.remove(inv, index)
client:SetAdminInventory(inv)
return
end
if (itemData.class) then
client:GiveItem(itemData.class)
client:Notify("Вы получили: " .. itemData.name)
end
end)
local adminRanks = {
["super admin"] = true,
["superadmin"] = true,
["projectteam"] = true,
["teh.admin"] = true,
["curator"] = true,
["sudo-curator"] = true,
["asist-sudo"] = true,
["admin"] = true,
["st.admin"] = true,
["ivent"] = true,
["st.event"] = true,
["event"] = true,
["disp"] = true,
["assistant"] = true,
["prem"] = true,
["dsmoder"] = true
}
local function IsAdminRank(rank)
if not rank or rank == "user" then return false end
local lowerRank = string.lower(rank)
-- Check exact match in the provided list
if adminRanks[lowerRank] then return true end
-- Keep generic string searches just in case
if lowerRank == "founder" or lowerRank == "owner" or lowerRank == "manager" then return true end
return false
end

View File

@@ -0,0 +1,194 @@
local PLUGIN = PLUGIN
function PLUGIN:HUDPaint()
local client = LocalPlayer()
if not IsValid(client) then return end
if not client:IsAdminMode() then return end
local scrW, scrH = ScrW(), ScrH()
local w, h = 220, 36
local x, y = scrW / 2 - w / 2, 20
surface.SetDrawColor(13, 13, 13, 220)
surface.DrawRect(x, y, w, h)
surface.SetDrawColor(200, 80, 80, 255)
surface.DrawOutlinedRect(x, y, w, h, 2)
draw.SimpleText("ADMIN MODE", "ixMenuButtonFont", scrW / 2, y + h / 2,
Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
surface.CreateFont("AdminESPFontSmall", {
font = "Arial",
size = 12,
weight = 500
})
CLIENT_ADMIN_GROUPS = CLIENT_ADMIN_GROUPS or {}
local function Draw3DBox(ent, color)
local mins, maxs = ent:OBBMins(), ent:OBBMaxs()
local corners = {
Vector(mins.x, mins.y, mins.z),
Vector(mins.x, maxs.y, mins.z),
Vector(maxs.x, maxs.y, mins.z),
Vector(maxs.x, mins.y, mins.z),
Vector(mins.x, mins.y, maxs.z),
Vector(mins.x, maxs.y, maxs.z),
Vector(maxs.x, maxs.y, maxs.z),
Vector(maxs.x, mins.y, maxs.z),
}
local lines = {
{1,2},{2,3},{3,4},{4,1},
{5,6},{6,7},{7,8},{8,5},
{1,5},{2,6},{3,7},{4,8}
}
cam.Start3D()
render.SetColorMaterial()
for _, l in ipairs(lines) do
render.DrawLine(
ent:LocalToWorld(corners[l[1]]),
ent:LocalToWorld(corners[l[2]]),
color, true
)
end
cam.End3D()
end
local function CanSeeESP()
local client = LocalPlayer()
if not IsValid(client) then return false end
if not client:IsAdminMode() then return false end
local group = client:GetUserGroup()
if not CLIENT_ADMIN_GROUPS[group] then return false end
return true
end
hook.Add("HUDPaint", "AdminMode_ESP", function()
if not CanSeeESP() then return end
local client = LocalPlayer()
for _, ply in ipairs(player.GetAll()) do
if ply ~= client and ply:Alive() then
local distance = client:GetPos():Distance(ply:GetPos())
Draw3DBox(ply, Color(255, 60, 60))
local char = ply:GetCharacter()
local top = (ply:GetPos() + Vector(0,0,85)):ToScreen()
local bottom = (ply:GetPos() + Vector(0,0,5)):ToScreen()
local faction = "Неизвестно"
local podr = "Неизвестно"
local spec = "Неизвестно"
local rank = "Неизвестно"
local samGroup = ply:GetUserGroup()
local samName = samGroup
if sam and sam.ranks and sam.ranks[samGroup] and sam.ranks[samGroup].Name then
samName = sam.ranks[samGroup].Name
end
if char then
local factionID = char:GetFaction()
local factionTable = ix.faction.indices[factionID]
if factionTable and factionTable.name then
faction = factionTable.name
end
if factionTable and factionTable.Podr and char.GetPodr then
local id = char:GetPodr()
if factionTable.Podr[id] and factionTable.Podr[id].name then
podr = factionTable.Podr[id].name
end
end
if factionTable and factionTable.Spec and char.GetSpec then
local id = char:GetSpec()
if factionTable.Spec[id] and factionTable.Spec[id].name then
spec = factionTable.Spec[id].name
end
end
if factionTable and factionTable.Ranks and char.GetRank then
local id = char:GetRank()
if factionTable.Ranks[id] and factionTable.Ranks[id][1] then
rank = factionTable.Ranks[id][1]
end
end
end
local steamid = ply:SteamID()
local hp = ply:Health()
local armor = ply:Armor()
local wep = IsValid(ply:GetActiveWeapon()) and ply:GetActiveWeapon():GetClass() or ""
local topLines = {}
if distance > 500 then
-- Издалека: только ник
topLines = { ply:Name() }
elseif distance > 200 then
-- Средняя дальность: ник + фракция + SteamID
topLines = {
ply:Name(),
"" .. faction,
"" .. steamid
}
else
-- Близко: вся информация
topLines = {
ply:Name(),
"" .. faction,
"" .. podr,
"" .. spec,
"" .. rank,
"" .. samName,
"" .. steamid
}
end
local y = top.y
for _, text in ipairs(topLines) do
surface.SetDrawColor(0, 0, 0, 160)
surface.DrawRect(top.x - 50, y - 5, 100, 12)
draw.SimpleText(text, "AdminESPFontSmall", top.x, y,
Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
y = y + 15
end
-- Нижняя информация всегда показывается близко
if distance <= 200 then
local bottomLines = {
"HP: " .. hp,
"Armor: " .. armor,
"Weapon: " .. wep
}
local y2 = bottom.y
for _, text in ipairs(bottomLines) do
surface.SetDrawColor(0, 0, 0, 160)
surface.DrawRect(bottom.x - 40, y2 - 5, 80, 10)
draw.SimpleText(text, "AdminESPFontSmall", bottom.x, y2,
Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
y2 = y2 + 12
end
end
end
end
end)

View File

@@ -0,0 +1,386 @@
ix.util.Include("cl_plugin.lua")
ix.util.Include("sv_plugin.lua")
local PLUGIN = PLUGIN
PLUGIN.name = "Admin Mode"
PLUGIN.author = "Scripty"
PLUGIN.description = "Перекид игрока по команде в админ фракцию"
local accessAdminGroups = {
["superadmin"] = true,
["super admin"] = true,
["projectteam"] = true,
["teh.admin"] = true,
["curator"] = true,
["sudo-curator"] = true,
["asist-sudo"] = true,
["admin"] = true,
["st.admin"] = true,
["ivent"] = true,
["st.event"] = true,
["event"] = true,
["disp"] = true,
["assistant"] = true,
["specadmin"] = true
}
local playerMeta = FindMetaTable("Player")
function playerMeta:IsAdminMode()
return self:GetNetVar("AdminMode", false)
end
local function CanUseAdminMode(ply)
return accessAdminGroups[ply:GetUserGroup()] == true
end
local function CanUseNoclip(ply)
if ply:IsAdminMode() then return true end
if ply:GetUserGroup() == "superadmin" then return true end
return false
end
local function GetWeaponsList(ply)
local list = {}
for _, wep in ipairs(ply:GetWeapons()) do
if IsValid(wep) then
list[#list + 1] = wep:GetClass()
end
end
return list
end
if (SERVER) then
util.AddNetworkString("AdminMode_Groups")
hook.Add("PlayerInitialSpawn", "AdminMode_SendGroups", function(ply)
net.Start("AdminMode_Groups")
net.WriteTable(accessAdminGroups)
net.Send(ply)
end)
function PLUGIN:PlayerDisconnected(ply)
if ply:IsAdminMode() then
local char = ply:GetCharacter()
if char then
local oldFaction = char:GetData("AdminMode_OldFaction")
if oldFaction then char:SetFaction(oldFaction) end
local oldPodr = char:GetData("AdminMode_OldPodr")
if oldPodr then char:SetPodr(oldPodr) end
local oldSpec = char:GetData("AdminMode_OldSpec")
if oldSpec then char:SetSpec(oldSpec) end
local oldRank = char:GetData("AdminMode_OldRank")
if oldRank then char:SetRank(oldRank) end
end
ply:SetNetVar("HideFromTab", false)
ply:SetNetVar("AdminMode", false)
end
end
function PLUGIN:PlayerLoadedCharacter(ply, char)
local squads = ix.plugin.list["squads"]
if ply:IsAdminMode() and squads then
local squad, squadID = squads:GetPlayerSquad(ply)
if squad then
if squad.leader == ply:SteamID() then
squads:DisbandSquad(squadID)
else
squads:LeaveSquad(ply)
end
net.Start("ixSquadSync")
net.WriteString("{}")
net.Send(ply)
end
end
if char:GetFaction() == FACTION_ADMIN and not ply:IsAdminMode() then
local oldFaction = char:GetData("AdminMode_OldFaction")
local oldPodr = char:GetData("AdminMode_OldPodr")
local oldSpec = char:GetData("AdminMode_OldSpec")
local oldRank = char:GetData("AdminMode_OldRank")
if oldFaction then char:SetFaction(oldFaction) end
if oldPodr then char:SetPodr(oldPodr) end
if oldSpec then char:SetSpec(oldSpec) end
if oldRank then char:SetRank(oldRank) end
char:SetData("AdminMode_OldFaction", nil)
char:SetData("AdminMode_OldPodr", nil)
char:SetData("AdminMode_OldSpec", nil)
char:SetData("AdminMode_OldRank", nil)
char:SetData("AdminMode_OldModel", nil)
char:SetData("AdminMode_OldWeapons", nil)
ply:SetNetVar("AdminMode", false)
ply:SetNetVar("HideFromTab", false)
local radio = ix.plugin.list["radio"]
if radio then
radio:SyncRadioState(ply, radio.defaultFrequency, false, false)
end
elseif ply:IsAdminMode() and char:GetFaction() ~= FACTION_ADMIN then
self:ToggleAdminMode(ply)
end
if ply:IsAdminMode() then
local radio = ix.plugin.list["radio"]
if radio then
radio:SyncRadioState(ply, radio.defaultFrequency, false, false)
end
end
end
function PLUGIN:OnCharacterSave(char)
local ply = char:GetPlayer()
if not IsValid(ply) then return end
if ply:IsAdminMode() then
local oldFaction = char:GetData("AdminMode_OldFaction")
if oldFaction then char:SetFaction(oldFaction) end
local oldPodr = char:GetData("AdminMode_OldPodr")
if oldPodr then char:SetPodr(oldPodr) end
local oldSpec = char:GetData("AdminMode_OldSpec")
if oldSpec then char:SetSpec(oldSpec) end
local oldRank = char:GetData("AdminMode_OldRank")
if oldRank then char:SetRank(oldRank) end
ply:SetNetVar("AdminMode", false)
end
end
function PLUGIN:ToggleAdminMode(ply)
if not IsValid(ply) then return end
if not CanUseAdminMode(ply) then
ply:ChatPrint("[!] У вас нет доступа к админ-моду.")
return
end
local char = ply:GetCharacter()
if not char then
ply:ChatPrint("[!] У вас нет активного персонажа.")
return
end
local enable = not ply:IsAdminMode()
ply:SetNetVar("AdminMode", enable)
if enable then
local squads = ix.plugin.list["squads"]
if squads then
local squad, squadID = squads:GetPlayerSquad(ply)
if squad then
if squad.leader == ply:SteamID() then
squads:DisbandSquad(squadID)
else
squads:LeaveSquad(ply)
end
end
end
char:SetData("AdminMode_OldFaction", char:GetFaction())
char:SetData("AdminMode_OldPodr", char:GetPodr())
char:SetData("AdminMode_OldSpec", char:GetSpec())
char:SetData("AdminMode_OldRank", char:GetRank())
char:SetData("AdminMode_OldModel", ply:GetModel())
char:SetData("AdminMode_OldWeapons", GetWeaponsList(ply))
ply:SetModel("models/ft/ft_admin.mdl")
if FACTION_ADMIN then
char:SetFaction(FACTION_ADMIN)
end
ply:GodEnable()
ply:SetNoDraw(true)
ply:SetMoveType(MOVETYPE_NOCLIP)
ply:StripWeapons()
ply:Give("weapon_physgun")
ply:Give("gmod_tool")
ply:Give("ix_hands")
ply:SelectWeapon("ix_hands")
ply:SetNetVar("HideFromTab", true)
ply:ChatPrint("[!] Админ-мод включен.")
else
local oldFaction = char:GetData("AdminMode_OldFaction")
local oldPodr = char:GetData("AdminMode_OldPodr")
local oldSpec = char:GetData("AdminMode_OldSpec")
local oldRank = char:GetData("AdminMode_OldRank")
if oldFaction and char:GetFaction() == FACTION_ADMIN then char:SetFaction(oldFaction) end
if oldPodr then char:SetPodr(oldPodr) end
if oldSpec then char:SetSpec(oldSpec) end
if oldRank then char:SetRank(oldRank) end
local oldModel = char:GetData("AdminMode_OldModel")
if oldModel then
ply:SetModel(oldModel)
end
local oldWeapons = char:GetData("AdminMode_OldWeapons") or {}
ply:StripWeapons()
for _, class in ipairs(oldWeapons) do
ply:Give(class)
end
if ply.sam_uncloak then
ply:sam_uncloak()
end
ply:SetNoDraw(false)
ply:SetRenderMode(RENDERMODE_NORMAL)
ply:SetColor(Color(255, 255, 255, 255))
ply:Fire("alpha", 255, 0)
ply:DrawWorldModel(true)
ply:SetMaterial("")
ply:SetNetVar("ixNoDraw", false)
ply:DrawShadow(true)
ply:SetupHands()
if #oldWeapons == 0 then
ply:Give("ix_hands")
end
ply:GodDisable()
ply:SetMoveType(MOVETYPE_WALK)
local restoredFaction = char:GetFaction()
local class = char:GetClass() or "default"
local spawnPlugin = ix.plugin.list["spawns"]
if spawnPlugin and spawnPlugin.spawns then
local factionTable = ix.faction.indices[restoredFaction]
if factionTable then
local factionID = factionTable.uniqueID
local factionSpawns = spawnPlugin.spawns[factionID]
if factionSpawns then
local className = "default"
for _, v in ipairs(ix.class.list) do
if v.index == class then
className = v.uniqueID
break
end
end
local points = factionSpawns[className] or factionSpawns["default"]
if points and not table.IsEmpty(points) then
local pos = table.Random(points)
timer.Simple(0.1, function()
if IsValid(ply) then
ply:SetPos(pos)
end
end)
end
end
end
end
char:SetData("AdminMode_OldFaction", nil)
char:SetData("AdminMode_OldPodr", nil)
char:SetData("AdminMode_OldSpec", nil)
char:SetData("AdminMode_OldRank", nil)
char:SetData("AdminMode_OldModel", nil)
char:SetData("AdminMode_OldWeapons", nil)
ply:SetNetVar("HideFromTab", false)
local radio = ix.plugin.list["radio"]
if radio then
radio:SyncRadioState(ply, radio.defaultFrequency, false, false)
end
ply:ChatPrint("[!] Админ-мод выключен.")
end
hook.Run("PlayerAdminModeToggled", ply, enable)
end
function PLUGIN:EntityTakeDamage(target, dmg)
if IsValid(target) and target:IsPlayer() and target:IsAdminMode() then
return true
end
local attacker = dmg:GetAttacker()
if IsValid(attacker) and attacker:IsPlayer() and attacker:IsAdminMode() then
return true
end
end
function PLUGIN:OnCharacterVarChanged(char, key, oldValue, newValue)
if (key == "faction") then
local ply = char:GetPlayer()
if (IsValid(ply) and ply:IsAdminMode() and newValue ~= FACTION_ADMIN) then
self:ToggleAdminMode(ply)
end
end
end
function PLUGIN:PlayerNoClip(ply, desiredState)
if not IsValid(ply) then return false end
if CanUseNoclip(ply) then
ply:SetNoDraw(desiredState)
if desiredState then
ply:SetRenderMode(RENDERMODE_NONE)
ply:SetColor(Color(255, 255, 255, 0))
else
ply:SetRenderMode(RENDERMODE_NORMAL)
ply:SetColor(Color(255, 255, 255, 255))
end
if desiredState then
ply:SetNetVar("HideFromTab", true)
ply:SetNetVar("ixNoDraw", true)
else
if not ply:IsAdminMode() then
ply:SetNetVar("HideFromTab", false)
ply:SetNetVar("ixNoDraw", false)
end
end
return true
end
return false
end
ix.command.Add("admin", {
description = "Включить/выключить админ-мод.",
OnRun = function(self, ply)
PLUGIN:ToggleAdminMode(ply)
end
})
ix.command.Add("adm", {
description = "Синоним /admin.",
OnRun = function(self, ply)
PLUGIN:ToggleAdminMode(ply)
end
})
end
CLIENT_ADMIN_GROUPS = CLIENT_ADMIN_GROUPS or {}
if CLIENT then
net.Receive("AdminMode_Groups", function()
CLIENT_ADMIN_GROUPS = net.ReadTable()
end)
end

View File

@@ -0,0 +1 @@
local PLUGIN = PLUGIN

View File

@@ -0,0 +1,5 @@
include("shared.lua")
function ENT:Draw()
self:DrawModel()
end

View File

@@ -0,0 +1,51 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
function ENT:Initialize()
self:SetModel("models/sw/shared/airdrop_large.mdl")
self:SetSolid(SOLID_VPHYSICS)
self:PhysicsInit(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
local phys = self:GetPhysicsObject()
if (IsValid(phys)) then
phys:Wake()
end
end
function ENT:Use(activator)
if (IsValid(activator) and activator:IsPlayer()) then
if (self.opened) then return end
self.opened = true
local weaponClass = self.ixWeapon or "tacrp_m320"
local character = activator:GetCharacter()
local inventory = character and character:GetInventory()
if (inventory and !isnumber(inventory) and ix.item.list[weaponClass]) then
inventory:Add(weaponClass)
ix.util.Notify("Вы получили " .. (ix.item.list[weaponClass].name or weaponClass) .. " из аирдропа.", activator)
else
activator:Give(weaponClass)
ix.util.Notify("Вы получили " .. (self.ixWeapon or "неизвестное оружие") .. " из аирдропа.", activator)
end
if (IsValid(self.ixSmoke)) then
self.ixSmoke:Remove()
end
local effect = EffectData()
effect:SetOrigin(self:GetPos())
effect:SetScale(1)
util.Effect("Explosion", effect)
self:Remove()
end
end
function ENT:OnTakeDamage(damage)
if (damage:GetDamage() > 50) then
self:Use(damage:GetAttacker())
end
end

View File

@@ -0,0 +1,6 @@
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Airdrop"
ENT.Category = "Helix"
ENT.Spawnable = false
ENT.AdminOnly = true

View File

@@ -0,0 +1,92 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Airdrop"
PLUGIN.author = "Scripty"
PLUGIN.description = "Airdrop plugin."
ix.config.Add("airdropPlaneModel", "models/il76/il_76_fly.mdl", "Модель самолета для аирдропа.", nil, {
category = "Airdrop"
})
ix.config.Add("airstrikePlaneModel", "models/gunkov2056/su25.mdl", "Модель самолета для авиаудара.", nil, {
category = "Airdrop"
})
ix.config.Add("airdropMinInterval", 3600, "Минимальный интервал между аирдропами (в секундах).", nil, {
data = {min = 60, max = 86400},
category = "Airdrop"
})
ix.config.Add("airdropMaxInterval", 7200, "Максимальный интервал между аирдропами (в секундах).", nil, {
data = {min = 60, max = 86400},
category = "Airdrop"
})
ix.config.Add("airdropWeapons", [[tacrp_io_degala
tacrp_io_fiveseven
tacrp_mr96
tacrp_pdw
tacrp_superv
tacrp_sd_aac_hb
tacrp_ak_ak12
tacrp_ak_an94
tacrp_sg551
tacrp_io_xm8car
tacrp_hk417
tacrp_io_scarh
tacrp_mg4
tacrp_io_xm8lmg
tacrp_io_sg550r
tacrp_io_sl8
tacrp_io_sg550
tacrp_io_vss
tacrp_as50
tacrp_ex_hecate
tacrp_civ_m320]], "Список оружия, которое может выпасть из аирдропа (каждое с новой строки).", nil, {
category = "Airdrop"
})
ix.config.Add("airstrikeMinInterval", 1800, "Минимальный интервал между авиаударами (в секундах).", nil, {
data = {min = 60, max = 86400},
category = "Airdrop"
})
ix.config.Add("airstrikeMaxInterval", 3600, "Максимальный интервал между авиаударами (в секундах).", nil, {
data = {min = 60, max = 86400},
category = "Airdrop"
})
ix.command.Add("AirdropForce", {
description = "Принудительно вызвать случайный аирдроп.",
privilege = "Manage Airdrops",
superAdminOnly = true,
OnRun = function(self, client)
PLUGIN:SpawnAirdrop()
ix.util.Notify("Аирдроп был вызван администратором.", nil, "all")
end
})
ix.command.Add("AirdropGive", {
description = "Вызвать аирдроп с конкретным оружием.",
privilege = "Manage Airdrops",
superAdminOnly = true,
arguments = {
ix.type.string
},
OnRun = function(self, client, weapon)
PLUGIN:SpawnAirdrop(weapon)
ix.util.Notify("Аирдроп с " .. weapon .. " был вызван администратором.", nil, "all")
end
})
ix.command.Add("AirstrikeForce", {
description = "Принудительно вызвать авиаудар.",
privilege = "Manage Airdrops",
superAdminOnly = true,
OnRun = function(self, client)
PLUGIN:SpawnAirstrike()
ix.util.Notify("Авиаудар был вызван администратором.", nil, "all")
end
})
ix.util.Include("sv_plugin.lua")

View File

@@ -0,0 +1,329 @@
local PLUGIN = PLUGIN
-- Координаты баз для запрета авиаударов
local BASE_RF = {
min = Vector(-12686, 4350, -10000),
max = Vector(-8714, 12126, 10000)
}
local BASE_UK = {
min = Vector(9778, 2972, -10000),
max = Vector(13576, 10852, 10000)
}
local function IsInBase(pos)
if (pos:WithinAABox(BASE_RF.min, BASE_RF.max)) then return true end
if (pos:WithinAABox(BASE_UK.min, BASE_UK.max)) then return true end
return false
end
function PLUGIN:SpawnAirdrop(weapon)
local center = Vector(0, 0, 0)
local players = player.GetAll()
if (#players > 0) then
local targetPlayer = players[math.random(#players)]
center = targetPlayer:GetPos()
end
local angle = math.random(0, 360)
local radian = math.rad(angle)
local altitude = math.random(2000, 2500)
local startPos = center + Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
startPos.z = altitude
local endPos = center - Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
endPos.z = altitude
util.PrecacheModel(ix.config.Get("airdropPlaneModel", "models/gunkov2056/su25.mdl"))
local plane = ents.Create("prop_dynamic")
plane:SetModel(ix.config.Get("airdropPlaneModel", "models/gunkov2056/su25.mdl"))
plane:SetPos(startPos)
local angles = (endPos - startPos):Angle()
plane:SetAngles(angles)
plane:SetMoveType(MOVETYPE_FLY)
plane:Spawn()
plane:Activate()
print("[Airdrop] Спавн самолета: " .. plane:GetModel() .. " на " .. tostring(startPos))
local speed = 2500
local distance = startPos:Distance(endPos)
local duration = distance / speed
local dropFrac = math.Rand(0.45, 0.55)
local dropped = false
local startTime = CurTime()
plane:EmitSound("su25/su25.wav", 150, 100, 1, CHAN_AUTO)
timer.Create("AirdropPlaneMove_" .. plane:EntIndex(), 0.05, 0, function()
if (!IsValid(plane)) then return end
local elapsed = CurTime() - startTime
local frac = elapsed / duration
if (frac >= 1) then
plane:StopSound("su25/su25.wav")
plane:Remove()
timer.Remove("AirdropPlaneMove_" .. plane:EntIndex())
return
end
plane:SetPos(LerpVector(frac, startPos, endPos))
if (!dropped and frac >= dropFrac) then
dropped = true
self:DropCrate(plane:GetPos(), weapon)
end
end)
end
function PLUGIN:SpawnAirstrike()
ix.util.Notify("Внимание! Обнаружена воздушная угроза. Авиаудар через 30 секунд!", nil, "all")
timer.Simple(30, function()
local targetPos = Vector(0, 0, 0)
local players = player.GetAll()
local attempts = 0
-- Ищем валидную точку для удара (не в базе)
repeat
if (#players > 0) then
targetPos = players[math.random(#players)]:GetPos()
else
targetPos = Vector(math.random(-10000, 10000), math.random(-10000, 10000), 0)
end
attempts = attempts + 1
until (!IsInBase(targetPos) or attempts > 10)
if (IsInBase(targetPos)) then return end -- Отмена если не нашли безопасную точку
local angle = math.random(0, 360)
local radian = math.rad(angle)
local altitude = math.random(2000, 2500)
local startPos = targetPos + Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
startPos.z = altitude
local endPos = targetPos - Vector(math.cos(radian) * 15000, math.sin(radian) * 15000, 0)
endPos.z = altitude
util.PrecacheModel(ix.config.Get("airstrikePlaneModel", "models/gunkov2056/su25.mdl"))
local plane = ents.Create("prop_dynamic")
plane:SetModel(ix.config.Get("airstrikePlaneModel", "models/gunkov2056/su25.mdl"))
plane:SetPos(startPos)
plane:SetAngles((endPos - startPos):Angle())
plane:SetMoveType(MOVETYPE_FLY)
plane:Spawn()
plane:Activate()
print("[Airstrike] Спавн самолета: " .. plane:GetModel() .. " на " .. tostring(startPos))
local speed = 3000
local duration = startPos:Distance(endPos) / speed
local dropped = false
local startTime = CurTime()
plane:EmitSound("su25/su25.wav", 150, 100, 1, CHAN_AUTO)
timer.Create("AirstrikePlaneMove_" .. plane:EntIndex(), 0.05, 0, function()
if (!IsValid(plane)) then return end
local elapsed = CurTime() - startTime
local frac = elapsed / duration
if (frac >= 1) then
plane:StopSound("su25/su25.wav")
plane:Remove()
timer.Remove("AirstrikePlaneMove_" .. plane:EntIndex())
return
end
plane:SetPos(LerpVector(frac, startPos, endPos))
if (!dropped and frac >= 0.5) then
dropped = true
self:DropBomb(plane:GetPos(), plane:GetForward() * speed)
end
end)
end)
end
function PLUGIN:DropBomb(pos, velocity)
local bomb = ents.Create("sw_bomb_fab250m62_v3")
if (!IsValid(bomb)) then
-- Если энтити нет, создадим обычный взрыв для теста
local exp = ents.Create("env_explosion")
exp:SetPos(pos)
exp:SetKeyValue("iMagnitude", "300")
exp:Spawn()
exp:Fire("Explode", 0, 0)
return
end
bomb:SetPos(pos)
bomb:SetAngles(velocity:Angle())
bomb:Spawn()
bomb:Activate()
local phys = bomb:GetPhysicsObject()
if (IsValid(phys)) then
phys:SetVelocity(velocity)
end
end
function PLUGIN:DropCrate(pos, weapon)
if (!weapon) then
local weaponsConfig = ix.config.Get("airdropWeapons", "")
local weapons = {}
if (type(weaponsConfig) == "string" and weaponsConfig:Trim() != "") then
for _, v in pairs(string.Split(weaponsConfig, "\n")) do
local s = v:Trim()
if (s != "") then
table.insert(weapons, s)
end
end
end
-- Если конфиг пустой или старый, берем список по умолчанию
if (#weapons == 0) then
weapons = {
"tacrp_io_degala",
"tacrp_io_fiveseven",
"tacrp_mr96",
"tacrp_pdw",
"tacrp_superv",
"tacrp_sd_aac_hb",
"tacrp_ak_ak12",
"tacrp_ak_an94",
"tacrp_sg551",
"tacrp_io_xm8car",
"tacrp_hk417",
"tacrp_io_scarh",
"tacrp_mg4",
"tacrp_io_xm8lmg",
"tacrp_io_sg550r",
"tacrp_io_sl8",
"tacrp_io_sg550",
"tacrp_io_vss",
"tacrp_as50",
"tacrp_ex_hecate",
"tacrp_civ_m320"
}
end
weapon = weapons[math.random(#weapons)]
end
local crate = ents.Create("ix_airdrop")
if (!IsValid(crate)) then
print("[Airdrop] ОШИБКА: Не удалось создать энтити ix_airdrop!")
return
end
crate:SetPos(pos)
crate:SetAngles(Angle(0, math.random(0, 360), 0))
crate:Spawn()
crate.ixWeapon = weapon
-- Создаем парашют
util.PrecacheModel("models/jessev92/bf2/parachute.mdl")
local parachute = ents.Create("prop_dynamic")
parachute:SetModel("models/jessev92/bf2/parachute.mdl")
parachute:SetPos(crate:GetPos() + Vector(0, 0, 50))
parachute:SetParent(crate)
parachute:Spawn()
crate.ixParachute = parachute
local phys = crate:GetPhysicsObject()
if (IsValid(phys)) then
phys:Wake()
phys:SetVelocity(Vector(0, 0, -200)) -- Скорость падения
phys:SetDragCoefficient(5)
phys:SetAngleDragCoefficient(100)
end
local function StartSmoke()
if (!IsValid(crate)) then return end
local smoke = ents.Create("env_smokestack")
smoke:SetPos(crate:GetPos())
smoke:SetAngles(Angle(-90, 0, 0))
smoke:SetKeyValue("InitialState", "1")
smoke:SetKeyValue("rendercolor", "255 100 0")
smoke:SetKeyValue("renderamt", "255")
smoke:SetKeyValue("SmokeMaterial", "particle/smokesprites_0001.vmt")
smoke:SetKeyValue("BaseSpread", "20")
smoke:SetKeyValue("SpreadSpeed", "10")
smoke:SetKeyValue("Speed", "80")
smoke:SetKeyValue("StartSize", "30")
smoke:SetKeyValue("EndSize", "120")
smoke:SetKeyValue("Rate", "40")
smoke:SetKeyValue("JetLength", "300")
smoke:Spawn()
smoke:Activate()
smoke:SetParent(crate)
crate.ixSmoke = smoke
ix.util.Notify("Аирдроп приземлился! Ищите оранжевый дым.", nil, "all")
end
-- Принудительно держим ящик ровно во время падения
timer.Create("AirdropUpright_" .. crate:EntIndex(), 0.1, 0, function()
if (!IsValid(crate)) then
timer.Remove("AirdropUpright_" .. crate:EntIndex())
return
end
local phys = crate:GetPhysicsObject()
if (IsValid(phys) and phys:IsMotionEnabled()) then
crate:SetAngles(Angle(0, crate:GetAngles().y, 0))
phys:SetAngleVelocity(Vector(0, 0, 0))
else
timer.Remove("AirdropUpright_" .. crate:EntIndex())
end
end)
timer.Create("AirdropLand_" .. crate:EntIndex(), 0.2, 0, function()
if (!IsValid(crate)) then
timer.Remove("AirdropLand_" .. crate:EntIndex())
return
end
local phys = crate:GetPhysicsObject()
if (!IsValid(phys)) then return end
-- Если скорость мала или мы коснулись земли
local tr = util.TraceLine({
start = crate:GetPos() + Vector(0, 0, 10),
endpos = crate:GetPos() - Vector(0, 0, 40),
filter = crate
})
if (tr.Hit or phys:GetVelocity():Length() < 5) then
phys:EnableMotion(false)
crate:SetPos(tr.HitPos)
crate:SetAngles(Angle(0, crate:GetAngles().y, 0))
if (IsValid(crate.ixParachute)) then
crate.ixParachute:Remove()
end
StartSmoke()
timer.Remove("AirdropLand_" .. crate:EntIndex())
timer.Remove("AirdropUpright_" .. crate:EntIndex())
end
end)
timer.Simple(900, function()
if (IsValid(crate)) then
if (IsValid(crate.ixSmoke)) then crate.ixSmoke:Remove() end
crate:Remove()
end
end)
end
function PLUGIN:Initialize()
self:SetupAirdropTimer()
self:SetupAirstrikeTimer()
end
function PLUGIN:SetupAirdropTimer()
local delay = math.random(ix.config.Get("airdropMinInterval", 3600), ix.config.Get("airdropMaxInterval", 7200))
timer.Create("ixAirdropTimer", delay, 1, function()
self:SpawnAirdrop()
self:SetupAirdropTimer()
end)
end
function PLUGIN:SetupAirstrikeTimer()
local delay = math.random(ix.config.Get("airstrikeMinInterval", 1800), ix.config.Get("airstrikeMaxInterval", 3600))
timer.Create("ixAirstrikeTimer", delay, 1, function()
self:SpawnAirstrike()
self:SetupAirstrikeTimer()
end)
end

View File

@@ -0,0 +1,553 @@
-- Цветовая схема (в стиле остальных интерфейсов)
local PLUGIN = PLUGIN
local COLOR_BG_DARK = Color(3, 5, 4)
local COLOR_BG_MEDIUM = Color(8, 12, 10)
local COLOR_BG_LIGHT = Color(12, 18, 14)
local COLOR_PRIMARY = Color(27, 94, 32)
local COLOR_PRIMARY_HOVER = Color(33, 110, 38)
local COLOR_ACCENT = Color(56, 102, 35)
local COLOR_ACCENT_HOVER = Color(66, 120, 45)
local COLOR_BORDER = Color(46, 125, 50, 80)
local COLOR_TEXT_PRIMARY = Color(165, 214, 167)
local COLOR_TEXT_SECONDARY = Color(129, 199, 132)
local COLOR_TEXT_DIM = Color(100, 150, 105)
-- Хук для камеры во время анимации
function PLUGIN:CalcView(ply, pos, angles, fov)
local str = ply:GetNW2String('TauntAnim')
if str != "" then
if GetConVar("rp_taunt_firstperson"):GetBool() then
local eye_id = ply:LookupAttachment('eyes')
local att = eye_id > 0 and ply:GetAttachment(eye_id) or nil
if not att then return end
local ang1 = angles
if GetConVar("rp_taunt_realistic_firstperson"):GetBool() then
ang1 = att.Ang
end
local view = {
origin = att.Pos,
angles = ang1,
fov = fov,
drawviewer = true
}
return view
else
local view = {
origin = pos - (angles:Forward() * 100),
angles = angles,
fov = fov,
drawviewer = true
}
return view
end
end
end
function PLUGIN:ShouldDrawLocalPlayer(ply)
local str = ply:GetNW2String('TauntAnim')
if str != "" and not GetConVar("rp_taunt_firstperson"):GetBool() then
return true
end
end
-- Подсказка на экране
function PLUGIN:HUDPaint()
local ply = LocalPlayer()
local str = ply:GetNW2String('TauntAnim')
if str != "" then
draw.SimpleTextOutlined("Нажмите SHIFT, чтобы убрать анимацию.", "Trebuchet18", ScrW()/2, ScrH()-250, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, color_black)
end
end
-- Таблица анимаций (импортируем из существующего аддона)
local AnimationCategories = {
{
name = "ЖЕСТЫ",
anims = {
{id = 1, name = "Помахать руками"},
{id = 2, name = "Помахать рукой"},
{id = 3, name = "Показать пальцем вперед"},
{id = 4, name = "Показать пальцем назад"},
{id = 5, name = "Поправить галстук"},
}
},
{
name = "СПОРТ",
anims = {
{id = 6, name = "Приседания"},
{id = 7, name = "Отжимания"},
{id = 8, name = "Подъем корпуса"},
{id = 9, name = "Берпи"},
}
},
{
name = "ПОЗЫ",
anims = {
{id = 10, name = "Стоять злобно"},
{id = 11, name = "Стоять напуганно"},
{id = 12, name = "Стоять, сложив руки"},
{id = 13, name = "Стоять, руки на поясе"},
{id = 14, name = "Стоять, держась за пояс"},
{id = 15, name = "Сидеть, рука на колене"},
{id = 16, name = "Сидеть в позе лотоса"},
{id = 17, name = "Сидеть в сонном состоянии"},
{id = 18, name = "Лежать в плохом состоянии"},
}
},
{
name = "ТАНЦЫ",
anims = {
{id = 19, name = "Танец 1"},
{id = 20, name = "Танец 2"},
{id = 21, name = "Танец 3"},
{id = 22, name = "Танец 4"},
{id = 33, name = "Танец в присядь"},
}
},
{
name = "ДЕЙСТВИЯ",
anims = {
{id = 23, name = "Согласие"},
{id = 24, name = "Несогласие"},
{id = 25, name = "Позвать с собой"},
{id = 26, name = "Поклониться"},
{id = 27, name = "Отдать честь"},
{id = 28, name = "Пить кофе"},
{id = 29, name = "Смотреть на объект"},
{id = 30, name = "Записывать в блокнот"},
{id = 31, name = "Спать"},
{id = 32, name = "Воинское приветствие"},
}
}
}
local animMenu = nil
local animMenuOpen = false
-- Функция создания меню анимаций
local function CreateAnimationMenu()
if IsValid(animMenu) then
animMenu:Remove()
end
-- Невидимая панель на весь экран для перехвата ввода
local basePanel = vgui.Create("DPanel")
basePanel:SetSize(ScrW(), ScrH())
basePanel:SetPos(0, 0)
basePanel:MakePopup()
basePanel.Paint = function() end -- Полностью прозрачная
-- Меню в правом верхнем углу
local menuWidth = 320
local menuHeight = 500
local padding = 20
local menu = vgui.Create("DPanel", basePanel)
menu:SetSize(menuWidth, menuHeight)
menu:SetPos(ScrW() - menuWidth - padding, padding)
menu.Paint = function(s, w, h)
-- Основной фон
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
-- Заголовок
draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawLine(0, 45, w, 45)
-- Текст заголовка
draw.SimpleText("АНИМАЦИИ", "DermaLarge", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- Декоративная линия
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(10, 43, w-20, 2)
-- Внешняя граница
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
-- Подсказка внизу
draw.SimpleText("C - Закрыть | SHIFT - Остановить анимацию", "DermaDefault", w/2, h-15, COLOR_TEXT_DIM, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
-- Кнопка закрытия
local closeBtn = vgui.Create("DButton", menu)
closeBtn:SetPos(menuWidth - 35, 8)
closeBtn:SetSize(28, 28)
closeBtn:SetText("")
closeBtn.Paint = function(s, w, h)
local col = s:IsHovered() and COLOR_ACCENT_HOVER or COLOR_ACCENT
draw.RoundedBox(4, 0, 0, w, h, col)
draw.SimpleText("×", "DermaLarge", w/2, h/2-2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
closeBtn.DoClick = function()
basePanel:Remove()
animMenuOpen = false
end
-- Скролл панель для категорий
local scroll = vgui.Create("DScrollPanel", menu)
scroll:SetPos(10, 55)
scroll:SetSize(menuWidth - 20, menuHeight - 90)
local sbar = scroll:GetVBar()
sbar:SetWide(6)
sbar.Paint = function(s, w, h)
draw.RoundedBox(3, 0, 0, w, h, COLOR_BG_MEDIUM)
end
sbar.btnGrip.Paint = function(s, w, h)
draw.RoundedBox(3, 0, 0, w, h, COLOR_ACCENT)
end
sbar.btnUp.Paint = function() end
sbar.btnDown.Paint = function() end
-- Создаем категории с анимациями
for _, category in ipairs(AnimationCategories) do
-- Заголовок категории
local catHeader = vgui.Create("DPanel", scroll)
catHeader:Dock(TOP)
catHeader:DockMargin(0, 5, 0, 3)
catHeader:SetTall(28)
catHeader.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
draw.SimpleText(category.name, "DermaDefaultBold", 10, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
-- Анимации в категории
for _, anim in ipairs(category.anims) do
local animBtn = vgui.Create("DButton", scroll)
animBtn:Dock(TOP)
animBtn:DockMargin(3, 2, 3, 0)
animBtn:SetTall(32)
animBtn:SetText("")
animBtn.Paint = function(s, w, h)
local col = COLOR_BG_MEDIUM
if s:IsHovered() then
col = COLOR_PRIMARY_HOVER
-- Акцентная линия слева
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(0, 0, 3, h)
end
draw.RoundedBox(4, 0, 0, w, h, col)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
-- Иконка
draw.SimpleText("", "DermaDefault", 12, h/2, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- Название анимации
draw.SimpleText(anim.name, "DermaDefault", 28, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
animBtn.DoClick = function()
RunConsoleCommand("rp_set_taunt", tostring(anim.id))
surface.PlaySound("buttons/button15.wav")
basePanel:Remove()
animMenuOpen = false
end
end
end
-- Обработка закрытия по нажатию вне меню
basePanel.OnMousePressed = function(s, keyCode)
if keyCode == MOUSE_LEFT then
-- Проверяем, кликнули ли вне меню
local mx, my = input.GetCursorPos()
local x, y = menu:GetPos()
local w, h = menu:GetSize()
if mx < x or mx > x + w or my < y or my > y + h then
basePanel:Remove()
animMenuOpen = false
end
end
end
-- Меню рации в левом верхнем углу
local radioWidth = 320
local radioHeight = 200
local radioMenu = vgui.Create("DPanel", basePanel)
radioMenu:SetSize(radioWidth, radioHeight)
radioMenu:SetPos(padding, padding)
radioMenu.Paint = function(s, w, h)
-- Основной фон
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
-- Заголовок
draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawLine(0, 45, w, 45)
-- Текст заголовка
draw.SimpleText("ЧАСТОТА РАЦИИ", "DermaLarge", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- Декоративная линия
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(10, 43, w-20, 2)
-- Внешняя граница
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
-- Получаем плагин рации
local radioPlugin = ix.plugin.list["radio"]
if not radioPlugin then
radioMenu:SetVisible(false)
else
local currentFreq = radioPlugin:GetFrequency(LocalPlayer())
-- Текущая частота
local freqLabel = vgui.Create("DLabel", radioMenu)
freqLabel:SetPos(20, 60)
freqLabel:SetSize(radioWidth - 40, 25)
freqLabel:SetText("Текущая частота: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", currentFreq))
freqLabel:SetFont("DermaDefault")
freqLabel:SetTextColor(COLOR_TEXT_PRIMARY)
-- Слайдер частоты
local freqSlider = vgui.Create("DNumSlider", radioMenu)
freqSlider:SetPos(10, 90)
freqSlider:SetSize(radioWidth - 20, 40)
freqSlider:SetText("")
freqSlider:SetMin(radioPlugin.minFrequency)
freqSlider:SetMax(radioPlugin.maxFrequency)
freqSlider:SetDecimals(radioPlugin.frequencyPrecision)
freqSlider:SetValue(currentFreq)
freqSlider.Label:SetTextColor(COLOR_TEXT_SECONDARY)
freqSlider.Label:SetFont("DermaDefault")
freqSlider.TextArea:SetTextColor(COLOR_TEXT_PRIMARY)
freqSlider.TextArea:SetFont("DermaDefault")
freqSlider.TextArea:SetEditable(true)
freqSlider.TextArea:SetNumeric(true)
freqSlider.TextArea.Paint = function(panel, w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_MEDIUM)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
panel:DrawTextEntryText(COLOR_TEXT_PRIMARY, COLOR_ACCENT, COLOR_TEXT_PRIMARY)
end
-- Обработчик ввода вручную
freqSlider.TextArea.OnEnter = function(panel)
local val = tonumber(panel:GetValue())
if val then
val = math.Clamp(val, radioPlugin.minFrequency, radioPlugin.maxFrequency)
freqSlider:SetValue(val)
end
end
freqSlider.Slider.Paint = function(panel, w, h)
draw.RoundedBox(4, 0, h/2-2, w, 4, COLOR_BG_MEDIUM)
local progress = (freqSlider:GetValue() - freqSlider:GetMin()) / (freqSlider:GetMax() - freqSlider:GetMin())
draw.RoundedBox(4, 0, h/2-2, w * progress, 4, COLOR_ACCENT)
end
freqSlider.Slider.Knob.Paint = function(s, w, h)
draw.RoundedBox(w/2, 0, 0, w, h, COLOR_PRIMARY)
if s:IsHovered() then
draw.RoundedBox(w/2, 2, 2, w-4, h-4, COLOR_ACCENT_HOVER)
end
end
freqSlider.OnValueChanged = function(_, value)
local format = "%0." .. radioPlugin.frequencyPrecision .. "f"
freqLabel:SetText("Текущая частота: " .. string.format(format, value))
end
-- Кнопка применения
local btnWidth = (radioWidth - 50) / 2
local applyBtn = vgui.Create("DButton", radioMenu)
applyBtn:SetPos(20, 150)
applyBtn:SetSize(btnWidth, 35)
applyBtn:SetText("")
applyBtn.Paint = function(s, w, h)
local col = s:IsHovered() and COLOR_PRIMARY_HOVER or COLOR_PRIMARY
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("ПРИМЕНИТЬ", "DermaDefaultBold", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
applyBtn.DoClick = function()
net.Start("ixRadioSetFrequency")
net.WriteFloat(freqSlider:GetValue())
net.SendToServer()
LocalPlayer():Notify("Частота рации изменена на: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", freqSlider:GetValue()))
end
-- Кнопка ручного ввода
local manualBtn = vgui.Create("DButton", radioMenu)
manualBtn:SetPos(30 + btnWidth, 150)
manualBtn:SetSize(btnWidth, 35)
manualBtn:SetText("")
manualBtn.Paint = function(s, w, h)
local col = s:IsHovered() and COLOR_ACCENT_HOVER or COLOR_ACCENT
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("ВВЕСТИ", "DermaDefaultBold", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
manualBtn.DoClick = function()
Derma_StringRequest(
"Ввод частоты",
"Введите частоту рации (от " .. radioPlugin.minFrequency .. " до " .. radioPlugin.maxFrequency .. "):",
tostring(currentFreq),
function(text)
local freq = tonumber(text)
if freq then
freq = math.Clamp(freq, radioPlugin.minFrequency, radioPlugin.maxFrequency)
net.Start("ixRadioSetFrequency")
net.WriteFloat(freq)
net.SendToServer()
freqSlider:SetValue(freq)
LocalPlayer():Notify("Частота рации изменена на: " .. string.format("%0." .. radioPlugin.frequencyPrecision .. "f", freq))
else
LocalPlayer():Notify("Неверный формат частоты!")
end
end,
function() end
)
end
end
-- Кнопки включения/выключения рации (справа от меню рации)
if radioPlugin then
local controlsWidth = 150
local controlsHeight = 200
local controlsPanel = vgui.Create("DPanel", basePanel)
controlsPanel:SetSize(controlsWidth, controlsHeight)
controlsPanel:SetPos(padding + radioWidth + 10, padding)
controlsPanel.Paint = function(s, w, h)
-- Основной фон
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
-- Заголовок
draw.RoundedBoxEx(8, 0, 0, w, 45, COLOR_BG_MEDIUM, true, true, false, false)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawLine(0, 45, w, 45)
-- Текст заголовка
draw.SimpleText("РАЦИЯ", "DermaDefault", w/2, 22, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- Декоративная линия
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(10, 43, w-20, 2)
-- Внешняя граница
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
-- Кнопка включения прослушивания
local listenBtn = vgui.Create("DButton", controlsPanel)
listenBtn:SetPos(10, 60)
listenBtn:SetSize(controlsWidth - 20, 40)
listenBtn:SetText("")
listenBtn.Paint = function(s, w, h)
local isListening = radioPlugin:IsListening(LocalPlayer())
local col = isListening and COLOR_ACCENT or COLOR_BG_MEDIUM
if s:IsHovered() then
col = isListening and COLOR_ACCENT_HOVER or COLOR_PRIMARY_HOVER
end
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
local status = isListening and "ВКЛ" or "ВЫКЛ"
draw.SimpleText("Прослушивание", "DermaDefault", w/2, h/2 - 7, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(status, "DermaDefaultBold", w/2, h/2 + 7, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
listenBtn.DoClick = function()
net.Start("ixRadioToggleListen")
net.SendToServer()
timer.Simple(0.1, function()
local status = radioPlugin:IsListening(LocalPlayer()) and "включено" or "выключено"
LocalPlayer():Notify("Прослушивание рации " .. status)
end)
end
-- Кнопка включения передачи
local transmitBtn = vgui.Create("DButton", controlsPanel)
transmitBtn:SetPos(10, 110)
transmitBtn:SetSize(controlsWidth - 20, 40)
transmitBtn:SetText("")
transmitBtn.Paint = function(s, w, h)
local isTransmitting = radioPlugin:IsTransmitting(LocalPlayer())
local col = isTransmitting and COLOR_ACCENT or COLOR_BG_MEDIUM
if s:IsHovered() then
col = isTransmitting and COLOR_ACCENT_HOVER or COLOR_PRIMARY_HOVER
end
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
local status = isTransmitting and "ВКЛ" or "ВЫКЛ"
draw.SimpleText("Передача", "DermaDefault", w/2, h/2 - 7, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(status, "DermaDefaultBold", w/2, h/2 + 7, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
transmitBtn.DoClick = function()
net.Start("ixRadioToggleTransmit")
net.SendToServer()
timer.Simple(0.1, function()
local status = radioPlugin:IsTransmitting(LocalPlayer()) and "включена" or "выключена"
LocalPlayer():Notify("Передача рации " .. status)
end)
end
end
animMenu = basePanel
animMenuOpen = true
end
-- Блокировка стандартного Context Menu и открытие нашего
hook.Add("OnContextMenuOpen", "AnimationMenuOverride", function()
-- Проверка на Alt+C для администраторов (открывает стандартное меню)
if input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then
local client = LocalPlayer()
if IsValid(client) and client:GetCharacter() then
-- Проверка на права администратора
if client:IsAdmin() or client:IsSuperAdmin() then
return -- Пропускаем и открываем стандартное меню
end
end
end
if not animMenuOpen then
CreateAnimationMenu()
end
return false -- Блокируем стандартное меню
end)
-- Закрытие меню при закрытии Context Menu
hook.Add("OnContextMenuClose", "AnimationMenuClose", function()
if animMenuOpen and IsValid(animMenu) then
animMenu:Remove()
animMenuOpen = false
end
end)
-- Подсказка на HUD
--hook.Add("HUDPaint", "AnimationMenuHint", function()
-- if not animMenuOpen then
-- draw.SimpleTextOutlined("C - Открыть меню (анимации + рация)", "DermaDefault", ScrW() - 10, ScrH() - 30, COLOR_TEXT_DIM, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER, 1, Color(0, 0, 0, 200))
-- end
--end)

View File

@@ -0,0 +1,192 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Animation Context Menu"
PLUGIN.author = "RefoselTeamWork"
PLUGIN.description = "Замена стандартного Context Menu на меню выбора анимаций"
PLUGIN.AnimationTable = {
[1] = {name = "Помахать руками", anim = "rp_wave", loop = false},
[2] = {name = "Помахать рукой", anim = "gesture_wave_original", loop = false},
[3] = {name = "Показать пальцем вперед", anim = "rp_point", loop = false},
[4] = {name = "Показать пальцем назад", anim = "rp_point_back", loop = false},
[5] = {name = "Поправить галстук", anim = "menu_gman", loop = false},
[6] = {name = "Приседания", anim = "rp_sport1_loop", loop = true},
[7] = {name = "Отжимания", anim = "rp_sport2_loop", loop = true},
[8] = {name = "Подъем корпуса", anim = "rp_sport3_loop", loop = true},
[9] = {name = "Берпи", anim = "rp_sport4_loop", loop = true},
[10] = {name = "Стоять злобно", anim = "rp_angry_loop", loop = true},
[11] = {name = "Стоять напуганно", anim = "idle_all_scared", loop = true},
[12] = {name = "Стоять, сложив руки", anim = "pose_standing_01", loop = true},
[13] = {name = "Стоять, руки на поясе", anim = "pose_standing_02", loop = true},
[14] = {name = "Стоять, держась за пояс", anim = "rp_cop_idle", loop = true},
[15] = {name = "Сидеть, рука на колене", anim = "pose_ducking_01", loop = true},
[16] = {name = "Сидеть в позе лотоса", anim = "pose_ducking_02", loop = true},
[17] = {name = "Сидеть в сонном состоянии", anim = "rp_sit_loop", loop = true},
[18] = {name = "Лежать в плохом состоянии", anim = "rp_injured_loop", loop = true},
[19] = {name = "Танец 1", anim = "rp_dance1_loop", loop = true},
[20] = {name = "Танец 2", anim = "rp_dance2_loop", loop = true},
[21] = {name = "Танец 3", anim = "rp_dance3_loop", loop = true},
[23] = {name = "Согласие", anim = "gesture_agree_original", loop = false},
[24] = {name = "Несогласие", anim = "gesture_disagree_original", loop = false},
[25] = {name = "Позвать с собой", anim = "gesture_becon_original", loop = false},
[26] = {name = "Поклониться", anim = "gesture_bow_original", loop = false},
[27] = {name = "Отдать честь", anim = "gesture_salute_original", loop = false},
[28] = {name = "Пить кофе", anim = "rp_drinking", loop = true, prop = {model = "models/props_junk/garbage_coffeemug001a.mdl", spawnfunc = function(mod, ent)
local att = ent:LookupAttachment("anim_attachment_RH")
local lvec = Vector(1,0,1)
timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function()
if IsValid(mod) then
local tab = ent:GetAttachment(att)
if (tab) then
mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x)
mod:SetAngles(tab.Ang)
end
if !IsValid(ent) or !ent:Alive() then
timer.Remove("WhileThatPropExist"..ent:EntIndex())
mod:Remove()
end
else
timer.Remove("WhileThatPropExist"..ent:EntIndex())
end
end)
end}},
[29] = {name = "Смотреть на объект", anim = "rp_medic_idle", loop = true},
[30] = {name = "Записывать в блокнот", anim = "rp_writing", loop = true, prop = {model = "models/props_lab/clipboard.mdl", spawnfunc = function(mod, ent)
local att = ent:LookupAttachment("anim_attachment_RH")
local lvec = Vector(2,-2,2)
timer.Create("WhileThatPropExist"..ent:EntIndex(), 0, 0, function()
if IsValid(mod) then
local tab = ent:GetAttachment(att)
if (tab) then
mod:SetPos(tab.Pos+tab.Ang:Up()*lvec.z+tab.Ang:Right()*lvec.y+tab.Ang:Forward()*lvec.x)
mod:SetAngles(tab.Ang+Angle(140,0,-100))
end
if !IsValid(ent) or !ent:Alive() then
timer.Remove("WhileThatPropExist"..ent:EntIndex())
mod:Remove()
end
else
timer.Remove("WhileThatPropExist"..ent:EntIndex())
end
end)
end}},
[31] = {name = "Спать", anim = "rp_sleep", loop = true},
[32] = {name = "Воинское приветствие", anim = "rp_salute1", loop = true, loop_stop = true},
[33] = {name = "Танец в присядь", anim = "rp_dance_squat", loop = true},
}
if SERVER then
concommand.Add("rp_set_taunt", function(ply, cmd, args)
local arg = tonumber(args[1])
if arg and arg > 0 and arg <= #PLUGIN.AnimationTable then
local tab = PLUGIN.AnimationTable[arg]
PLUGIN:SetTAnimation(ply, tab.anim, not tab.loop, arg)
end
end)
function PLUGIN:SetTAnimation(ply, anim, autostop, id)
ply:SetNW2String('TauntAnim', anim)
ply:SetNW2Float('TauntID', id)
ply:SetNW2Float('TAnimDelay', select(2, ply:LookupSequence(anim)))
ply:SetNW2Float('TAnimStartTime', CurTime())
ply:SetCycle(0)
local wep = ply:GetActiveWeapon()
if IsValid(wep) and anim != "" then
ply.TauntPreviousWeapon = wep:GetClass()
ply:SetActiveWeapon(nil)
elseif anim == "" and isstring(ply.TauntPreviousWeapon) then
ply:SelectWeapon(ply.TauntPreviousWeapon)
ply.TauntPreviousWeapon = nil
end
if autostop then
local delay = select(2, ply:LookupSequence(anim))
timer.Create("TauntAGRP"..ply:EntIndex(), delay, 1, function()
if not IsValid(ply) then return end
local anim2 = ply:GetNW2String('TauntAnim')
if anim == anim2 then
PLUGIN:SetTAnimation(ply, "")
end
end)
end
end
function PLUGIN:PlayerSwitchWeapon(ply, oldWeapon, newWeapon)
local str = ply:GetNW2String('TauntAnim')
if str != "" then
return true
end
end
function PLUGIN:Think()
for _, ply in ipairs(player.GetAll()) do
local str = ply:GetNW2String('TauntAnim')
if str != "" and (ply:InVehicle() or not ply:Alive() or ply:WaterLevel() >= 2) then
PLUGIN:SetTAnimation(ply, "")
end
end
end
else
CreateConVar("rp_taunt_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1)
CreateConVar("rp_taunt_realistic_firstperson", 0, FCVAR_ARCHIVE, "", 0, 1)
end
function PLUGIN:SetupMove(ply, mvd, cmd)
local str = ply:GetNW2String('TauntAnim')
if str != "" then
mvd:SetMaxSpeed(1)
mvd:SetMaxClientSpeed(1)
if SERVER and ply:KeyDown(IN_SPEED) then
PLUGIN:SetTAnimation(ply, "")
end
end
end
function PLUGIN:CalcMainActivity(ply, vel)
local str = ply:GetNW2String('TauntAnim')
local num = ply:GetNW2Float('TAnimDelay')
local id = ply:GetNW2Float('TauntID')
local st = ply:GetNW2Float('TAnimStartTime')
if str != "" and ply:Alive() then
local ls = PLUGIN.AnimationTable[id] and PLUGIN.AnimationTable[id].loop_stop
if ply:GetCycle() >= 1 then
if not ls then
ply:SetCycle(0)
if SERVER then
ply:SetNW2Float('TAnimStartTime', CurTime())
end
end
else
ply:SetCycle((CurTime()-st)/num)
local tab = PLUGIN.AnimationTable[id] and PLUGIN.AnimationTable[id].prop
if CLIENT and istable(tab) and (not IsValid(ply.TauntProp) or ply.TauntProp:GetModel() != tab.model) then
if IsValid(ply.TauntProp) then
ply.TauntProp:Remove()
end
ply.TauntProp = ClientsideModel(tab.model)
tab.spawnfunc(ply.TauntProp, ply)
elseif CLIENT and not istable(tab) and IsValid(ply.TauntProp) then
ply.TauntProp:Remove()
end
end
return -1, ply:LookupSequence(str)
else
if SERVER then
ply:SetNW2Float('TauntID', 0)
elseif CLIENT and IsValid(ply.TauntProp) then
ply.TauntProp:Remove()
end
end
end
ix.util.Include("cl_plugin.lua")

View File

@@ -0,0 +1,706 @@
local PLUGIN = PLUGIN
local COLOR_BG_DARK = Color(3, 5, 4)
local COLOR_BG_MEDIUM = Color(8, 12, 10)
local COLOR_BG_LIGHT = Color(12, 18, 14)
local COLOR_PRIMARY = Color(27, 94, 32)
local COLOR_PRIMARY_DARK = Color(15, 60, 18)
local COLOR_ACCENT = Color(56, 102, 35)
local COLOR_TEXT_PRIMARY = Color(165, 214, 167)
local COLOR_TEXT_SECONDARY = Color(102, 187, 106)
local COLOR_DANGER = Color(136, 14, 14)
local COLOR_WARNING = Color(191, 130, 0)
local COLOR_BORDER = Color(15, 60, 18, 60)
if not draw.Circle then
function draw.Circle(x, y, radius, color)
local segmentCount = math.max(16, radius)
surface.SetDrawColor(color or color_white)
local circle = {}
for i = 0, segmentCount do
local angle = math.rad((i / segmentCount) * 360)
table.insert(circle, {
x = x + math.cos(angle) * radius,
y = y + math.sin(angle) * radius
})
end
surface.DrawPoly(circle)
end
end
if not surface.DrawCircle then
function surface.DrawCircle(x, y, radius, color)
draw.Circle(x, y, radius, color)
end
end
local COLOR_TEXT_SECONDARY = Color(174, 213, 129)
local COLOR_DANGER = Color(229, 57, 53)
local COLOR_WARNING = Color(255, 193, 7)
local COLOR_BORDER = Color(46, 125, 50, 100)
net.Receive("ixArsenalOpen", function()
local supply = net.ReadUInt(32)
local factionID = net.ReadUInt(8)
local weaponsData = net.ReadTable()
local weaponHas = net.ReadTable()
local armorData = net.ReadTable()
local freeRemain = net.ReadUInt(32)
if not weaponsData or type(weaponsData) ~= "table" then weaponsData = {} end
if not weaponHas or type(weaponHas) ~= "table" then weaponHas = {} end
if IsValid(PLUGIN.menu) then
PLUGIN.menu:Remove()
end
local scrW, scrH = ScrW(), ScrH()
local frame = vgui.Create("DFrame")
frame:SetSize(scrW, scrH)
frame:SetPos(0, 0)
frame:SetTitle("")
frame:SetDraggable(false)
frame:ShowCloseButton(false)
frame:MakePopup()
frame.Paint = function(s, w, h)
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawRect(0, 0, w, h)
local gradHeight = 300
for i = 0, gradHeight do
local alpha = (1 - i/gradHeight) * 40
surface.SetDrawColor(COLOR_PRIMARY.r, COLOR_PRIMARY.g, COLOR_PRIMARY.b, alpha)
surface.DrawRect(0, i, w, 1)
end
surface.SetDrawColor(COLOR_BG_MEDIUM)
surface.DrawRect(0, 0, w, 100)
surface.SetDrawColor(COLOR_PRIMARY)
surface.DrawRect(0, 100, w, 3)
surface.SetDrawColor(COLOR_BORDER)
for i = 0, w, 200 do
surface.DrawRect(i, 0, 1, 100)
end
draw.SimpleText("◆ ВОЕННЫЙ АРСЕНАЛ ◆", "ixMenuButtonFont", w/2, 35, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText("СИСТЕМА СНАБЖЕНИЯ", "ixSmallFont", w/2, 68, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
local closeBtn = vgui.Create("DButton", frame)
closeBtn:SetText("")
closeBtn:SetSize(42, 42)
closeBtn:SetPos(scrW - 60, 18)
closeBtn.Paint = function(s, w, h)
local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7)
draw.RoundedBox(4, 0, 0, w, h, col)
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawOutlinedRect(0, 0, w, h, 2)
surface.SetDrawColor(COLOR_TEXT_PRIMARY)
surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7)
surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7)
if s:IsHovered() then
surface.SetDrawColor(255, 255, 255, 30)
draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30))
end
end
closeBtn.DoClick = function() frame:Close() end
local infoPanel = vgui.Create("DPanel", frame)
infoPanel:SetSize(scrW - 80, 70)
infoPanel:SetPos(40, 115)
infoPanel.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_LIGHT)
surface.SetDrawColor(COLOR_PRIMARY)
surface.DrawOutlinedRect(0, 0, w, h, 2)
end
local supplyBox = vgui.Create("DPanel", infoPanel)
supplyBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall())
supplyBox:SetPos(5, 0)
supplyBox.Paint = function(s, w, h)
draw.SimpleText("ОЧКИ СНАБЖЕНИЯ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
draw.SimpleText(tostring(supply), "ixMenuButtonFont", 20, 32, COLOR_ACCENT, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
surface.SetDrawColor(COLOR_PRIMARY)
draw.NoTexture()
surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY)
end
local currencySymbol = "$"
if PLUGIN and PLUGIN.config and PLUGIN.config.currencySymbol and factionID then
currencySymbol = PLUGIN.config.currencySymbol[factionID] or currencySymbol
end
local function formatTime(seconds)
seconds = math.max(0, tonumber(seconds) or 0)
if seconds <= 0 then return "Готово" end
local h = math.floor(seconds / 3600)
local m = math.floor((seconds % 3600) / 60)
local s = seconds % 60
if h > 0 then
return string.format("%02d:%02d:%02d", h, m, s)
else
return string.format("%02d:%02d", m, s)
end
end
local cooldownBox = vgui.Create("DPanel", infoPanel)
cooldownBox:SetSize(infoPanel:GetWide() / 2 - 10, infoPanel:GetTall())
cooldownBox:SetPos(infoPanel:GetWide() / 2 + 5, 0)
cooldownBox.Paint = function(s, w, h)
draw.SimpleText("БЕСПЛАТНОЕ СНАРЯЖЕНИЕ", "ixSmallFont", 20, 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
local timeText = formatTime(freeRemain)
local timeColor = freeRemain <= 0 and COLOR_PRIMARY or COLOR_WARNING
draw.SimpleText(timeText, "ixMenuButtonFont", 20, 32, timeColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
if freeRemain > 0 then
surface.SetDrawColor(COLOR_WARNING)
draw.NoTexture()
surface.DrawCircle(w - 35, h/2, 12, COLOR_WARNING)
else
surface.SetDrawColor(COLOR_PRIMARY)
draw.NoTexture()
surface.DrawCircle(w - 35, h/2, 12, COLOR_PRIMARY)
end
end
local colWidth = (scrW - 120) / 2
local startY = 205
-- Панель оружия
local weaponsPanel = vgui.Create("DPanel", frame)
weaponsPanel:SetSize(colWidth, scrH - startY - 40)
weaponsPanel:SetPos(40, startY)
weaponsPanel.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
end
local weaponsHeader = vgui.Create("DPanel", weaponsPanel)
weaponsHeader:SetSize(weaponsPanel:GetWide(), 45)
weaponsHeader:SetPos(0, 0)
weaponsHeader.Paint = function(s, w, h)
draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
draw.SimpleText("▣ ОРУЖИЕ", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- Декоративная линия
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(0, h-2, w, 2)
end
local weaponsScroll = vgui.Create("DScrollPanel", weaponsPanel)
weaponsScroll:SetSize(weaponsPanel:GetWide() - 20, weaponsPanel:GetTall() - 60)
weaponsScroll:SetPos(10, 50)
local sbar = weaponsScroll:GetVBar()
sbar:SetHideButtons(true)
function sbar:Paint(w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
end
function sbar.btnGrip:Paint(w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
end
-- Функция для создания окна выбора оружия из категории
local function OpenCategoryWeapons(categoryName, categoryIcon, categoryWeapons)
local catFrame = vgui.Create("DFrame")
catFrame:SetSize(800, 600)
catFrame:Center()
catFrame:SetTitle("")
catFrame:SetDraggable(true)
catFrame:ShowCloseButton(false)
catFrame:MakePopup()
catFrame.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_DARK)
surface.SetDrawColor(COLOR_PRIMARY)
surface.DrawOutlinedRect(0, 0, w, h, 3)
-- Заголовок
draw.RoundedBoxEx(8, 0, 0, w, 60, COLOR_PRIMARY_DARK, true, true, false, false)
draw.SimpleText(categoryIcon .. " " .. categoryName, "ixMenuButtonFont", w/2, 30, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(0, 60, w, 2)
end
-- Кнопка закрытия
local catCloseBtn = vgui.Create("DButton", catFrame)
catCloseBtn:SetText("")
catCloseBtn:SetSize(35, 35)
catCloseBtn:SetPos(catFrame:GetWide() - 45, 12)
catCloseBtn.Paint = function(s, w, h)
local col = s:IsHovered() and COLOR_DANGER or Color(COLOR_DANGER.r * 0.7, COLOR_DANGER.g * 0.7, COLOR_DANGER.b * 0.7)
draw.RoundedBox(4, 0, 0, w, h, col)
surface.SetDrawColor(COLOR_TEXT_PRIMARY)
surface.DrawLine(w*0.3, h*0.3, w*0.7, h*0.7)
surface.DrawLine(w*0.7, h*0.3, w*0.3, h*0.7)
end
catCloseBtn.DoClick = function() catFrame:Close() end
-- Скролл с оружием
local catScroll = vgui.Create("DScrollPanel", catFrame)
catScroll:SetSize(catFrame:GetWide() - 30, catFrame:GetTall() - 85)
catScroll:SetPos(15, 70)
local catSbar = catScroll:GetVBar()
catSbar:SetHideButtons(true)
function catSbar:Paint(w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
end
function catSbar.btnGrip:Paint(w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
end
-- Создаем карточки оружия
for class, data in SortedPairsByMemberValue(categoryWeapons, "name", true) do
local has = weaponHas[class]
local row = vgui.Create("DPanel", catScroll)
row:SetSize(catScroll:GetWide() - 10, 110)
row:Dock(TOP)
row:DockMargin(0, 0, 0, 10)
local isHovered = false
row.Paint = function(s, w, h)
local bgColor = has and Color(45, 25, 20) or COLOR_BG_LIGHT
if isHovered then
bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10)
end
draw.RoundedBox(6, 0, 0, w, h, bgColor)
local statusColor = has and Color(205, 127, 50) or COLOR_PRIMARY
draw.RoundedBoxEx(6, 0, 0, 5, h, statusColor, true, false, true, false)
surface.SetDrawColor(has and Color(205, 127, 50, 80) or COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
if isHovered then
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(5, h-3, w-10, 3)
end
end
row.OnCursorEntered = function() isHovered = true end
row.OnCursorExited = function() isHovered = false end
-- Название
local nameLabel = vgui.Create("DLabel", row)
nameLabel:SetText(data.name or class)
nameLabel:SetFont("ixSmallFont")
nameLabel:SetTextColor(COLOR_TEXT_PRIMARY)
nameLabel:SetPos(15, 10)
nameLabel:SizeToContents()
-- Цена
local money = data.moneyPrice or 0
local supply = data.supplyPrice or 0
local priceLabel = vgui.Create("DLabel", row)
local priceText = ""
if (money or 0) <= 0 and (supply or 0) <= 0 then
priceText = "★ БЕСПЛАТНО"
else
local parts = {}
if money > 0 then table.insert(parts, currencySymbol .. tostring(money)) end
if supply > 0 then table.insert(parts, tostring(supply) .. " ОС") end
priceText = table.concat(parts, "")
end
priceLabel:SetText(priceText)
priceLabel:SetFont("ixSmallFont")
priceLabel:SetTextColor(money <= 0 and supply <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY)
priceLabel:SetPos(15, 40)
priceLabel:SizeToContents()
-- Донат метка
if data.donate then
local donateLabel = vgui.Create("DLabel", row)
donateLabel:SetText("★ ПРЕМИУМ")
donateLabel:SetFont("ixSmallFont")
donateLabel:SetTextColor(COLOR_WARNING)
donateLabel:SetPos(15, 70)
donateLabel:SizeToContents()
end
-- Кнопка действия
local btn = vgui.Create("DButton", row)
btn:SetSize(120, 40)
btn:SetPos(row:GetWide() - 130, 35)
btn:SetText("")
local btnText = has and "ВЕРНУТЬ" or "ВЗЯТЬ ►"
local btnColor = has and Color(165, 85, 60) or COLOR_PRIMARY
local btnColorHover = has and Color(205, 105, 70) or COLOR_ACCENT
btn.Paint = function(s, w, h)
local col = s:IsHovered() and btnColorHover or btnColor
draw.RoundedBox(6, 0, 0, w, h, col)
if s:IsHovered() then
surface.SetDrawColor(255, 255, 255, 30)
draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30))
end
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText(btnText, "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
btn.DoClick = function()
if has then
Derma_Query("Вернуть снаряжение? Фракция получит 80% стоимости.", "Возврат", "Подтвердить", function()
net.Start("ixArsenalAction")
net.WriteString("return_weapon")
net.WriteString(class)
net.SendToServer()
catFrame:Close()
frame:Close()
end, "Отмена", function() end)
else
local options = {}
if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end
if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end
if #options == 0 then
Derma_Message("Невозможно приобрести это оружие", "Ошибка", "OK")
return
end
if #options == 1 then
net.Start("ixArsenalAction")
net.WriteString("buy_weapon")
net.WriteString(class)
net.WriteString(options[1].method)
net.SendToServer()
catFrame:Close()
frame:Close()
else
local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function()
net.Start("ixArsenalAction")
net.WriteString("buy_weapon")
net.WriteString(class)
net.WriteString(options[1].method)
net.SendToServer()
catFrame:Close()
frame:Close()
end, options[2].text, function()
net.Start("ixArsenalAction")
net.WriteString("buy_weapon")
net.WriteString(class)
net.WriteString(options[2].method)
net.SendToServer()
catFrame:Close()
frame:Close()
end, "Отмена", function() end)
if IsValid(query) then
query:ShowCloseButton(true)
end
end
end
end
end
end
-- Панель брони
local armorPanel = vgui.Create("DPanel", frame)
armorPanel:SetSize(colWidth, scrH - startY - 40)
armorPanel:SetPos(scrW - colWidth - 40, startY)
armorPanel.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, COLOR_BG_MEDIUM)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
end
local armorHeader = vgui.Create("DPanel", armorPanel)
armorHeader:SetSize(armorPanel:GetWide(), 45)
armorHeader:SetPos(0, 0)
armorHeader.Paint = function(s, w, h)
draw.RoundedBoxEx(8, 0, 0, w, h, COLOR_PRIMARY_DARK, true, true, false, false)
draw.SimpleText("⬢ ЗАЩИТА", "ixSmallFont", 15, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- Декоративная линия
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(0, h-2, w, 2)
end
local armorScroll = vgui.Create("DScrollPanel", armorPanel)
armorScroll:SetSize(armorPanel:GetWide() - 20, armorPanel:GetTall() - 60)
armorScroll:SetPos(10, 50)
local sbar2 = armorScroll:GetVBar()
sbar2:SetHideButtons(true)
function sbar2:Paint(w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_BG_DARK)
end
function sbar2.btnGrip:Paint(w, h)
draw.RoundedBox(4, 0, 0, w, h, COLOR_PRIMARY)
end
-- Группировка оружия по категориям
local categories = {
{name = "Основное оружие", icon = "", key = "primary", weapons = {}},
{name = "Пистолеты", icon = "", key = "secondary", weapons = {}},
{name = "Гранаты", icon = "", key = "grenades", weapons = {}},
{name = "Холодное оружие", icon = "", key = "melee", weapons = {}},
{name = "Прочее", icon = "", key = "other", weapons = {}}
}
-- Распределяем оружие по категориям
for class, data in pairs(weaponsData) do
if type(data) == "table" then
local cat = data.category or "other"
local added = false
if cat == "primary" then
table.insert(categories[1].weapons, {class = class, data = data})
added = true
elseif cat == "secondary" then
table.insert(categories[2].weapons, {class = class, data = data})
added = true
elseif cat == "grenade1" or cat == "grenade2" or cat == "grenade" then
table.insert(categories[3].weapons, {class = class, data = data})
added = true
elseif cat == "melee" then
table.insert(categories[4].weapons, {class = class, data = data})
added = true
end
if not added then
table.insert(categories[5].weapons, {class = class, data = data})
end
end
end
-- Создаем кнопки категорий
local totalWeapons = 0
for _, category in ipairs(categories) do
if #category.weapons > 0 then
totalWeapons = totalWeapons + #category.weapons
local catButton = vgui.Create("DPanel", weaponsScroll)
catButton:SetSize(weaponsScroll:GetWide() - 10, 80)
catButton:Dock(TOP)
catButton:DockMargin(0, 0, 0, 10)
catButton:SetCursor("hand")
local isHovered = false
catButton.Paint = function(s, w, h)
local bgColor = COLOR_BG_LIGHT
if isHovered then
bgColor = Color(bgColor.r + 15, bgColor.g + 20, bgColor.b + 15)
end
draw.RoundedBox(6, 0, 0, w, h, bgColor)
-- Боковая полоска
draw.RoundedBoxEx(6, 0, 0, 6, h, COLOR_PRIMARY, true, false, true, false)
-- Рамка
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
-- Акцентная линия при наведении
if isHovered then
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(6, h-3, w-12, 3)
end
-- Иконка категории
draw.SimpleText(category.icon, "ixMenuButtonFont", 30, h/2, COLOR_ACCENT, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- Название категории
draw.SimpleText(category.name, "ixSmallFont", 60, h/2 - 12, COLOR_TEXT_PRIMARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- Количество оружия
draw.SimpleText(#category.weapons .. " ед.", "ixSmallFont", 60, h/2 + 12, COLOR_TEXT_SECONDARY, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- Стрелка
draw.SimpleText("", "ixSmallFont", w - 25, h/2, COLOR_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
catButton.OnCursorEntered = function() isHovered = true end
catButton.OnCursorExited = function() isHovered = false end
catButton.OnMousePressed = function()
-- Подготовка данных для окна категории
local categoryWeapons = {}
for _, wpn in ipairs(category.weapons) do
categoryWeapons[wpn.class] = wpn.data
end
OpenCategoryWeapons(category.name, category.icon, categoryWeapons)
end
end
end
-- Если нет оружия вообще
if totalWeapons == 0 then
local emptyLabel = vgui.Create("DPanel", weaponsScroll)
emptyLabel:SetSize(weaponsScroll:GetWide(), 100)
emptyLabel:Dock(TOP)
emptyLabel:DockMargin(0, 20, 0, 0)
emptyLabel.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
draw.SimpleText("НЕТ ДОСТУПНОГО ОРУЖИЯ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
-- Заполнение брони
if not armorData or type(armorData) ~= "table" then armorData = {} end
if table.Count(armorData) > 0 then
for class, data in SortedPairsByMemberValue(armorData, "name", true) do
local row = vgui.Create("DPanel", armorScroll)
row:SetSize(armorScroll:GetWide() - 10, 100)
row:Dock(TOP)
row:DockMargin(0, 0, 0, 10)
local isHovered = false
row.Paint = function(s, w, h)
local bgColor = COLOR_BG_LIGHT
if isHovered then
bgColor = Color(bgColor.r + 10, bgColor.g + 15, bgColor.b + 10)
end
draw.RoundedBox(6, 0, 0, w, h, bgColor)
-- Боковая полоска
draw.RoundedBoxEx(6, 0, 0, 5, h, COLOR_ACCENT, true, false, true, false)
-- Рамка
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 2)
-- Акцентная линия при наведении
if isHovered then
surface.SetDrawColor(COLOR_ACCENT)
surface.DrawRect(5, h-3, w-10, 3)
end
end
row.OnCursorEntered = function() isHovered = true end
row.OnCursorExited = function() isHovered = false end
-- Название брони
local nameLabel = vgui.Create("DLabel", row)
nameLabel:SetText(data.name or class)
nameLabel:SetFont("ixSmallFont")
nameLabel:SetTextColor(COLOR_TEXT_PRIMARY)
nameLabel:SetPos(15, 10)
nameLabel:SizeToContents()
-- Параметры брони
local amountLabel = vgui.Create("DLabel", row)
amountLabel:SetText("⬢ Защита: " .. tostring(data.amount or 0) .. " ед.")
amountLabel:SetFont("ixSmallFont")
amountLabel:SetTextColor(COLOR_TEXT_SECONDARY)
amountLabel:SetPos(15, 35)
amountLabel:SizeToContents()
-- Цена
local moneyP = data.moneyPrice or 0
local supplyP = data.supplyPrice or 0
local priceText = ""
if (moneyP or 0) <= 0 and (supplyP or 0) <= 0 then
priceText = "★ БЕСПЛАТНО"
else
local parts = {}
if moneyP > 0 then table.insert(parts, currencySymbol .. tostring(moneyP)) end
if supplyP > 0 then table.insert(parts, tostring(supplyP) .. " ОС") end
priceText = table.concat(parts, "")
end
local priceLabel = vgui.Create("DLabel", row)
priceLabel:SetText(priceText)
priceLabel:SetFont("ixSmallFont")
priceLabel:SetTextColor(moneyP <= 0 and supplyP <= 0 and COLOR_ACCENT or COLOR_TEXT_PRIMARY)
priceLabel:SetPos(15, 58)
priceLabel:SizeToContents()
-- Кнопка покупки
local btn = vgui.Create("DButton", row)
btn:SetSize(120, 40)
btn:SetPos(row:GetWide() - 130, 30)
btn:SetText("")
btn.Paint = function(s, w, h)
local col = s:IsHovered() and COLOR_ACCENT or COLOR_PRIMARY
draw.RoundedBox(6, 0, 0, w, h, col)
if s:IsHovered() then
surface.SetDrawColor(255, 255, 255, 30)
draw.RoundedBox(6, 2, 2, w-4, h-4, Color(255, 255, 255, 30))
end
surface.SetDrawColor(COLOR_BG_DARK)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("КУПИТЬ ►", "ixSmallFont", w/2, h/2, COLOR_TEXT_PRIMARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
btn.DoClick = function()
local options = {}
if (data.moneyPrice or 0) > 0 then table.insert(options, {text = "Купить за деньги", method = "money"}) end
if (data.supplyPrice or 0) >= 0 then table.insert(options, {text = "Купить за очки", method = "supply"}) end
if #options == 0 then
Derma_Message("Невозможно приобрести эту броню", "Ошибка", "OK")
return
end
if #options == 1 then
net.Start("ixArsenalAction")
net.WriteString("buy_armor")
net.WriteString(class)
net.WriteString(options[1].method)
net.SendToServer()
frame:Close()
else
local query = Derma_Query("Выберите способ оплаты:", "Оплата", options[1].text, function()
net.Start("ixArsenalAction")
net.WriteString("buy_armor")
net.WriteString(class)
net.WriteString(options[1].method)
net.SendToServer()
frame:Close()
end, options[2].text, function()
net.Start("ixArsenalAction")
net.WriteString("buy_armor")
net.WriteString(class)
net.WriteString(options[2].method)
net.SendToServer()
frame:Close()
end, "Отмена", function() end)
if IsValid(query) then
query:ShowCloseButton(true)
end
end
end
end
else
local emptyLabel = vgui.Create("DPanel", armorScroll)
emptyLabel:SetSize(armorScroll:GetWide(), 100)
emptyLabel:Dock(TOP)
emptyLabel:DockMargin(0, 20, 0, 0)
emptyLabel.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, COLOR_BG_LIGHT)
surface.SetDrawColor(COLOR_BORDER)
surface.DrawOutlinedRect(0, 0, w, h, 1)
draw.SimpleText("НЕТ ДОСТУПНОЙ ЗАЩИТЫ", "ixSmallFont", w/2, h/2 - 10, COLOR_TEXT_SECONDARY, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText("Обратитесь к командованию", "ixSmallFont", w/2, h/2 + 15, Color(COLOR_TEXT_SECONDARY.r * 0.7, COLOR_TEXT_SECONDARY.g * 0.7, COLOR_TEXT_SECONDARY.b * 0.7), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
PLUGIN.menu = frame
end)

View File

@@ -0,0 +1,234 @@
AddCSLuaFile()
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Ящик с патронами"
ENT.Author = "Server"
ENT.Spawnable = true
ENT.AdminOnly = true
ENT.Category = "[FT] Система арсенала"
ENT.bNoPersist = true
if SERVER then
function ENT:Initialize()
self:SetModel("models/trenches/prop/para_ammo/ammo_supply.mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
local phys = self:GetPhysicsObject()
if IsValid(phys) then
phys:Wake()
end
-- Таблица для хранения кулдаунов игроков
self.playerCooldowns = {}
end
-- Получить информацию о патронах для оружия
function ENT:GetWeaponAmmoInfo(weapon)
if not IsValid(weapon) then return nil end
local ammoType = weapon:GetPrimaryAmmoType()
if ammoType == -1 then return nil end
local clipSize = weapon:GetMaxClip1()
if clipSize <= 0 then return nil end
return {
ammoType = ammoType,
clipSize = clipSize,
ammoName = game.GetAmmoName(ammoType) or "unknown"
}
end
-- Использование ящика
function ENT:Use(activator, caller)
if not IsValid(activator) or not activator:IsPlayer() then return end
-- Проверка кулдауна (1 секунда)
local steamID = activator:SteamID()
local now = CurTime()
if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 1 then
return
end
local char = activator:GetCharacter()
if not char then
activator:Notify("У вас нет активного персонажа")
return
end
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if not plugin then
activator:Notify("Плагин арсенала не найден")
return
end
-- Проверка оружия в руках
local weapon = activator:GetActiveWeapon()
if not IsValid(weapon) then
activator:Notify("Возьмите оружие в руки")
return
end
-- Получение информации о патронах
local ammoInfo = self:GetWeaponAmmoInfo(weapon)
local isMedicalKit = weapon:GetClass() == "tacrp_ifak"
if not ammoInfo and not isMedicalKit then
activator:Notify("Это оружие не использует патроны")
return
end
-- Специальная логика для медицинской аптечки IFAK
if isMedicalKit then
-- Проверяем текущие запасы медицинских расходников
local bandages = activator:GetAmmoCount(game.GetAmmoID("Bandages"))
local quikclots = activator:GetAmmoCount(game.GetAmmoID("Quikclots"))
local hemostats = activator:GetAmmoCount(game.GetAmmoID("Hemostats"))
local maxBandages = 4
local maxQuikclots = 3
local maxHemostats = 2
if bandages >= maxBandages and quikclots >= maxQuikclots and hemostats >= maxHemostats then
activator:Notify("У вас уже полный комплект медицинских расходников")
return
end
-- Рассчитываем стоимость пополнения
local costPerItem = 5 -- стоимость за единицу
local totalCost = 0
local itemsToGive = {}
if bandages < maxBandages then
local needed = maxBandages - bandages
totalCost = totalCost + needed * costPerItem
itemsToGive["Bandages"] = needed
end
if quikclots < maxQuikclots then
local needed = maxQuikclots - quikclots
totalCost = totalCost + needed * costPerItem
itemsToGive["Quikclots"] = needed
end
if hemostats < maxHemostats then
local needed = maxHemostats - hemostats
totalCost = totalCost + needed * costPerItem
itemsToGive["Hemostats"] = needed
end
-- Проверка достаточно ли очков снабжения
if supply < totalCost then
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
return
end
-- Выдаем расходники
for ammoType, amount in pairs(itemsToGive) do
activator:GiveAmmo(amount, ammoType, true)
end
-- Списываем очки снабжения
plugin:AddFactionSupply(factionID, -totalCost)
-- Устанавливаем кулдаун
self.playerCooldowns[steamID] = now
-- Уведомление
local itemList = {}
for ammoType, amount in pairs(itemsToGive) do
table.insert(itemList, string.format("%s: +%d", ammoType, amount))
end
activator:Notify(string.format("Получены медицинские расходники: %s", table.concat(itemList, ", ")))
-- Звуковой эффект
self:EmitSound("items/ammo_pickup.wav")
-- Логирование
print(string.format("[AMMOBOX] %s получил медицинские расходники. Стоимость: %d очков",
activator:Nick(), totalCost))
return
end
-- Проверяем конфиг арсенала для этого оружия (одноразовые гранатомёты)
local weaponClass = weapon:GetClass()
local arsenalConfig = plugin.config and plugin.config.weapons and plugin.config.weapons[weaponClass]
-- Детект по конфигу ИЛИ по типу патронов — rpg_round и PanzerFaust3 Rocket всегда одноразовые
local launcherAmmoTypes = { ["rpg_round"] = true, ["PanzerFaust3 Rocket"] = true }
local isOneShotLauncher = (arsenalConfig and (arsenalConfig.maxAmmo == 1 or arsenalConfig.maxCount == 1))
or launcherAmmoTypes[ammoInfo.ammoName]
-- Проверка фракции
local factionID = char:GetFaction()
local supply = plugin:GetFactionSupply(factionID)
supply = tonumber(supply) or 0
if isOneShotLauncher then
-- Одноразовый гранатомёт: max 1 снаряд в запасе
local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
if currentAmmo >= 1 then
activator:Notify("У вас уже есть снаряд для этого оружия")
return
end
-- Стоимость 1 снаряда через costPerBullet
local totalCost = 1
if supply < totalCost then
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
return
end
-- Выдаём ровно 1 снаряд через SetAmmo (GiveAmmo выдаёт минимум 6 для rpg_round)
activator:SetAmmo(currentAmmo + 1, ammoInfo.ammoType)
plugin:AddFactionSupply(factionID, -totalCost)
self.playerCooldowns[steamID] = now
activator:Notify("Получен 1 снаряд")
self:EmitSound("items/ammo_pickup.wav")
else
-- Обычное оружие: проверка реального количества патронов у игрока
local currentAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
local maxAllowedAmmo = ammoInfo.clipSize * 6 -- 6 магазинов запаса
if currentAmmo >= maxAllowedAmmo then
local currentMags = math.floor(currentAmmo / ammoInfo.clipSize)
activator:Notify(string.format("У вас уже максимум патронов (%d магазинов, 6/6)", currentMags))
return
end
-- Рассчитываем количество патронов (1 магазин)
local totalAmmo = ammoInfo.clipSize
local costPerBullet = 1
local totalCost = totalAmmo * costPerBullet
if supply < totalCost then
activator:Notify(string.format("Недостаточно очков снабжения. Требуется: %d, доступно: %d", totalCost, supply))
return
end
-- Выдаем патроны
activator:GiveAmmo(totalAmmo, ammoInfo.ammoType, true)
plugin:AddFactionSupply(factionID, -totalCost)
self.playerCooldowns[steamID] = now
-- Подсчёт текущих магазинов после выдачи
local newAmmo = activator:GetAmmoCount(ammoInfo.ammoType)
local newMags = math.floor(newAmmo / ammoInfo.clipSize)
local magsStr = math.min(newMags, 6) .. "/6"
activator:Notify(string.format("Получено %d %s (магазинов: %s)", totalAmmo,
totalAmmo == 1 and "патрон" or (totalAmmo < 5 and "патрона" or "патронов"), magsStr))
self:EmitSound("items/ammo_pickup.wav")
end
end
end
if CLIENT then
function ENT:Draw()
self:DrawModel()
end
end

View File

@@ -0,0 +1,70 @@
AddCSLuaFile()
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Арсенал"
ENT.Author = "Server"
ENT.Spawnable = true
ENT.AdminOnly = true
ENT.Category = "[FT] Система арсенала"
ENT.bNoPersist = true
if SERVER then
function ENT:Initialize()
self:SetModel("models/ft/shkaf.mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
local phys = self:GetPhysicsObject()
if IsValid(phys) then phys:Wake() end
end
function ENT:Use(activator, caller)
if not IsValid(activator) or not activator:IsPlayer() then return end
local char = activator:GetCharacter()
if not char then return end
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if not plugin then return end
local avail = plugin:GetAvailableWeapons(char)
local weaponsData = {}
local weaponHas = {}
for class, data in pairs(avail) do
weaponsData[class] = data
weaponHas[class] = false
end
for _, wep in ipairs(activator:GetWeapons()) do
if IsValid(wep) then
local c = wep:GetClass()
if weaponHas[c] ~= nil then weaponHas[c] = true end
end
end
local supply = plugin:GetFactionSupply(char:GetFaction())
supply = tonumber(supply) or 0
local freeRemain = plugin:GetFreeWeaponCooldownForPlayer(activator)
local factionID = char:GetFaction()
net.Start("ixArsenalOpen")
net.WriteUInt(supply, 32)
net.WriteUInt(factionID or 0, 8)
net.WriteTable(weaponsData)
net.WriteTable(weaponHas)
net.WriteTable(plugin.config.armor or {})
net.WriteUInt(math.max(0, tonumber(freeRemain) or 0), 32)
net.Send(activator)
end
end
if CLIENT then
function ENT:Draw()
self:DrawModel()
--local pos = self:GetPos() + Vector(0,0,10)
--local ang = Angle(0, LocalPlayer():EyeAngles().y - 90, 90)
--cam.Start3D2D(pos, ang, 0.1)
-- draw.SimpleText("АРСЕНАЛ", "DermaDefaultBold", 0, 0, Color(200,200,200), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
--cam.End3D2D()
end
end

View File

@@ -0,0 +1,596 @@
AddCSLuaFile()
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Гардероб"
ENT.Author = "Server"
ENT.Spawnable = true
ENT.AdminOnly = true
ENT.Category = "[FT] Система арсенала"
ENT.bNoPersist = true
if SERVER then
function ENT:Initialize()
self:SetModel("models/props_wasteland/controlroom_storagecloset001a.mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
local phys = self:GetPhysicsObject()
if IsValid(phys) then phys:Wake() end
self.playerCooldowns = {}
end
function ENT:GetModelBodygroups(model)
local tempEnt = ents.Create("prop_dynamic")
if not IsValid(tempEnt) then return {} end
tempEnt:SetModel(model)
tempEnt:Spawn()
local bodygroups = {}
for i = 0, tempEnt:GetNumBodyGroups() - 1 do
local name = tempEnt:GetBodygroupName(i)
local count = tempEnt:GetBodygroupCount(i)
if count > 1 then
bodygroups[i] = {name = name, count = count, index = i}
end
end
tempEnt:Remove()
return bodygroups
end
function ENT:GetModelSkinCount(model)
local tempEnt = ents.Create("prop_dynamic")
if not IsValid(tempEnt) then return 0 end
tempEnt:SetModel(model)
tempEnt:Spawn()
local skinCount = tempEnt:SkinCount() or 0
tempEnt:Remove()
return skinCount
end
function ENT:IsBlacklisted(model, type, index)
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if not plugin or not plugin.config.wardrobeBlacklist then return false end
local blacklist = plugin.config.wardrobeBlacklist[model]
if not blacklist then return false end
if type == "bodygroup" and blacklist.bodygroups then
return table.HasValue(blacklist.bodygroups, index)
elseif type == "skin" and blacklist.skins then
return table.HasValue(blacklist.skins, index)
end
return false
end
function ENT:Use(activator)
if not IsValid(activator) or not activator:IsPlayer() then return end
if not self.playerCooldowns then self.playerCooldowns = {} end
local steamID = activator:SteamID()
local now = CurTime()
if self.playerCooldowns[steamID] and (now - self.playerCooldowns[steamID]) < 2 then return end
local char = activator:GetCharacter()
if not char then return end
local model = activator:GetModel()
if not model then return end
local bodygroups = self:GetModelBodygroups(model)
local skinCount = self:GetModelSkinCount(model)
local availableBodygroups = {}
for idx, data in pairs(bodygroups) do
if not self:IsBlacklisted(model, "bodygroup", idx) then
availableBodygroups[idx] = data
end
end
local availableSkins = {}
for i = 0, skinCount - 1 do
if not self:IsBlacklisted(model, "skin", i) then
table.insert(availableSkins, i)
end
end
local currentBodygroups = {}
for idx in pairs(availableBodygroups) do
currentBodygroups[idx] = activator:GetBodygroup(idx)
end
local currentSkin = activator:GetSkin()
local currentPatch = char:GetData("patchIndex", 1)
self.playerCooldowns[steamID] = now
net.Start("ixWardrobeOpen")
net.WriteString(model)
net.WriteTable(availableBodygroups)
net.WriteTable(availableSkins)
net.WriteTable(currentBodygroups)
net.WriteUInt(currentSkin, 8)
net.WriteUInt(currentPatch, 8)
net.Send(activator)
end
net.Receive("ixWardrobeApply", function(len, client)
if not IsValid(client) then return end
local char = client:GetCharacter()
if not char then return end
local isWipe = net.ReadBool()
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if isWipe then
client:SetSkin(0)
char:SetData("skin", 0)
char:SetData("patchIndex", 1)
char:SetData("bodygroups", nil)
local bgs = client:GetBodyGroups()
for _, bg in ipairs(bgs) do
client:SetBodygroup(bg.id, 0)
end
if plugin and plugin.config.patchIndex then
client:SetSubMaterial(plugin.config.patchIndex, "")
end
return
end
local bodygroups = net.ReadTable()
local skin = net.ReadUInt(8)
local patch = net.ReadUInt(8)
local model = client:GetModel()
local faction = char:GetFaction()
for idx, value in pairs(bodygroups) do
if plugin and plugin.config.wardrobeAccess and plugin.config.wardrobeAccess[model] then
local bAccess = plugin.config.wardrobeAccess[model][idx]
if bAccess and bAccess[value] then
if not table.HasValue(bAccess[value], faction) then
value = 0
end
end
end
client:SetBodygroup(idx, value)
end
client:SetSkin(skin)
char:SetData("bodygroups", bodygroups)
char:SetData("skin", skin)
char:SetData("patchIndex", patch)
if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then
local pMat = plugin.config.patchMaterials[patch]
if pMat then
client:SetSubMaterial(plugin.config.patchIndex, pMat)
else
client:SetSubMaterial(plugin.config.patchIndex, "")
end
end
end)
end
if CLIENT then
local C_BG_D = Color(3, 5, 4)
local C_BG_M = Color(8, 12, 10)
local C_BG_L = Color(12, 18, 14)
local C_PRI = Color(27, 94, 32)
local C_PRI_H = Color(33, 110, 38)
local C_ACC = Color(56, 102, 35)
local C_ACC_H = Color(66, 120, 45)
local C_BDR = Color(46, 125, 50, 80)
local C_TXT_P = Color(165, 214, 167)
local C_TXT_S = Color(129, 199, 132)
local C_GRAD = Color(27, 94, 32, 15)
local C_WARN = Color(150, 40, 40)
local C_WARN_H = Color(180, 50, 50)
function ENT:Draw()
self:DrawModel()
end
net.Receive("ixWardrobeOpen", function()
local model = net.ReadString()
local bodygroups = net.ReadTable()
local availableSkins = net.ReadTable()
local currentBodygroups = net.ReadTable()
local currentSkin = net.ReadUInt(8)
local currentPatch = net.ReadUInt(8)
if IsRecruit(ply) then
ply:Notify("Новоприбывший не имеет доступа к гардеробу.")
return
end
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if not plugin then return end
local frame = vgui.Create("DFrame")
frame:SetSize(900, 600)
frame:Center()
frame:SetTitle("")
frame:ShowCloseButton(false)
frame:MakePopup()
frame:SetBackgroundBlur(true)
frame.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, C_BG_D)
surface.SetDrawColor(C_GRAD)
surface.DrawRect(0, 0, w, 3)
draw.RoundedBoxEx(8, 0, 0, w, 50, C_BG_M, true, true, false, false)
surface.SetDrawColor(C_BDR)
surface.DrawLine(0, 50, w, 50)
draw.SimpleText("ГАРДЕРОБ", "DermaLarge", w/2, 25, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
surface.SetDrawColor(C_ACC)
surface.DrawRect(20, 48, 30, 2)
surface.DrawRect(w-50, 48, 30, 2)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
local closeBtn = vgui.Create("DButton", frame)
closeBtn:SetPos(frame:GetWide() - 40, 10)
closeBtn:SetSize(30, 30)
closeBtn:SetText("")
closeBtn.Paint = function(s, w, h)
local col = s:IsHovered() and C_ACC_H or C_ACC
draw.RoundedBox(4, 0, 0, w, h, col)
draw.SimpleText("X", "DermaLarge", w/2, h/2-2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
closeBtn.DoClick = function() frame:Close() end
local modelPanel = vgui.Create("DModelPanel", frame)
modelPanel:SetPos(20, 60)
modelPanel:SetSize(400, 460)
modelPanel:SetModel(model)
local oldPaint = modelPanel.Paint
modelPanel.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
oldPaint(s, w, h)
end
modelPanel.LayoutEntity = function(s, ent)
if not s.bAnimSet then
local seq = ent:LookupSequence("idle_subtle")
if seq <= 0 then seq = ent:LookupSequence("idle_all_01") end
if seq > 0 then
ent:SetSequence(seq)
end
s.bAnimSet = true
end
if (s.bDragging) then
local x, y = gui.MousePos()
s.camAnims.rot.yaw = s.camAnims.rot.yaw + (x - s.lastX) * 0.8
s.camAnims.rot.pitch = math.Clamp(s.camAnims.rot.pitch + (y - s.lastY) * 0.8, -45, 45)
s.lastX, s.lastY = x, y
end
local speed = FrameTime() * 10
s.curZoom = Lerp(speed, s.curZoom, s.camAnims.zoom)
s.curRot = LerpAngle(speed, s.curRot, s.camAnims.rot)
local eyePos = ent:GetPos() + Vector(0, 0, 40)
s:SetCamPos(eyePos - s.curRot:Forward() * s.curZoom)
s:SetLookAng(s.curRot)
ent:FrameAdvance((RealTime() - (s.lastTick or RealTime())) * 1)
s.lastTick = RealTime()
end
modelPanel.camAnims = { zoom = 60, rot = Angle(0, 180, 0) }
modelPanel.curZoom = 60
modelPanel.curRot = Angle(0, 180, 0)
local function SetCameraAngle(angle)
if angle == "front" then
modelPanel.camAnims.rot = Angle(0, 180, 0)
elseif angle == "side" then
modelPanel.camAnims.rot = Angle(0, 90, 0)
elseif angle == "back" then
modelPanel.camAnims.rot = Angle(0, 0, 0)
end
end
modelPanel.OnMousePressed = function(s, code)
if code == MOUSE_LEFT then
s.bDragging = true
s.lastX, s.lastY = gui.MousePos()
end
end
modelPanel.OnMouseReleased = function(s, code)
if code == MOUSE_LEFT then
s.bDragging = false
end
end
modelPanel.OnCursorExited = function(s)
s.bDragging = false
end
modelPanel.OnMouseWheeled = function(s, delta)
s.camAnims.zoom = math.Clamp(s.camAnims.zoom - delta * 15, 20, 120)
end
timer.Simple(0.05, function()
if IsValid(modelPanel) then SetCameraAngle("front") end
end)
modelPanel.Think = function(s)
local entity = s:GetEntity()
if IsValid(entity) then
for idx, value in pairs(currentBodygroups) do
if entity:GetBodygroup(idx) ~= value then
entity:SetBodygroup(idx, value)
end
end
if entity:GetSkin() ~= currentSkin then
entity:SetSkin(currentSkin)
end
if plugin and plugin.config.patchIndex and plugin.config.patchMaterials then
local pMat = plugin.config.patchMaterials[currentPatch]
if pMat then
entity:SetSubMaterial(plugin.config.patchIndex, pMat)
else
entity:SetSubMaterial(plugin.config.patchIndex, "")
end
end
end
end
local viewButtons = vgui.Create("DPanel", frame)
viewButtons:SetPos(20, 530)
viewButtons:SetSize(400, 50)
viewButtons.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
local bw = 125
local bs = 12
local frontBtn = vgui.Create("DButton", viewButtons)
frontBtn:SetPos(10, 10)
frontBtn:SetSize(bw, 30)
frontBtn:SetText("")
frontBtn.Paint = function(s, w, h)
local col = s:IsHovered() and C_PRI_H or C_PRI
draw.RoundedBox(4, 0, 0, w, h, col)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
draw.SimpleText("СПЕРЕДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
frontBtn.DoClick = function() SetCameraAngle("front") end
local sideBtn = vgui.Create("DButton", viewButtons)
sideBtn:SetPos(10 + bw + bs, 10)
sideBtn:SetSize(bw, 30)
sideBtn:SetText("")
sideBtn.Paint = function(s, w, h)
local col = s:IsHovered() and C_PRI_H or C_PRI
draw.RoundedBox(4, 0, 0, w, h, col)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
draw.SimpleText("СБОКУ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
sideBtn.DoClick = function() SetCameraAngle("side") end
local backBtn = vgui.Create("DButton", viewButtons)
backBtn:SetPos(10 + (bw + bs) * 2, 10)
backBtn:SetSize(bw, 30)
backBtn:SetText("")
backBtn.Paint = function(s, w, h)
local col = s:IsHovered() and C_PRI_H or C_PRI
draw.RoundedBox(4, 0, 0, w, h, col)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
draw.SimpleText("СЗАДИ", "DermaDefault", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
backBtn.DoClick = function() SetCameraAngle("back") end
local settingsPanel = vgui.Create("DPanel", frame)
settingsPanel:SetPos(440, 60)
settingsPanel:SetSize(440, 520)
settingsPanel.Paint = function(s, w, h)
draw.RoundedBox(6, 0, 0, w, h, C_BG_M)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
local settingsScroll = vgui.Create("DScrollPanel", settingsPanel)
settingsScroll:Dock(FILL)
settingsScroll:DockMargin(10, 10, 10, 140)
local sbar = settingsScroll:GetVBar()
sbar:SetWide(8)
sbar.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_BG_D) end
sbar.btnGrip.Paint = function(s, w, h) draw.RoundedBox(4, 0, 0, w, h, C_ACC) end
sbar.btnUp.Paint = function() end
sbar.btnDown.Paint = function() end
local yPos = 5
if plugin and plugin.config.patchMaterials and #plugin.config.patchMaterials > 0 then
local patchLabel = vgui.Create("DPanel", settingsScroll)
patchLabel:SetPos(10, yPos)
patchLabel:SetSize(400, 35)
patchLabel.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
surface.SetDrawColor(C_ACC)
surface.DrawRect(0, 0, 3, h)
draw.SimpleText("ПАТЧИ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
yPos = yPos + 40
local pH = vgui.Create("DPanel", settingsScroll)
pH:SetPos(10, yPos)
pH:SetSize(400, 65)
pH.Paint = function(s, w, h)
local col = s:IsHovered() and C_BG_L or C_BG_D
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
local pS = vgui.Create("DNumSlider", pH)
pS:SetPos(5, 15)
pS:SetSize(390, 40)
pS:SetMin(1)
pS:SetMax(#plugin.config.patchMaterials)
pS:SetDecimals(0)
pS:SetValue(currentPatch)
pS:SetText("Тип патча")
pS.Slider.Paint = function(s, w, h)
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
local maxV = math.max(1, pS:GetMax() - pS:GetMin())
local frac = (pS:GetValue() - pS:GetMin()) / maxV
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
end
pS.Slider.Knob.Paint = function(s, w, h)
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
end
pS.OnValueChanged = function(s, value) currentPatch = math.floor(value) end
yPos = yPos + 70
end
if table.Count(bodygroups) > 0 then
local bgLabel = vgui.Create("DPanel", settingsScroll)
bgLabel:SetPos(10, yPos)
bgLabel:SetSize(400, 35)
bgLabel.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
surface.SetDrawColor(C_ACC)
surface.DrawRect(0, 0, 3, h)
draw.SimpleText("ЧАСТИ ТЕЛА", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
yPos = yPos + 40
for idx, data in SortedPairsByMemberValue(bodygroups, "index") do
local bgPanel = vgui.Create("DPanel", settingsScroll)
bgPanel:SetPos(10, yPos)
bgPanel:SetSize(400, 65)
bgPanel.Paint = function(s, w, h)
local col = s:IsHovered() and C_BG_L or C_BG_D
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
local label = vgui.Create("DLabel", bgPanel)
label:SetPos(10, 8)
label:SetSize(380, 20)
label:SetFont("DermaDefault")
label:SetText(data.name)
label:SetTextColor(C_TXT_P)
local slider = vgui.Create("DNumSlider", bgPanel)
slider:SetPos(5, 28)
slider:SetSize(390, 30)
slider:SetMin(0)
slider:SetMax(data.count - 1)
slider:SetDecimals(0)
slider:SetValue(currentBodygroups[idx] or 0)
slider:SetText("")
slider.Slider.Paint = function(s, w, h)
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
local maxV = math.max(1, slider:GetMax() - slider:GetMin())
local frac = (slider:GetValue() - slider:GetMin()) / maxV
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
end
slider.Slider.Knob.Paint = function(s, w, h)
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
end
slider.OnValueChanged = function(s, value) currentBodygroups[idx] = math.floor(value) end
yPos = yPos + 70
end
end
if #availableSkins > 1 then
local skinLabel = vgui.Create("DPanel", settingsScroll)
skinLabel:SetPos(10, yPos)
skinLabel:SetSize(400, 35)
skinLabel.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, C_BG_L)
surface.SetDrawColor(C_ACC)
surface.DrawRect(0, 0, 3, h)
draw.SimpleText("СКИНЫ", "DermaLarge", 10, h/2, C_TXT_P, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
yPos = yPos + 40
local skinPanel = vgui.Create("DPanel", settingsScroll)
skinPanel:SetPos(10, yPos)
skinPanel:SetSize(400, 65)
skinPanel.Paint = function(s, w, h)
local col = s:IsHovered() and C_BG_L or C_BG_D
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 1)
end
local skinSlider = vgui.Create("DNumSlider", skinPanel)
skinSlider:SetPos(5, 15)
skinSlider:SetSize(390, 40)
skinSlider:SetMin(0)
skinSlider:SetMax(#availableSkins)
skinSlider:SetDecimals(0)
skinSlider:SetValue(currentSkin)
skinSlider:SetText("Скин")
skinSlider.Slider.Paint = function(s, w, h)
draw.RoundedBox(4, 0, h/2-2, w, 4, C_BG_D)
local maxV = math.max(1, skinSlider:GetMax() - skinSlider:GetMin())
local frac = (skinSlider:GetValue() - skinSlider:GetMin()) / maxV
draw.RoundedBox(4, 0, h/2-2, w * frac, 4, C_ACC)
end
skinSlider.Slider.Knob.Paint = function(s, w, h)
draw.RoundedBox(w/2, 0, 0, w, h, C_PRI)
if s:IsHovered() then draw.RoundedBox(w/2, 2, 2, w-4, h-4, C_ACC_H) end
end
skinSlider.OnValueChanged = function(s, value) currentSkin = math.floor(value) end
yPos = yPos + 70
end
local wipeBtn = vgui.Create("DButton", settingsPanel)
wipeBtn:SetPos(10, 390)
wipeBtn:SetSize(420, 50)
wipeBtn:SetText("")
wipeBtn.Paint = function(s, w, h)
local col = s:IsHovered() and C_WARN_H or C_WARN
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("СБРОСИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
wipeBtn.DoClick = function()
net.Start("ixWardrobeApply")
net.WriteBool(true)
net.SendToServer()
frame:Close()
end
local applyBtn = vgui.Create("DButton", settingsPanel)
applyBtn:SetPos(10, 450)
applyBtn:SetSize(420, 60)
applyBtn:SetText("")
applyBtn.Paint = function(s, w, h)
local col = s:IsHovered() and C_PRI_H or C_PRI
draw.RoundedBox(6, 0, 0, w, h, col)
surface.SetDrawColor(C_GRAD)
surface.DrawRect(0, 0, w, h/2)
surface.SetDrawColor(C_BDR)
surface.DrawOutlinedRect(0, 0, w, h, 2)
draw.SimpleText("ПРИМЕНИТЬ", "DermaLarge", w/2, h/2, C_TXT_P, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
applyBtn.DoClick = function()
net.Start("ixWardrobeApply")
net.WriteBool(false)
net.WriteTable(currentBodygroups)
net.WriteUInt(currentSkin, 8)
net.WriteUInt(currentPatch, 8)
net.SendToServer()
frame:Close()
end
end)
end

View File

@@ -0,0 +1,979 @@
-- проверка пениса
local PLUGIN = PLUGIN
PLUGIN.config = {
currencyName = "Очки снабжения",
-- Начальное снабжение для фракций
startSupply = {
[FACTION_RUSSIAN or 1] = 2000,
[FACTION_UKRAINE or 2] = 2000,
}, -- [FACTION_ID] = amount
-- Максиммальное снабжение
maxSupply = 20000,
-- Минимум, при котором арсенал считается закрытым
minSupply = 0,
-- Cooldown for free weapons (supplyPrice == 0), per-player, seconds
freeWeaponCooldown = 3600, -- default 1 hour
-- Currency symbols per faction (client uses this to display money symbol)
currencySymbol = {
[FACTION_RUSSIAN or 1] = "",
[FACTION_UKRAINE or 2] = "$",
},
weapons = {
["tfa_inss_wpn_ak12"] = {
["category"] = "primary",
["moneyPrice"] = 11000,
["supplyPrice"] = 220,
["name"] = "AK-12"
},
["tacrp_mr96"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 1,
["name"] = "MR-96"
},
["tacrp_ak_ak74u"] = {
["category"] = "primary",
["moneyPrice"] = 3000,
["supplyPrice"] = 50,
["name"] = "AKС-74У"
},
["tacrp_io_glock18"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "Glock 18C"
},
["tacrp_ks23"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "КС-23"
},
["tacrp_as50"] = {
["category"] = "primary",
["moneyPrice"] = 20000,
["supplyPrice"] = 1000,
["name"] = "AS50"
},
["weapon_lvsrepair"] = {
["category"] = "tool1",
["moneyPrice"] = 100,
["supplyPrice"] = 10,
["name"] = "Сварка"
},
["tacrp_io_trg42"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 450,
["name"] = "Sako TRG-42"
},
["tacrp_sd_dual_degala"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 1,
["name"] = "Двойные Deagle"
},
["weapon_cigarette_camel"] = {
["category"] = "tool1",
["moneyPrice"] = 100,
["supplyPrice"] = 10,
["name"] = "Сигаретка"
},
["tacrp_io_sg550"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 200,
["name"] = "SIG SG 550"
},
["tacrp_io_rpk"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 150,
["name"] = "РПК-74"
},
["tacrp_uzi"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "UZI"
},
["parachute_swep"] = {
["category"] = "tool4",
["moneyPrice"] = 150,
["supplyPrice"] = 10,
["name"] = "Парашют"
},
["tacrp_hk417"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "HK 417"
},
["tacrp_sd_bizon"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "ПП-19 Бизон"
},
["weapon_sw_at4"] = {
["moneyPrice"] = 4500,
["supplyPrice"] = 450,
["maxCount"] = 1,
["ammoType"] = "rpg_round",
["category"] = "heavy",
["maxAmmo"] = 1,
["name"] = "AT4"
},
["fas2_ifak"] = {
["category"] = "tool7",
["moneyPrice"] = 500,
["supplyPrice"] = 50,
["name"] = "Аптечка"
},
["bandage"] = {
["category"] = "tool7",
["moneyPrice"] = 500,
["supplyPrice"] = 100,
["name"] = "Бинт"
},
["tacrp_io_saiga"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "Сайга-12К"
},
["tacrp_nade_frag"] = {
["category"] = "grenade",
["moneyPrice"] = 300,
["supplyPrice"] = 30,
["name"] = "Осколочная граната"
},
["tacrp_g36k"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "G36K"
},
["tfa_inss_wpn_l1a1"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 240,
["name"] = "L1A1"
},
["bodycam_tablet"] = {
["category"] = "tool3",
["moneyPrice"] = 500,
["supplyPrice"] = 50,
["name"] = "Боди-камера"
},
["gmod_camera"] = {
["category"] = "tool3",
["moneyPrice"] = 50,
["supplyPrice"] = 10,
["name"] = "Камера"
},
["admin_defib"] = {
["category"] = "tool7",
["moneyPrice"] = 8000,
["supplyPrice"] = 5000,
["name"] = "Дефибриллятор"
},
["tacrp_sg551"] = {
["category"] = "primary",
["moneyPrice"] = 20000,
["supplyPrice"] = 200,
["name"] = "SG 551"
},
["tacrp_knife"] = {
["category"] = "melee",
["moneyPrice"] = 50,
["supplyPrice"] = 0,
["name"] = "Нож"
},
["guitar"] = {
["category"] = "tool3",
["moneyPrice"] = 200,
["supplyPrice"] = 10,
["name"] = "Гитара"
},
["v92_bf2_medikit"] = {
["category"] = "tool9",
["moneyPrice"] = 1500,
["supplyPrice"] = 150,
["name"] = "Аптечка"
},
["weapon_sw_9k38"] = {
["category"] = "heavy",
["moneyPrice"] = 5000,
["supplyPrice"] = 500,
["name"] = "9K38"
},
["tacrp_mp7"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "MP7"
},
["weapon_lvsspikestrip"] = {
["category"] = "tool2",
["moneyPrice"] = 100,
["supplyPrice"] = 10,
["name"] = "Шипы"
},
["tacrp_p250"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 1,
["name"] = "P250"
},
["tacrp_m4"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "HK416"
},
["tfa_inss_wpn_m16a4"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 240,
["name"] = "M16A4"
},
["tacrp_ak_svd"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 450,
["name"] = "СВД"
},
["tacrp_io_xm8lmg"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 200,
["name"] = "ХМ8 LMG"
},
["tacrp_mg4"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 300,
["name"] = "HK MG4"
},
["tacrp_io_degala"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 1,
["name"] = "Desert Eagle"
},
["tacrp_mp5"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "MP5A3"
},
["special_bandage"] = {
["category"] = "tool8",
["moneyPrice"] = 1000,
["supplyPrice"] = 500,
["name"] = "Шина от переломов"
},
["tacrp_ak_aek971"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "АЕК-971"
},
["tacrp_ak_ak12"] = {
["category"] = "primary",
["moneyPrice"] = 11000,
["supplyPrice"] = 200,
["name"] = "АК-12"
},
["weapon_sw_rpg26"] = {
["moneyPrice"] = 4500,
["supplyPrice"] = 450,
["maxCount"] = 1,
["ammoType"] = "rpg_round",
["category"] = "heavy",
["maxAmmo"] = 1,
["name"] = "РПГ-26"
},
["tacrp_spr"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "M700"
},
["tacrp_ex_m4a1"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 300,
["name"] = "М4А1"
},
["tfa_new_inss_mk18"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 240,
["name"] = "MK18"
},
["tacrp_io_fiveseven"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 1,
["name"] = "FiveSeven"
},
["engineertoolmines"] = {
["category"] = "tool5",
["moneyPrice"] = 50000,
["supplyPrice"] = 1000,
["name"] = "Мины"
},
["tacrp_pdw"] = {
["category"] = "primary",
["moneyPrice"] = 3000,
["supplyPrice"] = 100,
["name"] = "KAC PDW"
},
["tacrp_ak_an94"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "АН-94"
},
["weapon_r_handcuffs"] = {
["category"] = "tool8",
["moneyPrice"] = 1000,
["supplyPrice"] = 10,
["name"] = "Наручники"
},
["tfa_inss_wpn_fn_fal"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 240,
["name"] = "FN FAL"
},
["weapon_rope_knife"] = {
["category"] = "tool6",
["moneyPrice"] = 50000,
["supplyPrice"] = 500,
["name"] = "Крюк кошка"
},
["tacrp_io_sg550r"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 300,
["name"] = "SG-550R"
},
["weapon_sw_fim92"] = {
["category"] = "heavy",
["moneyPrice"] = 5000,
["supplyPrice"] = 500,
["name"] = "FIM-92"
},
["tacrp_sd_groza"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "ОЦ-14"
},
["tacrp_ifak"] = {
["category"] = "tool7",
["moneyPrice"] = 500,
["supplyPrice"] = 50,
["name"] = "Аптечка IFAK"
},
["tacrp_aug"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 250,
["name"] = "AUG"
},
["weapon_sw_panzerfaust3"] = {
["moneyPrice"] = 5000,
["supplyPrice"] = 500,
["maxCount"] = 1,
["ammoType"] = "PanzerFaust3 Rocket",
["category"] = "heavy",
["maxAmmo"] = 1,
["name"] = "Панцерфауст 3"
},
["tacrp_io_scarh"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "FN SCAR"
},
["tacrp_io_xm8car"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 250,
["name"] = "XM8"
},
["tacrp_pa_makarov"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 1,
["name"] = "ПМ"
},
["tacrp_bekas"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "Bekas"
},
["weapon_cuff_elastic"] = {
["category"] = "tool8",
["moneyPrice"] = 300,
["supplyPrice"] = 20,
["name"] = "Наручники"
},
["tacrp_ex_hecate"] = {
["category"] = "primary",
["moneyPrice"] = 17000,
["supplyPrice"] = 300,
["name"] = "PGM Hecate"
},
["tacrp_ex_glock"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 20,
["name"] = "Glock"
},
["tfa_ins2_aks_r"] = {
["category"] = "primary",
["moneyPrice"] = 11000,
["supplyPrice"] = 220,
["name"] = "AKS-74U"
},
["tacrp_ak_ak74"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 50,
["name"] = "АК-74"
},
["tacrp_io_sl8"] = {
["category"] = "primary",
["moneyPrice"] = 9000,
["supplyPrice"] = 180,
["name"] = "SL8"
},
["tfa_ins2_moe_akm"] = {
["category"] = "primary",
["moneyPrice"] = 12000,
["supplyPrice"] = 240,
["name"] = "AKM MOE"
},
["tacrp_io_val"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "АС ВАЛ"
},
["tacrp_skorpion"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "VZ-61"
},
["swep_drone_grenade"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 1000,
["name"] = "Дрон с гранатой"
},
["tacrp_sd_pkm"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 300,
["name"] = "ПКМ"
},
["weapon_sw_rpg28"] = {
["moneyPrice"] = 5000,
["supplyPrice"] = 500,
["maxCount"] = 1,
["ammoType"] = "rpg_round",
["category"] = "heavy",
["maxAmmo"] = 1,
["name"] = "РПГ-28"
},
["tacrp_nade_flashbang"] = {
["category"] = "grenade1",
["moneyPrice"] = 200,
["supplyPrice"] = 20,
["name"] = "Светошумовая граната"
},
["v92_bf2_ammokit"] = {
["category"] = "tool3",
["moneyPrice"] = 1500,
["supplyPrice"] = 150,
["name"] = "Ящик с патронами"
},
["tacrp_io_vss"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 200,
["name"] = "ВСС Винторез"
},
["tacrp_io_p226"] = {
["category"] = "secondary",
["moneyPrice"] = 200,
["supplyPrice"] = 1,
["name"] = "P226"
},
["tfa_blast_ksvk_cqb"] = {
["category"] = "primary",
["moneyPrice"] = 15000,
["supplyPrice"] = 300,
["name"] = "KSvK 12.7"
},
["tacrp_io_k98"] = {
["category"] = "primary",
["moneyPrice"] = 5000,
["supplyPrice"] = 100,
["name"] = "KAR98K"
},
["tacrp_superv"] = {
["category"] = "primary",
["moneyPrice"] = 9000,
["supplyPrice"] = 180,
["name"] = "KRISS Vector"
},
["tacrp_nade_smoke"] = {
["category"] = "grenade2",
["moneyPrice"] = 150,
["supplyPrice"] = 15,
["name"] = "Дымовая граната"
},
["tacrp_sd_aac_hb"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "AAC Honey badger"
},
["tacrp_pa_vykhlop"] = {
["category"] = "primary",
["moneyPrice"] = 17000,
["supplyPrice"] = 350,
["name"] = "ВСК Выхлоп"
},
["tacrp_sd_g3"] = {
["category"] = "primary",
["moneyPrice"] = 10000,
["supplyPrice"] = 200,
["name"] = "G3A3"
},
["tfa_ins2_warface_cheytac_m200"] = {
["category"] = "primary",
["moneyPrice"] = 17000,
["supplyPrice"] = 340,
["name"] = "Cheytac M200"
}
},
-- ============================================
-- DONATE WEAPONS (donate_only = true означает что доступно ТОЛЬКО через донат)
-- Если donate_only НЕ указано или false, оружие может быть добавлено в spec/podr и будет платным
-- ============================================
-- Патроны: тип -> {name, price, amount}
ammo = {
-- ["rifle"] = { name = "Патроны для винтовки", price = 10, amount = 30 },
},
-- Броня (опционально)
armor = {
-- amount: how much armor will be set on the player after purchase
-- supplyPrice: cost in faction supply points (keeps naming consistent with weapons)
["armor_light"] = { name = "Легкая броня", supplyPrice = 50, moneyPrice = 0, amount = 25 },
["armor_medium"] = { name = "Средняя броня", supplyPrice = 75, moneyPrice = 0, amount = 50 },
["armor_heavy"] = { name = "Тяжелая броня", supplyPrice = 150, moneyPrice = 0, amount = 100 },
},
wardrobeBlacklist = {},
patchIndex = 15,
patchMaterials = {
"models/texture_vsu_1amb/path",
"models/texture_vsu_1amb/path2",
"models/texture_vsu_1amb/path3"
},
wardrobeAccess = {},
wardrobeStats = {},
wardrobeCHands = {}
}

View File

@@ -0,0 +1,64 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Arsenal"
PLUGIN.author = "Refosel"
PLUGIN.description = "Arsenal plugin (skeleton) — выдача оружия, патронов и управление очками снабжения."
ix.util.Include("sh_config.lua")
ix.util.Include("cl_plugin.lua")
ix.util.Include("sv_plugin.lua")
ix.command.Add("test_arsenal", {
description = "Test arsenal weapon availability.",
OnRun = function(self, client)
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if not plugin then return "Plugin not found" end
local char = client:GetCharacter()
if not char then return "No character" end
client:ChatPrint("--- Arsenal Test for " .. client:Nick() .. " ---")
local avail = plugin:GetAvailableWeapons(char)
client:ChatPrint("Available weapons total: " .. table.Count(avail))
for class, data in pairs(avail) do
client:ChatPrint(string.format(" [%s] %s (Donate: %s)", class, data.name or "N/A", tostring(data.isDonateVersion or false)))
end
client:ChatPrint("--- End Test ---")
end
})
ix.command.Add("arsenal_supply_set", {
description = "Set faction supply. Usage: arsenal_supply_set <amount> [faction id]",
adminOnly = true,
arguments = {
ix.type.number,
ix.type.number
},
OnRun = function(self, client, amount, faction)
if CLIENT then return end
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if not plugin then return client:Notify("Arsenal plugin not loaded") end
local fid = faction or client:GetCharacter():GetFaction()
plugin:SetFactionSupply(fid, math.max(tonumber(amount) or 0, 0))
client:Notify("Set supply for faction " .. tostring(fid) .. " to " .. tostring(plugin:GetFactionSupply(fid)))
end
})
ix.command.Add("arsenal_supply_add", {
description = "Add (or subtract) supply for a faction. Usage: arsenal_supply_add <delta> [faction id]",
adminOnly = true,
arguments = {
ix.type.number,
ix.type.number
},
OnRun = function(self, client, delta, faction)
if CLIENT then return end
local plugin = ix.plugin.list and ix.plugin.list["arsenal"]
if not plugin then return client:Notify("Arsenal plugin not loaded") end
local fid = faction or client:GetCharacter():GetFaction()
plugin:AddFactionSupply(fid, tonumber(delta) or 0)
client:Notify("Faction " .. tostring(fid) .. " supply now: " .. tostring(plugin:GetFactionSupply(fid)))
end
})

View File

@@ -0,0 +1,751 @@
if SERVER then
util.AddNetworkString("ixArsenalOpen")
util.AddNetworkString("ixArsenalAction")
util.AddNetworkString("ixWardrobeOpen")
util.AddNetworkString("ixWardrobeApply")
end
local PLUGIN = PLUGIN
local config = PLUGIN.config
local function IsRecruit(ply)
local char = ply:GetCharacter()
if not char then return false end
local spec = char.GetSpec and char:GetSpec() or 0
return spec == 1
end
-- Helpers to store complex data as JSON strings because SetData/GetData may not accept tables
local function _loadJSONData(plugin, key)
-- plugin:GetData() returns the whole plugin store (or default table). We keep subkeys inside that store.
local store = plugin:GetData() or {}
if type(store) ~= "table" then return {} end
local raw = store[key]
if raw == nil then return {} end
if type(raw) == "table" then return raw end
if type(raw) == "string" then
local ok, tbl = pcall(util.JSONToTable, raw)
if ok and istable(tbl) then return tbl end
end
return {}
end
local function _saveJSONData(plugin, key, tbl)
if type(tbl) ~= "table" then tbl = {} end
local store = plugin:GetData() or {}
if type(store) ~= "table" then store = {} end
-- store the table directly under the subkey; ix.data.Set will serialize the whole store
store[key] = tbl
plugin:SetData(store)
end
-- Faction supply storage helpers
function PLUGIN:GetFactionSupply(faction)
local store = _loadJSONData(self, "factionSupply") or {}
local key = tostring(faction)
local val = store[faction]
if val == nil then val = store[key] end
if val == nil then
-- fallback to configured startSupply or 0
return config.startSupply[faction] or 0
end
local maxSupply = (self.config and self.config.maxSupply) or 20000
return math.Clamp(tonumber(val) or 0, 0, maxSupply)
end
-- Returns remaining cooldown seconds for free weapons for a specific player (per-player persistent)
function PLUGIN:GetFreeWeaponCooldownForPlayer(client)
if not IsValid(client) or not client:IsPlayer() then return 0 end
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
local last = tonumber(stamps[steam]) or 0
local now = os.time()
local cd = tonumber(config.freeWeaponCooldown) or 0
if cd <= 0 then return 0 end
local remain = cd - (now - last)
if remain < 0 then remain = 0 end
return math.floor(remain)
end
-- Returns remaining cooldown seconds for donate weapons (10 minutes = 600 seconds)
function PLUGIN:GetDonateWeaponCooldown(client, weaponClass)
if not IsValid(client) or not client:IsPlayer() then return 0 end
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
if not stamps[steam] then return 0 end
local last = tonumber(stamps[steam][weaponClass]) or 0
local now = os.time()
local cd = 600 -- 10 минут
local remain = cd - (now - last)
if remain < 0 then remain = 0 end
return math.floor(remain)
end
function PLUGIN:SetDonateWeaponCooldown(client, weaponClass)
if not IsValid(client) or not client:IsPlayer() then return end
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
if not stamps[steam] then stamps[steam] = {} end
stamps[steam][weaponClass] = os.time()
_saveJSONData(self, "donateWeaponTimestamps", stamps)
end
function PLUGIN:SetFactionSupply(faction, amount)
local store = _loadJSONData(self, "factionSupply") or {}
local key = tostring(faction)
local value = math.max(tonumber(amount) or 0, 0)
store[key] = value
store[faction] = value
_saveJSONData(self, "factionSupply", store)
end
-- Compatibility helper: add (or subtract) supply for a faction
function PLUGIN:AddFactionSupply(faction, delta)
local amount = tonumber(delta) or 0
local cur = tonumber(self:GetFactionSupply(faction)) or 0
local maxSupply = (self.config and self.config.maxSupply) or 20000
local new = math.Clamp(cur + amount, 0, maxSupply)
if new == cur then return end -- Если ничего не изменилось (например, уперлись в лимит)
self:SetFactionSupply(faction, new)
-- Логирование изменения снабжения
local serverlogsPlugin = ix.plugin.list["serverlogs"]
if (serverlogsPlugin) then
local factionName = ix.faction.Get(faction).name or tostring(faction)
if amount > 0 then
local message = string.format("Фракция '%s' получила +%d очков снабжения (итого: %d)", factionName, amount, new)
serverlogsPlugin:AddLog("FACTION_SUPPLY_ADD", message, nil, {
faction = faction,
factionName = factionName,
amount = amount,
total = new
})
elseif amount < 0 then
local message = string.format("Фракция '%s' потратила %d очков снабжения (итого: %d)", factionName, math.abs(amount), new)
serverlogsPlugin:AddLog("FACTION_SUPPLY_USE", message, nil, {
faction = faction,
factionName = factionName,
amount = math.abs(amount),
total = new
})
end
end
return new
end
-- Получить список доступного оружия для персонажа (заглушка)
-- Рекомендуется реализовать: читать FACTION.Spec и FACTION.Podr из ix.faction.Get(faction)
function PLUGIN:GetAvailableWeapons(char)
local weapons = {}
local faction = char:GetFaction()
local factionTable = ix.faction.Get(faction)
if not factionTable then
return weapons
end
local allowed = {}
local specIndex = tonumber(char:GetSpec()) or 1
if factionTable.Spec and factionTable.Spec[specIndex] and istable(factionTable.Spec[specIndex].weapons) then
for _, c in ipairs(factionTable.Spec[specIndex].weapons) do
allowed[c] = true
end
end
local podrIndex = tonumber(char:GetPodr()) or 1
if factionTable.Podr and factionTable.Podr[podrIndex] and istable(factionTable.Podr[podrIndex].preset) then
for _, c in ipairs(factionTable.Podr[podrIndex].preset) do
allowed[c] = true
end
end
-- donate weapons from character data (with expiration check)
local donateWeaponsTimed = char:GetData("donate_weapons_timed", {})
local donateTable = {}
if donateWeaponsTimed and istable(donateWeaponsTimed) then
local now = os.time()
for wepClass, wepData in pairs(donateWeaponsTimed) do
if istable(wepData) and wepData.expires and tonumber(wepData.expires) > now then
donateTable[wepClass] = true
end
end
end
-- Поддержка старой системы без срока (если есть)
local donateList = char:GetData("donate_weapons", false)
if donateList and istable(donateList) then
for _, c in ipairs(donateList) do donateTable[c] = true end
end
-- IGS integration: check if player has any weapons bought via IGS
local client = char:GetPlayer()
if IsValid(client) then
if IGS then
if isfunction(IGS.GetItems) then
local items = IGS.GetItems()
for _, ITEM in pairs(items) do
local uid = (isfunction(ITEM.GetUID) and ITEM:GetUID()) or ITEM.uid
if not uid then continue end
local has = false
if isfunction(client.HasPurchase) then
has = client:HasPurchase(uid)
elseif isfunction(IGS.PlayerHasItem) then
has = IGS.PlayerHasItem(client, uid)
end
if has then
-- Try all possible ways IGS stores the weapon class
local weaponClass = nil
local weaponCandidates = {
(isfunction(ITEM.GetWeapon) and ITEM:GetWeapon()),
(isfunction(ITEM.GetMeta) and ITEM:GetMeta("weapon")),
ITEM.weapon,
ITEM.weapon_class,
ITEM.class,
ITEM.uniqueID,
ITEM.val,
uid
}
for _, candidate in ipairs(weaponCandidates) do
if candidate and candidate ~= "" then
weaponClass = candidate
break
end
end
if weaponClass and type(weaponClass) ~= "string" then
weaponClass = tostring(weaponClass)
end
if not weaponClass then
weaponClass = uid
end
if weaponClass then
-- Check if this class or a similar one exists in config
if config.weapons[weaponClass] then
donateTable[weaponClass] = true
else
for configClass, _ in pairs(config.weapons) do
if configClass:find(weaponClass, 1, true) then
donateTable[configClass] = true
end
end
end
end
end
end
end
end
end
-- Build result from config only if class is allowed and exists in config
local supply = tonumber(self:GetFactionSupply(faction)) or 0
for class, _ in pairs(allowed) do
local data = config.weapons[class]
if not data then continue end -- skip if not configured
-- Проверяем есть ли оружие в донате у игрока
local hasDonate = donateTable[class]
if hasDonate then
-- Если куплено в донате - показываем как донатное (бесплатно, с кулдауном)
local donateData = table.Copy(data)
donateData.isDonateVersion = true -- флаг что это донатная версия
donateData.supplyPrice = 0
donateData.moneyPrice = 0
weapons[class] = donateData
elseif data.donate_only then
-- Оружие только для доната, но не куплено - не показываем
continue
else
-- Обычное оружие из spec/podr - показываем с ценой из конфига
if supply <= 0 then
if (data.supplyPrice or 0) == 0 then weapons[class] = data end
else
weapons[class] = data
end
end
end
-- Добавляем оружие которое есть ТОЛЬКО в донате (не в spec/podr)
for wepClass, _ in pairs(donateTable) do
if not allowed[wepClass] then
local data = config.weapons[wepClass]
if data then
local donateData = table.Copy(data)
donateData.isDonateVersion = true
donateData.supplyPrice = 0
donateData.moneyPrice = 0
weapons[wepClass] = donateData
end
end
end
return weapons
end
-- Заглушки для действий (реализуйте логику по необходимости)
function PLUGIN:BuyWeapon(client, weaponClass, payMethod)
local char = client:GetCharacter()
if not char then return false, "Нет персонажа" end
local available = self:GetAvailableWeapons(char)
local data = available[weaponClass]
if not data then return false, "Оружие недоступно для покупки" end
payMethod = payMethod or "supply"
local faction = char:GetFaction()
-- Option A: pay by money (Helix)
if payMethod == "money" then
local price = data.moneyPrice or 0
if price == 0 then return false, "Это оружие нельзя купить за деньги" end
if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end
char:TakeMoney(price)
client:Give(weaponClass)
return true
end
-- Check if this is a donate weapon version
local isDonateWeapon = (data.isDonateVersion == true)
if isDonateWeapon then
-- Проверяем что оружие уже есть у игрока
if client:HasWeapon(weaponClass) then
return false, "У вас уже есть это оружие"
end
-- Проверяем кулдаун для донат-оружия (10 минут)
local cooldown = self:GetDonateWeaponCooldown(client, weaponClass)
if cooldown > 0 then
local minutes = math.floor(cooldown / 60)
local seconds = cooldown % 60
return false, string.format("Вы недавно брали это оружие. Подождите %02d:%02d", minutes, seconds)
end
-- Проверяем категорию (донат оружие тоже может иметь категорию)
local newCat = data.category or ""
if newCat ~= "" and not newCat:find("^tool") then
for _, wep in ipairs(client:GetWeapons()) do
if not IsValid(wep) then continue end
local wclass = wep:GetClass()
local wdata = config.weapons[wclass]
if wdata and wdata.category == newCat then
return false, "Вы уже имеете оружие этой категории: " .. newCat
end
end
end
-- Check max ammo limit for launchers
if data.maxAmmo then
local totalAmmo = 0
if data.ammoType == "PanzerFaust3 Rocket" or data.ammoType == "rpg_round" then
totalAmmo = client:GetAmmoCount("rpg_round") + client:GetAmmoCount("PanzerFaust3 Rocket")
else
totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round")
end
if totalAmmo >= data.maxAmmo then
return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")"
end
end
-- Выдаем оружие и ставим кулдаун
client:Give(weaponClass)
-- Reset ammo for launchers to prevent extra ammo
if data.ammoType then
client:SetAmmo(0, data.ammoType)
end
self:SetDonateWeaponCooldown(client, weaponClass)
-- Логирование выдачи донат-оружия
end
-- Default: pay by supply
local supplyCost = data.supplyPrice or 0
local factionSupply = self:GetFactionSupply(faction)
-- If weapon is free (supplyCost == 0), enforce per-player persistent cooldown
if supplyCost == 0 then
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
local last = tonumber(stamps[steam]) or 0
local now = os.time()
local cd = tonumber(config.freeWeaponCooldown) or 0
if cd > 0 and (now - last) < cd then
local remain = cd - (now - last)
return false, "Вы уже брали бесплатное оружие недавно. Подождите " .. tostring(remain) .. " сек."
end
-- don't write timestamp yet: write after successful give
else
if supplyCost > 0 then
if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end
self:AddFactionSupply(faction, -supplyCost)
end
end
-- Check category conflict: prevent two weapons from same category
local newCat = data.category or ""
if newCat ~= "" and not newCat:find("^tool") then
for _, wep in ipairs(client:GetWeapons()) do
if not IsValid(wep) then continue end
local wclass = wep:GetClass()
local wdata = config.weapons[wclass]
if wdata and wdata.category == newCat then
return false, "Вы уже имеете оружие этой категории: " .. newCat
end
end
end
-- Check max ammo limit for launchers
if data.maxAmmo then
local totalAmmo = client:GetAmmoCount(data.ammoType or "rpg_round")
if totalAmmo >= data.maxAmmo then
return false, "Вы достигли максимального количества снарядов для этого типа оружия (" .. tostring(data.maxAmmo) .. ")"
end
end
-- Check max count limit for weapons
if data.maxCount and client:HasWeapon(weaponClass) then
return false, "У вас уже есть это оружие"
end
client:Give(weaponClass)
-- Reset ammo for launchers to prevent extra ammo
if data.ammoType then
client:SetAmmo(0, data.ammoType)
end
-- If free weapon granted, save timestamp per-player
if supplyCost == 0 then
local stamps = _loadJSONData(self, "freeWeaponTimestamps") or {}
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
stamps[steam] = os.time()
_saveJSONData(self, "freeWeaponTimestamps", stamps)
end
-- Логирование выдачи оружия из арсенала
local serverlogsPlugin = ix.plugin.list["serverlogs"]
if (serverlogsPlugin) then
local weaponName = data.name or weaponClass
local factionName = ix.faction.Get(faction).name or tostring(faction)
local message = string.format("%s получил оружие '%s' из арсенала (стоимость: %d снабжения)", client:Nick(), weaponName, supplyCost)
serverlogsPlugin:AddLog("WEAPON_SPAWN", message, client, {
weaponClass = weaponClass,
weaponName = weaponName,
supplyCost = supplyCost,
faction = faction,
factionName = factionName
})
end
return true
end
function PLUGIN:BuyAmmo(client, ammoType)
local char = client:GetCharacter()
if not char then return false, "Нет персонажа" end
-- Запрет на покупку патронов для ракетниц
if ammoType == "rpg_round" or ammoType == "PanzerFaust3 Rocket" then
return false, "Патроны для этого типа оружия недоступны для покупки"
end
local ammoData = config.ammo[ammoType]
if not ammoData then return false, "Тип патронов не найден" end
local faction = char:GetFaction()
local supply = self:GetFactionSupply(faction)
local price = ammoData.price or 0
if price > 0 then
if supply < price then return false, "Недостаточно очков снабжения у фракции" end
self:AddFactionSupply(faction, -price)
end
client:GiveAmmo(ammoData.amount or 30, ammoType, true)
return true
end
function PLUGIN:ReturnWeapon(client, weaponClass)
local char = client:GetCharacter()
if not char then return false, "Нет персонажа" end
local data = config.weapons[weaponClass]
if not data then return false, "Оружие не найдено" end
local faction = char:GetFaction()
-- Remove weapon
client:StripWeapon(weaponClass)
-- Reset ammo for launchers to prevent extra ammo
if data.ammoType then
client:SetAmmo(0, data.ammoType)
end
-- Если это донат-оружие, сбрасываем кулдаун
if data.donate == true then
local stamps = _loadJSONData(self, "donateWeaponTimestamps") or {}
local steam = client:SteamID() or tostring(client:SteamID64() or "unknown")
if stamps[steam] and stamps[steam][weaponClass] then
stamps[steam][weaponClass] = nil
_saveJSONData(self, "donateWeaponTimestamps", stamps)
end
return true
end
local refund = math.floor((data.supplyPrice or 0) * 0.8)
if refund > 0 then self:AddFactionSupply(faction, refund) end
return true
end
function PLUGIN:SaveData()
local data = self:GetData() or {}
data.entities = {}
for _, entity in ipairs(ents.FindByClass("ix_arsenal")) do
data.entities[#data.entities + 1] = {
class = "ix_arsenal",
pos = entity:GetPos(),
angles = entity:GetAngles()
}
end
for _, entity in ipairs(ents.FindByClass("ix_ammobox")) do
data.entities[#data.entities + 1] = {
class = "ix_ammobox",
pos = entity:GetPos(),
angles = entity:GetAngles()
}
end
for _, entity in ipairs(ents.FindByClass("ix_wardrobe")) do
data.entities[#data.entities + 1] = {
class = "ix_wardrobe",
pos = entity:GetPos(),
angles = entity:GetAngles()
}
end
self:SetData(data)
end
function PLUGIN:LoadData()
local data = self:GetData() or {}
local entities = data.entities or data -- fallback to compatibility if no 'entities' key
for _, v in ipairs(entities) do
if not v.class then continue end
local entity = ents.Create(v.class or "ix_arsenal")
entity:SetPos(v.pos)
entity:SetAngles(v.angles)
entity:Spawn()
local phys = entity:GetPhysicsObject()
if IsValid(phys) then
phys:EnableMotion(false)
end
end
end
-- Обработчик сетевых действий от клиента
if SERVER then
net.Receive("ixArsenalAction", function(len, ply)
local action = net.ReadString()
local id = net.ReadString()
local plugin = ix.plugin.list["arsenal"]
if not plugin then return end
if IsRecruit(ply) then
ply:Notify("Новоприбывший не может пользоваться арсеналом.")
return
end
if action == "buy_weapon" then
local method = net.ReadString()
local ok, msg = plugin:BuyWeapon(ply, id, method)
if not ok then
ply:Notify(msg or "Ошибка покупки оружия")
end
elseif action == "buy_ammo" then
local ok, msg = plugin:BuyAmmo(ply, id)
if not ok then
ply:Notify(msg or "Ошибка покупки патронов")
end
elseif action == "return_weapon" then
local ok, msg = plugin:ReturnWeapon(ply, id)
if not ok then
ply:Notify(msg or "Ошибка возврата оружия")
end
elseif action == "buy_armor" then
local method = net.ReadString()
local ok, msg = plugin:BuyArmor(ply, id, method)
if not ok then
ply:Notify(msg or "Ошибка покупки брони")
end
end
end)
end
-- Buy armor implementation
function PLUGIN:BuyArmor(client, armorClass, payMethod)
local char = client:GetCharacter()
if not char then return false, "Нет персонажа" end
local aData = config.armor[armorClass]
if not aData then return false, "Броня не найдена" end
payMethod = payMethod or "supply"
-- Check current armor: Armor() is the player's current armor value
local curArmor = tonumber(client:Armor()) or 0
local giveAmount = tonumber(aData.amount) or 0
if curArmor >= giveAmount then
return false, "Вы не можете купить броню — у вас уже есть броня этого уровня или выше"
end
local faction = char:GetFaction()
-- money path
if payMethod == "money" then
local price = aData.moneyPrice or 0
if price <= 0 then return false, "Эту броню нельзя купить за деньги" end
if not char.HasMoney or not char:HasMoney(price) then return false, "Недостаточно денег" end
char:TakeMoney(price)
client:SetArmor(giveAmount)
return true
end
-- supply path
local supplyCost = aData.supplyPrice or 0
local factionSupply = self:GetFactionSupply(faction)
if supplyCost > 0 then
if factionSupply < supplyCost then return false, "Недостаточно очков снабжения у фракции" end
self:AddFactionSupply(faction, -supplyCost)
end
client:SetArmor(giveAmount)
return true
end
function PLUGIN:PlayerLoadedCharacter(client, character, currentChar)
if not IsValid(client) then return end
timer.Simple(0.5, function()
if not IsValid(client) or not client:GetCharacter() then return end
local char = client:GetCharacter()
local bodygroups = char:GetData("bodygroups", {})
local skin = char:GetData("skin", 0)
local patch = char:GetData("patchIndex", 1)
for idx, value in pairs(bodygroups) do
client:SetBodygroup(idx, value)
end
client:SetSkin(skin)
if self.config.patchIndex and self.config.patchMaterials then
local pMat = self.config.patchMaterials[patch]
if pMat then
client:SetSubMaterial(self.config.patchIndex, pMat)
else
client:SetSubMaterial(self.config.patchIndex, "")
end
end
hook.Run("PostPlayerLoadout", client)
end)
end
function PLUGIN:PostPlayerLoadout(client)
local char = client:GetCharacter()
if not char then return end
local model = client:GetModel()
local bgs = char:GetData("bodygroups", {})
local speedMod = 1
local maxArmor = 100
local ammoMod = 1
local stats = self.config.wardrobeStats and self.config.wardrobeStats[model]
if stats then
for idx, val in pairs(bgs) do
if stats[idx] and stats[idx][val] then
local st = stats[idx][val]
if st.speed then speedMod = speedMod * st.speed end
if st.armor then maxArmor = maxArmor + st.armor end
if st.ammo then ammoMod = ammoMod * st.ammo end
end
end
end
client:SetWalkSpeed(ix.config.Get("walkSpeed") * speedMod)
client:SetRunSpeed(ix.config.Get("runSpeed") * speedMod)
client:SetMaxArmor(maxArmor)
char:SetData("ammoMod", ammoMod)
end
function PLUGIN:EntityTakeDamage(target, dmginfo)
if not target:IsPlayer() then return end
local char = target:GetCharacter()
if not char then return end
local model = target:GetModel()
local bgs = char:GetData("bodygroups", {})
local resist = 1
local stats = self.config.wardrobeStats and self.config.wardrobeStats[model]
if stats then
for idx, val in pairs(bgs) do
if stats[idx] and stats[idx][val] then
local st = stats[idx][val]
if st.dmgResist then
resist = resist * st.dmgResist
end
end
end
end
if resist ~= 1 then
dmginfo:ScaleDamage(resist)
end
end
function PLUGIN:InitPostEntity()
-- Уменьшаем задержку до 2 секунд для более быстрой очистки при старте
timer.Simple(2, function()
-- Загружаем сырые данные из стора напрямую
local currentData = _loadJSONData(self, "factionSupply") or {}
-- Перебираем все записи в сторе. Это надежнее, чем идти по индексам фракций,
-- так как это затронет любые старые или "кривые" ключи в базе.
local changed = false
for key, value in pairs(currentData) do
local factionID = tonumber(key)
local rawVal = tonumber(value) or 0
if factionID and rawVal > 5000 then
local startVal = (self.config.startSupply and self.config.startSupply[factionID]) or 2000
-- Устанавливаем новое значение
currentData[key] = startVal
currentData[factionID] = startVal
changed = true
print(string.format("[Arsenal] Автосброс при старте: фракция %s, было %d, стало %d", key, rawVal, startVal))
end
end
if changed then
_saveJSONData(self, "factionSupply", currentData)
end
end)
end
function PLUGIN:PlayerSetHandsModel(client, ent)
local char = client:GetCharacter()
if not char then return end
local model = client:GetModel()
local bgs = char:GetData("bodygroups", {})
local chands = self.config.wardrobeCHands and self.config.wardrobeCHands[model]
if chands then
for idx, val in pairs(bgs) do
if chands[idx] and chands[idx][val] then
local hData = chands[idx][val]
ent:SetModel(hData.model or "models/weapons/c_arms_cstrike.mdl")
ent:SetSkin(hData.skin or 0)
ent:SetBodyGroups(hData.bodygroups or "0000000")
return true
end
end
end
end

View File

@@ -0,0 +1,113 @@
function AdvDupe2.ReceiveFile(data, autoSave)
AdvDupe2.RemoveProgressBar()
if not data then
AdvDupe2.Notify("File was not saved! (No data)",NOTIFY_ERROR,5)
return
end
local path
if autoSave then
if(LocalPlayer():GetInfo("advdupe2_auto_save_overwrite")~="0")then
path = AdvDupe2.GetFilename(AdvDupe2.AutoSavePath, true)
else
path = AdvDupe2.GetFilename(AdvDupe2.AutoSavePath)
end
else
path = AdvDupe2.GetFilename(AdvDupe2.SavePath)
end
path = AdvDupe2.SanitizeFilename(path)
local dupefile = file.Open(path, "wb", "DATA")
if not dupefile then
AdvDupe2.Notify("File was not saved! (Could not open file for writing)",NOTIFY_ERROR,5)
return
end
dupefile:Write(data)
dupefile:Close()
local errored = false
if(LocalPlayer():GetInfo("advdupe2_debug_openfile")=="1")then
if(not file.Exists(path, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end
local readFile = file.Open(path, "rb", "DATA")
if not readFile then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end
local readData = readFile:Read(readFile:Size())
readFile:Close()
local success, dupe, _info, _moreinfo = AdvDupe2.Decode(readData)
if(success)then
AdvDupe2.Notify("DEBUG CHECK: File successfully opens. No EOF errors.")
else
AdvDupe2.Notify("DEBUG CHECK: " .. dupe, NOTIFY_ERROR)
errored = true
end
end
local filename = string.StripExtension(string.GetFileFromFilename( path ))
if autoSave then
if(IsValid(AdvDupe2.FileBrowser.AutoSaveNode))then
local add = true
for i=1, #AdvDupe2.FileBrowser.AutoSaveNode.Files do
if(filename==AdvDupe2.FileBrowser.AutoSaveNode.Files[i].Label:GetText())then
add=false
break
end
end
if(add)then
AdvDupe2.FileBrowser.AutoSaveNode:AddFile(filename)
AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.AutoSaveNode)
end
end
else
AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode:AddFile(filename)
AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode)
end
if not errored then
AdvDupe2.Notify("File successfully saved!",NOTIFY_GENERIC, 5)
end
end
net.Receive("AdvDupe2_ReceiveFile", function()
local autoSave = net.ReadBool()
net.ReadStream(nil, function(data)
AdvDupe2.ReceiveFile(data, autoSave)
end)
end)
AdvDupe2.Uploading = false
function AdvDupe2.SendFile(name, data)
net.Start("AdvDupe2_ReceiveFile")
net.WriteString(name)
AdvDupe2.Uploading = net.WriteStream(data, function()
AdvDupe2.Uploading = nil
AdvDupe2.File = nil
AdvDupe2.RemoveProgressBar()
end)
net.SendToServer()
end
function AdvDupe2.UploadFile(ReadPath, ReadArea)
if AdvDupe2.Uploading then AdvDupe2.Notify("Already opening file, please wait.", NOTIFY_ERROR) return end
if(ReadArea==0)then
ReadPath = AdvDupe2.DataFolder.."/"..ReadPath..".txt"
elseif(ReadArea==1)then
ReadPath = AdvDupe2.DataFolder.."/-Public-/"..ReadPath..".txt"
else
ReadPath = "adv_duplicator/"..ReadPath..".txt"
end
if(not file.Exists(ReadPath, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end
local read = file.Read(ReadPath)
if not read then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end
local name = string.Explode("/", ReadPath)
name = name[#name]
name = string.sub(name, 1, #name-4)
local success, dupe, info, moreinfo = AdvDupe2.Decode(read)
if(success)then
AdvDupe2.SendFile(name, read)
AdvDupe2.LoadGhosts(dupe, info, moreinfo, name)
else
AdvDupe2.Notify("File could not be decoded. ("..dupe..") Upload Canceled.", NOTIFY_ERROR)
end
end

View File

@@ -0,0 +1,353 @@
function AdvDupe2.LoadGhosts(dupe, info, moreinfo, name, preview)
AdvDupe2.RemoveGhosts()
AdvDupe2.Ghosting = true
AdvDupe2.GhostToSpawn = {}
local count = 0
local time, desc, date, creator
if(info.ad1) then
local z = dupe.HeadEnt.Z
local Pos, Ang
time = moreinfo.Time or ""
desc = info.Description or ""
date = info.Date or ""
creator = info.Creator or ""
AdvDupe2.HeadEnt = dupe.HeadEnt.Index
AdvDupe2.HeadPos = dupe.HeadEnt.Pos
AdvDupe2.HeadZPos = z
AdvDupe2.HeadPos.Z = AdvDupe2.HeadPos.Z + z
for k, v in pairs(dupe.Entities) do
if(v.SavedParentIdx) then
if(not v.BuildDupeInfo) then v.BuildDupeInfo = {} end
v.BuildDupeInfo.DupeParentID = v.SavedParentIdx
Pos = v.LocalPos
Ang = v.LocalAngle
else
Pos, Ang = nil, nil
end
for i, p in pairs(v.PhysicsObjects) do
p.Pos = Pos or p.LocalPos
p.Pos.Z = p.Pos.Z - z
p.Angle = Ang or p.LocalAngle
p.LocalPos = nil
p.LocalAngle = nil
end
v.LocalPos = nil
v.LocalAngle = nil
AdvDupe2.GhostToSpawn[count] =
{
Model = v.Model,
PhysicsObjects = v.PhysicsObjects
}
if(AdvDupe2.HeadEnt == k) then
AdvDupe2.HeadEnt = count
end
count = count + 1
end
AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos
AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle
else
time = info.time or ""
desc = dupe.Description or ""
date = info.date or ""
creator = info.name or ""
AdvDupe2.HeadEnt = dupe.HeadEnt.Index
AdvDupe2.HeadZPos = dupe.HeadEnt.Z
AdvDupe2.HeadPos = dupe.HeadEnt.Pos
AdvDupe2.HeadOffset = dupe.Entities[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos
AdvDupe2.HeadAngle = dupe.Entities[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle
for k, v in pairs(dupe.Entities) do
AdvDupe2.GhostToSpawn[count] =
{
Model = v.Model,
PhysicsObjects = v.PhysicsObjects
}
if(AdvDupe2.HeadEnt == k) then
AdvDupe2.HeadEnt = count
end
count = count + 1
end
end
if(not preview) then
AdvDupe2.Info.File:SetText("File: "..name)
AdvDupe2.Info.Creator:SetText("Creator: "..creator)
AdvDupe2.Info.Date:SetText("Date: "..date)
AdvDupe2.Info.Time:SetText("Time: "..time)
AdvDupe2.Info.Size:SetText("Size: "..string.NiceSize(tonumber(info.size) or 0))
AdvDupe2.Info.Desc:SetText("Desc: "..(desc or ""))
AdvDupe2.Info.Entities:SetText("Entities: "..table.Count(dupe.Entities))
AdvDupe2.Info.Constraints:SetText("Constraints: "..table.Count(dupe.Constraints))
end
AdvDupe2.StartGhosting()
AdvDupe2.Preview = preview
end
function AdvDupe2.RemoveGhosts()
if(AdvDupe2.Ghosting) then
hook.Remove("Tick", "AdvDupe2_SpawnGhosts")
AdvDupe2.Ghosting = false
if(not AdvDupe2.BusyBar) then
AdvDupe2.RemoveProgressBar()
end
end
if(AdvDupe2.GhostEntities) then
for k, v in pairs(AdvDupe2.GhostEntities) do
if(IsValid(v))then
v:Remove()
end
end
end
if(IsValid(AdvDupe2.HeadGhost))then
AdvDupe2.HeadGhost:Remove()
end
AdvDupe2.CurrentGhost = 1
AdvDupe2.HeadGhost = nil
AdvDupe2.GhostEntities = nil
AdvDupe2.Preview = false
end
--Creates a ghost from the given entity's table
local function MakeGhostsFromTable(EntTable)
if(not EntTable) then return end
if(not EntTable.Model or EntTable.Model:sub(-4,-1) ~= ".mdl") then
EntTable.Model = "models/error.mdl"
end
local GhostEntity = ClientsideModel(EntTable.Model, RENDERGROUP_TRANSLUCENT)
-- If there are too many entities we might not spawn..
if not IsValid(GhostEntity) then
AdvDupe2.RemoveGhosts()
AdvDupe2.Notify("Too many entities to spawn ghosts!", NOTIFY_ERROR)
return
end
GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA ) --Was broken, making ghosts invisible
GhostEntity:SetColor( Color(255, 255, 255, 150) )
GhostEntity.Phys = EntTable.PhysicsObjects[0]
if util.IsValidRagdoll(EntTable.Model) then
local ref, parents, angs = {}, {}, {}
GhostEntity:SetupBones()
for k, v in pairs(EntTable.PhysicsObjects) do
local bone = GhostEntity:TranslatePhysBoneToBone(k)
local bonp = GhostEntity:GetBoneParent(bone)
if bonp == -1 then
ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR()
else
bonp = GhostEntity:TranslatePhysBoneToBone(GhostEntity:TranslateBoneToPhysBone(bonp))
parents[bone] = bonp
ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR() * GhostEntity:GetBoneMatrix(bonp)
end
local m = Matrix() m:SetAngles(v.Angle)
angs[bone] = m
end
for bone, ang in pairs( angs ) do
if parents[bone] and angs[parents[bone]] then
local localrotation = angs[parents[bone]]:GetInverseTR() * ang
local m = ref[bone] * localrotation
GhostEntity:ManipulateBoneAngles(bone, m:GetAngles())
else
local pos = GhostEntity:GetBonePosition(bone)
GhostEntity:ManipulateBonePosition(bone, -pos)
GhostEntity:ManipulateBoneAngles(bone, ref[bone]:GetAngles())
end
end
end
return GhostEntity
end
local function StopGhosting()
AdvDupe2.Ghosting = false
hook.Remove( "Tick", "AdvDupe2_SpawnGhosts" )
if not BusyBar then AdvDupe2.RemoveProgressBar() end
end
local function SpawnGhosts()
local ghostsPerTick = GetConVar( "advdupe2_ghost_rate" ):GetInt()
local ghostPercentLimit = GetConVar( "advdupe2_limit_ghost" ):GetFloat()
local finalGhost = math.min( AdvDupe2.TotalGhosts, math.max( math.Round( (ghostPercentLimit / 100) * AdvDupe2.TotalGhosts ), 0 ) )
local finalGhostInFrame = math.min( AdvDupe2.CurrentGhost + ghostsPerTick - 1, finalGhost )
for i = AdvDupe2.CurrentGhost, finalGhostInFrame do
local g = AdvDupe2.GhostToSpawn[i]
if g and i ~= AdvDupe2.HeadEnt then AdvDupe2.GhostEntities[i] = MakeGhostsFromTable( g ) end
end
AdvDupe2.CurrentGhost = finalGhostInFrame + 1
AdvDupe2.UpdateGhosts( true )
if not AdvDupe2.BusyBar then
AdvDupe2.ProgressBar.Percent = (AdvDupe2.CurrentGhost / AdvDupe2.TotalGhosts) * 100
end
if AdvDupe2.CurrentGhost > finalGhost then
StopGhosting()
end
end
net.Receive("AdvDupe2_SendGhosts", function(len, ply, len2)
AdvDupe2.RemoveGhosts()
AdvDupe2.GhostToSpawn = {}
AdvDupe2.HeadEnt = net.ReadInt(16)
AdvDupe2.HeadZPos = net.ReadFloat()
AdvDupe2.HeadPos = net.ReadVector()
local cache = {}
for i = 1, net.ReadInt(16) do
cache[i] = net.ReadString()
end
for i = 1, net.ReadInt(16) do
AdvDupe2.GhostToSpawn[i] =
{
Model = cache[net.ReadInt(16)],
PhysicsObjects = {}
}
for k = 0, net.ReadInt(8) do
AdvDupe2.GhostToSpawn[i].PhysicsObjects[k] =
{
Angle = net.ReadAngle(),
Pos = net.ReadVector()
}
end
end
AdvDupe2.CurrentGhost = 1
AdvDupe2.GhostEntities = {}
AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt])
AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos
AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle
AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost
AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn
if(AdvDupe2.TotalGhosts > 1) then
AdvDupe2.Ghosting = true
if(not AdvDupe2.BusyBar) then
AdvDupe2.InitProgressBar("Ghosting: ")
AdvDupe2.BusyBar = false
end
hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts)
else
AdvDupe2.Ghosting = false
end
end)
net.Receive("AdvDupe2_AddGhost", function(len, ply, len2)
local ghost = {Model = net.ReadString(), PhysicsObjects = {}}
for k = 0, net.ReadInt(8) do
ghost.PhysicsObjects[k] = {Angle = net.ReadAngle(), Pos = net.ReadVector()}
end
AdvDupe2.GhostEntities[AdvDupe2.CurrentGhost] = MakeGhostsFromTable(ghost)
AdvDupe2.CurrentGhost = AdvDupe2.CurrentGhost + 1
end)
function AdvDupe2.StartGhosting()
AdvDupe2.RemoveGhosts()
if(not AdvDupe2.GhostToSpawn) then return end
AdvDupe2.CurrentGhost = 1
AdvDupe2.GhostEntities = {}
AdvDupe2.Ghosting = true
AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt])
AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost
AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn
if AdvDupe2.TotalGhosts > 1 then
if not AdvDupe2.BusyBar then
AdvDupe2.InitProgressBar("Ghosting: ")
AdvDupe2.BusyBar = false
end
hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts)
else
AdvDupe2.Ghosting = false
end
end
net.Receive("AdvDupe2_StartGhosting", function()
AdvDupe2.StartGhosting()
end)
net.Receive("AdvDupe2_RemoveGhosts", AdvDupe2.RemoveGhosts)
--Update the ghost's postion and angles based on where the player is looking and the offsets
local Lheadpos, Lheadang = Vector(), Angle()
function AdvDupe2.UpdateGhosts(force)
if not IsValid(AdvDupe2.HeadGhost) then
AdvDupe2.RemoveGhosts()
AdvDupe2.Notify("Invalid ghost parent!", NOTIFY_ERROR)
return
end
local trace = LocalPlayer():GetEyeTrace()
if (not trace.Hit) then return end
local originpos, originang, headpos, headang
local worigin = GetConVar("advdupe2_offset_world"):GetBool()
if(GetConVar("advdupe2_original_origin"):GetBool())then
originang = Angle()
originpos = Vector(AdvDupe2.HeadPos)
headpos = AdvDupe2.HeadPos + AdvDupe2.HeadOffset
headang = AdvDupe2.HeadAngle
else
local hangle = worigin and Angle(0,0,0) or AdvDupe2.HeadAngle
local pz = math.Clamp(AdvDupe2.HeadZPos + GetConVar("advdupe2_offset_z"):GetFloat() or 0, -16000, 16000)
local ap = math.Clamp(GetConVar("advdupe2_offset_pitch"):GetFloat() or 0, -180, 180)
local ay = math.Clamp(GetConVar("advdupe2_offset_yaw" ):GetFloat() or 0, -180, 180)
local ar = math.Clamp(GetConVar("advdupe2_offset_roll" ):GetFloat() or 0, -180, 180)
originang = Angle(ap, ay, ar)
originpos = Vector(trace.HitPos); originpos.z = originpos.z + pz
headpos, headang = LocalToWorld(AdvDupe2.HeadOffset, hangle, originpos, originang)
end
if math.abs(Lheadpos.x - headpos.x) > 0.01 or
math.abs(Lheadpos.y - headpos.y) > 0.01 or
math.abs(Lheadpos.z - headpos.z) > 0.01 or
math.abs(Lheadang.p - headang.p) > 0.01 or
math.abs(Lheadang.y - headang.y) > 0.01 or
math.abs(Lheadang.r - headang.r) > 0.01 or force then
Lheadpos = headpos
Lheadang = headang
AdvDupe2.HeadGhost:SetPos(headpos)
AdvDupe2.HeadGhost:SetAngles(headang)
for k, ghost in ipairs(AdvDupe2.GhostEntities) do
local phys = ghost.Phys
if phys then
local pos, ang = LocalToWorld(phys.Pos, phys.Angle, originpos, originang)
ghost:SetPos(pos)
ghost:SetAngles(ang)
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,564 @@
--[[
Title: Adv. Dupe 2 Codec
Desc: Dupe encoder/decoder.
Author: emspike
Version: 2.0
]]
local REVISION = 5
AdvDupe2.CodecRevision = REVISION
AdvDupe2.MaxDupeSize = 32e6 -- 32 MB
include( "sh_codec_legacy.lua" )
AddCSLuaFile( "sh_codec_legacy.lua" )
local pairs = pairs
local error = error
local Vector = Vector
local Angle = Angle
local format = string.format
local char = string.char
local concat = table.concat
local compress = util.Compress
local decompress = util.Decompress
--[[
Name: GenerateDupeStamp
Desc: Generates an info table.
Params: <player> ply
Return: <table> stamp
]]
function AdvDupe2.GenerateDupeStamp(ply)
local stamp = {}
stamp.name = ply:GetName()
stamp.time = os.date("%I:%M %p")
stamp.date = os.date("%d %B %Y")
stamp.timezone = os.date("%z")
hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp)
return stamp
end
function AdvDupe2.SanitizeFilename(filename)
filename = string.gsub( filename, "[\":]", "_" )
filename = string.gsub( filename, "%s+", " " )
filename = string.gsub( filename, "%s*([\\/%.])%s*", "%1" )
return filename
end
local function makeInfo(tbl)
local info = ""
for k, v in pairs(tbl) do
info = concat{info,k,"\1",v,"\1"}
end
return info.."\2"
end
local AD2FF = "AD2F%s\n%s\n%s"
local tables, buff
local function noserializer() end
local enc = {}
for i = 1, 255 do enc[i] = noserializer end
local function isArray(tbl)
local ret = true
local m = 0
for k, v in pairs(tbl) do
m = m + 1
if k ~= m or enc[TypeID(v)] == noserializer then
ret = false
break
end
end
return ret
end
local function write(obj)
enc[TypeID(obj)](obj)
end
local len, tables, tablesLookup
enc[TYPE_TABLE] = function(obj) --table
if not tablesLookup[obj] then
tables = tables + 1
tablesLookup[obj] = tables
else
buff:WriteByte(247)
buff:WriteShort(tablesLookup[obj])
return
end
if isArray(obj) then
buff:WriteByte(254)
for i, v in pairs(obj) do
write(v)
end
else
buff:WriteByte(255)
for k, v in pairs(obj) do
if(enc[TypeID(k)] ~= noserializer and enc[TypeID(v)] ~= noserializer) then
write(k)
write(v)
end
end
end
buff:WriteByte(246)
end
enc[TYPE_BOOL] = function(obj) --boolean
buff:WriteByte(obj and 253 or 252)
end
enc[TYPE_NUMBER] = function(obj) --number
buff:WriteByte(251)
buff:WriteDouble(obj)
end
enc[TYPE_VECTOR] = function(obj) --vector
buff:WriteByte(250)
buff:WriteDouble(obj.x)
buff:WriteDouble(obj.y)
buff:WriteDouble(obj.z)
end
enc[TYPE_ANGLE] = function(obj) --angle
buff:WriteByte(249)
buff:WriteDouble(obj.p)
buff:WriteDouble(obj.y)
buff:WriteDouble(obj.r)
end
enc[TYPE_STRING] = function(obj) --string
len = #obj
if len < 246 then
buff:WriteByte(len)
buff:Write(obj)
else
buff:WriteByte(248)
buff:WriteULong(len)
buff:Write(obj)
end
end
local function error_nodeserializer()
buff:Seek(buff:Tell()-1)
error(format("Couldn't find deserializer for type {typeid:%d}!", buff:ReadByte()))
end
local reference = 0
local read4, read5
do --Version 4
local dec = {}
for i = 1, 255 do dec[i] = error_nodeserializer end
local function read()
local tt = buff:ReadByte()
if not tt then
error("Expected value, got EOF!")
end
if tt == 0 then
return nil
end
return dec[tt]()
end
read4 = read
dec[255] = function() --table
local t = {}
local k
reference = reference + 1
local ref = reference
repeat
k = read()
if k ~= nil then
t[k] = read()
end
until (k == nil)
tables[ref] = t
return t
end
dec[254] = function() --array
local t = {}
local k = 0
local v
reference = reference + 1
local ref = reference
repeat
k = k + 1
v = read()
if(v ~= nil) then
t[k] = v
end
until (v == nil)
tables[ref] = t
return t
end
dec[253] = function()
return true
end
dec[252] = function()
return false
end
dec[251] = function()
return buff:ReadDouble()
end
dec[250] = function()
return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
end
dec[249] = function()
return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
end
dec[248] = function() --null-terminated string
local start = buff:Tell()
local slen = 0
while buff:ReadByte() ~= 0 do
slen = slen + 1
end
buff:Seek(start)
local retv = buff:Read(slen)
if(not retv) then retv="" end
buff:ReadByte()
return retv
end
dec[247] = function() --table reference
reference = reference + 1
return tables[buff:ReadShort()]
end
for i = 1, 246 do dec[i] = function() return buff:Read(i) end end
end
do --Version 5
local dec = {}
for i = 1, 255 do dec[i] = error_nodeserializer end
local function read()
local tt = buff:ReadByte()
if not tt then
error("Expected value, got EOF!")
end
return dec[tt]()
end
read5 = read
dec[255] = function() --table
local t = {}
reference = reference + 1
tables[reference] = t
for k in read do
t[k] = read()
end
return t
end
dec[254] = function() --array
local t = {}
reference = reference + 1
tables[reference] = t
local k = 1
for v in read do
t[k] = v
k = k + 1
end
return t
end
dec[253] = function()
return true
end
dec[252] = function()
return false
end
dec[251] = function()
return buff:ReadDouble()
end
dec[250] = function()
return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
end
dec[249] = function()
return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble())
end
dec[248] = function() -- Length>246 string
local slen = buff:ReadULong()
local retv = buff:Read(slen)
if(not retv) then retv = "" end
return retv
end
dec[247] = function() --table reference
return tables[buff:ReadShort()]
end
dec[246] = function() --nil
return
end
for i = 1, 245 do dec[i] = function() return buff:Read(i) end end
dec[0] = function() return "" end
end
local function serialize(tbl)
tables = 0
tablesLookup = {}
buff = file.Open("ad2temp.txt", "wb", "DATA")
if not buff then error("Failed to open file data/ad2temp.txt for writing!") end
write(tbl)
buff:Close()
buff = file.Open("ad2temp.txt","rb","DATA")
if not buff then error("Failed to open file data/ad2temp.txt for reading!") end
local ret = buff:Read(buff:Size())
buff:Close()
return ret
end
local function deserialize(str, read)
if(str == nil) then
error("File could not be decompressed!")
return {}
end
tables = {}
reference = 0
buff = file.Open("ad2temp.txt","wb","DATA")
if not buff then error("Failed to open file data/ad2temp.txt for writing!") end
buff:Write(str)
buff:Flush()
buff:Close()
buff = file.Open("ad2temp.txt","rb", "DATA")
if not buff then error("Failed to open file data/ad2temp.txt for reading!") end
local success, tbl = pcall(read)
buff:Close()
if success then
return tbl
else
error(tbl)
end
end
--[[
Name: Encode
Desc: Generates the string for a dupe file with the given data.
Params: <table> dupe, <table> info, <function> callback, <...> args
Return: runs callback(<string> encoded_dupe, <...> args)
]]
function AdvDupe2.Encode(dupe, info, callback, ...)
local encodedTable = compress(serialize(dupe))
info.check = "\r\n\t\n"
info.size = #encodedTable
callback(AD2FF:format(char(REVISION), makeInfo(info), encodedTable),...)
end
--seperates the header and body and converts the header to a table
local function getInfo(str)
local last = str:find("\2")
if not last then
error("Attempt to read AD2 file with malformed info block!")
end
local info = {}
local ss = str:sub(1, last - 1)
for k, v in ss:gmatch("(.-)\1(.-)\1") do
info[k] = v
end
if info.check ~= "\r\n\t\n" then
if info.check == "\10\9\10" then
error("Detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)!")
elseif info.check ~= nil then
error("Detected AD2 file corrupted by newline replacements (copy/pasting the data in various editors can cause this!)")
else
error("Attempt to read AD2 file with malformed info block!")
end
end
return info, str:sub(last+2)
end
--decoders for individual versions go here
local versions = {}
versions[1] = AdvDupe2.LegacyDecoders[1]
versions[2] = AdvDupe2.LegacyDecoders[2]
versions[3] = function(encodedDupe)
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
encodedDupe = encodedDupe:Replace("\r\n", "\n")
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
return versions[4](encodedDupe)
end
versions[4] = function(encodedDupe)
local info, dupestring = getInfo(encodedDupe:sub(7))
return deserialize(decompress(dupestring, AdvDupe2.MaxDupeSize), read4), info
end
versions[5] = function(encodedDupe)
local info, dupestring = getInfo(encodedDupe:sub(7))
return deserialize(decompress(dupestring, AdvDupe2.MaxDupeSize), read5), info
end
function AdvDupe2.CheckValidDupe(dupe, info)
if not dupe.HeadEnt then return false, "Missing HeadEnt table" end
if not dupe.Entities then return false, "Missing Entities table" end
if not dupe.Constraints then return false, "Missing Constraints table" end
if not dupe.HeadEnt.Z then return false, "Missing HeadEnt.Z" end
if not dupe.HeadEnt.Pos then return false, "Missing HeadEnt.Pos" end
if not dupe.HeadEnt.Index then return false, "Missing HeadEnt.Index" end
if not dupe.Entities[dupe.HeadEnt.Index] then return false, "Missing HeadEnt index ["..dupe.HeadEnt.Index.."] from Entities table" end
for key, data in pairs(dupe.Entities) do
if not data.PhysicsObjects then return false, "Missing PhysicsObject table from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
if not data.PhysicsObjects[0] then return false, "Missing PhysicsObject[0] table from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
if info.ad1 then -- Advanced Duplicator 1
if not data.PhysicsObjects[0].LocalPos then return false, "Missing PhysicsObject[0].LocalPos from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
if not data.PhysicsObjects[0].LocalAngle then return false, "Missing PhysicsObject[0].LocalAngle from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
else -- Advanced Duplicator 2
if not data.PhysicsObjects[0].Pos then return false, "Missing PhysicsObject[0].Pos from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
if not data.PhysicsObjects[0].Angle then return false, "Missing PhysicsObject[0].Angle from Entity ["..key.."]["..data.Class.."]["..data.Model.."]" end
end
end
return true, dupe
end
--[[
Name: Decode
Desc: Generates the table for a dupe from the given string. Inverse of Encode
Params: <string> encodedDupe, <function> callback, <...> args
Return: runs callback(<boolean> success, <table/string> tbl, <table> info)
]]
function AdvDupe2.Decode(encodedDupe)
local sig, rev = encodedDupe:match("^(....)(.)")
if not rev then
return false, "Malformed dupe (wtf <5 chars long)!"
end
rev = rev:byte()
if sig ~= "AD2F" then
if sig == "[Inf" then --legacy support, ENGAGE (AD1 dupe detected)
local success, tbl, info, moreinfo = pcall(AdvDupe2.LegacyDecoders[0], encodedDupe)
if success then
info.ad1 = true
info.size = #encodedDupe
info.revision = 0
local index = tonumber(moreinfo.Head) or (istable(tbl.Entities) and next(tbl.Entities))
if not index then return false, "Missing head index" end
local pos
if isstring(moreinfo.StartPos) then
local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$")
pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0)
else
pos = Vector()
end
local z
if isstring(moreinfo.HoldPos) then
z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1
else
z = 0
end
tbl.HeadEnt = {
Index = index,
Pos = pos,
Z = z
}
else
ErrorNoHalt(tbl)
end
if success then
success, tbl = AdvDupe2.CheckValidDupe(tbl, info)
end
return success, tbl, info, moreinfo
else
return false, "Unknown duplication format!"
end
elseif rev > REVISION then
return false, format("Newer codec needed. (have rev %u, need rev %u) Update Advdupe2.",REVISION,rev)
elseif rev < 1 then
return false, format("Attempt to use an invalid format revision (rev %d)!", rev)
else
local success, tbl, info = pcall(versions[rev], encodedDupe)
if success then
success, tbl = AdvDupe2.CheckValidDupe(tbl, info)
end
if success then
info.revision = rev
end
return success, tbl, info
end
end
if CLIENT then
concommand.Add("advdupe2_to_json", function(_,_,arg)
if not arg[1] then print("Need AdvDupe2 file name argument!") return end
local readFileName = "advdupe2/"..arg[1]
local writeFileName = "advdupe2/"..string.StripExtension(arg[1])..".json"
writeFileName = AdvDupe2.SanitizeFilename(writeFileName)
local readFile = file.Open(readFileName, "rb", "DATA")
if not readFile then print("File could not be read or found! ("..readFileName..")") return end
local readData = readFile:Read(readFile:Size())
readFile:Close()
local ok, tbl = AdvDupe2.Decode(readData)
local writeFile = file.Open(writeFileName, "wb", "DATA")
if not writeFile then print("File could not be written! ("..writeFileName..")") return end
writeFile:Write(util.TableToJSON(tbl))
writeFile:Close()
print("File written! ("..writeFileName..")")
end)
concommand.Add("advdupe2_from_json", function(_,_,arg)
if not arg[1] then print("Need json file name argument!") return end
local readFileName = "advdupe2/"..arg[1]
local writeFileName = "advdupe2/"..string.StripExtension(arg[1])..".txt"
local readFile = file.Open(readFileName, "rb", "DATA")
if not readFile then print("File could not be read or found! ("..readFileName..")") return end
local readData = readFile:Read(readFile:Size())
readFile:Close()
AdvDupe2.Encode(util.JSONToTable(readData), {}, function(data)
local writeFile = file.Open(writeFileName, "wb", "DATA")
if not writeFile then print("File could not be written! ("..writeFileName..")") return end
writeFile:Write(data)
writeFile:Close()
print("File written! ("..writeFileName..")")
end)
end)
end

View File

@@ -0,0 +1,559 @@
--[[
Title: Adv. Dupe 2 Codec Legacy Support
Desc: Facilitates opening of dupes from AD1 and earlier AD2 versions.
Author: emspike
Version: 2.0
]]
local pairs = pairs
local type = type
local tonumber = tonumber
local error = error
local Vector = Vector
local Angle = Angle
local format = string.format
local char = string.char
local byte = string.byte
local sub = string.sub
local gsub = string.gsub
local find = string.find
local gmatch = string.gmatch
local match = string.match
local concat = table.concat
--[[
Name: GenerateDupeStamp
Desc: Generates an info table.
Params: <player> ply
Return: <table> stamp
]]
function AdvDupe2.GenerateDupeStamp(ply)
local stamp = {}
stamp.name = ply:GetName()
stamp.time = os.date("%I:%M %p")
stamp.date = os.date("%d %B %Y")
stamp.timezone = os.date("%z")
hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp)
return stamp
end
local AD2FF = "AD2F%s\n%s\n%s"
local decode_types_v1, decode_types_v2
local tables = 0
local str,pos
local a,b,c,m,n,w,tblref
local function read_v2()
local t = byte(str, pos+1)
if t then
local dt = decode_types_v2[t]
if dt then
pos = pos + 1
return dt()
else
error(format("encountered invalid data type (%u)\n",t))
end
else
error("expected value, got EOF\n")
end
end
decode_types_v2 = {
[1 ] = function()
error("expected value, got terminator\n")
end,
[2 ] = function() -- table
m = find(str, "\1", pos)
if m then
w = sub(str, pos+1, m-1)
pos = m
else
error("expected table identifier, got EOF\n")
end
local t = {}
tables[w] = t
while true do
if byte(str, pos+1) == 1 then
pos = pos + 1
return t
else
t[read_v2()] = read_v2()
end
end
end,
[3 ] = function() -- array
m = find(str, "\1", pos)
if m then
w = sub(str, pos+1, m-1)
pos = m
else
error("expected table identifier, got EOF\n")
end
local t, i = {}, 1
tables[w] = t
while true do
if byte(str,pos+1) == 1 then
pos = pos+1
return t
else
t[i] = read_v2()
i = i + 1
end
end
end,
[4 ] = function() -- false boolean
return false
end,
[5 ] = function() -- true boolean
return true
end,
[6 ] = function() -- number
m = find(str, "\1", pos)
if m then
a = tonumber(sub(str, pos+1, m-1)) or 0
pos = m
return a
else
error("expected number, got EOF\n")
end
end,
[7 ] = function() -- string
m = find(str,"\1",pos)
if m then
w = sub(str, pos+1, m-1)
pos = m
return w
else
error("expected string, got EOF\n")
end
end,
[8 ] = function() -- Vector
m,n = find(str,".-\1.-\1.-\1", pos)
if m then
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
pos = n
return Vector(tonumber(a), tonumber(b), tonumber(c))
else
error("expected vector, got EOF\n")
end
end,
[9 ] = function() -- Angle
m,n = find(str, ".-\1.-\1.-\1", pos)
if m then
a,b,c = match(str, "^(.-)\1(.-)\1(.-)\1",pos+1)
pos = n
return Angle(tonumber(a), tonumber(b), tonumber(c))
else
error("expected angle, got EOF\n")
end
end,
[10 ] = function() -- Table Reference
m = find(str,"\1",pos)
if m then
w = sub(str,pos+1,m-1)
pos = m
else
error("expected table identifier, got EOF\n")
end
tblref = tables[w]
if tblref then
return tblref
else
error(format("table identifier %s points to nil\n", w))
end
end
}
local function read_v1()
local t = byte(str,pos+1)
if t then
local dt = decode_types_v1[t]
if dt then
pos = pos + 1
return dt()
else
error(format("encountered invalid data type (%u)\n",t))
end
else
error("expected value, got EOF\n")
end
end
decode_types_v1 = {
[1 ] = function()
error("expected value, got terminator\n")
end,
[2 ] = function() -- table
local t = {}
while true do
if byte(str,pos+1) == 1 then
pos = pos+1
return t
else
t[read_v1()] = read_v1()
end
end
end,
[3 ] = function() -- array
local t, i = {}, 1
while true do
if byte(str,pos+1) == 1 then
pos = pos+1
return t
else
t[i] = read_v1()
i = i + 1
end
end
end,
[4 ] = function() -- false boolean
return false
end,
[5 ] = function() -- true boolean
return true
end,
[6 ] = function() -- number
m = find(str,"\1",pos)
if m then
a = tonumber(sub(str,pos+1,m-1)) or 0
pos = m
return a
else
error("expected number, got EOF\n")
end
end,
[7 ] = function() -- string
m = find(str,"\1",pos)
if m then
w = sub(str,pos+1,m-1)
pos = m
return w
else
error("expected string, got EOF\n")
end
end,
[8 ] = function() -- Vector
m,n = find(str,".-\1.-\1.-\1",pos)
if m then
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
pos = n
return Vector(tonumber(a), tonumber(b), tonumber(c))
else
error("expected vector, got EOF\n")
end
end,
[9 ] = function() -- Angle
m,n = find(str,".-\1.-\1.-\1",pos)
if m then
a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1)
pos = n
return Angle(tonumber(a), tonumber(b), tonumber(c))
else
error("expected angle, got EOF\n")
end
end
}
local function deserialize_v1(data)
str = data
pos = 0
tables = {}
return read_v1()
end
local function deserialize_v2(data)
str = data
pos = 0
tables = {}
return read_v2()
end
local function lzwDecode(encoded)
local dictionary_length = 256
local dictionary = {}
for i = 0, 255 do
dictionary[i] = char(i)
end
local pos = 2
local decompressed = {}
local decompressed_length = 1
local index = byte(encoded)
local word = dictionary[index]
decompressed[decompressed_length] = dictionary[index]
local entry
local encoded_length = #encoded
local firstbyte --of an index
while pos <= encoded_length do
firstbyte = byte(encoded,pos)
if firstbyte > 252 then --now we know it's a length indicator for a multibyte index
index = 0
firstbyte = 256 - firstbyte
--[[if pos+firstbyte > encoded_length then --will test for performance impact
error("expected index got EOF")
end]]
for i = pos+firstbyte, pos+1, -1 do
index = bit.bor(bit.lshift(index, 8), byte(encoded,i))
end
pos = pos + firstbyte + 1
else
index = firstbyte
pos = pos + 1
end
entry = dictionary[index] or (word..sub(word,1,1))
decompressed_length = decompressed_length + 1
decompressed[decompressed_length] = entry
dictionary[dictionary_length] = word..sub(entry,1,1)
dictionary_length = dictionary_length + 1
word = entry
end
return concat(decompressed)
end
--http://en.wikipedia.org/wiki/Huffman_coding#Decompression
local invcodes = {[2]={[0]="\254"},[5]={[22]="\1",[11]="\2"},[6]={[13]="\7",[35]="\6",[37]="\5",[58]="\3",[31]="\8",[9]="\13",[51]="\9",[55]="\10",[57]="\4",[59]="\15"},[7]={[1]="\14",[15]="\16",[87]="\31",[89]="\30",[62]="\26",[17]="\27",[97]="\19",[19]="\43",[10]="\12",[39]="\33",[41]="\24",[82]="\40",[3]="\32",[46]="\41",[47]="\38",[94]="\25",[65]="\23",[50]="\39",[26]="\11",[7]="\28",[33]="\18",[61]="\17",[25]="\42"},[8]={[111]="\101",[162]="\29",[2]="\34",[133]="\21",[142]="\36",[5]="\20",[21]="\37",[170]="\44",[130]="\22",[66]="\35"},[9]={[241]="\121",[361]="\104",[365]="\184",[125]="\227",[373]="\198",[253]="\117",[381]="\57",[270]="\49",[413]="\80",[290]="\47",[294]="\115",[38]="\112",[429]="\74",[433]="\0",[437]="\48",[158]="\183",[453]="\107",[166]="\111",[469]="\182",[477]="\241",[45]="\86",[489]="\69",[366]="\100",[497]="\61",[509]="\76",[49]="\53",[390]="\78",[279]="\196",[283]="\70",[414]="\98",[53]="\55",[422]="\109",[233]="\79",[349]="\89",[369]="\52",[14]="\105",[238]="\56",[319]="\162",[323]="\83",[327]="\63",[458]="\65",[335]="\231",[339]="\225",[337]="\114",[347]="\193",[493]="\139",[23]="\209",[359]="\250",[490]="\68",[42]="\54",[63]="\91",[286]="\97",[254]="\50",[510]="\108",[109]="\73",[67]="\103",[255]="\122",[69]="\170",[70]="\110",[407]="\176",[411]="\119",[110]="\120",[83]="\146",[149]="\163",[151]="\224",[85]="\51",[155]="\177",[79]="\251",[27]="\118",[447]="\159",[451]="\228",[455]="\175",[383]="\174",[463]="\243",[467]="\157",[173]="\210",[475]="\167",[177]="\84",[90]="\45",[487]="\206",[93]="\226",[495]="\245",[207]="\64",[127]="\147",[191]="\155",[511]="\153",[195]="\208",[197]="\85",[199]="\178",[181]="\82",[102]="\116",[103]="\71",[285]="\144",[105]="\102",[211]="\199",[213]="\123",[301]="\66",[305]="\46",[219]="\137",[81]="\67",[91]="\88",[157]="\130",[325]="\95",[29]="\58",[231]="\201",[117]="\99",[341]="\222",[237]="\77",[239]="\211",[71]="\223"},[10]={[710]="\149",[245]="\60",[742]="\172",[774]="\81",[134]="\151",[917]="\145",[274]="\216",[405]="\242",[146]="\194",[838]="\246",[298]="\248",[870]="\189",[1013]="\150",[894]="\190",[326]="\244",[330]="\166",[334]="\217",[465]="\179",[346]="\59",[354]="\180",[966]="\212",[974]="\143",[370]="\148",[998]="\154",[625]="\138",[382]="\161",[194]="\141",[198]="\126",[402]="\96",[206]="\185",[586]="\129",[721]="\187",[610]="\135",[618]="\181",[626]="\72",[226]="\62",[454]="\127",[658]="\113",[462]="\164",[234]="\214",[474]="\140",[242]="\106",[714]="\188",[730]="\87",[498]="\237",[746]="\125",[754]="\229",[786]="\128",[202]="\93",[18]="\255",[810]="\173",[846]="\131",[74]="\192",[842]="\142",[977]="\252",[858]="\235",[78]="\134",[874]="\234",[882]="\90",[646]="\92",[1006]="\160",[126]="\165",[914]="\221",[718]="\94",[738]="\238",[638]="\197",[482]="\230",[34]="\220",[962]="\133",[6]="\213",[706]="\219",[986]="\171",[994]="\233",[866]="\200",[1010]="\247",[98]="\169",[518]="\236",[494]="\207",[230]="\205",[542]="\191",[501]="\202",[530]="\203",[450]="\204",[209]="\158",[106]="\186",[590]="\136",[218]="\232",[733]="\124",[309]="\168",[221]="\152",[757]="\240",[113]="\215",[114]="\156",[362]="\239",[486]="\132",[358]="\249",[262]="\75",[30]="\218",[821]="\195",[546]="\253"}}
local function huffmanDecode(encoded)
local h1,h2,h3 = byte(encoded, 1, 3)
if (not h3) or (#encoded < 4) then
error("invalid input")
end
local original_length = bit.bor(bit.lshift(h3,16), bit.lshift(h2,8), h1)
local encoded_length = #encoded+1
local decoded = {}
local decoded_length = 0
local buffer = 0
local buffer_length = 0
local code
local code_len = 2
local temp
local pos = 4
while decoded_length < original_length do
if code_len <= buffer_length then
temp = invcodes[code_len]
code = bit.band(buffer, bit.lshift(1, code_len)-1)
if temp and temp[code] then --most of the time temp is nil
decoded_length = decoded_length + 1
decoded[decoded_length] = temp[code]
buffer = bit.rshift(buffer, code_len)
buffer_length = buffer_length - code_len
code_len = 2
else
code_len = code_len + 1
if code_len > 10 then
error("malformed code")
end
end
else
buffer = bit.bor(buffer, bit.lshift(byte(encoded, pos), buffer_length))
buffer_length = buffer_length + 8
pos = pos + 1
if pos > encoded_length then
error("malformed code")
end
end
end
return concat(decoded)
end
local function invEscapeSub(str)
local escseq,body = match(str,"^(.-)\n(.-)$")
if not escseq then error("invalid input") end
return gsub(body,escseq,"\26")
end
local dictionary
local subtables
local function deserializeChunk(chunk)
local ctype,val = byte(chunk),sub(chunk,3)
if ctype == 89 then return dictionary[ val ]
elseif ctype == 86 then
local a,b,c = match(val,"^(.-),(.-),(.+)$")
return Vector( tonumber(a), tonumber(b), tonumber(c) )
elseif ctype == 65 then
local a,b,c = match(val,"^(.-),(.-),(.+)$")
return Angle( tonumber(a), tonumber(b), tonumber(c) )
elseif ctype == 84 then
local t = {}
local tv = subtables[val]
if not tv then
tv = {}
subtables[ val ] = tv
end
tv[#tv+1] = t
return t
elseif ctype == 78 then return tonumber(val)
elseif ctype == 83 then return gsub(sub(val,2,-2),"»",";")
elseif ctype == 66 then return val == "t"
elseif ctype == 80 then return 1
end
error(format("AD1 deserialization failed: invalid chunk (%u:%s)",ctype,val))
end
local function deserializeAD1(dupestring)
dupestring = dupestring:Replace("\r\n", "\n")
local header, extraHeader, dupeBlock, dictBlock = dupestring:match("%[Info%]\n(.+)\n%[More Information%]\n(.+)\n%[Save%]\n(.+)\n%[Dict%]\n(.+)")
if not header then
error("unknown duplication format")
end
local info = {}
for k,v in header:gmatch("([^\n:]+):([^\n]+)") do
info[k] = v
end
local moreinfo = {}
for k,v in extraHeader:gmatch("([^\n:]+):([^\n]+)") do
moreinfo[k] = v
end
dictionary = {}
for k,v in dictBlock:gmatch("(.-):\"(.-)\"\n") do
dictionary[k] = v
end
local dupe = {}
for key,block in dupeBlock:gmatch("([^\n:]+):([^\n]+)") do
local tables = {}
subtables = {}
local head
for id,chunk in block:gmatch('(%w+){(.-)}') do
--check if this table is the trunk
if byte(id) == 72 then
id = sub(id,2)
head = id
end
tables[id] = {}
for kv in gmatch(chunk,'[^;]+') do
local k,v = match(kv,'(.-)=(.+)')
if k then
k = deserializeChunk( k )
v = deserializeChunk( v )
tables[id][k] = v
else
v = deserializeChunk( kv )
local tid = tables[id]
tid[#tid+1]=v
end
end
end
--Restore table references
for id,tbls in pairs( subtables ) do
for _,tbl in pairs( tbls ) do
if not tables[id] then error("attempt to reference a nonexistent table") end
for k,v in pairs(tables[id]) do
tbl[k] = v
end
end
end
dupe[key] = tables[ head ]
end
return dupe, info, moreinfo
end
--seperates the header and body and converts the header to a table
local function getInfo(str)
local last = str:find("\2")
if not last then
error("attempt to read AD2 file with malformed info block error 1")
end
local info = {}
local ss = str:sub(1,last-1)
for k,v in ss:gmatch("(.-)\1(.-)\1") do
info[k] = v
end
if info.check ~= "\r\n\t\n" then
if info.check == "\10\9\10" then
error("detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)")
else
error("attempt to read AD2 file with malformed info block error 2")
end
end
return info, str:sub(last+2)
end
--decoders for individual versions go here
local versions = {}
versions[2] = function(encodedDupe)
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
encodedDupe = encodedDupe:Replace("\r\n", "\n")
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
local info, dupestring = getInfo(encodedDupe:sub(7))
return deserialize_v2(
lzwDecode(
huffmanDecode(
invEscapeSub(dupestring)
)
)
), info
end
versions[1] = function(encodedDupe)
encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t")
encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t")
encodedDupe = encodedDupe:Replace("\r\n", "\n")
encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n")
local info, dupestring = getInfo(encodedDupe:sub(7))
return deserialize_v1(
lzwDecode(
huffmanDecode(
invEscapeSub(dupestring)
)
)
), info
end
versions[0] = deserializeAD1
AdvDupe2.LegacyDecoders = versions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,143 @@
--Save a file to the client
local function SaveFile(ply, cmd, args)
if(not ply.AdvDupe2 or not ply.AdvDupe2.Entities or next(ply.AdvDupe2.Entities)==nil)then AdvDupe2.Notify(ply,"Duplicator is empty, nothing to save.", NOTIFY_ERROR) return end
if(not game.SinglePlayer() and CurTime()-(ply.AdvDupe2.FileMod or 0) < 0)then
AdvDupe2.Notify(ply,"Cannot save at the moment. Please Wait...", NOTIFY_ERROR)
return
end
if(ply.AdvDupe2.Pasting || ply.AdvDupe2.Downloading)then
AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR)
return false
end
ply.AdvDupe2.FileMod = CurTime()+tonumber(GetConVarString("AdvDupe2_FileModificationDelay")+2)
local name = string.Explode("/", args[1])
ply.AdvDupe2.Name = name[#name]
net.Start("AdvDupe2_SetDupeInfo")
net.WriteString(ply.AdvDupe2.Name)
net.WriteString(ply:Nick())
net.WriteString(os.date("%d %B %Y"))
net.WriteString(os.date("%I:%M %p"))
net.WriteString("")
net.WriteString(args[2] or "")
net.WriteString(table.Count(ply.AdvDupe2.Entities))
net.WriteString(#ply.AdvDupe2.Constraints)
net.Send(ply)
local Tab = {Entities = ply.AdvDupe2.Entities, Constraints = ply.AdvDupe2.Constraints, HeadEnt = ply.AdvDupe2.HeadEnt, Description=args[2]}
AdvDupe2.Encode( Tab, AdvDupe2.GenerateDupeStamp(ply), function(data)
AdvDupe2.SendToClient(ply, data, false)
end)
end
concommand.Add("AdvDupe2_SaveFile", SaveFile)
function AdvDupe2.SendToClient(ply, data, autosave)
if(not IsValid(ply))then return end
if #data > AdvDupe2.MaxDupeSize then
AdvDupe2.Notify(ply,"Copied duplicator filesize is too big!",NOTIFY_ERROR)
return
end
ply.AdvDupe2.Downloading = true
AdvDupe2.InitProgressBar(ply,"Saving:")
net.Start("AdvDupe2_ReceiveFile")
net.WriteBool(autosave)
net.WriteStream(data, function()
ply.AdvDupe2.Downloading = false
end)
net.Send(ply)
end
function AdvDupe2.LoadDupe(ply,success,dupe,info,moreinfo)
if(not IsValid(ply))then return end
if not success then
AdvDupe2.Notify(ply,"Could not open "..dupe,NOTIFY_ERROR)
return
end
if(not game.SinglePlayer())then
if(tonumber(GetConVarString("AdvDupe2_MaxConstraints"))~=0 and #dupe["Constraints"]>tonumber(GetConVarString("AdvDupe2_MaxConstraints")))then
AdvDupe2.Notify(ply,"Amount of constraints is greater than "..GetConVarString("AdvDupe2_MaxConstraints"),NOTIFY_ERROR)
return false
end
end
ply.AdvDupe2.Entities = {}
ply.AdvDupe2.Constraints = {}
ply.AdvDupe2.HeadEnt={}
ply.AdvDupe2.Revision = info.revision
if(info.ad1)then
ply.AdvDupe2.HeadEnt.Index = tonumber(moreinfo.Head)
local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$")
ply.AdvDupe2.HeadEnt.Pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0)
local z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1
ply.AdvDupe2.HeadEnt.Z = z
ply.AdvDupe2.HeadEnt.Pos.Z = ply.AdvDupe2.HeadEnt.Pos.Z + z
local Pos
local Ang
for k,v in pairs(dupe["Entities"])do
Pos = nil
Ang = nil
if(v.SavedParentIdx)then
if(not v.BuildDupeInfo)then v.BuildDupeInfo = {} end
v.BuildDupeInfo.DupeParentID = v.SavedParentIdx
Pos = v.LocalPos*1
Ang = v.LocalAngle*1
end
for i,p in pairs(v.PhysicsObjects)do
p.Pos = Pos or (p.LocalPos*1)
p.Pos.Z = p.Pos.Z - z
p.Angle = Ang or (p.LocalAngle*1)
p.LocalPos = nil
p.LocalAngle = nil
p.Frozen = not p.Frozen -- adv dupe 2 does this wrong way
end
v.LocalPos = nil
v.LocalAngle = nil
end
ply.AdvDupe2.Entities = dupe["Entities"]
ply.AdvDupe2.Constraints = dupe["Constraints"]
else
ply.AdvDupe2.Entities = dupe["Entities"]
ply.AdvDupe2.Constraints = dupe["Constraints"]
ply.AdvDupe2.HeadEnt = dupe["HeadEnt"]
end
AdvDupe2.ResetOffsets(ply, true)
end
local function AdvDupe2_ReceiveFile(len, ply)
if not IsValid(ply) then return end
if not ply.AdvDupe2 then ply.AdvDupe2 = {} end
ply.AdvDupe2.Name = string.match(net.ReadString(), "([%w_ ]+)") or "Advanced Duplication"
local stream = net.ReadStream(ply, function(data)
if data then
AdvDupe2.LoadDupe(ply, AdvDupe2.Decode(data))
else
AdvDupe2.Notify(ply, "Duplicator Upload Failed!", NOTIFY_ERROR, 5)
end
ply.AdvDupe2.Uploading = false
end)
if ply.AdvDupe2.Uploading then
if stream then
stream:Remove()
end
AdvDupe2.Notify(ply, "Duplicator is Busy!", NOTIFY_ERROR, 5)
elseif stream then
ply.AdvDupe2.Uploading = true
AdvDupe2.InitProgressBar(ply, "Uploading: ")
end
end
net.Receive("AdvDupe2_ReceiveFile", AdvDupe2_ReceiveFile)

View File

@@ -0,0 +1,70 @@
util.AddNetworkString("AdvDupe2_SendGhosts")
util.AddNetworkString("AdvDupe2_AddGhost")
function AdvDupe2.SendGhost(ply, AddOne)
net.Start("AdvDupe2_AddGhost")
net.WriteString(AddOne.Model)
net.WriteInt(#AddOne.PhysicsObjects, 8)
for i=0, #AddOne.PhysicsObjects do
net.WriteAngle(AddOne.PhysicsObjects[i].Angle)
net.WriteVector(AddOne.PhysicsObjects[i].Pos)
end
net.Send(ply)
end
function AdvDupe2.SendGhosts(ply)
if(not ply.AdvDupe2.Entities)then return end
local cache = {}
local temp = {}
local mdls = {}
local cnt = 1
local add = true
local head
for k,v in pairs(ply.AdvDupe2.Entities)do
temp[cnt] = v
for i=1,#cache do
if(cache[i]==v.Model)then
mdls[cnt] = i
add=false
break
end
end
if(add)then
mdls[cnt] = table.insert(cache, v.Model)
else
add = true
end
if(k==ply.AdvDupe2.HeadEnt.Index)then
head = cnt
end
cnt = cnt+1
end
if(!head)then
AdvDupe2.Notify(ply, "Invalid head entity for ghosts.", NOTIFY_ERROR);
return
end
net.Start("AdvDupe2_SendGhosts")
net.WriteInt(head, 16)
net.WriteFloat(ply.AdvDupe2.HeadEnt.Z)
net.WriteVector(ply.AdvDupe2.HeadEnt.Pos)
net.WriteInt(#cache, 16)
for i=1,#cache do
net.WriteString(cache[i])
end
net.WriteInt(cnt-1, 16)
for i=1, #temp do
net.WriteInt(mdls[i], 16)
net.WriteInt(#temp[i].PhysicsObjects, 8)
for k=0, #temp[i].PhysicsObjects do
net.WriteAngle(temp[i].PhysicsObjects[k].Angle)
net.WriteVector(temp[i].PhysicsObjects[k].Pos)
end
end
net.Send(ply)
end

View File

@@ -0,0 +1,104 @@
--[[
Title: Miscellaneous
Desc: Contains miscellaneous (serverside) things AD2 needs to function that don't fit anywhere else.
Author: TB
Version: 1.0
]]
--[[
Name: SavePositions
Desc: Save the position of the entities to prevent sagging on dupe.
Params: <entity> Constraint
Returns: nil
]]
local function SavePositions( Constraint )
if IsValid(Constraint) then
if Constraint.BuildDupeInfo then return end
local BuildDupeInfo = {}
Constraint.BuildDupeInfo = BuildDupeInfo
local Ent1, Ent2
if IsValid(Constraint.Ent) then
if Constraint.Ent:GetPhysicsObjectCount()>1 then
BuildDupeInfo.Ent1Ang = Constraint.Ent:GetAngles()
else
BuildDupeInfo.Ent1Ang = Constraint.Ent:GetPhysicsObject():GetAngles()
end
end
if IsValid(Constraint.Ent1) then
if Constraint.Ent1:GetPhysicsObjectCount()>1 then
local Bone = Constraint.Ent1:GetPhysicsObjectNum(Constraint.Bone1)
BuildDupeInfo.Ent1Ang = Constraint.Ent1:GetAngles()
BuildDupeInfo.Ent1Pos = Constraint.Ent1:GetPos()
BuildDupeInfo.Bone1 = Constraint.Bone1
BuildDupeInfo.Bone1Pos = Bone:GetPos() - Constraint.Ent1:GetPos()
BuildDupeInfo.Bone1Angle = Bone:GetAngles()
else
local Bone = Constraint.Ent1:GetPhysicsObject()
BuildDupeInfo.Ent1Ang = Bone:GetAngles()
BuildDupeInfo.Ent1Pos = Bone:GetPos()
end
if IsValid(Constraint.Ent2) then
if Constraint.Ent2:GetPhysicsObjectCount()>1 then
local Bone = Constraint.Ent2:GetPhysicsObjectNum(Constraint.Bone2)
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Constraint.Ent2:GetPos()
BuildDupeInfo.Ent2Ang = Constraint.Ent2:GetAngles()
BuildDupeInfo.Bone2 = Constraint.Bone2
BuildDupeInfo.Bone2Pos = Bone:GetPos() - Constraint.Ent2:GetPos()
BuildDupeInfo.Bone2Angle = Bone:GetAngles()
else
local Bone = Constraint.Ent2:GetPhysicsObject()
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Bone:GetPos()
BuildDupeInfo.Ent2Ang = Bone:GetAngles()
end
elseif IsValid(Constraint.Ent4) then
if Constraint.Ent4:GetPhysicsObjectCount()>1 then
local Bone = Constraint.Ent4:GetPhysicsObjectNum(Constraint.Bone4)
BuildDupeInfo.Bone2 = Constraint.Bone4
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Constraint.Ent4:GetPos()
BuildDupeInfo.Ent2Ang = Constraint.Ent4:GetAngles()
BuildDupeInfo.Bone2Pos = Bone:GetPos() - Constraint.Ent4:GetPos()
BuildDupeInfo.Bone2Angle = Bone:GetAngles()
else
local Bone = Constraint.Ent4:GetPhysicsObject()
BuildDupeInfo.EntityPos = BuildDupeInfo.Ent1Pos - Bone:GetPos()
BuildDupeInfo.Ent2Ang = Bone:GetAngles()
end
end
end
end
end
local function monitorConstraint(name)
local oldFunc = constraint[name]
constraint[name] = function(...)
local Constraint, b, c = oldFunc(...)
if Constraint and Constraint:IsValid() then
SavePositions(Constraint)
end
return Constraint, b, c
end
end
monitorConstraint("AdvBallsocket")
monitorConstraint("Axis")
monitorConstraint("Ballsocket")
monitorConstraint("Elastic")
monitorConstraint("Hydraulic")
monitorConstraint("Keepupright")
monitorConstraint("Motor")
monitorConstraint("Muscle")
monitorConstraint("Pulley")
monitorConstraint("Rope")
monitorConstraint("Slider")
monitorConstraint("Weld")
monitorConstraint("Winch")

View File

@@ -0,0 +1,145 @@
include("shared.lua")
surface.CreateFont( "InfoRUS2", { font = "Enhanced Dot Digital-7", extended = true, size = 90, weight = 800, antialias = true })
surface.CreateFont( "InfoRUS3", { font = "Enhanced Dot Digital-7", extended = true, size = 50, weight = 800, antialias = true })
local font = "InfoRUS2"
local sizetable = {
[3] = {350, 0.5},
[4] = {470, -11.5},
[5] = {590, -11.5},
[6] = {710, 0.5},
[7] = {830, 0.5},
[8] = {950, 0.5},
}
function ENT:Initialize()
self.OldWide = self:GetWide()
self.frame = vgui.Create( "DPanel" )
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
self.frame.Text = self:GetText()
self.frame.Type = self:GetType()
self.frame.col = self:GetTColor()
self.frame.damage = 0
self.frame.appr = nil
self.frame.FX = self:GetFX()
self.frame.On = self:GetOn()
self.frame.alfa = 0
self.frame.speed = self:GetSpeed()
self.frame:SetPaintedManually( true )
self.frame.Paint = function(self,w,h)
if self.On <= 0 then
if self.alfa < 1 then return end
self.alfa = Lerp(FrameTime() * 5,self.alfa,0)
else
if self.FX > 0 then
self.alfa = math.random(100,220)
else
self.alfa = 255
end
end
surface.DisableClipping( false )
surface.SetFont(font)
local ww,hh = surface.GetTextSize(self.Text)
local multiplier = self.speed * 100
self.static = false
if self.damage < CurTime() and self.On then
if self.Type == 1 then
local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww
draw.DrawText(self.Text,font,xs,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
elseif self.Type == 2 then
if !self.appr or self.appr > ww then
self.appr = -w
else
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
end
draw.DrawText(self.Text,font,self.appr * -1,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
else
if !self.appr then
self.appr = 0
end
if w > ww then
if self.Type == 3 then
if self.appr < w-ww and !self.refl then
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
else
if self.appr <= 0 then
self.refl = nil
else
self.refl = true
self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier)
end
end
else
self.static = true
end
else
if self.appr > w-ww-50 and !self.refl then
self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier)
else
if self.appr >= 50 then
self.refl = nil
else
self.refl = true
self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier)
end
end
end
if self.static then
draw.DrawText(self.Text,font,w/2,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),1)
else
draw.DrawText(self.Text,font,self.appr,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
end
end
else
draw.DrawText(self.Text,font,math.random(0,w-ww),10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, math.random(0,255)),0)
end
surface.DisableClipping( true )
end
end
function ENT:Draw()
self:DrawModel()
if self.frame then
self.frame.Text = self:GetText()
self.frame.Type = self:GetType()
self.frame.col = self:GetTColor()
self.frame.FX = self:GetFX()
self.frame.On = self:GetOn()
self.frame.damage = self:GetNWInt("LastDamaged")
self.frame.speed = self:GetSpeed()
end
local Pos = self:GetPos()
local Ang = self:GetAngles()
local hight = 12
if self.OldWide != self:GetWide() then
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
self.OldWide = self:GetWide()
end
if self:GetWide() == 3 then
hight = 6
end
cam.Start3D2D(Pos + Ang:Up() * 1.1 - Ang:Right() * hight + Ang:Forward() * sizetable[self:GetWide()][2], Ang, 0.1)
self.frame:PaintManual()
cam.End3D2D()
end

View File

@@ -0,0 +1,54 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
util.AddNetworkString("LEDDamaged")
function ENT:Initialize()
self:SetText("Made by Mac with <3")
self:SetTColor(Vector(2.55,2,0))
self:SetType(1)
self:SetSpeed(1.5)
self:SetWide(6)
self:SetFX(1)
self:SetOn(1)
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMaterial("phoenix_storms/mat/mat_phx_carbonfiber")
self:SetColor(Color(0,0,0))
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self:SetNWInt("LastDamaged", 0)
local phys = self:GetPhysicsObject()
self.nodupe = true
self.ShareGravgun = true
phys:Wake()
end
function ENT:Think()
if self:GetModel() != "models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl" then
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
end
self:NextThink( CurTime() + 1 )
return true
end
function ENT:OnTakeDamage(data)
if data:GetDamagePosition() then
self:EmitSound("ambient/energy/spark".. math.random(1,6) ..".wav")
local effectdata = EffectData()
effectdata:SetOrigin( data:GetDamagePosition() )
util.Effect( "StunstickImpact", effectdata )
self:SetNWInt("LastDamaged", math.Round(CurTime()+2))
end
end

View File

@@ -0,0 +1,16 @@
ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Spawned Sign"
ENT.Author = "Mac"
ENT.Spawnable = false
ENT.AdminSpawnable = true
function ENT:SetupDataTables()
self:NetworkVar("String", 0, "Text")
self:NetworkVar("Vector", 0, "TColor")
self:NetworkVar("Int", 0, "Type")
self:NetworkVar("Int", 1, "Speed")
self:NetworkVar("Int", 2, "Wide")
self:NetworkVar("Int", 3, "On")
self:NetworkVar("Int", 4, "FX")
end

View File

@@ -0,0 +1,142 @@
include("shared.lua")
local font = "InfoRUS2"
local sizetable = {
[3] = {350, 0.5},
[4] = {470, -11.5},
[5] = {590, -11.5},
[6] = {710, 0.5},
[7] = {830, 0.5},
[8] = {950, 0.5},
}
function ENT:Initialize()
self.OldWide = self:GetWide()
self.frame = vgui.Create( "DPanel" )
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
self.frame.Text = self:GetText()
self.frame.Type = self:GetType()
self.frame.col = self:GetTColor()
self.frame.damage = 0
self.frame.appr = nil
self.frame.FX = self:GetFX()
self.frame.On = self:GetOn()
self.frame.alfa = 0
self.frame.speed = self:GetSpeed()
self.frame:SetPaintedManually( true )
self.frame.Paint = function(self,w,h)
if self.On <= 0 then
if self.alfa < 1 then return end
self.alfa = Lerp(FrameTime() * 5,self.alfa,0)
else
if self.FX > 0 then
self.alfa = math.random(100,220)
else
self.alfa = 255
end
end
surface.DisableClipping( false )
surface.SetFont(font)
local ww,hh = surface.GetTextSize(self.Text)
local multiplier = self.speed * 100
self.static = false
if self.damage < CurTime() and self.On then
if self.Type == 1 then
local xs = (math.fmod(SysTime() * multiplier,w+ww)) - ww
draw.DrawText(self.Text,font,xs,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
elseif self.Type == 2 then
if !self.appr or self.appr > ww then
self.appr = -w
else
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
end
draw.DrawText(self.Text,font,self.appr * -1,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
else
if !self.appr then
self.appr = 0
end
if w > ww then
if self.Type == 3 then
if self.appr < w-ww and !self.refl then
self.appr = math.Approach(self.appr, ww+w, FrameTime() * multiplier)
else
if self.appr <= 0 then
self.refl = nil
else
self.refl = true
self.appr = math.Approach(self.appr, 0, FrameTime() * multiplier)
end
end
else
self.static = true
end
else
if self.appr > w-ww-50 and !self.refl then
self.appr = math.Approach(self.appr, w-ww-50, FrameTime() * multiplier)
else
if self.appr >= 50 then
self.refl = nil
else
self.refl = true
self.appr = math.Approach(self.appr, 50, FrameTime() * multiplier)
end
end
end
if self.static then
draw.DrawText(self.Text,font,w/2,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),1)
else
draw.DrawText(self.Text,font,self.appr,10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, self.alfa),0)
end
end
else
draw.DrawText(self.Text,font,math.random(0,w-ww),10,Color(self.col.x * 100, self.col.y * 100, self.col.z * 100, math.random(0,255)),0)
end
surface.DisableClipping( true )
end
end
function ENT:Draw()
self:DrawModel()
if self.frame then
self.frame.Text = self:GetText()
self.frame.Type = self:GetType()
self.frame.col = self:GetTColor()
self.frame.FX = self:GetFX()
self.frame.On = self:GetOn()
self.frame.damage = self:GetNWInt("LastDamaged")
self.frame.speed = self:GetSpeed()
end
local Pos = self:GetPos()
local Ang = self:GetAngles()
local hight = 12
if self.OldWide != self:GetWide() then
self.frame:SetSize( sizetable[self:GetWide()][1], 120 )
self.OldWide = self:GetWide()
end
if self:GetWide() == 3 then
hight = 6
end
cam.Start3D2D(Pos + Ang:Up() * 1.1 - Ang:Right() * hight + Ang:Forward() * sizetable[self:GetWide()][2], Ang, 0.1)
self.frame:PaintManual()
cam.End3D2D()
end

View File

@@ -0,0 +1,70 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
util.AddNetworkString("LEDDamaged")
function ENT:Initialize()
self:SetText("Made by Mac with Wire and <3")
self:SetTColor(Vector(2.55,2,0))
self:SetType(1)
self:SetSpeed(1.5)
self:SetWide(6)
self:SetFX(1)
self:SetOn(1)
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMaterial("phoenix_storms/mat/mat_phx_carbonfiber")
self:SetColor(Color(0,0,0))
self:SetMoveType(MOVETYPE_VPHYSICS)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
self:SetNWInt("LastDamaged", 0)
local phys = self:GetPhysicsObject()
self.nodupe = true
self.ShareGravgun = true
phys:Wake()
self.Inputs = WireLib.CreateSpecialInputs(self, { "Text", "Type", "Speed", "Color", "Flicker", "On" }, { "STRING", "NORMAL", "NORMAL", "VECTOR", "NORMAL", "NORMAL" })
end
function ENT:TriggerInput(iname, value)
if iname == "Text" then
self:SetText(value)
elseif iname == "Type" then
self:SetType(math.Clamp(math.Round(value), 1, 4))
elseif iname == "Speed" then
self:SetSpeed(math.Clamp(value, 1, 10))
elseif iname == "Color" then
self:SetTColor(Vector(value.x/100, value.y/100, value.z/100))
elseif iname == "On" then
self:SetOn(value)
elseif iname == "Flicker" then
self:SetFX(math.Clamp(value, 0, 1))
end
end
function ENT:Think()
if self:GetModel() != "models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl" then
self:SetModel("models/squad/sf_plates/sf_plate1x"..self:GetWide()..".mdl")
end
end
function ENT:OnTakeDamage(data)
if data:GetDamagePosition() then
self:EmitSound("ambient/energy/spark".. math.random(1,6) ..".wav")
local effectdata = EffectData()
effectdata:SetOrigin( data:GetDamagePosition() )
util.Effect( "StunstickImpact", effectdata )
self:SetNWInt("LastDamaged", math.Round(CurTime()+2))
end
end

View File

@@ -0,0 +1,17 @@
ENT.Type = "anim"
ENT.Base = "base_wire_entity"
ENT.PrintName = "Spawned Sign Wire"
ENT.WireDebugName = "LED Screen"
ENT.Author = "Mac"
ENT.Spawnable = false
ENT.AdminSpawnable = true
function ENT:SetupDataTables()
self:NetworkVar("String", 0, "Text")
self:NetworkVar("Vector", 0, "TColor")
self:NetworkVar("Int", 0, "Type")
self:NetworkVar("Int", 1, "Speed")
self:NetworkVar("Int", 2, "Wide")
self:NetworkVar("Int", 3, "On")
self:NetworkVar("Int", 4, "FX")
end

View File

@@ -0,0 +1,6 @@
include( "shared.lua" )
function ENT:Draw()
self.BaseClass.Draw(self)
self.Entity:DrawModel()
end

View File

@@ -0,0 +1,291 @@
--[[
Title: Adv. Dupe 2 Contraption Spawner
Desc: A mobile duplicator
Author: TB
Version: 1.0
]]
AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "shared.lua" )
if(WireLib)then
include( "entities/base_wire_entity.lua" )
end
include( "shared.lua" )
function ENT:Initialize()
self.Entity:SetMoveType( MOVETYPE_NONE )
self.Entity:PhysicsInit( SOLID_VPHYSICS )
self.Entity:SetCollisionGroup( COLLISION_GROUP_WORLD )
self.Entity:DrawShadow( false )
local phys = self.Entity:GetPhysicsObject()
if phys:IsValid() then
phys:Wake()
end
self.UndoList = {}
self.Ghosts = {}
self.SpawnLastValue = 0
self.UndoLastValue = 0
self.LastSpawnTime = 0
self.DupeName = ""
self.CurrentPropCount = 0
if WireLib then
self.Inputs = Wire_CreateInputs(self.Entity, {"Spawn", "Undo"})
self.Outputs = WireLib.CreateSpecialOutputs(self.Entity, {"Out"}, { "NORMAL" })
end
end
/*-----------------------------------------------------------------------*
* Sets options for this spawner
*-----------------------------------------------------------------------*/
function ENT:SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel, hideprops )
self.delay = delay
self.undo_delay = undo_delay
--Key bindings
self.key = key
self.undo_key = undo_key
numpad.Remove( self.CreateKey )
numpad.Remove( self.UndoKey )
self.CreateKey = numpad.OnDown( ply, self.key , "ContrSpawnerCreate", self.Entity, true )
self.UndoKey = numpad.OnDown( ply, self.undo_key, "ContrSpawnerUndo" , self.Entity, true )
-- Other parameters
self.DisableGravity = disgrav
self.DisableDrag = disdrag
self.AddVelocity = addvel
self.HideProps = hideprops
-- Store the player's current dupe name
self.DupeName = tostring(ply.AdvDupe2.Name)
self:ShowOutput()
end
function ENT:UpdateOptions( options )
self:SetOptions( options["delay"], options["undo_delay"], options["key"], options["undo_key"])
end
function ENT:AddGhosts()
if self.HideProps then return end
local moveable = self:GetPhysicsObject():IsMoveable()
self:GetPhysicsObject():EnableMotion(false)
local EntTable, GhostEntity, Phys
local Offset = self.DupeAngle - self.EntAngle
for EntIndex,v in pairs(self.EntityTable)do
if(EntIndex!=self.HeadEnt)then
if(self.EntityTable[EntIndex].Class=="gmod_contr_spawner")then self.EntityTable[EntIndex] = nil continue end
EntTable = table.Copy(self.EntityTable[EntIndex])
if(EntTable.BuildDupeInfo && EntTable.BuildDupeInfo.PhysicsObjects)then
Phys = EntTable.BuildDupeInfo.PhysicsObjects[0]
else
if(!v.BuildDupeInfo)then v.BuildDupeInfo = {} end
v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects)
Phys = EntTable.PhysicsObjects[0]
end
GhostEntity = nil
if(EntTable.Model==nil || !util.IsValidModel(EntTable.Model)) then EntTable.Model="models/error.mdl" end
if ( EntTable.Model:sub( 1, 1 ) == "*" ) then
GhostEntity = ents.Create( "func_physbox" )
else
GhostEntity = ents.Create( "gmod_ghost" )
end
// If there are too many entities we might not spawn..
if ( !GhostEntity || GhostEntity == NULL ) then return end
duplicator.DoGeneric( GhostEntity, EntTable )
GhostEntity:Spawn()
GhostEntity:DrawShadow( false )
GhostEntity:SetMoveType( MOVETYPE_NONE )
GhostEntity:SetSolid( SOLID_VPHYSICS );
GhostEntity:SetNotSolid( true )
GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA )
GhostEntity:SetColor( Color(255, 255, 255, 150) )
GhostEntity:SetAngles(Phys.Angle)
GhostEntity:SetPos(self:GetPos() + Phys.Pos - self.Offset)
self:SetAngles(self.EntAngle)
GhostEntity:SetParent( self )
self:SetAngles(self.DupeAngle)
self.Ghosts[EntIndex] = GhostEntity
end
end
self:SetAngles(self.DupeAngle)
self:GetPhysicsObject():EnableMotion(moveable)
end
function ENT:GetCreationDelay() return self.delay end
function ENT:GetDeletionDelay() return self.undo_delay end
function ENT:OnTakeDamage( dmginfo ) self.Entity:TakePhysicsDamage( dmginfo ) end
function ENT:SetDupeInfo( HeadEnt, EntityTable, ConstraintTable )
self.HeadEnt = HeadEnt
self.EntityTable = EntityTable
self.ConstraintTable = ConstraintTable
if(!self.DupeAngle)then self.DupeAngle = self:GetAngles() end
if(!self.EntAngle)then self.EntAngle = EntityTable[HeadEnt].PhysicsObjects[0].Angle end
if(!self.Offset)then self.Offset = self.EntityTable[HeadEnt].PhysicsObjects[0].Pos end
local headpos, headang = EntityTable[HeadEnt].PhysicsObjects[0].Pos, EntityTable[HeadEnt].PhysicsObjects[0].Angle
for k, v in pairs(EntityTable) do
for o, p in pairs(v.PhysicsObjects) do
p.LPos, p.LAngle = WorldToLocal(p.Pos, p.Angle, headpos, headang)
end
end
end
function ENT:DoSpawn( ply )
-- Explicitly allow spawning if no player is provided, but an invalid player gets denied. This can happen when a player leaves the server.
if not (ply and ply:IsValid()) then return end
for k, v in pairs(self.EntityTable) do
for o, p in pairs(v.PhysicsObjects) do
p.Pos, p.Angle = self:LocalToWorld(p.LPos), self:LocalToWorldAngles(p.LAngle)
end
end
/*local AngleOffset = self.EntAngle
AngleOffset = self:GetAngles() - AngleOffset
local AngleOffset2 = Angle(0,0,0)
//AngleOffset2.y = AngleOffset.y
AngleOffset2:RotateAroundAxis(self:GetUp(), AngleOffset.y)
AngleOffset2:RotateAroundAxis(self:GetRight(),AngleOffset.p)
AngleOffset2:RotateAroundAxis(self:GetForward(),AngleOffset.r)*/
local Ents, Constrs = AdvDupe2.duplicator.Paste(ply, self.EntityTable, self.ConstraintTable, nil, nil, Vector(0,0,0), true)
local i = #self.UndoList+1
self.UndoList[i] = Ents
local undotxt = "AdvDupe2: Contraption ("..tostring(self.DupeName)..")"
undo.Create(undotxt)
local phys
for k,ent in pairs(Ents)do
phys = ent:GetPhysicsObject()
if IsValid(phys) then
phys:Wake()
if(self.DisableGravity==1)then phys:EnableGravity(false) end
if(self.DisableDrag==1)then phys:EnableDrag(false) end
phys:EnableMotion(true)
if(ent.SetForce)then ent.SetForce(ent, ent.force, ent.mul) end
if(self.AddVelocity==1)then
phys:SetVelocity( self:GetVelocity() )
phys:AddAngleVelocity( self:GetPhysicsObject():GetAngleVelocity() )
end
end
undo.AddEntity(ent)
end
undo.SetPlayer(ply)
undo.Finish()
if(self.undo_delay>0)then
timer.Simple(self.undo_delay, function()
if(self.UndoList && self.UndoList[i])then
for k,ent in pairs(self.UndoList[i]) do
if(IsValid(ent)) then
ent:Remove()
end
end
end
end)
end
end
function ENT:DoUndo( ply )
if(!self.UndoList || #self.UndoList == 0)then return end
local entities = self.UndoList[ #self.UndoList ]
self.UndoList[ #self.UndoList ] = nil
for _,ent in pairs(entities) do
if (IsValid(ent)) then
ent:Remove()
end
end
end
function ENT:TriggerInput(iname, value)
local ply = self:GetPlayer()
if(iname == "Spawn")then
if ((value > 0) == self.SpawnLastValue) then return end
self.SpawnLastValue = (value > 0)
if(self.SpawnLastValue)then
local delay = self:GetCreationDelay()
if (delay == 0) then self:DoSpawn( ply ) return end
if(CurTime() < self.LastSpawnTime)then return end
self:DoSpawn( ply )
self.LastSpawnTime=CurTime()+delay
end
elseif (iname == "Undo") then
// Same here
if((value > 0) == self.UndoLastValue)then return end
self.UndoLastValue = (value > 0)
if(self.UndoLastValue)then self:DoUndo(ply) end
end
end
local flags = {"Enabled", "Disabled"}
function ENT:ShowOutput()
local text = "\nGravity: "..((self.DisableGravity == 1) and flags[1] or flags[2])
text = text.."\nDrag: " ..((self.DisableDrag == 1) and flags[1] or flags[2])
text = text.."\nVelocity: "..((self.AddVelocity == 1) and flags[1] or flags[2])
self.Entity:SetOverlayText(
"Spawn Name: " .. tostring(self.DupeName) ..
"\nSpawn Delay: " .. tostring(self:GetCreationDelay()) ..
"\nUndo Delay: ".. tostring(self:GetDeletionDelay()) ..
text
)
end
/*-----------------------------------------------------------------------*
* Handler for spawn keypad input
*-----------------------------------------------------------------------*/
function SpawnContrSpawner( ply, ent )
if (!ent || !ent:IsValid()) then return end
local delay = ent:GetTable():GetCreationDelay()
if(delay == 0) then
ent:DoSpawn( ply )
return
end
if(CurTime() < ent.LastSpawnTime)then return end
ent:DoSpawn( ply )
ent.LastSpawnTime=CurTime()+delay
end
/*-----------------------------------------------------------------------*
* Handler for undo keypad input
*-----------------------------------------------------------------------*/
function UndoContrSpawner( ply, ent )
if (!ent || !ent:IsValid()) then return end
ent:DoUndo( ply, true )
end
numpad.Register( "ContrSpawnerCreate", SpawnContrSpawner )
numpad.Register( "ContrSpawnerUndo" , UndoContrSpawner )

View File

@@ -0,0 +1,10 @@
ENT.Type = "anim"
ENT.Base = WireLib and "base_wire_entity" or "base_gmodentity"
ENT.PrintName = "Contraption Spawner"
ENT.Author = "TB"
ENT.Contact = ""
ENT.Purpose = ""
ENT.Instructions = ""
ENT.Spawnable = false
ENT.AdminSpawnable = false

View File

@@ -0,0 +1,141 @@
AddCSLuaFile()
ENT.Type = "anim"
ENT.PrintName = ""
ENT.Author = ""
ENT.Contact = ""
ENT.Purpose = ""
ENT.Instructions = ""
ENT.Spawnable = false
ENT.AdminOnly = false
--[[---------------------------------------------------------
Name: Initialize
-----------------------------------------------------------]]
function ENT:Initialize()
local Radius = 6
local min = Vector( 1, 1, 1 ) * Radius * -0.5
local max = Vector( 1, 1, 1 ) * Radius * 0.5
if ( SERVER ) then
self.AttachedEntity = ents.Create( "prop_dynamic" )
self.AttachedEntity:SetModel( self:GetModel() )
self.AttachedEntity:SetAngles( self:GetAngles() )
self.AttachedEntity:SetPos( self:GetPos() )
self.AttachedEntity:SetSkin( self:GetSkin() )
self.AttachedEntity:Spawn()
self.AttachedEntity:SetParent( self.Entity )
self.AttachedEntity:DrawShadow( false )
self:SetModel( "models/props_junk/watermelon01.mdl" )
self:DeleteOnRemove( self.AttachedEntity )
-- Don't use the model's physics - create a box instead
self:PhysicsInitBox( min, max )
-- Set up our physics object here
local phys = self:GetPhysicsObject()
if ( IsValid( phys ) ) then
phys:Wake()
phys:EnableGravity( false )
phys:EnableDrag( false )
end
self:DrawShadow( false )
self:SetCollisionGroup( COLLISION_GROUP_WEAPON )
else
self.GripMaterial = Material( "sprites/grip" )
-- Get the attached entity so that clientside functions like properties can interact with it
local tab = ents.FindByClassAndParent( "prop_dynamic", self )
if ( tab && IsValid( tab[ 1 ] ) ) then self.AttachedEntity = tab[ 1 ] end
end
-- Set collision bounds exactly
self:SetCollisionBounds( min, max )
end
--[[---------------------------------------------------------
Name: Draw
-----------------------------------------------------------]]
function ENT:Draw()
render.SetMaterial( self.GripMaterial )
end
--[[---------------------------------------------------------
Name: PhysicsUpdate
-----------------------------------------------------------]]
function ENT:PhysicsUpdate( physobj )
if ( CLIENT ) then return end
-- Don't do anything if the player isn't holding us
if ( !self:IsPlayerHolding() && !self:IsConstrained() ) then
physobj:SetVelocity( Vector( 0, 0, 0 ) )
physobj:Sleep()
end
end
--[[---------------------------------------------------------
Name: Called after entity 'copy'
-----------------------------------------------------------]]
function ENT:OnEntityCopyTableFinish( tab )
-- We need to store the model of the attached entity
-- Not the one we have here.
tab.Model = self.AttachedEntity:GetModel()
-- Store the attached entity's table so we can restore it after being pasted
tab.AttachedEntityInfo = table.Copy( duplicator.CopyEntTable( self.AttachedEntity ) )
tab.AttachedEntityInfo.Pos = nil -- Don't even save angles and position, we are a parented entity
tab.AttachedEntityInfo.Angle = nil
-- Do NOT store the attached entity itself in our table!
-- Otherwise, if we copy-paste the prop with the duplicator, its AttachedEntity value will point towards the original prop's attached entity instead, and that'll break stuff
tab.AttachedEntity = nil
end
--[[---------------------------------------------------------
Name: PostEntityPaste
-----------------------------------------------------------]]
function ENT:PostEntityPaste( ply )
-- Restore the attached entity using the information we've saved
if ( IsValid( self.AttachedEntity ) ) and ( self.AttachedEntityInfo ) then
-- Apply skin, bodygroups, bone manipulator, etc.
duplicator.DoGeneric( self.AttachedEntity, self.AttachedEntityInfo )
if ( self.AttachedEntityInfo.EntityMods ) then
self.AttachedEntity.EntityMods = table.Copy( self.AttachedEntityInfo.EntityMods )
duplicator.ApplyEntityModifiers( ply, self.AttachedEntity )
end
if ( self.AttachedEntityInfo.BoneMods ) then
self.AttachedEntity.BoneMods = table.Copy( self.AttachedEntityInfo.BoneMods )
duplicator.ApplyBoneModifiers( ply, self.AttachedEntity )
end
self.AttachedEntityInfo = nil
end
end

View File

@@ -0,0 +1,386 @@
--A net extension which allows sending large streams of data without overflowing the reliable channel
--Keep it in lua/autorun so it will be shared between addons
AddCSLuaFile()
net.Stream = {}
net.Stream.SendSize = 20000 --This is the size of each packet to send
net.Stream.Timeout = 30 --How long to wait for client response before cleaning up
net.Stream.MaxWriteStreams = 1024 --The maximum number of write data items to store
net.Stream.MaxReadStreams = 128 --The maximum number of queued read data items to store
net.Stream.MaxChunks = 3200 --Maximum number of pieces the stream can send to the server. 64 MB
net.Stream.MaxSize = net.Stream.SendSize*net.Stream.MaxChunks
net.Stream.MaxTries = 3 --Maximum times the client may retry downloading the whole data
local WriteStreamQueue = {
__index = {
Add = function(self, stream)
local identifier = self.curidentifier
local startid = identifier
while self.queue[identifier] do
identifier = identifier % net.Stream.MaxWriteStreams + 1
if identifier == startid then
ErrorNoHalt("Netstream is full of WriteStreams!")
net.WriteUInt(0, 32)
return
end
end
self.curidentifier = identifier % net.Stream.MaxWriteStreams + 1
if next(self.queue)==nil then
self.activitytimeout = CurTime()+net.Stream.Timeout
timer.Create("netstream_queueclean", 5, 0, function() self:Clean() end)
end
self.queue[identifier] = stream
stream.identifier = identifier
return stream
end,
Write = function(self, ply)
local identifier = net.ReadUInt(32)
local chunkidx = net.ReadUInt(32)
local stream = self.queue[identifier]
--print("Got request", identifier, chunkidx, stream)
if stream then
if stream:Write(ply, chunkidx) then
self.activitytimeout = CurTime()+net.Stream.Timeout
stream.timeout = CurTime()+net.Stream.Timeout
end
else
-- Tell them the stream doesn't exist
net.Start("NetStreamRead")
net.WriteUInt(identifier, 32)
net.WriteUInt(0, 32)
if SERVER then net.Send(ply) else net.SendToServer() end
end
end,
Clean = function(self)
local t = CurTime()
for k, stream in pairs(self.queue) do
if (next(stream.clients)~=nil and t >= stream.timeout) or t >= self.activitytimeout then
stream:Remove()
self.queue[k] = nil
end
end
if next(self.queue)==nil then
timer.Remove("netstream_queueclean")
end
end,
},
__call = function(t)
return setmetatable({
activitytimeout = CurTime()+net.Stream.Timeout,
curidentifier = 1,
queue = {}
}, t)
end
}
setmetatable(WriteStreamQueue, WriteStreamQueue)
net.Stream.WriteStreams = WriteStreamQueue()
local ReadStreamQueue = {
__index = {
Add = function(self, stream)
local queue = self.queues[stream.player]
if #queue == net.Stream.MaxReadStreams then
ErrorNoHalt("Receiving too many ReadStream requests!")
return
end
for _, v in ipairs(queue) do
if v.identifier == stream.identifier then
ErrorNoHalt("Tried to start a new ReadStream for an already existing stream!")
return
end
end
queue[#queue+1] = stream
if #queue == 1 then
stream:Request()
end
return stream
end,
Remove = function(self, stream)
local queue = rawget(self.queues, stream.player)
if queue then
if stream == queue[1] then
table.remove(queue, 1)
local nextInQueue = queue[1]
if nextInQueue then
nextInQueue:Request()
else
self.queues[stream.player] = nil
end
else
for k, v in ipairs(queue) do
if v == stream then
table.remove(queue, k)
break
end
end
end
end
end,
Read = function(self, ply)
local identifier = net.ReadUInt(32)
local queue = rawget(self.queues, ply)
if queue and queue[1] then
queue[1]:Read(identifier)
end
end
},
__call = function(t)
return setmetatable({
queues = setmetatable({}, {__index = function(t,k) local r={} t[k]=r return r end})
}, t)
end
}
setmetatable(ReadStreamQueue, ReadStreamQueue)
net.Stream.ReadStreams = ReadStreamQueue()
local WritingDataItem = {
__index = {
Write = function(self, ply, chunkidx)
local client = self.clients[ply]
if client.finished then return false end
if chunkidx == #self.chunks+1 then self:Finished(ply) return true end
if client.downloads+#self.chunks-client.progress >= net.Stream.MaxTries * #self.chunks then self:Finished(ply) return false end
client.downloads = client.downloads + 1
local chunk = self.chunks[chunkidx]
if not chunk then return false end
client.progress = chunkidx
--print("Sending", "NetStreamRead", self.identifier, #chunk.data, chunkidx, chunk.crc)
net.Start("NetStreamRead")
net.WriteUInt(self.identifier, 32)
net.WriteUInt(#chunk.data, 32)
net.WriteUInt(chunkidx, 32)
net.WriteString(chunk.crc)
net.WriteData(chunk.data, #chunk.data)
if CLIENT then net.SendToServer() else net.Send(ply) end
return true
end,
Finished = function(self, ply)
self.clients[ply].finished = true
if self.callback then
local ok, err = xpcall(self.callback, debug.traceback, ply)
if not ok then ErrorNoHalt(err) end
end
end,
GetProgress = function(self, ply)
return self.clients[ply].progress / #self.chunks
end,
Remove = function(self)
local sendTo = {}
for ply, client in pairs(self.clients) do
if not client.finished then
client.finished = true
if CLIENT or ply:IsValid() then sendTo[#sendTo+1] = ply end
end
end
if next(sendTo)~=nil then
--print("Sending", "NetStreamRead", self.identifier, 0)
net.Start("NetStreamRead")
net.WriteUInt(self.identifier, 32)
net.WriteUInt(0, 32)
if SERVER then net.Send(sendTo) else net.SendToServer() end
end
end
},
__call = function(t, data, callback)
local chunks = {}
for i=1, math.ceil(#data / net.Stream.SendSize) do
local datachunk = string.sub(data, (i - 1) * net.Stream.SendSize + 1, i * net.Stream.SendSize)
chunks[i] = { data = datachunk, crc = util.CRC(datachunk) }
end
return setmetatable({
timeout = CurTime()+net.Stream.Timeout,
chunks = chunks,
callback = callback,
lasttouched = 0,
clients = setmetatable({},{__index = function(t,k)
local r = {
finished = false,
downloads = 0,
progress = 0,
} t[k]=r return r
end})
}, t)
end
}
setmetatable(WritingDataItem, WritingDataItem)
local ReadingDataItem = {
__index = {
Request = function(self)
if self.downloads+self.numchunks-#self.chunks >= net.Stream.MaxTries*self.numchunks then self:Remove() return end
self.downloads = self.downloads + 1
timer.Create("NetStreamReadTimeout" .. self.identifier, net.Stream.Timeout*0.5, 1, function() self:Request() end)
self:WriteRequest()
end,
WriteRequest = function(self)
--print("Requesting", self.identifier, #self.chunks)
net.Start("NetStreamWrite")
net.WriteUInt(self.identifier, 32)
net.WriteUInt(#self.chunks+1, 32)
if CLIENT then net.SendToServer() else net.Send(self.player) end
end,
Read = function(self, identifier)
if self.identifier ~= identifier then self:Request() return end
local size = net.ReadUInt(32)
if size == 0 then self:Remove() return end
local chunkidx = net.ReadUInt(32)
if chunkidx ~= #self.chunks+1 then self:Request() return end
local crc = net.ReadString()
local data = net.ReadData(size)
if crc ~= util.CRC(data) then self:Request() return end
self.chunks[chunkidx] = data
if #self.chunks == self.numchunks then self:Remove(true) return end
self:Request()
end,
GetProgress = function(self)
return #self.chunks/self.numchunks
end,
Remove = function(self, finished)
timer.Remove("NetStreamReadTimeout" .. self.identifier)
local data
if finished then
data = table.concat(self.chunks)
if self.compressed then
data = util.Decompress(data, net.Stream.MaxSize)
end
self:WriteRequest() -- Notify we finished
end
local ok, err = xpcall(self.callback, debug.traceback, data)
if not ok then ErrorNoHalt(err) end
net.Stream.ReadStreams:Remove(self)
end
},
__call = function(t, ply, callback, numchunks, identifier, compressed)
return setmetatable({
identifier = identifier,
chunks = {},
compressed = compressed,
numchunks = numchunks,
callback = callback,
player = ply,
downloads = 0
}, t)
end
}
setmetatable(ReadingDataItem, ReadingDataItem)
function net.WriteStream(data, callback, dontcompress)
if not isstring(data) then
error("bad argument #1 to 'WriteStream' (string expected, got " .. type(data) .. ")", 2)
end
if callback ~= nil and not isfunction(callback) then
error("bad argument #2 to 'WriteStream' (function expected, got " .. type(callback) .. ")", 2)
end
local compressed = not dontcompress
if compressed then
data = util.Compress(data) or ""
end
if #data == 0 then
net.WriteUInt(0, 32)
return
end
if #data > net.Stream.MaxSize then
ErrorNoHalt("net.WriteStream request is too large! ", #data/1048576, "MiB")
net.WriteUInt(0, 32)
return
end
local stream = net.Stream.WriteStreams:Add(WritingDataItem(data, callback, compressed))
if not stream then return end
--print("WriteStream", #stream.chunks, stream.identifier, compressed)
net.WriteUInt(#stream.chunks, 32)
net.WriteUInt(stream.identifier, 32)
net.WriteBool(compressed)
return stream
end
--If the receiver is a player then add it to a queue.
--If the receiver is the server then add it to a queue for each individual player
function net.ReadStream(ply, callback)
if CLIENT then
ply = NULL
else
if type(ply) ~= "Player" then
error("bad argument #1 to 'ReadStream' (Player expected, got " .. type(ply) .. ")", 2)
elseif not ply:IsValid() then
error("bad argument #1 to 'ReadStream' (Tried to use a NULL entity!)", 2)
end
end
if not isfunction(callback) then
error("bad argument #2 to 'ReadStream' (function expected, got " .. type(callback) .. ")", 2)
end
local numchunks = net.ReadUInt(32)
if numchunks == nil then
return
elseif numchunks == 0 then
local ok, err = xpcall(callback, debug.traceback, "")
if not ok then ErrorNoHalt(err) end
return
end
local identifier = net.ReadUInt(32)
local compressed = net.ReadBool()
if numchunks > net.Stream.MaxChunks then
ErrorNoHalt("ReadStream requests from ", ply, " is too large! ", numchunks * net.Stream.SendSize / 1048576, "MiB")
return
end
--print("ReadStream", numchunks, identifier, compressed)
return net.Stream.ReadStreams:Add(ReadingDataItem(ply, callback, numchunks, identifier, compressed))
end
if SERVER then
util.AddNetworkString("NetStreamWrite")
util.AddNetworkString("NetStreamRead")
end
--Send requested stream data
net.Receive("NetStreamWrite", function(len, ply)
net.Stream.WriteStreams:Write(ply or NULL)
end)
--Download the sent stream data
net.Receive("NetStreamRead", function(len, ply)
net.Stream.ReadStreams:Read(ply or NULL)
end)

View File

@@ -0,0 +1,46 @@
/*
____ _ _ ____ __ __ _ _
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|___/
*/
local function PermaPropsViewer()
if not LocalPlayer().DrawPPEnt or not istable(LocalPlayer().DrawPPEnt) then return end
local pos = LocalPlayer():EyePos() + LocalPlayer():EyeAngles():Forward() * 10
local ang = LocalPlayer():EyeAngles()
ang = Angle(ang.p + 90, ang.y, 0)
for k, v in pairs(LocalPlayer().DrawPPEnt) do
if not v or not v:IsValid() then LocalPlayer().DrawPPEnt[k] = nil continue end
render.ClearStencil()
render.SetStencilEnable(true)
render.SetStencilWriteMask(255)
render.SetStencilTestMask(255)
render.SetStencilReferenceValue(15)
render.SetStencilFailOperation(STENCILOPERATION_REPLACE)
render.SetStencilZFailOperation(STENCILOPERATION_REPLACE)
render.SetStencilPassOperation(STENCILOPERATION_KEEP)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS)
render.SetBlend(0)
v:DrawModel()
render.SetBlend(1)
render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL)
cam.Start3D2D(pos, ang, 1)
surface.SetDrawColor(255, 0, 0, 255)
surface.DrawRect(-ScrW(), -ScrH(), ScrW() * 2, ScrH() * 2)
cam.End3D2D()
v:DrawModel()
render.SetStencilEnable(false)
end
end
hook.Add("PostDrawOpaqueRenderables", "PermaPropsViewer", PermaPropsViewer)

View File

@@ -0,0 +1,468 @@
/*
____ _ _ ____ __ __ _ _
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|___/
*/
surface.CreateFont( "pp_font", {
font = "Arial",
size = 20,
weight = 700,
shadow = false
} )
local function pp_open_menu()
local Len = net.ReadFloat()
local Data = net.ReadData( Len )
local UnCompress = util.Decompress( Data )
local Content = util.JSONToTable( UnCompress )
local Main = vgui.Create( "DFrame" )
Main:SetSize( 600, 355 )
Main:Center()
Main:SetTitle("")
Main:SetVisible( true )
Main:SetDraggable( true )
Main:ShowCloseButton( true )
Main:MakePopup()
Main.Paint = function(self)
draw.RoundedBox( 0, 0, 0, self:GetWide(), self:GetTall(), Color(155, 155, 155, 220) )
surface.SetDrawColor( 17, 148, 240, 255 )
surface.DrawOutlinedRect( 0, 0, self:GetWide(), self:GetTall() )
draw.RoundedBox( 0, 0, 0, self:GetWide(), 25, Color(17, 148, 240, 200) )
surface.SetDrawColor( 17, 148, 240, 255 )
surface.DrawOutlinedRect( 0, 0, self:GetWide(), 25 )
draw.DrawText( "PermaProps Config", "pp_font", 10, 2.2, Color(255, 255, 255, 255), TEXT_ALIGN_LEFT )
end
local BSelect
local PSelect
local MainPanel = vgui.Create( "DPanel", Main )
MainPanel:SetPos( 190, 51 )
MainPanel:SetSize( 390, 275 )
MainPanel.Paint = function( self )
surface.SetDrawColor( 50, 50, 50, 200 )
surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() )
surface.DrawOutlinedRect(0, 15, self:GetWide(), 40)
end
PSelect = MainPanel
local MainLabel = vgui.Create("DLabel", MainPanel)
MainLabel:SetFont("pp_font")
MainLabel:SetPos(140, 25)
MainLabel:SetColor(Color(50, 50, 50, 255))
MainLabel:SetText("Hey ".. LocalPlayer():Nick() .." !")
MainLabel:SizeToContents()
local MainLabel2 = vgui.Create("DLabel", MainPanel)
MainLabel2:SetFont("pp_font")
MainLabel2:SetPos(80, 80)
MainLabel2:SetColor(Color(50, 50, 50, 255))
MainLabel2:SetText("There are ".. ( Content.MProps or 0 ) .." props on this map.\n\nThere are ".. ( Content.TProps or 0 ) .." props in the DB.")
MainLabel2:SizeToContents()
local RemoveMapProps = vgui.Create( "DButton", MainPanel )
RemoveMapProps:SetText( " Clear map props " )
RemoveMapProps:SetFont("pp_font")
RemoveMapProps:SetSize( 370, 30)
RemoveMapProps:SetPos( 10, 160 )
RemoveMapProps:SetTextColor( Color( 50, 50, 50, 255 ) )
RemoveMapProps.DoClick = function()
net.Start("pp_info_send")
net.WriteTable({CMD = "CLR_MAP"})
net.SendToServer()
end
RemoveMapProps.Paint = function(self)
surface.SetDrawColor(50, 50, 50, 255)
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
end
local ClearMapProps = vgui.Create( "DButton", MainPanel )
ClearMapProps:SetText( " Clear map props in the DB " )
ClearMapProps:SetFont("pp_font")
ClearMapProps:SetSize( 370, 30)
ClearMapProps:SetPos( 10, 200 )
ClearMapProps:SetTextColor( Color( 50, 50, 50, 255 ) )
ClearMapProps.DoClick = function()
Derma_Query("Are you sure you want clear map props in the db ?\nYou can't undo this action !", "PermaProps 4.0", "Yes", function() net.Start("pp_info_send") net.WriteTable({CMD = "DEL_MAP"}) net.SendToServer() end, "Cancel")
end
ClearMapProps.Paint = function(self)
surface.SetDrawColor(50, 50, 50, 255)
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
end
local ClearAllProps = vgui.Create( "DButton", MainPanel )
ClearAllProps:SetText( " Clear all props in the DB " )
ClearAllProps:SetFont("pp_font")
ClearAllProps:SetSize( 370, 30)
ClearAllProps:SetPos( 10, 240 )
ClearAllProps:SetTextColor( Color( 50, 50, 50, 255 ) )
ClearAllProps.DoClick = function()
Derma_Query("Are you sure you want clear all props in the db ?\nYou can't undo this action !", "PermaProps 4.0", "Yes", function() net.Start("pp_info_send") net.WriteTable({CMD = "DEL_ALL"}) net.SendToServer() end, "Cancel")
end
ClearAllProps.Paint = function(self)
surface.SetDrawColor(50, 50, 50, 255)
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
end
local BMain = vgui.Create("DButton", Main)
BSelect = BMain
BMain:SetText("Main")
BMain:SetFont("pp_font")
BMain:SetSize(160, 50)
BMain:SetPos(15, 27 + 25)
BMain:SetTextColor( Color( 255, 255, 255, 255 ) )
BMain.PaintColor = Color(17, 148, 240, 100)
BMain.Paint = function(self)
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor)
surface.SetDrawColor(17, 148, 240, 255)
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
end
BMain.DoClick = function( self )
if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end
BSelect = self
self.PaintColor = Color(17, 148, 240, 100)
if PSelect then PSelect:Hide() end
MainPanel:Show()
PSelect = MainPanel
end
local ConfigPanel = vgui.Create( "DPanel", Main )
ConfigPanel:SetPos( 190, 51 )
ConfigPanel:SetSize( 390, 275 )
ConfigPanel.Paint = function( self )
surface.SetDrawColor( 50, 50, 50, 200 )
surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() )
end
ConfigPanel:Hide()
local CheckCustom = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckCustom:SetPos( 5, 30 )
CheckCustom:SetText( "Custom permissions" )
CheckCustom:SetValue( 0 )
CheckCustom:SizeToContents()
CheckCustom:SetTextColor( Color( 0, 0, 0, 255) )
CheckCustom:SetDisabled( true )
local GroupsList = vgui.Create( "DComboBox", ConfigPanel )
GroupsList:SetPos( 5, 5 )
GroupsList:SetSize( 125, 20 )
GroupsList:SetValue( "Select a group..." )
local CheckBox1 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox1:SetPos( 150, 10 )
CheckBox1:SetText( "Menu" )
CheckBox1:SizeToContents()
CheckBox1:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox1:SetDisabled( true )
CheckBox1.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Menu", Name = GroupsList:GetValue()})
net.SendToServer()
end
local CheckBox2 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox2:SetPos( 150, 30 )
CheckBox2:SetText( "Edit permissions" )
CheckBox2:SizeToContents()
CheckBox2:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox2:SetDisabled( true )
CheckBox2.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Permissions", Name = GroupsList:GetValue()})
net.SendToServer()
end
local CheckBox3 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox3:SetPos( 150, 50 )
CheckBox3:SetText( "Physgun permaprops" )
CheckBox3:SizeToContents()
CheckBox3:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox3:SetDisabled( true )
CheckBox3.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Physgun", Name = GroupsList:GetValue()})
net.SendToServer()
end
local CheckBox4 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox4:SetPos( 150, 70 )
CheckBox4:SetText( "Tool permaprops" )
CheckBox4:SizeToContents()
CheckBox4:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox4:SetDisabled( true )
CheckBox4.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Tool", Name = GroupsList:GetValue()})
net.SendToServer()
end
local CheckBox5 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox5:SetPos( 150, 90 )
CheckBox5:SetText( "Property permaprops" )
CheckBox5:SizeToContents()
CheckBox5:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox5:SetDisabled( true )
CheckBox5.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Property", Name = GroupsList:GetValue()})
net.SendToServer()
end
local CheckBox6 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox6:SetPos( 150, 110 )
CheckBox6:SetText( "Save props" )
CheckBox6:SizeToContents()
CheckBox6:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox6:SetDisabled( true )
CheckBox6.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Save", Name = GroupsList:GetValue()})
net.SendToServer()
end
local CheckBox7 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox7:SetPos( 150, 130 )
CheckBox7:SetText( "Delete permaprops" )
CheckBox7:SizeToContents()
CheckBox7:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox7:SetDisabled( true )
CheckBox7.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Delete", Name = GroupsList:GetValue()})
net.SendToServer()
end
local CheckBox8 = vgui.Create( "DCheckBoxLabel", ConfigPanel )
CheckBox8:SetPos( 150, 150 )
CheckBox8:SetText( "Update permaprops" )
CheckBox8:SizeToContents()
CheckBox8:SetTextColor( Color( 0, 0, 0, 255) )
CheckBox8:SetDisabled( true )
CheckBox8.OnChange = function(Self, Value)
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Update", Name = GroupsList:GetValue()})
net.SendToServer()
end
GroupsList.OnSelect = function( panel, index, value )
CheckCustom:SetDisabled( false )
CheckCustom:SetChecked( Content.Permissions[value].Custom )
CheckBox1:SetDisabled( !Content.Permissions[value].Custom )
CheckBox1:SetChecked( Content.Permissions[value].Menu )
CheckBox2:SetDisabled( !Content.Permissions[value].Custom )
CheckBox2:SetChecked( Content.Permissions[value].Permissions )
CheckBox3:SetDisabled( !Content.Permissions[value].Custom )
CheckBox3:SetChecked( Content.Permissions[value].Physgun )
CheckBox4:SetDisabled( !Content.Permissions[value].Custom )
CheckBox4:SetChecked( Content.Permissions[value].Tool )
CheckBox5:SetDisabled( !Content.Permissions[value].Custom )
CheckBox5:SetChecked( Content.Permissions[value].Property )
CheckBox6:SetDisabled( !Content.Permissions[value].Custom )
CheckBox6:SetChecked( Content.Permissions[value].Save )
CheckBox7:SetDisabled( !Content.Permissions[value].Custom )
CheckBox7:SetChecked( Content.Permissions[value].Delete )
CheckBox8:SetDisabled( !Content.Permissions[value].Custom )
CheckBox8:SetChecked( Content.Permissions[value].Update )
end
for k, v in pairs(Content.Permissions) do
GroupsList:AddChoice(k)
end
CheckCustom.OnChange = function(Self, Value)
CheckBox1:SetDisabled( !Value )
CheckBox2:SetDisabled( !Value )
CheckBox3:SetDisabled( !Value )
CheckBox4:SetDisabled( !Value )
CheckBox5:SetDisabled( !Value )
CheckBox6:SetDisabled( !Value )
CheckBox7:SetDisabled( !Value )
CheckBox8:SetDisabled( !Value )
net.Start("pp_info_send")
net.WriteTable({CMD = "VAR", Val = Value, Data = "Custom", Name = GroupsList:GetValue()})
net.SendToServer()
end
local BConfig = vgui.Create("DButton", Main)
BConfig:SetText("Configuration")
BConfig:SetFont("pp_font")
BConfig:SetSize(160, 50)
BConfig:SetPos(15, 71 + 55)
BConfig:SetTextColor( Color( 255, 255, 255, 255 ) )
BConfig.PaintColor = Color(0, 0, 0, 0)
BConfig.Paint = function(self)
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor)
surface.SetDrawColor(17, 148, 240, 255)
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
end
BConfig.DoClick = function( self )
if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end
BSelect = self
self.PaintColor = Color(17, 148, 240, 100)
if PSelect then PSelect:Hide() end
ConfigPanel:Show()
PSelect = ConfigPanel
end
local PropsPanel = vgui.Create( "DPanel", Main )
PropsPanel:SetPos( 190, 51 )
PropsPanel:SetSize( 390, 275 )
PropsPanel.Paint = function( self )
surface.SetDrawColor( 50, 50, 50, 200 )
surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() )
end
PropsPanel:Hide()
local PropsList = vgui.Create( "DListView", PropsPanel )
PropsList:SetMultiSelect( false )
PropsList:SetSize( 390, 275 )
local ColID = PropsList:AddColumn( "ID" )
local ColEnt = PropsList:AddColumn( "Entity" )
local ColMdl = PropsList:AddColumn( "Model" )
ColID:SetMinWidth(50)
ColID:SetMaxWidth(50)
PropsList.Paint = function( self )
surface.SetDrawColor(17, 148, 240, 255)
end
PropsList.OnRowRightClick = function(panel, line)
local MenuButtonOptions = DermaMenu()
MenuButtonOptions:AddOption("Draw entity", function()
if not LocalPlayer().DrawPPEnt or not istable(LocalPlayer().DrawPPEnt) then LocalPlayer().DrawPPEnt = {} end
if LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:IsValid() then return end
local ent = ents.CreateClientProp( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Model )
ent:SetPos( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Pos )
ent:SetAngles( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Angle )
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = ent
end )
if LocalPlayer().DrawPPEnt and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] then
MenuButtonOptions:AddOption("Stop Drawing", function()
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:Remove()
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = nil
end )
end
if LocalPlayer().DrawPPEnt != nil and istable(LocalPlayer().DrawPPEnt) and table.Count(LocalPlayer().DrawPPEnt) > 0 then
MenuButtonOptions:AddOption("Stop Drawing All", function()
for k, v in pairs(LocalPlayer().DrawPPEnt) do
LocalPlayer().DrawPPEnt[k]:Remove()
LocalPlayer().DrawPPEnt[k] = nil
end
end )
end
MenuButtonOptions:AddOption("Remove", function()
net.Start("pp_info_send")
net.WriteTable({CMD = "DEL", Val = PropsList:GetLine(line):GetValue(1)})
net.SendToServer()
if LocalPlayer().DrawPPEnt and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] != nil then
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:Remove()
LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = nil
end
PropsList:RemoveLine(line)
end )
MenuButtonOptions:Open()
end
for k, v in pairs(Content.PropsList) do
PropsList:AddLine(k, v.Class, v.Model)
end
local BProps = vgui.Create("DButton", Main)
BProps:SetText("Props List")
BProps:SetFont("pp_font")
BProps:SetSize(160, 50)
BProps:SetPos(15, 115 + 85)
BProps:SetTextColor( Color( 255, 255, 255, 255 ) )
BProps.PaintColor = Color(0, 0, 0, 0)
BProps.Paint = function(self)
draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor)
surface.SetDrawColor(17, 148, 240, 255)
surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall())
end
BProps.DoClick = function( self )
if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end
BSelect = self
self.PaintColor = Color(17, 148, 240, 100)
if PSelect then PSelect:Hide() end
PropsPanel:Show()
PSelect = PropsPanel
end
end
net.Receive("pp_open_menu", pp_open_menu)

View File

@@ -0,0 +1,323 @@
/*
____ _ _ ____ __ __ _ _
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|___/
*/
if not PermaProps then PermaProps = {} end
function PermaProps.PPGetEntTable( ent )
if !ent or !ent:IsValid() then return false end
local content = {}
content.Class = ent:GetClass()
content.Pos = ent:GetPos()
content.Angle = ent:GetAngles()
content.Model = ent:GetModel()
content.Skin = ent:GetSkin()
//content.Mins, content.Maxs = ent:GetCollisionBounds()
content.ColGroup = ent:GetCollisionGroup()
content.Name = ent:GetName()
content.ModelScale = ent:GetModelScale()
content.Color = ent:GetColor()
content.Material = ent:GetMaterial()
content.Solid = ent:GetSolid()
content.RenderMode = ent:GetRenderMode()
if PermaProps.SpecialENTSSave[ent:GetClass()] != nil and isfunction(PermaProps.SpecialENTSSave[ent:GetClass()]) then
local othercontent = PermaProps.SpecialENTSSave[ent:GetClass()](ent)
if not othercontent then return false end
if othercontent != nil and istable(othercontent) then
table.Merge(content, othercontent)
end
end
do
local othercontent = hook.Run("PermaProps.OnEntitySaved", ent)
if othercontent and istable(othercontent) then
table.Merge(content, othercontent)
end
end
if ( ent.GetNetworkVars ) then
content.DT = ent:GetNetworkVars()
end
local sm = ent:GetMaterials()
if ( sm and istable(sm) ) then
for k, v in pairs( sm ) do
if ( ent:GetSubMaterial( k )) then
content.SubMat = content.SubMat or {}
content.SubMat[ k ] = ent:GetSubMaterial( k-1 )
end
end
end
local bg = ent:GetBodyGroups()
if ( bg ) then
for k, v in pairs( bg ) do
if ( ent:GetBodygroup( v.id ) > 0 ) then
content.BodyG = content.BodyG or {}
content.BodyG[ v.id ] = ent:GetBodygroup( v.id )
end
end
end
if ent:GetPhysicsObject() and ent:GetPhysicsObject():IsValid() then
content.Frozen = !ent:GetPhysicsObject():IsMoveable()
end
if content.Class == "prop_dynamic" then
content.Class = "prop_physics"
end
--content.Table = PermaProps.UselessContent( ent:GetTable() )
return content
end
function PermaProps.PPEntityFromTable( data, id )
if not id or not isnumber(id) then return false end
if not data or not istable(data) then return false end
if data.Class == "prop_physics" and data.Frozen then
data.Class = "prop_dynamic" -- Can reduce lags
end
local ent = ents.Create(data.Class)
if !ent then return false end
if !ent:IsVehicle() then if !ent:IsValid() then return false end end
ent:SetPos( data.Pos or Vector(0, 0, 0) )
ent:SetAngles( data.Angle or Angle(0, 0, 0) )
ent:SetModel( data.Model or "models/error.mdl" )
ent:SetSkin( data.Skin or 0 )
//ent:SetCollisionBounds( ( data.Mins or 0 ), ( data.Maxs or 0 ) )
ent:SetCollisionGroup( data.ColGroup or 0 )
ent:SetName( data.Name or "" )
ent:SetModelScale( data.ModelScale or 1 )
ent:SetMaterial( data.Material or "" )
ent:SetSolid( data.Solid or 6 )
if PermaProps.SpecialENTSSpawn[data.Class] != nil and isfunction(PermaProps.SpecialENTSSpawn[data.Class]) then
PermaProps.SpecialENTSSpawn[data.Class](ent, data.Other)
else
ent:Spawn()
end
hook.Run("PermaProps.OnEntityCreated", ent, data)
ent:SetRenderMode( data.RenderMode or RENDERMODE_NORMAL )
ent:SetColor( data.Color or Color(255, 255, 255, 255) )
if data.EntityMods != nil and istable(data.EntityMods) then -- OLD DATA
if data.EntityMods.material then
ent:SetMaterial( data.EntityMods.material["MaterialOverride"] or "")
end
if data.EntityMods.colour then
ent:SetColor( data.EntityMods.colour.Color or Color(255, 255, 255, 255))
end
end
if data.DT then
for k, v in pairs( data.DT ) do
if ( data.DT[ k ] == nil ) then continue end
if !isfunction(ent[ "Set" .. k ]) then continue end
ent[ "Set" .. k ]( ent, data.DT[ k ] )
end
end
if data.BodyG then
for k, v in pairs( data.BodyG ) do
ent:SetBodygroup( k, v )
end
end
if data.SubMat then
for k, v in pairs( data.SubMat ) do
if type(k) != "number" or type(v) != "string" then continue end
ent:SetSubMaterial( k-1, v )
end
end
if data.Frozen != nil then
local phys = ent:GetPhysicsObject()
if phys and phys:IsValid() then
phys:EnableMotion(!data.Frozen)
end
end
/*if data.Table then
table.Merge(ent:GetTable(), data.Table)
end*/
ent.PermaProps_ID = id
ent.PermaProps = true
// For all idiots who don't know how to config FPP, FUCK YOU
function ent:CanTool( ply, trace, tool )
if trace and IsValid(trace.Entity) and trace.Entity.PermaProps then
if tool == "permaprops" then
return true
end
return PermaProps.HasPermission( ply, "Tool")
end
end
return ent
end
function PermaProps.ReloadPermaProps()
for k, v in pairs( ents.GetAll() ) do
if v.PermaProps == true then
v:Remove()
end
end
local content = PermaProps.SQL.Query( "SELECT * FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" )
if not content or content == nil then return end
for k, v in pairs( content ) do
local data = util.JSONToTable(v.content)
local e = PermaProps.PPEntityFromTable(data, tonumber(v.id))
if !e or !e:IsValid() then continue end
end
end
hook.Add("InitPostEntity", "InitializePermaProps", PermaProps.ReloadPermaProps)
hook.Add("PostCleanupMap", "WhenCleanUpPermaProps", PermaProps.ReloadPermaProps) -- #MOMO
timer.Simple(5, function() PermaProps.ReloadPermaProps() end) -- When the hook isn't call ...
function PermaProps.SparksEffect( ent )
local effectdata = EffectData()
effectdata:SetOrigin(ent:GetPos())
effectdata:SetMagnitude(2.5)
effectdata:SetScale(2)
effectdata:SetRadius(3)
util.Effect("Sparks", effectdata, true, true)
end
function PermaProps.IsUserGroup( ply, name )
if not ply:IsValid() then return false end
return ply:GetNetworkedString("UserGroup") == name
end
function PermaProps.IsAdmin( ply )
if ( PermaProps.IsUserGroup(ply, "superadmin") or false ) then return true end
if ( PermaProps.IsUserGroup(ply, "admin") or false ) then return true end
return false
end
function PermaProps.IsSuperAdmin( ply )
return ( PermaProps.IsUserGroup(ply, "superadmin") or false )
end
function PermaProps.UselessContent( tbl )
local function SortFcn( tbl2 )
for k, v in pairs( tbl2 ) do
if isfunction( v ) or isentity( v ) then
tbl2[k] = nil
elseif istable( v ) then
SortFcn( v )
end
end
return tbl2
end
for k, v in pairs( tbl ) do
if isfunction( v ) or isentity( v ) then
tbl[k] = nil
elseif istable( v ) then
SortFcn( v )
end
end
return tbl
end

View File

@@ -0,0 +1,185 @@
/*
____ _ _ ____ __ __ _ _
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|___/
*/
util.AddNetworkString("pp_open_menu")
util.AddNetworkString("pp_info_send")
local function PermissionLoad()
if not PermaProps then PermaProps = {} end
if not PermaProps.Permissions then PermaProps.Permissions = {} end
PermaProps.Permissions["superadmin"] = { Physgun = true, Tool = true, Property = true, Save = true, Delete = true, Update = true, Menu = true, Permissions = true, Inherits = "admin", Custom = true }
PermaProps.Permissions["admin"] = { Physgun = false, Tool = false, Property = false, Save = true, Delete = true, Update = true, Menu = true, Permissions = false, Inherits = "user", Custom = true }
PermaProps.Permissions["user"] = { Physgun = false, Tool = false, Property = false, Save = false, Delete = false, Update = false, Menu = false, Permissions = false, Inherits = "user", Custom = true }
if CAMI then
for k, v in pairs(CAMI.GetUsergroups()) do
if k == "superadmin" or k == "admin" or k == "user" then continue end
PermaProps.Permissions[k] = { Physgun = false, Tool = false, Property = false, Save = false, Delete = false, Update = false, Menu = false, Permissions = false, Inherits = v.Inherits, Custom = false }
end
end
if file.Exists( "permaprops_config.txt", "DATA" ) then
file.Delete( "permaprops_config.txt" )
end
if file.Exists( "permaprops_permissions.txt", "DATA" ) then
local content = file.Read("permaprops_permissions.txt", "DATA")
local tablecontent = util.JSONToTable( content )
for k, v in pairs(tablecontent) do
if PermaProps.Permissions[k] == nil then
tablecontent[k] = nil
end
end
table.Merge(PermaProps.Permissions, ( tablecontent or {} ))
end
end
hook.Add("Initialize", "PermaPropPermLoad", PermissionLoad)
hook.Add("CAMI.OnUsergroupRegistered", "PermaPropPermLoadCAMI", PermissionLoad) -- In case something changes
local function PermissionSave()
file.Write( "permaprops_permissions.txt", util.TableToJSON(PermaProps.Permissions) )
end
local function pp_open_menu( ply )
if !PermaProps.HasPermission( ply, "Menu") then ply:ChatPrint("Access denied !") return end
local SendTable = {}
local Data_PropsList = sql.Query( "SELECT * FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" )
if Data_PropsList and #Data_PropsList < 200 then
for k, v in pairs( Data_PropsList ) do
local data = util.JSONToTable(v.content)
SendTable[v.id] = {Model = data.Model, Class = data.Class, Pos = data.Pos, Angle = data.Angle}
end
elseif Data_PropsList and #Data_PropsList > 200 then -- Too much props dude :'(
for i = 1, 199 do
local data = util.JSONToTable(Data_PropsList[i].content)
SendTable[Data_PropsList[i].id] = {Model = data.Model, Class = data.Class, Pos = data.Pos, Angle = data.Angle}
end
end
local Content = {}
Content.MProps = tonumber(sql.QueryValue("SELECT COUNT(*) FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";"))
Content.TProps = tonumber(sql.QueryValue("SELECT COUNT(*) FROM permaprops;"))
Content.PropsList = SendTable
Content.Permissions = PermaProps.Permissions
local Data = util.TableToJSON( Content )
local Compressed = util.Compress( Data )
net.Start( "pp_open_menu" )
net.WriteFloat( Compressed:len() )
net.WriteData( Compressed, Compressed:len() )
net.Send( ply )
end
concommand.Add("pp_cfg_open", pp_open_menu)
local function pp_info_send( um, ply )
if !PermaProps.HasPermission( ply, "Menu") then ply:ChatPrint("Access denied !") return end
local Content = net.ReadTable()
if Content["CMD"] == "DEL" then
Content["Val"] = tonumber(Content["Val"])
if Content["Val"] != nil and Content["Val"] <= 0 then return end
sql.Query("DELETE FROM permaprops WHERE id = ".. sql.SQLStr(Content["Val"]) .. ";")
for k, v in pairs(ents.GetAll()) do
if v.PermaProps_ID == Content["Val"] then
ply:ChatPrint("You erased " .. v:GetClass() .. " with a model of " .. v:GetModel() .. " from the database.")
v:Remove()
break
end
end
elseif Content["CMD"] == "VAR" then
if PermaProps.Permissions[Content["Name"]] == nil or PermaProps.Permissions[Content["Name"]][Content["Data"]] == nil then return end
if !isbool(Content["Val"]) then return end
if Content["Name"] == "superadmin" and ( Content["Data"] == "Custom" or Content["Data"] == "Permissions" or Content["Data"] == "Menu" ) then return end
if !PermaProps.HasPermission( ply, "Permissions") then ply:ChatPrint("Access denied !") return end
PermaProps.Permissions[Content["Name"]][Content["Data"]] = Content["Val"]
PermissionSave()
elseif Content["CMD"] == "DEL_MAP" then
sql.Query( "DELETE FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" )
PermaProps.ReloadPermaProps()
ply:ChatPrint("You erased all props from the map !")
elseif Content["CMD"] == "DEL_ALL" then
sql.Query( "DELETE FROM permaprops;" )
PermaProps.ReloadPermaProps()
ply:ChatPrint("You erased all props !")
elseif Content["CMD"] == "CLR_MAP" then
for k, v in pairs( ents.GetAll() ) do
if v.PermaProps == true then
v:Remove()
end
end
ply:ChatPrint("You have removed all props !")
end
end
net.Receive("pp_info_send", pp_info_send)

View File

@@ -0,0 +1,73 @@
/*
____ _ _ ____ __ __ _ _
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|___/
Thanks to ARitz Cracker for this part
*/
function PermaProps.HasPermission( ply, name )
if !PermaProps or !PermaProps.Permissions or !PermaProps.Permissions[ply:GetUserGroup()] then return false end
if PermaProps.Permissions[ply:GetUserGroup()].Custom == false and PermaProps.Permissions[ply:GetUserGroup()].Inherits and PermaProps.Permissions[PermaProps.Permissions[ply:GetUserGroup()].Inherits] then
return PermaProps.Permissions[PermaProps.Permissions[ply:GetUserGroup()].Inherits][name]
end
return PermaProps.Permissions[ply:GetUserGroup()][name]
end
local function PermaPropsPhys( ply, ent, phys )
if ent.PermaProps then
return PermaProps.HasPermission( ply, "Physgun")
end
end
hook.Add("PhysgunPickup", "PermaPropsPhys", PermaPropsPhys)
hook.Add( "CanPlayerUnfreeze", "PermaPropsUnfreeze", PermaPropsPhys) -- Prevents people from pressing RELOAD on the physgun
local function PermaPropsTool( ply, tr, tool )
if IsValid(tr.Entity) then
if tr.Entity.PermaProps then
if tool == "permaprops" then
return true
end
return PermaProps.HasPermission( ply, "Tool")
end
if tr.Entity:GetClass() == "sammyservers_textscreen" and tool == "permaprops" then -- Let people use PermaProps on textscreen
return true
end
end
end
hook.Add( "CanTool", "PermaPropsTool", PermaPropsTool)
local function PermaPropsProperty( ply, property, ent )
if IsValid(ent) and ent.PermaProps and tool ~= "permaprops" then
return PermaProps.HasPermission( ply, "Property")
end
end
hook.Add( "CanProperty", "PermaPropsProperty", PermaPropsProperty)

View File

@@ -0,0 +1,345 @@
/*
____ _ _ ____ __ __ _ _
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|___/
*/
if not PermaProps then PermaProps = {} end
PermaProps.SpecialENTSSpawn = {}
PermaProps.SpecialENTSSpawn["gmod_lamp"] = function( ent, data)
ent:SetFlashlightTexture( data["Texture"] )
ent:SetLightFOV( data["fov"] )
ent:SetColor( Color( data["r"], data["g"], data["b"], 255 ) )
ent:SetDistance( data["distance"] )
ent:SetBrightness( data["brightness"] )
ent:Switch( true )
ent:Spawn()
ent.Texture = data["Texture"]
ent.KeyDown = data["KeyDown"]
ent.fov = data["fov"]
ent.distance = data["distance"]
ent.r = data["r"]
ent.g = data["g"]
ent.b = data["b"]
ent.brightness = data["brightness"]
return true
end
PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"] = function( ent, data)
if ( ent:GetModel() == "models/buggy.mdl" ) then ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jeep_test.txt" ) end
if ( ent:GetModel() == "models/vehicle.mdl" ) then ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jalopy.txt" ) end
if ( data["VehicleTable"] && data["VehicleTable"].KeyValues ) then
for k, v in pairs( data["VehicleTable"].KeyValues ) do
ent:SetKeyValue( k, v )
end
end
ent:Spawn()
ent:Activate()
ent:SetVehicleClass( data["VehicleName"] )
ent.VehicleName = data["VehicleName"]
ent.VehicleTable = data["VehicleTable"]
ent.ClassOverride = data["Class"]
return true
end
PermaProps.SpecialENTSSpawn["prop_vehicle_jeep_old"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"]
PermaProps.SpecialENTSSpawn["prop_vehicle_airboat"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"]
PermaProps.SpecialENTSSpawn["prop_vehicle_prisoner_pod"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"]
PermaProps.SpecialENTSSpawn["prop_ragdoll"] = function( ent, data )
if !data or !istable( data ) then return end
ent:Spawn()
ent:Activate()
if data["Bones"] then
for objectid, objectdata in pairs( data["Bones"] ) do
local Phys = ent:GetPhysicsObjectNum( objectid )
if !IsValid( Phys ) then continue end
if ( isvector( objectdata.Pos ) && isangle( objectdata.Angle ) ) then
local pos, ang = LocalToWorld( objectdata.Pos, objectdata.Angle, Vector(0, 0, 0), Angle(0, 0, 0) )
Phys:SetPos( pos )
Phys:SetAngles( ang )
Phys:Wake()
if objectdata.Frozen then
Phys:EnableMotion( false )
end
end
end
end
if data["BoneManip"] and ent:IsValid() then
for k, v in pairs( data["BoneManip"] ) do
if ( v.s ) then ent:ManipulateBoneScale( k, v.s ) end
if ( v.a ) then ent:ManipulateBoneAngles( k, v.a ) end
if ( v.p ) then ent:ManipulateBonePosition( k, v.p ) end
end
end
if data["Flex"] and ent:IsValid() then
for k, v in pairs( data["Flex"] ) do
ent:SetFlexWeight( k, v )
end
if ( Scale ) then
ent:SetFlexScale( Scale )
end
end
return true
end
PermaProps.SpecialENTSSpawn["sammyservers_textscreen"] = function( ent, data )
if !data or !istable( data ) then return end
ent:Spawn()
ent:Activate()
if data["Lines"] then
for k, v in pairs(data["Lines"] or {}) do
ent:SetLine(k, v.text, Color(v.color.r, v.color.g, v.color.b, v.color.a), v.size, v.font, v.rainbow or 0)
end
end
return true
end
PermaProps.SpecialENTSSpawn["NPC"] = function( ent, data )
if data and istable( data ) then
if data["Equipment"] then
local valid = false
for _, v in pairs( list.Get( "NPCUsableWeapons" ) ) do
if v.class == data["Equipment"] then valid = true break end
end
if ( data["Equipment"] && data["Equipment"] != "none" && valid ) then
ent:SetKeyValue( "additionalequipment", data["Equipment"] )
ent.Equipment = data["Equipment"]
end
end
end
ent:Spawn()
ent:Activate()
return true
end
if list.Get( "NPC" ) and istable(list.Get( "NPC" )) then
for k, v in pairs(list.Get( "NPC" )) do
PermaProps.SpecialENTSSpawn[k] = PermaProps.SpecialENTSSpawn["NPC"]
end
end
PermaProps.SpecialENTSSpawn["item_ammo_crate"] = function( ent, data )
if data and istable(data) and data["type"] then
ent.type = data["type"]
ent:SetKeyValue( "AmmoType", math.Clamp( data["type"], 0, 9 ) )
end
ent:Spawn()
ent:Activate()
return true
end
PermaProps.SpecialENTSSave = {}
PermaProps.SpecialENTSSave["gmod_lamp"] = function( ent )
local content = {}
content.Other = {}
content.Other["Texture"] = ent.Texture
content.Other["KeyDown"] = ent.KeyDown
content.Other["fov"] = ent.fov
content.Other["distance"] = ent.distance
content.Other["r"] = ent.r
content.Other["g"] = ent.g
content.Other["b"] = ent.b
content.Other["brightness"] = ent.brightness
return content
end
PermaProps.SpecialENTSSave["prop_vehicle_jeep"] = function( ent )
if not ent.VehicleTable then return false end
local content = {}
content.Other = {}
content.Other["VehicleName"] = ent.VehicleName
content.Other["VehicleTable"] = ent.VehicleTable
content.Other["ClassOverride"] = ent.ClassOverride
return content
end
PermaProps.SpecialENTSSave["prop_vehicle_jeep_old"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"]
PermaProps.SpecialENTSSave["prop_vehicle_airboat"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"]
PermaProps.SpecialENTSSave["prop_vehicle_prisoner_pod"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"]
PermaProps.SpecialENTSSave["prop_ragdoll"] = function( ent )
local content = {}
content.Other = {}
content.Other["Bones"] = {}
local num = ent:GetPhysicsObjectCount()
for objectid = 0, num - 1 do
local obj = ent:GetPhysicsObjectNum( objectid )
if ( !obj:IsValid() ) then continue end
content.Other["Bones"][ objectid ] = {}
content.Other["Bones"][ objectid ].Pos = obj:GetPos()
content.Other["Bones"][ objectid ].Angle = obj:GetAngles()
content.Other["Bones"][ objectid ].Frozen = !obj:IsMoveable()
if ( obj:IsAsleep() ) then content.Other["Bones"][ objectid ].Sleep = true end
content.Other["Bones"][ objectid ].Pos, content.Other["Bones"][ objectid ].Angle = WorldToLocal( content.Other["Bones"][ objectid ].Pos, content.Other["Bones"][ objectid ].Angle, Vector( 0, 0, 0 ), Angle( 0, 0, 0 ) )
end
if ( ent:HasBoneManipulations() ) then
content.Other["BoneManip"] = {}
for i = 0, ent:GetBoneCount() do
local t = {}
local s = ent:GetManipulateBoneScale( i )
local a = ent:GetManipulateBoneAngles( i )
local p = ent:GetManipulateBonePosition( i )
if ( s != Vector( 1, 1, 1 ) ) then t[ 's' ] = s end -- scale
if ( a != Angle( 0, 0, 0 ) ) then t[ 'a' ] = a end -- angle
if ( p != Vector( 0, 0, 0 ) ) then t[ 'p' ] = p end -- position
if ( table.Count( t ) > 0 ) then
content.Other["BoneManip"][ i ] = t
end
end
end
content.Other["FlexScale"] = ent:GetFlexScale()
for i = 0, ent:GetFlexNum() do
local w = ent:GetFlexWeight( i )
if ( w != 0 ) then
content.Other["Flex"] = content.Other["Flex"] or {}
content.Other["Flex"][ i ] = w
end
end
return content
end
PermaProps.SpecialENTSSave["sammyservers_textscreen"] = function( ent )
local content = {}
content.Other = {}
content.Other["Lines"] = ent.lines or {}
return content
end
PermaProps.SpecialENTSSave["prop_effect"] = function( ent )
local content = {}
content.Class = "pp_prop_effect"
content.Model = ent.AttachedEntity:GetModel()
return content
end
PermaProps.SpecialENTSSave["pp_prop_effect"] = PermaProps.SpecialENTSSave["prop_effect"]
PermaProps.SpecialENTSSave["NPC"] = function( ent )
if !ent.Equipment then return {} end
local content = {}
content.Other = {}
content.Other["Equipment"] = ent.Equipment
return content
end
if list.Get( "NPC" ) and istable(list.Get( "NPC" )) then
for k, v in pairs(list.Get( "NPC" )) do
PermaProps.SpecialENTSSave[k] = PermaProps.SpecialENTSSave["NPC"]
end
end
PermaProps.SpecialENTSSave["item_ammo_crate"] = function( ent )
local content = {}
content.Other = {}
content.Other["type"] = ent.type
return content
end

View File

@@ -0,0 +1,30 @@
/*
____ _ _ ____ __ __ _ _
/ ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___
| | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \
| |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) |
\____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/
|___/
*/
sql.Query("CREATE TABLE IF NOT EXISTS permaprops('id' INTEGER NOT NULL, 'map' TEXT NOT NULL, 'content' TEXT NOT NULL, PRIMARY KEY('id'));")
if not PermaProps then PermaProps = {} end
PermaProps.SQL = {}
/* NOT WORKS AT THE MOMENT
PermaProps.SQL.MySQL = false
PermaProps.SQL.Host = "127.0.0.1"
PermaProps.SQL.Username = "username"
PermaProps.SQL.Password = "password"
PermaProps.SQL.Database_name = "PermaProps"
PermaProps.SQL.Database_port = 3306
PermaProps.SQL.Preferred_module = "mysqloo"
*/
function PermaProps.SQL.Query( data )
return sql.Query( data )
end

View File

@@ -0,0 +1,80 @@
PLUGIN.name = "Build Tools"
PLUGIN.author = "Various (Портирован для Helix)"
PLUGIN.description = "Набор строительных инструментов: AdvDupe2, Permaprops, Precision Tool"
-- Устанавливаем высокий приоритет загрузки, чтобы плагин загрузился раньше инструментов
PLUGIN.priority = 100
-- Конфигурация
ix.config.Add("buildToolsEnabled", true, "Включить строительные инструменты", nil, {
category = "Build Tools"
})
ix.config.Add("buildToolsAdvDupe2", true, "Включить AdvDupe2 (дублирование)", nil, {
category = "Build Tools"
})
ix.config.Add("buildToolsPermaprops", true, "Включить Permaprops (постоянные пропы)", nil, {
category = "Build Tools"
})
ix.config.Add("buildToolsPrecision", true, "Включить Precision Tool (точное позиционирование)", nil, {
category = "Build Tools"
})
ix.config.Add("buildToolsNoCollideWorld", true, "Включить NoCollide World (отключение коллизий с миром)", nil, {
category = "Build Tools"
})
-- Загрузка NetStream (библиотека для сетевых сообщений)
ix.util.Include("netstream.lua")
-- Загрузка AdvDupe2
if ix.config.Get("buildToolsAdvDupe2", true) then
-- Инициализация глобальной таблицы AdvDupe2
AdvDupe2 = AdvDupe2 or {}
print("[Build Tools] Initializing AdvDupe2...")
-- Shared
ix.util.Include("advdupe2/sh_codec_legacy.lua")
ix.util.Include("advdupe2/sh_codec.lua")
-- Server
ix.util.Include("advdupe2/sv_file.lua")
ix.util.Include("advdupe2/sv_ghost.lua")
ix.util.Include("advdupe2/sv_clipboard.lua")
ix.util.Include("advdupe2/sv_misc.lua")
-- Client
ix.util.Include("advdupe2/cl_file.lua")
ix.util.Include("advdupe2/cl_ghost.lua")
ix.util.Include("advdupe2/file_browser.lua")
print("[Build Tools] AdvDupe2 loaded successfully")
end
-- Загрузка Permaprops
if ix.config.Get("buildToolsPermaprops", true) then
-- Server
ix.util.Include("permaprops/sv_lib.lua")
ix.util.Include("permaprops/sv_menu.lua")
ix.util.Include("permaprops/sv_permissions.lua")
ix.util.Include("permaprops/sv_specialfcn.lua")
ix.util.Include("permaprops/sv_sql.lua")
-- Client
ix.util.Include("permaprops/cl_drawent.lua")
ix.util.Include("permaprops/cl_menu.lua")
end
function PLUGIN:Initialize()
print("[Build Tools] Загружены строительные инструменты!")
if ix.config.Get("buildToolsAdvDupe2", true) then
print("[Build Tools] AdvDupe2 активирован")
end
if ix.config.Get("buildToolsPermaprops", true) then
print("[Build Tools] Permaprops активирован")
end
end

View File

@@ -0,0 +1,135 @@
PLUGIN.captureData = PLUGIN.captureData or {}
net.Receive("ixCaptureSync", function()
local plugin = ix.plugin.Get("capture")
if (!plugin) then return end
local point = net.ReadEntity()
local progress = net.ReadFloat()
local owner = net.ReadUInt(8)
local capturing = net.ReadUInt(8)
local players = net.ReadUInt(8)
local blocked = net.ReadBool()
if (IsValid(point)) then
plugin.captureData[point] = {
progress = progress,
owner = owner,
capturing = capturing,
players = players,
blocked = blocked
}
end
end)
local PANEL = {}
function PANEL:Init()
self.progress = 0
self.targetProgress = 0
self.alpha = 0
self.targetAlpha = 0
self.blinkAlpha = 0
end
function PANEL:SetCaptureData(data)
self.targetProgress = data.progress or 0
self.owner = data.owner or 0
self.capturing = data.capturing or 0
self.players = data.players or 0
self.blocked = data.blocked or false
if (data.capturing and data.capturing > 0) then
self.targetAlpha = 255
else
self.targetAlpha = 0
end
end
function PANEL:Think()
self.progress = Lerp(FrameTime() * 5, self.progress, self.targetProgress)
self.alpha = Lerp(FrameTime() * 8, self.alpha, self.targetAlpha)
if (self.blocked) then
self.blinkAlpha = math.abs(math.sin(CurTime() * 5)) * 255
else
self.blinkAlpha = 0
end
local plugin = ix.plugin.Get("capture")
if (plugin) then
self:SetSize(plugin.hudWidth, plugin.hudHeight)
self:SetPos(ScrW() - plugin.hudWidth - plugin.hudPosX, plugin.hudPosY)
end
end
function PANEL:Paint(w, h)
if (self.alpha < 1) then return end
surface.SetAlphaMultiplier(self.alpha / 255)
local barW = 40
local barH = h
local padding = 10
local barX = (w - barW) / 2
draw.RoundedBox(8, barX, 0, barW, barH, ColorAlpha(Color(20, 20, 20), self.alpha * 0.9))
if (self.progress > 0) then
local fillH = (barH - 4) * (self.progress / 100)
local color = Color(100, 150, 255)
if (self.capturing and self.capturing > 0) then
if (self.capturing == FACTION_RUSSIAN) then
color = Color(200, 50, 50)
elseif (self.capturing == FACTION_UKRAINE) then
color = Color(50, 100, 200)
end
end
if (self.blocked) then
color = ColorAlpha(color, self.blinkAlpha)
end
draw.RoundedBox(6, barX + 2, 2, barW - 4, fillH, ColorAlpha(color, self.alpha))
end
local text = string.format("%.0f%%", self.progress)
if (self.players and self.players > 0) then
text = text .. "\n(" .. self.players .. ")"
end
draw.SimpleText(text, "DermaDefault", w / 2, barH + 15, ColorAlpha(color_white, self.alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
surface.SetAlphaMultiplier(1)
end
vgui.Register("ixCaptureUI", PANEL, "DPanel")
function PLUGIN:HUDPaint()
local ply = LocalPlayer()
if (!IsValid(ply)) then return end
for point, data in pairs(self.captureData) do
if (IsValid(point) and ply:GetPos():Distance(point:GetPos()) <= self.captureRadius) then
if (!IsValid(self.captureUI)) then
self.captureUI = vgui.Create("ixCaptureUI")
end
self.captureUI:SetCaptureData(data)
return
end
end
if (IsValid(self.captureUI)) then
self.captureUI:SetCaptureData({progress = 0, owner = 0, capturing = 0, players = 0, blocked = false})
end
end
function PLUGIN:OnReloaded()
if (IsValid(self.captureUI)) then
self.captureUI:Remove()
self.captureUI = nil
end
end

View File

@@ -0,0 +1,117 @@
ENT.Type = "anim"
ENT.PrintName = "Точка захвата"
ENT.Category = "[FT] Захват точек"
ENT.Spawnable = true
ENT.AdminOnly = true
ENT.RenderGroup = RENDERGROUP_BOTH
ENT.bNoPersist = true
function ENT:SetupDataTables()
self:NetworkVar("Float", 0, "CaptureProgress")
self:NetworkVar("Int", 0, "OwnerFaction")
self:NetworkVar("Int", 1, "FlagSkin")
end
if SERVER then
function ENT:Initialize()
self:SetModel("models/flag/ua_flag_rework.mdl")
self:PhysicsInit(SOLID_VPHYSICS)
self:SetMoveType(MOVETYPE_NONE)
self:SetSolid(SOLID_VPHYSICS)
self:SetUseType(SIMPLE_USE)
local phys = self:GetPhysicsObject()
if (IsValid(phys)) then
phys:EnableMotion(false)
end
-- Установка начального скина (никто не захватил)
self:SetFlagSkin(2)
self:SetSkin(2)
self:SetOwnerFaction(0)
local plugin = ix.plugin.Get("capture")
if (plugin) then
plugin:RegisterCapturePoint(self)
end
end
-- Функция для установки владельца с автоматической сменой скина
function ENT:SetOwnerFactionID(factionID)
self:SetOwnerFaction(factionID or 0)
local skinID = 2 -- По умолчанию никто
if factionID == FACTION_RUSSIAN then
skinID = 1 -- РФ - скин 1
elseif factionID == FACTION_UKRAINE then
skinID = 0 -- ВСУ - скин 0
end
self:SetFlagSkin(skinID)
self:SetSkin(skinID)
end
function ENT:Use(activator, caller)
end
else
function ENT:Draw()
self:DrawModel()
-- Синхронизация скина с NetworkVar
local skinID = self:GetFlagSkin()
if skinID and self:GetSkin() ~= skinID then
self:SetSkin(skinID)
end
--local plugin = ix.plugin.Get("capture")
--if (!plugin or !plugin.captureData[self]) then return end
--
--local data = plugin.captureData[self]
--local pos = self:GetPos() + Vector(0, 0, plugin.labelOffsetZ)
--local ang = EyeAngles()
--ang:RotateAroundAxis(ang:Forward(), 90)
--ang:RotateAroundAxis(ang:Right(), 90)
--cam.Start3D2D(pos, Angle(0, ang.y, 90), plugin.labelScale)
-- if (data.capturing and data.capturing > 0) then
-- local barW = 400
-- local barH = 60
-- local x, y = -barW / 2, -barH / 2
--
-- draw.RoundedBox(8, x, y - 80, barW, barH, Color(20, 20, 20, 230))
--
-- local fillW = (barW - 8) * (data.progress / 100)
-- local color = Color(100, 150, 255)
--
-- if (data.capturing == FACTION_RUSSIAN) then
-- color = Color(200, 50, 50)
-- elseif (data.capturing == FACTION_UKRAINE) then
-- color = Color(50, 100, 200)
-- end
--
-- draw.RoundedBox(6, x + 4, y - 76, fillW, barH - 8, color)
--
-- local text = string.format("%.0f%%", data.progress)
-- draw.SimpleText(text, "DermaLarge", 0, -80, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- end
--
-- local ownerText = "Никем не захвачена"
-- local ownerColor = Color(150, 150, 150)
--
-- if (data.owner and data.owner > 0) then
-- local faction = ix.faction.Get(data.owner)
-- if (faction) then
-- ownerText = faction.name
-- if (data.owner == FACTION_RUSSIAN) then
-- ownerColor = Color(200, 50, 50)
-- elseif (data.owner == FACTION_UKRAINE) then
-- ownerColor = Color(50, 100, 200)
-- end
-- end
-- end
--
-- draw.SimpleText(ownerText, "DermaLarge", 0, 0, ownerColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
--cam.End3D2D()
end
end

View File

@@ -0,0 +1,44 @@
PLUGIN.name = "Capture Points"
PLUGIN.author = "RefoselTeamWork"
PLUGIN.description = "Система захвата точек"
PLUGIN.captureRadius = 1024
PLUGIN.captureBaseTime = 35
PLUGIN.moneyReward = 3000
PLUGIN.arsenalReward = 500
PLUGIN.vehicleReward = 500
-- Настройки периодических наград
PLUGIN.tickInterval = 60 -- Интервал начисления в секундах
PLUGIN.tickSupplyPerPoint = 200 -- Очки снабжения за одну точку
PLUGIN.tickVehiclePerPoint = 150 -- Очки техники за одну точку
PLUGIN.hudPosX = 0
PLUGIN.hudPosY = 20
PLUGIN.hudWidth = 200
PLUGIN.hudHeight = 300
PLUGIN.labelOffsetZ = 80
PLUGIN.labelScale = 0.1
PLUGIN.factionMinOnline = {
[FACTION_RUSSIAN] = 1,
[FACTION_UKRAINE] = 0
}
PLUGIN.defaultPoints = {
{pos = Vector(-6788.718262, -2758.777832, 620.980103), ang = Angle(0, 90, 0)},
{pos = Vector(-386.355896, -13121.211914, 139.868973), ang = Angle(0, 90, 0)},
{pos = Vector(4969.474121, -10055.811523, 114.868973), ang = Angle(0, 90, 0)},
{pos = Vector(15625.968750, -13238.951172, 282.868958), ang = Angle(0, 90, 0)},
{pos = Vector(12809.052734, -4790.340820, 115.868973), ang = Angle(0, 90, 0)},
{pos = Vector(13539.985352, 4858.571289, 121.868973), ang = Angle(0, 90, 0)},
{pos = Vector(1916.585205, 2236.532715, 226.868958), ang = Angle(0, 90, 0)},
{pos = Vector(5527.783203, 2510.990479, 736.868958), ang = Angle(0, 90, 0)},
}
PLUGIN.minCapturePlayers = 1
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")

View File

@@ -0,0 +1,414 @@
util.AddNetworkString("ixCaptureSync")
PLUGIN.capturePoints = PLUGIN.capturePoints or {}
function PLUGIN:GetPlayersInRadius(point, radius)
local players = {}
local pos = point:GetPos()
for _, ply in ipairs(player.GetAll()) do
if ply:Alive() and pos:Distance(ply:GetPos()) <= radius and not (ply.IsAdminMode and ply:IsAdminMode()) then
table.insert(players, ply)
end
end
return players
end
function PLUGIN:GetFactionFromPlayers(players)
local factions = {}
for _, ply in ipairs(players) do
local char = ply:GetCharacter()
if (char) then
local faction = char:GetFaction()
factions[faction] = (factions[faction] or 0) + 1
end
end
return factions
end
function PLUGIN:GetFactionOnlineCount(faction)
local count = 0
for _, ply in ipairs(player.GetAll()) do
local char = ply:GetCharacter()
if char and char:GetFaction() == faction and not (ply.IsAdminMode and ply:IsAdminMode()) then
count = count + 1
end
end
return count
end
function PLUGIN:CanFactionCapture(faction)
local minOnline = self.factionMinOnline[faction]
if (!minOnline) then return true end
local online = self:GetFactionOnlineCount(faction)
return online >= minOnline
end
function PLUGIN:CanCapture(factions)
local count = 0
for _ in pairs(factions) do
count = count + 1
end
return count == 1
end
function PLUGIN:GiveRewards(faction, players)
for _, ply in ipairs(players) do
local char = ply:GetCharacter()
if (char) then
char:GiveMoney(self.moneyReward)
ply:Notify("Вы получили " .. self.moneyReward .. " за захват точки!")
end
end
if (ix.plugin.list["arsenal"]) then
ix.plugin.list["arsenal"]:AddFactionSupply(faction, self.arsenalReward)
end
if (ix.plugin.list["vehicles"]) then
ix.plugin.list["vehicles"]:AddFactionPoints(faction, self.vehicleReward)
end
-- Логируем выдачу наград
local serverlogsPlugin = ix.plugin.list["serverlogs"]
if (serverlogsPlugin) then
local factionName = ix.faction.Get(faction).name or tostring(faction)
local message = string.format("Фракция '%s' получила награду за захват: %d снабжения, %d очков техники, %d денег на игрока",
factionName, self.arsenalReward, self.vehicleReward, self.moneyReward)
serverlogsPlugin:AddLog("CAPTURE_REWARD", message, nil, {
faction = faction,
factionName = factionName,
arsenalReward = self.arsenalReward,
vehicleReward = self.vehicleReward,
moneyReward = self.moneyReward,
playerCount = #players
})
end
end
function PLUGIN:ProcessCapture(point)
local players = self:GetPlayersInRadius(point, self.captureRadius)
if (#players == 0) then
if point.capturingFaction != nil then
point.capturingFaction = nil
point.capturingPlayers = 0
self:SyncCapturePoint(point)
end
return
end
local factions = self:GetFactionFromPlayers(players)
if (!self:CanCapture(factions)) then
if point.capturingFaction != nil or !point.captureBlocked then
point.capturingFaction = nil
point.capturingPlayers = 0
point.captureBlocked = true
self:SyncCapturePoint(point)
end
return
end
point.captureBlocked = false
local faction, playerCount = next(factions)
if (playerCount < self.minCapturePlayers) then
if point.capturingFaction != nil or !point.captureBlocked then
point.capturingFaction = nil
point.capturingPlayers = 0
point.captureBlocked = true
self:SyncCapturePoint(point)
end
return
end
if (!self:CanFactionCapture(faction)) then
if point.capturingFaction != nil or !point.captureBlocked then
point.capturingFaction = nil
point.capturingPlayers = 0
point.captureBlocked = true
self:SyncCapturePoint(point)
end
return
end
if (point.ownerFaction == faction) then
if point.captureProgress != 100 or point.capturingFaction != nil then
point.captureProgress = 100
point.capturingFaction = nil
point.capturingPlayers = 0
self:SyncCapturePoint(point)
end
return
end
-- Логируем начало захвата при первом касании
if (!point.capturingFaction or point.capturingFaction != faction) then
-- Сбрасываем прогресс если начинается захват другой фракцией
point.captureProgress = 0
-- Уведомляем владельцев о захвате
if (point.ownerFaction and point.ownerFaction != 0 and point.ownerFaction != faction) then
local capturerName = ix.faction.Get(faction).name or "Противник"
for _, v in ipairs(player.GetAll()) do
local char = v:GetCharacter()
if (char and char:GetFaction() == point.ownerFaction) then
v:Notify("ВНИМАНИЕ! Ваша точка захватывается фракцией " .. capturerName .. "!")
end
end
end
local serverlogsPlugin = ix.plugin.list["serverlogs"]
if (serverlogsPlugin) then
local factionName = ix.faction.Get(faction).name or tostring(faction)
local pointPos = tostring(point:GetPos())
local message = string.format("Фракция '%s' начала захват точки (позиция: %s)", factionName, pointPos)
serverlogsPlugin:AddLog("CAPTURE_START", message, nil, {
faction = faction,
factionName = factionName,
playerCount = playerCount,
pointPosition = pointPos
})
end
end
point.capturingFaction = faction
point.capturingPlayers = playerCount
local baseSpeed = 100 / self.captureBaseTime -- Progress per second
local captureSpeed = baseSpeed * playerCount * FrameTime()
point.captureProgress = (point.captureProgress or 0) + captureSpeed
if (point.captureProgress >= 100) then
point.captureProgress = 100
local oldOwner = point.ownerFaction
point.ownerFaction = faction
point:SetOwnerFactionID(faction)
point.capturingFaction = nil
point.capturingPlayers = 0
self:GiveRewards(faction, players)
self:SaveData()
-- Логируем успешный захват точки
local serverlogsPlugin = ix.plugin.list["serverlogs"]
if (serverlogsPlugin) then
local factionName = ix.faction.Get(faction).name or tostring(faction)
local pointPos = tostring(point:GetPos())
local oldFactionData = oldOwner and ix.faction.Get(oldOwner)
local oldFactionName = (oldFactionData and oldFactionData.name) or (oldOwner and tostring(oldOwner)) or "Нейтральная"
local message = string.format("Точка захвачена фракцией '%s' (предыдущий владелец: %s)", factionName, oldFactionName)
serverlogsPlugin:AddLog("CAPTURE_COMPLETE", message, nil, {
faction = faction,
factionName = factionName,
oldOwner = oldOwner,
oldOwnerName = oldFactionName,
playerCount = playerCount,
pointPosition = pointPos
})
end
for _, ply in ipairs(player.GetAll()) do
local char = ply:GetCharacter()
if (char and char:GetFaction() == faction) then
ply:Notify("Точка захвачена вашей фракцией!")
end
end
end
-- Only sync if progress changed significantly, something started/stopped, or a timer passed
local curTime = CurTime()
point.nextSync = point.nextSync or 0
if (point.lastSyncProgress == nil or
math.abs(point.captureProgress - point.lastSyncProgress) >= 1 or
curTime >= point.nextSync) then
self:SyncCapturePoint(point)
point.lastSyncProgress = point.captureProgress
point.nextSync = curTime + 0.2 -- Sync at most 5 times per second
end
end
function PLUGIN:SyncCapturePoint(point)
net.Start("ixCaptureSync")
net.WriteEntity(point)
net.WriteFloat(point.captureProgress or 0)
net.WriteUInt(point.ownerFaction or 0, 8)
net.WriteUInt(point.capturingFaction or 0, 8)
net.WriteUInt(point.capturingPlayers or 0, 8)
net.WriteBool(point.captureBlocked or false)
net.Broadcast()
end
function PLUGIN:Think()
if (!self.nextTickReward) then
self.nextTickReward = CurTime() + (self.tickInterval or 300)
elseif (CurTime() >= self.nextTickReward) then
self:ProcessPeriodicRewards()
self.nextTickReward = CurTime() + (self.tickInterval or 300)
end
for _, point in ipairs(self.capturePoints) do
if (IsValid(point)) then
self:ProcessCapture(point)
end
end
if (!self.nextAutoSave or CurTime() >= self.nextAutoSave) then
self:SaveData()
self.nextAutoSave = CurTime() + 60
end
end
function PLUGIN:RegisterCapturePoint(point)
table.insert(self.capturePoints, point)
point.captureProgress = 0
point.ownerFaction = nil
point:SetOwnerFactionID(0)
point.capturingFaction = nil
point.capturingPlayers = 0
end
function PLUGIN:EntityRemoved(entity)
if (entity:GetClass() == "ix_capture_point") then
for i, point in ipairs(self.capturePoints) do
if (point == entity) then
table.remove(self.capturePoints, i)
break
end
end
end
end
function PLUGIN:SaveData()
local data = {}
for _, point in ipairs(ents.FindByClass("ix_capture_point")) do
if IsValid(point) then
data[#data + 1] = {
pos = point:GetPos(),
angles = point:GetAngles(),
ownerFaction = point.ownerFaction or 0,
captureProgress = point.captureProgress or 0
}
end
end
self:SetData(data)
end
function PLUGIN:LoadData()
local data = self:GetData()
if (!data or #data == 0) then
for _, v in ipairs(self.defaultPoints or {}) do
local point = ents.Create("ix_capture_point")
if IsValid(point) then
point:SetPos(v.pos)
point:SetAngles(v.ang)
point:Spawn()
timer.Simple(0.1, function()
if IsValid(point) then
point.ownerFaction = 0
point:SetOwnerFactionID(0)
point.captureProgress = 0
end
end)
end
end
return
end
for _, v in ipairs(data) do
local point = ents.Create("ix_capture_point")
if IsValid(point) then
point:SetPos(v.pos)
point:SetAngles(v.angles)
point:Spawn()
timer.Simple(0.1, function()
if IsValid(point) then
point.ownerFaction = v.ownerFaction
point:SetOwnerFactionID(v.ownerFaction or 0)
point.captureProgress = v.captureProgress or 0
self:SyncCapturePoint(point)
end
end)
end
end
end
function PLUGIN:OnUnloaded()
self:SaveData()
end
-- Подсчет захваченных точек каждой фракцией
function PLUGIN:GetCapturedPointsCount()
local counts = {}
for _, point in ipairs(self.capturePoints) do
if IsValid(point) and point.ownerFaction and point.ownerFaction ~= 0 then
counts[point.ownerFaction] = (counts[point.ownerFaction] or 0) + 1
end
end
return counts
end
-- Периодическое начисление очков
function PLUGIN:ProcessPeriodicRewards()
local counts = self:GetCapturedPointsCount()
for faction, pointCount in pairs(counts) do
if pointCount > 0 then
local supplyReward = pointCount * self.tickSupplyPerPoint
local vehicleReward = pointCount * self.tickVehiclePerPoint
if ix.plugin.list["arsenal"] then
ix.plugin.list["arsenal"]:AddFactionSupply(faction, supplyReward)
end
if ix.plugin.list["vehicles"] then
ix.plugin.list["vehicles"]:AddFactionPoints(faction, vehicleReward)
end
local factionName = ix.faction.Get(faction).name or "Фракция"
for _, ply in ipairs(player.GetAll()) do
local char = ply:GetCharacter()
if char and char:GetFaction() == faction then
ply:Notify(string.format("Ваша фракция получила: +%d снабжения, +%d очков техники (точек: %d)",
supplyReward, vehicleReward, pointCount))
end
end
end
end
end
ix.command.Add("CaptureSystemSave", {
description = "Сохранить точки захвата",
superAdminOnly = true,
OnRun = function(self, client)
local plugin = ix.plugin.Get("capture")
if (!plugin) then
return "@commandNoExist"
end
plugin:SaveData()
local count = #ents.FindByClass("ix_capture_point")
client:Notify("Точки захвата сохранены (" .. count .. " шт.)")
end
})

View File

@@ -0,0 +1,53 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Carrying Entities"
PLUGIN.author = "Scripty"
PLUGIN.description = "Позволяет игрокам поднимать любые энтити на клавишу E через стандартную механику."
-- Список классов, которые ЗАПРЕЩЕНО поднимать
local BLACKLIST = {
["player"] = true,
["worldspawn"] = true,
["func_door"] = true,
["func_door_rotating"] = true,
["prop_door_rotating"] = true,
["func_movelinear"] = true,
["prop_dynamic"] = true,
["ix_vendor"] = true,
["gmod_hands"] = true,
["viewmodel"] = true
}
if SERVER then
-- Этот хук вызывается ядром Helix во время GM:AllowPlayerPickup
function PLUGIN:CanPlayerPickupEntity(client, entity)
if (!IsValid(entity) or entity:IsPlayer() or entity:IsWorld()) then
return false
end
local class = entity:GetClass()
if (BLACKLIST[class]) then
return false
end
-- Проверка защиты Helix (Prop Protection)
local char = client:GetCharacter()
local owner = entity:GetNetVar("owner", 0)
-- Разрешаем владельцу, админу или если объект ничей
if (owner == 0 or (char and owner == char:GetID()) or client:IsAdmin()) then
local phys = entity:GetPhysicsObject()
-- Разрешаем подбирать только то, что имеет физику
if (IsValid(phys) and phys:IsMoveable()) then
return true
end
-- Прямое разрешение для пакетов крови (если физика считается не-moveable)
if (class == "bloodbag_medicmod") then
return true
end
end
return false
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
local PLUGIN = PLUGIN
local PANEL = {}
function PANEL:Init()
ix.gui.chatTabCustomize = self
self:SetTitle(L("chatNewTab"))
self:SetSizable(true)
self:SetSize(ScrW() * 0.5, ScrH() * 0.5)
self.settings = self:Add("ixSettings")
self.settings:Dock(FILL)
self.settings:SetSearchEnabled(true)
self.settings:AddCategory(L("chatAllowedClasses"))
-- controls
local controlsPanel = self:Add("Panel")
controlsPanel:Dock(BOTTOM)
controlsPanel:DockMargin(0, 4, 0, 0)
controlsPanel:SetTall(32)
self.create = controlsPanel:Add("DButton")
self.create:SetText(L("create"))
self.create:SizeToContents()
self.create:Dock(FILL)
self.create:DockMargin(0, 0, 4, 0)
self.create.DoClick = ix.util.Bind(self, self.CreateClicked)
local uncheckAll = controlsPanel:Add("DButton")
uncheckAll:SetText(L("uncheckAll"))
uncheckAll:SizeToContents()
uncheckAll:Dock(RIGHT)
uncheckAll.DoClick = function()
self:SetAllValues(false)
end
local checkAll = controlsPanel:Add("DButton")
checkAll:SetText(L("checkAll"))
checkAll:SizeToContents()
checkAll:Dock(RIGHT)
checkAll:DockMargin(0, 0, 4, 0)
checkAll.DoClick = function()
self:SetAllValues(true)
end
-- chat class settings
self.name = self.settings:AddRow(ix.type.string)
self.name:SetText(L("chatTabName"))
self.name:SetValue(L("chatNewTabTitle"))
self.name:SetZPos(-1)
for k, _ in SortedPairs(ix.chat.classes) do
local panel = self.settings:AddRow(ix.type.bool, L("chatAllowedClasses"))
panel:SetText(k)
panel:SetValue(true, true)
end
self.settings:SizeToContents()
self:Center()
self:MakePopup()
end
function PANEL:PopulateFromTab(name, filter)
self.tab = name
self:SetTitle(L("chatCustomize"))
self.create:SetText(L("update"))
self.name:SetValue(name)
for _, v in ipairs(self.settings:GetRows()) do
if (filter[v:GetText()]) then
v:SetValue(false, true)
end
end
end
function PANEL:SetAllValues(bValue)
for _, v in ipairs(self.settings:GetRows()) do
if (v == self.name) then
continue
end
v:SetValue(tobool(bValue), true)
end
end
function PANEL:CreateClicked()
local name = self.tab and self.tab or self.name:GetValue()
if (self.tab != self.name:GetValue() and PLUGIN:TabExists(self.name:GetValue())) then
ix.util.Notify(L("chatTabExists"))
return
end
local filter = {}
for _, v in ipairs(self.settings:GetRows()) do
-- we only want to add entries for classes we don't want shown
if (!v:GetValue()) then
filter[v:GetText()] = true
end
end
if (self.tab) then
self:OnTabUpdated(name, filter, self.name:GetValue())
else
self:OnTabCreated(name, filter)
end
self:Remove()
end
function PANEL:OnTabCreated(id, filter)
end
function PANEL:OnTabUpdated(id, filter, newID)
end
vgui.Register("ixChatboxTabCustomize", PANEL, "DFrame")

View File

@@ -0,0 +1,205 @@
-- Переопределение стилей чатбокса под общий дизайн Military RP
local PLUGIN = PLUGIN
local gradient = surface.GetTextureID("vgui/gradient-u")
local gradientUp = surface.GetTextureID("vgui/gradient-d")
-- Цвета в стиле HUD
local Colors = {
green = Color(84, 147, 90),
dark_green = Color(52, 91, 60),
darker_green = Color(35, 53, 29),
gray = Color(193, 193, 193),
gray_dark = Color(94, 94, 94),
black = Color(0, 0, 0, 220),
black_light = Color(0, 0, 0, 150),
white = Color(255, 255, 255)
}
-- Переопределяем стандартные функции рисования чатбокса
local SKIN = derma.GetDefaultSkin()
-- Фон чатбокса
function SKIN:PaintChatboxBackground(panel, width, height)
-- Blur эффект
ix.util.DrawBlur(panel, 8)
-- Основной фон с градиентом
surface.SetDrawColor(Colors.black)
surface.DrawRect(0, 0, width, height)
if (panel:GetActive()) then
-- Зелёный градиент сверху когда чат активен
surface.SetDrawColor(Colors.dark_green.r, Colors.dark_green.g, Colors.dark_green.b, 120)
surface.SetTexture(gradientUp)
surface.DrawTexturedRect(0, panel.tabs.buttons:GetTall(), width, height * 0.25)
end
-- Обводка в стиле HUD
surface.SetDrawColor(Colors.darker_green)
surface.DrawOutlinedRect(0, 0, width, height)
-- Тонкая внутренняя обводка
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 60)
surface.DrawOutlinedRect(1, 1, width - 2, height - 2)
end
-- Кнопки табов
function SKIN:PaintChatboxTabButton(panel, width, height)
if (panel:GetActive()) then
-- Активная вкладка - зелёный градиент
surface.SetDrawColor(Colors.dark_green)
surface.DrawRect(0, 0, width, height)
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 100)
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height)
else
-- Неактивная вкладка
surface.SetDrawColor(Colors.black_light)
surface.DrawRect(0, 0, width, height)
-- Индикатор непрочитанных сообщений
if (panel:GetUnread()) then
surface.SetDrawColor(ColorAlpha(Color(200, 200, 50), Lerp(panel.unreadAlpha, 0, 120)))
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height - 1)
end
end
-- Разделитель вкладок
surface.SetDrawColor(Colors.darker_green)
surface.DrawRect(width - 1, 0, 1, height)
end
-- Панель табов
function SKIN:PaintChatboxTabs(panel, width, height, alpha)
-- Фон панели табов
surface.SetDrawColor(Colors.black_light)
surface.DrawRect(0, 0, width, height)
-- Градиент вниз
surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 150)
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()
-- Линия под активной вкладкой (зелёная)
surface.SetDrawColor(Colors.green)
surface.DrawRect(x, height - 2, button:GetWide(), 2)
-- Обводка панели табов
surface.SetDrawColor(Colors.darker_green)
surface.DrawRect(0, height - 1, x, 1) -- слева
surface.DrawRect(x + button:GetWide(), height - 1, width - x - button:GetWide(), 1) -- справа
end
end
-- Поле ввода текста
function SKIN:PaintChatboxEntry(panel, width, height)
-- Фон поля ввода
surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 100)
surface.DrawRect(0, 0, width, height)
-- Текст
panel:DrawTextEntryText(Colors.white, Colors.green, Colors.white)
-- Обводка
surface.SetDrawColor(Colors.dark_green)
surface.DrawOutlinedRect(0, 0, width, height)
-- Внутренний highlight когда активно
if panel:HasFocus() then
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, 80)
surface.DrawOutlinedRect(1, 1, width - 2, height - 2)
end
end
-- Превью команды
function SKIN:DrawChatboxPreviewBox(x, y, text, color)
color = color or Colors.green
local textWidth, textHeight = surface.GetTextSize(text)
local width, height = textWidth + 8, textHeight + 8
-- Фон
surface.SetDrawColor(Colors.dark_green)
surface.DrawRect(x, y, width, height)
-- Градиент
surface.SetDrawColor(color.r, color.g, color.b, 60)
surface.SetTexture(gradient)
surface.DrawTexturedRect(x, y, width, height)
-- Текст
surface.SetTextColor(Colors.white)
surface.SetTextPos(x + width * 0.5 - textWidth * 0.5, y + height * 0.5 - textHeight * 0.5)
surface.DrawText(text)
-- Обводка
surface.SetDrawColor(Colors.darker_green)
surface.DrawOutlinedRect(x, y, width, height)
return width
end
-- Префикс команды
function SKIN:DrawChatboxPrefixBox(panel, width, height)
local color = panel:GetBackgroundColor() or Colors.green
-- Фон
surface.SetDrawColor(Colors.dark_green)
surface.DrawRect(0, 0, width, height)
-- Градиент
surface.SetDrawColor(color.r, color.g, color.b, 80)
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height)
-- Обводка
surface.SetDrawColor(Colors.darker_green)
surface.DrawOutlinedRect(0, 0, width, height)
end
-- Автодополнение команд
function SKIN:PaintChatboxAutocompleteEntry(panel, width, height)
-- Выделенный фон
if (panel.highlightAlpha > 0) then
surface.SetDrawColor(Colors.dark_green.r, Colors.dark_green.g, Colors.dark_green.b, panel.highlightAlpha * 150)
surface.DrawRect(0, 0, width, height)
-- Градиент выделения
surface.SetDrawColor(Colors.green.r, Colors.green.g, Colors.green.b, panel.highlightAlpha * 60)
surface.SetTexture(gradient)
surface.DrawTexturedRect(0, 0, width, height)
end
-- Разделитель между командами
surface.SetDrawColor(Colors.darker_green.r, Colors.darker_green.g, Colors.darker_green.b, 100)
surface.DrawRect(0, height - 1, width, 1)
end
hook.Add("LoadFonts", "MilitaryRPChatFonts", function()
-- Переопределяем шрифты чата на Montserrat
local scale = ix.option.Get("chatFontScale", 1)
surface.CreateFont("ixChatFont", {
font = "Montserrat",
size = 17 * scale,
weight = 400,
extended = true
})
surface.CreateFont("ixChatFontItalics", {
font = "Montserrat",
size = 17 * scale,
weight = 400,
italic = true,
extended = true
})
end)

View File

@@ -0,0 +1,25 @@
NAME = "English"
LANGUAGE = {
-- Category
optChat = "Chat",
-- Settings
optChatNotices = "Chat Notices",
optChatNoticesDesc = "Show system notifications in chat",
optChatTimestamps = "Timestamps",
optChatTimestampsDesc = "Display message send time",
optChatFontScale = "Font Scale",
optChatFontScaleDesc = "Chat font scale (0.1-2)",
optChatOutline = "Text Outline",
optChatOutlineDesc = "Add outline to chat text for better readability",
optChatTabs = "Chat Tabs",
optChatTabsDesc = "Chat tabs configuration (JSON)",
optChatPosition = "Chat Position",
optChatPositionDesc = "Chat window position and size (JSON)",
}

View File

@@ -0,0 +1,25 @@
NAME = "Русский"
LANGUAGE = {
-- Category
optChat = "Чат",
-- Settings
optChatNotices = "Уведомления в чате",
optChatNoticesDesc = "Показывать системные уведомления в чате",
optChatTimestamps = "Временные метки",
optChatTimestampsDesc = "Отображать время отправки сообщений",
optChatFontScale = "Размер шрифта",
optChatFontScaleDesc = "Масштаб шрифта в чате (0.1-2)",
optChatOutline = "Обводка текста",
optChatOutlineDesc = "Добавить обводку к тексту чата для лучшей читаемости",
optChatTabs = "Вкладки чата",
optChatTabsDesc = "Конфигурация вкладок чата (JSON)",
optChatPosition = "Позиция чата",
optChatPositionDesc = "Позиция и размер окна чата (JSON)",
}

View File

@@ -0,0 +1,188 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Chatbox"
PLUGIN.author = "`impulse"
PLUGIN.description = "Replaces the chatbox to enable customization, autocomplete, and useful info."
if (CLIENT) then
ix.util.Include("derma/cl_chatskin.lua")
ix.chat.history = ix.chat.history or {} -- array of strings the player has entered into the chatbox
ix.chat.currentCommand = ""
ix.chat.currentArguments = {}
ix.option.Add("chatNotices", ix.type.bool, false, {
category = "optChat",
name = "optChatNotices",
description = "optChatNoticesDesc"
})
ix.option.Add("chatTimestamps", ix.type.bool, false, {
category = "optChat",
name = "optChatTimestamps",
description = "optChatTimestampsDesc"
})
ix.option.Add("chatFontScale", ix.type.number, 1, {
category = "optChat",
name = "optChatFontScale",
description = "optChatFontScaleDesc",
min = 0.1, max = 2, decimals = 2,
OnChanged = function()
hook.Run("LoadFonts", ix.config.Get("font"), ix.config.Get("genericFont"))
PLUGIN:CreateChat()
end
})
ix.option.Add("chatOutline", ix.type.bool, false, {
category = "optChat",
name = "optChatOutline",
description = "optChatOutlineDesc"
})
-- tabs and their respective filters
ix.option.Add("chatTabs", ix.type.string, "", {
category = "optChat",
name = "optChatTabs",
description = "optChatTabsDesc",
hidden = function()
return true
end
})
-- chatbox size and position
ix.option.Add("chatPosition", ix.type.string, "", {
category = "optChat",
name = "optChatPosition",
description = "optChatPositionDesc",
hidden = function()
return true
end
})
function PLUGIN:CreateChat()
if (IsValid(self.panel)) then
self.panel:Remove()
end
self.panel = vgui.Create("ixChatbox")
self.panel:SetupTabs(util.JSONToTable(ix.option.Get("chatTabs", "")))
self.panel:SetupPosition(util.JSONToTable(ix.option.Get("chatPosition", "")))
hook.Run("ChatboxCreated")
end
function PLUGIN:TabExists(id)
if (!IsValid(self.panel)) then
return false
end
return self.panel.tabs:GetTabs()[id] != nil
end
function PLUGIN:SaveTabs()
local tabs = {}
for id, panel in pairs(self.panel.tabs:GetTabs()) do
tabs[id] = panel:GetFilter()
end
ix.option.Set("chatTabs", util.TableToJSON(tabs))
end
function PLUGIN:SavePosition()
local x, y = self.panel:GetPos()
local width, height = self.panel:GetSize()
ix.option.Set("chatPosition", util.TableToJSON({x, y, width, height}))
end
function PLUGIN:InitPostEntity()
self:CreateChat()
end
function PLUGIN:PlayerBindPress(client, bind, pressed)
bind = bind:lower()
if (bind:find("messagemode") and pressed) then
-- Запрещаем открывать чат если игрок мёртв
if !LocalPlayer():Alive() then
return true
end
self.panel:SetActive(true)
return true
end
end
function PLUGIN:OnPauseMenuShow()
if (!IsValid(ix.gui.chat) or !ix.gui.chat:GetActive()) then
return
end
ix.gui.chat:SetActive(false)
return false
end
function PLUGIN:HUDShouldDraw(element)
if (element == "CHudChat") then
return false
end
end
function PLUGIN:ScreenResolutionChanged(oldWidth, oldHeight)
self:CreateChat()
end
function PLUGIN:ChatText(index, name, text, messageType)
if (messageType == "none" and IsValid(self.panel)) then
self.panel:AddMessage(text)
end
end
-- luacheck: globals chat
chat.ixAddText = chat.ixAddText or chat.AddText
function chat.AddText(...)
if (IsValid(PLUGIN.panel)) then
PLUGIN.panel:AddMessage(...)
end
-- log chat message to console
local text = {}
for _, v in ipairs({...}) do
if (istable(v) or isstring(v)) then
text[#text + 1] = v
elseif (isentity(v) and v:IsPlayer()) then
text[#text + 1] = team.GetColor(v:Team())
text[#text + 1] = v:Name()
elseif (type(v) != "IMaterial") then
text[#text + 1] = tostring(v)
end
end
text[#text + 1] = "\n"
MsgC(unpack(text))
end
else
util.AddNetworkString("ixChatMessage")
net.Receive("ixChatMessage", function(length, client)
local text = net.ReadString()
if ((client.ixNextChat or 0) < CurTime() and isstring(text) and text:find("%S")) then
local maxLength = ix.config.Get("chatMax")
if (text:utf8len() > maxLength) then
text = text:utf8sub(0, maxLength)
end
hook.Run("PlayerSay", client, text)
client.ixNextChat = CurTime() + 0.5
end
end)
end

View File

@@ -0,0 +1,158 @@
PLUGIN.name = "Chat Control"
PLUGIN.author = "RefoselDev"
PLUGIN.description = "Удаление ненужных типов чатов"
hook.Add("InitializedChatClasses", "ixChatControl.RemoveChats", function()
ix.chat.classes.it = nil
ix.chat.classes.connect = nil
ix.chat.classes.disconnect = nil
ix.chat.classes.event = nil
ix.chat.Register("rp", {
OnChatAdd = function(self, speaker, text)
local name = IsValid(speaker) and speaker:Name() or "Console"
chat.AddText(Color(26, 221, 0), "[RP] ", name, ": ", text)
end,
prefix = {"/rp", "/RP"},
description = "Чат РП отыгровок",
color = Color(150, 200, 255),
CanHear = function(self, speaker, listener)
return listener:GetCharacter() != nil
end,
deadCanChat = false
})
-- Чат между фракциями
ix.chat.Register("adgl", {
OnChatAdd = function(self, speaker, text)
local name = IsValid(speaker) and speaker:Name() or "Console"
local prefix = "[ФРАКЦИЯ] "
local prefixColor = Color(255, 200, 100)
if (IsValid(speaker) and speaker:GetCharacter()) then
local faction = speaker:GetCharacter():GetFaction()
if (faction == FACTION_RUSSIAN) then
prefix = "[ВС РФ → ВСУ] "
prefixColor = Color(255, 196, 0)
elseif (faction == FACTION_UKRAINE) then
prefix = "[ВСУВС РФ] "
prefixColor = Color(255, 196, 0)
end
end
chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text)
end,
prefix = {"/adgl"},
description = "Чат между фракциями",
color = Color(255, 200, 100),
CanHear = function(self, speaker, listener)
-- Слышат только игроки с персонажем и определенной фракцией
if (!listener:GetCharacter()) then
return false
end
local listenerFaction = listener:GetCharacter():GetFaction()
return listenerFaction == FACTION_RUSSIAN or listenerFaction == FACTION_UKRAINE
end,
deadCanChat = false
})
-- Объявления стороны
ix.chat.Register("ad", {
OnChatAdd = function(self, speaker, text)
local name = IsValid(speaker) and speaker:Name() or "Console"
local prefix = "[ОБЪЯВЛЕНИЕ] "
local prefixColor = Color(200, 200, 200)
if (IsValid(speaker) and speaker:GetCharacter()) then
local faction = speaker:GetCharacter():GetFaction()
if (faction == FACTION_RUSSIAN) then
prefix = "[ВС РФ] "
prefixColor = Color(255, 100, 100)
elseif (faction == FACTION_UKRAINE) then
prefix = "[ВСУ] "
prefixColor = Color(255, 196, 0)
end
end
chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text)
end,
prefix = {"/ad"},
description = "Объявления стороны",
color = Color(200, 200, 200),
CanHear = function(self, speaker, listener)
-- Слышат только игроки той же фракции
if (!listener:GetCharacter() or !speaker:GetCharacter()) then
return false
end
local speakerFaction = speaker:GetCharacter():GetFaction()
local listenerFaction = listener:GetCharacter():GetFaction()
return speakerFaction == listenerFaction
end,
deadCanChat = false
})
-- Чат подразделения
ix.chat.Register("podr", {
OnChatAdd = function(self, speaker, text)
local name = IsValid(speaker) and speaker:Name() or "Console"
local prefix = "[ПОДРАЗДЕЛЕНИЕ] "
local prefixColor = Color(100, 150, 255)
if (IsValid(speaker) and speaker:GetCharacter()) then
local character = speaker:GetCharacter()
local faction = character:GetFaction()
local podrID = (character.GetPodr and character:GetPodr()) or 1
local factionTable = ix.faction.indices[faction]
if (factionTable and factionTable.Podr and factionTable.Podr[podrID]) then
local podrName = factionTable.Podr[podrID].name or "Неизвестно"
prefix = "["..podrName.."] "
if (faction == FACTION_RUSSIAN) then
prefixColor = Color(255, 196, 0)
elseif (faction == FACTION_UKRAINE) then
prefixColor = Color(255, 196, 0)
end
end
end
chat.AddText(prefixColor, prefix, Color(255, 255, 255), name, ": ", text)
end,
prefix = {"/podr", "/p"},
description = "Чат подразделения",
color = Color(100, 150, 255),
CanHear = function(self, speaker, listener)
-- Слышат только игроки той же фракции и того же подразделения
if (!listener:GetCharacter() or !speaker:GetCharacter()) then
return false
end
local speakerChar = speaker:GetCharacter()
local listenerChar = listener:GetCharacter()
local speakerFaction = speakerChar:GetFaction()
local listenerFaction = listenerChar:GetFaction()
if (speakerFaction != listenerFaction) then
return false
end
local speakerPodr = (speakerChar.GetPodr and speakerChar:GetPodr()) or 1
local listenerPodr = (listenerChar.GetPodr and listenerChar:GetPodr()) or 1
return speakerPodr == listenerPodr
end,
deadCanChat = false
})
if (SERVER) then
print("[Chat Control] Удалены чаты: it, connect, disconnect, event")
print("[Chat Control] Добавлены чаты: /rp, /adgl, /ad, /podr")
end
end)

View File

@@ -0,0 +1,209 @@
-- Создание шрифтов
surface.CreateFont("ixDeathScreenMain", {
font = "OperiusRegular",
size = 180,
weight = 300,
extended = true
})
surface.CreateFont("ixDeathScreenSub", {
font = "OperiusRegular",
size = 90,
weight = 300,
extended = true
})
-- Резервный шрифт если OperiusRegular недоступен
surface.CreateFont("ixDeathScreenMainFallback", {
font = "Roboto",
size = 180,
weight = 700,
extended = true
})
surface.CreateFont("ixDeathScreenSubFallback", {
font = "Roboto",
size = 90,
weight = 500,
extended = true
})
surface.CreateFont("ixDeathScreenSmall", {
font = "Roboto",
size = 40,
weight = 400,
extended = true
})
-- Проверка доступности шрифта
local function GetFont(primary, fallback)
local w, h = surface.GetTextSize("TEST")
if w > 0 then
return primary
end
return fallback
end
local deathScreenPanel = nil
-- Получение экрана смерти от сервера
net.Receive("ixDeathScreen", function()
if not ix.config.Get("deathScreenEnabled") then
return
end
-- Закрываем старую панель если существует
if IsValid(deathScreenPanel) then
deathScreenPanel:Remove()
end
local duration = net.ReadFloat()
local minDuration = net.ReadFloat()
local killerName = net.ReadString()
deathScreenPanel = vgui.Create("DFrame")
deathScreenPanel:SetSize(ScrW(), ScrH())
deathScreenPanel:SetPos(0, 0)
deathScreenPanel:SetTitle("")
deathScreenPanel:ShowCloseButton(false)
deathScreenPanel:SetDraggable(false)
deathScreenPanel:MakePopup()
deathScreenPanel:SetKeyboardInputEnabled(false)
deathScreenPanel:SetMouseInputEnabled(false)
local colorAlpha = 0
local textAlpha = 0
local fadeOutStarted = false
local startTime = RealTime()
local canRespawnTime = startTime + minDuration
local fadeInSpeed = ix.config.Get("deathScreenFadeInSpeed", 5)
local textFadeSpeed = ix.config.Get("deathScreenTextFadeSpeed", 0.5)
local textColor = ix.config.Get("deathScreenTextColor", Color(132, 43, 60))
local mainText = ix.config.Get("deathScreenMainText", "YOU'RE DEAD")
local respawnText = ix.config.Get("deathScreenRespawnText", "НАЖМИТЕ [ПРОБЕЛ] ЧТОБЫ ВОЗРОДИТЬСЯ")
local subText = "[Убил: " .. killerName .. "]"
-- Убраны таймеры автоматического закрытия
-- Обработка нажатия пробела
deathScreenPanel.Think = function(s)
if (fadeOutStarted) then return end
local lp = LocalPlayer()
if (!IsValid(lp)) then return end
-- Если игрока возродили (медик или админ), убираем экран
-- Проверяем только спустя 2 секунды, чтобы избежать багов при переходе в режим смерти
if (RealTime() > startTime + 2) then
if (lp:Alive() and !lp:GetNWBool("HeartAttack", false)) then
fadeOutStarted = true
timer.Simple(0.5, function()
if (IsValid(s)) then s:Remove() end
end)
return
end
end
-- Проверка возможности сдаться
local character = lp:GetCharacter()
local minBalance = ix.config.Get("deathRespawnMinBalance", 5000)
local canAfford = character and character:GetMoney() >= minBalance
if (canAfford and RealTime() >= canRespawnTime and input.IsKeyDown(KEY_SPACE)) then
net.Start("ixPlayerRespawn")
net.SendToServer()
fadeOutStarted = true
timer.Simple(0.5, function()
if (IsValid(s)) then s:Remove() end
end)
end
end
deathScreenPanel.Paint = function(s, w, h)
if fadeOutStarted then
colorAlpha = math.Approach(colorAlpha, 0, FrameTime() * 255)
else
colorAlpha = math.Approach(colorAlpha, 255, FrameTime() * fadeInSpeed * 255)
end
draw.RoundedBox(0, 0, 0, w, h, Color(0, 0, 0, colorAlpha))
end
local textLabel = vgui.Create("DLabel", deathScreenPanel)
textLabel:SetSize(deathScreenPanel:GetWide(), deathScreenPanel:GetTall())
textLabel:SetPos(0, 0)
textLabel:SetText("")
textLabel.Paint = function(s, w, h)
local elapsed = RealTime() - startTime
if fadeOutStarted then
textAlpha = math.Approach(textAlpha, 0, FrameTime() * 255 * 4)
elseif elapsed > 1 then
textAlpha = math.Approach(textAlpha, 255, FrameTime() * textFadeSpeed * 255)
end
-- Определяем доступные шрифты
local mainFont = GetFont("ixDeathScreenMain", "ixDeathScreenMainFallback")
local subFont = GetFont("ixDeathScreenSub", "ixDeathScreenSubFallback")
-- Рисуем текст
draw.SimpleText(mainText, mainFont, w/2, h/2,
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(subText, subFont, w/2, h/2 + 144,
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- Рисуем оставшееся время или подсказку
local character = LocalPlayer():GetCharacter()
local minBalance = ix.config.Get("deathRespawnMinBalance", 5000)
local canAfford = character and character:GetMoney() >= minBalance
if (RealTime() < canRespawnTime) then
local remaining = math.max(0, math.ceil(canRespawnTime - RealTime()))
draw.SimpleText("ВОЗРОЖДЕНИЕ ЧЕРЕЗ: " .. remaining, subFont, w/2, h/2 + 250,
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
elseif (canAfford) then
draw.SimpleText(respawnText, subFont, w/2, h/2 + 250,
Color(textColor.r, textColor.g, textColor.b, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
else
draw.SimpleText("НЕДОСТАТОЧНО СРЕДСТВ ДЛЯ СДАЧИ (" .. ix.currency.Get(minBalance) .. ")", subFont, w/2, h/2 + 250,
Color(200, 50, 50, textAlpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
-- Рисуем предупреждение о штрафе
local penalty = ix.config.Get("deathPenaltyAmount", 3000)
if (penalty > 0) then
local warningText = string.format(ix.config.Get("deathPenaltyWarningText", "При сдаче с вас спишут штраф: %s"), ix.currency.Get(penalty))
draw.SimpleText(warningText, "ixDeathScreenSmall", w/2, h/2 + 330,
Color(textColor.r, textColor.g, textColor.b, textAlpha * 0.8), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
end)
-- Очистка при отключении
hook.Add("InitPostEntity", "ixDeathScreenCleanup", function()
if IsValid(deathScreenPanel) then
deathScreenPanel:Remove()
end
end)
-- Камера следует за регдоллом при смерти
hook.Add("CalcView", "ixDeathScreenView", function(ply, pos, angles, fov)
if (IsValid(deathScreenPanel) and !ply:Alive()) then
local ragdoll = ply:GetRagdollEntity()
if (IsValid(ragdoll)) then
local eyes = ragdoll:GetAttachment(ragdoll:LookupAttachment("eyes"))
if (eyes) then
return {
origin = eyes.Pos,
angles = eyes.Ang,
fov = fov
}
end
end
end
end)

View File

@@ -0,0 +1,138 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Murder Drones Death Screen"
PLUGIN.description = "Добавляет кинематографичный экран смерти в стиле Murder Drones"
PLUGIN.author = "Scripty & Ported to Helix"
-- Конфигурация
ix.config.Add("deathScreenEnabled", true, "Включить экран смерти Murder Drones", nil, {
category = "Death Screen"
})
ix.config.Add("deathScreenDuration", 300, "Длительность показа экрана смерти (секунды)", nil, {
data = {min = 1, max = 600, decimals = 1},
category = "Death Screen"
})
ix.config.Add("deathScreenMinDuration", 20, "Минимальное время до возможности возродиться (секунды)", nil, {
data = {min = 0, max = 60, decimals = 1},
category = "Death Screen"
})
ix.config.Add("deathScreenFadeInSpeed", 5, "Скорость появления экрана", nil, {
data = {min = 1, max = 20, decimals = 1},
category = "Death Screen"
})
ix.config.Add("deathScreenTextFadeSpeed", 0.5, "Скорость появления текста", nil, {
data = {min = 0.1, max = 5, decimals = 1},
category = "Death Screen"
})
ix.config.Add("deathScreenTextColor", Color(132, 43, 60), "Цвет текста экрана смерти", nil, {
category = "Death Screen"
})
ix.config.Add("deathScreenMainText", "YOU'RE DEAD", "Основной текст экрана смерти", nil, {
category = "Death Screen"
})
ix.config.Add("deathScreenSubText", "[IDIOT]", "Подзаголовок экрана смерти", nil, {
category = "Death Screen"
})
ix.config.Add("deathScreenRespawnText", "НАЖМИТЕ [ПРОБЕЛ] ЧТОБЫ ВОЗРОДИТЬСЯ", "Текст подсказки для возрождения", nil, {
category = "Death Screen"
})
ix.config.Add("deathScreenDisableSound", true, "Отключить стандартный звук смерти", nil, {
category = "Death Screen"
})
ix.config.Add("deathPenaltyAmount", 3000, "Сумма штрафа за возрождение", nil, {
data = {min = 0, max = 100000},
category = "Death Screen"
})
ix.config.Add("deathPenaltyWarningText", "При сдаче с вас спишут штраф: %s", "Текст предупреждения о штрафе", nil, {
category = "Death Screen"
})
ix.config.Add("deathRespawnMinBalance", 5000, "Минимальный баланс для возможности сдаться", nil, {
data = {min = 0, max = 100000},
category = "Death Screen"
})
if SERVER then
util.AddNetworkString("ixDeathScreen")
util.AddNetworkString("ixPlayerRespawn")
function PLUGIN:PlayerDeath(ply, inflictor, attacker)
if not ix.config.Get("deathScreenEnabled") then
return
end
local maxDuration = ix.config.Get("deathScreenDuration", 300)
local minDuration = ix.config.Get("deathScreenMinDuration", 20)
local killerName = "Неизвестно"
if (IsValid(attacker)) then
if (attacker:IsPlayer()) then
killerName = attacker:Name()
elseif (attacker:IsNPC()) then
killerName = attacker:GetClass()
elseif (IsValid(attacker:GetOwner()) and attacker:GetOwner():IsPlayer()) then
killerName = attacker:GetOwner():Name()
elseif (attacker.GetCreator and IsValid(attacker:GetCreator()) and attacker:GetCreator():IsPlayer()) then
killerName = attacker:GetCreator():Name()
else
killerName = (attacker:GetClass() != "worldspawn") and attacker:GetClass() or "Окружение"
end
end
net.Start("ixDeathScreen")
net.WriteFloat(maxDuration)
net.WriteFloat(minDuration)
net.WriteString(killerName)
net.Send(ply)
-- Блокируем стандартный спавн Helix
local duration = RespawnTime[ply:GetUserGroup()] or 60
ply.ixDeathTime = RealTime()
ply.ixNextSpawn = RealTime() + duration
ply:SetNetVar("deathTime", CurTime() + maxDuration)
-- Сохраняем реальное минимальное время для проверки
ply.ixDeathScreenRespawnTime = CurTime() + minDuration
end
-- Разрешаем создание стандартного регдолла Helix
function PLUGIN:ShouldSpawnClientRagdoll(ply)
return true
end
net.Receive("ixPlayerRespawn", function(len, ply)
if (IsValid(ply) and !ply:Alive() and ply:GetCharacter()) then
if ((ply.ixDeathScreenRespawnTime or 0) <= CurTime()) then
local character = ply:GetCharacter()
local minBalance = ix.config.Get("deathRespawnMinBalance", 5000)
-- Проверка баланса перед спавном
if (character:GetMoney() < minBalance) then
ply:Notify("У вас недостаточно средств, чтобы сдаться. Нужно минимум " .. ix.currency.Get(minBalance))
return
end
local penalty = ix.config.Get("deathPenaltyAmount", 3000)
if (penalty > 0) then
character:TakeMoney(penalty)
ply:Notify("Вы сдались. С вашего счета списано " .. ix.currency.Get(penalty) .. " штрафа.")
end
ply:Spawn()
end
end
end)
end
ix.util.Include("cl_deathscreen.lua")

View File

@@ -0,0 +1,11 @@
local PLUGIN = PLUGIN
-- Просмотр украденного документа
net.Receive("ixDisguise_ViewStolenID", function()
local data = net.ReadTable()
local plugin = ix.plugin.Get("military_id")
if plugin then
plugin:ShowDocument(data)
end
end)

View File

@@ -0,0 +1,83 @@
ITEM.name = "Украденный военный билет"
ITEM.description = "Военный билет, снятый с убитого противника. Содержит его данные."
ITEM.model = "models/props_c17/paper01.mdl"
ITEM.width = 1
ITEM.height = 1
ITEM.category = "Маскировка"
ITEM.noDrop = false
-- Функция предъявления документа
ITEM.functions.Show = {
name = "Предъявить",
tip = "Показать военный билет ближайшему игроку",
icon = "icon16/eye.png",
OnRun = function(item)
local client = item.player
if CLIENT then
-- Создаем меню выбора игрока
local menu = DermaMenu()
for _, ply in ipairs(player.GetAll()) do
if ply ~= client and ply:GetPos():Distance(client:GetPos()) <= 200 then
menu:AddOption(ply:Name(), function()
net.Start("ixDisguise_ShowStolenID")
net.WriteUInt(ply:UserID(), 16)
net.WriteUInt(item:GetID(), 32)
net.SendToServer()
end)
end
end
if menu:ChildCount() == 0 then
menu:AddOption("Нет игроков поблизости", function() end):SetDisabled(true)
end
menu:Open()
end
return false
end,
OnCanRun = function(item)
return !IsValid(item.entity)
end
}
-- Функция просмотра своего документа
ITEM.functions.View = {
name = "Просмотреть",
tip = "Посмотреть содержимое военного билета",
icon = "icon16/page_white_text.png",
OnRun = function(item)
local client = item.player
if SERVER then
local data = {
name = item:GetData("name", "Неизвестно"),
faction = item:GetData("faction", "Неизвестно"),
subdivision = item:GetData("subdivision", "Неизвестно"),
specialization = item:GetData("specialization", "Неизвестно"),
rank = item:GetData("rank", "Неизвестно")
}
net.Start("ixDisguise_ViewStolenID")
net.WriteTable(data)
net.Send(client)
end
return false
end,
OnCanRun = function(item)
return !IsValid(item.entity)
end
}
-- Кастомное отображение в инвентаре
if CLIENT then
function ITEM:PaintOver(item, w, h)
local victimName = self:GetData("name")
if victimName then
draw.SimpleText(victimName, "DermaDefault", w/2, h - 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM)
end
end
end

View File

@@ -0,0 +1,101 @@
ITEM.name = "Украденная форма"
ITEM.description = "Военная форма, снятая с убитого противника. Позволяет замаскироваться под врага."
ITEM.model = "models/props_c17/suitcase001a.mdl"
ITEM.width = 2
ITEM.height = 2
ITEM.category = "Маскировка"
ITEM.noDrop = false
-- Функция надевания формы
ITEM.functions.Wear = {
name = "Надеть",
tip = "Надеть украденную форму для маскировки",
icon = "icon16/user_suit.png",
OnRun = function(item)
local client = item.player
local character = client:GetCharacter()
if SERVER then
-- Проверяем кулдаун
local plugin = ix.plugin.Get("disguise")
local canUse, msg = plugin:CanUseDisguise(client)
if not canUse then
client:Notify(msg)
return false
end
-- Проверяем, не в маскировке ли уже
if character:GetData("disguised") then
client:Notify("Вы уже в маскировке!")
return false
end
-- Получаем украденную модель
local stolenModel = item:GetData("stolenModel")
if not stolenModel then
client:Notify("Форма повреждена!")
return false
end
-- Сохраняем оригинальную модель и надеваем украденную
local originalModel = client:GetModel()
character:SetData("originalModel", originalModel)
character:SetData("disguiseModel", stolenModel)
character:SetData("disguised", true)
client:SetModel(stolenModel)
local victimName = item:GetData("victimName", "неизвестного")
client:Notify("Вы надели форму " .. victimName .. ". Будьте осторожны!")
-- Удаляем предмет после использования
item:Remove()
end
return false
end,
OnCanRun = function(item)
return !IsValid(item.entity)
end
}
-- Функция снятия формы
ITEM.functions.Remove = {
name = "Снять",
tip = "Снять маскировку",
icon = "icon16/user_delete.png",
OnRun = function(item)
local client = item.player
local character = client:GetCharacter()
if SERVER then
if not character:GetData("disguised") then
client:Notify("Вы не в маскировке!")
return false
end
local plugin = ix.plugin.Get("disguise")
if plugin then
plugin:RemoveDisguise(client)
end
end
return false
end,
OnCanRun = function(item)
local client = item.player
local character = client and client:GetCharacter()
return !IsValid(item.entity) and character and character:GetData("disguised")
end
}
-- Кастомное отображение в инвентаре
if CLIENT then
function ITEM:PaintOver(item, w, h)
local victimName = self:GetData("victimName")
if victimName then
draw.SimpleText("Форма: " .. victimName, "DermaDefault", w/2, h - 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM)
end
end
end

View File

@@ -0,0 +1,15 @@
PLUGIN.name = "Disguise System"
PLUGIN.author = "Your Name"
PLUGIN.description = "Система маскировки с украденной формой и документами"
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")
if SERVER then
util.AddNetworkString("ixDisguise_ViewStolenID")
util.AddNetworkString("ixDisguise_ShowStolenID")
end
-- Конфигурация
PLUGIN.dropChance = 0.3 -- 30% шанс выпадения предметов
PLUGIN.disguiseCooldown = 600 -- 10 минут в секундах

View File

@@ -0,0 +1,99 @@
local PLUGIN = PLUGIN
-- Таблица для хранения кулдаунов
PLUGIN.disguiseCooldowns = PLUGIN.disguiseCooldowns or {}
-- Снятие маскировки
function PLUGIN:RemoveDisguise(client)
if not IsValid(client) then return end
local character = client:GetCharacter()
if not character then return end
-- Возвращаем оригинальную модель
local originalModel = character:GetModel()
client:SetModel(originalModel)
-- Удаляем флаг маскировки
character:SetData("disguised", false)
character:SetData("disguiseModel", nil)
-- Устанавливаем кулдаун
self.disguiseCooldowns[client:SteamID64()] = CurTime() + self.disguiseCooldown
client:Notify("Маскировка снята! Вы не сможете переодеться в течение 10 минут.")
end
-- Проверка кулдауна
function PLUGIN:CanUseDisguise(client)
local steamID = client:SteamID64()
local cooldown = self.disguiseCooldowns[steamID]
if cooldown and CurTime() < cooldown then
local remaining = math.ceil(cooldown - CurTime())
local minutes = math.floor(remaining / 60)
local seconds = remaining % 60
return false, string.format("Переодеться можно через %d:%02d", minutes, seconds)
end
return true
end
-- Хук на получение урона
function PLUGIN:EntityTakeDamage(target, dmg)
if not IsValid(target) or not target:IsPlayer() then return end
local character = target:GetCharacter()
if not character then return end
-- Проверяем, в маскировке ли игрок
if character:GetData("disguised") then
self:RemoveDisguise(target)
end
end
-- Хук на взятие оружия в руки
function PLUGIN:PlayerSwitchWeapon(client, oldWeapon, newWeapon)
if not IsValid(client) or not IsValid(newWeapon) then return end
local character = client:GetCharacter()
if not character then return end
-- Проверяем, в маскировке ли игрок
if character:GetData("disguised") then
self:RemoveDisguise(client)
end
end
-- Отправка данных украденного военного билета
net.Receive("ixDisguise_ShowStolenID", function(len, client)
local targetID = net.ReadUInt(16)
local itemID = net.ReadUInt(32)
local target = Player(targetID)
if not IsValid(target) or target:GetPos():Distance(client:GetPos()) > 200 then
client:Notify("Игрок слишком далеко!")
return
end
local item = ix.item.instances[itemID]
if not item or item.player ~= client then
return
end
-- Отправляем данные документа целевому игроку
local data = {
name = item:GetData("name", "Неизвестно"),
faction = item:GetData("faction", "Неизвестно"),
subdivision = item:GetData("subdivision", "Неизвестно"),
specialization = item:GetData("specialization", "Неизвестно"),
rank = item:GetData("rank", "Неизвестно")
}
net.Start("ixDisguise_ViewStolenID")
net.WriteTable(data)
net.Send(target)
client:Notify("Вы предъявили военный билет игроку " .. target:Name())
target:Notify(client:Name() .. " предъявил вам военный билет")
end)

View File

@@ -0,0 +1,3 @@
local PLUGIN = PLUGIN
-- Клиентские опции (если нужны дополнительные настройки на клиенте)

View File

@@ -0,0 +1,6 @@
PLUGIN.name = "Dynamic Height"
PLUGIN.author = "RefoselTeamWork"
PLUGIN.description = "Автоматическая настройка высоты камеры игрока в зависимости от модели персонажа."
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")

View File

@@ -0,0 +1,113 @@
local PLUGIN = PLUGIN
-- Конфигурационные переменные
ix.config.Add("dynamicHeightEnabled", true, "Включить динамическую высоту камеры.", nil, {
category = "Динамическая высота"
})
ix.config.Add("dynamicHeightMaxManual", false, "Использовать ручную максимальную высоту.", nil, {
category = "Динамическая высота"
})
ix.config.Add("dynamicHeightMinManual", false, "Использовать ручную минимальную высоту.", nil, {
category = "Динамическая высота"
})
ix.config.Add("dynamicHeightMin", 16, "Минимальная высота камеры (присед).", nil, {
data = {min = 5, max = 180},
category = "Динамическая высота"
})
ix.config.Add("dynamicHeightMax", 64, "Максимальная высота камеры (стоя).", nil, {
data = {min = 5, max = 180},
category = "Динамическая высота"
})
local function UpdateView(ply)
if not ix.config.Get("dynamicHeightEnabled", true) then
if ply.ixDynamicHeightChanged then
ply:SetViewOffset(Vector(0, 0, 64))
ply:SetViewOffsetDucked(Vector(0, 0, 28))
ply.ixDynamicHeightChanged = nil
end
return
end
-- Определяем высоту модели
local height_max = 64
local height_min = 16
-- Создаем временную сущность для измерения высоты стоя
local entity = ents.Create("base_anim")
entity:SetModel(ply:GetModel())
entity:ResetSequence(entity:LookupSequence("idle_all_01"))
local bone = entity:LookupBone("ValveBiped.Bip01_Neck1")
if bone then
height_max = entity:GetBonePosition(bone).z + 5
end
-- Создаем временную сущность для измерения высоты приседа
local entity2 = ents.Create("base_anim")
entity2:SetModel(ply:GetModel())
entity2:ResetSequence(entity2:LookupSequence("cidle_all"))
local bone2 = entity2:LookupBone("ValveBiped.Bip01_Neck1")
if bone2 then
height_min = entity2:GetBonePosition(bone2).z + 5
end
-- Удаляем временные сущности
entity:Remove()
entity2:Remove()
-- Применяем настройки высоты
local min = ix.config.Get("dynamicHeightMin", 16)
local max = ix.config.Get("dynamicHeightMax", 64)
if ix.config.Get("dynamicHeightMinManual", false) then
ply:SetViewOffsetDucked(Vector(0, 0, min))
else
ply:SetViewOffsetDucked(Vector(0, 0, height_min))
end
if ix.config.Get("dynamicHeightMaxManual", false) then
ply:SetViewOffset(Vector(0, 0, max))
else
ply:SetViewOffset(Vector(0, 0, height_max))
end
ply.ixDynamicHeightChanged = true
end
local function UpdateTrueModel(ply)
if ply:GetNWString("ixDynamicHeight:TrueModel") ~= ply:GetModel() then
ply:SetNWString("ixDynamicHeight:TrueModel", ply:GetModel())
UpdateView(ply)
end
end
function PLUGIN:PlayerSpawn(ply)
UpdateTrueModel(ply)
end
function PLUGIN:PlayerTick(ply)
UpdateTrueModel(ply)
end
-- Обновление при изменении конфигов
local function OnConfigChanged(key, oldValue, newValue)
if string.StartWith(key, "dynamicHeight") then
for _, ply in pairs(player.GetAll()) do
UpdateView(ply)
end
end
end
function PLUGIN:OnConfigChanged(key, oldValue, newValue)
if key == "dynamicHeightEnabled" or
key == "dynamicHeightMin" or
key == "dynamicHeightMax" or
key == "dynamicHeightMinManual" or
key == "dynamicHeightMaxManual" then
OnConfigChanged(key, oldValue, newValue)
end
end

View File

@@ -0,0 +1,22 @@
net.Receive("AnnounceMessage", function()
local msg = net.ReadString()
chat.AddText(Color(255, 0, 0), msg)
end)
local eventText = ""
local eventTime = 0
net.Receive("EventMessage", function()
eventText = net.ReadString()
eventTime = CurTime() + 5 -- show for 5 seconds
end)
function PLUGIN:HUDPaint()
if CurTime() < eventTime then
surface.SetFont("ixBigFont")
local w, h = surface.GetTextSize(eventText)
surface.SetTextColor(255, 255, 255)
surface.SetTextPos(ScrW() / 2 - w / 2, 50)
surface.DrawText(eventText)
end
end

View File

@@ -0,0 +1,6 @@
PLUGIN.name = "Event Announce"
PLUGIN.author = "Scripty"
PLUGIN.description = "/event /annonce"
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")

View File

@@ -0,0 +1,21 @@
util.AddNetworkString("EventMessage")
util.AddNetworkString("AnnounceMessage")
function PLUGIN:PlayerSay(ply, text)
local userGroup = ply:GetUserGroup() or ""
if userGroup == "superadmin" or userGroup == "ivent" then
if string.StartWith(text, "/event ") then
local msg = string.sub(text, 8)
net.Start("EventMessage")
net.WriteString(msg)
net.Broadcast()
return ""
elseif string.StartWith(text, "/annonce ") then
local msg = string.sub(text, 10)
net.Start("AnnounceMessage")
net.WriteString(msg)
net.Broadcast()
return ""
end
end
end

View File

@@ -0,0 +1,115 @@
local PLUGIN = PLUGIN
local doomsday_active = false
local doomsday_start_time = 0
net.Receive("doomsday_night_hud_update", function()
doomsday_active = net.ReadBool()
doomsday_start_time = net.ReadInt(32)
end)
function PLUGIN:DoomsdayHUDPaint()
local active = doomsday_active or GetGlobalBool("doomsday_night_active", false)
local start_time = doomsday_start_time == 0 and GetGlobalInt("doomsday_night_start_time", 0) or doomsday_start_time
local alive_count = GetGlobalInt("doomsday_night_alive_count", 0)
if (!active or start_time == 0) then return end
local elapsed = CurTime() - start_time
local minutes = math.floor(elapsed / 60)
local seconds = math.floor(elapsed % 60)
local x, y = ScrW() - 280, 20
local w, h = 260, 120
surface.SetDrawColor(20, 20, 40, 200)
surface.DrawRect(x, y, w, h)
local border_alpha = 150 + math.abs(math.sin(CurTime() * 2)) * 105
surface.SetDrawColor(100, 0, 100, border_alpha)
surface.DrawOutlinedRect(x, y, w, h, 3)
surface.SetFont("DermaDefaultBold")
surface.SetTextColor(200, 100, 255, 255)
local title = "🌙 СУДНАЯ НОЧЬ"
local tw, th = surface.GetTextSize(title)
surface.SetTextPos(x + (w - tw) / 2, y + 8)
surface.DrawText(title)
surface.SetFont("DermaDefault")
surface.SetTextColor(255, 255, 255, 255)
local time_text = string.format("Время: %02d:%02d", minutes, seconds)
tw, th = surface.GetTextSize(time_text)
surface.SetTextPos(x + (w - tw) / 2, y + th + 15)
surface.DrawText(time_text)
local alive_pulse = 200 + math.abs(math.sin(CurTime() * 3)) * 55
surface.SetTextColor(255, 150, 150, alive_pulse)
local alive_text = string.format("Выживших: %d", alive_count)
tw, th = surface.GetTextSize(alive_text)
surface.SetTextPos(x + (w - tw) / 2, y + th + 45)
surface.DrawText(alive_text)
if (LocalPlayer():GetObserverMode() != OBS_MODE_NONE) then
surface.SetTextColor(255, 100, 100, alive_pulse)
local spec_text = "РЕЖИМ НАБЛЮДЕНИЯ"
tw, th = surface.GetTextSize(spec_text)
surface.SetTextPos(x + (w - tw) / 2, y + h - 25)
surface.DrawText(spec_text)
end
end
function PLUGIN:DoomsdayPlayerStats()
if (!GetGlobalBool("doomsday_night_active", false)) then return end
local ply = LocalPlayer()
local x, y, w, h = 20, 20, 220, 120
surface.SetDrawColor(0, 0, 0, 150)
surface.DrawRect(x, y, w, h)
surface.SetDrawColor(100, 0, 100, 200)
surface.DrawOutlinedRect(x, y, w, h, 2)
surface.SetFont("DermaDefaultBold")
surface.SetTextColor(200, 200, 255, 255)
surface.SetTextPos(x + 10, y + 10)
surface.DrawText("📊 Ваша статистика:")
surface.SetFont("DermaDefault")
surface.SetTextColor(255, 255, 255, 255)
surface.SetTextPos(x + 10, y + 35)
surface.DrawText(string.format("❤️ HP: %d", ply:Health()))
surface.SetTextPos(x + 10, y + 55)
surface.DrawText(string.format("🛡️ Armor: %d", ply:Armor()))
local weapon = ply:GetActiveWeapon()
if (IsValid(weapon)) then
local w_name = string.gsub(weapon:GetClass(), "tfa_ins2_", ""):upper()
surface.SetTextPos(x + 10, y + 75)
surface.DrawText("🔫 Оружие: " .. w_name)
end
end
function PLUGIN:DoomsdayDeathEffect()
if (!GetGlobalBool("doomsday_night_active", false)) then return end
local ply = LocalPlayer()
if (ply:Alive()) then return end
local alpha = 50 + math.abs(math.sin(CurTime() * 2)) * 30
surface.SetDrawColor(150, 0, 0, alpha)
surface.DrawRect(0, 0, ScrW(), ScrH())
surface.SetFont("DermaLarge")
local title_pulse = 200 + math.abs(math.sin(CurTime() * 3)) * 55
surface.SetTextColor(255, 50, 50, title_pulse)
local text = "💀 ВЫБЫЛ 💀"
local tw, th = surface.GetTextSize(text)
surface.SetTextPos((ScrW() - tw) / 2, ScrH() / 2 - 100)
surface.DrawText(text)
end
function PLUGIN:HUDPaint()
self:DrawEventHUD()
self:DoomsdayHUDPaint()
self:DoomsdayPlayerStats()
self:DoomsdayDeathEffect()
end

View File

@@ -0,0 +1,131 @@
local PLUGIN = PLUGIN
local event_active = false
local event_start_time = 0
local event_zone_active = false
local event_zone_min = nil
local event_zone_max = nil
-- Net Receivers
net.Receive("events_system_notification", function()
local message = net.ReadString()
local lines = string.Split(message, "\n")
for _, line in ipairs(lines) do
if (string.Trim(line) != "") then
chat.AddText(Color(255, 100, 100), " [EVENT] ", Color(255, 255, 255), line)
end
end
surface.PlaySound("buttons/button15.wav")
end)
net.Receive("events_system_hud_update", function()
event_active = net.ReadBool()
event_start_time = net.ReadInt(32)
end)
net.Receive("events_system_zone_update", function()
event_zone_active = net.ReadBool()
if (event_zone_active) then
event_zone_min = net.ReadVector()
event_zone_max = net.ReadVector()
else
event_zone_min = nil
event_zone_max = nil
end
end)
-- Fonts
surface.CreateFont("EventHUD_Title", { font = "Roboto", size = 24, weight = 700, antialias = true, shadow = true })
surface.CreateFont("EventHUD_Time", { font = "Roboto", size = 32, weight = 800, antialias = true, shadow = false })
surface.CreateFont("EventHUD_Label", { font = "Roboto", size = 16, weight = 500, antialias = true })
surface.CreateFont("EventHUD_Warning", { font = "Roboto", size = 14, weight = 600, antialias = true })
local function DrawGradientBox(x, y, w, h, color1, color2, vertical)
local steps = 20
local step_size = vertical and (h / steps) or (w / steps)
for i = 0, steps - 1 do
local t = i / steps
local r = Lerp(t, color1.r, color2.r)
local g = Lerp(t, color1.g, color2.g)
local b = Lerp(t, color1.b, color2.b)
local a = Lerp(t, color1.a, color2.a)
surface.SetDrawColor(r, g, b, a)
if (vertical) then
surface.DrawRect(x, y + i * step_size, w, step_size + 1)
else
surface.DrawRect(x + i * step_size, y, step_size + 1, h)
end
end
end
function PLUGIN:DrawEventHUD()
local active = event_active or GetGlobalBool("events_system_active", false)
local start_time = event_start_time == 0 and GetGlobalInt("events_system_start_time", 0) or event_start_time
if (!active or start_time == 0) then return end
local elapsed = CurTime() - start_time
local minutes = math.floor(elapsed / 60)
local seconds = math.floor(elapsed % 60)
local padding = 20
local x = ScrW() - 320 - padding
local y = padding
local w, h = 320, 140
DrawGradientBox(x, y, w, h, Color(15, 15, 20, 220), Color(25, 25, 35, 200), true)
local pulse = math.abs(math.sin(CurTime() * 2))
DrawGradientBox(x, y, w, 4, Color(255, 50, 80, 180 + pulse * 75), Color(255, 100, 50, 180 + pulse * 75), false)
surface.SetDrawColor(255, 80, 100, 100 + pulse * 80)
for i = 1, 2 do surface.DrawOutlinedRect(x - i, y - i, w + i * 2, h + i * 2, 1) end
local offsetY = y + 15
surface.SetFont("EventHUD_Title")
surface.SetTextColor(255, 80 + pulse * 50, 80 + pulse * 50, 255)
local title = "⚔ БОЕВОЙ ИВЕНТ"
local tw, th = surface.GetTextSize(title)
surface.SetTextPos(x + (w - tw) / 2, offsetY)
surface.DrawText(title)
offsetY = offsetY + th + 10
surface.SetFont("EventHUD_Time")
surface.SetTextColor(255, 255, 255, 255)
local time_text = string.format("%02d:%02d", minutes, seconds)
tw, th = surface.GetTextSize(time_text)
surface.SetTextPos(x + (w - tw) / 2, offsetY)
surface.DrawText(time_text)
offsetY = offsetY + th + 10
surface.SetFont("EventHUD_Warning")
surface.SetTextColor(255, 220, 100, 200 + pulse * 55)
local warning = "⚠ При смерти выбываете"
tw, th = surface.GetTextSize(warning)
surface.SetTextPos(x + (w - tw) / 2, offsetY)
surface.DrawText(warning)
end
function PLUGIN:PostDrawTranslucentRenderables()
if (!event_zone_active or !event_zone_min or !event_zone_max) then return end
local min, max = event_zone_min, event_zone_max
local corners = {
Vector(min.x, min.y, min.z), Vector(max.x, min.y, min.z), Vector(max.x, max.y, min.z), Vector(min.x, max.y, min.z),
Vector(min.x, min.y, max.z), Vector(max.x, min.y, max.z), Vector(max.x, max.y, max.z), Vector(min.x, max.y, max.z)
}
local color = Color(0, 255, 100, 150)
render.DrawLine(corners[1], corners[2], color, true)
render.DrawLine(corners[2], corners[3], color, true)
render.DrawLine(corners[3], corners[4], color, true)
render.DrawLine(corners[4], corners[1], color, true)
render.DrawLine(corners[5], corners[6], color, true)
render.DrawLine(corners[6], corners[7], color, true)
render.DrawLine(corners[7], corners[8], color, true)
render.DrawLine(corners[8], corners[5], color, true)
render.DrawLine(corners[1], corners[5], color, true)
render.DrawLine(corners[2], corners[6], color, true)
render.DrawLine(corners[3], corners[7], color, true)
render.DrawLine(corners[4], corners[8], color, true)
end

View File

@@ -0,0 +1,212 @@
local PLUGIN = PLUGIN
PLUGIN.name = "Система Ивентов"
PLUGIN.author = "Scripty & RefoselTeamWork"
PLUGIN.description = "Продвинутая система ивентов с сужающейся зоной и кастомными спавнами."
ix.config.Add("eventAllowedRanks", "superadmin,curator,ivent", "Ранги, которым разрешено управление ивентами (через запятую).", nil, {
category = "Events"
})
-- Shared Config (ported from sh_config.lua)
PLUGIN.Config = {
sounds = {
event_start = "ambient/alarms/klaxon1.wav",
event_end = "ambient/levels/citadel/citadel_hit.wav",
player_join = "buttons/button15.wav"
}
}
ix.util.Include("sv_plugin.lua")
ix.util.Include("sv_doomsday.lua")
ix.util.Include("cl_plugin.lua")
ix.util.Include("cl_doomsday.lua")
-- Commands Adaptation
ix.command.Add("EventStart", {
description = "Запустить стандартный боевой ивент.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
if (PLUGIN.active) then
return "Ивент уже активен!"
end
PLUGIN:StartEvent(client)
return "Запуск ивента..."
end
})
ix.command.Add("EventStop", {
description = "Остановить текущий активный ивент.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
if (!PLUGIN.active) then
return "Нет активного ивента для остановки!"
end
PLUGIN:EndEvent(client)
return "Остановка ивента..."
end
})
ix.command.Add("EventOtkat", {
description = "Отменить активный ивент с уведомлением.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
if (!PLUGIN.active) then
return "Нет активного ивента для отмены!"
end
PLUGIN:EndEvent(client, true)
return "Отмена ивента..."
end
})
ix.command.Add("EventJoin", {
description = "Присоединиться к активному ивенту.",
OnRun = function(self, client)
if (!PLUGIN.active) then
return "Нет активного ивента для входа."
end
if (PLUGIN.participants[client:SteamID()]) then
return "Вы уже участвуете!"
end
PLUGIN:AddParticipant(client)
local participants_count = table.Count(PLUGIN.participants)
PLUGIN:BroadcastMessage(string.format("%s присоединился к ивенту! Участников: %d", client:Nick(), participants_count))
end
})
ix.command.Add("EventAddAll", {
description = "Добавить всех игроков онлайн в ивент.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
if (!PLUGIN.active) then
return "Ивент не активен!"
end
PLUGIN:AddAllPlayers(client)
end
})
ix.command.Add("EventSetZone1", {
description = "Установить первую точку зоны ивента.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
PLUGIN:SetZonePoint1(client)
end
})
ix.command.Add("EventSetZone2", {
description = "Установить вторую точку зоны ивента.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
PLUGIN:SetZonePoint2(client)
end
})
ix.command.Add("EventClearZone", {
description = "Очистить зону ивента.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
PLUGIN:ClearZone(client)
end
})
ix.command.Add("EventZoneStart", {
description = "Начать сужение зоны ивента.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
PLUGIN:StartZoneShrinking(client)
end
})
ix.command.Add("EventZoneStop", {
description = "Остановить сужение зоны ивента.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
PLUGIN:StopZoneShrinking(client)
end
})
-- Temporary Spawns Commands
ix.command.Add("EventSpawnAdd", {
description = "Добавить временный спавн для фракции или spec_def.",
privilege = "Manage Events",
adminOnly = true,
arguments = {
ix.type.string, -- Faction Name
bit.bor(ix.type.number, ix.type.optional) -- spec_def
},
OnRun = function(self, client, factionName, specDef)
local faction = ix.faction.teams[factionName]
if (!faction) then
-- Try fuzzy match
for _, v in pairs(ix.faction.indices) do
if (ix.util.StringMatches(v.name, factionName) or ix.util.StringMatches(v.uniqueID, factionName)) then
faction = v
break
end
end
end
if (!faction) then
return "Некорректное название фракции!"
end
PLUGIN:AddTempSpawn(faction.uniqueID, specDef, client:GetPos(), client:GetAngles())
local msg = "Временный спавн добавлен для фракции: " .. faction.name
if (specDef) then
msg = msg .. " (spec_def: " .. specDef .. ")"
end
return msg
end
})
ix.command.Add("EventSpawnRemove", {
description = "Удалить временные спавны ивента в радиусе.",
privilege = "Manage Events",
adminOnly = true,
arguments = {
bit.bor(ix.type.number, ix.type.optional) -- Radius
},
OnRun = function(self, client, radius)
radius = radius or 120
local count = PLUGIN:RemoveTempSpawns(client:GetPos(), radius)
return "Удалено " .. count .. " временных спавнов."
end
})
-- Doomsday Commands
ix.command.Add("DoomsdayStart", {
description = "Запустить ивент Судная Ночь.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
if (DoomsdayNight.active) then
return "Судная Ночь уже активна!"
end
DoomsdayNight:StartEvent(client)
end
})
ix.command.Add("DoomsdayStop", {
description = "Остановить ивент Судная Ночь.",
privilege = "Manage Events",
adminOnly = true,
OnRun = function(self, client)
if (!DoomsdayNight.active) then
return "Судная Ночь не активна!"
end
DoomsdayNight:EndEvent(client, true)
end
})

View File

@@ -0,0 +1,184 @@
DoomsdayNight = DoomsdayNight or {}
DoomsdayNight.active = false
DoomsdayNight.start_time = 0
DoomsdayNight.participants = {}
DoomsdayNight.spectators = {}
DoomsdayNight.spawn_positions = {}
DoomsdayNight.weapons = {
"tfa_ins2_ak74", "tfa_ins2_m4a1", "tfa_ins2_ak12", "tfa_ins2_m16a4", "tfa_ins2_scar_h_ssr",
"tfa_ins2_m40a1", "tfa_ins2_mosin", "tfa_ins2_svd", "tfa_ins2_m590", "tfa_ins2_toz",
"tfa_ins2_glock_17", "tfa_ins2_m9", "tfa_ins2_makarov", "tfa_ins2_mp5", "tfa_ins2_ump45", "tfa_ins2_mp40"
}
util.AddNetworkString("doomsday_night_notification")
util.AddNetworkString("doomsday_night_hud_update")
function DoomsdayNight:Init()
SetGlobalBool("doomsday_night_active", false)
SetGlobalInt("doomsday_night_start_time", 0)
SetGlobalInt("doomsday_night_alive_count", 0)
self:GenerateSpawnPositions()
end
function DoomsdayNight:GenerateSpawnPositions()
local map = game.GetMap()
local map_positions = {
["gm_construct"] = {
Vector(980, -1500, -79), Vector(-700, 1000, 200), Vector(1500, 800, 100), Vector(-1200, -800, 50)
},
["rp_valkyrie_forest_v1"] = {
Vector(5000, 3000, 200), Vector(-5000, -3000, 150), Vector(8000, -2000, 100), Vector(-8000, 2000, 180)
}
}
self.spawn_positions = map_positions[map] or { Vector(1000, 1000, 100), Vector(-1000, -1000, 100) }
end
function DoomsdayNight:StartEvent(ply)
local players = player.GetAll()
if (#players < 2) then
ply:Notify("Недостаточно игроков для начала Судной Ночи!")
return
end
self.active = true
self.start_time = CurTime()
self.participants = {}
self.spectators = {}
SetGlobalBool("doomsday_night_active", true)
SetGlobalInt("doomsday_night_start_time", CurTime())
for _, player in ipairs(players) do
if (IsValid(player)) then self:PreparePlayer(player) end
end
self:BroadcastMessage("🌙 СУДНАЯ НОЧЬ НАЧАЛАСЬ! 🌙\nСражайтесь до последнего выжившего!\nВыдано случайное оружие.")
timer.Simple(3, function() if (self.active) then self:TeleportPlayers() end end)
self:UpdateHUD()
end
function DoomsdayNight:PreparePlayer(ply)
local steamid = ply:SteamID()
self.participants[steamid] = {
start_pos = ply:GetPos(),
join_time = CurTime(),
kills = 0,
nick = ply:Nick(),
alive = true
}
ply:StripWeapons()
self:GiveRandomWeapons(ply)
end
function DoomsdayNight:GiveRandomWeapons(ply)
if (!IsValid(ply) or !ply:Alive()) then return end
ply:Give("ix_hands")
local weapons_to_give = {}
local used = {}
for i = 1, math.random(2, 3) do
local w = self.weapons[math.random(1, #self.weapons)]
if (!used[w]) then
used[w] = true
table.insert(weapons_to_give, w)
end
end
for _, w_class in ipairs(weapons_to_give) do
local weapon = ply:Give(w_class)
if (IsValid(weapon)) then
ply:GiveAmmo(500, weapon:GetPrimaryAmmoType(), true)
end
end
ply:Give("weapon_frag")
ply:GiveAmmo(2, "Grenade", true)
ply:SetHealth(100)
ply:SetArmor(100)
end
function DoomsdayNight:TeleportPlayers()
local players = {}
for steamid, data in pairs(self.participants) do
local ply = player.GetBySteamID(steamid)
if (IsValid(ply)) then table.insert(players, ply) end
end
local positions = table.Copy(self.spawn_positions)
for i, ply in ipairs(players) do
local spawn_pos = positions[((i - 1) % #positions) + 1]
ply:SetPos(spawn_pos)
ply:SetEyeAngles(Angle(0, math.random(0, 360), 0))
end
SetGlobalInt("doomsday_night_alive_count", #players)
self:BroadcastMessage("⚡ Игроки телепортированы! К БОЮ!")
end
function DoomsdayNight:EndEvent(ply, cancelled)
if (!self.active) then return end
self.active = false
SetGlobalBool("doomsday_night_active", false)
for steamid, _ in pairs(self.spectators) do
local spec_ply = player.GetBySteamID(steamid)
if (IsValid(spec_ply)) then
spec_ply:UnSpectate()
spec_ply:Spawn()
end
end
self:BroadcastMessage(cancelled and "🛑 СУДНАЯ НОЧЬ ОТМЕНЕНА" or "🏆 СУДНАЯ НОЧЬ ЗАВЕРШЕНА")
self.participants = {}
self.spectators = {}
self:UpdateHUD()
end
function DoomsdayNight:OnPlayerKilled(victim, attacker)
if (!self.active) then return end
local data = self.participants[victim:SteamID()]
if (!data) then return end
data.alive = false
if (IsValid(attacker) and attacker:IsPlayer()) then
local attacker_data = self.participants[attacker:SteamID()]
if (attacker_data) then attacker_data.kills = attacker_data.kills + 1 end
end
timer.Simple(2, function()
if (IsValid(victim)) then
self.spectators[victim:SteamID()] = true
victim:Spectate(OBS_MODE_ROAMING)
victim:Notify("👁️ Вы перешли в режим наблюдения до конца ивента.")
end
end)
local alive_count = 0
for _, d in pairs(self.participants) do
if (d.alive) then alive_count = alive_count + 1 end
end
SetGlobalInt("doomsday_night_alive_count", alive_count)
if (alive_count <= 1) then
timer.Simple(3, function() if (self.active) then self:EndEvent() end end)
end
end
function DoomsdayNight:BroadcastMessage(msg)
for _, ply in ipairs(player.GetAll()) do ply:ChatPrint(msg) end
end
function DoomsdayNight:UpdateHUD()
net.Start("doomsday_night_hud_update")
net.WriteBool(self.active)
net.WriteInt(self.start_time, 32)
net.Broadcast()
end
-- Hook aliases for PLUGIN
function PLUGIN:DoomsdayNight_Init() DoomsdayNight:Init() end
function PLUGIN:DoomsdayNight_PlayerDeath(victim, inflictor, attacker) DoomsdayNight:OnPlayerKilled(victim, attacker) end
hook.Add("InitPostEntity", "DoomsdayNight_Init", function() DoomsdayNight:Init() end)

View File

@@ -0,0 +1,360 @@
local PLUGIN = PLUGIN
PLUGIN.active = PLUGIN.active or false
PLUGIN.start_time = PLUGIN.start_time or 0
PLUGIN.participants = PLUGIN.participants or {}
PLUGIN.eliminated_players = PLUGIN.eliminated_players or {}
PLUGIN.initiator = PLUGIN.initiator or nil
PLUGIN.tempSpawns = PLUGIN.tempSpawns or {} -- [factionUniqueID] = { [specDef or "default"] = { {pos, ang}, ... } }
-- Zone Data
PLUGIN.zone = PLUGIN.zone or {
pos1 = nil,
pos2 = nil,
min = nil,
max = nil,
shrinking = false,
center = nil,
shrink_speed = 1,
min_size = 500
}
-- Networking
util.AddNetworkString("events_system_notification")
util.AddNetworkString("events_system_hud_update")
util.AddNetworkString("events_system_zone_update")
-- Initialization
function PLUGIN:InitPostEntity()
SetGlobalBool("events_system_active", false)
SetGlobalInt("events_system_start_time", 0)
self.tempSpawns = self:GetData() or {}
end
function PLUGIN:SaveTempSpawns()
self:SetData(self.tempSpawns)
end
-- Permissions Check (using ix.config)
function PLUGIN:HasPermission(ply)
if (!IsValid(ply)) then return false end
if (ply:IsSuperAdmin()) then return true end
local allowed = string.Split(ix.config.Get("eventAllowedRanks", ""), ",")
local userGroup = ply:GetUserGroup()
for _, rank in ipairs(allowed) do
if (string.lower(userGroup) == string.lower(string.Trim(rank))) then
return true
end
end
return false
end
-- Zone Logic
function PLUGIN:SetZonePoint1(ply)
self.zone.pos1 = ply:GetPos()
self:SendMessage(ply, "✅ Точка зоны 1 установлена: " .. tostring(self.zone.pos1))
if (self.zone.pos2) then self:CalculateZoneBounds() end
end
function PLUGIN:SetZonePoint2(ply)
self.zone.pos2 = ply:GetPos()
self:SendMessage(ply, "✅ Точка зоны 2 установлена: " .. tostring(self.zone.pos2))
if (self.zone.pos1) then self:CalculateZoneBounds() end
end
function PLUGIN:CalculateZoneBounds()
if (!self.zone.pos1 or !self.zone.pos2) then return end
self.zone.min = Vector(
math.min(self.zone.pos1.x, self.zone.pos2.x),
math.min(self.zone.pos1.y, self.zone.pos2.y),
math.min(self.zone.pos1.z, self.zone.pos2.z)
)
self.zone.max = Vector(
math.max(self.zone.pos1.x, self.zone.pos2.x),
math.max(self.zone.pos1.y, self.zone.pos2.y),
math.max(self.zone.pos1.z, self.zone.pos2.z)
)
self:BroadcastZone()
local size = self.zone.max - self.zone.min
self:BroadcastMessage(string.format("🎯 Зона ивента установлена! Размер: %.0f x %.0f x %.0f", size.x, size.y, size.z))
end
function PLUGIN:BroadcastZone()
if (!self.zone.min or !self.zone.max) then return end
net.Start("events_system_zone_update")
net.WriteBool(true)
net.WriteVector(self.zone.min)
net.WriteVector(self.zone.max)
net.Broadcast()
end
function PLUGIN:ClearZone(ply)
self.zone.pos1 = nil
self.zone.pos2 = nil
self.zone.min = nil
self.zone.max = nil
self.zone.shrinking = false
if (timer.Exists("EventsSystem_ZoneShrink")) then
timer.Remove("EventsSystem_ZoneShrink")
end
net.Start("events_system_zone_update")
net.WriteBool(false)
net.Broadcast()
if (IsValid(ply)) then
self:SendMessage(ply, "🗑️ Зона ивента очищена.")
end
end
function PLUGIN:StartZoneShrinking(ply)
if (!self.zone.min or !self.zone.max) then
if (IsValid(ply)) then self:SendMessage(ply, "❌ Сначала установите зону!") end
return
end
if (self.zone.shrinking) then
if (IsValid(ply)) then self:SendMessage(ply, "⚠️ Зона уже сужается!") end
return
end
self.zone.center = (self.zone.min + self.zone.max) / 2
self.zone.shrinking = true
timer.Create("EventsSystem_ZoneShrink", 0.1, 0, function()
self:ShrinkZone()
end)
self:BroadcastMessage("🔴 ВНИМАНИЕ! Зона начала сужаться к центру!")
for _, player in ipairs(player.GetAll()) do
player:EmitSound("ambient/alarms/warningbell1.wav", 75, 100)
end
end
function PLUGIN:StopZoneShrinking(ply)
if (!self.zone.shrinking) then return end
self.zone.shrinking = false
timer.Remove("EventsSystem_ZoneShrink")
local size = self.zone.max - self.zone.min
self:BroadcastMessage(string.format("⏸️ Сужение зоны остановлено! Текущий размер: %.0f x %.0f", size.x, size.y))
end
function PLUGIN:ShrinkZone()
if (!self.zone.shrinking or !self.zone.min or !self.zone.max or !self.zone.center) then return end
local shrink_amount = self.zone.shrink_speed * 0.1
local new_min = Vector(self.zone.min.x + shrink_amount, self.zone.min.y + shrink_amount, self.zone.min.z)
local new_max = Vector(self.zone.max.x - shrink_amount, self.zone.max.y - shrink_amount, self.zone.max.z)
if (new_max.x - new_min.x <= self.zone.min_size or new_max.y - new_min.y <= self.zone.min_size) then
self:StopZoneShrinking(nil)
self:BroadcastMessage("❗ Зона достигла минимального размера!")
return
end
self.zone.min = new_min
self.zone.max = new_max
self:BroadcastZone()
end
function PLUGIN:IsInZone(pos)
if (!self.zone.min or !self.zone.max) then return true end
return pos.x >= self.zone.min.x and pos.x <= self.zone.max.x and
pos.y >= self.zone.min.y and pos.y <= self.zone.max.y and
pos.z >= self.zone.min.z and pos.z <= self.zone.max.z
end
function PLUGIN:CheckZoneBoundaries()
if (!self.active) then return end
for steamid, data in pairs(self.participants) do
local ply = player.GetBySteamID(steamid)
if (IsValid(ply) and ply:Alive()) then
if (!self:IsInZone(ply:GetPos())) then
ply:TakeDamage(10, ply, ply)
ply:Notify("⚠️ ВНИМАНИЕ! Вы вне зоны ивента! Немедленно вернитесь!")
ply:EmitSound("buttons/button10.wav", 75, 100)
end
end
end
end
-- Event Control
function PLUGIN:StartEvent(ply)
self.active = true
self.start_time = CurTime()
self.participants = {}
self.eliminated_players = {}
self.initiator = ply
SetGlobalBool("events_system_active", true)
SetGlobalInt("events_system_start_time", CurTime())
local initiator_name = IsValid(ply) and ply:Nick() or "Администратор"
local start_message = "⚔️ БОЕВОЙ ИВЕНТ НАЧАЛСЯ! ⚔️\nОрганизатор: " .. initiator_name .. "\nКак участвовать: Используйте /EventJoin\nЦель: Выжить и победить!"
self:BroadcastMessage(start_message)
for _, player in ipairs(player.GetAll()) do
player:EmitSound(self.Config.sounds.event_start, 75, 100, 0.8)
end
self:UpdateHUD()
timer.Create("EventsSystem_ZoneCheck", 1, 0, function() self:CheckZoneBoundaries() end)
end
function PLUGIN:EndEvent(ply, cancelled)
if (!self.active) then return end
local stats_lines = {
"╔═══════════════════════════════════════╗",
"║ 📊 ФИНАЛЬНАЯ СТАТИСТИКА 📊 ║",
"╚═══════════════════════════════════════╝",
string.format("👥 Всего участников: %d", table.Count(self.participants) + table.Count(self.eliminated_players))
}
self.active = false
SetGlobalBool("events_system_active", false)
SetGlobalInt("events_system_start_time", 0)
local end_message = cancelled and "🛑 ИВЕНТ ОТМЕНЕН АДМИНИСТРАТОРОМ" or "🏆 ИВЕНТ ЗАВЕРШЕН 🏆"
self:BroadcastMessage(end_message)
for _, line in ipairs(stats_lines) do self:BroadcastMessage(line) end
for _, player in ipairs(player.GetAll()) do
player:EmitSound(self.Config.sounds.event_end, 75, 100, 0.6)
end
self.participants = {}
self.eliminated_players = {}
timer.Remove("EventsSystem_ZoneCheck")
self:UpdateHUD()
end
function PLUGIN:AddParticipant(ply)
if (!self.active or !IsValid(ply)) then return end
self.participants[ply:SteamID()] = {
join_time = CurTime(),
kills = 0,
nick = ply:Nick()
}
ply:Notify("✅ ВЫ ПРИСОЕДИНИЛИСЬ К ИВЕНТУ!")
ply:EmitSound(self.Config.sounds.player_join, 50, 100, 0.5)
end
function PLUGIN:AddAllPlayers(admin_ply)
for _, ply in ipairs(player.GetAll()) do
self:AddParticipant(ply)
end
self:BroadcastMessage("📢 Все игроки добавлены в ивент!")
end
-- Hooks
function PLUGIN:PlayerDeath(victim, inflictor, attacker)
if (!self.active) then return end
local steamid = victim:SteamID()
local data = self.participants[steamid]
if (!data) then return end
if (IsValid(attacker) and attacker:IsPlayer()) then
local attacker_data = self.participants[attacker:SteamID()]
if (attacker_data) then attacker_data.kills = attacker_data.kills + 1 end
end
self.eliminated_players[steamid] = data
self.participants[steamid] = nil
victim:Notify("💀 Вы выбыли из ивента!")
self:BroadcastMessage(string.format("💀 %s выбыл! Убийств: %d", victim:Nick(), data.kills))
timer.Simple(0.5, function() self:CheckEventEnd() end)
end
function PLUGIN:CheckEventEnd()
if (!self.active) then return end
if (table.Count(self.participants) <= 1) then
self:EndEvent()
end
end
function PLUGIN:PlayerDisconnected(ply)
if (!self.active) then return end
self.participants[ply:SteamID()] = nil
end
-- Messaging
function PLUGIN:SendMessage(ply, message)
if (!IsValid(ply)) then return end
net.Start("events_system_notification")
net.WriteString(message)
net.Send(ply)
end
function PLUGIN:BroadcastMessage(message)
for _, ply in ipairs(player.GetAll()) do
ply:ChatPrint(message)
end
end
function PLUGIN:UpdateHUD()
net.Start("events_system_hud_update")
net.WriteBool(self.active)
net.WriteInt(self.start_time, 32)
net.Broadcast()
end
-- Temporary Spawns Implementation
function PLUGIN:AddTempSpawn(factionID, specDef, pos, ang)
specDef = specDef or "default"
self.tempSpawns[factionID] = self.tempSpawns[factionID] or {}
self.tempSpawns[factionID][specDef] = self.tempSpawns[factionID][specDef] or {}
table.insert(self.tempSpawns[factionID][specDef], {pos = pos, ang = ang})
self:SaveTempSpawns()
end
function PLUGIN:RemoveTempSpawns(pos, radius)
local count = 0
for factionID, specs in pairs(self.tempSpawns) do
for specDef, points in pairs(specs) do
for k, v in pairs(points) do
if (v.pos:Distance(pos) <= radius) then
points[k] = nil
count = count + 1
end
end
end
end
if (count > 0) then self:SaveTempSpawns() end
return count
end
function PLUGIN:PostPlayerLoadout(client)
if (!self.active) then return end
local char = client:GetCharacter()
if (!char) then return end
local faction = ix.faction.indices[client:Team()]
if (!faction) then return end
local factionID = faction.uniqueID
local podr = char:GetPodr()
local factionTable = ix.faction.Get(client:Team())
local specDef = (factionTable and factionTable.Podr and factionTable.Podr[podr]) and factionTable.Podr[podr].spec_def or "default"
local points = (self.tempSpawns[factionID] and self.tempSpawns[factionID][specDef]) or (self.tempSpawns[factionID] and self.tempSpawns[factionID]["default"])
if (points and !table.IsEmpty(points)) then
local point = table.Random(points)
client:SetPos(point.pos)
client:SetEyeAngles(point.ang)
end
end

View File

@@ -0,0 +1,444 @@
-- Extended Spawnmenu - Legacy Addons (старые аддоны и загрузки)
local PLUGIN = PLUGIN
language.Add("spawnmenu.category.addonslegacy", "Addons - Legacy")
language.Add("spawnmenu.category.downloads", "Downloads")
local function AddRecursive( pnl, folder )
local files, folders = file.Find( folder .. "*", "MOD" )
for k, v in pairs( files or {} ) do
if ( !string.EndsWith( v, ".mdl" ) ) then continue end
local cp = spawnmenu.GetContentType( "model" )
if ( cp ) then
local mdl = folder .. v
mdl = string.sub( mdl, string.find( mdl, "models/" ), string.len( mdl ) )
mdl = string.gsub( mdl, "models/models/", "models/" )
cp( pnl, { model = mdl } )
end
end
for k, v in pairs( folders or {} ) do AddRecursive( pnl, folder .. v .. "/" ) end
end
local function CountRecursive( folder )
local files, folders = file.Find( folder .. "*", "MOD" )
local val = 0
for k, v in pairs( files or {} ) do if ( string.EndsWith( v, ".mdl" ) ) then val = val + 1 end end
for k, v in pairs( folders or {} ) do val = val + CountRecursive( folder .. v .. "/" ) end
return val
end
hook.Add( "PopulateContent", "LegacyAddonProps", function( pnlContent, tree, node )
if ( !IsValid( node ) or !IsValid( pnlContent ) ) then
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR LEGACY ADDONS!!!" )
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR LEGACY ADDONS!!!" )
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR LEGACY ADDONS!!!" )
return
end
local ViewPanel = vgui.Create( "ContentContainer", pnlContent )
ViewPanel:SetVisible( false )
local addons = {}
local _files, folders = file.Find( "addons/*", "MOD" )
for _, f in pairs( folders ) do
if ( !file.IsDir( "addons/" .. f .. "/models/", "MOD" ) ) then continue end
local count = CountRecursive( "addons/" .. f .. "/models/", "MOD" )
if ( count == 0 ) then continue end
table.insert( addons, {
name = f,
count = count,
path = "addons/" .. f .. "/models/"
} )
end
local LegacyAddons = node:AddNode( "#spawnmenu.category.addonslegacy", "icon16/folder_database.png" )
for _, f in SortedPairsByMemberValue( addons, "name" ) do
local models = LegacyAddons:AddNode( f.name .. " (" .. f.count .. ")", "icon16/bricks.png" )
models.DoClick = function()
ViewPanel:Clear( true )
AddRecursive( ViewPanel, f.path )
pnlContent:SwitchPanel( ViewPanel )
end
end
--[[ -------------------------- DOWNLOADS -------------------------- ]]
local fi, fo = file.Find( "download/models", "MOD" )
if ( !fi && !fo ) then return end
local Downloads = node:AddFolder( "#spawnmenu.category.downloads", "download/models", "MOD", false, false, "*.*" )
Downloads:SetIcon( "icon16/folder_database.png" )
Downloads.OnNodeSelected = function( self, selectedNode )
ViewPanel:Clear( true )
local path = selectedNode:GetFolder()
if ( !string.EndsWith( path, "/" ) && string.len( path ) > 1 ) then path = path .. "/" end
local path_mdl = string.sub( path, string.find( path, "/models/" ) + 1 )
for k, v in pairs( file.Find( path .. "/*.mdl", selectedNode:GetPathID() ) ) do
local cp = spawnmenu.GetContentType( "model" )
if ( cp ) then
cp( ViewPanel, { model = path_mdl .. "/" .. v } )
end
end
pnlContent:SwitchPanel( ViewPanel )
end
end )
--[[ -------------------------------------------------------------------------- The addon info -------------------------------------------------------------------------- ]]
concommand.Add( "extsm_addoninfo", function()
local frame = vgui.Create( "DFrame" )
frame:SetSize( ScrW() - 100, ScrH() - 100 )
frame:Center()
frame:MakePopup()
local sp = frame:Add( "DScrollPanel" )
sp:Dock( FILL )
sp:Add( "rb655_addonInfo" )
end )
hook.Add( "AddToolMenuCategories", "LegacyAddonPropsInfoCategory", function()
spawnmenu.AddToolCategory( "Utilities", "Robotboy655", "#Robotboy655" )
end )
hook.Add( "PopulateToolMenu", "LegacyAddonPropsInfoThing", function()
spawnmenu.AddToolMenuOption( "Utilities", "Robotboy655", "LegacyInfoPanel", "Addon Information", "", "", function( panel )
panel:ClearControls()
panel:Button( "Open addon data window", "extsm_addoninfo" )
end )
end )
----------------------------------
function ScreenScaleH( size )
return size * ( ScrH() / 480.0 )
end
surface.CreateFont( "AddonInfo_Header", {
font = "Helvetica",
size = ScreenScaleH( 24 ),
weight = 1000
} )
surface.CreateFont( "AddonInfo_Text", {
font = "Helvetica",
size = ScreenScaleH( 9 ),
weight = 1000
} )
surface.CreateFont( "AddonInfo_Small", {
font = "Helvetica",
size = ScreenScaleH( 8 )
} )
local function GetWorkshopLeftovers()
local subscriptions = {}
for id, t in pairs( engine.GetAddons() ) do
subscriptions[ tonumber( t.wsid ) ] = true
end
local t = {}
for id, fileh in pairs( file.Find( "addons/*.gma", "MOD" ) ) do
local a = string.StripExtension( fileh )
a = string.Explode( "_", a )
a = tonumber( a[ #a ] )
if ( !subscriptions[ a ] ) then
table.insert( t, fileh )
end
end
return t
end
local function GetSize( b )
b = b / 1000
if ( b < 1000 ) then
return math.floor( b * 10 ) / 10 .. " KB"
end
b = b / 1000
if ( b < 1000 ) then
return math.floor( b * 10 ) / 10 .. " MB"
end
b = b / 1000
return math.floor( b * 10 ) / 10 .. " GB"
end
local function DrawText( txt, font, x, y, clr )
draw.SimpleText( txt, font, x, y, clr )
surface.SetFont( font )
return surface.GetTextSize( txt )
end
local PANEL = {}
function PANEL:Init()
self.Computed = false
end
function PANEL:Compute()
self.WorkshopSize = 0
for id, fle in pairs( file.Find( "addons/*.gma", "MOD" ) ) do
self.WorkshopSize = self.WorkshopSize + ( file.Size( "addons/" .. fle, "MOD" ) or 0 )
end
self.WorkshopWaste = 0
self.WorkshopWasteFiles = {}
for id, fle in pairs( GetWorkshopLeftovers() ) do
self.WorkshopWaste = self.WorkshopWaste + ( file.Size( "addons/" .. fle, "MOD" ) or 0 )
table.insert( self.WorkshopWasteFiles, { "addons/" .. fle, ( file.Size( "addons/" .. fle, "MOD" ) or 0 ) } )
end
-- -------------------------------------------
local _files, folders = file.Find( "addons/*", "MOD" )
self.LegacyAddons = {}
for k, v in pairs( folders or {} ) do
self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed"
if ( file.IsDir( "addons/" .. v .. "/models/", "MOD" ) ) then
self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed (Has Models)"
end
local _fi, fo = file.Find( "addons/" .. v .. "/*", "MOD" )
if ( table.Count( fo or {} ) < 1 ) then
self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed (Empty)"
end
if ( !file.IsDir( "addons/" .. v .. "/models/", "MOD" ) && !file.IsDir( "addons/" .. v .. "/materials/", "MOD" ) && !file.IsDir( "addons/" .. v .. "/lua/", "MOD" ) && !file.IsDir( "addons/" .. v .. "/sound/", "MOD" ) ) then
self.LegacyAddons[ "addons/" .. v .. "/" ] = "Installed Incorrectly!"
end
end
-- -------------------------------------------
local luaFiles = file.Find( "cache/lua/*", "MOD" ) -- Too many files to count actual size!
self.LuaCacheSize = #luaFiles * 1400
self.LuaCacheFiles = #luaFiles
local wsFiles = file.Find( "cache/workshop/*", "MOD" )
self.WSCacheSize = 0
for id, fle in pairs( wsFiles ) do
self.WSCacheSize = self.WSCacheSize + ( file.Size( "cache/workshop/" .. fle, "MOD" ) or 0 )
end
self.WSCacheFiles = #wsFiles
self.Computed = true
end
function PANEL:Paint( w, h )
if ( !self.Computed ) then
self:Compute()
end
local txtW = self:GetParent():GetWide()
local txtH = 0
-- -----------------------
local tW, tH = DrawText( "Cache Sizes", "AddonInfo_Header", 0, txtH, color_white )
txtH = txtH + tH
local localH = 0
local localW = 0
-- -----------------------
tW, tH = DrawText( "~" .. GetSize( self.LuaCacheSize or 0 ) .. " (" .. self.LuaCacheFiles .. " files)", "AddonInfo_Small", 0, txtH + localH, Color( 220, 220, 220 ) )
localH = localH + tH
localW = math.max( localW, tW )
tW, tH = DrawText( "~" .. GetSize( self.WSCacheSize or 0 ) .. " (" .. self.WSCacheFiles .. " files)", "AddonInfo_Small", 0, txtH + localH, Color( 220, 220, 220 ) )
localH = localH + tH
localW = math.max( localW, tW )
-- -----------------------
localW = localW + 25
tW, tH = DrawText( "Server Lua cache", "AddonInfo_Small", localW, txtH, color_white )
txtH = txtH + tH
tW, tH = DrawText( "Workshop download cache", "AddonInfo_Small", localW, txtH, color_white )
txtH = txtH + tH
-- -------------------------------------------
txtH = txtH + ScreenScaleH( 8 )
tW, tH = DrawText( "Workshop Subscriptions", "AddonInfo_Header", 0, txtH, color_white )
txtH = txtH + tH
-- -------------------------------------------
tW, tH = DrawText( "Used Size: ", "AddonInfo_Text", 0, txtH, color_white )
local maxW = tW
txtH = txtH + tH
tW, tH = DrawText( "Wasted Space: ", "AddonInfo_Text", 0, txtH, color_white )
maxW = math.max( maxW, tW )
txtH = txtH + tH
tW, tH = DrawText( "Total Size: ", "AddonInfo_Text", 0, txtH, color_white )
maxW = math.max( maxW, tW )
txtH = txtH - tH * 2
-- -------------------------------------------
tW, tH = DrawText( GetSize( ( self.WorkshopSize - self.WorkshopWaste ) or 0 ), "AddonInfo_Text", maxW, txtH, Color( 220, 220, 220 ) )
txtH = txtH + tH
tW, tH = DrawText( GetSize( self.WorkshopWaste or 0 ), "AddonInfo_Text", maxW, txtH, Color( 220, 220, 220 ) )
txtH = txtH + tH
tW, tH = DrawText( GetSize( self.WorkshopSize or 0 ), "AddonInfo_Text", maxW, txtH, Color( 220, 220, 220 ) )
txtH = txtH + tH * 2
-- -------------------------------------------
tW, tH = DrawText( "Files that aren't used: ( Safe to delete )", "AddonInfo_Text", 0, txtH, color_white )
txtH = txtH + tH
localH = 0
localW = 0
for id, t in pairs( self.WorkshopWasteFiles or {} ) do
tW, tH = DrawText( GetSize( t[ 2 ] ) .. " ", "AddonInfo_Small", 0, txtH + localH, Color( 220, 220, 220 ) )
localH = localH + tH
localW = math.max( localW, tW )
end
for id, t in pairs( self.WorkshopWasteFiles or {} ) do
tW, tH = DrawText( t[ 1 ], "AddonInfo_Small", localW, txtH, color_white )
txtH = txtH + tH
end
-- -------------------------------------------
tW, tH = DrawText( "Legacy Addons", "AddonInfo_Header", 0, txtH + ScreenScaleH( 8 ), color_white )
txtH = txtH + tH + ScreenScaleH( 8 )
-- -------------------------------------------
tW, tH = DrawText( "Legacy Addons with models:", "AddonInfo_Text", 0, txtH, color_white )
txtH = txtH + tH
if ( table.Count( self.LegacyAddons or {} ) > 0 ) then
local maxNameW = 0
local oldH = txtH
for path, status in pairs( self.LegacyAddons or {} ) do
tW, tH = DrawText( path, "AddonInfo_Small", 0, txtH, color_white )
maxNameW = math.max( maxNameW, tW )
txtH = txtH + tH
end
maxNameW = maxNameW + 25
txtH = oldH
for path, status in pairs( self.LegacyAddons or {} ) do
tW, tH = DrawText( status, "AddonInfo_Small", maxNameW, txtH, Color( 220, 220, 220 ) )
txtH = txtH + tH
end
else
tW, tH = DrawText( "None.", "AddonInfo_Small", 0, txtH, color_white )
txtH = txtH + tH
end
if ( !system.IsWindows() ) then
txtH = txtH + tH
tW, tH = DrawText( "OSX AND LINUX USERS BEWARE:", "AddonInfo_Text", 0, txtH, color_white )
txtH = txtH + tH
tW, tH = DrawText( "MAKE SURE ALL FILE AND FOLDER NAMES", "AddonInfo_Text", 0, txtH, color_white )
txtH = txtH + tH
tW, tH = DrawText( "IN ALL ADDONS ARE LOWERCASE ONLY", "AddonInfo_Text", 0, txtH, color_white )
txtH = txtH + tH
tW, tH = DrawText( "INCLUDING ALL SUB FOLDERS", "AddonInfo_Text", 0, txtH, color_white )
txtH = txtH + tH
end
txtH = txtH + tH
-- -------------------------------------------
self:SetSize( txtW, txtH )
end
vgui.Register( "rb655_addonInfo", PANEL, "Panel" )
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
-- I spent too much time on this than I care to admit
hook.Add( "PopulatePropMenu", "rb655_LoadLegacySpawnlists", function()
local sid = 0 --table.Count( spawnmenu.GetPropTable() )
--local added = false
for id, spawnlist in pairs( file.Find( "settings/spawnlist/*.txt", "MOD" ) ) do
local content = file.Read( "settings/spawnlist/" .. spawnlist, "MOD" )
if ( !content ) then continue end
--[[local is = string.find( content, "TableToKeyValues" )
if ( is != nil ) then continue end
for id, t in pairs( spawnmenu.GetPropTable() ) do -- This somehow freezes the game when opening Q menu => FUCK THIS SHIT
if ( t.name == "Legacy Spawnlists" ) then
added = true
sid = t.id
end
end
if ( !added ) then
spawnmenu.AddPropCategory( "rb655_legacy_spawnlists", "Legacy Spawnlists", {}, "icon16/folder.png", sid, 0 )
added = true
end]]
content = util.KeyValuesToTable( content )
if ( !content.entries or content.contents ) then continue end
local contents = {}
for eid, entry in pairs( content.entries ) do
if ( type( entry ) == "table" ) then entry = entry.model end
table.insert( contents, { type = "model", model = entry } )
end
if ( !content.information ) then content.information = { name = spawnlist } end
spawnmenu.AddPropCategory( "settings/spawnlist/" .. spawnlist, content.information.name, contents, "icon16/page.png", sid + id, sid )
end
end )

View File

@@ -0,0 +1,790 @@
-- Extended Spawnmenu - Клиентская часть (звуки и материалы)
local PLUGIN = PLUGIN
local cl_addTabs = CreateClientConVar("rb655_create_sm_tabs", "0", true, true)
--[[local function removeOldTabls()
for k, v in pairs( g_SpawnMenu.CreateMenu.Items ) do
if (v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.npcs" ) or
v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.entities" ) or
v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.weapons" ) or
v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.vehicles" ) or
v.Tab:GetText() == language.GetPhrase( "spawnmenu.category.postprocess" ) ) then
g_SpawnMenu.CreateMenu:CloseTab( v.Tab, true )
end
end
end
hook.Add( "PopulateContent", "rb655_extended_spawnmenu", function( pnlContent, tree, node )
removeOldTabls() removeOldTabls() removeOldTabls() -- For some reason it doesn't work with only one call
end )]]
local function getGameList()
local games = engine.GetGames()
table.insert( games, {
title = "All",
folder = "GAME",
icon = "all",
mounted = true
} )
table.insert( games, {
title = "Garry's Mod",
folder = "garrysmod",
mounted = true
} )
return games
end
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
local theSound = nil
function rb655_playsound( snd )
if ( theSound ) then theSound:Stop() end
theSound = CreateSound( LocalPlayer(), snd )
theSound:Play()
end
net.Receive( "rb655_playsound", function( len )
rb655_playsound( net.ReadString() )
end )
spawnmenu.AddContentType( "sound", function( container, obj )
if ( !obj.nicename ) then return end
if ( !obj.spawnname ) then return end
local icon = vgui.Create( "ContentIcon", container )
icon:SetContentType( "sound" )
icon:SetSpawnName( obj.spawnname )
icon:SetName( obj.nicename )
icon:SetMaterial( "icon16/sound.png" )
icon.DoClick = function()
rb655_playsound( obj.spawnname )
end
icon.OpenMenu = function( icn )
local menu = DermaMenu()
menu:AddOption( "#spawnmenu.menu.copy", function() SetClipboardText( obj.spawnname ) end ):SetIcon( "icon16/page_copy.png" )
menu:AddOption( "Play on all clients", function() RunConsoleCommand( "rb655_playsound_all", obj.spawnname ) end ):SetIcon( "icon16/sound.png" )
menu:AddOption( "Stop all sounds", function() RunConsoleCommand( "stopsound" ) end ):SetIcon( "icon16/sound_mute.png" )
menu:AddSpacer()
menu:AddOption( "#spawnmenu.menu.delete", function() icn:Remove() hook.Run( "SpawnlistContentChanged", icn ) end ):SetIcon( "icon16/bin_closed.png" )
menu:Open()
end
if ( IsValid( container ) ) then
container:Add( icon )
end
return icon
end )
local function OnSndNodeSelected( self, node, name, path, pathid, icon, ViewPanel, pnlContent )
ViewPanel:Clear( true )
local Path = node:GetFolder()
local files = file.Find( Path .. "/*.wav", node:GetPathID() )
files = table.Add( files, file.Find( Path .. "/*.mp3", node:GetPathID() ) )
files = table.Add( files, file.Find( Path .. "/*.ogg", node:GetPathID() ) )
local offset = 0
local limit = 512
if ( node.offset ) then offset = node.offset or 0 end
for k, v in pairs( files ) do
if ( k > limit + offset ) then
if ( !node.Done ) then
offset = offset + limit
local mats = ( self.Parent or node ):AddNode( ( self.Text or node:GetText() ) .. " (" .. offset .. " - " .. offset + limit .. ")" )
mats:SetFolder( node:GetFolder() )
mats.Text = self.Text or node:GetText()
mats.Parent = self.Parent or node
mats:SetPathID( node:GetPathID() )
mats:SetIcon( node:GetIcon() )
mats.offset = offset
mats.OnNodeSelected = function( mats_self, mats_node )
OnSndNodeSelected( mats_self, mats_node, mats_self.Text, mats_node:GetFolder(), mats_node:GetPathID(), mats_node:GetIcon(), ViewPanel, pnlContent )
end
end
node.Done = true
break end
if ( k <= offset ) then continue end
local p = Path .. "/"
if ( string.StartWith( path, "addons/" ) or string.StartWith( path, "download/" ) ) then
p = string.sub( p, string.find( p, "/sound/" ) + 1 )
end
p = string.sub( p .. v, 7 )
spawnmenu.CreateContentIcon( "sound", ViewPanel, { spawnname = p, nicename = string.Trim( v ) } )
end
pnlContent:SwitchPanel( ViewPanel )
end
local function AddBrowseContentSnd( node, name, icon, path, pathid )
local ViewPanel = node.ViewPanel
local pnlContent = node.pnlContent
if ( !string.EndsWith( path, "/" ) && string.len( path ) > 1 ) then path = path .. "/" end
local fi, fo = file.Find( path .. "sound", pathid )
if ( !fo && !fi ) then return end
local sounds = node:AddFolder( name, path .. "sound", pathid, false, false, "*.*" )
sounds:SetIcon( icon )
sounds.OnNodeSelected = function( self, node_sel )
OnSndNodeSelected( self, node_sel, name, path, pathid, icon, ViewPanel, pnlContent )
end
end
language.Add( "spawnmenu.category.browsesounds", "Browse Sounds" )
local function RefreshAddonSounds( browseAddonSounds )
for _, addon in SortedPairsByMemberValue( engine.GetAddons(), "title" ) do
if ( !addon.downloaded ) then continue end
if ( !addon.mounted ) then continue end
if ( !table.HasValue( select( 2, file.Find( "*", addon.title ) ), "sound" ) ) then continue end
AddBrowseContentSnd( browseAddonSounds, addon.title, "icon16/bricks.png", "", addon.title )
end
end
local function RefreshGameSounds( browseGameSounds )
local games = getGameList()
for _, game in SortedPairsByMemberValue( games, "title" ) do
if ( !game.mounted ) then continue end
AddBrowseContentSnd( browseGameSounds, game.title, "games/16/" .. ( game.icon or game.folder ) .. ".png", "", game.folder )
end
end
local browseGameSounds
local browseAddonSounds
hook.Add( "PopulateContent", "SpawnmenuLoadSomeSounds", function( pnlContent, tree, browseNode ) timer.Simple( 0.5, function()
if ( !IsValid( tree ) or !IsValid( pnlContent ) ) then
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR SOUNDS !!!" )
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR SOUNDS !!!" )
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR SOUNDS !!!" )
return
end
local ViewPanel = vgui.Create( "ContentContainer", pnlContent )
ViewPanel:SetVisible( false )
local browseSounds = tree:AddNode( "#spawnmenu.category.browsesounds", "icon16/sound.png" )
browseSounds.ViewPanel = ViewPanel
browseSounds.pnlContent = pnlContent
--[[ --------------------------------------------------------------------------------------- ]]
browseAddonSounds = browseSounds:AddNode( "#spawnmenu.category.addons", "icon16/folder_database.png" )
browseAddonSounds.ViewPanel = ViewPanel
browseAddonSounds.pnlContent = pnlContent
RefreshAddonSounds( browseAddonSounds )
--[[ --------------------------------------------------------------------------------------- ]]
local addon_sounds = {}
local _, snd_folders = file.Find( "addons/*", "MOD" )
for _, addon in SortedPairs( snd_folders ) do
if ( !file.IsDir( "addons/" .. addon .. "/sound/", "MOD" ) ) then continue end
table.insert( addon_sounds, addon )
end
local browseLegacySounds = browseSounds:AddNode( "#spawnmenu.category.addonslegacy", "icon16/folder_database.png" )
browseLegacySounds.ViewPanel = ViewPanel
browseLegacySounds.pnlContent = pnlContent
for _, addon in SortedPairsByValue( addon_sounds ) do
AddBrowseContentSnd( browseLegacySounds, addon, "icon16/bricks.png", "addons/" .. addon .. "/", "MOD" )
end
--[[ --------------------------------------------------------------------------------------- ]]
AddBrowseContentSnd( browseSounds, "#spawnmenu.category.downloads", "icon16/folder_database.png", "download/", "MOD" )
--[[ --------------------------------------------------------------------------------------- ]]
browseGameSounds = browseSounds:AddNode( "#spawnmenu.category.games", "icon16/folder_database.png" )
browseGameSounds.ViewPanel = ViewPanel
browseGameSounds.pnlContent = pnlContent
RefreshGameSounds( browseGameSounds )
end ) end )
hook.Add( "GameContentChanged", "ES_RefreshSpawnmenuSounds", function()
if ( IsValid( browseAddonSounds ) ) then
-- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones?
browseAddonSounds:Clear()
browseAddonSounds.ViewPanel:Clear( true )
RefreshAddonSounds( browseAddonSounds )
end
if ( IsValid( browseGameSounds ) ) then
-- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones?
browseGameSounds:Clear()
browseGameSounds.ViewPanel:Clear( true )
RefreshGameSounds( browseGameSounds )
end
end )
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
local function IsMaterialUsableOnEntities( matPath )
-- A png file? No thanks
if ( string.GetExtensionFromFilename( matPath ) ) then return false end
local mat = Material( matPath )
if ( !string.find( mat:GetShader(), "LightmappedGeneric" )
&& !string.find( mat:GetShader(), "WorldVertexTransition" )
&& !string.find( mat:GetShader(), "Spritecard" )
&& !string.find( mat:GetShader(), "Water" )
&& !string.find( mat:GetShader(), "Cable" )
--&& !string.find( mat:GetShader(), "UnlitGeneric" )
&& !string.find( mat:GetShader(), "Refract" ) ) then
return true
end
return false
end
local DisplayedWarning = false
local function DisplayOneTimeWarning()
if ( DisplayedWarning ) then return end
DisplayedWarning = true
Derma_Message( "Please note that not all materials are usable on entities, such as map textures, etc.\nYou can still try though!", "Warning", "OK" )
end
spawnmenu.AddContentType( "material", function( container, obj )
if ( !obj.nicename ) then return end
if ( !obj.spawnname ) then return end
local icon = vgui.Create( "ContentIcon", container )
icon:SetContentType( "material" )
icon:SetSpawnName( obj.spawnname )
icon:SetName( obj.nicename )
if ( string.GetExtensionFromFilename( obj.spawnname ) == "png" ) then
icon:SetMaterial( obj.spawnname )
else
icon.Image:SetImage( obj.spawnname )
end
icon.DoClick = function()
if ( !IsMaterialUsableOnEntities( obj.spawnname ) ) then DisplayOneTimeWarning() end
RunConsoleCommand( "material_override", obj.spawnname )
spawnmenu.ActivateTool( "material" )
surface.PlaySound( "garrysmod/ui_click.wav" )
end
icon.OpenMenu = function( icn )
local menu = DermaMenu()
menu:AddOption( "#spawnmenu.menu.copy", function() SetClipboardText( obj.spawnname ) end ):SetIcon( "icon16/page_copy.png" )
local str = "Use with Material Tool"
if ( !IsMaterialUsableOnEntities( obj.spawnname ) ) then
str = "Try to use with Material Tool (Probably won't work)"
end
menu:AddOption( str, function()
RunConsoleCommand( "material_override", obj.spawnname )
spawnmenu.ActivateTool( "material" )
end ):SetIcon( "icon16/pencil.png" )
menu:AddSpacer()
menu:AddOption( "#spawnmenu.menu.delete", function() icn:Remove() hook.Run( "SpawnlistContentChanged", icn ) end ):SetIcon( "icon16/bin_closed.png" )
menu:Open()
end
if ( IsValid( container ) ) then
container:Add( icon )
end
return icon
end )
local function OnMatNodeSelected( self, node, name, path, pathid, icon, ViewPanel, pnlContent )
ViewPanel:Clear( true )
local Path = node:GetFolder()
local mat_files = file.Find( Path .. "/*.vmt", node:GetPathID() )
mat_files = table.Add( mat_files, file.Find( Path .. "/*.png", node:GetPathID() ) )
local offset = 0
local limit = 512
if ( node.offset ) then offset = node.offset or 0 end
for k, v in pairs( mat_files ) do
if ( k > limit + offset ) then
if ( !node.Done ) then
offset = offset + limit
local mats = ( self.Parent or node ):AddNode( ( self.Text or node:GetText() ) .. " (" .. offset .. " - " .. offset + limit .. ")" )
mats:SetFolder( node:GetFolder() )
mats.Text = self.Text or node:GetText()
mats.Parent = self.Parent or node
mats:SetPathID( node:GetPathID() )
mats:SetIcon( node:GetIcon() )
mats.offset = offset
mats.OnNodeSelected = function( self_mats, node_sel )
OnMatNodeSelected( self_mats, node_sel, self_mats.Text, node_sel:GetFolder(), node_sel:GetPathID(), node_sel:GetIcon(), ViewPanel, pnlContent )
end
end
node.Done = true
break end
if ( k <= offset ) then continue end
local p = Path .. "/"
if ( string.StartWith( path, "addons/" ) or string.StartWith( path, "download/" ) ) then
p = string.sub( p, string.find( p, "/materials/" ) + 1 )
end
p = string.sub( p .. v, 11 )
if ( string.GetExtensionFromFilename( p ) == "vmt" ) then
p = string.StripExtension( p )
v = string.StripExtension( v )
end
if ( Material( p ):GetShader() == "Spritecard" ) then continue end
spawnmenu.CreateContentIcon( "material", ViewPanel, { spawnname = p, nicename = v } )
end
pnlContent:SwitchPanel( ViewPanel )
end
local function AddBrowseContentMaterial( node, name, icon, path, pathid )
local ViewPanel = node.ViewPanel
local pnlContent = node.pnlContent
if ( !string.EndsWith( path, "/" ) && string.len( path ) > 1 ) then path = path .. "/" end
local fi, fo = file.Find( path .. "materials", pathid )
if ( !fi && !fo ) then return end
local materials = node:AddFolder( name, path .. "materials", pathid, false, false, "*.*" )
materials:SetIcon( icon )
materials.OnNodeSelected = function( self, node_sel )
OnMatNodeSelected( self, node_sel, name, path, pathid, icon, ViewPanel, pnlContent )
end
end
language.Add( "spawnmenu.category.browsematerials", "Browse Materials" )
local function RefreshAddonMaterials( node )
for _, addon in SortedPairsByMemberValue( engine.GetAddons(), "title" ) do
if ( !addon.downloaded ) then continue end
if ( !addon.mounted ) then continue end
if ( !table.HasValue( select( 2, file.Find( "*", addon.title ) ), "materials" ) ) then continue end
AddBrowseContentMaterial( node, addon.title, "icon16/bricks.png", "", addon.title )
end
end
local function RefreshGameMaterials( node )
local games = getGameList()
for _, game in SortedPairsByMemberValue( games, "title" ) do
if ( !game.mounted ) then continue end
AddBrowseContentMaterial( node, game.title, "games/16/" .. ( game.icon or game.folder ) .. ".png", "", game.folder )
end
end
local browseAddonMaterials
local browseGameMaterials
hook.Add( "PopulateContent", "SpawnmenuLoadSomeMaterials", function( pnlContent, tree, browseNode ) timer.Simple( 0.5, function()
if ( !IsValid( tree ) or !IsValid( pnlContent ) ) then
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR MATERIALS!!!" )
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR MATERIALS!!!" )
print( "!!! Extended Spawnmenu: FAILED TO INITALIZE PopulateContent HOOK FOR MATERIALS!!!" )
return
end
local ViewPanel = vgui.Create( "ContentContainer", pnlContent )
ViewPanel:SetVisible( false )
local browseMaterials = tree:AddNode( "#spawnmenu.category.browsematerials", "icon16/picture_empty.png" )
browseMaterials.ViewPanel = ViewPanel
browseMaterials.pnlContent = pnlContent
--[[ --------------------------------------------------------------------------------------- ]]
browseAddonMaterials = browseMaterials:AddNode( "#spawnmenu.category.addons", "icon16/folder_database.png" )
browseAddonMaterials.ViewPanel = ViewPanel
browseAddonMaterials.pnlContent = pnlContent
RefreshAddonMaterials( browseAddonMaterials )
--[[ --------------------------------------------------------------------------------------- ]]
local addon_mats = {}
local _, mat_folders = file.Find( "addons/*", "MOD" )
for _, addon in SortedPairs( mat_folders ) do
if ( !file.IsDir( "addons/" .. addon .. "/materials/", "MOD" ) ) then continue end
table.insert( addon_mats, addon )
end
local browseLegacyMaterials = browseMaterials:AddNode( "#spawnmenu.category.addonslegacy", "icon16/folder_database.png" )
browseLegacyMaterials.ViewPanel = ViewPanel
browseLegacyMaterials.pnlContent = pnlContent
for _, addon in SortedPairsByValue( addon_mats ) do
AddBrowseContentMaterial( browseLegacyMaterials, addon, "icon16/bricks.png", "addons/" .. addon .. "/", "MOD" )
end
--[[ --------------------------------------------------------------------------------------- ]]
AddBrowseContentMaterial( browseMaterials, "#spawnmenu.category.downloads", "icon16/folder_database.png", "download/", "MOD" )
--[[ --------------------------------------------------------------------------------------- ]]
browseGameMaterials = browseMaterials:AddNode( "#spawnmenu.category.games", "icon16/folder_database.png" )
browseGameMaterials.ViewPanel = ViewPanel
browseGameMaterials.pnlContent = pnlContent
RefreshGameMaterials( browseGameMaterials )
end ) end )
hook.Add( "GameContentChanged", "ES_RefreshSpawnmenuMaterials", function()
if ( IsValid( browseAddonMaterials ) ) then
-- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones?
browseAddonMaterials:Clear()
browseAddonMaterials.ViewPanel:Clear( true )
RefreshAddonMaterials( browseAddonMaterials )
end
if ( IsValid( browseGameMaterials ) ) then
-- TODO: Maybe be more advaced and do not delete => recreate all the nodes, only delete nodes for addons that were removed, add only the new ones?
browseGameMaterials:Clear()
browseGameMaterials.ViewPanel:Clear( true )
RefreshGameMaterials( browseGameMaterials )
end
end )
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
--[[ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ]]
hook.Add( "PopulateContent", "rb655_extended_spawnmenu_entities", function( pnlContent, tree, node )
if ( !cl_addTabs:GetBool() ) then return end
local node_w = tree:AddNode( "#spawnmenu.category.entities", "icon16/bricks.png" )
node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent )
node_w.PropPanel:SetVisible( false )
function node_w:DoClick()
pnlContent:SwitchPanel( self.PropPanel )
end
local Categorised = {}
local SpawnableEntities = list.Get( "SpawnableEntities" )
if ( SpawnableEntities ) then
for k, v in pairs( SpawnableEntities ) do
v.Category = v.Category or "Other"
Categorised[ v.Category ] = Categorised[ v.Category ] or {}
table.insert( Categorised[ v.Category ], v )
end
end
for CategoryName, v in SortedPairs( Categorised ) do
local node_new = node_w:AddNode( CategoryName, "icon16/bricks.png" )
local CatPropPanel = vgui.Create( "ContentContainer", pnlContent )
CatPropPanel:SetVisible( false )
local Header = vgui.Create("ContentHeader", node_w.PropPanel )
Header:SetText( CategoryName )
node_w.PropPanel:Add( Header )
for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do
local t = {
nicename = ent.PrintName or ent.ClassName,
spawnname = ent.ClassName,
material = "entities/" .. ent.ClassName .. ".png",
admin = ent.AdminOnly
}
spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", CatPropPanel, t )
spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", node_w.PropPanel, t )
end
function node_new:DoClick()
pnlContent:SwitchPanel( CatPropPanel )
end
end
end )
hook.Add( "PopulateContent", "rb655_extended_spawnmenu_post_processing", function( pnlContent, tree, node )
if ( !cl_addTabs:GetBool() ) then return end
local node_w = tree:AddNode( "#spawnmenu.category.postprocess", "icon16/picture.png" )
node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent )
node_w.PropPanel:SetVisible( false )
function node_w:DoClick()
pnlContent:SwitchPanel( self.PropPanel )
end
-- Get the table
local Categorised = {}
local PostProcess = list.Get( "PostProcess" )
if ( PostProcess ) then
for k, v in pairs( PostProcess ) do
v.category = v.category or "Other"
v.name = k
Categorised[ v.category ] = Categorised[ v.category ] or {}
table.insert( Categorised[ v.category ], v )
end
end
-- Put table into panels
for CategoryName, v in SortedPairs( Categorised ) do
local node_new = node_w:AddNode( CategoryName, "icon16/picture.png" )
local CatPropPanel = vgui.Create( "ContentContainer", pnlContent )
CatPropPanel:SetVisible( false )
local Header = vgui.Create( "ContentHeader", node_w.PropPanel )
Header:SetText( CategoryName )
node_w.PropPanel:Add( Header )
for k, pp in SortedPairsByMemberValue( v, "PrintName" ) do
if ( pp.func ) then pp.func( CatPropPanel ) pp.func( node_w.PropPanel ) continue end
local t = {
name = pp.name,
icon = pp.icon
}
spawnmenu.CreateContentIcon( "postprocess", CatPropPanel, t )
spawnmenu.CreateContentIcon( "postprocess", node_w.PropPanel, t )
end
function node_new:DoClick()
pnlContent:SwitchPanel( CatPropPanel )
end
end
end )
hook.Add( "PopulateContent", "rb655_extended_spawnmenu_npcs", function( pnlContent, tree, node )
if ( !cl_addTabs:GetBool() ) then return end
local node_w = tree:AddNode( "#spawnmenu.category.npcs", "icon16/monkey.png" )
node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent )
node_w.PropPanel:SetVisible( false )
function node_w:DoClick()
pnlContent:SwitchPanel( self.PropPanel )
end
local NPCList = list.Get( "NPC" )
local Categories = {}
for k, v in pairs( NPCList ) do
local Category = v.Category or "Other"
local Tab = Categories[ Category ] or {}
Tab[ k ] = v
Categories[ Category ] = Tab
end
for CategoryName, v in SortedPairs( Categories ) do
local node_new = node_w:AddNode( CategoryName, "icon16/monkey.png" )
local CatPropPanel = vgui.Create( "ContentContainer", pnlContent )
CatPropPanel:SetVisible( false )
local Header = vgui.Create("ContentHeader", node_w.PropPanel )
Header:SetText( CategoryName )
node_w.PropPanel:Add( Header )
for name, ent in SortedPairsByMemberValue( v, "Name" ) do
local t = {
nicename = ent.Name or name,
spawnname = name,
material = "entities/" .. name .. ".png",
weapon = ent.Weapons,
admin = ent.AdminOnly
}
spawnmenu.CreateContentIcon( "npc", CatPropPanel, t )
spawnmenu.CreateContentIcon( "npc", node_w.PropPanel, t )
end
function node_new:DoClick()
pnlContent:SwitchPanel( CatPropPanel )
end
end
end )
hook.Add( "PopulateContent", "rb655_extended_spawnmenu_vehicles", function( pnlContent, tree, node )
if ( !cl_addTabs:GetBool() ) then return end
local node_w = tree:AddNode( "#spawnmenu.category.vehicles", "icon16/car.png" )
node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent )
node_w.PropPanel:SetVisible( false )
function node_w:DoClick()
pnlContent:SwitchPanel( self.PropPanel )
end
local Categorised = {}
local Vehicles = list.Get( "Vehicles" )
if ( Vehicles ) then
for k, v in pairs( Vehicles ) do
v.Category = v.Category or "Other"
Categorised[ v.Category ] = Categorised[ v.Category ] or {}
v.ClassName = k
v.PrintName = v.Name
v.ScriptedEntityType = "vehicle"
table.insert( Categorised[ v.Category ], v )
end
end
for CategoryName, v in SortedPairs( Categorised ) do
local node_new = node_w:AddNode( CategoryName, "icon16/car.png" )
local CatPropPanel = vgui.Create( "ContentContainer", pnlContent )
CatPropPanel:SetVisible( false )
local Header = vgui.Create("ContentHeader", node_w.PropPanel )
Header:SetText( CategoryName )
node_w.PropPanel:Add( Header )
for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do
local t = {
nicename = ent.PrintName or ent.ClassName,
spawnname = ent.ClassName,
material = "entities/" .. ent.ClassName .. ".png",
admin = ent.AdminOnly
}
spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", node_w.PropPanel, t )
spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", CatPropPanel, t )
end
function node_new:DoClick()
pnlContent:SwitchPanel( CatPropPanel )
end
end
end )
hook.Add( "PopulateContent", "rb655_extended_spawnmenu_weapons", function( pnlContent, tree, node )
if ( !cl_addTabs:GetBool() ) then return end
local node_w = tree:AddNode( "#spawnmenu.category.weapons", "icon16/gun.png" )
node_w.PropPanel = vgui.Create( "ContentContainer", pnlContent )
node_w.PropPanel:SetVisible( false )
function node_w:DoClick()
pnlContent:SwitchPanel( self.PropPanel )
end
local Weapons = list.Get( "Weapon" )
local Categorised = {}
for k, weapon in pairs( Weapons ) do
if ( !weapon.Spawnable && !weapon.AdminSpawnable ) then continue end
Categorised[ weapon.Category ] = Categorised[ weapon.Category ] or {}
table.insert( Categorised[ weapon.Category ], weapon )
end
for CategoryName, v in SortedPairs( Categorised ) do
local node_new = node_w:AddNode( CategoryName, "icon16/gun.png" )
local CatPropPanel = vgui.Create( "ContentContainer", pnlContent )
CatPropPanel:SetVisible( false )
local Header = vgui.Create("ContentHeader", node_w.PropPanel )
Header:SetText( CategoryName )
node_w.PropPanel:Add( Header )
for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do
local t = {
nicename = ent.PrintName or ent.ClassName,
spawnname = ent.ClassName,
material = "entities/" .. ent.ClassName .. ".png",
admin = ent.AdminOnly
}
spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", CatPropPanel, t )
spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", node_w.PropPanel, t )
end
function node_new:DoClick()
pnlContent:SwitchPanel( CatPropPanel )
end
end
end )

View File

@@ -0,0 +1,39 @@
PLUGIN.name = "Extended Spawnmenu"
PLUGIN.author = "RB655 (Портирован для Helix)"
PLUGIN.description = "Расширенное спавн-меню с поддержкой звуков и legacy аддонов"
ix.util.Include("cl_spawnmenu.lua")
ix.util.Include("cl_legacy.lua")
-- Конфигурация
ix.config.Add("extSpawnmenuEnabled", true, "Включить расширенное спавн-меню", nil, {
category = "Extended Spawnmenu"
})
ix.config.Add("extSpawnmenuCreateTabs", false, "Создавать отдельные вкладки для категорий", nil, {
category = "Extended Spawnmenu"
})
-- Серверная часть
if SERVER then
util.AddNetworkString("rb655_playsound")
-- Команда для проигрывания звуков всем игрокам
concommand.Add("rb655_playsound_all", function(ply, cmd, args)
if not ply:IsSuperAdmin() or not args[1] or string.Trim(args[1]) == "" then return end
net.Start("rb655_playsound")
net.WriteString(args[1] or "")
net.Broadcast()
end)
end
-- Клиентская часть
if CLIENT then
-- ConVar для совместимости
CreateClientConVar("rb655_create_sm_tabs", "0", true, true)
end
function PLUGIN:Initialize()
print("[Extended Spawnmenu] Расширенное спавн-меню загружено!")
print("[Extended Spawnmenu] Добавлены: Browse Sounds, Materials, Legacy Addons")
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,253 @@
local PLUGIN = PLUGIN
PLUGIN.donateCatalog = PLUGIN.donateCatalog or {
{
id = "currency",
name = "Рубли",
tagline = "Пополнение игрового баланса",
accent = { r = 1, g = 104, b = 44 },
items = {
{
id = "money_1000",
title = "1.000 ₽",
price = 50,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах"
},
reward = {
type = "money",
amount = 1000
}
},
{
id = "money_5000",
title = "5.000 ₽",
price = 225,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах",
"Выгодно: +10% бонус"
},
tag = "ВЫГОДНО",
reward = {
type = "money",
amount = 5000
}
},
{
id = "money_10000",
title = "10.000 ₽",
price = 400,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах",
"Выгодно: +20% бонус"
},
tag = "ХИТ",
reward = {
type = "money",
amount = 10000
}
},
{
id = "money_25000",
title = "25.000 ₽",
price = 900,
currency = "IGS",
perks = {
"Пополнение игрового баланса",
"Мгновенное зачисление",
"Использование в магазинах",
"Максимально выгодно: +28% бонус"
},
tag = "ЛУЧШЕЕ",
reward = {
type = "money",
amount = 25000
}
}
}
},
{
id = "weapons",
name = "Оружие",
tagline = "Донатное оружие в ваш арсенал",
accent = { r = 1, g = 104, b = 44 },
items = {
{ id = "ak12", title = "AK-12", price1Month = 1200, price3Month = 3200, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "tacrp_ak_ak12", name = "AK-12" } },
{ id = "mdr", title = "Desert Tech MDR", price1Month = 1500, price3Month = 4000, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_mdr", name = "MDR" } },
{ id = "mp7a1", title = "MP7A1", price1Month = 1300, price3Month = 3500, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_mp7a1", name = "MP7A1" } },
{ id = "ai_axmc", title = "AI AXMC", price1Month = 2000, price3Month = 5500, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ai_axmc", name = "AI AXMC" } },
{ id = "t5000", title = "T-5000", price1Month = 1900, price3Month = 5200, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_t5000", name = "T-5000" } },
{ id = "saiga12k", title = "Сайга-12К", price1Month = 1400, price3Month = 3800, currency = "IGS", perks = { "Автоматический дробовик", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_saiga12k", name = "Сайга-12К" } },
{ id = "deagle", title = "Desert Eagle XIX", price1Month = 800, price3Month = 2200, currency = "IGS", perks = { "Мощный пистолет", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_deagle_xix", name = "Desert Eagle" } },
{ id = "apb", title = "АПБ", price1Month = 700, price3Month = 1900, currency = "IGS", perks = { "Бесшумный пистолет", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_apb", name = "АПБ" } },
{ id = "spear", title = "SPEAR", price1Month = 1600, price3Month = 4400, currency = "IGS", perks = { "Доступен в арсенале", "Кулдаун 10 минут", "Любая фракция" }, reward = { type = "weapon", weaponClass = "arc9_eft_spear", name = "SPEAR" } },
{ id = "sr25", title = "SR-25", price1Month = 1800, price3Month = 4900, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_sr25", name = "SR-25" } },
{ id = "dvl10", title = "ДВЛ-10", price1Month = 1900, price3Month = 5200, currency = "IGS", perks = { "Снайперская винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_dvl10", name = "ДВЛ-10" } },
{ id = "hultafors", title = "Dead Blow Hammer", price1Month = 300, price3Month = 800, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_hultafors", name = "Hammer" } },
{ id = "cultist", title = "Cultist Knife", price1Month = 400, price3Month = 1100, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_cultist", name = "Cultist Knife" } },
{ id = "akula", title = "Akula", price1Month = 350, price3Month = 950, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_akula", name = "Akula" } },
{ id = "crash", title = "Crash Axe", price1Month = 350, price3Month = 950, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_crash", name = "Crash Axe" } },
{ id = "kukri", title = "Kukri", price1Month = 400, price3Month = 1100, currency = "IGS", perks = { "Холодное оружие", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_melee_kukri", name = "Kukri" } },
{ id = "ak50", title = "AK-50", price1Month = 2200, price3Month = 6000, currency = "IGS", perks = { "Мощная штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, tag = "ХИТ", reward = { type = "weapon", weaponClass = "arc9_eft_ak50", name = "AK-50" } },
{ id = "vss", title = "ВСС Винторез", price1Month = 1500, price3Month = 4100, currency = "IGS", perks = { "Бесшумная винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_vss", name = "ВСС" } },
{ id = "mr43", title = "МР-43", price1Month = 1000, price3Month = 2700, currency = "IGS", perks = { "Дробовик", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_mr43", name = "МР-43" } },
{ id = "rpk16", title = "РПК-16", price1Month = 1700, price3Month = 4600, currency = "IGS", perks = { "Ручной пулемёт", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_rpk16", name = "РПК-16" } },
{ id = "rshg2", title = "РШГ-2", price1Month = 2500, price3Month = 6800, currency = "IGS", perks = { "Гранатомёт", "Доступен в арсенале", "Кулдаун 10 минут" }, tag = "РЕДКОЕ", reward = { type = "weapon", weaponClass = "arc9_eft_rshg2", name = "РШГ-2" } },
{ id = "ash12", title = "АШ-12", price1Month = 1800, price3Month = 4900, currency = "IGS", perks = { "Штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ash12", name = "АШ-12" } },
{ id = "rd704", title = "RD-704", price1Month = 1400, price3Month = 3800, currency = "IGS", perks = { "Штурмовая винтовка", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_rd704", name = "RD-704" } },
{ id = "ppsh41", title = "ППШ-41", price1Month = 900, price3Month = 2400, currency = "IGS", perks = { "Легендарный ПП", "Доступен в арсенале", "Кулдаун 10 минут" }, reward = { type = "weapon", weaponClass = "arc9_eft_ppsh41", name = "ППШ-41" } }
}
},
{
id = "other",
name = "Другое",
tagline = "Дополнительные возможности",
accent = { r = 1, g = 104, b = 44 },
items = {
{
id = "voice_chat",
title = "Говорилка",
price = 500,
currency = "IGS",
perks = {
"Разблокировка голосового чата",
"Радиус слышимости 200м",
"Без ограничений по времени",
"Мгновенная активация"
},
reward = {
type = "voice_chat",
successMessage = "Голосовой чат разблокирован!"
}
}
}
},
{
id = "privileges",
name = "Привелегии",
tagline = "VIP статусы и возможности",
accent = { r = 1, g = 104, b = 44 },
items = {
{
id = "vip",
title = "VIP",
price1Month = 300,
price3Month = 800,
currency = "IGS",
perks = {
"Цветной ник и иконка VIP в чате",
"Приоритет при входе на сервер",
"Скидка 10% в арсенале",
"Доступ к VIP-командам"
},
reward = {
type = "privilege",
tier = "vip"
}
},
{
id = "vip_plus",
title = "VIP+",
price1Month = 500,
price3Month = 1400,
currency = "IGS",
perks = {
"Все преимущества VIP",
"Увеличенная зарплата (+15%)",
"Скидка 15% в арсенале",
"Доступ к уникальным моделям",
"Еженедельный бонус валюты"
},
reward = {
type = "privilege",
tier = "vip_plus"
}
},
{
id = "premium",
title = "Premium",
price1Month = 800,
price3Month = 2200,
currency = "IGS",
perks = {
"Все преимущества VIP+",
"Увеличенная зарплата (+25%)",
"Скидка 20% в арсенале",
"Приоритетный спавн техники",
"Доступ к Premium-командам",
"Уникальные анимации и эффекты"
},
tag = "ХИТ",
reward = {
type = "privilege",
tier = "premium"
}
},
{
id = "sponsor",
title = "Спонсор",
price1Month = 1300,
price3Month = 3600,
currency = "IGS",
perks = {
"Доступ к личному Discord-серверу разработчиков проекта с различной информацией, включая оповещения о свежих обновлениях",
"Увеличенный доход за убийство противника на сервере",
"Быстрый захват точки противника (в 1,5 раза быстрее)",
"Уменьшенное ограничение (по времени) на выкат техники",
"Постоянно действующие скидки в 20% на покупку доната (НЕ ЧЕРЕЗ АВТО-ДОНАТ)",
"Уникальные возможности на сервере",
"Участие в закрытых бета-тестированиях и голосованиях"
},
tag = "ЭКСКЛЮЗИВ",
reward = {
type = "privilege",
tier = "sponsor"
}
}
}
}
}
local function BuildDonateLookup()
PLUGIN.donateItemsByID = {}
for _, category in ipairs(PLUGIN.donateCatalog or {}) do
for _, item in ipairs(category.items or {}) do
PLUGIN.donateItemsByID[item.id] = item
end
end
end
BuildDonateLookup()
function PLUGIN:GetDonateCatalog()
return self.donateCatalog or {}
end
function PLUGIN:GetDonateCategory(identifier)
if not identifier then return end
for _, category in ipairs(self:GetDonateCatalog()) do
if category.id == identifier then
return category
end
end
end
function PLUGIN:GetDonateProduct(identifier)
if not identifier then return end
if not self.donateItemsByID then
BuildDonateLookup()
end
return self.donateItemsByID[identifier]
end

View File

@@ -0,0 +1,36 @@
PLUGIN.name = "F4 Menu"
PLUGIN.author = "RefoselTeamWork"
PLUGIN.description = "F4 Menu for Military RP"
-- Ссылки на социальные сети
PLUGIN.socialLinks = {
discord = "https://discord.gg/paQdrP7aD7",
steam = "https://steamcommunity.com/sharedfiles/filedetails/?id=3630170114",
tiktok = "https://www.tiktok.com/@front.team0?_t=ZS-8zQYdHdzDB1&_r=1",
telegram = "https://t.me/frontteamproject"
}
-- Материалы иконок социальных сетей
PLUGIN.socialIcons = {
discord = "materials/ft_ui/military/vnu/f4menu/icons/discord.png",
steam = "materials/ft_ui/military/vnu/f4menu/icons/steam.png",
tiktok = "materials/ft_ui/military/vnu/f4menu/icons/tiktok.png",
telegram = "materials/ft_ui/military/vnu/f4menu/icons/telegram.png"
}
ix.util.Include("sh_donate_config.lua")
ix.util.Include("sh_thanks_config.lua")
ix.util.Include("sv_plugin.lua")
ix.util.Include("cl_plugin.lua")
ix.command.Add("Donate", {
description = "Открыть меню доната (IGS)",
alias = {"Donat", "Донат"},
OnRun = function(self, client)
if (CLIENT) then
RunConsoleCommand("igs")
else
client:ConCommand("igs")
end
end
})

View File

@@ -0,0 +1,181 @@
local PLUGIN = PLUGIN
PLUGIN.thanksPages = PLUGIN.thanksPages or {
{
id = "founders",
title = "Руководители",
people = {
{
name = "Oleg Zakon",
model = "models/player/gman_high.mdl",
sequence = "pose_standing_01",
description = "Основатель и идейный вдохновитель Front Team. Отвечает за общее развитие проекта, стратегию и ключевые решения. Следит за качеством контента, стабильностью сервера и взаимодействием с сообществом.",
link = "https://steamcommunity.com/profiles/76561198204118180"
},
{
name = "Бугор",
model = "models/player/breen.mdl",
sequence = "pose_standing_01",
description = "Правая рука владельца. Курирует техническую часть, развитие игровых механик и поддержку проекта. Обеспечивает бесперебойную работу сервера и помогает в реализации идей сообщества.",
link = "https://steamcommunity.com/profiles/76561199186141452"
}
}
},
{
id = "developers",
title = "Разработчики",
people = {
{
name = "Биба",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Настройщик игровых механик",
link = "https://steamcommunity.com/profiles/76561199189433293"
},
{
name = "Scripty",
model = "models/player/skeleton.mdl",
sequence = "pose_standing_04",
description = "Разработчик",
link = "https://steamcommunity.com/profiles/76561198164572709"
},
{
name = "ItzTomber",
model = "models/player/kleiner.mdl",
sequence = "pose_standing_04",
description = "Разработчик",
link = "https://steamcommunity.com/profiles/76561198341626975"
},
{
name = "Hari",
model = "models/player/magnusson.mdl",
sequence = "pose_standing_02",
description = "Разработчик.",
link = "https://steamcommunity.com/profiles/76561198201651767"
},
{
name = "Refosel",
model = "models/kemono_friends/ezo_red_fox/ezo_red_fox_player.mdl",
sequence = "pose_standing_01",
description = "Главный Разработчик",
link = "https://steamcommunity.com/profiles/76561198393073512"
},
{
name = "Прохор",
model = "models/player/odessa.mdl",
sequence = "pose_standing_02",
description = "Маппер.",
link = "no link"
},
{
name = "Сварщик",
model = "models/player/eli.mdl",
sequence = "pose_standing_04",
description = "Модделер.",
link = "https://steamcommunity.com/profiles/76561198440513870"
},
{
name = "Козырный",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Figma дизайнер.",
link = "https://steamcommunity.com/profiles/76561199077227396"
}
}
},
{
id = "helix",
title = "Helix",
people = {
{
name = "NutScript",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://github.com/NutScript/NutScript"
},
{
name = "Alex Grist",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197979205163"
},
{
name = "Igor Radovanovic",
model = "models/player/combine_soldier.mdl",
sequence = "pose_standing_04",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197990111113"
},
{
name = "Jaydawg",
model = "models/player/combine_soldier_prisonguard.mdl",
sequence = "pose_standing_02",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197970371430"
},
{
name = "nebulous",
model = "models/player/combine_super_soldier.mdl",
sequence = "pose_standing_01",
description = "Спасибо за Helix.",
link = "https://github.com/NebulousCloud"
},
{
name = "Black Tea",
model = "models/player/combine_soldier_prisonguard.mdl",
sequence = "pose_standing_02",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197999893894"
},
{
name = "Rain GBizzle",
model = "models/player/combine_soldier.mdl",
sequence = "pose_standing_04",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561198036111376"
},
{
name = "Luna",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://steamcommunity.com/profiles/76561197988658543"
},
{
name = "Contributors",
model = "models/player/police.mdl",
sequence = "pose_ducking_01",
description = "Спасибо за Helix.",
link = "https://github.com/NebulousCloud/helix/graphs/contributors"
}
}
}
}
function PLUGIN:GetThanksPages()
return self.thanksPages or {}
end
function PLUGIN:GetThanksPage(identifier)
if not identifier then return end
for _, page in ipairs(self:GetThanksPages()) do
if page.id == identifier then
return page
end
end
end
function PLUGIN:GetThanksPerson(pageID, personName)
local page = self:GetThanksPage(pageID)
if not page then return end
for _, person in ipairs(page.people or {}) do
if person.name == personName then
return person
end
end
end

View File

@@ -0,0 +1,667 @@
local PLUGIN = PLUGIN
util.AddNetworkString("ix.F4_RequestInfo")
util.AddNetworkString("ix.F4_SendInfo")
util.AddNetworkString("ix.F4_DonatePurchase")
util.AddNetworkString("ix.F4_DonatePurchaseResult")
util.AddNetworkString("ixChangeName")
util.AddNetworkString("ixF4UpdateName")
local function GetFactionCounters(factionID, podIndex, specIndex)
local factionOnline, samePod, sameSpec = 0, 0, 0
for _, ply in ipairs(player.GetAll()) do
if not IsValid(ply) then continue end
if ply:Team() ~= factionID then continue end
factionOnline = factionOnline + 1
local char = ply:GetCharacter()
if not char then continue end
if char:GetPodr() == podIndex then
samePod = samePod + 1
end
if char:GetSpec() == specIndex then
sameSpec = sameSpec + 1
end
end
return factionOnline, samePod, sameSpec
end
local function BuildInfoPayload(client)
local char = client:GetCharacter()
if not char then return end
local factionID = char:GetFaction()
local factionTable = ix.faction.Get(factionID)
local podIndex = char:GetPodr()
local specIndex = char:GetSpec()
local rankIndex = char:GetRank() or 1
local factionOnline, samePod, sameSpec = GetFactionCounters(factionID, podIndex, specIndex)
local vehiclesPlugin = ix.plugin.Get("vehicles")
local arsenalPlugin = ix.plugin.Get("arsenal")
local techPoints = 0
if vehiclesPlugin and vehiclesPlugin.GetFactionPoints then
techPoints = vehiclesPlugin:GetFactionPoints(factionID) or 0
end
local supplyPoints = 0
if arsenalPlugin and arsenalPlugin.GetFactionSupply then
supplyPoints = arsenalPlugin:GetFactionSupply(factionID) or 0
end
local activeVehicles = 0
if vehiclesPlugin and vehiclesPlugin.activeVehicles then
for _, data in pairs(vehiclesPlugin.activeVehicles) do
if not istable(data) then continue end
if data.faction == factionID then
activeVehicles = activeVehicles + 1
end
end
end
local subdivisionData = {
index = podIndex,
name = "Не назначено",
preset = {},
specDefault = 1,
members = samePod,
model = "",
skin = 0,
bodygroups = {}
}
local specData = {
index = specIndex,
name = "Не назначено",
weapons = {},
members = sameSpec,
podr = 0
}
local rankData = {
index = rankIndex,
name = "Без звания"
}
if factionTable then
if factionTable.Podr and factionTable.Podr[podIndex] then
local unit = factionTable.Podr[podIndex]
subdivisionData.name = unit.name or subdivisionData.name
subdivisionData.preset = unit.preset or subdivisionData.preset
subdivisionData.specDefault = unit.spec_def or subdivisionData.specDefault
subdivisionData.model = unit.model or subdivisionData.model
subdivisionData.skin = unit.skin or subdivisionData.skin
subdivisionData.bodygroups = unit.bodygroups or subdivisionData.bodygroups
end
if factionTable.Spec and factionTable.Spec[specIndex] then
local spec = factionTable.Spec[specIndex]
specData.name = spec.name or specData.name
specData.weapons = spec.weapons or specData.weapons
specData.podr = spec.podr or specData.podr
end
if factionTable.Ranks and factionTable.Ranks[rankIndex] then
local rank = factionTable.Ranks[rankIndex]
rankData.name = rank[1] or rankData.name
end
end
local factionColor = nil
if factionTable and factionTable.color then
factionColor = { r = factionTable.color.r, g = factionTable.color.g, b = factionTable.color.b }
end
local supplyStatus = "Стабильно"
local minSupply = 0
if arsenalPlugin and arsenalPlugin.config then
minSupply = arsenalPlugin.config.minSupply or 0
end
if supplyPoints <= minSupply then
supplyStatus = "Критически низко"
elseif supplyPoints <= minSupply * 2 and minSupply > 0 then
supplyStatus = "Низко"
end
return {
timestamp = os.time(),
faction = {
id = factionID,
name = factionTable and factionTable.name or "Неизвестно",
description = factionTable and factionTable.description or "",
color = factionColor,
online = factionOnline,
techPoints = techPoints,
supplyPoints = supplyPoints,
supplyStatus = supplyStatus,
activeVehicles = activeVehicles
},
character = {
name = char:GetName(),
money = char:GetMoney(),
id = char:GetID() or 0,
rank = rankData,
subdivision = subdivisionData,
spec = specData
}
}
end
net.Receive("ix.F4_RequestInfo", function(_, client)
if not IsValid(client) then return end
local payload = BuildInfoPayload(client)
if not payload then return end
net.Start("ix.F4_SendInfo")
net.WriteTable(payload)
net.Send(client)
end)
local DONATE_PURCHASE_COOLDOWN = 2
PLUGIN.donatePurchaseCooldowns = PLUGIN.donatePurchaseCooldowns or {}
local function GetCooldownKey(client)
return client:SteamID64() or client:SteamID() or client:EntIndex()
end
function PLUGIN:GetIGSBalance(client)
if not IsValid(client) then return 0 end
if client.IGSFunds then
local ok, funds = pcall(client.IGSFunds, client)
if ok and isnumber(funds) then
return math.max(math.floor(funds), 0)
end
end
if client.GetNetVar then
local funds = client:GetNetVar("igsFunds", 0)
if isnumber(funds) then
return math.max(math.floor(funds), 0)
end
end
return 0
end
function PLUGIN:AdjustIGSBalance(client, amount)
if not IsValid(client) then return false, "Игрок недоступен" end
amount = tonumber(amount) or 0
if amount == 0 then return true end
if not client.AddIGSFunds then
return false, "IGS недоступен"
end
local ok, err = pcall(client.AddIGSFunds, client, amount)
if ok then
return true
end
return false, err or "Ошибка IGS"
end
function PLUGIN:IsDonateOnCooldown(client)
local key = GetCooldownKey(client)
local expires = self.donatePurchaseCooldowns[key] or 0
if CurTime() < expires then
return true
end
end
function PLUGIN:ArmDonateCooldown(client)
local key = GetCooldownKey(client)
self.donatePurchaseCooldowns[key] = CurTime() + DONATE_PURCHASE_COOLDOWN
end
function PLUGIN:CanPurchaseDonateProduct(client, entry)
local char = client:GetCharacter()
if not char then
return false, "Нет активного персонажа"
end
if not entry then
return false, "Предложение не найдено"
end
if entry.oneTime then
local history = char:GetData("donate_purchases", {})
if history[entry.id] then
return false, "Этот набор уже был приобретен"
end
end
local hookResult, hookMessage = hook.Run("CanPlayerBuyDonateProduct", client, entry)
if hookResult == false then
return false, hookMessage or "Покупка отклонена"
end
return true
end
function PLUGIN:GrantDonateItems(client, grantData)
if not grantData then return end
local char = client:GetCharacter()
if not char then return end
if istable(grantData.weapons) and #grantData.weapons > 0 then
local owned = char:GetData("donate_weapons", {})
local changed
for _, class in ipairs(grantData.weapons) do
if isstring(class) and not table.HasValue(owned, class) then
table.insert(owned, class)
changed = true
end
end
if changed then
char:SetData("donate_weapons", owned)
end
end
if istable(grantData.cosmetics) and #grantData.cosmetics > 0 then
local cosmetics = char:GetData("donate_cosmetics", {})
for _, cosmeticID in ipairs(grantData.cosmetics) do
if isstring(cosmeticID) then
cosmetics[cosmeticID] = true
end
end
char:SetData("donate_cosmetics", cosmetics)
end
if istable(grantData.vehicles) and #grantData.vehicles > 0 then
local vehicles = char:GetData("donate_vehicles", {})
for _, certificate in ipairs(grantData.vehicles) do
if isstring(certificate) then
vehicles[#vehicles + 1] = certificate
end
end
char:SetData("donate_vehicles", vehicles)
end
if istable(grantData.boosts) and #grantData.boosts > 0 then
local calls = char:GetData("donate_support_calls", {})
for _, callID in ipairs(grantData.boosts) do
calls[#calls + 1] = callID
end
char:SetData("donate_support_calls", calls)
end
if istable(grantData.items) and #grantData.items > 0 then
local pending = char:GetData("donate_items", {})
for _, itemID in ipairs(grantData.items) do
pending[#pending + 1] = { id = itemID, ts = os.time() }
end
char:SetData("donate_items", pending)
end
end
function PLUGIN:ApplyDonateReward(client, entry, mode)
local char = client:GetCharacter()
if not char then
return false, "Нет активного персонажа"
end
local reward = entry.reward
if not reward then
local hookResult, hookMessage = hook.Run("OnDonateReward", client, entry, mode)
if hookResult ~= nil then
return hookResult, hookMessage
end
return false, "Награда не настроена"
end
local rewardType = reward.type
if rewardType == "privilege" then
local tier = reward.tier or entry.id
local duration = nil
if mode == "3month" then
duration = os.time() + (90 * 86400) -- 90 дней
else
duration = os.time() + (30 * 86400) -- 30 дней (1 месяц)
end
local privileges = char:GetData("donate_privileges", {})
privileges[tier] = {
tier = tier,
expires = duration,
granted = os.time(),
mode = mode or "month"
}
char:SetData("donate_privileges", privileges)
-- Применяем группу через SAM если доступно
if sam then
local groupMap = {
vip = "vip",
vip_plus = "vip_plus",
premium = "premium",
sponsor = "sponsor"
}
local rank = groupMap[tier]
if rank then
local expireTime = mode == "3month" and os.time() + (90 * 86400) or os.time() + (30 * 86400)
sam.player.set_rank(client, rank, expireTime)
end
end
local modeText = mode == "3month" and "на 3 месяца" or "на 1 месяц"
return true, reward.successMessage or ("Привилегия " .. tier .. " активирована " .. modeText)
elseif rewardType == "weapon" then
local weaponClass = reward.weaponClass or reward.class
if not weaponClass then
return false, "Класс оружия не указан"
end
-- Добавляем оружие в список донат-оружия персонажа со сроком действия
local donateWeapons = char:GetData("donate_weapons_timed", {})
if not istable(donateWeapons) then donateWeapons = {} end
-- Вычисляем срок действия
local expireTime
if mode == "3month" then
expireTime = os.time() + (90 * 86400) -- 90 дней
else
expireTime = os.time() + (30 * 86400) -- 30 дней
end
-- Добавляем или обновляем срок
donateWeapons[weaponClass] = {
expires = expireTime,
granted = os.time(),
mode = mode or "1month"
}
char:SetData("donate_weapons_timed", donateWeapons)
local modeText = mode == "3month" and "на 3 месяца" or "на 1 месяц"
return true, reward.successMessage or ("Донат-оружие " .. (reward.name or weaponClass) .. " добавлено в ваш арсенал " .. modeText)
elseif rewardType == "money" then
local amount = reward.amount or 0
if amount > 0 and char.GiveMoney then
char:GiveMoney(math.floor(amount))
return true, reward.successMessage or ("Вам начислено " .. amount .. "")
end
return false, "Ошибка начисления денег"
elseif rewardType == "voice_chat" then
-- Разблокировка голосового чата
char:SetData("voice_chat_unlocked", true)
-- Если есть система SAM, можно добавить флаг
if sam then
-- Можно добавить специальную группу или флаг для голосового чата
end
return true, reward.successMessage or "Голосовой чат разблокирован!"
elseif rewardType == "bundle" then
if reward.money and reward.money > 0 and char.GiveMoney then
char:GiveMoney(math.floor(reward.money))
end
if reward.supply and reward.supply ~= 0 then
local arsenal = ix.plugin.Get("arsenal")
if arsenal and arsenal.AddFactionSupply then
arsenal:AddFactionSupply(client:Team(), reward.supply)
end
end
if reward.techPoints and reward.techPoints ~= 0 then
local vehicles = ix.plugin.Get("vehicles")
if vehicles and vehicles.AddFactionPoints then
vehicles:AddFactionPoints(client:Team(), reward.techPoints)
end
end
if reward.grantedItems then
self:GrantDonateItems(client, reward.grantedItems)
end
return true, reward.successMessage or "Комплект успешно выдан"
elseif rewardType == "pass" then
local tier = reward.tier or entry.id
local duration = math.max(1, tonumber(reward.durationDays) or 30) * 86400
local passes = char:GetData("donate_passes", {})
passes[tier] = {
tier = tier,
expires = os.time() + duration,
bonuses = reward.bonuses or {}
}
char:SetData("donate_passes", passes)
return true, reward.successMessage or "Подписка активирована"
elseif rewardType == "booster" then
local boosterID = reward.boosterID or entry.id
local duration = math.max(1, tonumber(reward.durationHours) or 24) * 3600
local boosters = char:GetData("donate_boosters", {})
boosters[boosterID] = {
expires = os.time() + duration,
multiplier = reward.multiplier or 1.0
}
char:SetData("donate_boosters", boosters)
return true, reward.successMessage or "Бустер активирован"
elseif rewardType == "case" then
local caseID = reward.caseID or entry.id
local cases = char:GetData("donate_cases", {})
cases[caseID] = (cases[caseID] or 0) + 1
char:SetData("donate_cases", cases)
return true, reward.successMessage or "Кейс добавлен в коллекцию"
elseif rewardType == "cosmetic" then
local unlockID = reward.unlockID or entry.id
local cosmetics = char:GetData("donate_cosmetics", {})
cosmetics[unlockID] = true
char:SetData("donate_cosmetics", cosmetics)
return true, reward.successMessage or "Косметика разблокирована"
end
local hookResult, hookMessage = hook.Run("OnDonateReward", client, entry)
if hookResult ~= nil then
return hookResult, hookMessage
end
return false, "Тип награды не поддерживается"
end
function PLUGIN:MarkDonatePurchase(client, entry)
local char = client:GetCharacter()
if not char then return end
local history = char:GetData("donate_purchases", {})
history[entry.id] = (history[entry.id] or 0) + 1
char:SetData("donate_purchases", history)
end
function PLUGIN:SendDonateResult(client, success, message, productID)
if not IsValid(client) then return end
net.Start("ix.F4_DonatePurchaseResult")
net.WriteBool(success and true or false)
net.WriteString(message or "")
net.WriteString(productID or "")
net.Send(client)
end
function PLUGIN:HandleDonatePurchase(client, productID, price, mode)
local entry = self:GetDonateProduct(productID)
local ok, msg = self:CanPurchaseDonateProduct(client, entry)
if not ok then
return false, msg, entry
end
local actualPrice = price or tonumber(entry and entry.price) or 0
if actualPrice <= 0 then
return false, "Цена предложения не настроена", entry
end
local funds = self:GetIGSBalance(client)
if funds < actualPrice then
return false, "Недостаточно средств на балансе", entry
end
local debited, debitError = self:AdjustIGSBalance(client, -actualPrice)
if not debited then
return false, debitError or "Не удалось списать средства", entry
end
local success, rewardMessage = self:ApplyDonateReward(client, entry, mode)
if not success then
self:AdjustIGSBalance(client, actualPrice)
return false, rewardMessage or "Ошибка выдачи награды", entry
end
self:MarkDonatePurchase(client, entry, mode)
hook.Run("PostDonatePurchase", client, entry, actualPrice, mode)
return true, rewardMessage or "Покупка завершена", entry
end
net.Receive("ix.F4_DonatePurchase", function(_, client)
if not IsValid(client) or not client:IsPlayer() then return end
local productID = net.ReadString() or ""
local price = net.ReadUInt(32) or 0
local mode = net.ReadString() or "month"
if productID == "" then return end
if PLUGIN:IsDonateOnCooldown(client) then
PLUGIN:SendDonateResult(client, false, "Слишком частые запросы", productID)
return
end
PLUGIN:ArmDonateCooldown(client)
local success, message, entry = PLUGIN:HandleDonatePurchase(client, productID, price, mode)
local entryID = entry and entry.id or productID
if success then
local priceText = tostring(price)
local modeText = mode == "forever" and " (навсегда)" or " (на месяц)"
print(string.format("[F4 Donate] %s (%s) купил %s%s за %s", client:Name(), client:SteamID(), entryID, modeText, priceText))
else
print(string.format("[F4 Donate] Ошибка покупки %s игроком %s: %s", entryID, client:Name(), tostring(message)))
end
PLUGIN:SendDonateResult(client, success, message, entryID)
end)
-- Admin command to add IGS funds
ix.command.Add("GiveIGS", {
description = "Выдать IGS средства игроку",
CanRun = function(self, client)
local userGroup = string.lower(client:GetUserGroup() or "user")
return client:IsAdmin() or AdminPrivs[userGroup]
end,
arguments = {
ix.type.player,
ix.type.number
},
OnRun = function(self, client, target, amount)
if not IsValid(target) then
return "@invalidTarget"
end
amount = math.floor(tonumber(amount) or 0)
if amount == 0 then
return "Укажите корректную сумму"
end
local success, err = PLUGIN:AdjustIGSBalance(target, amount)
if success then
local action = amount > 0 and "выдал" or "снял"
client:Notify(string.format("Вы %s %d IGS для %s", action, math.abs(amount), target:Name()))
target:Notify(string.format("Администратор %s вам %d IGS", action == "выдал" and "выдал" or "снял у вас", math.abs(amount)))
print(string.format("[IGS Admin] %s (%s) %s %d IGS для %s (%s)",
client:Name(), client:SteamID(), action, math.abs(amount), target:Name(), target:SteamID()))
return ""
else
return err or "Ошибка при изменении баланса"
end
end
})
-- Admin command to check IGS balance
ix.command.Add("CheckIGS", {
description = "Проверить IGS баланс игрока",
CanRun = function(self, client)
local userGroup = string.lower(client:GetUserGroup() or "user")
return client:IsAdmin() or AdminPrivs[userGroup]
end,
arguments = {
ix.type.player
},
OnRun = function(self, client, target)
if not IsValid(target) then
return "@invalidTarget"
end
local balance = PLUGIN:GetIGSBalance(target)
return string.format("Баланс IGS игрока %s: %d руб.", target:Name(), balance)
end
})
net.Receive("ixChangeName", function(len, client)
if not IsValid(client) then return end
local character = client:GetCharacter()
if not character then return end
local newName = net.ReadString()
if not newName or newName == "" then return end
if #newName < 3 or #newName > 50 then
client:Notify("Имя должно содержать от 3 до 50 символов")
return
end
if newName:find("[<>\"\\/]") then
client:Notify("Имя содержит недопустимые символы")
return
end
local oldName = character:GetName()
character:SetName(newName)
client:Notify("Ваше имя успешно изменено на: " .. newName)
net.Start("ixF4UpdateName")
net.WriteString(newName)
net.Send(client)
hook.Run("OnCharacterVarChanged", character, "name", oldName, newName)
end)
-- Фикс выдачи донат-оружия IGS в Helix
hook.Add("PostPlayerLoadout", "IGS_Helix_WeaponLoadoutFix", function(client)
if not IsValid(client) or not IGS then return end
timer.Simple(1.5, function()
if not IsValid(client) or not client:GetCharacter() then return end
if isfunction(IGS.PlayerLoadout) then
IGS.PlayerLoadout(client)
end
if isfunction(IGS.GetItems) and IsValid(client) and client.HasPurchase then
for _, ITEM in pairs(IGS.GetItems()) do
if not ITEM or not isfunction(ITEM.GetUID) then continue end
local weaponClass = (isfunction(ITEM.GetWeapon) and ITEM:GetWeapon()) or ITEM.weapon
if weaponClass and client:HasPurchase(ITEM:GetUID()) then
if not client:HasWeapon(weaponClass) then
client:Give(weaponClass)
end
end
end
end
end)
end)

View File

@@ -0,0 +1,220 @@
-- Клиентское меню строительства
local PLUGIN = PLUGIN
if CLIENT then
function DrawItems(items, panel, mode, main)
for i=1, #items do
if items[i].cat == mode then
local DButton = panel:Add( "DButton" )
DButton:SetHeight(60)
DButton:SetFont( "Font1" )
DButton:SetText( items[i].name .. " | Запас: " .. items[i].count )
DButton:SetIcon( items[i].icon )
DButton:Dock( TOP )
DButton:DockMargin( 0, 10, 0, 10 )
DButton.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255))
draw.RoundedBox(5, 2, 2, w-4, h-4, Color(200, 200, 200, 255))
end
DButton.DoClick = function()
if items[i].model != nil then
net.Start("buildtrench")
net.WriteInt(i, 13)
net.SendToServer()
main:Close()
end
if items[i].entity != nil then
net.Start("setentity")
net.WriteInt(i, 13)
net.SendToServer()
main:Close()
end
end
end
end
end
surface.CreateFont("Font3", {
font = "Arial",
extended = true,
weight = 900,
size = 24
})
surface.CreateFont("Font2", {
font = "Arial",
extended = true,
weight = 800,
size = 20
})
surface.CreateFont("Font", {
font = "Arial",
extended = true,
size = 20
})
surface.CreateFont("Font1", {
font = "Arial",
weight = 900,
extended = true,
size = 20
})
local faded_black = Color(100, 100, 100, 0)
net.Receive("openmenu", function(len, ply)
local alpha1 = 255
local alpha2 = 0
local alpha3 = 0
local alpha4 = 0
local catlabel
local DScrollPanel
local items = net.ReadTable()
local mode = 0
local main = vgui.Create("DFrame")
main:SetSize(500, 500)
main:Center()
main:SetTitle(" ")
main:SetDraggable(false)
main:ShowCloseButton(false)
main:MakePopup()
main.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, faded_black)
end
local p = vgui.Create( "DPanel", main )
p:Dock( TOP )
p:DockMargin( 0, 0, 0, 0 )
p:SetHeight(40)
p.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255))
draw.RoundedBox(5, 2, 2, w-4, h-4, Color(150, 150, 150, 255))
end
local namelabel = vgui.Create( "DLabel", p )
namelabel:Dock( LEFT )
namelabel:DockMargin( 20, 0, 20, 0 )
namelabel:SetSize(200, 20)
namelabel:SetFont( "Font" )
namelabel:SetText( "Инженерный комплект" )
local closebutton = vgui.Create( "DButton", p )
closebutton:Dock( RIGHT )
closebutton:DockMargin( 20, 0, 20, 0 )
closebutton:SetText( " " )
closebutton:SetImage("entities/closebutton.png")
closebutton:SetSize( 40, 40 )
closebutton.Paint = function(self, w, h)
draw.RoundedBox(2, 0, 0, w, h, Color(150, 150, 150, 0))
end
closebutton.DoClick = function()
main:Close()
end
local ppp = vgui.Create( "DPanel", main )
ppp:Dock( TOP )
ppp:DockMargin( 0, 20, 0, 20 )
ppp:SetHeight(60)
ppp.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255))
draw.RoundedBox(5, 2, 2, w-4, h-4, Color(150, 150, 150, 255))
end
local entity = vgui.Create( "DButton", ppp )
entity:Dock( LEFT )
entity:DockMargin( 70, 10, 0, 10 )
entity:SetText( " " )
entity:SetImage("entities/entity.png")
entity:SetHeight(40)
entity:SetWidth(40)
entity.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha1))
end
entity.DoClick = function()
DScrollPanel:GetCanvas():Clear()
DrawItems(items, DScrollPanel, 0, main)
catlabel:SetText( " Объекты" )
alpha1 = 255
alpha2 = 0
alpha3 = 0
alpha4 = 0
end
local tier1 = vgui.Create( "DButton", ppp )
tier1:Dock( LEFT )
tier1:DockMargin( 70, 10, 0, 10 )
tier1:SetText( " " )
tier1:SetImage("entities/tier1.png")
tier1:SetHeight(40)
tier1:SetWidth(40)
tier1.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha2))
end
tier1.DoClick = function()
DScrollPanel:GetCanvas():Clear()
DrawItems(items, DScrollPanel, 1, main)
catlabel:SetText( "Легкие укрепления" )
alpha1 = 0
alpha2 = 255
alpha3 = 0
alpha4 = 0
end
local tier2 = vgui.Create( "DButton", ppp )
tier2:Dock( LEFT )
tier2:DockMargin( 70, 10, 0, 10 )
tier2:SetText( " " )
tier2:SetImage("entities/tier2.png")
tier2:SetHeight(40)
tier2:SetWidth(40)
tier2.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha3))
end
tier2.DoClick = function()
DScrollPanel:GetCanvas():Clear()
DrawItems(items, DScrollPanel, 2, main)
catlabel:SetText( "Средние укрепления" )
alpha1 = 0
alpha2 = 0
alpha3 = 255
alpha4 = 0
end
local tier3 = vgui.Create( "DButton", ppp )
tier3:Dock( LEFT )
tier3:DockMargin( 70, 10, 0, 10 )
tier3:SetText( " " )
tier3:SetImage("entities/tier3.png")
tier3:SetHeight(40)
tier3:SetWidth(40)
tier3.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(120, 120, 120, alpha4))
end
tier3.DoClick = function()
DScrollPanel:GetCanvas():Clear()
DrawItems(items, DScrollPanel, 3, main)
catlabel:SetText( "Тяжелые укрепления" )
alpha1 = 0
alpha2 = 0
alpha3 = 0
alpha4 = 255
end
local pp = vgui.Create( "DPanel", main )
pp:Dock( TOP )
pp:DockMargin( 0, 0, 0, 20 )
pp:SetHeight(300)
pp.Paint = function(self, w, h)
draw.RoundedBox(5, 0, 0, w, h, Color(0, 0, 0, 255))
draw.RoundedBox(5, 2, 2, w-4, h-4, Color(150, 150, 150, 255))
end
catlabel = vgui.Create( "DLabel", pp )
catlabel:Dock( TOP )
catlabel:DockMargin( 170, 20, 0, 10 )
catlabel:SetSize(350, 20)
catlabel:SetFont( "Font" )
catlabel:SetText( " Объекты" )
DScrollPanel = vgui.Create( "DScrollPanel", pp )
DScrollPanel:Dock( TOP )
DScrollPanel:SetHeight(240)
DScrollPanel:DockMargin( 6, 6, 6, 6 )
DrawItems(items, DScrollPanel, 0, main)
end)
end

View File

@@ -0,0 +1,114 @@
-- Клиентская часть смены скинов
local PLUGIN = PLUGIN
local function OpenSkinMenu(ply, ent)
if not IsValid(ent) then return end
local frame = vgui.Create("DFrame")
frame:SetSize(250, 400)
frame:SetTitle("Выбор скина")
frame:Center()
frame:MakePopup()
local skinPanel = vgui.Create("DScrollPanel", frame)
skinPanel:SetSize(230, 350)
skinPanel:SetPos(10, 30)
net.Start("RequestSkinData")
net.WriteEntity(ent)
net.SendToServer()
net.Receive("ReceiveSkinData", function()
local totalSkins = net.ReadInt(8)
local skinNames = net.ReadTable()
for i = 0, totalSkins - 1 do
local skinName = skinNames[i] or "Скин " .. i
local button = vgui.Create("DButton", skinPanel)
button:SetText(skinName)
button:SetSize(210, 40)
button:SetPos(10, 10 + i * 50)
button.DoClick = function()
net.Start("ChangeEntitySkin")
net.WriteEntity(ent)
net.WriteInt(i, 8)
net.SendToServer()
end
end
end)
end
hook.Add("PlayerBindPress", "OpenSkinMenuOnUse", function(ply, bind, pressed)
local allowedGroups = {
owner = true,
admin = true,
["spec admin"] = true,
prem = true,
sponsor = true
}
if allowedGroups[ply:GetUserGroup()] then
if bind == "+use" and pressed then
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if IsValid(ent) and trace.HitPos:Distance(ply:GetPos()) < 1000 then
local activeWeapon = ply:GetActiveWeapon()
if IsValid(activeWeapon) and activeWeapon:GetClass() == "weapon_hands" then
net.Start("CheckAllowedEntity")
net.WriteEntity(ent)
net.SendToServer()
net.Receive("EntityAllowed", function()
OpenSkinMenu(ply, ent)
end)
end
end
end
end
end)
concommand.Add("get_bodygroup_info", function(ply)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) then
print("[Ошибка] Вы не смотрите на объект!")
return
end
local bodygroups = ent:GetBodyGroups()
print("========[ Bodygroup Info ]========")
print("Модель: " .. ent:GetModel())
for _, bg in ipairs(bodygroups) do
local currentValue = ent:GetBodygroup(bg.id)
print(string.format("Bodygroup: %s (ID: %d) | Выбрано: %d", bg.name, bg.id, currentValue))
end
local materials = ent:GetMaterials()
print("========[ Sub-Materials ]========")
for i = 0, #materials - 1 do
local mat = ent:GetSubMaterial(i)
if mat == "" then mat = "По умолчанию" end
print(string.format("SubMaterial ID: %d | Материал: %s", i, mat))
end
end)
concommand.Add("get_skin_info", function(ply)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) then
print("[Ошибка] Вы не смотрите на объект!")
return
end
local totalSkins = ent:SkinCount() - 1
print("========[ Skin Info ]========")
print("Модель: " .. ent:GetModel())
print("Доступно скинов: " .. totalSkins)
for i = 0, totalSkins do
print(string.format("Skin ID: %d", i))
end
end)

View File

@@ -0,0 +1,9 @@
include("shared.lua")
function ENT:Draw()
self:DrawModel()
end
function ENT:IsTranslucent()
return true
end

View File

@@ -0,0 +1,58 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
local StartPos
local StartAng
function ENT:Initialize()
self:SetMoveType(MOVETYPE_NONE)
StartPos = self:GetPos()
StartAng = self:GetAngles()
self:SetNWInt('MaxHp',self.hp)
end
function ENT:Think()
self:SetNWInt('Hp',self.hp)
if self:GetNWInt("Hp") > self:GetNWInt("MaxHp") then
self:SetNWInt('Hp',self:GetNWInt("MaxHp"))
end
if SERVER then
if self.poscoef == 0 and self.building then
self.Owner:Freeze(false)
self.Owner.building = false
self:PhysicsInitStatic(6)
self:SetCollisionGroup(0)
self.building = false
end
if self.building then
self.Owner.building = true
self.poscoef = self.poscoef + 1
self.Owner:Freeze(true)
self:SetAngles(StartAng)
self:SetPos(StartPos - self:GetAngles():Up() * -self.poscoef)
end
end
end
function ENT:OnRemove()
if self.HasIdle then
self:StopSound("TFA_INS2_RPG7.Loop")
self.HasIdle = false
end
end
function ENT:Use(activator, caller)
end
function ENT:OnTakeDamage( dmg )
if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end
if self.Exploded then return end
if self.hp > 0 and self.hp - dmg:GetDamage() <= 0 then
self.Exploded = true
self:Remove()
end
self.hp = self.hp - dmg:GetDamage()
dmg:SetAttacker(self)
dmg:SetInflictor(self)
self:TakePhysicsDamage( dmg )
end

View File

@@ -0,0 +1,8 @@
ENT.Type = "anim"
ENT.PrintName = "Contact Explosive"
ENT.Author = ""
ENT.Contact = ""
ENT.Purpose = ""
ENT.Instructions = ""
ENT.DoNotDuplicate = true
ENT.DisableDuplicator = true

View File

@@ -0,0 +1,9 @@
include("shared.lua")
function ENT:Draw()
self:DrawModel()
end
function ENT:IsTranslucent()
return true
end

View File

@@ -0,0 +1,58 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
local StartPos
local StartAng
function ENT:Initialize()
self:SetMoveType(MOVETYPE_NONE)
StartPos = self:GetPos()
StartAng = self:GetAngles()
self:SetNWInt('MaxHp',self.hp)
end
function ENT:Think()
self:SetNWInt('Hp',self.hp)
if self:GetNWInt("Hp") > self:GetNWInt("MaxHp") then
self:SetNWInt('Hp',self:GetNWInt("MaxHp"))
end
if SERVER then
if self.poscoef == 0 and self.building then
self.Owner:Freeze(false)
self.Owner.building = false
self:PhysicsInitStatic(6)
self:SetCollisionGroup(0)
self.building = false
end
if self.building then
self.Owner.building = true
self.poscoef = self.poscoef + 1
self.Owner:Freeze(true)
self:SetAngles(StartAng)
self:SetPos(StartPos - self:GetAngles():Up() * -self.poscoef)
end
end
end
function ENT:OnRemove()
if self.HasIdle then
self:StopSound("TFA_INS2_RPG7.Loop")
self.HasIdle = false
end
end
function ENT:Use(activator, caller)
end
function ENT:OnTakeDamage( dmg )
if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end
if self.Exploded then return end
if self.hp > 0 and self.hp - dmg:GetDamage() <= 0 then
self.Exploded = true
self:Remove()
end
self.hp = self.hp - dmg:GetDamage()
dmg:SetAttacker(self)
dmg:SetInflictor(self)
self:TakePhysicsDamage( dmg )
end

View File

@@ -0,0 +1,8 @@
ENT.Type = "anim"
ENT.PrintName = "Contact Explosive"
ENT.Author = ""
ENT.Contact = ""
ENT.Purpose = ""
ENT.Instructions = ""
ENT.DoNotDuplicate = true
ENT.DisableDuplicator = true

View File

@@ -0,0 +1,29 @@
include("shared.lua")
surface.CreateFont( "PlayerTagFont", {
font = "Arial",
size = 72,
} )
function ENT:Draw()
self:DrawModel()
angle = self:GetAngles() + Angle(0,90,90)
pos = self:GetPos() + angle:Forward() * 0 + angle:Up() * 0 + angle:Right() * -30
cam.Start3D2D( pos, angle, 0.05 )
surface.SetFont( "PlayerTagFont" )
local tW, tH = surface.GetTextSize( self.DisplayName )
local padX = 20
local padY = 5
surface.SetDrawColor( 0, 0, 0, 200 )
surface.DrawRect( -tW / 2 - padX, -padY, tW + padX * 2, tH + padY * 2 )
draw.SimpleText( self.DisplayName, "PlayerTagFont", -tW / 2, 0, color_white )
cam.End3D2D()
end
function ENT:IsTranslucent()
return true
end

View File

@@ -0,0 +1,43 @@
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
ENT.hp = 500
function ENT:Initialize()
self:SetModel(self.Model)
self:PhysicsInit(6)
end
function ENT:Think()
end
function ENT:OnRemove()
end
function ENT:Use(activator, caller)
if activator:GetActiveWeapon():GetClass() == "engineertool" or "engineertoolfpv" or "engineertoolmines" then
print(activator:GetActiveWeapon().Structures)
activator:GetActiveWeapon().Structures[self.ItemId].count = activator:GetActiveWeapon().Structures[self.ItemId].count + self.ItemCount
net.Start('additem')
net.WriteInt(self.ItemId, 13)
net.WriteInt(self.ItemCount, 13)
net.Send(activator)
self:Remove()
end
end
function ENT:OnTakeDamage( dmg )
if dmg:GetInflictor() == self or dmg:GetAttacker() == self then return end
if self.Exploded then return end
if self.hp > 0 and self.hp - dmg:GetDamage() <= 0 then
self.Exploded = true
self:Remove()
end
self.hp = self.hp - dmg:GetDamage()
dmg:SetAttacker(self)
dmg:SetInflictor(self)
self:TakePhysicsDamage( dmg )
end

View File

@@ -0,0 +1,12 @@
ENT.Type = "anim"
ENT.PrintName = "Ящик с припасами"
ENT.Category = "Engineer Kit"
ENT.Author = ""
ENT.Contact = ""
ENT.Purpose = ""
ENT.Instructions = ""
ENT.Spawnable = true
ENT.Model = "models/Items/ammocrate_smg1.mdl"
ENT.ItemId = 1
ENT.ItemCount = 1
ENT.DisplayName = "ПТРК 'Корнет'"

View File

@@ -0,0 +1,251 @@
if CLIENT then
SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/weapon_doietoolus")
SWEP.DrawWeaponInfoBox = false
SWEP.BounceWeaponIcon = false
killicon.Add( "weapon_doietoolus", "vgui/hud/weapon_doietoolus", Color( 0, 0, 0, 255 ) )
end
SWEP.PrintName = "Entrenching Tool"
SWEP.Category = "Engiener Tools"
SWEP.Spawnable= true
SWEP.AdminSpawnable= true
SWEP.AdminOnly = false
SWEP.ViewModelFOV = 60
SWEP.ViewModel = "models/weapons/doi/v_etool_us.mdl"
SWEP.WorldModel = "models/weapons/doi/w_etool_us.mdl"
SWEP.ViewModelFlip = false
SWEP.AutoSwitchTo = false
SWEP.AutoSwitchFrom = false
SWEP.Slot = 0
SWEP.SlotPos = 0
SWEP.UseHands = true
SWEP.HoldType = "melee2"
SWEP.FiresUnderwater = false
SWEP.DrawCrosshair = false
SWEP.DrawAmmo = true
SWEP.Base = "weapon_base"
SWEP.CSMuzzleFlashes = true
SWEP.Sprint = 0
SWEP.Primary.ClipSize = 0
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = "none"
SWEP.Secondary.ClipSize = 0
SWEP.Secondary.DefaultClip = 0
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = "none"
SWEP.Structures = {
{
name = "Мешки с песком",
icon = "entities/closebutton.png",
cat = 1,
hp = 10000,
model = "models/props_fortifications/sandbags_corner1.mdl",
count = 3,
corrangle = Angle(0,0,0)
},
{
name = "Большие мешки с песком",
icon = "entities/closebutton.png",
cat = 1,
hp = 20000,
model = "models/props_fortifications/sandbags_corner1_tall.mdl",
count = 2,
corrangle = Angle(0,0,0)
},
{
name = "Противотанковые ёжи",
icon = "entities/closebutton.png",
cat = 1,
hp = 50000,
model = "models/test_sborka_props/barricades/ezi_antitank_2.mdl",
count = 2,
corrangle = Angle(0,-90,0)
},
{
name = "Танковый окоп",
icon = "entities/closebutton.png",
cat = 2,
hp = 100000,
model = "models/trenches/prop/tank_cover/tank_cover.mdl",
count = 3,
corrangle = Angle(0,0,0)
},
}
function SWEP:Initialize()
self:SetWeaponHoldType( self.HoldType )
self:GetOwner().buildtime = 0
end
function SWEP:Deploy()
self:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() )
self.Weapon:SendWeaponAnim( ACT_VM_DRAW )
Sprint = 0
end
function SWEP:Holster()
self.NextSecondaryAttack = 0
Sprint = 0
return true
end
function SWEP:PrimaryAttack()
if !(self.Weapon:GetNextPrimaryFire() < CurTime()) || not(Sprint == 0) then return end
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
self.Owner:SetAnimation( PLAYER_ATTACK1 )
self:SetNextPrimaryFire( CurTime() + 1)
self:SetNextSecondaryFire( CurTime() + 1)
if SERVER then
net.Start("openmenu")
net.WriteTable(self.Structures)
net.Send(self:GetOwner())
end
end
function SWEP:SecondaryAttack()
end
function SWEP:Reload()
if SERVER then
local item = self:GetOwner():GetEyeTrace().Entity
if item:IsValid() then
if item.id != nil then
if item.Owner == self:GetOwner() then
if (item:GetClass() == self.Structures[item.id].entity) or (item:GetModel() == self.Structures[item.id].model) then
item:Remove()
self.Structures[item.id].count = self.Structures[item.id].count + 1
end
end
end
end
end
end
function SWEP:DrawHUDBackground()
if self:GetOwner().buildtime > 0 then
draw.RoundedBox( 20, ScrW()/2-150, ScrH()/2-30, 0+300-self:GetOwner().buildtime*15, 60, Color( 50, 50, 50 , 180) )
draw.RoundedBox( 20, ScrW()/2-145, ScrH()/2-25, 0+290-self:GetOwner().buildtime*14.5, 50, Color( 0, 255, 0 ,125) )
draw.DrawText( tostring(100 - self:GetOwner().buildtime*5) .. '%', "Font3", ScrW()/2-20, ScrH()/2-15, Color( 255, 255, 255 ,255), 0 )
end
end
local delay = 0.5
local nextOccurance = 0
local flag1 = true
if CLIENT then
function SWEP:RenderScreen()
if self.Owner.buildtime < 0 or Sprint == 2 then
self.Weapon:SendWeaponAnim(ACT_VM_IDLE)
end
if input.IsKeyDown(KEY_F) and flag1 then
net.Start("buildstop12")
net.SendToServer()
self.Owner.building = false
self.Owner.buildtime = -3
flag1 = false
timer.Simple(1, function() flag1 = true end)
end
local timeLeft = nextOccurance - CurTime()
if timeLeft < 0 and self:GetOwner().buildtime >= 0 then
self:GetOwner().buildtime = self:GetOwner().buildtime - 0.5
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
self:GetOwner():SetAnimation( PLAYER_ATTACK1 )
self:SetNextPrimaryFire( CurTime() + delay)
nextOccurance = CurTime() + delay
end
net.Receive("buildanim", function(len, ply)
self:GetOwner().buildtime = 20
end)
end
end
if SERVER then
function SWEP:Think()
net.Receive("buildstop12", function(len, ply)
ply:Freeze(false)
if self.LastEnt:IsValid() then
self.Structures[self.LastEnt.id].count = self.Structures[self.LastEnt.id].count + 1
self.LastEnt:Remove()
end
end)
net.Receive("buildtrench", function(len, ply)
if ply:GetActiveWeapon():GetClass() != "engineertool" then return end
local id = net.ReadInt(13)
if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then
if self.Structures[id].count > 0 then
net.Start("buildanim")
net.WriteEntity(item)
net.Send(self:GetOwner())
local item = ents.Create("util_structure")
item:SetPos(ply:GetEyeTrace().HitPos)
item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + Angle(90,ply:EyeAngles().y,0))
item:SetModel(self.Structures[id].model)
item.hp = self.Structures[id].hp
item.id = id
item.poscoef = -100
item.building = true
item.Owner = self:GetOwner()
item:Spawn()
self.LastEnt = item
self.Structures[id].count = self.Structures[id].count - 1
end
end
end)
net.Receive("setentity", function(len, ply)
if ply:GetActiveWeapon():GetClass() != "engineertool" then return end
local id = net.ReadInt(13)
if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then
if self.Structures[id].count > 0 then
local item = ents.Create(self.Structures[id].entity)
item:SetPos(ply:GetEyeTrace().HitPos + ply:GetEyeTrace().HitNormal * 40)
item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + self.Structures[id].corrangle)
item.id = id
item.Owner = self:GetOwner()
item:Spawn()
self.LastEnt = item
self.Structures[id].count = self.Structures[id].count - 1
end
end
end)
if (Sprint == 0) then
if self.Owner:KeyDown(IN_SPEED) and (self.Owner:KeyDown(IN_FORWARD) || self.Owner:KeyDown(IN_BACK) || self.Owner:KeyDown(IN_MOVELEFT) || self.Owner:KeyDown(IN_MOVERIGHT)) then
Sprint = 1
end
end
if (Sprint == 1) then
self.Weapon:SendWeaponAnim(ACT_VM_SPRINT_IDLE)
Sprint = 2
end
if (Sprint == 2) then
if self.Owner:KeyReleased(IN_SPEED) then
self.Weapon:SendWeaponAnim(ACT_VM_IDLE)
Sprint = 0
end
end
end
end

View File

@@ -0,0 +1,231 @@
if CLIENT then
SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/weapon_doietoolus")
SWEP.DrawWeaponInfoBox = false
SWEP.BounceWeaponIcon = false
killicon.Add( "weapon_doietoolus", "vgui/hud/weapon_doietoolus", Color( 0, 0, 0, 255 ) )
end
SWEP.PrintName = "Entrenching Tool FPV"
SWEP.Category = "Engiener Tools"
SWEP.Spawnable= true
SWEP.AdminSpawnable= true
SWEP.AdminOnly = false
SWEP.ViewModelFOV = 60
SWEP.ViewModel = "models/weapons/doi/v_etool_us.mdl"
SWEP.WorldModel = "models/weapons/doi/w_etool_us.mdl"
SWEP.ViewModelFlip = false
SWEP.AutoSwitchTo = false
SWEP.AutoSwitchFrom = false
SWEP.Slot = 0
SWEP.SlotPos = 0
SWEP.UseHands = true
SWEP.HoldType = "melee2"
SWEP.FiresUnderwater = false
SWEP.DrawCrosshair = false
SWEP.DrawAmmo = true
SWEP.Base = "weapon_base"
SWEP.CSMuzzleFlashes = true
SWEP.Sprint = 0
SWEP.Primary.ClipSize = 0
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = "none"
SWEP.Secondary.ClipSize = 0
SWEP.Secondary.DefaultClip = 0
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = "none"
SWEP.Structures = {
{
name = "FPV-дрон",
icon = "entities/entity.png",
cat = 0,
entity = "entity_drone_bomb",
count = 2,
id = 1,
corrangle = Angle(90,0,0)
},
{
name = "Разведывательный дрон",
icon = "entities/entity.png",
cat = 0,
entity = "entity_drone_base",
count = 1,
id = 2,
corrangle = Angle(90,0,0)
},
{
name = "РЭБ",
icon = "entities/entity.png",
cat = 0,
entity = "entity_dronejammer",
count = 1,
id = 3,
corrangle = Angle(90,0,0)
},
{
name = "Зарядная станция для дрона",
icon = "entities/entity.png",
cat = 0,
entity = "entity_dronepad",
count = 1,
id = 4,
corrangle = Angle(90,0,0)
},
}
function SWEP:Initialize()
self:SetWeaponHoldType( self.HoldType )
self:GetOwner().buildtime = 0
end
function SWEP:Deploy()
self:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() )
self.Weapon:SendWeaponAnim( ACT_VM_DRAW )
Sprint = 0
end
function SWEP:Holster()
self.NextSecondaryAttack = 0
Sprint = 0
return true
end
function SWEP:PrimaryAttack()
if !(self.Weapon:GetNextPrimaryFire() < CurTime()) || not(Sprint == 0) then return end
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
self.Owner:SetAnimation( PLAYER_ATTACK1 )
self:SetNextPrimaryFire( CurTime() + 1)
self:SetNextSecondaryFire( CurTime() + 1)
if SERVER then
net.Start("openmenu")
net.WriteTable(self.Structures)
net.Send(self:GetOwner())
end
end
function SWEP:SecondaryAttack()
end
function SWEP:Reload()
if SERVER then
local item = self:GetOwner():GetEyeTrace().Entity
if item:IsValid() then
local structure_id = item.structure_id
if structure_id != nil then
if item:GetOwner() == self:GetOwner() then
local struct = self.Structures[structure_id]
if (item:GetClass() == struct.entity) or (item:GetModel() == struct.model) then
item:Remove()
struct.count = struct.count + 1
end
end
end
end
end
end
function SWEP:DrawHUDBackground()
if self:GetOwner().buildtime > 0 then
draw.RoundedBox( 20, ScrW()/2-150, ScrH()/2-30, 0+300-self:GetOwner().buildtime*15, 60, Color( 50, 50, 50 , 180) )
draw.RoundedBox( 20, ScrW()/2-145, ScrH()/2-25, 0+290-self:GetOwner().buildtime*14.5, 50, Color( 0, 255, 0 ,125) )
draw.DrawText( tostring(100 - self:GetOwner().buildtime*5) .. '%', "Font3", ScrW()/2-20, ScrH()/2-15, Color( 255, 255, 255 ,255), 0 )
end
end
local delay = 0.5
local nextOccurance = 0
local flag1 = true
if CLIENT then
function SWEP:RenderScreen()
if self.Owner.buildtime < 0 or Sprint == 2 then
self.Weapon:SendWeaponAnim(ACT_VM_IDLE)
end
if input.IsKeyDown(KEY_F) and flag1 then
net.Start("buildstop12")
net.SendToServer()
self.Owner.building = false
self.Owner.buildtime = -3
flag1 = false
timer.Simple(1, function() flag1 = true end)
end
local timeLeft = nextOccurance - CurTime()
if timeLeft < 0 and self:GetOwner().buildtime >= 0 then
self:GetOwner().buildtime = self:GetOwner().buildtime - 0.5
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
self:GetOwner():SetAnimation( PLAYER_ATTACK1 )
self:SetNextPrimaryFire( CurTime() + delay)
nextOccurance = CurTime() + delay
end
net.Receive("buildanim", function(len, ply)
self:GetOwner().buildtime = 20
end)
end
end
if SERVER then
function SWEP:Think()
net.Receive("buildstop12", function(len, ply)
ply:Freeze(false)
if self.LastEnt:IsValid() then
self.Structures[self.LastEnt.id].count = self.Structures[self.LastEnt.id].count + 1
self.LastEnt:Remove()
end
end)
net.Receive("setentity", function(len, ply)
if ply:GetActiveWeapon():GetClass() ~= "engineertoolfpv" then return end
local id = net.ReadInt(13)
local struct = self.Structures[id]
if struct and struct.count > 0 then
local trace = ply:GetEyeTrace()
if (trace.HitPos - trace.StartPos):Length() > 300 then return end
local item = ents.Create(struct.entity)
item:SetPos(trace.HitPos + trace.HitNormal * 10)
item:SetAngles(trace.HitNormal:Angle() + struct.corrangle)
item:Spawn()
struct.count = struct.count - 1
item.structure_id = id
if IsValid(ply) and ply:IsPlayer() then
item:SetOwner(ply)
end
end
end)
if (Sprint == 0) then
if self.Owner:KeyDown(IN_SPEED) and (self.Owner:KeyDown(IN_FORWARD) || self.Owner:KeyDown(IN_BACK) || self.Owner:KeyDown(IN_MOVELEFT) || self.Owner:KeyDown(IN_MOVERIGHT)) then
Sprint = 1
end
end
if (Sprint == 1) then
self.Weapon:SendWeaponAnim(ACT_VM_SPRINT_IDLE)
Sprint = 2
end
if (Sprint == 2) then
if self.Owner:KeyReleased(IN_SPEED) then
self.Weapon:SendWeaponAnim(ACT_VM_IDLE)
Sprint = 0
end
end
end
end

View File

@@ -0,0 +1,225 @@
if CLIENT then
SWEP.WepSelectIcon = surface.GetTextureID("vgui/hud/weapon_doietoolus")
SWEP.DrawWeaponInfoBox = false
SWEP.BounceWeaponIcon = false
killicon.Add( "weapon_doietoolus", "vgui/hud/weapon_doietoolus", Color( 0, 0, 0, 255 ) )
end
SWEP.PrintName = "Entrenching Tool MINES"
SWEP.Category = "Engiener Tools"
SWEP.Spawnable= true
SWEP.AdminSpawnable= true
SWEP.AdminOnly = false
SWEP.ViewModelFOV = 60
SWEP.ViewModel = "models/weapons/doi/v_etool_us.mdl"
SWEP.WorldModel = "models/weapons/doi/w_etool_us.mdl"
SWEP.ViewModelFlip = false
SWEP.AutoSwitchTo = false
SWEP.AutoSwitchFrom = false
SWEP.Slot = 0
SWEP.SlotPos = 0
SWEP.UseHands = true
SWEP.HoldType = "melee2"
SWEP.FiresUnderwater = false
SWEP.DrawCrosshair = false
SWEP.DrawAmmo = true
SWEP.Base = "weapon_base"
SWEP.CSMuzzleFlashes = true
SWEP.Sprint = 0
SWEP.Primary.ClipSize = 0
SWEP.Primary.DefaultClip = 0
SWEP.Primary.Automatic = false
SWEP.Primary.Ammo = "none"
SWEP.Secondary.ClipSize = 0
SWEP.Secondary.DefaultClip = 0
SWEP.Secondary.Automatic = false
SWEP.Secondary.Ammo = "none"
SWEP.Structures = {
{
name = "Мина 'ТМ-62'",
icon = "entities/entity.png",
cat = 0,
entity = "sw_mine_tm62_v3",
count = 10,
corrangle = Angle(90,0,0)
},
}
function SWEP:Initialize()
self:SetWeaponHoldType( self.HoldType )
self:GetOwner().buildtime = 0
end
function SWEP:Deploy()
self:SetNextPrimaryFire( CurTime() + self.Owner:GetViewModel():SequenceDuration() )
self.Weapon:SendWeaponAnim( ACT_VM_DRAW )
Sprint = 0
end
function SWEP:Holster()
self.NextSecondaryAttack = 0
Sprint = 0
return true
end
function SWEP:PrimaryAttack()
if !(self.Weapon:GetNextPrimaryFire() < CurTime()) || not(Sprint == 0) then return end
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
self.Owner:SetAnimation( PLAYER_ATTACK1 )
self:SetNextPrimaryFire( CurTime() + 1)
self:SetNextSecondaryFire( CurTime() + 1)
if SERVER then
net.Start("openmenu")
net.WriteTable(self.Structures)
net.Send(self:GetOwner())
end
end
function SWEP:SecondaryAttack()
end
function SWEP:Reload()
if SERVER then
local item = self:GetOwner():GetEyeTrace().Entity
if item:IsValid() then
if item.id != nil then
if item.Owner == self:GetOwner() then
if (item:GetClass() == self.Structures[item.id].entity) or (item:GetModel() == self.Structures[item.id].model) then
item:Remove()
self.Structures[item.id].count = self.Structures[item.id].count + 1
end
end
end
end
end
end
function SWEP:DrawHUDBackground()
if self:GetOwner().buildtime > 0 then
draw.RoundedBox( 20, ScrW()/2-150, ScrH()/2-30, 0+300-self:GetOwner().buildtime*15, 60, Color( 50, 50, 50 , 180) )
draw.RoundedBox( 20, ScrW()/2-145, ScrH()/2-25, 0+290-self:GetOwner().buildtime*14.5, 50, Color( 0, 255, 0 ,125) )
draw.DrawText( tostring(100 - self:GetOwner().buildtime*5) .. '%', "Font3", ScrW()/2-20, ScrH()/2-15, Color( 255, 255, 255 ,255), 0 )
end
end
local delay = 0.5
local nextOccurance = 0
local flag1 = true
if CLIENT then
function SWEP:RenderScreen()
if self.Owner.buildtime < 0 or Sprint == 2 then
--print(1)
self.Weapon:SendWeaponAnim(ACT_VM_IDLE)
end
if input.IsKeyDown(KEY_F) and flag1 then
net.Start("buildstop12")
net.SendToServer()
self.Owner.building = false
self.Owner.buildtime = -3
flag1 = false
timer.Simple(1, function() flag1 = true end)
end
local timeLeft = nextOccurance - CurTime()
if timeLeft < 0 and self:GetOwner().buildtime >= 0 then
self:GetOwner().buildtime = self:GetOwner().buildtime - 0.5
self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK)
self:GetOwner():SetAnimation( PLAYER_ATTACK1 )
self:SetNextPrimaryFire( CurTime() + delay)
nextOccurance = CurTime() + delay
end
net.Receive("buildanim", function(len, ply)
print(2)
self:GetOwner().buildtime = 20
end)
end
end
if SERVER then
function SWEP:Think()
net.Receive("buildstop12", function(len, ply)
ply:Freeze(false)
if self.LastEnt:IsValid() then
self.Structures[self.LastEnt.id].count = self.Structures[self.LastEnt.id].count + 1
self.LastEnt:Remove()
end
end)
net.Receive("buildtrench", function(len, ply)
if ply:GetActiveWeapon():GetClass() != "engineertool" then return end
local id = net.ReadInt(13)
if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then
if self.Structures[id].count > 0 then
net.Start("buildanim")
net.WriteEntity(item)
net.Send(self:GetOwner())
local item = ents.Create("util_structure")
item:SetPos(ply:GetEyeTrace().HitPos)
item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + Angle(90,ply:EyeAngles().y,0))
item:SetModel(self.Structures[id].model)
item.hp = self.Structures[id].hp
item.id = id
item.poscoef = -100
item.building = true
item.Owner = self:GetOwner()
item:Spawn()
self.LastEnt = item
self.Structures[id].count = self.Structures[id].count - 1
end
end
end)
net.Receive("setentity", function(len, ply)
if ply:GetActiveWeapon():GetClass() != "engineertoolmines" then return end
local id = net.ReadInt(13)
if (ply:GetEyeTrace().HitPos - ply:GetEyeTrace().StartPos):Length() <= 300 then
if self.Structures[id].count > 0 then
local item = ents.Create(self.Structures[id].entity)
item:SetPos(ply:GetEyeTrace().HitPos + ply:GetEyeTrace().HitNormal * 40)
item:SetAngles(ply:GetEyeTrace().HitNormal:Angle() + self.Structures[id].corrangle)
item.id = id
item.Owner = self:GetOwner()
item:Spawn()
self.LastEnt = item
self.Structures[id].count = self.Structures[id].count - 1
end
end
end)
if (Sprint == 0) then
if self.Owner:KeyDown(IN_SPEED) and (self.Owner:KeyDown(IN_FORWARD) || self.Owner:KeyDown(IN_BACK) || self.Owner:KeyDown(IN_MOVELEFT) || self.Owner:KeyDown(IN_MOVERIGHT)) then
Sprint = 1
end
end
if (Sprint == 1) then
self.Weapon:SendWeaponAnim(ACT_VM_SPRINT_IDLE)
Sprint = 2
end
if (Sprint == 2) then
if self.Owner:KeyReleased(IN_SPEED) then
self.Weapon:SendWeaponAnim(ACT_VM_IDLE)
Sprint = 0
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More