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

FREE MushroomPlanter : 2.4 2.4

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

namespace Oxide.Plugins
{
    [Info("MushroomPlanter", "NINJA WORKS", "2.4")]
    [Description("Mushroom cultivation plugin - Trial Version")]
    public class MushroomPlanter : RustPlugin
    {

        private const bool DEBUG_MODE = false;

        [PluginReference]
        private Plugin ImageLibrary;

        private const string SeedInfoUIName = "MushroomPlanterInfoPanel";

        #region Seed Info UI Constants
        private const string UI_ANCHOR_MIN_X = "0.65";
        private const string UI_ANCHOR_MIN_Y = "0.32";
        private const string UI_ANCHOR_MAX_X = "0.945";
        private const string UI_ANCHOR_MAX_Y = "0.92";
        private const string UI_BG_COLOR     = "0 0 0 0";

        private const int    UI_TITLE_HEIGHT     = 30;
        private const int    UI_TITLE_FONT_SIZE  = 13;
        private const string UI_TITLE_COLOR      = "0.91 0.86 0.83 1";
        private const string UI_TITLE_BG_COLOR   = "0.324 0.310 0.296 0.8";
        private const int    UI_PANEL_MARGIN     = 0;
        private const int    UI_PANEL_MARGIN_BOT = 0;

        private const float  UI_SCALE            = 1.0f;

        private const int    UI_CONTENT_PAD      = 10;
        private const int    UI_HEADER_HEIGHT    = 26;
        private const int    UI_HEADER_INFO_GAP  = 6;
        private const int    UI_LINE_HEIGHT      = 18;
        private const int    UI_ICON_TEXT_GAP    = 12;
        private const int    UI_INFO_DROP_GAP    = 10;
        private const int    UI_DROP_LABEL_GAP   = 6;
        private const int    UI_SECTION_GAP      = 14;

        private const int    UI_SEED_ICON_SIZE   = 60;
        private const int    UI_DROP_ICON_SIZE   = 44;
        private const float  UI_DROP_IMG_SCALE   = 0.8f;
        private const int    UI_DROP_ICON_GAP    = 5;
        private const int    UI_ICONS_PER_ROW    = 6;

        private const int    UI_HEADER_FONT_SIZE = 13;
        private const int    UI_TEXT_FONT_SIZE   = 11;
        private const int    UI_DROP_FONT_SIZE   = 11;

        private const string UI_HEADER_COLOR     = "0.91 0.86 0.83 1";
        private const string UI_TEXT_COLOR       = "0.91 0.86 0.83 1";
        private const string UI_DROP_LABEL_COLOR = "0.91 0.86 0.83 1";
        private const string UI_ICON_BG_COLOR    = "0.324 0.310 0.296 0.8";

        private const float  UI_SCROLL_SENS      = 30f;
        private const float  UI_SCROLL_ELASTICITY = 0.1f;
        private const float  UI_SCROLL_DECEL     = 0.135f;
        #endregion

        #region Configuration

        private Configuration config;

        public class Configuration
        {
            [JsonProperty("Mushroom Settings")]
            public PickupPlantSettings Mushroom { get; set; }

            [JsonProperty("Wild Mushroom Mycelium Drop Settings")]
            public WildMushroomMyceliumSettings WildMycelium { get; set; }

            public static Configuration DefaultConfig()
            {
                return new Configuration
                {
                    WildMycelium = new WildMushroomMyceliumSettings(),
                    Mushroom = new PickupPlantSettings
                    {
                        SeedName = "Mushroom mycelium",
                        SkinId = 3675820909,
                        CheckInterval = 900f,
                        GrowthChance = 1.0f,
                        WaterRequired = 300f,
                        MaxDropTypes = 1,
                        DropItems = new List<DropItemConfig>
                        {
                            new DropItemConfig("mushroom", null, 0, 1, 3, 1.0f),
                            new DropItemConfig("mushroom", "Enoki mushroom", 3676097640, 1, 3, 1.0f),
                            new DropItemConfig("mushroom", "King oyster Mushroom", 3676103539, 1, 3, 1.0f),
                            new DropItemConfig("mushroom", "Maitake mushroom", 3676103641, 1, 3, 1.0f),
                            new DropItemConfig("mushroom", "Shiitake mushroom", 3676103885, 1, 3, 1.0f),
                            new DropItemConfig("mushroom", "Shimeji mushroom", 3676103982, 1, 3, 1.0f),
                            new DropItemConfig("spoiled.produce", "Fly agaric", 3676103435, 1, 1, 0.1f),
                            new DropItemConfig("apple", "truffles", 3676104055, 1, 1, 0.1f),
                            new DropItemConfig("apple", "Matsutake mushroom", 3676103783, 1, 1, 0.1f)
                        }
                    }
                };
            }
        }

        public class PickupPlantSettings
        {
            [JsonProperty("Seed Name")]
            public string SeedName { get; set; }

            [JsonProperty("Seed Skin ID")]
            public ulong SkinId { get; set; }

            [JsonProperty("Growth Time (seconds)")]
            public float CheckInterval { get; set; }

            [JsonProperty("Growth Chance (0.0-1.0)")]
            public float GrowthChance { get; set; }

            [JsonProperty("Water Required")]
            public float WaterRequired { get; set; }

            [JsonProperty("Max Drop Types Per Harvest")]
            public int MaxDropTypes { get; set; }

            [JsonProperty("Drop Items")]
            public List<DropItemConfig> DropItems { get; set; }
        }

        public class DropItemConfig
        {
            [JsonProperty("Item Short Name")]
            public string ItemShortName { get; set; }

            [JsonProperty("Custom Item Name")]
            public string CustomItemName { get; set; }

            [JsonProperty("Skin ID")]
            public ulong SkinId { get; set; }

            [JsonProperty("Min Amount")]
            public int MinAmount { get; set; }

            [JsonProperty("Max Amount")]
            public int MaxAmount { get; set; }

            [JsonProperty("Drop Chance (0.0-1.0)")]
            public float DropChance { get; set; }

            public DropItemConfig() { }

            public DropItemConfig(string itemShortName, string customItemName, ulong skinId, int minAmount, int maxAmount, float dropChance)
            {
                ItemShortName = itemShortName;
                CustomItemName = customItemName;
                SkinId = skinId;
                MinAmount = minAmount;
                MaxAmount = maxAmount;
                DropChance = dropChance;
            }
        }

        public class WildMushroomMyceliumSettings
        {
            [JsonProperty("Enabled")]
            public bool Enabled { get; set; } = true;

            [JsonProperty("Drop Chance (0.0-1.0)")]
            public float DropChance { get; set; } = 1.0f;

            [JsonProperty("Min Amount")]
            public int MinAmount { get; set; } = 2;

            [JsonProperty("Max Amount")]
            public int MaxAmount { get; set; } = 3;
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null)
                {
                    LoadDefaultConfig();
                }
                if (config.WildMycelium == null)
                {
                    config.WildMycelium = new WildMushroomMyceliumSettings();
                }
            }
            catch
            {
                DebugError("Failed to load config file. Creating default config.");
                LoadDefaultConfig();
            }
            SaveConfig();
        }

        protected override void LoadDefaultConfig()
        {
            config = Configuration.DefaultConfig();
        }

        protected override void SaveConfig()
        {
            Config.WriteObject(config, true);
        }

        #endregion

        private const float MinimumWaterForPlantGrowth = 0f;

        private const int BATCH_SIZE = 100;
        private Coroutine savePickUpPlantsCoroutine;

        private static readonly string[] MushroomClusterPrefabs = new string[]
        {
            "assets/bundled/prefabs/autospawn/collectable/mushrooms/mushroom-cluster-5.prefab",
            "assets/bundled/prefabs/autospawn/collectable/mushrooms/mushroom-cluster-6.prefab"
        };

        private const string LargePlanterPrefab = "assets/prefabs/deployable/planters/planter.large.deployed.prefab";
        private const string SmallPlanterPrefab = "assets/prefabs/deployable/planters/planter.small.deployed.prefab";
        private const string BathtubPlanterPrefab = "assets/prefabs/misc/decor_dlc/bath tub planter/bathtub.planter.deployed.prefab";
        private const string MinecartPlanterPrefab = "assets/prefabs/misc/decor_dlc/minecart planter/minecart.planter.deployed.prefab";
        private const string RailRoadPlanterPrefab = "assets/prefabs/misc/decor_dlc/rail road planter/railroadplanter.deployed.prefab";
        private const string SinglePlantPotPrefab = "assets/prefabs/deployable/plant pots/plantpot.single.deployed.prefab";
        private const string TrianglePlanterPrefab = "assets/prefabs/deployable/planters/planter.triangle.deployed.prefab";
        private const string RailRoadTrianglePlanterPrefab = "assets/prefabs/misc/decor_dlc/rail road planter/triangle_railroad_planter.deployed.prefab";

        private class PlantDropItem
        {
            public string ItemShortName { get; set; }
            public ulong SkinId { get; set; }
            public int MinAmount { get; set; }
            public int MaxAmount { get; set; }
            public float DropChance { get; set; }
            public string CustomItemName { get; set; }

            public PlantDropItem(string itemShortName, ulong skinId, int minAmount, int maxAmount, float dropChance, string customItemName = null)
            {
                ItemShortName = itemShortName;
                SkinId = skinId;
                MinAmount = minAmount;
                MaxAmount = maxAmount;
                DropChance = Mathf.Clamp01(dropChance);
                CustomItemName = customItemName;
            }
        }

        private class SpecialPlantInfo
        {
            public List<PlantDropItem> DropItems { get; set; }
            public int MaxDropTypes { get; set; }

            public SpecialPlantInfo(List<PlantDropItem> dropItems, int maxDropTypes = 1)
            {
                DropItems = dropItems ?? new List<PlantDropItem>();
                MaxDropTypes = maxDropTypes;
            }
        }

        private HashSet<ItemId> specialPlantGrowthFertilizers = new HashSet<ItemId>();
        private Dictionary<string, SpecialPlantInfo> specialPlants;
        private Dictionary<string, List<SerializablePickUpPlantData>> savedPickUpPlants = new Dictionary<string, List<SerializablePickUpPlantData>>();

        private Dictionary<ItemId, Timer> _seedTimers = new Dictionary<ItemId, Timer>();

        private Timer _mushroomTimer;

        private class SerializablePickUpPlantData
        {
            public string PrefabName;
            public Vector3 LocalPosition;
            public Vector3 LocalRotationEuler;

            public SerializablePickUpPlantData() { }

            public SerializablePickUpPlantData(BaseEntity plant)
            {
                PrefabName = plant.PrefabName;
                LocalPosition = plant.transform.localPosition;
                LocalRotationEuler = plant.transform.localRotation.eulerAngles;
            }

            public BaseEntity CreatePickUpPlant(PlanterBox planter)
            {
                Vector3 worldPosition = planter.transform.TransformPoint(LocalPosition);
                Quaternion worldRotation = planter.transform.rotation * Quaternion.Euler(LocalRotationEuler);

                BaseEntity plant = GameManager.server.CreateEntity(PrefabName, worldPosition, worldRotation);
                if (plant != null)
                {
                    plant.OwnerID = planter.OwnerID;
                    plant.enableSaving = true;
                    plant.Spawn();
                    plant.SendNetworkUpdateImmediate();
                    plant.SetParent(planter, worldPositionStays: true);
                    plant.transform.hasChanged = true;
                    plant.UpdateNetworkGroup();
                    plant.SendNetworkUpdateImmediate();
                }
                return plant;
            }
        }

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Title"] = "Seed Info",
                ["GrowthTime"] = "Growth Time",
                ["Chance"] = "Chance",
                ["WaterRequired"] = "Water Required",
                ["MaxDropTypes"] = "Max Drop Types",
                ["DropItems"] = "Harvestable Crops"
            }, this, "en");

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Title"] = "種の情報",
                ["GrowthTime"] = "成長時間",
                ["Chance"] = "成長確率",
                ["WaterRequired"] = "必要水量",
                ["MaxDropTypes"] = "最大ドロップ種類",
                ["DropItems"] = "収穫可能な作物"
            }, this, "ja");

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Title"] = "种子信息",
                ["GrowthTime"] = "生长时间",
                ["Chance"] = "生长概率",
                ["WaterRequired"] = "需水量",
                ["MaxDropTypes"] = "最大掉落种类",
                ["DropItems"] = "可收获作物"
            }, this, "zh-CN");

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["Title"] = "Информация о семенах",
                ["GrowthTime"] = "Время роста",
                ["Chance"] = "Шанс роста",
                ["WaterRequired"] = "Требуется воды",
                ["MaxDropTypes"] = "Макс. типов дропа",
                ["DropItems"] = "Собираемый урожай"
            }, this, "ru");
        }

        private string Msg(string key, BasePlayer player = null)
        {
            return lang.GetMessage(key, this, player?.UserIDString);
        }

        private void OnServerInitialized()
        {
            try
            {
                if (specialPlants == null)
                {
                    specialPlants = new Dictionary<string, SpecialPlantInfo>();
                }

                specialPlants.Clear();

                var mushroomDropItems = new List<PlantDropItem>();
                foreach (var dropConfig in config.Mushroom.DropItems)
                {
                    mushroomDropItems.Add(new PlantDropItem(
                        dropConfig.ItemShortName,
                        dropConfig.SkinId,
                        dropConfig.MinAmount,
                        dropConfig.MaxAmount,
                        dropConfig.DropChance,
                        dropConfig.CustomItemName
                    ));
                }
                specialPlants.Add(MushroomClusterPrefabs[0], new SpecialPlantInfo(mushroomDropItems, config.Mushroom.MaxDropTypes));
                specialPlants.Add(MushroomClusterPrefabs[1], new SpecialPlantInfo(mushroomDropItems, config.Mushroom.MaxDropTypes));

                if (timer == null)
                {
                    DebugWarning("Warning: timer is null. Some functionalities may not work.");
                }
                else
                {
                    NextTick(() => ScheduleExistingSeeds());
                }

                CleanupInvalidPlanterData();
                NextTick(() => {
                    RemoveAllPickUpPlants();
                    LoadPickUpPlants();
                });

                DebugLog("MushroomPlanter (Trial) initialized successfully.");

                LoadAllSkinImages();
            }
            catch (Exception ex)
            {
                DebugError($"Error in OnServerInitialized: {ex.Message}");
                DebugError($"StackTrace: {ex.StackTrace}");
            }
        }

        private void OnServerSave()
        {
            NextTick(() =>
            {
                CleanupInvalidPlanterData();

                if (savePickUpPlantsCoroutine != null)
                    ServerMgr.Instance.StopCoroutine(savePickUpPlantsCoroutine);

                savePickUpPlantsCoroutine = ServerMgr.Instance.StartCoroutine(SavePickUpPlantsCoroutine());
            });
        }

        void Unload()
        {
            _mushroomTimer?.Destroy();

            foreach (var kvp in _seedTimers)
            {
                kvp.Value?.Destroy();
            }
            _seedTimers.Clear();

            if (savePickUpPlantsCoroutine != null)
                ServerMgr.Instance.StopCoroutine(savePickUpPlantsCoroutine);

            SavePickUpPlantsImmediate();

            foreach (var player in BasePlayer.activePlayerList)
            {
                DestroySeedInfoUI(player);
            }

            NextTick(() => {
                RemoveAllPickUpPlants();
            });
        }

        void RemoveAllPickUpPlants()
        {
            var planters = BaseNetworkable.serverEntities.OfType<PlanterBox>();
            int removedCount = 0;

            foreach (var planter in planters)
            {
                var plantsToRemove = new List<BaseEntity>();
                foreach (var child in planter.children)
                {
                    if (IsPickUpPlant(child))
                    {
                        plantsToRemove.Add(child);
                    }
                }

                foreach (var plant in plantsToRemove)
                {
                    plant.Kill();
                    removedCount++;
                }
            }

            DebugLog($"Removed {removedCount} pickup plants during plugin operation.");
        }

        private System.Collections.IEnumerator SavePickUpPlantsCoroutine()
        {
            savedPickUpPlants.Clear();
            var planters = BaseNetworkable.serverEntities.OfType<PlanterBox>().ToArray();
            int totalPlanters = planters.Length;
            int processedCount = 0;

            DebugLog($"[Batch Process] Pickup plants save started: {totalPlanters} planters (batch size: {BATCH_SIZE})");

            for (int i = 0; i < totalPlanters; i += BATCH_SIZE)
            {
                int endIndex = Mathf.Min(i + BATCH_SIZE, totalPlanters);

                for (int j = i; j < endIndex; j++)
                {
                    var planter = planters[j];
                    if (planter == null || planter.IsDestroyed)
                        continue;

                    var plantList = new List<SerializablePickUpPlantData>();
                    foreach (var child in planter.children)
                    {
                        if (IsPickUpPlant(child))
                        {
                            plantList.Add(new SerializablePickUpPlantData(child));
                        }
                    }

                    if (plantList.Count > 0)
                    {
                        savedPickUpPlants[planter.net.ID.Value.ToString()] = plantList;
                    }
                    processedCount++;
                }

                yield return null;
            }

            Interface.Oxide.DataFileSystem.WriteObject("MushroomPlanter_PickUpPlants", savedPickUpPlants);
            DebugLog($"[Batch Process] Pickup plants data saved: {processedCount}/{totalPlanters} planters");
        }

        private void SavePickUpPlantsImmediate()
        {
            savedPickUpPlants.Clear();
            var planters = BaseNetworkable.serverEntities.OfType<PlanterBox>();
            int savedCount = 0;

            foreach (var planter in planters)
            {
                if (planter == null || planter.IsDestroyed)
                    continue;

                var plantList = new List<SerializablePickUpPlantData>();
                foreach (var child in planter.children)
                {
                    if (IsPickUpPlant(child))
                    {
                        plantList.Add(new SerializablePickUpPlantData(child));
                    }
                }
                if (plantList.Count > 0)
                {
                    savedPickUpPlants[planter.net.ID.Value.ToString()] = plantList;
                    savedCount++;
                }
            }
            Interface.Oxide.DataFileSystem.WriteObject("MushroomPlanter_PickUpPlants", savedPickUpPlants);
            DebugLog($"Pickup plant data saved immediately: {savedCount} planters");
        }

        void SavePickUpPlants()
        {
            SavePickUpPlantsImmediate();
        }

        void LoadPickUpPlants()
        {
            savedPickUpPlants = Interface.Oxide.DataFileSystem.ReadObject<Dictionary<string, List<SerializablePickUpPlantData>>>("MushroomPlanter_PickUpPlants");
            if (savedPickUpPlants == null)
            {
                savedPickUpPlants = new Dictionary<string, List<SerializablePickUpPlantData>>();
                DebugLog("No saved pickup plants data found. Starting fresh.");
                return;
            }

            HashSet<string> validPlanterIds = new HashSet<string>();
            var planters = BaseNetworkable.serverEntities.OfType<PlanterBox>();
            foreach (var planter in planters)
            {
                validPlanterIds.Add(planter.net.ID.Value.ToString());
            }

            int restoredCount = 0;

            foreach (var entry in savedPickUpPlants.ToList())
            {
                if (validPlanterIds.Contains(entry.Key) && ulong.TryParse(entry.Key, out ulong entityId))
                {
                    var planter = BaseNetworkable.serverEntities.Find(new NetworkableId(entityId)) as PlanterBox;
                    if (planter != null)
                    {
                        foreach (var plantData in entry.Value)
                        {
                            BaseEntity plant = plantData.CreatePickUpPlant(planter);
                            if (plant != null)
                            {
                                restoredCount++;
                            }
                        }
                    }
                }
                else
                {
                    savedPickUpPlants.Remove(entry.Key);
                }
            }

            Interface.Oxide.DataFileSystem.WriteObject("MushroomPlanter_PickUpPlants", savedPickUpPlants);
            DebugLog($"Restored {restoredCount} pickup plants across {savedPickUpPlants.Count} planters.");
        }

        void CleanupInvalidPlanterData()
        {
            int removedPickUpEntries = 0;

            HashSet<string> validPlanterIds = new HashSet<string>();
            var planters = BaseNetworkable.serverEntities.OfType<PlanterBox>();

            foreach (var planter in planters)
            {
                validPlanterIds.Add(planter.net.ID.Value.ToString());
            }

            List<string> invalidPickUpKeys = savedPickUpPlants.Keys
                .Where(key => !validPlanterIds.Contains(key))
                .ToList();

            foreach (string key in invalidPickUpKeys)
            {
                savedPickUpPlants.Remove(key);
                removedPickUpEntries++;
            }

            if (removedPickUpEntries > 0)
            {
                Interface.Oxide.DataFileSystem.WriteObject("MushroomPlanter_PickUpPlants", savedPickUpPlants);
                DebugLog($"Cleanup: {removedPickUpEntries} pickup plant data entries removed");
            }
        }

        bool IsPickUpPlant(BaseEntity entity)
        {
            return MushroomClusterPrefabs.Contains(entity.PrefabName);
        }

        private object OnCollectiblePickup(CollectibleEntity entity, BasePlayer player)
        {
            if (entity == null || player == null)
                return null;

            var planter = entity.GetParentEntity() as PlanterBox;

            if (planter == null)
            {
                TryGiveWildMycelium(entity, player);
                return null;
            }

            if (!IsPickUpPlant(entity))
                return null;

            string prefabName = entity.PrefabName;
            var matchingPlant = specialPlants.FirstOrDefault(kvp => prefabName.EndsWith(kvp.Key, StringComparison.OrdinalIgnoreCase));

            if (matchingPlant.Equals(default(KeyValuePair<string, SpecialPlantInfo>)))
                return null;

            var plantInfo = matchingPlant.Value;
            var shuffledDropItems = plantInfo.DropItems.OrderBy(x => UnityEngine.Random.value).ToList();
            int droppedCount = 0;

            foreach (var dropItem in shuffledDropItems)
            {
                if (droppedCount >= plantInfo.MaxDropTypes)
                    break;

                if (UnityEngine.Random.value <= dropItem.DropChance)
                {
                    int amount = UnityEngine.Random.Range(dropItem.MinAmount, dropItem.MaxAmount + 1);
                    Item newItem = ItemManager.CreateByName(dropItem.ItemShortName, amount, dropItem.SkinId);

                    if (!string.IsNullOrEmpty(dropItem.CustomItemName))
                    {
                        newItem.name = dropItem.CustomItemName;
                    }

                    player.GiveItem(newItem);
                    droppedCount++;
                }
            }

            Vector3 pickupPosition = entity.transform.position;
            entity.Kill();

            Effect.server.Run("assets/bundled/prefabs/fx/collect/collect mushroom.prefab", pickupPosition);

            return true;
        }

        private void TryGiveWildMycelium(CollectibleEntity entity, BasePlayer player)
        {
            if (config.WildMycelium == null || !config.WildMycelium.Enabled)
                return;

            if (!MushroomClusterPrefabs.Contains(entity.PrefabName))
                return;

            if (UnityEngine.Random.value > config.WildMycelium.DropChance)
                return;

            int amount = UnityEngine.Random.Range(config.WildMycelium.MinAmount, config.WildMycelium.MaxAmount + 1);
            if (amount <= 0)
                return;

            Item mycelium = ItemManager.CreateByName("fertilizer", amount, config.Mushroom.SkinId);
            if (mycelium == null)
                return;

            mycelium.name = config.Mushroom.SeedName;
            player.GiveItem(mycelium);
        }

        private void OnItemUse(Item item, int amount)
        {
            if (item == null || item.info == null)
                return;

            if (item.info.shortname != "fertilizer" || amount <= 0)
                return;

            if (item.parent == null)
                return;

            var planter = item.GetEntityOwner() as PlanterBox;
            if (planter == null || planter.IsDestroyed)
                return;

            ulong skinId = item.skin;

            if (skinId != config.Mushroom.SkinId)
                return;

            int originalAmount = item.amount;
            int itemId = item.info.itemid;
            ItemId itemUid = item.uid;

            NextTick(() =>
            {
                if (item == null || !item.IsValid())
                    return;

                if (planter == null || planter.IsDestroyed || planter.inventory?.itemList == null)
                    return;

                int usedAmount = originalAmount - item.amount;
                if (usedAmount <= 0)
                    return;

                if (specialPlantGrowthFertilizers.Contains(itemUid))
                {
                    specialPlantGrowthFertilizers.Remove(itemUid);
                    return;
                }

                Item existingFertilizer = planter.inventory.itemList
                    .FirstOrDefault(i => i != null && i.info != null && i.info.itemid == itemId && i.skin == skinId && i != item);

                if (existingFertilizer != null)
                {
                    int spaceInStack = existingFertilizer.MaxStackable() - existingFertilizer.amount;
                    int amountToAdd = Math.Min(usedAmount, spaceInStack);

                    existingFertilizer.amount += amountToAdd;
                    existingFertilizer.MarkDirty();

                    usedAmount -= amountToAdd;
                }

                if (usedAmount > 0)
                {
                    Item newFertilizer = ItemManager.CreateByItemID(itemId, usedAmount, skinId);

                    if (newFertilizer != null)
                    {
                        if (!planter.inventory.GiveItem(newFertilizer))
                        {
                            newFertilizer.Remove();
                        }
                    }
                }

                planter.SendNetworkUpdate();
            });
        }

        void ScheduleExistingSeeds()
        {
            var planters = BaseNetworkable.serverEntities.OfType<PlanterBox>();
            int count = 0;
            foreach (var planter in planters)
            {
                if (planter == null || planter.IsDestroyed || planter.inventory?.itemList == null)
                    continue;

                foreach (Item item in planter.inventory.itemList)
                {
                    if (item == null || item.info == null || item.info.shortname != "fertilizer")
                        continue;

                    if (item.skin == config.Mushroom.SkinId)
                    {
                        ScheduleSeedTimer(planter, item, config.Mushroom.CheckInterval, config.Mushroom.GrowthChance, config.Mushroom.WaterRequired);
                        count++;
                    }
                }
            }
            DebugLog($"Scheduled timers for {count} existing seeds.");
        }

        void ScheduleSeedTimer(PlanterBox planter, Item seedItem, float interval, float growthChance, float waterRequired)
        {
            CancelSeedTimer(seedItem.uid);

            var seedTimer = timer.Every(interval, () =>
            {
                if (seedItem == null || seedItem.parent == null || seedItem.amount < 1)
                {
                    CancelSeedTimer(seedItem.uid);
                    return;
                }

                if (planter == null || !planter.IsValid() || planter.IsDestroyed)
                {
                    CancelSeedTimer(seedItem.uid);
                    return;
                }

                TryGrowSingleSeed(planter, seedItem, growthChance, waterRequired);
            });

            _seedTimers[seedItem.uid] = seedTimer;
        }

        void CancelSeedTimer(ItemId uid)
        {
            if (_seedTimers.TryGetValue(uid, out var existingTimer))
            {
                existingTimer?.Destroy();
                _seedTimers.Remove(uid);
            }
        }

        void TryGrowSingleSeed(PlanterBox planter, Item fertilizer, float growthChance, float waterRequired)
        {
            if (planter == null || !planter.IsValid() || planter.IsDestroyed)
                return;

            if (fertilizer == null || fertilizer.amount < 1)
                return;

            if (UnityEngine.Random.value > growthChance)
                return;

            if (planter.soilSaturation < waterRequired)
                return;

            if (IsPlayerOnPlanter(planter))
                return;

            PlanterInfo planterInfo = GetPlanterInfo(planter);
            List<Vector3> availablePositions = GetAvailablePositions(planter, planterInfo);

            if (availablePositions.Count == 0)
                return;

            ulong skinId = fertilizer.skin;
            string prefab = GetPrefabForSkinId(skinId);
            if (string.IsNullOrEmpty(prefab))
                return;

            if (skinId == config.Mushroom.SkinId)
            {
                specialPlantGrowthFertilizers.Add(fertilizer.uid);
            }

            int posIndex = UnityEngine.Random.Range(0, availablePositions.Count);
            Vector3 position = availablePositions[posIndex];

            Quaternion randomRotation = Quaternion.Euler(0, UnityEngine.Random.Range(0f, 360f), 0);
            BaseEntity plant = GameManager.server.CreateEntity(prefab, position, randomRotation);

            if (plant != null)
            {
                planter.soilSaturation -= (int)waterRequired;

                if (planter.soilSaturation < 0)
                    planter.soilSaturation = 0;

                plant.Spawn();
                plant.SendNetworkUpdateImmediate();
                plant.SetParent(planter, worldPositionStays: true);
                plant.transform.hasChanged = true;
                plant.UpdateNetworkGroup();
                plant.SendNetworkUpdateImmediate();

                planter.SendNetworkUpdate();

                fertilizer.UseItem(1);

                if (fertilizer.amount < 1)
                {
                    CancelSeedTimer(fertilizer.uid);
                }
            }
        }

        string GetPrefabForSkinId(ulong skinId)
        {
            if (skinId == config.Mushroom.SkinId)
                return MushroomClusterPrefabs[UnityEngine.Random.Range(0, MushroomClusterPrefabs.Length)];
            return "";
        }

        private bool IsPlayerOnPlanter(PlanterBox planter)
        {
            if (planter == null || planter.IsDestroyed)
                return false;

            Bounds planterBounds = GetPlanterBounds(planter);
            planterBounds.Expand(new Vector3(0.0f, 0.5f, 0.0f));

            var players = BasePlayer.activePlayerList.Concat(BasePlayer.sleepingPlayerList);
            foreach (var player in players)
            {
                if (player == null || player.IsDestroyed || player.IsDead())
                    continue;

                if (planterBounds.Contains(player.transform.position))
                    return true;

                if (player.IsSleeping() && planterBounds.Contains(player.eyes.position))
                    return true;
            }

            return false;
        }

        private Bounds GetPlanterBounds(PlanterBox planter)
        {
            Bounds result;

            var collider = planter.GetComponent<Collider>();
            if (collider != null)
            {
                result = collider.bounds;
            }
            else
            {
                Vector3 size = Vector3.one;

                switch (planter.PrefabName)
                {
                    case LargePlanterPrefab:
                    case RailRoadPlanterPrefab:
                        size = new Vector3(3f, 0.5f, 3f);
                        break;
                    case SmallPlanterPrefab:
                        size = new Vector3(1.5f, 0.5f, 0.5f);
                        break;
                    case BathtubPlanterPrefab:
                        size = new Vector3(2f, 0.5f, 0.8f);
                        break;
                    case MinecartPlanterPrefab:
                        size = new Vector3(1.5f, 0.5f, 0.8f);
                        break;
                    case SinglePlantPotPrefab:
                        size = new Vector3(0.5f, 0.5f, 0.5f);
                        break;
                    case TrianglePlanterPrefab:
                        size = new Vector3(1.5f, 0.5f, 1.5f);
                        break;
                }

                result = new Bounds(planter.transform.position, size);
            }

            return result;
        }

        private bool _isSortingFertilizers;

        private void OnItemAddedToContainer(ItemContainer container, Item item)
        {
            if (_isSortingFertilizers) return;
            if (container == null || item == null || item.info == null) return;
            if (container.entityOwner is PlanterBox planter && item.info.shortname == "fertilizer")
            {
                if (item.skin == config.Mushroom.SkinId)
                {
                    ScheduleSeedTimer(planter, item, config.Mushroom.CheckInterval, config.Mushroom.GrowthChance, config.Mushroom.WaterRequired);
                }

                SortFertilizers(planter);
            }
        }

        private void OnItemRemovedFromContainer(ItemContainer container, Item item)
        {
            if (_isSortingFertilizers) return;
            if (container == null || item == null || item.info == null) return;
            if (container.entityOwner is PlanterBox && item.info.shortname == "fertilizer")
            {
                if (item.skin == config.Mushroom.SkinId)
                {
                    CancelSeedTimer(item.uid);
                }
            }
        }

        void SortFertilizers(PlanterBox planter)
        {
            if (planter == null || planter.IsDestroyed || planter.inventory?.itemList == null) return;

            _isSortingFertilizers = true;
            try
            {
                List<Item> fertilizers = planter.inventory.itemList
                    .Where(i => i != null && i.info != null && i.info.shortname == "fertilizer")
                    .OrderBy(i => i.skin == 0 ? 0 : 1)
                    .ThenBy(i => i.skin)
                    .ToList();

                if (fertilizers.Count > 0 && fertilizers.Any(i => i.skin != 0))
                {
                    for (int i = 0; i < fertilizers.Count; i++)
                    {
                        Item fertilizer = fertilizers[i];
                        int oldSlot = fertilizer.position;
                        int newSlot = i;

                        if (oldSlot != newSlot)
                        {
                            fertilizer.position = newSlot;
                        }
                    }

                    planter.inventory.MarkDirty();
                    planter.SendNetworkUpdate();
                }
            }
            finally
            {
                _isSortingFertilizers = false;
            }
        }

        PlanterInfo GetPlanterInfo(PlanterBox planter)
        {
            switch (planter.PrefabName)
            {
                case LargePlanterPrefab:
                    return new PlanterInfo(3, 3, 3f, 9, 0.3f);
                case SmallPlanterPrefab:
                    return new PlanterInfo(1, 3, 1.5f, 3, 0.3f);
                case BathtubPlanterPrefab:
                    return new PlanterInfo(1, 3, 2f, 3, 0.6f);
                case MinecartPlanterPrefab:
                    return new PlanterInfo(1, 2, 1.5f, 2, 0.8f);
                case RailRoadPlanterPrefab:
                    return new PlanterInfo(3, 3, 3f, 9, 0.4f);
                case SinglePlantPotPrefab:
                    return new PlanterInfo(1, 1, 0.5f, 1, 0.3f);
                case TrianglePlanterPrefab:
                    return new PlanterInfo(2, 2, 1.5f, 4, 0.3f);
                case RailRoadTrianglePlanterPrefab:
                    return new PlanterInfo(2, 2, 1.5f, 4, 0.3f);
                default:
                    return new PlanterInfo(1, 3, 1.5f, 3, 0.3f);
            }
        }

        List<Vector3> GetAvailablePositions(PlanterBox planter, PlanterInfo info)
        {
            switch (planter.PrefabName)
            {
                case LargePlanterPrefab:
                case RailRoadPlanterPrefab:
                    return GetLargePlanterPositions(planter, info);
                case SmallPlanterPrefab:
                    return GetSmallPlanterPositions(planter, info);
                case BathtubPlanterPrefab:
                    return GetLinearPlanterPositions(planter, info, 0.9f, planter.transform.right);
                case MinecartPlanterPrefab:
                    return GetLinearPlanterPositions(planter, info, 0.6f, planter.transform.right);
                case SinglePlantPotPrefab:
                    return GetSinglePlantPotPositions(planter, info);
                case TrianglePlanterPrefab:
                    return GetTrianglePlanterPositions(planter, info);
                case RailRoadTrianglePlanterPrefab:
                    return GetTrianglePlanterPositions(planter, info);
                default:
                    return GetSmallPlanterPositions(planter, info);
            }
        }

        List<Vector3> GetLargePlanterPositions(PlanterBox planter, PlanterInfo info)
        {
            List<Vector3> positions = new List<Vector3>();
            float cellSizeX = info.Size / info.GridSizeX;
            float cellSizeZ = info.Size / info.GridSizeZ;
            float startOffsetX = -cellSizeX * (info.GridSizeX - 1) / 2;
            float startOffsetZ = -cellSizeZ * (info.GridSizeZ - 1) / 2;

            for (int row = 0; row < info.GridSizeZ; row++)
            {
                for (int col = 0; col < info.GridSizeX; col++)
                {
                    float xOffset = startOffsetX + col * cellSizeX;
                    float zOffset = startOffsetZ + row * cellSizeZ;

                    Vector3 position = planter.transform.position +
                                       planter.transform.right * xOffset +
                                       planter.transform.forward * zOffset +
                                       new Vector3(0, info.Height, 0);

                    if (!IsPositionOccupied(planter, position, Mathf.Min(cellSizeX, cellSizeZ) / 2))
                    {
                        positions.Add(position);
                    }
                }
            }
            return positions;
        }

        List<Vector3> GetSmallPlanterPositions(PlanterBox planter, PlanterInfo info)
        {
            List<Vector3> positions = new List<Vector3>();
            float planterLength = info.Size;
            float effectiveLength = planterLength * 1.3f;
            float spacing = effectiveLength / (info.MaxMushrooms - 1);

            for (int i = 0; i < info.MaxMushrooms; i++)
            {
                float offset = (i - 1) * spacing;
                Vector3 position = planter.transform.position +
                                   planter.transform.right * offset +
                                   new Vector3(0, info.Height, 0);
                if (!IsPositionOccupied(planter, position, spacing / 2))
                {
                    positions.Add(position);
                }
            }
            return positions;
        }

        List<Vector3> GetLinearPlanterPositions(PlanterBox planter, PlanterInfo info, float effectiveLengthFactor, Vector3 direction)
        {
            List<Vector3> positions = new List<Vector3>();
            float planterLength = info.Size;
            float effectiveLength = planterLength * effectiveLengthFactor;
            float spacing = effectiveLength / (info.MaxMushrooms - 1);

            for (int i = 0; i < info.MaxMushrooms; i++)
            {
                float offset = (i - (info.MaxMushrooms - 1) / 2f) * spacing;
                Vector3 position = planter.transform.position +
                                   direction * offset +
                                   new Vector3(0, info.Height, 0);

                if (!IsPositionOccupied(planter, position, spacing / 2))
                {
                    positions.Add(position);
                }
            }
            return positions;
        }

        List<Vector3> GetSinglePlantPotPositions(PlanterBox planter, PlanterInfo info)
        {
            List<Vector3> positions = new List<Vector3>();

            Vector3 position = planter.transform.position + new Vector3(0, info.Height, 0);

            if (!IsPositionOccupied(planter, position, 0.25f))
            {
                positions.Add(position);
            }

            return positions;
        }

        List<Vector3> GetTrianglePlanterPositions(PlanterBox planter, PlanterInfo info)
        {
            List<Vector3> positions = new List<Vector3>();
            float radius = info.Size * 0.7f;

            float rotationOffset = Mathf.PI / 3.4f;

            float globalOffsetX = -0.2f;
            float globalOffsetZ = -0.0f;

            float triangleCheckRadius = 0.2f;

            for (int i = 0; i < 3; i++)
            {
                float angle = (Mathf.PI * 2 / 3 * i) + rotationOffset;

                float adjustedRadius = radius * 0.6f;

                Vector3 localPosition = new Vector3(
                    Mathf.Cos(angle) * adjustedRadius + globalOffsetX,
                    info.Height,
                    Mathf.Sin(angle) * adjustedRadius + globalOffsetZ
                );

                Vector3 worldPosition = planter.transform.TransformPoint(localPosition);

                if (!IsPositionOccupied(planter, worldPosition, triangleCheckRadius))
                {
                    positions.Add(worldPosition);
                }
            }

            Vector3 centerLocalPosition = new Vector3(globalOffsetX, info.Height, globalOffsetZ);
            Vector3 centerWorldPosition = planter.transform.TransformPoint(centerLocalPosition);

            if (!IsPositionOccupied(planter, centerWorldPosition, triangleCheckRadius))
            {
                positions.Add(centerWorldPosition);
            }

            return positions;
        }

        bool IsPositionOccupied(PlanterBox planter, Vector3 position, float radius)
        {
            foreach (var child in planter.children)
            {
                if (child is GrowableEntity || IsPickUpPlant(child))
                {
                    if (Vector3.Distance(child.transform.position, position) < radius * 1.5f)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private object CanBuild(Planner planner, Construction prefab, Construction.Target target)
        {
            BasePlayer player = planner.GetOwnerPlayer();
            if (player == null) return null;

            if (prefab.fullName.Contains("plants"))
            {
                PlanterBox planter = GetTargetPlanter(player);
                if (planter != null)
                {
                    Vector3 worldPosition = GetPlantPosition(player, planter);
                    if (worldPosition == Vector3.zero)
                    {
                        return null;
                    }

                    Vector3 localPosition = planter.transform.InverseTransformPoint(worldPosition);

                    bool isTrianglePlanter = planter.PrefabName == TrianglePlanterPrefab ||
                                             planter.PrefabName == RailRoadTrianglePlanterPrefab;

                    if (!isTrianglePlanter)
                    {
                        Vector3 planterScale = planter.transform.lossyScale;
                        float maxDistance = Mathf.Max(planterScale.x, planterScale.z) / 2 + 2.0f;
                        if (Mathf.Abs(localPosition.x) > maxDistance ||
                            Mathf.Abs(localPosition.z) > maxDistance)
                        {
                            return null;
                        }
                    }

                    float checkRadius = isTrianglePlanter ? 0.3f : 0.8f;
                    if (IsSpecialPlantNearby(planter, localPosition, checkRadius))
                    {
                        return false;
                    }
                }
            }

            return null;
        }

        Vector3 GetPlantPosition(BasePlayer player, PlanterBox planter)
        {
            RaycastHit hit;
            if (Physics.Raycast(player.eyes.HeadRay(), out hit, 30f, LayerMask.GetMask("Deployed", "Construction", "Terrain", "World", "Geology")))
            {
                if (hit.GetEn
// ... [Code preview truncated due to length. Full source in attached file] ...

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