C#:
using Facepunch;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core.Plugins;
using ProtoBuf;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("CompoundTeleport", "k1lly0u", "2.0.13")]
[Description("Allow players to respawn at, or teleport to, the Outpost and Bandit Camp")]
class CompoundTeleport : RustPlugin
{
#region Fields
[PluginReference] Plugin NoEscape;
private Hash<ulong, Cooldown> cooldowns = new Hash<ulong, Cooldown>();
private Hash<ulong, Action> pending = new Hash<ulong, Action>();
private SpawnManager Outpost { get; set; }
private SpawnManager Bandit { get; set; }
private const string TELEPORT_PERMISSION = "compoundteleport.tp";
private const string RESPAWN_PERMISSION = "compoundteleport.respawn";
private const string ADMIN_PERMISSION = "compoundteleport.admin";
private const int TARGET_LAYERS = ~(1 << 10 | 1 << 18 | 1 << 28 | 1 << 29);
private const string RESPAWN_BAG_CMD = "respawn_sleepingbag";
private const string REMOVE_BAG_CMD = "respawn_sleepingbag_remove";
private const string RESPAWN_CMD = "respawn";
private const uint OUTPOST_ID = 111;
private const uint BANDIT_ID = 112;
#endregion
#region Oxide Hooks
private void Loaded()
{
lang.RegisterMessages(Messages, this);
permission.RegisterPermission(TELEPORT_PERMISSION, this);
permission.RegisterPermission(RESPAWN_PERMISSION, this);
permission.RegisterPermission(ADMIN_PERMISSION, this);
foreach (KeyValuePair<string, ConfigData.TeleportOptions.VIP> kvp in configData.Teleport.VIPTimes)
permission.RegisterPermission(kvp.Key, this);
cmd.AddChatCommand(configData.Teleport.OutpostCommand, this, cmdTeleportOutpost);
cmd.AddChatCommand(configData.Teleport.BanditCommand, this, cmdTeleportBandit);
cmd.AddChatCommand(configData.Teleport.CancelCommand, this, cmdTeleportCancel);
}
private void OnServerInitialized() => GenerateSpawnPoints();
private void OnPlayerConnected(BasePlayer player)
{
if (player.IsDead())
NextTick(() => SendRespawnOptions(player));
}
private void OnPlayerDeath(BasePlayer player, HitInfo hitInfo) => CancelPendingTeleports(player);
private void OnEntityDeath(BasePlayer player, HitInfo hitInfo) => NextTick(()=> SendRespawnOptions(player));
private object OnServerCommand(ConsoleSystem.Arg arg)
{
BasePlayer player = arg.Player();
if (player == null || !player.IsConnected)
return null;
if (configData.Respawn.DisableIfHostile && player.IsHostile())
return null;
if (player.IsDead() || player.IsSpectating())
{
if (arg.cmd.Name == RESPAWN_CMD)
{
if (configData.Respawn.ForceTo == "None")
return null;
if (configData.Respawn.ForceTo == "Outpost" && Outpost != null)
{
RespawnPlayerAt(player, Outpost.Get());
return false;
}
if (configData.Respawn.ForceTo == "Bandit" && Bandit != null)
{
RespawnPlayerAt(player, Bandit.Get());
return false;
}
}
if (arg.cmd.Name == REMOVE_BAG_CMD)
{
uint num = arg.GetUInt(0, 0);
if (num == OUTPOST_ID || num == BANDIT_ID)
return false;
}
if (arg.cmd.Name == RESPAWN_BAG_CMD)
{
uint num = arg.GetUInt(0, 0);
if (num == OUTPOST_ID && Outpost != null)
{
RespawnPlayerAt(player, Outpost.Get());
return false;
}
else if (num == BANDIT_ID && Bandit != null)
{
RespawnPlayerAt(player, Bandit.Get());
return false;
}
}
}
return null;
}
private void Unload()
{
foreach (BasePlayer player in BasePlayer.activePlayerList)
CancelPendingTeleports(player);
}
#endregion
#region Functions
private void GenerateSpawnPoints()
{
if (ConVar.Server.level == "HapisIsland")
Outpost = new SpawnManager(new Vector3(-211f, 105f, -432f), 40f, "road", "carpark", "pavement");
else
{
foreach (MonumentInfo monument in UnityEngine.Object.FindObjectsOfType<MonumentInfo>())
{
if (monument.name.Contains("/compound.prefab"))
Outpost = new SpawnManager(monument.transform.position, 40f, "road", "carpark", "pavement");
else if (monument.name.Contains("/bandit_town.prefab"))
Bandit = new SpawnManager(monument.transform.position, 80f, "walkway");
}
}
if (Outpost == null && Bandit == null)
{
PrintError("Unable to find a Outpost or Bandit Camp on this map.Unable to continue...");
return;
}
if (Outpost != null)
Puts($"Generated {Outpost.Count} spawn points at the Outpost");
if (Bandit != null)
Puts($"Generated {Bandit.Count} spawn points at the Bandit Camp");
}
public void SendRespawnOptions(BasePlayer player)
{
if (player == null || player.IsNpc || !player.IsConnected)
return;
if (!configData.Respawn.AllowBandit && !configData.Respawn.AllowOutpost)
return;
if (configData.Respawn.DisableIfHostile && player.IsHostile())
return;
if (!permission.UserHasPermission(player.UserIDString, RESPAWN_PERMISSION) && !permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION))
return;
if (NoEscape)
{
if (configData.NoEscape.RaidBlockedRespawn)
{
bool success = NoEscape.Call<bool>("IsRaidBlocked", player);
if (success)
{
SendReply(player, msg("Error.RaidBlocked", player.userID));
return;
}
}
if (configData.NoEscape.CombatBlockedRespawn)
{
bool success = NoEscape.Call<bool>("IsCombatBlocked", player);
if (success)
{
SendReply(player, msg("Error.CombatBlocked", player.userID));
return;
}
}
}
using (RespawnInformation respawnInformation = Pool.Get<RespawnInformation>())
{
respawnInformation.spawnOptions = Pool.Get<List<RespawnInformation.SpawnOptions>>();
SleepingBag[] sleepingBagArray = SleepingBag.FindForPlayer(player.userID, true);
for (int i = 0; i < (int)sleepingBagArray.Length; i++)
{
SleepingBag sleepingBag = sleepingBagArray[i];
RespawnInformation.SpawnOptions d = Pool.Get<RespawnInformation.SpawnOptions>();
d.id = sleepingBag.net.ID;
d.name = sleepingBag.niceName;
d.worldPosition = sleepingBag.transform.position;
d.type = sleepingBag.RespawnType;
d.unlockSeconds = sleepingBag.unlockSeconds;
respawnInformation.spawnOptions.Add(d);
}
Cooldown cooldown;
cooldowns.TryGetValue(player.userID, out cooldown);
if (Outpost != null && configData.Respawn.AllowOutpost)
{
RespawnInformation.SpawnOptions d = Pool.Get<RespawnInformation.SpawnOptions>();
d.id = OUTPOST_ID;
d.name = msg("Name.Outpost", player.userID);
d.worldPosition = Outpost.Position;
d.type = RespawnInformation.SpawnOptions.RespawnType.Bed;
d.unlockSeconds = (cooldown?.respawn ?? 0) != 0 ? Mathf.Clamp((cooldown.respawn - Facepunch.Math.Epoch.Current), 0, int.MaxValue) : 0;
respawnInformation.spawnOptions.Add(d);
}
if (Bandit != null && configData.Respawn.AllowBandit)
{
RespawnInformation.SpawnOptions d = Pool.Get<RespawnInformation.SpawnOptions>();
d.id = BANDIT_ID;
d.name = msg("Name.Bandit", player.userID);
d.worldPosition = Bandit.Position;
d.type = RespawnInformation.SpawnOptions.RespawnType.Bed;
d.unlockSeconds = (cooldown?.respawn ?? 0) != 0 ? Mathf.Clamp((cooldown.respawn - Facepunch.Math.Epoch.Current), 0, int.MaxValue) : 0;
respawnInformation.spawnOptions.Add(d);
}
respawnInformation.previousLife = player.previousLifeStory;
respawnInformation.fadeIn = (player.previousLifeStory == null ? false : player.previousLifeStory.timeDied > (Facepunch.Math.Epoch.Current - 5));
player.ClientRPCPlayer<RespawnInformation>(null, player, "OnRespawnInformation", respawnInformation);
}
}
private void RespawnPlayerAt(BasePlayer player, Vector3 position)
{
Cooldown cooldown;
if (!cooldowns.TryGetValue(player.userID, out cooldown))
cooldowns[player.userID] = cooldown = new Cooldown();
player.RespawnAt(position, Quaternion.identity);
cooldown.respawn = Facepunch.Math.Epoch.Current + configData.Respawn.Cooldown;
}
private bool HasPendingTeleports(BasePlayer player) => pending.ContainsKey(player.userID);
private void CancelPendingTeleports(BasePlayer player)
{
if (player == null)
return;
if (HasPendingTeleports(player))
{
player.CancelInvoke(pending[player.userID]);
pending.Remove(player.userID);
}
}
#endregion
#region Helpers
private string FormatTime(double time)
{
TimeSpan dateDifference = TimeSpan.FromSeconds((float)time);
int days = dateDifference.Days;
int hours = dateDifference.Hours + (days * 24);
int mins = dateDifference.Minutes;
int secs = dateDifference.Seconds;
if (hours > 0)
return string.Format("{0:00}h:{1:00}m:{2:00}s", hours, mins, secs);
else if (mins > 0)
return string.Format("{0:00}m:{1:00}s", mins, secs);
else return string.Format("{0:00}s", secs);
}
private int GetCooldownTime(string playerId)
{
int cooldown = configData.Teleport.Cooldown;
foreach(KeyValuePair<string, ConfigData.TeleportOptions.VIP> kvp in configData.Teleport.VIPTimes)
{
if (permission.UserHasPermission(playerId, kvp.Key))
cooldown = kvp.Value.Cooldown;
}
return cooldown;
}
private int GetWaitTime(string playerId)
{
int wait = configData.Teleport.Wait;
foreach (KeyValuePair<string, ConfigData.TeleportOptions.VIP> kvp in configData.Teleport.VIPTimes)
{
if (permission.UserHasPermission(playerId, kvp.Key))
wait = kvp.Value.Wait;
}
return wait;
}
private void TeleportToOutpost(BasePlayer player) => MovePosition(player, Outpost.Get());
private void TeleportToBandit(BasePlayer player) => MovePosition(player, Bandit.Get());
private void MovePosition(BasePlayer player, Vector3 destination)
{
try
{
if (player.isMounted)
player.GetMounted().DismountPlayer(player, true);
player.SetParent(null, true, true);
player.StartSleeping();
player.SetPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot, true);
player.EnablePlayerCollider();
player.SetServerFall(true);
player.RemoveFromTriggers();
player.MovePosition(destination);
player.ClientRPCPlayer(null, player, "ForcePositionTo", destination);
player.UpdateNetworkGroup();
player.SendNetworkUpdateImmediate(false);
player.ClearEntityQueue(null);
player.SendFullSnapshot();
}
finally
{
//player.UpdatePlayerCollider(true);
//player.UpdatePlayerRigidbody(true);
player.EnablePlayerCollider();
//player.AddPlayerRigidbody();
player.SetServerFall(false);
Cooldown cooldown;
if (!cooldowns.TryGetValue(player.userID, out cooldown))
cooldowns[player.userID] = cooldown = new Cooldown();
cooldown.command = Facepunch.Math.Epoch.Current + GetCooldownTime(player.UserIDString);
pending.Remove(player.userID);
}
}
#endregion
#region Spawn Handler
private class SpawnManager
{
private List<Vector3> _spawnPoints;
private List<Vector3> _availablePoints;
public int Count { get { return _spawnPoints.Count; } }
public Vector3 Position { get; private set; }
public SpawnManager() { }
public SpawnManager(Vector3 position, float size, params string[] targetNames)
{
RaycastHit rayHit;
_spawnPoints = new List<Vector3>();
for (int i = 0; i < 150; i++)
{
Vector3 p = position + (UnityEngine.Random.insideUnitSphere * size);
p.y = position.y + 100f;
if (Physics.SphereCast(new Ray(p, Vector3.down), 0.5f, out rayHit, 150, TARGET_LAYERS, QueryTriggerInteraction.Ignore))
{
if (rayHit.collider is TerrainCollider)
_spawnPoints.Add(rayHit.point);
else
{
if (targetNames != null)
{
if (targetNames.Any(rayHit.collider.name.Contains))
_spawnPoints.Add(rayHit.point);
}
}
}
}
_availablePoints = new List<Vector3>(_spawnPoints);
this.Position = position;
}
public Vector3 Get()
{
Vector3 point = _availablePoints.GetRandom();
_availablePoints.Remove(point);
if (_availablePoints.Count == 0)
_availablePoints = new List<Vector3>(_spawnPoints);
return point;
}
}
#endregion
#region Commands
private void cmdTeleportOutpost(BasePlayer player, string command, string[] args)
{
if (!MeetsAllRequirements(player))
return;
InvokeTeleport(player, new Action(() => TeleportToOutpost(player)));
SendReply(player, string.Format(msg("Notification.TPPending", player.userID), msg("Name.Outpost", player.userID), FormatTime(GetWaitTime(player.UserIDString))));
}
private void cmdTeleportBandit(BasePlayer player, string command, string[] args)
{
if (!MeetsAllRequirements(player))
return;
InvokeTeleport(player, new Action(() => TeleportToBandit(player)));
SendReply(player, string.Format(msg("Notification.TPPending", player.userID), msg("Name.Bandit", player.userID), FormatTime(GetWaitTime(player.UserIDString))));
}
private void InvokeTeleport(BasePlayer player, Action action)
{
player.Invoke(action, GetWaitTime(player.UserIDString));
pending[player.userID] = action;
}
private bool MeetsAllRequirements(BasePlayer player)
{
if (permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION))
return true;
if (!permission.UserHasPermission(player.UserIDString, TELEPORT_PERMISSION))
{
SendReply(player, msg("Error.NoPermission", player.userID));
return false;
}
if (HasPendingTeleports(player))
{
SendReply(player, msg("Error.TPPending", player.userID));
return false;
}
if (configData.Teleport.Hostile && player.IsHostile())
{
SendReply(player, msg("Error.Hostile", player.userID));
return false;
}
if (configData.Teleport.BuildingBlocked && player.IsBuildingBlocked())
{
SendReply(player, msg("Error.BuildingBlocked", player.userID));
return false;
}
Cooldown cooldown;
if (cooldowns.TryGetValue(player.userID, out cooldown))
{
int cooldownTime = cooldown.command - Facepunch.Math.Epoch.Current;
if (cooldownTime > 0)
{
SendReply(player, string.Format(msg("Error.Cooldown", player.userID), FormatTime(cooldownTime)));
return false;
}
}
if (IsEscapeBlocked(player))
return false;
return true;
}
private void cmdTeleportCancel(BasePlayer player, string command, string[] args)
{
if (!HasPendingTeleports(player))
{
SendReply(player, msg("Notification.NonePending", player.userID));
return;
}
CancelPendingTeleports(player);
SendReply(player, msg("Notification.Cancelled", player.userID));
}
#endregion
#region Plugin Integration
private bool IsEscapeBlocked(BasePlayer player)
{
if (NoEscape)
{
if (configData.NoEscape.RaidBlocked)
{
bool success = NoEscape.Call<bool>("IsRaidBlocked", player);
if (success)
{
SendReply(player, msg("Error.RaidBlocked", player.userID));
return true;
}
}
if (configData.NoEscape.CombatBlocked)
{
bool success = NoEscape.Call<bool>("IsCombatBlocked", player);
if (success)
{
SendReply(player, msg("Error.CombatBlocked", player.userID));
return true;
}
}
}
return false;
}
#endregion
#region Config
private ConfigData configData;
private class ConfigData
{
[JsonProperty(PropertyName = "NoEscape Options")]
public NoEscapeOptions NoEscape { get; set; }
[JsonProperty(PropertyName = "Teleport Options")]
public TeleportOptions Teleport { get; set; }
[JsonProperty(PropertyName = "Respawn Options")]
public RespawnOptions Respawn { get; set; }
public class TeleportOptions
{
[JsonProperty(PropertyName = "Amount of time to wait before teleporting (seconds)")]
public int Wait { get; set; }
[JsonProperty(PropertyName = "Cooldown time (seconds)")]
public int Cooldown { get; set; }
[JsonProperty(PropertyName = "Block teleport if player is hostile")]
public bool Hostile { get; set; }
[JsonProperty(PropertyName = "Block teleport if player is building blocked")]
public bool BuildingBlocked { get; set; }
[JsonProperty(PropertyName = "Teleport command name (Outpost)")]
public string OutpostCommand { get; set; }
[JsonProperty(PropertyName = "Teleport command name (Bandit Camp)")]
public string BanditCommand { get; set; }
[JsonProperty(PropertyName = "Teleport cancel command name")]
public string CancelCommand { get; set; }
[JsonProperty(PropertyName = "VIP Times")]
public Dictionary<string, VIP> VIPTimes { get; set; }
public class VIP
{
[JsonProperty(PropertyName = "Amount of time to wait before teleporting (seconds)")]
public int Wait { get; set; }
[JsonProperty(PropertyName = "Cooldown time (seconds)")]
public int Cooldown { get; set; }
}
}
public class RespawnOptions
{
[JsonProperty(PropertyName = "Allow respawning at Outpost")]
public bool AllowOutpost { get; set; }
[JsonProperty(PropertyName = "Allow respawning at Bandit Camp")]
public bool AllowBandit { get; set; }
[JsonProperty(PropertyName = "Disable respawn options if player is hostile")]
public bool DisableIfHostile { get; set; }
[JsonProperty(PropertyName = "Cooldown time (seconds)")]
public int Cooldown { get; set; }
[JsonProperty(PropertyName = "Force respawns to (None, Outpost, Bandit)")]
public string ForceTo { get; set; }
}
public class NoEscapeOptions
{
[JsonProperty(PropertyName = "Block teleport when raid blocked")]
public bool RaidBlocked { get; set; }
[JsonProperty(PropertyName = "Block teleport when combat blocked")]
public bool CombatBlocked { get; set; }
[JsonProperty(PropertyName = "Disable respawn options when raid blocked")]
public bool RaidBlockedRespawn { get; set; }
[JsonProperty(PropertyName = "Disable respawn options when combat blocked")]
public bool CombatBlockedRespawn { get; set; }
}
public Oxide.Core.VersionNumber Version { get; set; }
}
protected override void LoadConfig()
{
base.LoadConfig();
configData = Config.ReadObject<ConfigData>();
if (configData.Version < Version)
UpdateConfigValues();
Config.WriteObject(configData, true);
}
protected override void LoadDefaultConfig() => configData = GetBaseConfig();
private ConfigData GetBaseConfig()
{
return new ConfigData
{
NoEscape = new ConfigData.NoEscapeOptions
{
CombatBlocked = true,
RaidBlocked = true,
CombatBlockedRespawn = true,
RaidBlockedRespawn = true,
},
Teleport = new ConfigData.TeleportOptions
{
BuildingBlocked = true,
BanditCommand = "bandit",
CancelCommand = "canceltp",
Cooldown = 300,
Hostile = true,
OutpostCommand = "outpost",
Wait = 10,
VIPTimes = new Dictionary<string, ConfigData.TeleportOptions.VIP>
{
["compoundteleport.vip1"] = new ConfigData.TeleportOptions.VIP() { Cooldown = 150, Wait = 5 },
["compoundteleport.vip2"] = new ConfigData.TeleportOptions.VIP() { Cooldown = 30, Wait = 3 },
}
},
Respawn = new ConfigData.RespawnOptions
{
Cooldown = 300,
ForceTo = "None",
AllowBandit = true,
AllowOutpost = true
},
Version = Version
};
}
protected override void SaveConfig() => Config.WriteObject(configData, true);
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
ConfigData baseConfig = GetBaseConfig();
if (configData.Version < new Core.VersionNumber(2, 0, 0))
configData = baseConfig;
if (configData.Version < new Core.VersionNumber(2, 0, 6))
{
configData.NoEscape = baseConfig.NoEscape;
}
if (configData.Version < new Core.VersionNumber(2, 0, 7))
configData.Teleport.VIPTimes = baseConfig.Teleport.VIPTimes;
configData.Version = Version;
PrintWarning("Config update completed!");
}
#endregion
#region Data
private class Cooldown
{
public int respawn;
public int command;
}
#endregion
#region Localization
private string msg(string key, ulong playerId = 0UL) => lang.GetMessage(key, this, playerId != 0UL ? playerId.ToString() : null);
private Dictionary<string, string> Messages = new Dictionary<string, string>
{
["Name.Outpost"] = "Outpost",
["Name.Bandit"] = "Bandit Camp",
["Error.NoPermission"] = "You do not have permission to use this command",
["Error.TPPending"] = "You already have a teleport pending",
["Error.Hostile"] = "You can not teleport whilst you are marked hostile",
["Error.Cooldown"] = "You must wait another <color=#ce422b>{0}</color> before teleporting again",
["Error.RaidBlocked"] = "You can not teleport while <color=#ce422b>raid blocked</color>!",
["Error.CombatBlocked"] = "You can not teleport while <color=#ce422b>combat blocked</color>!",
["Error.BuildingBlocked"] = "You can not teleport while <color=#ce422b>building blocked</color>!",
["Notification.TPPending"] = "Teleporting to <color=#ce422b>{0}</color> in <color=#ce422b>{1}</color>",
["Notification.NonePending"] = "You do not have any teleports pending",
["Notification.Cancelled"] = "Cancelled pending teleports",
};
#endregion
}
}