add sborka
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
})
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user