C#:
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Facepunch;
using Newtonsoft.Json;
using Oxide.Core;
using Rust;
using UnityEngine;
namespace Oxide.Plugins {
[Info("BestRespawner", "l1berty", "1.0.9")]
[Description("Плагин позволяет игрокам спавниться в случайной точке на карте")]
internal class BestRespawner : RustPlugin {
private const string PERMISSION_USE = "bestrespawner.use";
// дефолтный пермишен
private const int MASK_BIOME = (int)(TerrainBiome.Enum.Arid | TerrainBiome.Enum.Temperate | TerrainBiome.Enum.Tundra | TerrainBiome.Enum.Arctic);
private const int MASK_SPLAT = (int)(TerrainSplat.Enum.Dirt | TerrainSplat.Enum.Snow | TerrainSplat.Enum.Sand | TerrainSplat.Enum.Rock | TerrainSplat.Enum.Grass | TerrainSplat.Enum.Forest | TerrainSplat.Enum.Stones | TerrainSplat.Enum.Gravel);
private Coroutine findSpawnPosCoroutine;
private readonly List<Vector3> spawnPositionCache = new List<Vector3>();
private List<Collider> colliders;
private List<BaseEntity> entities;
private List<BasePlayer> players;
#region Oxide Hooks
private void Init() {
colliders = Pool.GetList<Collider>();
entities = Pool.GetList<BaseEntity>();
players = Pool.GetList<BasePlayer>();
permission.RegisterPermission(PERMISSION_USE, this);
}
private void OnServerInitialized() {
findSpawnPosCoroutine = ServerMgr.Instance.StartCoroutine(FindSpawnPositions(5000));
}
private void Unload() {
Pool.Free(ref entities);
Pool.Free(ref colliders);
Pool.Free(ref players);
if (findSpawnPosCoroutine != null) {
ServerMgr.Instance.StopCoroutine(findSpawnPosCoroutine);
}
}
private IEnumerator FindSpawnPositions(int attempts = 1500) // поиск точек спавна
{
List<Vector3> list = Pool.GetList<Vector3>();
float mapSizeX = TerrainMeta.Size.x / 2;
float mapSizeZ = TerrainMeta.Size.z / 2;
Vector3 randomPos = Vector3.zero;
for (int i = 0; i < attempts; i++) {
randomPos.x = UnityEngine.Random.Range(-mapSizeX, mapSizeX);
randomPos.z = UnityEngine.Random.Range(-mapSizeZ, mapSizeZ);
if (TestPos(ref randomPos)) {
list.Add(randomPos);
}
if (i % 20 == 0) yield return CoroutineEx.waitForEndOfFrame;
}
spawnPositionCache.AddRange(list);
PrintWarning($"Удачно найдено {list.Count} точек спавна игроков.");
Pool.FreeList(ref list);
findSpawnPosCoroutine = null;
}
private object OnPlayerRespawn(BasePlayer player) // проверка
{
if (!permission.UserHasPermission(player.UserIDString, PERMISSION_USE)) {
return null;
}
var spawnPos = GetRandomSpawnPos();
// неудача
if (!spawnPos.HasValue) {
PrintWarning("Невозможно сгенерировать случайные точки спавына, превышен лимит, спавню по дефолту");
return null;
}
if (Interface.CallHook("OnRandomRespawn", player, spawnPos.Value) != null) {
return null;
}
return new BasePlayer.SpawnPoint {
pos = spawnPos.Value,
}
;
}
#endregion Oxide Hooks
private Vector3? GetRandomSpawnPos() // основная функция плагина
{
for (int i = 0; i < configData.maxAttempts; i++) {
if (spawnPositionCache.Count <= 0) return null;
var spawnPos = spawnPositionCache.GetRandom();
if (TestPosAgain(spawnPos)) {
return spawnPos;
}
spawnPositionCache.Remove(spawnPos);
if (spawnPositionCache.Count < 50 && findSpawnPosCoroutine == null) {
findSpawnPosCoroutine = ServerMgr.Instance.StartCoroutine(FindSpawnPositions());
}
}
return null;
}
private bool TestPos(ref Vector3 randomPos) {
RaycastHit hitInfo;
if (!Physics.Raycast(randomPos + Vector3.up * 300f, Vector3.down, out hitInfo, 400f, Layers.Solid) || hitInfo.GetEntity() != null) {
return false;
}
randomPos.y = hitInfo.point.y;
var slope = GetPosSlope(randomPos);
if (slope < configData.minSlope || slope > configData.maxSlope) {
return false;
}
bool flag;
var biome = GetPosBiome(randomPos);
if (configData.biomes.TryGetValue(biome, out flag) && !flag) {
return false;
}
var splat = GetPosSplat(randomPos);
if (configData.splats.TryGetValue(splat, out flag) && !flag) {
return false;
}
if (AntiHack.TestInsideTerrain(randomPos)) {
return false;
}
if (!ValidBounds.Test(randomPos)) {
return false;
}
return TestPosAgain(randomPos);
}
private bool TestPosAgain(Vector3 spawnPos) {
if (WaterLevel.Test(spawnPos)) {
return false;
}
colliders.Clear();
Vis.Colliders(spawnPos, 3f, colliders);
foreach (var collider in colliders) {
switch (collider.gameObject.layer) {
case (int)Layer.Prevent_Building : if (configData.preventSpawnAtMonument) {
return false;
}
break;
case (int)Layer.Vehicle_Large : // тестирование карго
case (int)Layer.Vehicle_World : case (int)Layer.Vehicle_Detailed : return false;
}
if (configData.preventSpawnAtZone && collider.name.Contains("zonemanager", CompareOptions.IgnoreCase)) // в конфиге - зона
{
return false;
}
if (configData.preventSpawnAtRadZone && collider.name.Contains("radiation", CompareOptions.IgnoreCase)) // в конфиге - рад. зона
{
return false;
}
if (collider.name.Contains("fireball", CompareOptions.IgnoreCase) || collider.name.Contains("iceberg", CompareOptions.IgnoreCase) || collider.name.Contains("ice_sheet", CompareOptions.IgnoreCase)) {
return false;
}
}
if (configData.radiusFromPlayers > 0) // настройка радиуса спавна от других игроков (Настройка в конфиге)
{
players.Clear();
Vis.Entities(spawnPos, configData.radiusFromPlayers, players, Layers.Mask.Player_Server);
foreach (var player in players) {
if (!player.IsSleeping()) {
return false;
}
}
}
if (configData.radiusFromBuilding > 0) // настройка радиуса спавна от построек (Настройка в конфиге)
{
entities.Clear();
Vis.Entities(spawnPos, configData.radiusFromBuilding, entities, Layers.PlayerBuildings);
if (entities.Any()) return false;
}
return true;
}
#region Helpers
private static TerrainBiome.Enum GetPosBiome(Vector3 position) => (TerrainBiome.Enum)TerrainMeta.BiomeMap.GetBiomeMaxType(position, MASK_BIOME);
private static TerrainSplat.Enum GetPosSplat(Vector3 position) => (TerrainSplat.Enum)TerrainMeta.SplatMap.GetSplatMaxType(position, MASK_SPLAT);
private static float GetPosSlope(Vector3 position) => TerrainMeta.HeightMap.GetSlope(position);
#endregion Helpers
#region ConfigurationFile
private ConfigData configData;
private class ConfigData {
[JsonProperty(PropertyName = "Максимальное кол-во попыток респавна")]
public int maxAttempts = 200;
[JsonProperty(PropertyName = "Мин. дистанция респавна от других игроков (НПС включительно)")]
public float radiusFromPlayers = 20.0f;
[JsonProperty(PropertyName = "Мин. дистанция респавна от построек других игроков")]
public float radiusFromBuilding = 20.0f;
[JsonProperty(PropertyName = "Запретить игрокам спавниться в постройках")]
public bool preventSpawnAtMonument = true;
[JsonProperty(PropertyName = "Запретить игрокам спавниться за зоной")]
public bool preventSpawnAtZone = true;
[JsonProperty(PropertyName = "Запретить игрокам спавниться в Рад. Зоне")]
public bool preventSpawnAtRadZone = true;
[JsonProperty(PropertyName = "Минимальный уклон (Не знаешь - не трогай)")]
public float minSlope = 0f;
[JsonProperty(PropertyName = "Максимальный уклон (Не знаешь - не трогай)")]
public float maxSlope = 60f;
[JsonProperty(PropertyName = "Настройка биомов")]
// Для спавна в определённом биоме (true - спавнить, false - нет)
public Dictionary<TerrainBiome.Enum, bool> biomes = new Dictionary<TerrainBiome.Enum, bool> {
[TerrainBiome.Enum.Arid]
= true, [TerrainBiome.Enum.Temperate] = true, [TerrainBiome.Enum.Tundra] = true, [TerrainBiome.Enum.Arctic] = true,
}
;
[JsonProperty(PropertyName = "Настройка поверхностей")]
// Для запрета спавна на любой поверхности (Точки не будут генерироваться)
public Dictionary<TerrainSplat.Enum, bool> splats = new Dictionary<TerrainSplat.Enum, bool> {
[TerrainSplat.Enum.Dirt]
= true, [TerrainSplat.Enum.Snow] = true, [TerrainSplat.Enum.Sand] = true, [TerrainSplat.Enum.Rock] = true, [TerrainSplat.Enum.Grass] = true, [TerrainSplat.Enum.Forest] = true, [TerrainSplat.Enum.Stones] = true, [TerrainSplat.Enum.Gravel] = true,
}
;
}
protected override void LoadConfig() // последние штрихи
{
base.LoadConfig();
try {
configData = Config.ReadObject<ConfigData>();
if (configData == null) LoadDefaultConfig();
}
catch {
PrintError("Конфигурационный файл повреждён!");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void LoadDefaultConfig() {
PrintWarning("Создание нового конфигурационного файла!");
configData = new ConfigData();
}
protected override void SaveConfig() => Config.WriteObject(configData);
#endregion ConfigurationFile
}
}