Иконка ресурса

FREE FAnimalWorldSpawner : 1.0.0 1.0.0

Нет прав для скачивания
C#:
using System.Collections.Generic;
using Oxide.Core;
using Rust;
using UnityEngine;

namespace Oxide.Plugins;

[Info("FAnimalWorldSpawner", "fufeldev", "1.0.0")]
public class FAnimalWorldSpawner : RustPlugin
{
    private const string BearPrefab = "assets/rust.ai/agents/bear/bear.prefab";
    private const string BoarPrefab = "assets/rust.ai/agents/boar/boar.prefab";
    private const int BearTarget = 38;
    private const int BoarTarget = 50;
    private const int MaxSpawnAttempts = 250;
    private const float RespawnDelay = 30f;
    private const float RadiusFromBuildings = 35f;
    private const float RadiusFromPlayers = 20f;
    private const float MaxSlope = 45f;

    private readonly HashSet<NetworkableId> _bears = new HashSet<NetworkableId>();
    private readonly HashSet<NetworkableId> _boars = new HashSet<NetworkableId>();
    private readonly List<Collider> _colliders = new List<Collider>();
    private Timer _checkTimer;
    private bool _unloading;

    private void OnServerInitialized()
    {
        MaintainPopulation();
        _checkTimer = timer.Every(60f, MaintainPopulation);
    }

    private void Unload()
    {
        _unloading = true;

        if (_checkTimer != null)
        {
            _checkTimer.Destroy();
            _checkTimer = null;
        }

        KillTracked(_bears);
        KillTracked(_boars);
        _bears.Clear();
        _boars.Clear();
    }

    private void OnEntityKill(BaseAnimalNPC animal)
    {
        if (animal == null || animal.net == null) return;
        if (_bears.Remove(animal.net.ID))
        {
            if (_unloading) return;
            timer.Once(RespawnDelay, () => SpawnMissing(BearPrefab, _bears, BearTarget));
            return;
        }

        if (_boars.Remove(animal.net.ID))
        {
            if (_unloading) return;
            timer.Once(RespawnDelay, () => SpawnMissing(BoarPrefab, _boars, BoarTarget));
        }
    }

    [ChatCommand("fanimal")]
    private void CmdAnimalChat(BasePlayer player, string command, string[] args)
    {
        if (player != null && !player.IsAdmin) return;

        string type = args != null && args.Length > 0 ? args[0].ToLowerInvariant() : "all";
        int amount = args != null && args.Length > 1 ? ParseAmount(args[1], 0) : 0;
        string message;
        int spawned = ForceSpawn(type, amount, out message);
        Reply(player, spawned < 0 ? message : $"{message}. Сейчас: медведи {_bears.Count}, кабаны {_boars.Count}");
    }

    [ConsoleCommand("fanimal.spawn")]
    private void CmdAnimalConsole(ConsoleSystem.Arg arg)
    {
        BasePlayer player = arg.Player();
        if (player != null && !player.IsAdmin) return;

        string type = arg.GetString(0, "all").ToLowerInvariant();
        int amount = arg.GetInt(1, 0);
        string message;
        int spawned = ForceSpawn(type, amount, out message);
        SendReply(arg, spawned < 0 ? message : $"{message}. Сейчас: медведи {_bears.Count}, кабаны {_boars.Count}");
    }

    private void MaintainPopulation()
    {
        Cleanup(_bears);
        Cleanup(_boars);
        SpawnMissing(BearPrefab, _bears, BearTarget);
        SpawnMissing(BoarPrefab, _boars, BoarTarget);
    }

    private void SpawnMissing(string prefab, HashSet<NetworkableId> ids, int target)
    {
        Cleanup(ids);
        int missing = target - ids.Count;
        for (int i = 0; i < missing; i++)
            SpawnAnimal(prefab, ids);
    }

    private void SpawnAnimal(string prefab, HashSet<NetworkableId> ids)
    {
        Vector3 position;
        if (!TryFindPosition(out position))
        {
            PrintWarning($"Не удалось найти свободную точку для {prefab}");
            return;
        }

        BaseEntity entity = GameManager.server.CreateEntity(prefab, position, Quaternion.Euler(0f, UnityEngine.Random.Range(0f, 360f), 0f));
        if (entity == null)
        {
            PrintWarning($"Не удалось создать {prefab}");
            return;
        }

        entity.enableSaving = false;
        entity.Spawn();
        if (entity.net != null) ids.Add(entity.net.ID);
    }

    private int ForceSpawn(string type, int amount, out string message)
    {
        Cleanup(_bears);
        Cleanup(_boars);

        if (type == "bear" || type == "bears")
        {
            int count = amount > 0 ? amount : BearTarget;
            SpawnMany(BearPrefab, _bears, count);
            message = $"Заспавнил медведей: {count}";
            return count;
        }

        if (type == "boar" || type == "boars" || type == "pig")
        {
            int count = amount > 0 ? amount : BoarTarget;
            SpawnMany(BoarPrefab, _boars, count);
            message = $"Заспавнил кабанов: {count}";
            return count;
        }

        if (type == "all")
        {
            int bears = amount > 0 ? amount : BearTarget;
            int boars = amount > 0 ? amount : BoarTarget;
            SpawnMany(BearPrefab, _bears, bears);
            SpawnMany(BoarPrefab, _boars, boars);
            message = $"Заспавнил медведей: {bears}, кабанов: {boars}";
            return bears + boars;
        }

        message = "Команда: /fanimal all, /fanimal bear 5, /fanimal boar 5 или fanimal.spawn all 5";
        return -1;
    }

    private void SpawnMany(string prefab, HashSet<NetworkableId> ids, int amount)
    {
        for (int i = 0; i < amount; i++)
            SpawnAnimal(prefab, ids);
    }

    private int ParseAmount(string value, int fallback)
    {
        int amount;
        return int.TryParse(value, out amount) ? amount : fallback;
    }

    private void Reply(BasePlayer player, string message)
    {
        if (player == null)
        {
            Puts(message);
            return;
        }

        PrintToChat(player, message);
    }

    private bool TryFindPosition(out Vector3 position)
    {
        float x = TerrainMeta.Size.x * 0.5f;
        float z = TerrainMeta.Size.z * 0.5f;

        for (int i = 0; i < MaxSpawnAttempts; i++)
        {
            position = new Vector3(UnityEngine.Random.Range(-x, x), 0f, UnityEngine.Random.Range(-z, z));
            if (IsValidPosition(ref position)) return true;
        }

        position = Vector3.zero;
        return false;
    }

    private bool IsValidPosition(ref Vector3 position)
    {
        RaycastHit hit;
        if (!Physics.Raycast(position + Vector3.up * 300f, Vector3.down, out hit, 400f, Layers.Solid)) return false;
        if (hit.GetEntity() != null) return false;

        position.y = hit.point.y + 0.15f;
        if (WaterLevel.Test(position, true, true)) return false;
        if (TerrainMeta.HeightMap.GetSlope(position) > MaxSlope) return false;
        if (AntiHack.TestInsideTerrain(position)) return false;
        if (!IsInsideWorld(position)) return false;
        if (HasBlockedCollider(position)) return false;
        if (HasBuildingNearby(position)) return false;
        if (HasAwakePlayerNearby(position)) return false;

        return true;
    }

    private bool HasBlockedCollider(Vector3 position)
    {
        _colliders.Clear();
        Vis.Colliders(position, 4f, _colliders);
        for (int i = 0; i < _colliders.Count; i++)
        {
            Collider collider = _colliders[i];
            if (collider == null) continue;

            int layer = collider.gameObject.layer;
            if (layer == (int)Layer.Prevent_Building) return true;
            if (layer == (int)Layer.Vehicle_Large || layer == (int)Layer.Vehicle_World || layer == (int)Layer.Vehicle_Detailed) return true;

            string name = collider.name.ToLowerInvariant();
            if (name.Contains("iceberg") || name.Contains("ice_sheet") || name.Contains("fireball") || name.Contains("radiation") || name.Contains("zonemanager")) return true;
        }

        return false;
    }

    private bool HasBuildingNearby(Vector3 position)
    {
        List<BaseEntity> entities = Facepunch.Pool.Get<List<BaseEntity>>();
        Vis.Entities(position, RadiusFromBuildings, entities, Layers.PlayerBuildings);
        bool result = entities.Count > 0;
        Facepunch.Pool.FreeUnmanaged(ref entities);
        return result;
    }

    private bool HasAwakePlayerNearby(Vector3 position)
    {
        List<BasePlayer> players = Facepunch.Pool.Get<List<BasePlayer>>();
        Vis.Entities(position, RadiusFromPlayers, players, Layers.Mask.Player_Server);
        for (int i = 0; i < players.Count; i++)
        {
            BasePlayer player = players[i];
            if (player != null && !player.IsSleeping())
            {
                Facepunch.Pool.FreeUnmanaged(ref players);
                return true;
            }
        }

        Facepunch.Pool.FreeUnmanaged(ref players);
        return false;
    }

    private bool IsInsideWorld(Vector3 position)
    {
        if (position.IsNaNOrInfinity()) return false;
        if (SingletonComponent<ValidBounds>.Instance && !SingletonComponent<ValidBounds>.Instance.worldBounds.Contains(position)) return false;
        if (World.Procedural && position.y < TerrainMeta.Position.y) return false;
        return !TerrainMeta.OutOfMargin(position);
    }

    private void Cleanup(HashSet<NetworkableId> ids)
    {
        List<NetworkableId> remove = Facepunch.Pool.Get<List<NetworkableId>>();
        foreach (NetworkableId id in ids)
        {
            BaseAnimalNPC animal = BaseNetworkable.serverEntities.Find(id) as BaseAnimalNPC;
            if (animal == null || animal.IsDestroyed) remove.Add(id);
        }

        for (int i = 0; i < remove.Count; i++)
            ids.Remove(remove[i]);

        Facepunch.Pool.FreeUnmanaged(ref remove);
    }

    private void KillTracked(HashSet<NetworkableId> ids)
    {
        foreach (NetworkableId id in ids)
        {
            BaseAnimalNPC animal = BaseNetworkable.serverEntities.Find(id) as BaseAnimalNPC;
            if (animal != null && !animal.IsDestroyed) animal.Kill();
        }
    }
}

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху