C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using Network;
using Network.Visibility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Plugins;
using Rust;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("SimpleHomes", "Milestorme", "2.2.9")]
[Description("Lightweight home, outpost, and bandit teleports with migration and daily limit support.")]
public class SimpleHomes : RustPlugin
{
[PluginReference]
private Plugin NoEscape;
private const string PermUse = "simplehomes.use";
private const string PermVip = "simplehomes.vip";
private const string PermOutpost = "simplehomes.outpost";
private const string PermBandit = "simplehomes.bandit";
private const string PermTownNoCooldown = "simplehomes.town.nocooldown";
private ConfigData config;
private StoredData storedData;
private readonly Dictionary<ulong, PendingTeleport> pendingTeleports = new Dictionary<ulong, PendingTeleport>();
private readonly List<Vector3> outpostSpawns = new List<Vector3>();
private readonly List<Vector3> banditSpawns = new List<Vector3>();
private readonly Dictionary<ulong, bool> vipCache = new Dictionary<ulong, bool>();
private Timer saveTimer;
private bool isDirty;
private System.Random random;
#region Configuration
private class ConfigData
{
[JsonProperty(PropertyName = "Chat")]
public ChatSettings Chat = new ChatSettings();
[JsonProperty(PropertyName = "Migration")]
public MigrationSettings Migration = new MigrationSettings();
[JsonProperty(PropertyName = "Home")]
public HomeSettings Home = new HomeSettings();
[JsonProperty(PropertyName = "Town Teleports")]
public TownSettings Town = new TownSettings();
[JsonProperty(PropertyName = "Safety")]
public SafetySettings Safety = new SafetySettings();
[JsonProperty(PropertyName = "Wipe Reset")]
public WipeResetSettings WipeReset = new WipeResetSettings();
[JsonProperty(PropertyName = "Debug")]
public bool Debug = false;
}
private class ChatSettings
{
[JsonProperty(PropertyName = "Prefix")]
public string Prefix = "<color=#00BFFF>SimpleHomes</color>: ";
[JsonProperty(PropertyName = "Chat Steam64ID")]
public ulong SteamId = 76561199056025689;
}
private class MigrationSettings
{
[JsonProperty(PropertyName = "Auto Migrate NTeleportation Homes")]
public bool AutoMigrateNteleportationHomes = true;
[JsonProperty(PropertyName = "Mark Migration Complete Even If Source Missing")]
public bool MarkCompleteIfSourceMissing = false;
}
private class HomeSettings
{
[JsonProperty(PropertyName = "Enabled")]
public bool Enabled = true;
[JsonProperty(PropertyName = "Homes Limit")]
public int Limit = 2;
[JsonProperty(PropertyName = "VIP Homes Limit")]
public int VipLimit = 5;
[JsonProperty(PropertyName = "Teleport Cooldown Seconds")]
public int Cooldown = 600;
[JsonProperty(PropertyName = "VIP Teleport Cooldown Seconds")]
public int VipCooldown = 300;
[JsonProperty(PropertyName = "Teleport Countdown Seconds")]
public int Countdown = 15;
[JsonProperty(PropertyName = "VIP Teleport Countdown Seconds")]
public int VipCountdown = 5;
[JsonProperty(PropertyName = "Daily Limit")]
public int DailyLimit = 0;
[JsonProperty(PropertyName = "VIP Daily Limit")]
public int VipDailyLimit = 0;
[JsonProperty(PropertyName = "Require Building Privilege To Set Home")]
public bool RequirePrivilegeToSet = true;
[JsonProperty(PropertyName = "Require Building Privilege To Teleport Home")]
public bool RequirePrivilegeToTeleport = false;
[JsonProperty(PropertyName = "Block Set Home While Building Blocked")]
public bool BlockSetHomeWhileBuildingBlocked = false;
[JsonProperty(PropertyName = "Block Set Home Without Cupboard")]
public bool BlockSetHomeWithoutCupboard = false;
[JsonProperty(PropertyName = "Cancel Teleport On Any Damage")]
public bool CancelOnAnyDamage = true;
[JsonProperty(PropertyName = "Cancel Teleport On Player Damage")]
public bool CancelOnPlayerDamage = true;
[JsonProperty(PropertyName = "Cancel Teleport On Fall Damage")]
public bool CancelOnFallDamage = true;
[JsonProperty(PropertyName = "Block While Crafting")]
public bool BlockWhileCrafting = false;
[JsonProperty(PropertyName = "Block While Swimming")]
public bool BlockWhileSwimming = false;
[JsonProperty(PropertyName = "Block While Wounded")]
public bool BlockWhileWounded = true;
}
private class TownSettings
{
[JsonProperty(PropertyName = "Enable Outpost")]
public bool EnableOutpost = true;
[JsonProperty(PropertyName = "Enable Bandit")]
public bool EnableBandit = true;
[JsonProperty(PropertyName = "Outpost Command")]
public string OutpostCommand = "outpost";
[JsonProperty(PropertyName = "Bandit Command")]
public string BanditCommand = "bandit";
[JsonProperty(PropertyName = "Cancel Command")]
public string CancelCommand = "cancelteleport";
[JsonProperty(PropertyName = "Outpost Cooldown Seconds")]
public int OutpostCooldown = 0;
[JsonProperty(PropertyName = "Outpost Countdown Seconds")]
public int OutpostCountdown = 15;
[JsonProperty(PropertyName = "Outpost Daily Limit")]
public int OutpostDailyLimit = 0;
[JsonProperty(PropertyName = "Bandit Cooldown Seconds")]
public int BanditCooldown = 0;
[JsonProperty(PropertyName = "Bandit Countdown Seconds")]
public int BanditCountdown = 15;
[JsonProperty(PropertyName = "Bandit Daily Limit")]
public int BanditDailyLimit = 0;
[JsonProperty(PropertyName = "VIP Cooldown Seconds")]
public int VipCooldown = 0;
[JsonProperty(PropertyName = "VIP Countdown Seconds")]
public int VipCountdown = 5;
[JsonProperty(PropertyName = "VIP Outpost Daily Limit")]
public int VipOutpostDailyLimit = 0;
[JsonProperty(PropertyName = "VIP Bandit Daily Limit")]
public int VipBanditDailyLimit = 0;
[JsonProperty(PropertyName = "Block Teleport When Mounted")]
public bool BlockWhenMounted = false;
[JsonProperty(PropertyName = "Block Teleport From Cargo")]
public bool BlockFromCargo = false;
[JsonProperty(PropertyName = "Force Reset Hostile Timer On Teleport")]
public bool ResetHostileOnSuccess = true;
}
private class SafetySettings
{
[JsonProperty(PropertyName = "Use Global Cooldown")]
public bool UseGlobalCooldown = true;
[JsonProperty(PropertyName = "Global Cooldown Seconds")]
public int GlobalCooldown = 0;
[JsonProperty(PropertyName = "VIP Global Cooldown Seconds")]
public int VipGlobalCooldown = 0;
[JsonProperty(PropertyName = "Respect NoEscape")]
public bool RespectNoEscape = true;
[JsonProperty(PropertyName = "Force Client Loading Screen During Teleport")]
public bool ForceClientLoadingScreenDuringTeleport = true;
[JsonProperty(PropertyName = "Use Direct MovePosition Teleport")]
public bool UseDirectMovePositionTeleport = true;
[JsonProperty(PropertyName = "Force Client Position RPC After Teleport")]
public bool ForceClientPositionRpcAfterTeleport = true;
[JsonProperty(PropertyName = "Post Teleport Network Refresh Count")]
public int PostTeleportNetworkRefreshCount = 12;
[JsonProperty(PropertyName = "Post Teleport Network Refresh Interval Seconds")]
public float PostTeleportNetworkRefreshInterval = 0.10f;
}
private class WipeResetSettings
{
[JsonProperty(PropertyName = "Reset Cooldowns On Wipe")]
public bool ResetCooldownsOnWipe = true;
[JsonProperty(PropertyName = "Reset Daily Limits On Wipe")]
public bool ResetDailyLimitsOnWipe = true;
[JsonProperty(PropertyName = "Reset Homes On Wipe")]
public bool ResetHomesOnWipe = true;
}
protected override void LoadDefaultConfig()
{
config = new ConfigData();
}
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<ConfigData>();
if (config == null)
{
LoadDefaultConfig();
}
}
catch
{
PrintWarning("Config error, generating new config.");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig()
{
Config.WriteObject(config, true);
}
#endregion
#region Data
private class StoredData
{
[JsonProperty(PropertyName = "Players")]
public Dictionary<ulong, PlayerData> Players = new Dictionary<ulong, PlayerData>();
[JsonProperty(PropertyName = "Migration Completed")]
public bool MigrationCompleted = false;
[JsonProperty(PropertyName = "Last Map Seed")]
public int LastMapSeed = 0;
[JsonProperty(PropertyName = "Last Map Size")]
public int LastMapSize = 0;
[JsonProperty(PropertyName = "Last Save Created Time")]
public string LastSaveCreatedTime = string.Empty;
}
private class PlayerData
{
[JsonProperty(PropertyName = "Homes")]
public Dictionary<string, SerializableVector3> Homes = new Dictionary<string, SerializableVector3>(StringComparer.OrdinalIgnoreCase);
[JsonProperty(PropertyName = "Home Cooldown Until")]
public int HomeCooldownUntil = 0;
[JsonProperty(PropertyName = "Town Cooldown Until")]
public int TownCooldownUntil = 0;
[JsonProperty(PropertyName = "Global Cooldown Until")]
public int GlobalCooldownUntil = 0;
[JsonProperty(PropertyName = "Home Daily Date")]
public string HomeDailyDate = string.Empty;
[JsonProperty(PropertyName = "Home Daily Count")]
public int HomeDailyCount = 0;
[JsonProperty(PropertyName = "Town Daily Date")]
public string TownDailyDate = string.Empty;
[JsonProperty(PropertyName = "Outpost Daily Count")]
public int OutpostDailyCount = 0;
[JsonProperty(PropertyName = "Bandit Daily Count")]
public int BanditDailyCount = 0;
}
private class SerializableVector3
{
[JsonProperty(PropertyName = "x")]
public float x;
[JsonProperty(PropertyName = "y")]
public float y;
[JsonProperty(PropertyName = "z")]
public float z;
public SerializableVector3()
{
}
public SerializableVector3(Vector3 v)
{
x = v.x;
y = v.y;
z = v.z;
}
public Vector3 ToVector3()
{
return new Vector3(x, y, z);
}
}
private class PendingTeleport
{
public Timer Timer;
public string Type;
public string HomeName;
public Vector3 Destination;
}
private class NTeleportationHomeRecord
{
[JsonProperty(PropertyName = "l")]
public Dictionary<string, object> Buildings = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
[JsonProperty(PropertyName = "b")]
public Dictionary<string, object> Boats = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
[JsonProperty(PropertyName = "t")]
public object Teleports;
}
private class NTeleportationBoatHome
{
[JsonProperty(PropertyName = "Value")]
public ulong Value;
[JsonProperty(PropertyName = "Offset")]
public SerializableVector3 Offset = new SerializableVector3();
}
private void LoadData()
{
try
{
storedData = Interface.GetMod().DataFileSystem.ReadObject<StoredData>(Name);
}
catch
{
storedData = new StoredData();
}
if (storedData == null)
{
storedData = new StoredData();
}
}
private void SaveData()
{
Interface.GetMod().DataFileSystem.WriteObject(Name, storedData);
isDirty = false;
if (saveTimer != null && !saveTimer.Destroyed)
{
saveTimer.Destroy();
saveTimer = null;
}
}
private void MarkDirty()
{
isDirty = true;
if (saveTimer != null && !saveTimer.Destroyed)
{
return;
}
saveTimer = timer.Once(10f, delegate()
{
saveTimer = null;
if (isDirty)
{
SaveData();
}
});
}
#endregion
#region Oxide Hooks
private void Init()
{
random = new System.Random();
vipCache.Clear();
permission.RegisterPermission(PermUse, this);
permission.RegisterPermission(PermVip, this);
permission.RegisterPermission(PermOutpost, this);
permission.RegisterPermission(PermBandit, this);
permission.RegisterPermission(PermTownNoCooldown, this);
LoadData();
RegisterCommands();
}
private void OnServerInitialized()
{
FindTownSpawns();
HandleWipeReset();
TryMigrateNteleportationHomes();
}
private void Unload()
{
foreach (KeyValuePair<ulong, PendingTeleport> pair in pendingTeleports)
{
if (pair.Value != null && pair.Value.Timer != null && !pair.Value.Timer.Destroyed)
{
pair.Value.Timer.Destroy();
}
}
pendingTeleports.Clear();
if (saveTimer != null && !saveTimer.Destroyed)
{
saveTimer.Destroy();
saveTimer = null;
}
if (isDirty)
{
SaveData();
}
}
private void OnServerSave()
{
if (isDirty)
{
SaveData();
}
}
private void OnPlayerConnected(BasePlayer player)
{
if (player == null)
{
return;
}
NextTick(delegate()
{
if (player == null || !player.IsConnected)
{
return;
}
vipCache[player.userID] = IsVip(player);
});
}
private void OnPlayerDisconnected(BasePlayer player, string reason)
{
CancelTeleport(player, null);
if (player != null)
{
vipCache.Remove(player.userID);
}
}
private void OnEntityTakeDamage(BaseCombatEntity entity, HitInfo info)
{
BasePlayer player = entity as BasePlayer;
if (player == null || info == null)
{
return;
}
PendingTeleport pending;
if (!pendingTeleports.TryGetValue(player.userID, out pending))
{
return;
}
NextTick(delegate()
{
if (player == null || !player.IsConnected || info == null || info.damageTypes == null || info.damageTypes.Total() <= 0f)
{
return;
}
BasePlayer attacker = GetSafePlayer(info);
if (pending.Type == "home")
{
if (config.Home.CancelOnAnyDamage)
{
CancelTeleport(player, Lang("CancelDamage", player.UserIDString));
return;
}
if (config.Home.CancelOnPlayerDamage && attacker != null)
{
CancelTeleport(player, Lang("CancelPlayerDamage", player.UserIDString));
return;
}
if (config.Home.CancelOnFallDamage && info.damageTypes.Has(DamageType.Fall))
{
CancelTeleport(player, Lang("CancelFallDamage", player.UserIDString));
return;
}
}
else
{
if (config.Home.CancelOnAnyDamage)
{
CancelTeleport(player, Lang("CancelDamage", player.UserIDString));
return;
}
if (config.Home.CancelOnPlayerDamage && attacker != null)
{
CancelTeleport(player, Lang("CancelPlayerDamage", player.UserIDString));
return;
}
if (config.Home.CancelOnFallDamage && info.damageTypes.Has(DamageType.Fall))
{
CancelTeleport(player, Lang("CancelFallDamage", player.UserIDString));
return;
}
}
});
}
#endregion
#region Commands
private void RegisterCommands()
{
cmd.AddChatCommand("home", this, "CmdHome");
cmd.AddChatCommand(config.Town.OutpostCommand, this, "CmdOutpost");
cmd.AddChatCommand(config.Town.BanditCommand, this, "CmdBandit");
cmd.AddChatCommand(config.Town.CancelCommand, this, "CmdCancelTeleport");
cmd.AddChatCommand("shmigrate", this, "CmdShMigrate");
}
private void CmdHome(BasePlayer player, string command, string[] args)
{
if (player == null)
{
return;
}
if (!config.Home.Enabled)
{
ReplyKey(player, "HomeDisabled");
return;
}
if (!permission.UserHasPermission(player.UserIDString, PermUse))
{
ReplyKey(player, "NoPermHome");
return;
}
if (args == null || args.Length == 0)
{
ReplyKey(player, "HomeUsage");
return;
}
string sub = args[0].ToLowerInvariant();
if (sub == "add")
{
if (args.Length != 2)
{
ReplyKey(player, "HomeUsageAdd");
return;
}
TryAddHome(player, args[1]);
return;
}
if (sub == "list")
{
if (args.Length != 1)
{
ReplyKey(player, "HomeUsageList");
return;
}
ListHomes(player);
return;
}
if (sub == "remove")
{
if (args.Length != 2)
{
ReplyKey(player, "HomeUsageRemove");
return;
}
RemoveHome(player, args[1]);
return;
}
if (args.Length == 1)
{
TeleportHome(player, args[0]);
return;
}
ReplyKey(player, "HomeUsage");
}
private void CmdOutpost(BasePlayer player, string command, string[] args)
{
if (player == null)
{
return;
}
if (!config.Town.EnableOutpost)
{
ReplyKey(player, "OutpostDisabled");
return;
}
if (!permission.UserHasPermission(player.UserIDString, PermOutpost))
{
ReplyKey(player, "NoPermOutpost");
return;
}
if (outpostSpawns.Count == 0)
{
ReplyKey(player, "OutpostNotFound");
return;
}
string error = CanUseTownTeleport(player, "outpost");
if (!string.IsNullOrEmpty(error))
{
Reply(player, error);
return;
}
int countdown = GetTownCountdown(player, GetTownBaseCountdown("outpost"));
Vector3 destination = outpostSpawns[random.Next(outpostSpawns.Count)];
BeginTeleport(player, "town", "outpost", destination, countdown, "Teleporting to Outpost in {0} seconds. Type /" + config.Town.CancelCommand + " to cancel.");
}
private void CmdBandit(BasePlayer player, string command, string[] args)
{
if (player == null)
{
return;
}
if (!config.Town.EnableBandit)
{
ReplyKey(player, "BanditDisabled");
return;
}
if (!permission.UserHasPermission(player.UserIDString, PermBandit))
{
ReplyKey(player, "NoPermBandit");
return;
}
if (banditSpawns.Count == 0)
{
ReplyKey(player, "BanditNotFound");
return;
}
string error = CanUseTownTeleport(player, "bandit");
if (!string.IsNullOrEmpty(error))
{
Reply(player, error);
return;
}
int countdown = GetTownCountdown(player, GetTownBaseCountdown("bandit"));
Vector3 destination = banditSpawns[random.Next(banditSpawns.Count)];
BeginTeleport(player, "town", "bandit", destination, countdown, "Teleporting to Bandit in {0} seconds. Type /" + config.Town.CancelCommand + " to cancel.");
}
private void CmdCancelTeleport(BasePlayer player, string command, string[] args)
{
if (player == null)
{
return;
}
PendingTeleport pending;
if (!pendingTeleports.TryGetValue(player.userID, out pending))
{
ReplyKey(player, "NoActiveTeleport");
return;
}
CancelTeleport(player, Lang("CancelGeneric", player.UserIDString));
}
#endregion
#region Home Logic
private void TryAddHome(BasePlayer player, string name)
{
if (!IsValidHomeName(name))
{
ReplyKey(player, "InvalidHomeName");
return;
}
PlayerData pdata = GetPlayerData(player.userID);
if (pdata.Homes.ContainsKey(name))
{
ReplyKey(player, "HomeExists");
return;
}
if (pdata.Homes.Count >= GetHomeLimit(player))
{
ReplyKey(player, "HomeLimitReached", GetHomeLimit(player));
return;
}
string setError = CanSetHome(player);
if (!string.IsNullOrEmpty(setError))
{
Reply(player, setError);
return;
}
pdata.Homes[name] = new SerializableVector3(player.transform.position);
MarkDirty();
ReplyKey(player, "HomeSaved", name);
}
private void ListHomes(BasePlayer player)
{
PlayerData pdata = GetPlayerData(player.userID);
ResetHomeDailyIfNeeded(pdata);
if (pdata.Homes.Count == 0)
{
ReplyKey(player, "NoHomesSaved", FormatDailyRemaining(player.UserIDString, Math.Max(0, GetHomeDailyLimit(player) - pdata.HomeDailyCount), GetHomeDailyLimit(player)));
return;
}
ReplyKey(player, "YourHomes");
foreach (KeyValuePair<string, SerializableVector3> entry in pdata.Homes)
{
Vector3 pos = entry.Value.ToVector3();
ReplyKey(player, "HomeListEntry", entry.Key, pos.x, pos.y, pos.z);
}
Reply(player, FormatDailyRemaining(player.UserIDString, Math.Max(0, GetHomeDailyLimit(player) - pdata.HomeDailyCount), GetHomeDailyLimit(player)));
}
private void RemoveHome(BasePlayer player, string name)
{
PlayerData pdata = GetPlayerData(player.userID);
if (!pdata.Homes.Remove(name))
{
ReplyKey(player, "HomeNotFound");
return;
}
MarkDirty();
ReplyKey(player, "HomeRemoved", name);
}
private void TeleportHome(BasePlayer player, string name)
{
PlayerData pdata = GetPlayerData(player.userID);
SerializableVector3 home;
if (!pdata.Homes.TryGetValue(name, out home))
{
ReplyKey(player, "HomeNotFound");
return;
}
string error = CanUseHomeTeleport(player);
if (!string.IsNullOrEmpty(error))
{
Reply(player, error);
return;
}
int countdown = GetHomeCountdown(player);
BeginTeleport(player, "home", name, home.ToVector3(), countdown, "Teleporting to home '{0}' in {1} seconds.");
}
private string CanSetHome(BasePlayer player)
{
if (config.Home.BlockWhileWounded && player.IsWounded())
{
return Lang("SetHomeWounded", player.UserIDString);
}
if (config.Home.BlockWhileSwimming && player.IsSwimming())
{
return Lang("SetHomeSwimming", player.UserIDString);
}
if (config.Home.BlockWhileCrafting && IsCrafting(player))
{
return Lang("SetHomeCrafting", player.UserIDString);
}
BuildingPrivlidge priv = player.GetBuildingPrivilege();
if (config.Home.BlockSetHomeWithoutCupboard && priv == null)
{
return Lang("SetHomeNoCupboard", player.UserIDString);
}
if (config.Home.BlockSetHomeWhileBuildingBlocked && !player.CanBuild())
{
return Lang("SetHomeBlocked", player.UserIDString);
}
if (config.Home.RequirePrivilegeToSet)
{
if (priv == null || !priv.IsAuthed(player))
{
return Lang("SetHomeNotAuthed", player.UserIDString);
}
}
return null;
}
private string CanUseHomeTeleport(BasePlayer player, bool ignorePending = false)
{
if (player.IsDead())
{
return Lang("TpDead", player.UserIDString);
}
if (config.Home.BlockWhileWounded && player.IsWounded())
{
return Lang("TpWounded", player.UserIDString);
}
if (config.Home.BlockWhileSwimming && player.IsSwimming())
{
return Lang("TpSwimming", player.UserIDString);
}
if (config.Home.BlockWhileCrafting && IsCrafting(player))
{
return Lang("TpCrafting", player.UserIDString);
}
if (config.Home.RequirePrivilegeToTeleport)
{
BuildingPrivlidge priv = player.GetBuildingPrivilege();
if (priv == null || !priv.IsAuthed(player))
{
return Lang("TpHomeNotAuthed", player.UserIDString);
}
}
if (config.Safety.RespectNoEscape && NoEscape != null)
{
object raidBlocked = NoEscape.Call("IsRaidBlocked", player);
if (raidBlocked is bool && (bool)raidBlocked)
{
return Lang("TpRaidBlocked", player.UserIDString);
}
object combatBlocked = NoEscape.Call("IsCombatBlocked", player);
if (combatBlocked is bool && (bool)combatBlocked)
{
return Lang("TpCombatBlocked", player.UserIDString);
}
}
if (!ignorePending && pendingTeleports.ContainsKey(player.userID))
{
return Lang("TpAlreadyPending", player.UserIDString);
}
PlayerData pdata = GetPlayerData(player.userID);
int now = GetUnix();
int globalRemaining = GetRemainingGlobalCooldown(player, pdata, now);
if (globalRemaining > 0)
{
return string.Format("Global teleport cooldown: {0}.", FormatTime(globalRemaining));
}
int homeRemaining = pdata.HomeCooldownUntil - now;
if (homeRemaining > 0)
{
return string.Format("Home teleport cooldown: {0}.", FormatTime(homeRemaining));
}
int dailyLimit = GetHomeDailyLimit(player);
if (dailyLimit > 0)
{
ResetHomeDailyIfNeeded(pdata);
if (pdata.HomeDailyCount >= dailyLimit)
{
return string.Format("You have reached your daily home limit of {0}.", dailyLimit);
}
}
return null;
}
#endregion
#region Town Logic
private string CanUseTownTeleport(BasePlayer player, string town, bool ignorePending = false)
{
if (player.IsDead())
{
return Lang("TpDead", player.UserIDString);
}
if (player.IsWounded())
{
return Lang("TpWounded", player.UserIDString);
}
if (!player.CanBuild())
{
return Lang("TpNoBuild", player.UserIDString);
}
if (config.Town.BlockWhenMounted && player.isMounted)
{
return Lang("TpMounted", player.UserIDString);
}
if (config.Town.BlockFromCargo && player.GetComponentInParent<CargoShip>() != null)
{
return Lang("TpCargo", player.UserIDString);
}
if (player.IsHostile())
{
return Lang("TpHostile", player.UserIDString);
}
if (config.Safety.RespectNoEscape && NoEscape != null)
{
object raidBlocked = NoEscape.Call("IsRaidBlocked", player);
if (raidBlocked is bool && (bool)raidBlocked)
{
return Lang("TpRaidBlocked", player.UserIDString);
}
object combatBlocked = NoEscape.Call("IsCombatBlocked", player);
if (combatBlocked is bool && (bool)combatBlocked)
{
return Lang("TpCombatBlocked", player.UserIDString);
}
}
if (!ignorePending && pendingTeleports.ContainsKey(player.userID))
{
return Lang("TpAlreadyPending", player.UserIDString);
}
PlayerData pdata = GetPlayerData(player.userID);
int now = GetUnix();
int globalRemaining = GetRemainingGlobalCooldown(player, pdata, now);
if (globalRemaining > 0)
{
return string.Format("Global teleport cooldown: {0}.", FormatTime(globalRemaining));
}
if (!permission.UserHasPermission(player.UserIDString, PermTownNoCooldown))
{
int townRemaining = pdata.TownCooldownUntil - now;
if (townRemaining > 0)
{
return string.Format("{0} teleport cooldown: {1}.", town, FormatTime(townRemaining));
}
}
int dailyLimit = GetTownDailyLimit(player, town);
if (dailyLimit > 0)
{
ResetTownDailyIfNeeded(pdata);
if (GetTownDailyCount(pdata, town) >= dailyLimit)
{
return Lang("TownDailyLimitReached", player.UserIDString, town, dailyLimit);
}
}
return null;
}
private void FindTownSpawns()
{
outpostSpawns.Clear();
banditSpawns.Clear();
foreach (MonumentInfo monument in TerrainMeta.Path.Monuments)
{
if (monument == null || string.IsNullOrEmpty(monument.name))
{
continue;
}
string lower = monument.name.ToLowerInvariant();
if (config.Town.EnableOutpost && lower.Contains("compound"))
{
List<BaseEntity> list = new List<BaseEntity>();
Vis.Entities(monument.transform.position, 25f, list);
foreach (BaseEntity entity in list)
{
if (entity != null && entity.name != null && entity.name.Contains("chair"))
{
Vector3 pos = entity.transform.position;
pos.y += 1f;
if (!outpostSpawns.Contains(pos))
{
outpostSpawns.Add(pos);
}
}
}
}
else if (config.Town.EnableBandit && lower.Contains("bandit"))
{
Vector3 t = monument.transform.position + -monument.transform.right * -50.75f;
Vector3 pos = t + (monument.transform.forward * -21.75f) + (Vector3.up * 7f);
if (!banditSpawns.Contains(pos))
{
banditSpawns.Add(pos);
}
}
}
DebugLog(string.Format("Found {0} outpost spawn(s) and {1} bandit spawn(s).", outpostSpawns.Count, banditSpawns.Count));
}
#endregion
#region Teleport Logic
private void BeginTeleport(BasePlayer player, string type, string name, Vector3 destination, int countdown, string startMessage)
{
PendingTeleport pending = new PendingTeleport();
pending.Type = type;
pending.HomeName = name;
pending.Destination = destination;
pending.Timer = timer.Once(countdown, delegate()
{
PendingTeleport current;
if (!pendingTeleports.TryGetValue(player.userID, out current))
{
return;
}
string error = type == "home" ? CanUseHomeTeleport(player, true) : CanUseTownTeleport(player, name, true);
if (!string.IsNullOrEmpty(error))
{
pendingTeleports.Remove(player.userID);
Reply(player, error);
return;
}
TeleportPlayer(player, destination);
int now = GetUnix();
PlayerData pdata = GetPlayerData(player.userID);
string successMessage;
if (type == "home")
{
pdata.HomeCooldownUntil = now + GetHomeCooldown(player);
int dailyLimit = GetHomeDailyLimit(player);
if (dailyLimit > 0)
{
ResetHomeDailyIfNeeded(pdata);
pdata.HomeDailyCount++;
}
if (config.Safety.UseGlobalCooldown)
{
pdata.GlobalCooldownUntil = now + GetGlobalCooldown(player);
}
successMessage = Lang("HomeTeleportSuccess", player.UserIDString, name);
if (dailyLimit > 0)
{
successMessage += " " + FormatDailyRemaining(player.UserIDString, Math.Max(0, dailyLimit - pdata.HomeDailyCount), dailyLimit);
}
}
else
{
if (!permission.UserHasPermission(player.UserIDString, PermTownNoCooldown))
{
pdata.TownCooldownUntil = now + GetTownCooldown(player, GetTownBaseCooldown(name));
}
int townDailyLimit = GetTownDailyLimit(player, name);
if (townDailyLimit > 0)
{
ResetTownDailyIfNeeded(pdata);
IncrementTownDailyCount(pdata, name);
}
if (config.Safety.UseGlobalCooldown)
{
pdata.GlobalCooldownUntil = now + GetGlobalCooldown(player);
}
if (config.Town.ResetHostileOnSuccess)
{
ResetHostile(player);
}
successMessage = Lang("TownTeleportSuccess", player.UserIDString, GetTownDisplayName(name));
if (townDailyLimit > 0)
{
successMessage += " " + FormatDailyRemaining(player.UserIDString, Math.Max(0, townDailyLimit - GetTownDailyCount(pdata, name)), townDailyLimit);
}
}
if (pending.Timer != null && !pending.Timer.Destroyed)
{
pending.Timer.Destroy();
}
pendingTeleports.Remove(player.userID);
MarkDirty();
Reply(player, successMessage);
});
pendingTeleports[player.userID] = pending;
PlayerData currentData = GetPlayerData(player.userID);
string message;
if (type == "home")
{
int homeDailyLimit = GetHomeDailyLimit(player);
message = string.Format(startMessage, name, countdown);
if (homeDailyLimit > 0)
{
ResetHomeDailyIfNeeded(currentData);
message += " " + FormatDailyRemaining(player.UserIDString, Math.Max(0, homeDailyLimit - (currentData.HomeDailyCount + 1)), homeDailyLimit);
}
}
else
{
int townDailyLimit = GetTownDailyLimit(player, name);
message = string.Format(startMessage, countdown);
if (townDailyLimit > 0)
{
ResetTownDailyIfNeeded(currentData);
message += " " + FormatDailyRemaining(player.UserIDString, Math.Max(0, townDailyLimit - (GetTownDailyCount(currentData, name) + 1)), townDailyLimit);
}
}
Reply(player, message);
}
private void CancelTeleport(BasePlayer player, string message)
{
if (player == null)
{
return;
}
PendingTeleport pending;
if (!pendingTeleports.TryGetValue(player.userID, out pending))
{
return;
}
if (pending.Timer != null && !pending.Timer.Destroyed)
{
pending.Timer.Destroy();
}
pendingTeleports.Remove(player.userID);
if (!string.IsNullOrEmpty(message))
{
Reply(player, message);
}
}
private void TeleportPlayer(BasePlayer player, Vector3 position)
{
if (player == null || !player.IsConnected || player.net == null || player.net.connection == null)
{
return;
}
player.EnsureDismounted();
player.Server_CancelGesture();
if (player.HasParent())
{
player.SetParent(null, true, true);
}
player.PauseFlyHackDetection(5f);
player.PauseSpeedHackDetection(5f);
player.ApplyStallProtection(4f);
player.UpdateActiveItem(default(ItemId));
bool startedSnapshot = false;
try
{
// Match the updated NTeleportation snapshot flow:
// start a quick client loading transition, teleport, update the network group,
// then send the newer complete snapshot. Do not clear the entity queue and do not
// use the removed/unsafe SendFullSnapshot path.
if (player.IsConnected)
{
player.StartSleeping();
player.SetPlayerFlag(BasePlayer.PlayerFlags.ReceivingSnapshot, true);
startedSnapshot = true;
player.ClientRPC(RpcTarget.Player("StartLoading_Quick", player), true);
}
player.Teleport(position + new Vector3(0f, 0.1f, 0f));
if (player.IsConnected && !player.limitNetworking && !player.isInvisible)
{
player.UpdateNetworkGroup();
player.SendNetworkUpdateImmediate();
}
if (player.IsConnected)
{
player.SendCompleteSnapshot();
}
if (!player.limitNetworking && !player.isInvisible)
{
player.ForceUpdateTriggers();
}
}
finally
{
timer.Once(0.5f, delegate()
{
if (player == null || !player.IsConnected)
{
return;
}
if (startedSnapshot || player.IsSleeping())
{
player.EndSleeping();
}
RefreshPlayerNetwork(player);
});
}
NextTick(delegate()
{
RefreshPlayerNetwork(player);
});
}
private void RefreshPlayerNetwork(BasePlayer player)
{
if (player == null || !player.IsConnected || player.net == null || player.net.connection == null)
{
return;
}
player.UpdateNetworkGroup();
player.SendNetworkUpdateImmediate();
}
private void ResetHostile(BasePlayer player)
{
player.State.unHostileTimestamp = 0;
player.ClientRPCPlayer(null, player, "SetHostileLength", 0f);
}
#endregion
private bool TryParseImportedVector(object raw, out SerializableVector3 vector)
{
vector = null;
if (raw == null)
{
return false;
}
string rawString = raw as string;
if (!string.IsNullOrEmpty(rawString))
{
return TryParseImportedVectorString(rawString, out vector);
}
JToken token = raw as JToken;
if (token != null)
{
if (token.Type == JTokenType.String)
{
return TryParseImportedVectorString(token.ToObject<string>(), out vector);
}
if (token.Type == JTokenType.Object)
{
JToken xToken = token["x"] ?? token["X"];
JToken yToken = token["y"] ?? token["Y"];
JToken zToken = token["z"] ?? token["Z"];
if (xToken != null && yToken != null && zToken != null)
{
float x;
float y;
float z;
if (float.TryParse(xToken.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out x) &&
float.TryParse(yToken.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out y) &&
float.TryParse(zToken.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out z))
{
vector = new SerializableVector3 { x = x, y = y, z = z };
return true;
}
}
}
}
return false;
}
private bool TryParseImportedVectorString(string value, out SerializableVector3 vector)
{
vector = null;
if (string.IsNullOrEmpty(value))
{
return false;
}
string[] parts = value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 3)
{
return false;
}
float x;
float y;
float z;
if (!float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out x))
{
return false;
}
if (!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y))
{
return false;
}
if (!float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out z))
{
return false;
}
vector = new SerializableVector3 { x = x, y = y, z = z };
return true;
}
#region Migration and Wipe Reset
private void TryMigrateNteleportationHomes(bool force = false)
{
if (!config.Migration.AutoMigrateNteleportationHomes && !force)
{
return;
}
if (storedData.MigrationCompleted && !force)
{
return;
}
string[] candidates = new string[]
{
"NTeleportationHome",
"NTeleportation/Home",
"Home"
};
bool importedAnything = false;
bool sourceFound = false;
int importedCount = 0;
foreach (string fileName in candidates)
// ... [Code preview truncated due to length. Full source in attached file] ...