C#:
///////////////////////////////////////////////////////////////////////////
// Made by RubMyBricks //
// Discord: rubmybricks //
// DiscordServer: https://discord.gg/gPg92292HS //
// Website: www.bricksx.xyz //
// //
// Feel free to message me on //
// discord if you have any issues! //
///////////////////////////////////////////////////////////////////////////
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using UnityEngine;
using System.Collections.Generic;
using Newtonsoft.Json;
using System;
using System.Linq;
namespace Oxide.Plugins
{
[Info("Safezone Respawner", "RubMyBricks", "1.5")]
[Description("Allows players to respawn at safezones upon death!")]
public class SafezoneRespawner : RustPlugin
{
[PluginReference] private Plugin ImageLibrary;
#region Constants
private const string PermissionUse = "safezonerespawner.use";
private const string GUI_PANEL_NAME = "SafezoneRespawnGUI";
private const string BANDIT_PANEL_NAME = "BanditRespawnGUI";
private const string OUTPOST_IMAGE_ID = "safezone_outpost_button";
private const string BANDIT_IMAGE_ID = "safezone_bandit_button";
#endregion
#region Fields
private Vector3 outpostPosition;
private Vector3 banditPosition;
private Vector3 outpostSpawnPoint;
private Vector3 banditSpawnPoint;
private bool outpostFound = false;
private bool banditFound = false;
private Dictionary<ulong, List<SleepingBag>> tempBags = new Dictionary<ulong, List<SleepingBag>>();
private Dictionary<ulong, HashSet<string>> renderedReady = new Dictionary<ulong, HashSet<string>>();
private const string BAG_PREFAB = "assets/prefabs/deployable/sleeping bag/sleepingbag_leather_deployed.prefab";
private Dictionary<ulong, Timer> guiTimers = new Dictionary<ulong, Timer>();
private Dictionary<ulong, Dictionary<string, DateTime>> playerCooldowns = new Dictionary<ulong, Dictionary<string, DateTime>>();
private HashSet<ulong> recentlyRespawned = new HashSet<ulong>();
private bool isInitialized = false;
private ConfigData config;
//cache
private HashSet<ulong> permissionCache = new HashSet<ulong>();
private readonly Dictionary<ulong, BasePlayer> playerCache = new Dictionary<ulong, BasePlayer>();
private Timer cacheCleanupTimer;
private const int CACHE_CLEANUP_INTERVAL = 300;
#endregion
#region Configuration
private class ConfigData
{
[JsonProperty(PropertyName = "Enable Outpost Respawn")]
public bool EnableOutpost { get; set; } = true;
[JsonProperty(PropertyName = "Enable Bandit Camp Respawn")]
public bool EnableBandit { get; set; } = false;
[JsonProperty(PropertyName = "Remove Hostility On Respawn")]
public bool RemoveHostilityOnRespawn { get; set; } = false;
[JsonProperty(PropertyName = "Use ImageLibrary for Icons")]
public bool UseImageLibrary { get; set; } = false;
[JsonProperty(PropertyName = "Spawn Settings")]
public SpawnSettings SpawnSettings { get; set; } = new SpawnSettings();
[JsonProperty(PropertyName = "Custom Images")]
public CustomImages Images { get; set; } = new CustomImages();
[JsonProperty(PropertyName = "GUI Settings")]
public GUISettings GuiSettings { get; set; } = new GUISettings();
[JsonProperty(PropertyName = "Manual Spawn Points")]
public ManualSpawnSettings ManualSpawns { get; set; } = new ManualSpawnSettings();
[JsonProperty(PropertyName = "Respawn Method (UI, Bed, or Both)")]
public string RespawnMethod { get; set; } = "Bed";
[JsonProperty(PropertyName = "Bed Settings")]
public BedSettings BedSettings { get; set; } = new BedSettings();
}
private class SpawnSettings
{
[JsonProperty(PropertyName = "Outpost Cooldown (seconds)")]
public float OutpostCooldown { get; set; } = 120f;
[JsonProperty(PropertyName = "Bandit Camp Cooldown (seconds)")]
public float BanditCooldown { get; set; } = 120f;
[JsonProperty(PropertyName = "Enable Health Setting")]
public bool EnableHealth { get; set; } = false;
[JsonProperty(PropertyName = "Enable Food Setting")]
public bool EnableFood { get; set; } = false;
[JsonProperty(PropertyName = "Enable Water Setting")]
public bool EnableWater { get; set; } = false;
[JsonProperty(PropertyName = "Spawn Health (max =100")]
public float SpawnHealth { get; set; } = 100f;
[JsonProperty(PropertyName = "Spawn Food (max =300?)")]
public float SpawnFood { get; set; } = 300f;
[JsonProperty(PropertyName = "Spawn Water (max =250)")]
public float SpawnWater { get; set; } = 200f;
[JsonProperty(PropertyName = "Spawn Position Offsets")]
public SpawnOffsets SpawnOffsets { get; set; } = new SpawnOffsets();
}
private class PositionData
{
public float x { get; set; }
public float y { get; set; }
public float z { get; set; }
public PositionData() { }
public PositionData(float x, float y, float z) { this.x = x; this.y = y; this.z = z; }
public Vector3 ToVector3() => new Vector3(x, y, z);
}
private class SpawnOffsets
{
[JsonProperty(PropertyName = "Outpost Offset (X,Y,Z)")]
public PositionData OutpostOffset { get; set; } = new PositionData(0f, 0f, -23f);
[JsonProperty(PropertyName = "Bandit Camp Offset (X,Y,Z)")]
public PositionData BanditOffset { get; set; } = new PositionData(0f, 0f, -25f);
[JsonProperty(PropertyName = "Height Above Ground")]
public float HeightAboveGround { get; set; } = 0.3f;
}
private class CustomImages
{
[JsonProperty(PropertyName = "Outpost Button Image URL")]
public string OutpostImage { get; set; } = "your_custom_image_url";
[JsonProperty(PropertyName = "Bandit Button Image URL")]
public string BanditImage { get; set; } = "your_custom_image_url";
}
private class GUISettings
{
[JsonProperty(PropertyName = "Outpost Button Color")]
public string OutpostColor { get; set; } = "0.42 0.55 0.24 1";
[JsonProperty(PropertyName = "Bandit Button Color")]
public string BanditColor { get; set; } = "0.55 0.42 0.24 1";
[JsonProperty(PropertyName = "Text Color")]
public string TextColor { get; set; } = "1 1 1 1";
[JsonProperty(PropertyName = "Cooldown Text Color")]
public string CooldownColor { get; set; } = "0.7 0.7 0.7 1";
[JsonProperty(PropertyName = "Disabled Button Color")]
public string DisabledColor { get; set; } = "0.3 0.3 0.3 1";
}
private class ManualSpawnSettings
{
[JsonProperty(PropertyName = "Use Manual Outpost Position (use for custom maps)")]
public bool UseManualOutpost { get; set; } = false;
[JsonProperty(PropertyName = "Manual Outpost Position (X,Y,Z)")]
public PositionData ManualOutpostPosition { get; set; } = new PositionData();
[JsonProperty(PropertyName = "Use Manual Bandit Position (use for custiom maps)")]
public bool UseManualBandit { get; set; } = false;
[JsonProperty(PropertyName = "Manual Bandit Position (X,Y,Z)")]
public PositionData ManualBanditPosition { get; set; } = new PositionData();
}
private class BedSettings
{
[JsonProperty(PropertyName = "Outpost Bed Name")]
public string OutpostBedName { get; set; } = "Outpost";
[JsonProperty(PropertyName = "Bandit Camp Bed Name")]
public string BanditBedName { get; set; } = "Bandit Camp";
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<ConfigData>();
if (config == null) throw new System.Exception();
SaveConfig();
}
catch
{
PrintWarning("Config file is corrupt :(, creating new config file!");
LoadDefaultConfig();
}
}
protected override void LoadDefaultConfig()
{
config = new ConfigData();
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
#endregion
#region Initialization
void OnServerInitialized()
{
permission.RegisterPermission(PermissionUse, this);
InitializeSpawnPoints();
InitializeCaching();
if (config.UseImageLibrary)
{
if (ImageLibrary == null)
{
PrintWarning("ImageLibrary is not loaded! Plugin will use default icons.");
config.UseImageLibrary = false;
SaveConfig();
}
else
{
string outpostUrl = EnsureHttps(config.Images.OutpostImage);
string banditUrl = EnsureHttps(config.Images.BanditImage);
bool outpostSuccess = ImageLibrary.Call<bool>("AddImage", outpostUrl, OUTPOST_IMAGE_ID);
if (!outpostSuccess)
{
PrintWarning($"Failed to load outpost image from {outpostUrl}");
}
bool banditSuccess = ImageLibrary.Call<bool>("AddImage", banditUrl, BANDIT_IMAGE_ID);
if (!banditSuccess)
{
PrintWarning($"Failed to load bandit camp image from {banditUrl}");
}
}
}
}
private string EnsureHttps(string url)
{
if (string.IsNullOrEmpty(url)) return url;
if (url.StartsWith("http://i.imgur.com"))
{
return url.Replace("http://", "https://");
}
else if (url.Contains("imgur.com") && !url.Contains("i.imgur.com"))
{
string[] urlParts = url.Split('/');
string imgurId = urlParts[urlParts.Length - 1];
return $"https://i.imgur.com/{imgurId}.png";
}
return url;
}
private void InitializeSpawnPoints()
{
try
{
Puts("Searching for safezones...");
outpostFound = false;
banditFound = false;
var monuments = TerrainMeta.Path.Monuments;
Puts($"Found {monuments.Count} monuments total");
foreach (var monument in monuments)
{
string monumentName = monument.name.ToLower();
Puts($"Checking monument: {monumentName}");
if (monumentName.Contains("outpost") || monumentName.Contains("compound"))
{
Puts($"Found Outpost at position: {monument.transform.position}");
outpostPosition = monument.transform.position;
Vector3 outpostTarget = outpostPosition + config.SpawnSettings.SpawnOffsets.OutpostOffset.ToVector3();
outpostSpawnPoint = GetGroundPosition(outpostTarget);
outpostFound = true;
Puts($"Set outpost spawn point at: {outpostSpawnPoint}");
}
else if (monumentName.Contains("bandit"))
{
Puts($"Found Bandit Camp at position: {monument.transform.position}");
banditPosition = monument.transform.position;
Vector3 banditTarget = banditPosition + config.SpawnSettings.SpawnOffsets.BanditOffset.ToVector3();
banditSpawnPoint = GetGroundPosition(banditTarget);
banditFound = true;
Puts($"Set bandit spawn point at: {banditSpawnPoint}");
}
}
if (config.ManualSpawns.UseManualOutpost)
{
outpostSpawnPoint = config.ManualSpawns.ManualOutpostPosition.ToVector3();
outpostPosition = outpostSpawnPoint;
outpostFound = true;
Puts($"Using MANUAL outpost spawn point: {outpostSpawnPoint}");
}
if (config.ManualSpawns.UseManualBandit)
{
banditSpawnPoint = config.ManualSpawns.ManualBanditPosition.ToVector3();
banditPosition = banditSpawnPoint;
banditFound = true;
Puts($"Using MANUAL bandit spawn point: {banditSpawnPoint}");
}
if (config.EnableOutpost && !outpostFound)
PrintWarning("Outpost/Compound was not found on this map. Outpost respawn is disabled to avoid spawning players under the world.");
if (config.EnableBandit && !banditFound)
PrintWarning("Bandit Camp was not found on this map. Bandit respawn is disabled to avoid spawning players under the world.");
isInitialized = true;
Puts("Spawn points initialized successfully");
}
catch (System.Exception ex)
{
PrintError($"Error initializing spawn points: {ex}");
}
}
private Vector3 GetGroundPosition(Vector3 pos)
{
float heightmapY = TerrainMeta.HeightMap.GetHeight(pos);
float startY = Mathf.Max(pos.y, heightmapY) + 250f;
Vector3 start = new Vector3(pos.x, startY, pos.z);
int mask = LayerMask.GetMask("Terrain", "World", "Construction", "Default");
float bestY = float.MinValue;
RaycastHit[] hits = Physics.RaycastAll(start, Vector3.down, 600f, mask, QueryTriggerInteraction.Ignore);
foreach (var hit in hits)
{
if (hit.collider == null) continue;
if (hit.point.y > bestY)
bestY = hit.point.y;
}
float groundY = bestY > float.MinValue ? bestY : heightmapY;
float waterY = TerrainMeta.WaterMap != null ? TerrainMeta.WaterMap.GetHeight(pos) : 0f;
if (groundY < waterY)
groundY = waterY;
return new Vector3(pos.x, groundY + config.SpawnSettings.SpawnOffsets.HeightAboveGround, pos.z);
}
private Quaternion GetSpawnRotation(Vector3 lookTarget, Vector3 from)
{
Vector3 dir = lookTarget - from;
dir.y = 0f;
if (dir.sqrMagnitude < 0.01f) return Quaternion.identity;
return Quaternion.LookRotation(dir.normalized);
}
private void InitializeCaching()
{
cacheCleanupTimer?.Destroy();
cacheCleanupTimer = timer.Every(CACHE_CLEANUP_INTERVAL, CleanupCaches);
}
private void CleanupCaches()
{
var offlinePermPlayers = permissionCache.ToList();
foreach (var userId in offlinePermPlayers)
{
var player = BasePlayer.FindByID(userId);
if (player == null || !player.IsConnected)
{
permissionCache.Remove(userId);
}
}
var offlinePlayers = playerCache.Keys.ToList();
foreach (var userId in offlinePlayers)
{
var player = BasePlayer.FindByID(userId);
if (player == null || !player.IsConnected)
{
playerCache.Remove(userId);
}
}
}
#endregion
#region Cooldown Methods
private bool IsOnCooldown(ulong userId, string location)
{
if (!playerCooldowns.ContainsKey(userId))
return false;
if (!playerCooldowns[userId].ContainsKey(location))
return false;
var timeRemaining = GetCooldownTimeRemaining(userId, location);
return timeRemaining > 0;
}
private double GetCooldownTimeRemaining(ulong userId, string location)
{
if (!playerCooldowns.ContainsKey(userId) || !playerCooldowns[userId].ContainsKey(location))
return 0;
var timeSinceLast = DateTime.UtcNow - playerCooldowns[userId][location];
float cooldownSeconds = location == "outpost"
? config.SpawnSettings.OutpostCooldown
: config.SpawnSettings.BanditCooldown;
return Math.Max(0, cooldownSeconds - timeSinceLast.TotalSeconds);
}
private void StartCooldown(ulong userId, string location)
{
if (!playerCooldowns.ContainsKey(userId))
playerCooldowns[userId] = new Dictionary<string, DateTime>();
playerCooldowns[userId][location] = DateTime.UtcNow;
}
#endregion
#region Spawn Methods
private void SetPlayerSpawnConditions(BasePlayer player)
{
if (player == null) return;
if (config.SpawnSettings.EnableHealth)
{
player.health = config.SpawnSettings.SpawnHealth;
}
var metabolism = player.metabolism;
if (metabolism != null)
{
if (config.SpawnSettings.EnableFood)
{
metabolism.calories.value = config.SpawnSettings.SpawnFood;
}
if (config.SpawnSettings.EnableWater)
{
metabolism.hydration.value = config.SpawnSettings.SpawnWater;
}
}
}
#endregion
#region GUI
private void ShowRespawnGUI(BasePlayer player)
{
if (!isInitialized) return;
var elements = new CuiElementContainer();
const string BUTTON_HEIGHT_MIN = "0.15";
const string BUTTON_HEIGHT_MAX = "0.22";
const float OUTPOST_MIN_X = 0.45f;
const float OUTPOST_MAX_X = 0.55f;
const float BANDIT_MIN_X = 0.56f;
const float BANDIT_MAX_X = 0.66f;
if (config.EnableOutpost && outpostFound)
{
AddRespawnButton(
elements,
$"{GUI_PANEL_NAME}_overlay",
GUI_PANEL_NAME,
OUTPOST_IMAGE_ID,
"safezone.spawn outpost",
"OUTPOST",
config.GuiSettings.OutpostColor,
IsOnCooldown(player.userID, "outpost"),
GetCooldownTimeRemaining(player.userID, "outpost"),
OUTPOST_MIN_X, OUTPOST_MAX_X,
BUTTON_HEIGHT_MIN, BUTTON_HEIGHT_MAX,
TextAnchor.MiddleLeft);
}
if (config.EnableBandit && banditFound)
{
AddRespawnButton(
elements,
$"{BANDIT_PANEL_NAME}_overlay",
BANDIT_PANEL_NAME,
BANDIT_IMAGE_ID,
"safezone.spawn bandit",
"BANDIT",
config.GuiSettings.BanditColor,
IsOnCooldown(player.userID, "bandit"),
GetCooldownTimeRemaining(player.userID, "bandit"),
BANDIT_MIN_X, BANDIT_MAX_X,
BUTTON_HEIGHT_MIN, BUTTON_HEIGHT_MAX,
TextAnchor.MiddleCenter);
}
DestroyGUI(player);
CuiHelper.AddUi(player, elements);
var ready = new HashSet<string>();
if (config.EnableOutpost && outpostFound && GetCooldownTimeRemaining(player.userID, "outpost") <= 0)
ready.Add("outpost");
if (config.EnableBandit && banditFound && GetCooldownTimeRemaining(player.userID, "bandit") <= 0)
ready.Add("bandit");
renderedReady[player.userID] = ready;
ScheduleGuiRefresh(player);
}
private void AddRespawnButton(
CuiElementContainer elements,
string overlayName,
string panelName,
string imageId,
string command,
string label,
string baseColor,
bool onCooldown,
double timeRemaining,
float minX,
float maxX,
string minY,
string maxY,
TextAnchor align)
{
elements.Add(new CuiPanel
{
Image = { Color = "0 0 0 0" },
RectTransform =
{
AnchorMin = $"{minX} {minY}",
AnchorMax = $"{maxX} {maxY}"
},
CursorEnabled = true
}, "Overlay", overlayName);
elements.Add(new CuiPanel
{
Image = { Color = onCooldown ? config.GuiSettings.DisabledColor : baseColor },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
CursorEnabled = true
}, overlayName, panelName);
elements.Add(new CuiElement
{
Name = $"{panelName}_icon",
Parent = panelName,
Components =
{
config.UseImageLibrary && ImageLibrary != null
? (ICuiComponent)new CuiRawImageComponent
{
Png = (string)ImageLibrary.Call("GetImage", imageId)
}
: new CuiImageComponent
{
Sprite = "assets/icons/arrow_right.png",
Color = config.GuiSettings.TextColor
},
new CuiRectTransformComponent
{
AnchorMin = "0.02 0.1",
AnchorMax = "0.25 0.9",
OffsetMin = "1 1",
OffsetMax = "-1 -1"
}
}
});
string buttonText = timeRemaining > 0 ? $"{label} ({timeRemaining:F0}s)" : $"{label} »";
string textColor = timeRemaining > 0 ? config.GuiSettings.CooldownColor : config.GuiSettings.TextColor;
elements.Add(new CuiElement
{
Name = $"{panelName}_label",
Parent = panelName,
Components =
{
new CuiTextComponent
{
Text = buttonText,
FontSize = 18,
Align = align,
Color = textColor
},
new CuiRectTransformComponent { AnchorMin = "0.26 0", AnchorMax = "0.95 1" }
}
});
if (timeRemaining <= 0)
{
elements.Add(new CuiButton
{
Button = { Command = command, Color = "0 0 0 0" },
RectTransform = { AnchorMin = "0 0", AnchorMax = "1 1" },
Text = { Text = "" }
}, panelName);
}
}
private void ScheduleGuiRefresh(BasePlayer player)
{
double outpostCd = config.EnableOutpost && outpostFound ? GetCooldownTimeRemaining(player.userID, "outpost") : 0;
double banditCd = config.EnableBandit && banditFound ? GetCooldownTimeRemaining(player.userID, "bandit") : 0;
if (Math.Max(outpostCd, banditCd) <= 0)
return;
if (guiTimers.ContainsKey(player.userID))
guiTimers[player.userID]?.Destroy();
guiTimers[player.userID] = timer.Once(1f, () => UpdateRespawnGUI(player));
}
private void UpdateRespawnGUI(BasePlayer player)
{
if (player == null || !player.IsConnected || !player.IsDead())
return;
double outpostCd = config.EnableOutpost && outpostFound ? GetCooldownTimeRemaining(player.userID, "outpost") : 0;
double banditCd = config.EnableBandit && banditFound ? GetCooldownTimeRemaining(player.userID, "bandit") : 0;
HashSet<string> ready;
if (!renderedReady.TryGetValue(player.userID, out ready))
ready = new HashSet<string>();
bool needFullRebuild =
(config.EnableOutpost && outpostFound && outpostCd <= 0 && !ready.Contains("outpost")) ||
(config.EnableBandit && banditFound && banditCd <= 0 && !ready.Contains("bandit"));
if (needFullRebuild)
{
ShowRespawnGUI(player);
return;
}
var elements = new CuiElementContainer();
if (config.EnableOutpost && outpostFound && outpostCd > 0)
AddCooldownLabel(elements, GUI_PANEL_NAME, "OUTPOST", outpostCd, TextAnchor.MiddleLeft);
if (config.EnableBandit && banditFound && banditCd > 0)
AddCooldownLabel(elements, BANDIT_PANEL_NAME, "BANDIT", banditCd, TextAnchor.MiddleCenter);
if (config.EnableOutpost && outpostFound && outpostCd > 0)
CuiHelper.DestroyUi(player, $"{GUI_PANEL_NAME}_label");
if (config.EnableBandit && banditFound && banditCd > 0)
CuiHelper.DestroyUi(player, $"{BANDIT_PANEL_NAME}_label");
CuiHelper.AddUi(player, elements);
ScheduleGuiRefresh(player);
}
private void AddCooldownLabel(CuiElementContainer elements, string panelName, string label, double remaining, TextAnchor align)
{
elements.Add(new CuiElement
{
Name = $"{panelName}_label",
Parent = panelName,
Components =
{
new CuiTextComponent
{
Text = $"{label} ({remaining:F0}s)",
FontSize = 18,
Align = align,
Color = config.GuiSettings.CooldownColor
},
new CuiRectTransformComponent { AnchorMin = "0.26 0", AnchorMax = "0.95 1" }
}
});
}
private bool UseUI()
{
string m = (config.RespawnMethod ?? "bed").ToLower();
return m == "ui" || m == "both";
}
private bool UseBed()
{
string m = (config.RespawnMethod ?? "bed").ToLower();
return m == "bed" || m == "both";
}
private void SpawnRespawnBeds(BasePlayer player)
{
if (player == null) return;
RemoveRespawnBeds(player);
var bags = new List<SleepingBag>();
if (config.EnableOutpost && outpostFound)
{
var bag = CreateBed(player, outpostSpawnPoint, config.BedSettings.OutpostBedName);
if (bag != null)
{
ApplyBedCooldown(bag, GetCooldownTimeRemaining(player.userID, "outpost"));
bags.Add(bag);
}
}
if (config.EnableBandit && banditFound)
{
var bag = CreateBed(player, banditSpawnPoint, config.BedSettings.BanditBedName);
if (bag != null)
{
ApplyBedCooldown(bag, GetCooldownTimeRemaining(player.userID, "bandit"));
bags.Add(bag);
}
}
if (bags.Count > 0)
tempBags[player.userID] = bags;
}
private void ApplyBedCooldown(SleepingBag bag, double remaining)
{
if (bag == null) return;
bag.unlockTime = remaining > 0 ? Time.realtimeSinceStartup + (float)remaining : 0f;
}
private SleepingBag CreateBed(BasePlayer player, Vector3 position, string bedName)
{
try
{
var entity = GameManager.server.CreateEntity(BAG_PREFAB, position, Quaternion.identity);
var bag = entity as SleepingBag;
if (bag == null)
{
entity?.Kill();
return null;
}
bag.deployerUserID = player.userID;
bag.niceName = bedName;
bag.Spawn();
bag.limitNetworking = true;
return bag;
}
catch (System.Exception ex)
{
PrintError($"Failed to create respawn bed: {ex}");
return null;
}
}
private void RemoveRespawnBeds(BasePlayer player)
{
if (player == null) return;
List<SleepingBag> bags;
if (tempBags.TryGetValue(player.userID, out bags))
{
foreach (var bag in bags)
{
if (bag != null && !bag.IsDestroyed)
bag.Kill();
}
tempBags.Remove(player.userID);
}
}
private void DestroyGUI(BasePlayer player)
{
CuiHelper.DestroyUi(player, $"{GUI_PANEL_NAME}_overlay");
CuiHelper.DestroyUi(player, $"{BANDIT_PANEL_NAME}_overlay");
}
#endregion
#region Hooks
private Timer initialGUITimer;
void OnPlayerDeath(BasePlayer player, HitInfo info)
{
if (!permission.UserHasPermission(player.UserIDString, PermissionUse))
return;
if (!isInitialized)
{
InitializeSpawnPoints();
}
if (UseBed())
SpawnRespawnBeds(player);
if (UseUI())
{
initialGUITimer?.Destroy();
initialGUITimer = timer.Once(4.0f, () =>
{
if (!player.IsDead()) return;
ShowRespawnGUI(player);
timer.Once(0.5f, () =>
{
if (!player.IsDead()) return;
ShowRespawnGUI(player);
});
});
}
}
void OnPlayerRespawned(BasePlayer player)
{
if (guiTimers.ContainsKey(player.userID))
{
guiTimers[player.userID]?.Destroy();
guiTimers.Remove(player.userID);
}
DestroyGUI(player);
if (UseBed())
{
Vector3 pos = player.transform.position;
if (config.EnableOutpost && outpostFound && Vector3.Distance(pos, outpostSpawnPoint) < 3f)
{
SetPlayerSpawnConditions(player);
StartCooldown(player.userID, "outpost");
}
else if (config.EnableBandit && banditFound && Vector3.Distance(pos, banditSpawnPoint) < 3f)
{
SetPlayerSpawnConditions(player);
StartCooldown(player.userID, "bandit");
}
RemoveRespawnBeds(player);
}
renderedReady.Remove(player.userID);
if (config.RemoveHostilityOnRespawn)
{
recentlyRespawned.Add(player.userID);
player.State.unHostileTimestamp = CurrentTime() + 300;
timer.Once(5f, () => {
if (recentlyRespawned.Contains(player.userID))
{
recentlyRespawned.Remove(player.userID);
}
});
}
}
object OnPlayerAssist(BasePlayer player, BasePlayer target)
{
if (config.RemoveHostilityOnRespawn && recentlyRespawned.Contains(player.userID))
return true;
return null;
}
object OnTimeSincePvP(BasePlayer player, float oldTimer)
{
if (config.RemoveHostilityOnRespawn && recentlyRespawned.Contains(player.userID))
return 0f;
return null;
}
object OnPlayerEnterAggressiveTrigger(BasePlayer player, TriggerBase trigger)
{
if (config.RemoveHostilityOnRespawn && recentlyRespawned.Contains(player.userID))
return true;
return null;
}
object OnPlayerExitAggressiveTrigger(BasePlayer player, TriggerBase trigger)
{
if (config.RemoveHostilityOnRespawn && recentlyRespawned.Contains(player.userID))
return true;
return null;
}
object OnPlayerMarkHostile(BasePlayer player, float duration)
{
if (player == null) return null;
if (config.RemoveHostilityOnRespawn && recentlyRespawned.Contains(player.userID))
{
return true;
}
return null;
}
object CanBeHostile(BasePlayer player)
{
if (player == null) return null;
if (config.RemoveHostilityOnRespawn && recentlyRespawned.Contains(player.userID))
{
return false;
}
return null;
}
void OnPlayerDisconnected(BasePlayer player)
{
if (guiTimers.ContainsKey(player.userID))
{
guiTimers[player.userID]?.Destroy();
guiTimers.Remove(player.userID);
}
if (recentlyRespawned.Contains(player.userID))
recentlyRespawned.Remove(player.userID);
RemoveRespawnBeds(player);
renderedReady.Remove(player.userID);
DestroyGUI(player);
}
private bool HasPermission(BasePlayer player)
{
if (permissionCache.Contains(player.userID))
return true;
if (permission.UserHasPermission(player.UserIDString, PermissionUse))
{
permissionCache.Add(player.userID);
return true;
}
return false;
}
void OnPlayerConnected(BasePlayer player)
{
if (player.IsDead() && permission.UserHasPermission(player.UserIDString, PermissionUse))
{
timer.Once(1.0f, () =>
{
if (player == null || !player.IsConnected || !player.IsDead()) return;
if (UseBed())
SpawnRespawnBeds(player);
if (UseUI())
{
ShowRespawnGUI(player);
timer.Once(0.5f, () =>
{
if (player == null || !player.IsConnected || !player.IsDead()) return;
ShowRespawnGUI(player);
});
}
});
}
}
private float CurrentTime() => Time.realtimeSinceStartup;
#endregion
#region Commands
[ConsoleCommand("safezone.spawn")]
private void RespawnAtSafezoneCommand(ConsoleSystem.Arg arg)
{
var player = arg.Player();
if (player == null) return;
if (!isInitialized)
{
player.ChatMessage("Safezone respawn locations have not initialized yet");
return;
}
if (!permission.UserHasPermission(player.UserIDString, PermissionUse))
{
player.ChatMessage("You don't have permission to use safezone respawning");
return;
}
string location = arg.GetString(0, "outpost").ToLower();
if (IsOnCooldown(player.userID, location))
{
var timeRemaining = GetCooldownTimeRemaining(player.userID, location);
player.ChatMessage($"You must wait {timeRemaining:F0} seconds before using this spawn point again");
return;
}
try
{
if (location == "outpost" && config.EnableOutpost && outpostFound)
{
Quaternion spawnRotation = GetSpawnRotation(outpostPosition, outpostSpawnPoint);
player.RespawnAt(outpostSpawnPoint, spawnRotation);
SetPlayerSpawnConditions(player);
StartCooldown(player.userID, "outpost");
}
else if (location == "bandit" && config.EnableBandit && banditFound)
{
Quaternion spawnRotation = GetSpawnRotation(banditPosition, banditSpawnPoint);
player.RespawnAt(banditSpawnPoint, spawnRotation);
SetPlayerSpawnConditions(player);
StartCooldown(player.userID, "bandit");
}
DestroyGUI(player);
}
catch (System.Exception ex)
{
PrintError($"Error during respawn: {ex}");
player.ChatMessage("An error occurred while trying to respawn you");
}
}
#endregion
void Unload()
{
foreach (var timer in guiTimers.Values)
{
timer?.Destroy();
}
guiTimers.Clear();
recentlyRespawned.Clear();
cacheCleanupTimer?.Destroy();
permissionCache.Clear();
playerCache.Clear();
foreach (var bags in tempBags.Values)
{
foreach (var bag in bags)
{
if (bag != null && !bag.IsDestroyed)
bag.Kill();
}
}
tempBags.Clear();
renderedReady.Clear();
foreach (var player in BasePlayer.activePlayerList)
{
DestroyGUI(player);
}
}
}
}