C#:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using Rust;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("OneTimeSpawn", "codeboy", "1.0.1")]
class OneTimeSpawn : RustPlugin
{
#region Classes
class MonumentAdapter
{
public MonoBehaviour Object => (MonoBehaviour)_monumentInfo["Object"];
public string PrefabName => (string)_monumentInfo["PrefabName"];
public string ShortName => (string)_monumentInfo["ShortName"];
public string Alias => (string)_monumentInfo["Alias"];
public Vector3 Position => (Vector3)_monumentInfo["Position"];
public Quaternion Rotation => (Quaternion)_monumentInfo["Rotation"];
public OBB[] BoundingBoxes => (OBB[])_monumentInfo["BoundingBoxes"];
private Dictionary<string, object> _monumentInfo;
public MonumentAdapter(Dictionary<string, object> monumentInfo)
{
_monumentInfo = monumentInfo;
}
public Vector3 TransformPoint(Vector3 localPosition) =>
((Func<Vector3, Vector3>)_monumentInfo["TransformPoint"]).Invoke(localPosition);
public Vector3 InverseTransformPoint(Vector3 worldPosition) =>
((Func<Vector3, Vector3>)_monumentInfo["InverseTransformPoint"]).Invoke(worldPosition);
public Vector3 ClosestPointOnBounds(Vector3 position) =>
((Func<Vector3, Vector3>)_monumentInfo["ClosestPointOnBounds"]).Invoke(position);
public bool IsInBounds(Vector3 position) =>
((Func<Vector3, bool>)_monumentInfo["IsInBounds"]).Invoke(position);
}
#endregion
#region Fields
private const string PermissionToUse = "onetimespawn.use";
private MonumentAdapter _cachedMonument;
private List<ulong> _alreadySpawned;
private BasePlayer.SpawnPoint cachedSpawnpoint = null;
[PluginReference] private Plugin MonumentFinder;
#endregion
#region Hooks
private void OnServerInitialized()
{
permission.RegisterPermission(PermissionToUse, this);
LoadData();
if (cfg.Position == Vector3.zero)
{
PrintError("Spawn position is (0, 0, 0). Please install position via command /spawnpos when you have ownerid");
PrintError("Spawn position is (0, 0, 0). Please install position via command /spawnpos when you have ownerid");
PrintError("Spawn position is (0, 0, 0). Please install position via command /spawnpos when you have ownerid");
PrintError("Spawn position is (0, 0, 0). Please install position via command /spawnpos when you have ownerid");
PrintError("Spawn position is (0, 0, 0). Please install position via command /spawnpos when you have ownerid");
return;
}
if (HasLinkedMonument() && !MonumentFinder)
{
PrintError("MonumentFinder plugin is not installed. Please install it or remove link to monument in config");
PrintError("MonumentFinder plugin is not installed. Please install it or remove link to monument in config");
PrintError("MonumentFinder plugin is not installed. Please install it or remove link to monument in config");
Interface.Oxide.UnloadPlugin(Title);
return;
}
SetupSpawnpoint(isServerStart:true);
}
private void OnNewSave()
{
if (cfg.WipeData)
{
PrintWarning("Players data was successfully wiped with server wipe!");
_alreadySpawned.Clear();
SaveData();
}
if (cfg.DeleteSpawnPositionOnMapWipe)
{
PrintWarning("Spawnpoint was wiped with server wipe! Please, install new position with command /spawnpos and reload plugin");
cfg.Position = Vector3.zero;
SaveConfig();
cachedSpawnpoint = null;
}
}
private void Unload() => SaveData();
private void OnServerSave() => SaveData();
private object OnPlayerRespawn(BasePlayer player, BasePlayer.SpawnPoint spawnPoint)
{
if (cachedSpawnpoint == null)
return null;
if (!_alreadySpawned.Contains(player.userID))
{
_alreadySpawned.Add(player.userID);
return cachedSpawnpoint;
}
return null;
}
private bool HasLinkedMonument() => !string.IsNullOrEmpty(cfg.LinkToMonument);
private bool IsValidLinkedMonument()
{
if (string.IsNullOrEmpty(cfg.LinkToMonument))
return true;
return GetLinkedMonument() != null;
}
private Vector3 SetupSpawnpoint(Vector3 pos = default, BasePlayer reply = null, bool isServerStart = false)
{
Vector3 toSave = pos;
if (isServerStart)
{
toSave = cfg.Position;
if (HasLinkedMonument() && IsValidLinkedMonument())
{
var mon = GetLinkedMonument();
pos = mon.TransformPoint(toSave);
}
else
{
pos = toSave;
}
}
else
{
if (HasLinkedMonument())
{
if (!IsValidLinkedMonument())
{
PrintError("Linked monument is not found. Please, check monument name in config");
return default;
}
var mon = GetLinkedMonument();
toSave = mon.InverseTransformPoint(pos);
}
else
{
if (pos == default)
pos = cfg.Position;
}
}
cachedSpawnpoint = new()
{
pos = pos,
rot = default
};
if (reply != null)
reply.ChatMessage($"Spawnpoint was successfully installed! Position: {pos.ToString()}. {(HasLinkedMonument() ? "Linked monument" : "No Linked monument")}");
else
PrintWarning($"Spawnpoint was successfully installed! Position: {pos.ToString()}. {(HasLinkedMonument() ? "Linked monument" : "No Linked monument")}");
return toSave;
}
private MonumentAdapter? GetLinkedMonument()
{
if (_cachedMonument != null)
return _cachedMonument;
if (string.IsNullOrEmpty(cfg.LinkToMonument))
return null;
var monument = (List<Dictionary<string, object>>)MonumentFinder?.Call("API_Find", cfg.LinkToMonument)!;
if (monument.IsNullOrEmpty())
return null;
_cachedMonument = new(monument[0]);
return _cachedMonument;
}
#endregion
#region Commands
[ChatCommand("spawnpos")]
private void cmdSpawnPos(BasePlayer player)
{
if (player == null)
return;
if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, PermissionToUse))
return;
cfg.Position = SetupSpawnpoint(player.GetNetworkPosition(), player, false);
SaveConfig(cfg);
}
#endregion
#region Config
private ConfigData cfg;
public class ConfigData
{
[JsonProperty("Spawn position")] public Vector3 Position;
[JsonProperty("Wipe already spawned players on map wipe?")]
public bool WipeData;
[JsonProperty("Delete spawn position on map wipe?")]
public bool DeleteSpawnPositionOnMapWipe;
[JsonProperty("Link to monument (requires MonumentFinder plugin). If not empty, Spawn position will be set as offset from monument center")]
public string LinkToMonument;
}
protected override void LoadDefaultConfig()
{
var config = new ConfigData()
{
Position = Vector3.zero,
WipeData = false,
DeleteSpawnPositionOnMapWipe = false,
LinkToMonument = ""
};
SaveConfig(config);
}
protected override void LoadConfig()
{
base.LoadConfig();
cfg = Config.ReadObject<ConfigData>();
SaveConfig(cfg);
}
private void SaveConfig(object config)
{
Config.WriteObject(config, true);
}
#endregion
#region Data
private void SaveData()
{
Interface.Oxide.DataFileSystem.WriteObject($"{Title}/alreadySpawned", _alreadySpawned);
}
void LoadData()
{
_alreadySpawned = Interface.Oxide?.DataFileSystem?.ReadObject<List<ulong>>($"{Title}/alreadySpawned")
?? new();
}
#endregion
}
}