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

FREE MagicTree : 3.0.5 3.0.5

Нет прав для скачивания
C#:
// Все права принадлежат дискорд сообществу https://discord.gg/VgNHPpNrz6
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Oxide.Plugins
{
    // fix путей префабов 3.0.5
    
    [Info("MagicTree", "byKomander & KirillRnD", "3.0.5")]
    public class MagicTree : RustPlugin
    {
        #region Классы данных и конфигурация

        public class Seed
        {
            public string shortname;
            public string name;
            public ulong skinId;
        }

        public class Wood
        {
            [JsonProperty("UID Дерева")] public NetworkableId woodId;
            [JsonProperty("Осталось времени")] public int NeedTime;
            [JsonProperty("Осталось времени до разрушения")] public int NeedTimeToDestroy = -1;
            [JsonProperty("Текущая стадия (0=A,1=B,2=C,3=D,4=E)")] public int CurrentStage;
            [JsonProperty("Позиция")] public Vector3 woodPos;
            [JsonProperty("Ящики (ID)")] public List<NetworkableId> BoxListed = new List<NetworkableId>();

            [JsonIgnore] public List<BaseEntity> boxes = new List<BaseEntity>();
            [JsonIgnore] public bool finalBoxSpawned = false;
            [JsonIgnore] public bool FinalNotified = false;
        }

        public class BoxItemsList
        {
            [JsonProperty("Shortname предмета")] public string ShortName;
            [JsonProperty("Мин.кол-во")] public int MinAmount;
            [JsonProperty("Макс.кол-во")] public int MaxAmount;
            [JsonProperty("Шанс что предмет будет добавлен (максимально 100%)")] public int Change;
            [JsonProperty("SkinID предмета")] public ulong SkinID;
            [JsonProperty("Имя предмета при создании (Оставьте поле пустым чтобы использовать стандартное название итема)")] public string Name;
            [JsonProperty("Это чертеж")] public bool IsBlueprnt;
        }

        public static PluginConfig config;

        public class PluginConfig
        {
            [JsonProperty("Time (сек на все 5 стадии)")]
            public int Time = 600;

            [JsonProperty("TimetoDestroy (сколько живёт после финала)")]
            public int TimetoDestroy = 1800;

            [JsonProperty("Посадка деревьев разрешена только в земле (запрещены плантации и прочее)")]
            public bool PlanterBoxDisable = false;

            [JsonProperty("Множитель добычи при финальной срубке магического дерева")]
            public int Bonus = 1;

            [JsonProperty("Количество вещей в ящике")]
            public int ItemsCount = 3;

            [JsonProperty("Кол-во ящиков на дереве")]
            public int BoxCount = 3;

            [JsonProperty("Шанс выпадения зерна с дерева (макс-100)")]
            public int Chance = 5;

            [JsonProperty("Префаб ящика")]
            public string CrateBasic = "assets/bundled/prefabs/radtown/crate_underwater_basic.prefab";

            [JsonProperty("Permission (/seed)")]
            public string Permission = "MagicTree.perm";

            [JsonProperty("Список лута (BoxItemsList)")]
            public List<BoxItemsList> casesItems = new List<BoxItemsList>();

            [JsonProperty("Эффект успеха")]
            public string SucEffect = "";

            [JsonProperty("Эффект ошибки")]
            public string ErrorEffect = "";

            [JsonProperty("Настройки семени")]
            public Seed seed = new Seed { shortname = "seed.hemp", name = "Семена магического дерева", skinId = 1787823357 };

            [JsonProperty("BiomeStages (5 префаба на биом)")]
            public Dictionary<string, List<string>> BiomeStages = new Dictionary<string, List<string>>();

            [JsonProperty("Версия конфигурации")]
            public VersionNumber PluginVersion = new VersionNumber(2, 0, 3);

            [JsonProperty("Настройки уведомлений")]
            public Notifications notifications = new Notifications();

            public class Notifications
            {
                [JsonProperty("Ошибка команды")]
                public string CmdError = "Неправильно ввели команду.";
                [JsonProperty("Семена можно сажать только в землю")]
                public string DisablePlantSeed = "Семена разрешено садить только в землю";
                [JsonProperty("Семена нельзя сажать в чужих постройках")]
                public string DisableAuthSeed = "Семена нельзя садить в зоне чужого шкафа";
                [JsonProperty("Ошибка количества")]
                public string CountError = "Неверное кол-во!";
                [JsonProperty("Нет прав")]
                public string Permission = "У вас нет прав!";
                [JsonProperty("Дерево посажено")]
                public string Wood = "<size=14><b>Вы посадили магическое дерево!</b></size>\n<size=9>Скоро оно вырастет и даст плоды!</size>";
                [JsonProperty("Дерево созрело (финальный текст)")]
                public string InfoTextFull = "Магическое дерево выросло - руби и собирай лут!";
                [JsonProperty("Информация о стадии созревания")]
                public string InfoDdraw = "<size=25><b>Магическое дерево</b></size>\n<size=17>Этап созревания дерева: {0}/5\n\nВремя до полного созревания: {1}</size>";
                [JsonProperty("Неверное количество (пример)")]
                public string WrongAmount = "Неверное количество.Пример: /seed 5";
                [JsonProperty("Игрок не найден")]
                public string PlayerNotFound = "Игрок не найден.Пример: /seed PlayerName 5";
                [JsonProperty("Использование команды seed")]
                public string SeedUsage = "Использование:\n/seed AMOUNT или /seed PLAYER AMOUNT";
                [JsonProperty("Уведомление о получении семени (всплывающее)")]
                public string SeedNotification = "<size=14>Вам выпало магическое семечко!</size>^<size=9>Посадите его, оно вырастет в дерево с лутом</size>";
            }

            public static PluginConfig DefaultConfig()
            {
                var defCfg = new PluginConfig
                {
                    Time = 600,
                    TimetoDestroy = 1800,
                    PlanterBoxDisable = false,
                    Bonus = 1,
                    ItemsCount = 3,
                    BoxCount = 3,
                    Chance = 5,
                    CrateBasic = "assets/bundled/prefabs/radtown/crate_underwater_basic.prefab",
                    Permission = "MagicTree.perm",
                    casesItems = new List<BoxItemsList>
                    {
                        new BoxItemsList { ShortName = "scrap", MinAmount = 40, MaxAmount = 80, Change = 80, SkinID = 0, Name = "", IsBlueprnt = false },
                        new BoxItemsList { ShortName = "gunpowder", MinAmount = 100, MaxAmount = 200, Change = 50, SkinID = 0, Name = "", IsBlueprnt = false },
                        new BoxItemsList { ShortName = "metal.refined", MinAmount = 10, MaxAmount = 50, Change = 30, SkinID = 0, Name = "", IsBlueprnt = false }
                    },
                    BiomeStages = new Dictionary<string, List<string>>
                    {
                        ["Arid"] = new List<string>
                        {
                            "assets/bundled/prefabs/autospawn/resource/v3_arid_forest/palm_tree_short_c.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arid_forest/palm_tree_short_a.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arid_forest/palm_tree_med_a.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arid_forest/palm_tree_tall_a.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arid_forest/palm_tree_tall_b.prefab"
                        },
                        ["Temperate1"] = new List<string>
                        {
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_a.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_b.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_c.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_d.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_dead_a.prefab"
                        },
                        ["Temperate2"] = new List<string>
                        {
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/birch_small_temp.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/birch_medium_temp.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/birch_large_temp.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/birch_xl_temp.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/birch_xl_temp.prefab"
                        },
                        ["Tundra"] = new List<string>
                        {
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_a.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_b.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_c.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_d.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_dead_a.prefab"
                        },
                        ["Arctic"] = new List<string>
                        {
                            "assets/bundled/prefabs/autospawn/resource/v3_arctic_forest/pine_a_snow.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arctic_forest/pine_b_snow.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arctic_forest/pine_c_snow.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arctic_forest/pine_d_snow.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_arctic_forest/pine_dead_snow_a.prefab"
                        },
                        ["Swamp"] = new List<string>
                        {
                            "assets/bundled/prefabs/autospawn/resource/v3_swamp_forest/birch_a.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_swamp_forest/birch_b.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_swamp_forest/birch_c.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_swamp_forest/birch_d.prefab",
                            "assets/bundled/prefabs/autospawn/resource/v3_swamp_forest/birch_dead.prefab"
                        }
                    },
                    PluginVersion = new VersionNumber(2, 0, 3)
                };
                defCfg.seed = new Seed { shortname = "seed.hemp", name = "Семена магического дерева", skinId = 1787823357 };
                return defCfg;
            }
        }

        #endregion

        #region Поля и Lifecycle

        private Dictionary<ulong, Dictionary<NetworkableId, Wood>> WoodsList = new Dictionary<ulong, Dictionary<NetworkableId, Wood>>();
        public static MagicTree ins;
        private List<TreeComponent> treeComponents = new List<TreeComponent>();
        private Dictionary<NetworkableId, bool> generatedBoxes = new Dictionary<NetworkableId, bool>();
        private Dictionary<NetworkableId, Dictionary<string, int>> intendedLoot = new Dictionary<NetworkableId, Dictionary<string, int>>();
        private Dictionary<ulong, float> lastSeedTime = new Dictionary<ulong, float>();
        private HashSet<NetworkableId> usedTrees = new HashSet<NetworkableId>();
        private const float SEED_COOLDOWN = 1f;

        protected override void LoadDefaultConfig()
        {
            PrintWarning("MagicTree: Создаём шаблонный конфиг по умолчанию...");
            config = PluginConfig.DefaultConfig();
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<PluginConfig>();
                if (config == null)
                    throw new Exception("Конфигурация вернула null");
            }
            catch (Exception ex)
            {
                PrintWarning("MagicTree: Ошибка чтения конфига: " + ex.Message);
                LoadDefaultConfig();
                SaveConfig();
                return;
            }
            // Если конфиг существует, оставляем его без обновления, чтобы не перезаписывать ваши правки.}

        protected override void SaveConfig() => Config.WriteObject(config, true);

        void Loaded()
        {
            ins = this;
            permission.RegisterPermission(config.Permission, this);
            lastSeedTime.Clear();
            usedTrees.Clear();
            PrintWarning("MagicTree Loaded()");
        }

        #endregion

        #region Load/Save Data

        void LoadData()
        {
            try
            {
                WoodsList = Interface.GetMod().DataFileSystem.ReadObject<Dictionary<ulong, Dictionary<NetworkableId, Wood>>>("MagicTree_Data");
                if (WoodsList == null)
                    WoodsList = new Dictionary<ulong, Dictionary<NetworkableId, Wood>>();
            }
            catch
            {
                WoodsList = new Dictionary<ulong, Dictionary<NetworkableId, Wood>>();
            }
        }
        void SaveData() => Interface.GetMod().DataFileSystem.WriteObject("MagicTree_Data", WoodsList);

        #endregion

        #region AddOrRemoveComponent

        void AddOrRemoveComponent(string mode, TreeComponent component, BaseEntity tree, ulong ownerId)
        {
            if (!WoodsList.ContainsKey(ownerId)) return;
            switch (mode)
            {
                case "add":
                    if (!WoodsList[ownerId].ContainsKey(tree.net.ID)) return;
                    var data = WoodsList[ownerId][tree.net.ID];
                    if (data == null) return;
                    var go = new GameObject("TreeCompObj_Restore");
                    go.transform.position = tree.transform.position;
                    var newComp = go.AddComponent<TreeComponent>();
                    newComp.Init(data, tree);
                    treeComponents.Add(newComp);
                    break;
                case "remove":
                    if (component == null || tree == null) return;
                    if (!WoodsList[ownerId].ContainsKey(tree.net.ID)) return;
                    var w = WoodsList[ownerId][tree.net.ID];
                    if (w.boxes.Count > 0)
                    {
                        foreach (var b in w.boxes)
                            b?.Kill();
                        w.boxes.Clear();
                    }
                    component.DestroyComponent();
                    if (treeComponents.Contains(component))
                        treeComponents.Remove(component);
                    break;
            }
        }

        #endregion

        #region OnEntityBuilt

        void OnEntityBuilt(Planner planner, GameObject go, Vector3 pos)
        {
            if (planner == null || go == null) return;
            var player = planner.GetOwnerPlayer();
            if (player == null) return;
            var ent = go.ToBaseEntity();
            if (ent == null) return;
            if (ent.skinID == config.seed.skinId)
            {
                NextTick(() =>
                {
                    if (ent == null || ent.IsDestroyed) return;
                    if (config.PlanterBoxDisable && ent.GetParentEntity() != null)
                    {
                        Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{config.notifications.DisablePlantSeed}\"");
                        AddSeed(player, 1, false);
                        ent.Kill();
                        return;
                    }
                    if (player.GetBuildingPrivilege()?.IsAuthed(player) == false)
                    {
                        Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{config.notifications.DisableAuthSeed}\"");
                        AddSeed(player, 1, false);
                        ent.Kill();
                        return;
                    }
                    SpawnWood(player.userID, ent.transform.position, null, ent, null);
                    Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{config.notifications.Wood}\"");
                });
            }
        }

        #endregion

        #region SpawnWood (5 стадий)

        public void SpawnWood(ulong ownerId, Vector3 pos, BaseEntity oldTree, BaseEntity seedEntity, TreeComponent oldComp)
        {
            string biome = GetDominantBiome(pos);
            string foundKey = config.BiomeStages.Keys.FirstOrDefault(k => k.Equals(biome, StringComparison.OrdinalIgnoreCase));
            if (foundKey == null)
            {
                PrintWarning($"[MagicTree] Биом '{biome}' не найден.Используем 'Temperate1'.");
                foundKey = "Temperate1";
            }

            var prefabs = config.BiomeStages[foundKey];
            if (prefabs.Count < 5)
            {
                PrintWarning($"[MagicTree] Для биома '{foundKey}' требуется 5 префабов, а есть {prefabs.Count}.");
                return;
            }

            int stageIndex = oldTree == null ? 0 : WoodsList[ownerId][oldTree.net.ID].CurrentStage + 1;
            if (stageIndex > 4) return;

            string prefabPath = prefabs[stageIndex];
            
            // Проверяем существование префаба перед созданием
            var prefab = GameManager.server.FindPrefab(prefabPath);
            if (prefab == null)
            {
                PrintWarning($"[MagicTree] Префаб не найден: {prefabPath}.Используем запасной вариант.");
                prefabPath = "assets/bundled/prefabs/autospawn/resource/v3_temp_forest/pine_a.prefab";
                prefab = GameManager.server.FindPrefab(prefabPath);
                
                if (prefab == null)
                {
                    PrintError($"[MagicTree] Не удалось создать дерево: запасной префаб тоже не найден.");
                    return;
                }
            }

            if (oldTree == null)
            {
                seedEntity?.Kill();
            }

            var newTree = GameManager.server.CreateEntity(prefabPath, pos);
            if (!newTree)
            {
                PrintError($"[MagicTree] Не удалось создать дерево из префаба: {prefabPath}");
                return;
            }

            newTree.OwnerID = ownerId;
            newTree.Spawn();

            if (oldTree == null)
            {
                if (!WoodsList.ContainsKey(ownerId))
                    WoodsList[ownerId] = new Dictionary<NetworkableId, Wood>();
                WoodsList[ownerId][newTree.net.ID] = new Wood { woodId = newTree.net.ID, CurrentStage = 0, NeedTime = config.Time / 5, woodPos = pos };
            }
            else
            {
                WoodsList[ownerId].Remove(oldTree.net.ID);
                WoodsList[ownerId][newTree.net.ID] = new Wood { woodId = newTree.net.ID, CurrentStage = stageIndex, NeedTime = config.Time / 5, woodPos = pos };
                oldTree.Kill();
            }

            var go = new GameObject($"TreeStage{stageIndex}Obj");
            go.transform.position = pos;
            var comp = go.AddComponent<TreeComponent>();
            comp.Init(WoodsList[ownerId][newTree.net.ID], newTree);
            treeComponents.Add(comp);

            if (oldComp != null && treeComponents.Contains(oldComp))
                treeComponents.Remove(oldComp);
        }

        #endregion

        #region TreeComponent

        public class TreeComponent : BaseEntity
        {
            public BaseEntity tree;
            public Wood data;
            private SphereCollider sphereCollider;
            public List<BasePlayer> PlayersTrigger = new List<BasePlayer>();

            void Awake()
            {
                sphereCollider = gameObject.GetComponent<SphereCollider>() ?? gameObject.AddComponent<SphereCollider>();
                sphereCollider.gameObject.layer = (int)Rust.Layer.Reserved1;
                sphereCollider.isTrigger = true;
                sphereCollider.radius = 4f;
            }

            public void Init(Wood w, BaseEntity entity)
            {
                if (entity == null) { Destroy(this); return; }
                tree = entity;
                data = w;
                InvokeRepeating(nameof(DrawInfo), 1f, 1f);
            }

            void OnTriggerEnter(Collider other)
            {
                var pl = other.GetComponentInParent<BasePlayer>();
                if (pl != null)
                {
                    PlayersTrigger.RemoveAll(x => x == pl);
                    PlayersTrigger.Add(pl);
                }
            }

            void OnTriggerExit(Collider other)
            {
                var pl = other.GetComponentInParent<BasePlayer>();
                if (pl != null)
                    PlayersTrigger.RemoveAll(x => x == pl);
            }

            void DrawInfo()
            {
                if (data == null || tree == null || tree.IsDestroyed) { Destroy(this); return; }
                if (data.NeedTime <= 0 && data.CurrentStage == 4 && !data.finalBoxSpawned && data.BoxListed.Count == 0)
                {
                    data.finalBoxSpawned = true;
                    // Создаём adaptiveCounts для данного дерева не передавая – каждый вызов SpawnBox будет накапливать статистику
                    Dictionary<string, int> adaptiveCounts = new Dictionary<string, int>();
                    MagicTree.ins.SpawnBox(data, config.BoxCount, tree, tree.OwnerID, 0, adaptiveCounts);
                    data.CurrentStage = 5;
                }
                if (data.NeedTime <= 0 && data.CurrentStage < 4)
                {
                    MagicTree.ins.SpawnWood(tree.OwnerID, tree.transform.position, tree, null, this);
                }
                if (data.CurrentStage >= 5 && data.BoxListed.Count > 0)
                {
                    foreach (var pl in PlayersTrigger)
                    {
                        SendNotification(pl, config.notifications.InfoTextFull, 1.1f);
                        if (!data.FinalNotified)
                        {
                            MagicTree.ins.Server.Command($"gametipsapi.showforplayer {pl.userID} 0 \"{config.notifications.InfoTextFull}\"");
                        }
                    }
                    data.FinalNotified = true;
                    data.NeedTimeToDestroy++;
                    if (data.NeedTimeToDestroy > config.TimetoDestroy)
                    {
                        if (MagicTree.ins.WoodsList.ContainsKey(tree.OwnerID) &&
                            MagicTree.ins.WoodsList[tree.OwnerID].ContainsKey(tree.net.ID))
                            MagicTree.ins.WoodsList[tree.OwnerID].Remove(tree.net.ID);
                        var hitInfo = new HitInfo(null, tree, Rust.DamageType.Generic, tree.Health(), tree.transform.position);
                        tree.OnAttacked(hitInfo);
                        Destroy(this);
                    }
                }
                data.NeedTime--;
                PlayersTrigger.RemoveAll(p => p == null || !p.IsConnected || Vector3.Distance(p.transform.position, tree.transform.position) > 4f);
                foreach (var pl in PlayersTrigger)
                {
                    if (data.CurrentStage < 5 && data.NeedTime > 0)
                    {
                        string tStr = FormatTime(data.NeedTime);
                        string msg = string.Format(config.notifications.InfoDdraw, data.CurrentStage + 1, tStr);
                        SendNotification(pl, msg, 1.1f);
                    }
                }
            }

            void SendNotification(BasePlayer player, string msg, float duration)
            {
                if (player == null) return;
                player.SendConsoleCommand("ddraw.text", duration, "1 1 1 1", tree.transform.position + Vector3.up * 2f, msg);
            }

            void OnDestroy()
            {
                if (data != null && data.boxes.Count > 0)
                {
                    foreach (var bx in data.boxes)
                    {
                        if (!bx) continue;
                        bx.SetFlag(BaseEntity.Flags.Busy, false, true);
                        bx.gameObject.AddComponent<MagicTreeHelpers.MagicTreeRBC>();
                    }
                }
            }

            public void DestroyComponent() => Destroy(this);
        }

        #endregion

        #region SpawnBox & AddMagicLoot

        // Изменённый SpawnBox – создаём adaptiveCounts для данного дерева и передаём его в каждый вызов AddMagicLoot
        public void SpawnBox(Wood data, int i, BaseEntity tree, ulong ownerID, int countBox = 0, Dictionary<string, int> adaptiveCounts = null)
        {
            if (data == null || tree == null) return;
            data.BoxListed.Clear();
            data.boxes.Clear();
            if (countBox == 0) countBox = config.BoxCount;
            if (adaptiveCounts == null)
                adaptiveCounts = new Dictionary<string, int>();

            for (int k = 0; k < countBox; k++)
            {
                Vector3 offset = new Vector3(UnityEngine.Random.Range(-5f, 5f),
                                             UnityEngine.Random.Range(5f, 9f),
                                             UnityEngine.Random.Range(-5f, 5f));
                var boxEnt = GameManager.server.CreateEntity(config.CrateBasic, tree.transform.position + offset, Quaternion.identity);
                if (!boxEnt) continue;
                var container = boxEnt.GetComponent<LootContainer>();
                if (container != null)
                {
                    container.initialLootSpawn = false;
                    container.lootDefinition = null;
                }
                boxEnt.enableSaving = false;
                boxEnt.Spawn();
                if (container != null && container.inventory != null)
                {
                    container.inventory.capacity = 12;
                    container.inventory.itemList.Clear();
                }
                // Передаём adaptiveCounts в модифицированный метод AddMagicLoot
                AddMagicLoot(boxEnt, adaptiveCounts);
                boxEnt.SetFlag(BaseEntity.Flags.Reserved8, false, true);
                boxEnt.SetFlag(BaseEntity.Flags.Busy, true, true);
                data.BoxListed.Add(boxEnt.net.ID);
                data.boxes.Add(boxEnt);
            }
        }

        // Изменённый метод AddMagicLoot с адаптивным снижением шансов и взвешенным выбором.void AddMagicLoot(BaseEntity box, Dictionary<string, int> adaptiveCounts)
        {
            if (box == null) return;
            var container = box.GetComponent<LootContainer>();
            if (container == null) return;
            Puts($"[MagicTree Debug] AddMagicLoot для ящика {box.net.ID}");
            if (generatedBoxes.ContainsKey(box.net.ID))
            {
                Puts($"[MagicTree Debug] Ящик {box.net.ID} уже генерировался, пропускаем.");
                return;
            }
            generatedBoxes[box.net.ID] = true;
            if (container.inventory.itemList != null && container.inventory.itemList.Count > 0)
                container.inventory.itemList.Clear();
            box.gameObject.AddComponent<LootFiller>();
            NetworkableId boxId = box.net.ID;
            intendedLoot[boxId] = new Dictionary<string, int>();

            Puts($"[MagicTree Loot] Генерация лута для ящика {box.net.ID}");
            // Получаем список уникальных предметов из конфига
            var allItems = config.casesItems.GroupBy(x => x.ShortName).Select(g => g.First()).ToList();
            int requiredSlots = Math.Min(config.ItemsCount, allItems.Count);
            Puts($"[MagicTree Loot] Требуется добавить {requiredSlots} уникальных предметов (из {allItems.Count} доступных).");

            // Используем взвешенный выбор с учетом адаптивного снижения шансов
            for (int slot = 0; slot < requiredSlots; slot++)
            {
                var weightedList = new List<Tuple<BoxItemsList, float>>();
                foreach (var item in allItems)
                {
                    float effectiveChance = item.Change;
                    if (adaptiveCounts.TryGetValue(item.ShortName, out int count))
                    {
                        effectiveChance = item.Change * (1 - 0.2f * count);
                    }
                    if (effectiveChance < 0) effectiveChance = 0;
                    weightedList.Add(Tuple.Create(item, effectiveChance));
                }
                float totalWeight = weightedList.Sum(x => x.Item2);
                BoxItemsList selected = null;
                if (totalWeight > 0)
                {
                    float r = UnityEngine.Random.Range(0f, totalWeight);
                    float cumulative = 0;
                    foreach (var tuple in weightedList)
                    {
                        cumulative += tuple.Item2;
                        if (r <= cumulative)
                        {
                            selected = tuple.Item1;
                            break;
                        }
                    }
                }
                // Если не удалось выбрать по весам, берем с наивысшим базовым шансом
                if (selected == null)
                {
                    selected = allItems.OrderByDescending(x => x.Change).First();
                }
                float currentEffective = selected.Change;
                if (adaptiveCounts.TryGetValue(selected.ShortName, out int cnt))
                    currentEffective = selected.Change * (1 - 0.2f * cnt);
                Puts($"[MagicTree Loot] Выбран предмет: {selected.ShortName} (Базовый Chance={selected.Change}, адаптивный effective chance={currentEffective})");

                allItems.Remove(selected);
                if (adaptiveCounts.ContainsKey(selected.ShortName))
                    adaptiveCounts[selected.ShortName]++;
                else
                    adaptiveCounts[selected.ShortName] = 1;

                int minVal = Math.Min(selected.MinAmount, selected.MaxAmount);
                int maxVal = Math.Max(selected.MinAmount, selected.MaxAmount);
                if (maxVal == 0)
                {
                    Puts($"[MagicTree Loot] {selected.ShortName}: min=0, max=0, пропускаем");
                    slot--;
                    continue;
                }
                int amount = UnityEngine.Random.Range(minVal, maxVal + 1);
                Puts($"[MagicTree Loot] Сгенерировано для {selected.ShortName}: {amount} (диапазон [{minVal}..{maxVal}])");
                intendedLoot[boxId][selected.ShortName] = amount;
                var defItem = ItemManager.FindItemDefinition(selected.ShortName);
                if (defItem == null)
                {
                    PrintError($"[MagicTree] Предмет {selected.ShortName} не найден!");
                    continue;
                }
                if (selected.IsBlueprnt)
                    CreateBlueprintItems(container.inventory, defItem, selected, amount);
                else
                    CreateStackableItems(container.inventory, defItem, selected, amount);
            }
            timer.Once(0.5f, () => AdjustLoot(box));
        }

        void AdjustLoot(BaseEntity box)
        {
            var container = box.GetComponent<LootContainer>();
            if (container == null || container.inventory == null) return;
            NetworkableId boxId = box.net.ID;
            if (!intendedLoot.ContainsKey(boxId)) return;
            var intended = intendedLoot[boxId];
            foreach (var item in container.inventory.itemList)
            {
                if (intended.ContainsKey(item.info.shortname))
                {
                    int expected = intended[item.info.shortname];
                    if (item.amount > expected)
                    {
                        Puts($"[MagicTree AdjustLoot] Корректирую {item.info.shortname}: {item.amount} -> {expected}");
                        item.amount = expected;
                    }
                }
            }
        }

        void CreateBlueprintItems(ItemContainer invContainer, ItemDefinition defItem, BoxItemsList selected, int amount)
        {
            for (int i = 0; i < amount; i++)
            {
                var bp = ItemManager.CreateByName("blueprintbase", 1);
                if (bp == null)
                {
                    PrintError($"[MagicTree] Не удалось создать blueprintbase для {selected.ShortName}!");
                    continue;
                }
                bp.blueprintTarget = defItem.itemid;
                if (!string.IsNullOrEmpty(selected.Name))
                    bp.name = selected.Name;
                bool moved = bp.MoveToContainer(invContainer);
                Puts($"[MagicTree Debug] Blueprint {selected.ShortName} (x1) moved: {moved}");
            }
        }

        void CreateStackableItems(ItemContainer invContainer, ItemDefinition defItem, BoxItemsList selected, int amount)
        {
            int maxStack = defItem.stackable;
            if (maxStack < 1) maxStack = 1;
            int left = amount;
            while (left > 0)
            {
                int spawnAmount = Math.Min(left, maxStack);
                var newItem = ItemManager.CreateByName(selected.ShortName, spawnAmount, selected.SkinID);
                if (newItem == null)
                {
                    PrintError($"[MagicTree] Предмет {selected.ShortName} не найден или не удалось создать!");
                    break;
                }
                if (!string.IsNullOrEmpty(selected.Name))
                    newItem.name = selected.Name;
                bool moved = newItem.MoveToContainer(invContainer);
                Puts($"[MagicTree Debug] Stackable {selected.ShortName} (x{spawnAmount}) moved: {moved}");
                left -= spawnAmount;
            }
        }

        public class LootFiller : MonoBehaviour { }

        #endregion

        #region /seed Command

        [ChatCommand("seed")]
        void CmdSeed(BasePlayer player, string command, string[] args)
        {
            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString, config.Permission))
            {
                Effect.server.Run(config.ErrorEffect, player, 0, Vector3.zero, Vector3.forward);
                return;
            }
            if (args.Length == 1)
            {
                if (!int.TryParse(args[0], out int amount))
                {
                    Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{config.notifications.WrongAmount}\"");
                    return;
                }
                AddSeed(player, amount, false);
                return;
            }
            if (args.Length == 2)
            {
                var target = BasePlayer.Find(args[0]);
                if (!target)
                {
                    Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{config.notifications.PlayerNotFound}\"");
                    return;
                }
                if (!int.TryParse(args[1], out int amt2))
                {
                    Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{config.notifications.WrongAmount}\"");
                    return;
                }
                AddSeed(target, amt2);
                return;
            }
            Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{config.notifications.SeedUsage}\"");
        }

        void AddSeed(BasePlayer pl, int amount, bool showMsg = true)
        {
            if (pl == null) return;

            var item = ItemManager.CreateByName(config.seed.shortname, amount, config.seed.skinId);
            if (item == null)
            {
                PrintError($"[MagicTree Error] Failed to create seed item: {config.seed.shortname}");
                return;
            }

            item.name = config.seed.name;

            // Пробуем добавить предмет в основной инвентарь
            if (item.MoveToContainer(pl.inventory.containerMain))
            {
                Puts($"[MagicTree Debug] Successfully gave seed to {pl.displayName}");
                if (showMsg)
                {
                    // Используем встроенное уведомление вместо GameTipsAPI
                    pl.ChatMessage(config.notifications.SeedNotification.Replace("^", "\n"));
                    
                    if (!string.IsNullOrEmpty(config.SucEffect))
                    {
                        Effect.server.Run(config.SucEffect, pl, 0, Vector3.zero, Vector3.forward);
                    }
                }
            }
            else if (item.MoveToContainer(pl.inventory.containerBelt)) // Пробуем пояс
            {
                Puts($"[MagicTree Debug] Gave seed to belt for {pl.displayName}");
                if (showMsg)
                {
                    pl.ChatMessage(config.notifications.SeedNotification.Replace("^", "\n"));
                }
            }
            else // Если инвентарь полон, дропаем на землю
            {
                Puts($"[MagicTree Debug] Inventory full, dropping seed on ground for {pl.displayName}");
                item.Drop(pl.transform.position + new Vector3(0, 1, 0), Vector3.zero);
                if (showMsg)
                {
                    pl.ChatMessage("Ваш инвентарь полон! Семя упало на землю.");
                }
            }
        }

        #endregion

        #region Helpers

        public static string FormatTime(int sec)
        {
            if (sec < 0) sec = 0;
            var t = TimeSpan.FromSeconds(sec);
            return $"{t.Hours:00}:{t.Minutes:00}:{t.Seconds:00}";
        }

        public void ShowGameTip(BasePlayer player, string msg)
        {
            if (player == null) return;
            Server.Command($"gametipsapi.showforplayer {player.userID} 0 \"{msg}\"");
        }

        #endregion

        #region Биом

        private string GetDominantBiome(Vector3 pos)
        {
            if ((TerrainMeta.TopologyMap.GetTopology(pos) & (int)TerrainTopology.Enum.Swamp) != 0)
                return "Swamp";
            float arid = TerrainMeta.BiomeMap.GetBiome(pos, 1);
            float temperate = TerrainMeta.BiomeMap.GetBiome(pos, 2);
            float tundra = TerrainMeta.BiomeMap.GetBiome(pos, 4);
            float arctic = TerrainMeta.BiomeMap.GetBiome(pos, 8);
            string biome = "Arid";
            float maxVal = arid;
            if (temperate > maxVal) { biome = "Temperate1"; maxVal = temperate; }
            if (tundra > maxVal) { biome = "Tundra"; maxVal = tundra; }
            if (arctic > maxVal) biome = "Arctic";
            if (config.BiomeStages.ContainsKey("Temperate2") && biome == "Arid")
                biome = "Temperate2";
            return biome;
        }

        #endregion

        #region Gathering & Damage

        void OnEntityTakeDamage(BaseEntity entity, HitInfo info)
        {
            if (entity == null || info == null) return;
            
            var player = info.InitiatorPlayer;
            if (player == null) return;

            // Проверяем, является ли сущность деревом
            if (!entity.PrefabName.Contains("tree")) return;

            // Проверяем, не было ли уже семени с этого дерева
            if (usedTrees.Contains(entity.net.ID))
            {
                return;
            }

            // Проверяем кулдаун игрока
            if (lastSeedTime.ContainsKey(player.userID))
            {
                if (Time.time - lastSeedTime[player.userID] < SEED_COOLDOWN) return;
            }

            // Проверяем, не является ли это магическим деревом
            foreach (var ownerList in WoodsList.Values)
            {
                if (ownerList.ContainsKey(entity.net.ID)) return;
            }

            // Проверяем шанс выпадения семени
            if (UnityEngine.Random.Range(0, 100) < config.Chance)
            {
                lastSeedTime[player.userID] = Time.time;
                usedTrees.Add(entity.net.ID); // Отмечаем дерево как использованное
                AddSeed(player, 1, true);
                Puts($"[MagicTree Debug] Seed dropped from tree {entity.net.ID} to player {player.displayName}");
            }
        }

        // Добавляем очистку использованных деревьев при их уничтожении
        void OnEntityDeath(BaseEntity entity, HitInfo info)
        {
            if (entity == null) return;
            
            if (usedTrees.Contains(entity.net.ID))
            {
                usedTrees.Remove(entity.net.ID);
                Puts($"[MagicTree Debug] Removed dead tree {entity.net.ID} from used trees list");
            }
        }

        // Периодическая очистка списка использованных деревьев (на случай, если дерево было уничтожено без события OnEntityDeath)
        void OnServerSave()
        {
            HashSet<NetworkableId> treesToRemove = new HashSet<NetworkableId>();
            
            foreach (var treeId in usedTrees)
            {
                var entity = BaseNetworkable.serverEntities.Find(treeId) as BaseEntity;
                if (entity == null || entity.IsDestroyed)
                {
                    treesToRemove.Add(treeId);
                }
            }

            foreach (var treeId in treesToRemove)
            {
                usedTrees.Remove(treeId);
                Puts($"[MagicTree Debug] Cleaned up destroyed tree {treeId} from used trees list");
            }
        }

        void OnGather(ResourceDispenser dispenser, BaseEntity entity, Item item)
        {
            if (dispenser == null || entity == null || item == null) return;
            
            var player = entity.ToPlayer();
            if (player == null) return;

            var tree = dispenser.GetComponent<BaseEntity>();
            if (tree == null) return;

            // Проверяем, является ли это магическим деревом
            bool isMagicTree = false;
            foreach (var ownerList in WoodsList.Values)
            {
                if (ownerList.ContainsKey(tree.net.ID))
                {
                    isMagicTree = true;
                    var wood = ownerList[tree.net.ID];
                    if (wood.CurrentStage >= 4)
                    {
                        // Для магического дерева только увеличиваем количество ресурсов
                        int originalAmount = item.amount;
                        item.amount *= config.Bonus;
                        Puts($"[MagicTree Debug] Magic tree resource bonus: {originalAmount} -> {item.amount}");
                    }
                    break;
                }
            }

            // Если это обычное дерево, проверяем шанс выпадения семени
            if (!isMagicTree && item.info.shortname.Contains("wood"))
            {
                if (UnityEngine.Random.Range(0, 100) < config.Chance)
                {
                    Puts($"[MagicTree Debug] Seed drop from normal tree gathering for {player.displayName}");
                    AddSeed(player, 1, true);
                }
            }
        }

        // Добавляем метод для очистки словаря кулдаунов
        void OnPlayerDisconnected(BasePlayer player)
        {
            if (player == null) return;
            lastSeedTime.Remove(player.userID);
        }

        #endregion
    }
}

namespace Oxide.Plugins.MagicTreeHelpers
{
    using UnityEngine;
    public class MagicTreeRBC : MonoBehaviour
    {
        BaseEntity check;
        void Awake()
        {
            check = GetComponent<BaseEntity>();
            InvokeRepeating(nameof(Untie), 0f, 2f);
        }
        void Untie()
        {
            if (check == null || check.IsDestroyed)
            {
                Destroy(this);
                return;
            }
            check.SetFlag(BaseEntity.Flags.Reserved8, false, false);
            var body = check.GetComponent<Rigidbody>();
            if (body != null)
            {
                body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
                if (!IsOnGround(check.transform.position) && body.detectCollisions)
                    body.isKinematic = true;
                else
                {
                    Destroy(this);
                    return;
                }
                body.isKinematic = false;
                body.mass = 10f;
            }
        }
        bool IsOnGround(Vector3 pos)
        {
            float y = TerrainMeta.HeightMap.GetHeight(pos);
            return (pos.y - y) < 0.1f;
        }
    }
}

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