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