C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Facepunch;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using Network;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Oxide.Plugins
{
[Info("Custom Resource Spawns", "k1lly0u", "0.2.33", ResourceId = 1783)]
[Description("Creates additional spawn points for resources of your choosing, that re-spawn on a timer")]
class CustomResourceSpawns : RustPlugin
{
#region Fields
private RaycastHit raycastHit;
private static readonly RaycastHit[] raycastBuffer = new RaycastHit[256];
private readonly List<BaseEntity> currentResourceEntities = new List<BaseEntity>();
private readonly List<Timer> refreshTimers = new List<Timer>();
private readonly Dictionary<ulong, int> resourceCreators = new Dictionary<ulong, int>();
private const string ADMIN_PERMISSION = "customresourcespawns.admin";
private struct Resource
{
public static readonly List<Resource> List = new List<Resource>();
public static readonly HashSet<string> Categories = new HashSet<string>();
private const string RESOURCES = "assets/bundled/prefabs/autospawn/resource/";
private const string COLLECTABLES = "assets/bundled/prefabs/autospawn/collectable/";
private const string LOOT = "loot";
public int ID { get; private set; }
public string Category { get; private set; }
public string Shortname { get; private set; }
public string ResourcePath { get; private set; }
public Resource(int id, BaseEntity baseEntity)
{
ID = id;
Category = baseEntity.PrefabName.Replace(RESOURCES, "").Replace(COLLECTABLES, "").Replace(baseEntity.ShortPrefabName, "").Replace(".prefab", "").TrimEnd('/');
Shortname = baseEntity.ShortPrefabName;
ResourcePath = baseEntity.PrefabName;
Categories.Add(Category);
}
public static void Populate()
{
Clear();
int id = 1;
foreach (string str in FileSystem.Backend.cache.Keys)
{
if ((str.StartsWith(RESOURCES) || str.StartsWith(COLLECTABLES)) && !str.Contains(LOOT))
{
BaseEntity baseEntity = GameManager.server.FindPrefab(str).GetComponent<BaseEntity>();
if (baseEntity != null)
{
AddResource(id, baseEntity);
id++;
}
}
}
}
public static bool FindByID(int id, out Resource resource)
{
for (int i = 0; i < List.Count; i++)
{
resource = List[i];
if (resource.ID == id)
return true;
}
resource = default(Resource);
return false;
}
public static bool Exists(int id)
{
for (int i = 0; i < List.Count; i++)
{
if (List[i].ID == id)
return true;
}
return false;
}
private static void AddResource(int id, BaseEntity baseEntity) => List.Add(new Resource(id, baseEntity));
public static void Clear() => List.Clear();
}
#endregion Fields
#region Oxide Hooks
private void OnServerInitialized()
{
permission.RegisterPermission(ADMIN_PERMISSION, this);
Resource.Populate();
LoadData();
InitializeResourceSpawns();
}
protected override void LoadDefaultMessages() => lang.RegisterMessages(Messages, this);
private void OnEntityKill(BaseEntity entity)
{
if (entity == null)
return;
if (currentResourceEntities.Contains(entity))
{
InitiateRefresh(entity);
currentResourceEntities.Remove(entity);
}
}
private void OnCollectiblePickup(Item item, BasePlayer player, CollectibleEntity entity)
{
BaseEntity ent = entity.GetEntity();
if (ent != null)
{
if (currentResourceEntities.Contains(ent))
{
InitiateRefresh(ent);
currentResourceEntities.Remove(ent);
}
}
}
private void OnPlayerInput(BasePlayer player, InputState input)
{
if (resourceCreators.ContainsKey(player.userID))
if (input.WasJustPressed(BUTTON.FIRE_PRIMARY))
{
int type = resourceCreators[player.userID];
AddSpawn(player, type);
}
}
private void Unload()
{
for (int i = currentResourceEntities.Count - 1; i >= 0; i--)
currentResourceEntities[i].Kill();
currentResourceEntities.Clear();
for (int i = 0; i < refreshTimers.Count; i++)
refreshTimers[i].Destroy();
refreshTimers.Clear();
}
#endregion Oxide Hooks
#region Resource Control
private void InitializeResourceSpawns()
{
foreach (StoredData.StoredResource resource in storedData.resources)
{
InitializeNewSpawn(resource.Type, resource.Position);
}
}
private void InitiateRefresh(BaseEntity resource)
{
Vector3 position = resource.transform.position;
string type = resource.PrefabName;
refreshTimers.Add(timer.Once(configData.RespawnTimer * 60, () =>
{
InitializeNewSpawn(type, position);
}));
currentResourceEntities.Remove(resource);
}
private void InitializeNewSpawn(string type, Vector3 position)
{
BaseEntity entity = InstantiateEntity(type, position);
entity.enableSaving = false;
entity.Spawn();
currentResourceEntities.Add(entity);
}
private BaseEntity InstantiateEntity(string type, Vector3 position)
{
GameObject gameObject = Instantiate.GameObject(GameManager.server.FindPrefab(type), position, new Quaternion());
gameObject.name = type;
SceneManager.MoveGameObjectToScene(gameObject, Rust.Server.EntityScene);
UnityEngine.Object.Destroy(gameObject.GetComponent<Spawnable>());
if (!gameObject.activeSelf)
gameObject.SetActive(true);
BaseEntity component = gameObject.GetComponent<BaseEntity>();
return component;
}
#endregion Resource Control
#region Resource Spawning
private void AddSpawn(BasePlayer player, int id)
{
if (Resource.FindByID(id, out Resource resource))
{
BaseEntity entity = InstantiateEntity(resource.ResourcePath, GetSpawnPosition(player));
entity.enableSaving = false;
entity.Spawn();
storedData.resources.Add(new StoredData.StoredResource { Position = entity.transform.position, Type = resource.ResourcePath });
currentResourceEntities.Add(entity);
SaveData();
}
}
#endregion
#region Helper Methods
private Vector3 GetSpawnPosition(BasePlayer player)
{
int hits = Physics.RaycastNonAlloc(player.eyes.HeadRay(), raycastBuffer, 20f, -1, QueryTriggerInteraction.Ignore);
float closestdist = float.MaxValue;
Vector3 closestHitpoint = player.transform.position;
for (int i = 0; i < hits; i++)
{
RaycastHit raycastHit = raycastBuffer[i];
if (raycastHit.distance < closestdist)
{
closestdist = raycastHit.distance;
closestHitpoint = raycastHit.point;
}
}
return closestHitpoint;
}
private BaseEntity FindResourceEntity(BasePlayer player) => Physics.Raycast(player.eyes.HeadRay(), out raycastHit, 10f) ? raycastHit.GetEntity() : null;
private BaseEntity FindResourcePosition(Vector3 position)
{
foreach (BaseEntity entry in currentResourceEntities)
{
if (entry.transform.position == position)
return entry;
}
return null;
}
private void FindInRadius(Vector3 position, float radius, List<Vector3> foundResources)
{
foreach (StoredData.StoredResource item in storedData.resources)
{
if (Vector3.Distance(position, item.Position) < radius)
foundResources.Add(item.Position);
}
}
private bool RemoveResource(BaseEntity entity)
{
if (currentResourceEntities.Contains(entity))
{
RemoveFromData(entity);
currentResourceEntities.Remove(entity);
entity.Kill();
return true;
}
return false;
}
private bool RemoveFromData(BaseEntity baseEntity)
{
foreach (StoredData.StoredResource resource in storedData.resources)
{
if (resource.Type.Equals(baseEntity.PrefabName, StringComparison.OrdinalIgnoreCase) && Vector3.Distance(baseEntity.transform.position, resource.Position) < 0.25f)
{
storedData.resources.Remove(resource);
return true;
}
}
return false;
}
private void ShowResourceCategories(BasePlayer player)
{
SendEchoConsole(player.net.connection, "Available resource categories");
foreach (string category in Resource.Categories)
SendEchoConsole(player.net.connection, category);
}
private void ShowResourcesByCategory(BasePlayer player, string category)
{
SendEchoConsole(player.net.connection, string.Format("Showing resources with category : {0}", category));
foreach (Resource resource in Resource.List)
{
if (resource.Category.Equals(category, StringComparison.OrdinalIgnoreCase))
{
SendEchoConsole(player.net.connection, string.Format("{0} - {1}", resource.ID, resource.Shortname));
}
}
}
private void ShowCurrentResources(BasePlayer player)
{
foreach (StoredData.StoredResource resource in storedData.resources)
SendEchoConsole(player.net.connection, string.Format("{0} - {1}", resource.Position, resource.Type));
}
private void SendEchoConsole(Network.Connection cn, string msg)
{
if (Net.sv.IsConnected())
{
NetWrite netWrite = Net.sv.StartWrite();
netWrite.PacketID(Network.Message.Type.ConsoleMessage);
netWrite.String(msg);
netWrite.Send(new SendInfo(cn));
}
}
#endregion
#region Commands
[ChatCommand("crs")]
private void chatResourceSpawn(BasePlayer player, string command, string[] args)
{
if (!CanSpawnResources(player)) return;
if (resourceCreators.ContainsKey(player.userID))
{
resourceCreators.Remove(player.userID);
ChatMessage(player, lang.GetMessage("endAdd", this, player.UserIDString));
return;
}
if (args.Length == 0)
{
SendReply(player, lang.GetMessage("synAdd1", this, player.UserIDString));
SendReply(player, lang.GetMessage("synRem", this, player.UserIDString));
SendReply(player, lang.GetMessage("synRemNear", this, player.UserIDString));
SendReply(player, lang.GetMessage("synList", this, player.UserIDString));
SendReply(player, lang.GetMessage("synResourceCats", this, player.UserIDString));
SendReply(player, lang.GetMessage("synResource1", this, player.UserIDString));
SendReply(player, lang.GetMessage("synWipe", this, player.UserIDString));
return;
}
if (args.Length >= 1)
{
switch (args[0].ToLower())
{
case "add":
{
if (!int.TryParse(args[1], out int id))
{
ChatMessage(player, Message("notNum", player.UserIDString));
return;
}
if (Resource.Exists(id))
{
resourceCreators.Add(player.userID, id);
ChatMessage(player, Message("adding", player.UserIDString));
return;
}
ChatMessage(player, Message("notType", player.UserIDString));
}
return;
case "remove":
{
if (args.Length >= 2 && args[1].ToLower() == "near")
{
float radius = 10f;
if (args.Length == 3)
float.TryParse(args[2], out radius);
List<Vector3> list = Pool.Get<List<Vector3>>();
FindInRadius(player.transform.position, radius, list);
if (list.Count > 0)
{
int totalDestroyed = 0;
foreach (Vector3 position in list)
{
BaseEntity entity = FindResourcePosition(position);
if (entity != null && RemoveResource(entity))
totalDestroyed++;
}
ChatMessage(player, string.Format(Message("removedNear", player.UserIDString), totalDestroyed, radius));
goto FREE_LIST;
}
else ChatMessage(player, string.Format(Message("noFind", player.UserIDString), radius.ToString()));
FREE_LIST:
Pool.FreeUnmanaged(ref list);
return;
}
BaseEntity resource = FindResourceEntity(player);
if (resource != null)
{
if (currentResourceEntities.Contains(resource))
{
if (RemoveResource(resource))
{
SaveData();
ChatMessage(player, Message("RemovedResource", player.UserIDString));
return;
}
}
else ChatMessage(player, Message("notReg", player.UserIDString));
return;
}
ChatMessage(player, Message("notBox", player.UserIDString));
}
return;
case "list":
ShowCurrentResources(player);
ChatMessage(player, Message("checkConsole", player.UserIDString));
return;
case "categories":
ShowResourceCategories(player);
ChatMessage(player, Message("checkConsole", player.UserIDString));
return;
case "resources":
if (args.Length >= 2)
{
ShowResourcesByCategory(player, args[1]);
ChatMessage(player, Message("checkConsole", player.UserIDString));
}
else SendReply(player, lang.GetMessage("synResource1", this, player.UserIDString));
return;
case "near":
{
float radius = 10f;
if (args.Length == 2)
float.TryParse(args[1], out radius);
List<Vector3> list = Pool.Get<List<Vector3>>();
FindInRadius(player.transform.position, radius, list);
if (list.Count > 0)
{
ChatMessage(player, string.Format(Message("foundResources", player.UserIDString), list.Count));
foreach (Vector3 position in list)
player.SendConsoleCommand("ddraw.box", 30f, Color.magenta, position, 1f);
}
else ChatMessage(player, string.Format(Message("noFind", player.UserIDString), radius.ToString()));
Pool.FreeUnmanaged(ref list);
}
return;
case "wipe":
{
int count = storedData.resources.Count;
for (int i = currentResourceEntities.Count - 1; i >= 0; i--)
{
currentResourceEntities[i].Kill();
currentResourceEntities.RemoveAt(i);
}
storedData.resources.Clear();
SaveData();
ChatMessage(player, string.Format(Message("wipedAll1", player.UserIDString), count));
}
return;
default:
break;
}
}
}
private bool CanSpawnResources(BasePlayer player)
{
if (permission.UserHasPermission(player.UserIDString, ADMIN_PERMISSION))
return true;
ChatMessage(player, Message("noPerms", player.UserIDString));
return false;
}
#endregion Chat Commands
#region Config
private ConfigData configData;
private class ConfigData
{
public int RespawnTimer { 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
{
RespawnTimer = 20,
Version = Version
};
}
protected override void SaveConfig() => Config.WriteObject(configData, true);
private void UpdateConfigValues()
{
PrintWarning("Config update detected! Updating config values...");
configData.Version = Version;
PrintWarning("Config update completed!");
}
#endregion
#region Data Management
private StoredData storedData;
private DynamicConfigFile dynamicConfigFile;
private void SaveData() => dynamicConfigFile.WriteObject(storedData);
private void LoadData()
{
dynamicConfigFile = Interface.Oxide.DataFileSystem.GetFile("CustomSpawns/crs_data");
dynamicConfigFile.Settings.Converters = new JsonConverter[] { new UnityVector3Converter() };
storedData = dynamicConfigFile.ReadObject<StoredData>();
if (storedData == null)
storedData = new StoredData();
}
private class StoredData
{
public List<StoredResource> resources = new List<StoredResource>();
public class StoredResource
{
public string Type;
public Vector3 Position;
}
}
private class UnityVector3Converter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Vector3 vector = (Vector3)value;
writer.WriteValue($"{vector.x} {vector.y} {vector.z}");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
string[] values = reader.Value.ToString().Trim().Split(' ');
return new Vector3(Convert.ToSingle(values[0]), Convert.ToSingle(values[1]), Convert.ToSingle(values[2]));
}
JObject o = JObject.Load(reader);
return new Vector3(Convert.ToSingle(o["x"]), Convert.ToSingle(o["y"]), Convert.ToSingle(o["z"]));
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Vector3);
}
}
#endregion Data Management
#region Messages
private void ChatMessage(BasePlayer player, string message) => SendReply(player, $"<color=orange>{Title}:</color> <color=#939393>{message}</color>");
private string Message(string key, string playerid = null) => lang.GetMessage(key, this, playerid);
private readonly Dictionary<string, string> Messages = new Dictionary<string, string>()
{
["checkConsole"] = "Check your console for a list of resources",
["noPerms"] = "You do not have permission to use this command",
["notType"] = "The number you have entered is not on the list",
["notNum"] = "You must enter a resource number",
["notBox"] = "You are not looking at a resource",
["notReg"] = "This is not a custom placed resource",
["RemovedResource"] = "Resource deleted",
["synAdd1"] = "<color=orange>/crs add <id> </color><color=#939393>- Adds a new resource</color>",
["synRem"] = "<color=orange>/crs remove </color><color=#939393>- Remove the resource you are looking at</color>",
["synRemNear"] = "<color=orange>/crs remove near <radius> </color><color=#939393>- Removes the resources within <radius> (default 10M)</color>",
["synResourceCats"]= "<color=orange>/crs categories </color><color=#939393>- List available resource categories</color>",
["synResource1"] = "<color=orange>/crs resources <category> </color><color=#939393>- List available resources in the specified category</color>",
["synWipe"] = "<color=orange>/crs wipe </color><color=#939393>- Wipes all custom placed resources</color>",
["synList"] = "<color=orange>/crs list </color><color=#939393>- Puts all custom resource details to console</color>",
["synNear"] = "<color=orange>/crs near XX </color><color=#939393>- Shows custom resources in radius XX</color>",
["wipedAll1"] = "Wiped {0} custom resource spawns",
["foundResources"] = "Found {0} resource spawns near you",
["noFind"] = "Couldn't find any resources in radius: {0}M",
["adding"] = "You have activated the resouce tool.Look where you want to place and press shoot.Type /crs to end",
["endAdd"] = "You have de-activated the resouce tool",
["removedNear"] = "Removed {0} resources within a {1}M radius of your position"
};
#endregion Messaging
}
}