C#:
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.IO;
using Rust;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("Stadia", "fufeldev", "1.0.0")]
public class Stadia : RustPlugin
{
[PluginReference] private Plugin FBradleyRoute;
private const string UiRoot = "Stadia_UI";
private const string PhaseBackplate = "Hudaima_TopStats_Backplates_1";
private const string PhaseTime = "phase_time";
private const string PhaseWarmup = "phase_warmup";
private const string PhasePvp = "phase_pvp";
private const string PhaseBuild = "phase_build";
private const string PhaseRaid = "phase_raid";
private const int PhaseWarmupSeconds = 300;
private const int PhasePvpSeconds = 1200;
private const int PhaseBuildSeconds = 3600;
private const int PhaseRaidSeconds = 7200;
private const float BradleySpawnDelay = 180f;
private const float HeliSpawnDelay = 420f;
private const string HeliPrefab = "assets/prefabs/npc/patrol helicopter/patrolhelicopter.prefab";
private readonly Dictionary<string, string> _imageStorageIds = new Dictionary<string, string>();
private readonly HashSet<ulong> _openedPlayers = new HashSet<ulong>();
private readonly Dictionary<ulong, int> _kills = new Dictionary<ulong, int>();
private readonly Dictionary<ulong, int> _deaths = new Dictionary<ulong, int>();
private string _backplatePng;
private string _phaseTimePng;
private string _phaseBuildPng;
private string _phaseName = PhaseRaid;
private int _phaseRemainingSeconds;
private long _phaseEndsAtUnix;
private Timer _phaseTimer;
private Timer _saveTimer;
private Timer _bradleySpawnTimer;
private Timer _heliSpawnTimer;
private class StoredData
{
public string WipeId;
public string CurrentPhase;
public long PhaseEndsAtUnix;
public long SavedAtUnix;
}
private StoredData _data = new StoredData();
private static readonly Dictionary<string, string> ImageFiles = new Dictionary<string, string>
{
{ PhaseBackplate, "Hudaima_TopStats_Backplates_1.png" },
{ PhaseTime, "phase time.png" },
{ PhaseBuild, "phase build.png" }
};
private void OnServerInitialized()
{
LoadImageAssets();
LoadData();
RestorePhase();
_phaseTimer = timer.Every(1f, TickPhase);
_saveTimer = timer.Every(120f, SavePhaseData);
timer.Once(1f, ShowAll);
}
private void OnPlayerInit(BasePlayer player)
{
QueueShow(player);
}
private void OnPlayerConnected(BasePlayer player)
{
QueueShow(player);
}
private void OnPlayerRespawned(BasePlayer player)
{
QueueShow(player);
}
private void QueueShow(BasePlayer player)
{
if (player == null || player.IsNpc)
{
return;
}
timer.Once(2f, () =>
{
if (player != null && player.IsConnected && !player.IsNpc)
{
Show(player);
}
});
}
private void LoadImageAssets()
{
var imageDir = Path.Combine(Interface.Oxide.DataDirectory, "Stadia", "Images");
Directory.CreateDirectory(imageDir);
_imageStorageIds.Clear();
foreach (var kv in ImageFiles)
{
var path = Path.Combine(imageDir, kv.Value);
if (!File.Exists(path))
{
PrintWarning($"[UIAssets] Missing PNG file: {path}");
continue;
}
try
{
var bytes = File.ReadAllBytes(path);
_imageStorageIds[kv.Key] = FileStorage.server.Store(bytes, FileStorage.Type.png, CommunityEntity.ServerInstance.net.ID).ToString();
}
catch (Exception ex)
{
PrintWarning($"[UIAssets] Failed to load {kv.Key}: {ex.Message}");
}
}
_backplatePng = Img(PhaseBackplate);
_phaseTimePng = Img(PhaseTime);
_phaseBuildPng = Img(PhaseBuild);
}
private string Img(string key)
{
return _imageStorageIds.TryGetValue(key, out var stored) ? stored : string.Empty;
}
private void LoadData()
{
try
{
_data = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name) ?? new StoredData();
}
catch
{
_data = new StoredData();
}
}
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject(Name, _data);
}
private void SavePhaseData()
{
if (_data == null) _data = new StoredData();
_data.WipeId = SaveRestore.WipeId;
_data.CurrentPhase = _phaseName;
_data.PhaseEndsAtUnix = _phaseEndsAtUnix;
_data.SavedAtUnix = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
SaveData();
}
private void RestorePhase()
{
if (_data == null) _data = new StoredData();
if (IsNewWipe())
{
_data.CurrentPhase = PhaseWarmup;
_data.PhaseEndsAtUnix = 0;
_data.WipeId = SaveRestore.WipeId;
SetPhase(PhaseWarmup, false);
return;
}
string phase = NormalizePhase(_data.CurrentPhase);
if (string.IsNullOrEmpty(phase) || _data.PhaseEndsAtUnix <= 0)
{
SetPhase(PhaseWarmup, false);
return;
}
long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
long endsAt = _data.PhaseEndsAtUnix;
for (int i = 0; i < 10000 && endsAt <= now; i++)
{
phase = GetNextPhase(phase);
endsAt += GetPhaseSeconds(phase);
}
if (endsAt <= now)
{
SetPhase(PhaseBuild, false);
return;
}
SetPhase(phase, false, Math.Max(1, (int)(endsAt - now)));
_phaseEndsAtUnix = endsAt;
SavePhaseData();
}
private bool IsNewWipe()
{
string wipeId = SaveRestore.WipeId;
if (!string.IsNullOrEmpty(_data.WipeId) && !string.IsNullOrEmpty(wipeId) && _data.WipeId != wipeId) return true;
if (!string.IsNullOrEmpty(_data.WipeId) || _data.SavedAtUnix <= 0 || SaveRestore.SaveCreatedTime.Year < 2020) return false;
long saveCreatedAt = new DateTimeOffset(SaveRestore.SaveCreatedTime.ToUniversalTime()).ToUnixTimeSeconds();
return saveCreatedAt > _data.SavedAtUnix;
}
private string NormalizePhase(string phase)
{
if (string.IsNullOrWhiteSpace(phase)) return null;
phase = phase.ToLowerInvariant();
if (phase == "warmup" || phase == PhaseWarmup || phase == "time" || phase == PhaseTime) return PhaseWarmup;
if (phase == "pvp" || phase == PhasePvp) return PhasePvp;
if (phase == "build" || phase == PhaseBuild) return PhaseBuild;
if (phase == "raid" || phase == PhaseRaid) return PhaseRaid;
return null;
}
private string GetNextPhase(string phase)
{
if (phase == PhaseWarmup) return PhaseBuild;
if (phase == PhaseBuild) return PhasePvp;
if (phase == PhasePvp) return PhaseRaid;
return PhaseBuild;
}
[ChatCommand("stadia.show")]
private void ShowUICommand(BasePlayer player, string command, string[] args)
{
Show(player);
}
[ChatCommand("stadia.hide")]
private void HideUICommand(BasePlayer player, string command, string[] args)
{
Hide(player);
}
[ChatCommand("stadia.phase")]
private void PhaseChatCommand(BasePlayer player, string command, string[] args)
{
if (player == null || !player.IsAdmin) return;
SetPhase(args);
}
[ChatCommand("stage")]
private void StageChatCommand(BasePlayer player, string command, string[] args)
{
PhaseChatCommand(player, command, args);
}
[ConsoleCommand("stadia.show")]
private void ShowConsoleCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null)
{
ShowAll();
return;
}
Show(player);
}
[ConsoleCommand("stadia.hide")]
private void HideConsoleCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null)
{
HideAll();
return;
}
Hide(player);
}
[ConsoleCommand("stadia.phase")]
private void PhaseConsoleCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player != null && !player.IsAdmin) return;
if (arg.Args == null || arg.Args.Length == 0) return;
SetPhase(arg.Args[0].ToString(), true, arg.Args.Length > 1 ? ParsePhaseSeconds(arg.Args[1].ToString()) : -1);
}
[ConsoleCommand("stage")]
private void StageConsoleCommand(ConsoleSystem.Arg arg)
{
PhaseConsoleCommand(arg);
}
[ConsoleCommand("stadia.test")]
private void TestConsoleCommand(ConsoleSystem.Arg arg)
{
ShowAll();
}
private void OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (player == null || player.IsNpc)
{
return;
}
if (!_deaths.ContainsKey(player.userID))
{
_deaths[player.userID] = 0;
}
_deaths[player.userID]++;
if (info != null && (info.InitiatorPlayer == player || info.damageTypes.Has(DamageType.Suicide)))
{
UpdatePlayer(player);
return;
}
var killer = info?.InitiatorPlayer;
if (killer != null && killer != player && !killer.IsNpc)
{
if (!_kills.ContainsKey(killer.userID))
{
_kills[killer.userID] = 0;
}
_kills[killer.userID]++;
UpdatePlayer(killer);
}
UpdatePlayer(player);
}
private void SetPhase(string[] args)
{
if (args == null || args.Length == 0)
{
return;
}
SetPhase(args[0], true, args.Length > 1 ? ParsePhaseSeconds(args[1]) : -1);
}
private void SetPhase(string phase, Boolean notify = true, Int32 customSeconds = -1)
{
phase = NormalizePhase(phase);
if (string.IsNullOrEmpty(phase)) return;
_phaseName = phase;
_phaseRemainingSeconds = customSeconds > 0 ? customSeconds : GetPhaseSeconds(_phaseName);
_phaseEndsAtUnix = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + Math.Max(1, _phaseRemainingSeconds);
SavePhaseData();
if (notify) Interface.CallHook("OnStadiaPhaseChanged", _phaseName);
if (phase == PhasePvp && notify)
{
SchedulePvpSpawns();
}
else if (phase != PhasePvp)
{
CancelPvpSpawns();
}
RebuildOpened();
}
private Int32 ParsePhaseSeconds(string value)
{
if (String.IsNullOrWhiteSpace(value)) return -1;
value = value.Trim().ToLowerInvariant();
if (Int32.TryParse(value, out Int32 seconds)) return seconds;
if (value.EndsWith("s") && Int32.TryParse(value.Substring(0, value.Length - 1), out seconds)) return seconds;
if (value.EndsWith("m") && Int32.TryParse(value.Substring(0, value.Length - 1), out seconds)) return seconds * 60;
if (value.EndsWith("h") && Int32.TryParse(value.Substring(0, value.Length - 1), out seconds)) return seconds * 3600;
return -1;
}
private void TickPhase()
{
if (_phaseRemainingSeconds <= 0)
{
return;
}
_phaseRemainingSeconds--;
if (_phaseRemainingSeconds > 0)
{
UpdatePhaseTime();
return;
}
if (_phaseName == PhaseWarmup)
{
SetPhase(PhaseBuild);
return;
}
if (_phaseName == PhaseBuild)
{
SetPhase(PhasePvp);
return;
}
if (_phaseName == PhasePvp)
{
SetPhase(PhaseRaid);
return;
}
UpdatePhaseTime();
}
private void Show(BasePlayer player)
{
if (player == null || !player.IsConnected)
{
return;
}
_openedPlayers.Add(player.userID);
CuiHelper.DestroyUi(player, UiRoot);
CuiHelper.AddUi(player, CreateUI(player));
}
private void ShowAll()
{
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsNpc)
{
continue;
}
Show(player);
}
}
private void Hide(BasePlayer player)
{
if (player == null)
{
return;
}
_openedPlayers.Remove(player.userID);
CuiHelper.DestroyUi(player, UiRoot);
}
private void HideAll()
{
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsNpc)
{
continue;
}
Hide(player);
}
}
private void UpdateAll()
{
if (_openedPlayers.Count == 0)
{
return;
}
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsNpc)
{
continue;
}
if (_openedPlayers.Contains(player.userID))
{
CuiHelper.AddUi(player, CreateUpdateContainer(player));
}
}
}
private void RebuildOpened()
{
if (_openedPlayers.Count == 0) return;
List<ulong> opened = new List<ulong>(_openedPlayers);
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsNpc) continue;
if (!opened.Contains(player.userID)) continue;
Show(player);
}
}
private void UpdatePlayer(BasePlayer player)
{
if (player == null || !player.IsConnected || player.IsNpc)
{
return;
}
if (!_openedPlayers.Contains(player.userID))
{
return;
}
CuiHelper.AddUi(player, CreateUpdateContainer(player));
}
private void UpdatePhaseTime()
{
if (_openedPlayers.Count == 0)
{
return;
}
var container = CreatePhaseTimeUpdateContainer();
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsNpc)
{
continue;
}
if (_openedPlayers.Contains(player.userID))
{
CuiHelper.AddUi(player, container);
}
}
}
private int GetKills(ulong userId)
{
return _kills.TryGetValue(userId, out var value) ? value : 0;
}
private int GetDeaths(ulong userId)
{
return _deaths.TryGetValue(userId, out var value) ? value : 0;
}
private float GetKd(ulong userId)
{
var deaths = GetDeaths(userId);
return deaths <= 0 ? GetKills(userId) : (float)GetKills(userId) / deaths;
}
private CuiElementContainer CreateUI(BasePlayer player)
{
var container = new CuiElementContainer();
container.Add(new CuiElement
{
Name = UiRoot,
Parent = "Hud",
Components =
{
new CuiRectTransformComponent { AnchorMin = "0 0", AnchorMax = "1 1", OffsetMin = "0 0", OffsetMax = "0 0" }
}
});
AddBackplate(container);
AddPhaseText(container);
AddKills(container, player);
AddDeaths(container, player);
AddKd(container, player);
return container;
}
private CuiElementContainer CreateUpdateContainer(BasePlayer player)
{
var container = new CuiElementContainer();
container.Add(new CuiElement
{
Name = "Hudaima_TopStats_Backplates_1",
Update = true,
Components =
{
new CuiRawImageComponent { Color = "1 1 1 1", Png = CurrentBackplatePng() }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Warmup_Title_Text_1",
Update = true,
Components =
{
new CuiTextComponent { Text = CurrentPhaseText(), FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Warmup_Value_Text_1",
Update = true,
Components =
{
new CuiTextComponent { Text = CurrentPhaseValueText(), FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Kills_Value_Text_1",
Update = true,
Components =
{
new CuiTextComponent { Text = GetKills(player.userID).ToString(), FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1.000 0.278 0.278 1", Font = "robotocondensed-bold.ttf" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Death_Value_Text_1",
Update = true,
Components =
{
new CuiTextComponent { Text = GetDeaths(player.userID).ToString(), FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1.000 0.278 0.278 1", Font = "robotocondensed-bold.ttf" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Kd_Value_Text_1",
Update = true,
Components =
{
new CuiTextComponent { Text = GetKd(player.userID).ToString("0.0"), FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1.000 0.278 0.278 1", Font = "robotocondensed-bold.ttf" }
}
});
return container;
}
private CuiElementContainer CreatePhaseTimeUpdateContainer()
{
var container = new CuiElementContainer();
container.Add(new CuiElement
{
Name = "Hudaima_Warmup_Value_Text_1",
Update = true,
Components =
{
new CuiTextComponent { Text = CurrentPhaseValueText(), FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" }
}
});
return container;
}
private void AddBackplate(CuiElementContainer container)
{
container.Add(new CuiElement
{
Name = "Hudaima_TopStats_Backplates_1",
Parent = UiRoot,
Components =
{
new CuiRawImageComponent { Color = "1 1 1 1", Png = CurrentBackplatePng() },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "20.6667 -85.3333", OffsetMax = "237.3333 -24.0000" }
}
});
}
private void AddPhaseText(CuiElementContainer container)
{
container.Add(new CuiElement
{
Name = "Hudaima_Warmup_Title_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = CurrentPhaseText(), FontSize = 12, Align = TextAnchor.MiddleLeft, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "34.0000 -46.0000", OffsetMax = "138.0000 -30.0000" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Warmup_Value_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = CurrentPhaseValueText(), FontSize = 11, Align = TextAnchor.MiddleCenter, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "179.0000 -46.6667", OffsetMax = "228.3333 -30.0000" }
}
});
}
private void AddKills(CuiElementContainer container, BasePlayer player)
{
var kills = GetKills(player.userID);
container.Add(new CuiElement
{
Name = "Hudaima_Kills_Value_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = kills.ToString(), FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1.000 0.278 0.278 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "30.0000 -80.0000", OffsetMax = "60.0000 -62.6667" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Kills_Label_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = "KILLS", FontSize = 8, Align = TextAnchor.MiddleLeft, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "53.3333 -79.3333", OffsetMax = "88.0000 -67.3333" }
}
});
}
private void AddDeaths(CuiElementContainer container, BasePlayer player)
{
var deaths = GetDeaths(player.userID);
container.Add(new CuiElement
{
Name = "Hudaima_Death_Value_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = deaths.ToString(), FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1.000 0.278 0.278 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "101.6667 -80.0000", OffsetMax = "129.6667 -62.6667" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Death_Label_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = "DEATH", FontSize = 8, Align = TextAnchor.MiddleLeft, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "121.3333 -79.3333", OffsetMax = "156.0000 -67.3333" }
}
});
}
private void AddKd(CuiElementContainer container, BasePlayer player)
{
var kd = GetKd(player.userID);
container.Add(new CuiElement
{
Name = "Hudaima_Kd_Value_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = kd.ToString("0.0"), FontSize = 14, Align = TextAnchor.MiddleCenter, Color = "1.000 0.278 0.278 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "175.6667 -80.0000", OffsetMax = "216.3333 -62.6667" }
}
});
container.Add(new CuiElement
{
Name = "Hudaima_Kd_Label_Text_1",
Parent = UiRoot,
Components =
{
new CuiTextComponent { Text = "KD", FontSize = 8, Align = TextAnchor.MiddleLeft, Color = "0.925 0.925 0.925 1", Font = "robotocondensed-bold.ttf" },
new CuiRectTransformComponent { AnchorMin = "0.0000 1.0000", AnchorMax = "0.0000 1.0000", OffsetMin = "206.0000 -79.3333", OffsetMax = "223.3333 -67.3333" }
}
});
}
private string CurrentBackplatePng()
{
if (_phaseName == PhaseWarmup || _phaseName == PhasePvp)
{
return _phaseTimePng;
}
if (_phaseName == PhaseBuild)
{
return _phaseBuildPng;
}
return _backplatePng;
}
private string CurrentPhaseText()
{
if (_phaseName == PhaseWarmup)
{
return "WARMUP TIME";
}
if (_phaseName == PhasePvp)
{
return "PVP PHASE";
}
if (_phaseName == PhaseBuild)
{
return "BUILD PHASE";
}
return "RAID PHASE";
}
private string CurrentPhaseValueText()
{
var seconds = Mathf.Max(0, _phaseRemainingSeconds);
if (seconds >= 3600)
{
return TimeSpan.FromSeconds(seconds).ToString(@"hh\:mm\:ss");
}
return TimeSpan.FromSeconds(seconds).ToString(@"mm\:ss");
}
[HookMethod("GetCurrentPhase")]
public string GetCurrentPhase() => _phaseName;
private int GetPhaseSeconds(string phase)
{
if (phase == PhaseWarmup)
{
return PhaseWarmupSeconds;
}
if (phase == PhasePvp)
{
return PhasePvpSeconds;
}
if (phase == PhaseBuild)
{
return PhaseBuildSeconds;
}
return PhaseRaidSeconds;
}
private void SchedulePvpSpawns()
{
CancelPvpSpawns();
_bradleySpawnTimer = timer.Once(BradleySpawnDelay, SpawnBradleyApc);
_heliSpawnTimer = timer.Once(HeliSpawnDelay, SpawnPatrolHeli);
}
private void CancelPvpSpawns()
{
DestroyTimer(ref _bradleySpawnTimer);
DestroyTimer(ref _heliSpawnTimer);
}
private void SpawnBradleyApc()
{
if (FBradleyRoute != null)
{
var result = FBradleyRoute.Call("SpawnRouteBradley");
if (result != null)
{
Puts($"[PVP] Bradley spawned via FBradleyRoute at route point, netId={result}");
return;
}
Puts("[PVP] SpawnRouteBradley returned null, falling back to BradleySpawner");
}
else
{
Puts("[PVP] FBradleyRoute plugin not loaded, using BradleySpawner");
}
var spawner = BradleySpawner.singleton;
if (spawner == null)
{
Puts("[PVP] BradleySpawner.singleton is null");
return;
}
if (spawner.spawned != null && spawner.spawned.IsAlive())
{
Puts("[PVP] Bradley already alive, skip");
return;
}
spawner.spawned = null;
spawner.SpawnBradley();
Puts("[PVP] Bradley spawned via BradleySpawner.SpawnBradley()");
}
private void SpawnPatrolHeli()
{
var entity = GameManager.server.CreateEntity(HeliPrefab);
if (entity == null) return;
entity.Spawn();
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
if (_phaseName != PhasePvp) return;
if (entity == null || info == null || info.damageTypes == null) return;
if (info.WeaponPrefab == null) return;
string weaponName = info.WeaponPrefab.name;
if (!weaponName.Contains("rocket") && !weaponName.Contains("explosive.timed")) return;
if (entity is BasePlayer || entity is BradleyAPC) return;
info.damageTypes.Clear();
}
private void Unload()
{
SavePhaseData();
DestroyTimer(ref _phaseTimer);
DestroyTimer(ref _saveTimer);
CancelPvpSpawns();
foreach (var player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsNpc)
{
continue;
}
CuiHelper.DestroyUi(player, UiRoot);
}
_openedPlayers.Clear();
}
private void DestroyTimer(ref Timer value)
{
if (value == null) return;
value.Destroy();
value = null;
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
if (player == null)
{
return;
}
_openedPlayers.Remove(player.userID);
}
}
}