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

FREE TameableWilds : 2.2.0 2.2.0

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

namespace Oxide.Plugins
{
    [Info("TameableWilds", "ne8ula", "2.2.0")]
    [Description("Tame wild animals with blowpipe darts! Pet, feed, inventory, lead, ride & call. v2.0 major overhaul.")]
    class TameableWilds : RustPlugin
    {
        #region Fields

        private static TameableWilds _plugin;
        private static Configuration _config;
        private StoredData _storedData;

        private static readonly Dictionary<ulong, float> _holdTime = new Dictionary<ulong, float>();
        private static readonly HashSet<PantherComponent> _panthers = new HashSet<PantherComponent>();
        private static readonly Dictionary<ulong, float> _weaponMsgCooldown = new Dictionary<ulong, float>();

        private const string PREFAB_WOODBOX = "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab";

        #endregion

        #region Configuration

        private class Configuration
        {
            [JsonProperty("Version")]
            public string Version { get; set; }

            [JsonProperty("Tame Duration Seconds")]
            public float TameDurationSeconds { get; set; } = 3f;

            [JsonProperty("Meat Items Required In Inventory To Tame (0 = no meat needed)")]
            public int MeatRequired { get; set; } = 0;

            [JsonProperty("Meat Item Shortnames Accepted (any of these)")]
            public List<string> AcceptedMeat { get; set; } = new List<string>
            {
                "raw.bear.meat",
                "raw.meat",
                "cooked.bear.meat",
                "cooked.meat"
            };

            [JsonProperty("Ride Speed")]
            public float RideSpeed { get; set; } = 8f;

            [JsonProperty("Idle Follow Distance (meters behind player when idle)")]
            public float IdleFollowDistance { get; set; } = 5f;

            [JsonProperty("Ride Turn Speed")]
            public float TurnSpeed { get; set; } = 5f;

            [JsonProperty("Chat Command To Release Animal")]
            public string ReleaseCommand { get; set; } = "prelease";

            [JsonProperty("Chat Command To Call Animal")]
            public string CallCommand { get; set; } = "pcall";

            [JsonProperty("Require Permission To Tame")]
            public bool RequirePermission { get; set; } = false;

            [JsonProperty("Taming Radius (meters)")]
            public float TamingRadius { get; set; } = 5f;

            [JsonProperty("Tranquilize Duration Seconds (how long animal stays down)")]
            public float TranquilizeDurationSeconds { get; set; } = 60f;

            [JsonProperty("Require Incapacitate Dart To Tranquilize")]
            public bool RequireDartToTame { get; set; } = true;

            [JsonProperty("Debug Mode")]
            public bool DebugMode { get; set; } = false;

            [JsonProperty("Memory Watchdog Enabled (cleans up before crash)")]
            public bool MemoryWatchdog { get; set; } = true;

            [JsonProperty("Memory Warning Threshold MB (start monitoring)")]
            public int MemoryWarningThresholdMB { get; set; } = 20480;

            [JsonProperty("Memory Critical Threshold MB (aggressive cleanup)")]
            public int MemoryCriticalThresholdMB { get; set; } = 24576;
        }

        private static readonly string[] AnimalShortNames = { "panther", "tiger" };
        private static readonly Dictionary<string, string> AnimalPrefabs = new Dictionary<string, string>
        {
            { "panther", "assets/rust.ai/agents/panther/panther.prefab" },
            { "tiger", "assets/rust.ai/agents/tiger/tiger.prefab" }
        };
        private static readonly Dictionary<string, string> AnimalComponentNames = new Dictionary<string, string>
        {
            { "panther", "Panther" },
            { "tiger", "Tiger" }
        };

        private static bool IsTargetAnimal(BaseEntity entity)
        {
            if (entity == null) return false;
            if (entity.ShortPrefabName.Contains("corpse")) return false;
            foreach (var name in AnimalShortNames)
                if (entity.ShortPrefabName.Contains(name)) return true;
            return false;
        }

        private static bool IsTargetCorpse(BaseEntity entity)
        {
            if (entity == null) return false;
            if (!entity.ShortPrefabName.Contains("corpse")) return false;
            foreach (var name in AnimalShortNames)
                if (entity.ShortPrefabName.Contains(name)) return true;
            return false;
        }

        private static string GetAnimalShortName(BaseEntity entity)
        {
            if (entity == null) return null;
            foreach (var name in AnimalShortNames)
                if (entity.ShortPrefabName.Contains(name)) return name;
            return null;
        }

        private static string GetAnimalPrefab(BaseEntity entity)
        {
            var shortName = GetAnimalShortName(entity);
            if (shortName != null && AnimalPrefabs.TryGetValue(shortName, out var prefab))
                return prefab;
            return AnimalPrefabs["panther"];
        }

        private static string GetAnimalComponentName(BaseEntity entity)
        {
            var shortName = GetAnimalShortName(entity);
            if (shortName != null && AnimalComponentNames.TryGetValue(shortName, out var compName))
                return compName;
            return "Panther";
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            _config = Config.ReadObject<Configuration>();
            if (string.Compare(_config.Version, Version.ToString()) < 0)
                UpdateConfig();
            SaveConfig();
        }

        protected override void LoadDefaultConfig() => _config = GetDefaultConfig();
        protected override void SaveConfig() => Config.WriteObject(_config, true);

        private void UpdateConfig()
        {
            PrintWarning("Config update detected, updating to version " + Version);
            _config.Version = Version.ToString();
        }

        private Configuration GetDefaultConfig()
        {
            return new Configuration
            {
                Version = Version.ToString(),
                TameDurationSeconds = 3f,
                MeatRequired = 0,
                AcceptedMeat = new List<string> { "raw.bear.meat", "raw.meat", "cooked.bear.meat", "cooked.meat" },
                RideSpeed = 8f,
                IdleFollowDistance = 5f,
                TurnSpeed = 5f,
                ReleaseCommand = "prelease",
                CallCommand = "pcall",
                RequirePermission = false,
                TamingRadius = 5f,
                TranquilizeDurationSeconds = 60f,
                RequireDartToTame = true,
                DebugMode = true
            };
        }

        #endregion

        #region Stored Data

        private class StoredData
        {
            [JsonProperty("Tamed Animals")]
            public Dictionary<ulong, PantherData> TamedPanthers { get; set; } = new Dictionary<ulong, PantherData>();
        }

        private sealed class PantherData
        {
            [JsonProperty("Owner Id")]
            public ulong OwnerId { get; set; }

            [JsonProperty("Tamed At")]
            public DateTime TamedAt { get; set; }

            [JsonProperty("Prefab")]
            public string Prefab { get; set; }

            [JsonProperty("Save Pos X")]
            public float SavePosX { get; set; }

            [JsonProperty("Save Pos Y")]
            public float SavePosY { get; set; }

            [JsonProperty("Save Pos Z")]
            public float SavePosZ { get; set; }
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject($"{Name}/tamed_animals", _storedData);

        #endregion

        #region Debug

        private void Debug(string msg)
        {
            if (_config != null && _config.DebugMode)
                Puts($"[DEBUG] {msg}");
        }

        #endregion

        #region Oxide Hooks

        private void Init()
        {
            _plugin = this;
            _storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>($"{Name}/tamed_animals");
            if (_storedData == null)
                _storedData = new StoredData();

            // Force disable debug to prevent RAM spam from stale config
            _config.DebugMode = false;

            // Always register permission so admins can assign it to groups anytime
            if (!permission.PermissionExists("tameablewilds.tame"))
                permission.RegisterPermission("tameablewilds.tame", this);
        }

        private void OnServerInitialized()
        {
            cmd.AddChatCommand(_config.ReleaseCommand, this, nameof(CmdRelease));
            cmd.AddChatCommand(_config.CallCommand, this, nameof(CmdCallPanther));
            cmd.AddChatCommand("tame", this, nameof(CmdTameMenu));
            cmd.AddConsoleCommand("rp.menu", this, nameof(CmdMenuAction));
            cmd.AddConsoleCommand("rp.tame", this, nameof(CmdTameAction));
            cmd.AddConsoleCommand("rp.closemenu", this, nameof(CmdCloseMenu));
            RehydratePanthers();

            if (_config.MemoryWatchdog)
                timer.Repeat(60f, 0, MemoryWatchdogTick);
        }

        private void MemoryWatchdogTick()
        {
            try
            {
                long usedMB = System.Diagnostics.Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024);

                if (usedMB >= _config.MemoryCriticalThresholdMB)
                {
                    Puts($"[MEMORY CRITICAL] {usedMB}MB used - aggressive cleanup!");
                    System.GC.Collect(System.GC.MaxGeneration, System.GCCollectionMode.Forced, true, true);
                    System.GC.WaitForPendingFinalizers();

                    int killed = 0;
                    foreach (var net in BaseNetworkable.serverEntities)
                    {
                        if (!(net is BaseEntity entity)) continue;
                        if (entity.IsDestroyed) continue;
                        if (!IsTargetAnimal(entity)) continue;
                        var comp = entity.GetComponent<PantherComponent>();
                        if (comp != null && comp.IsTamed) continue;
                        entity.Kill();
                        killed++;
                    }
                    Puts($"[MEMORY CRITICAL] Killed {killed} wild animals, GC collected");
                }
                else if (usedMB >= _config.MemoryWarningThresholdMB)
                {
                    Puts($"[MEMORY WARNING] {usedMB}MB used - monitoring");
                }
            }
            catch (Exception ex)
            {
                Debug($"MemoryWatchdog error: {ex.Message}");
            }
        }

        private void Unload()
        {
            SaveData();

            // Clear stored data FIRST so OnEntityKill doesn't block kills
            _storedData.TamedPanthers.Clear();

            // Kill all tamed animals — they will respawn from saved data on next load
            // via RehydratePanthers (prefab + position stored in data file)
            foreach (PantherComponent panther in _panthers.ToArray())
            {
                if (panther == null) continue;
                var entity = panther._panther;

                // Drop storage contents at animal's position before destroying
                if (panther._storage != null && !panther._storage.IsDestroyed)
                {
                    var storageInv = panther._storage.inventory;
                    if (storageInv != null && storageInv.itemList.Count > 0)
                    {
                        var dropPos = entity != null ? entity.transform.position + Vector3.up * 1f : Vector3.zero;
                        foreach (var item in storageInv.itemList.ToList())
                        {
                            try
                            {
                                item.RemoveFromContainer();
                                var dropped = ItemManager.Create(item.info, item.amount, item.skin);
                                if (dropped != null)
                                {
                                    var worldEnt = dropped.CreateWorldObject(dropPos, Quaternion.identity);
                                    if (worldEnt != null)
                                        worldEnt.GetComponent<Rigidbody>()?.WakeUp();
                                }
                                item.Remove(0f);
                            }
                            catch { }
                        }
                    }
                }

                panther.DestroySelf();

                if (entity != null && !entity.IsDestroyed)
                {
                    var combat = entity.GetComponent<BaseCombatEntity>();
                    if (combat != null)
                    {
                        combat._maxHealth = 1000f;
                        combat.health = 1000f;
                    }
                    try { entity.Kill(); } catch { }
                }
            }
            _panthers.Clear();
            _holdTime.Clear();

            _config = null;
            _plugin = null;
        }

        private void OnEntitySpawned(BaseEntity entity)
        {
            if (entity == null) return;
            if (!IsTargetAnimal(entity)) return;

            bool isAlreadyTamed = entity.net != null && _storedData.TamedPanthers.ContainsKey((ulong)entity.net.ID.Value);
            if (!isAlreadyTamed) return; // Wild animals: no component, no overhead

            // Tamed animal rebuild: boost health and install component
            var combat = entity.GetComponent<BaseCombatEntity>();
            if (combat != null)
            {
                combat._maxHealth = 99999f;
                combat.health = 99999f;
            }

            // Immediately disable FSM and null its state to stop attacks/BudgetedUpdate
            var spawnFlags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic;
            foreach (var c in entity.GetComponents<Component>())
            {
                if (c == null) continue;
                var cn = c.GetType().Name;
                if (cn.Contains("FSM") || cn.Contains("Fsm"))
                {
                    try
                    {
                        if (c is MonoBehaviour mb) mb.enabled = false;
                        var ft = c.GetType();
                        var csf = ft.GetField("<CurrentState>k__BackingField", spawnFlags);
                        if (csf == null) csf = ft.GetField("CurrentState", spawnFlags);
                        if (csf != null) csf.SetValue(c, null);
                        var af = ft.GetField("_agent", spawnFlags);
                        if (af != null) af.SetValue(c, null);
                    }
                    catch { }
                }
            }

            NextTick(() =>
            {
                if (entity == null || entity.IsDestroyed) return;
                if (entity.gameObject.GetComponent<PantherComponent>() != null) return;

                PantherComponent.Install(entity, true);
            });
        }

        private object OnEntityKill(BaseNetworkable entity)
        {
            if (entity == null) return null;
            if (!IsTargetAnimal(entity as BaseEntity)) return null;

            ulong entId = entity.net != null ? (ulong)entity.net.ID.Value : 0;
            if (entId == 0) return null;

            var comp = (entity as BaseEntity)?.GetComponent<PantherComponent>();
            bool isTamed = _storedData.TamedPanthers.ContainsKey(entId);
            bool isTranq = comp != null && comp.IsTranquilized;

            // If component was already destroyed (during rebuild), allow kill
            if (comp == null && isTamed)
            {
                // Remove from stored data since we're rebuilding
                _storedData.TamedPanthers.Remove(entId);
                SaveData();
                return null;
            }

            if (isTamed || isTranq)
            {
                if (comp != null)
                    comp.WasKilled = true;

                var combat = entity as BaseCombatEntity;
                if (combat != null)
                {
                    combat.health = combat._maxHealth;
                    combat.SendNetworkUpdate();
                }

                var ent = entity as BaseEntity;
                if (ent != null)
                {
                    // Snap to terrain if underground
                    var pos = ent.transform.position;
                    float terrainH = TerrainMeta.HeightMap.GetHeight(pos);
                    if (pos.y < terrainH)
                    {
                        ent.transform.position = new Vector3(pos.x, terrainH + 0.1f, pos.z);
                    }

                    // Clear Disabled flag (set by Die() to hide entity)
                    ent.flags &= ~BaseEntity.Flags.Disabled;
                    ent.SendNetworkUpdate();

                    // Re-enable Model component
                    var model = ent.GetComponent<Model>();
                    if (model != null)
                    {
                        model.enabled = true;
                    }

                    // Force all renderers visible
                    var renderers = ent.GetComponentsInChildren<Renderer>();
                    foreach (var r in renderers)
                    {
                        if (r != null)
                            r.enabled = true;
                    }

                    // Force immediate full network update
                    ent.SendNetworkUpdateImmediate();
                }

                // Find and destroy any corpse that spawned from this animal
                var corpseBuffer = new List<BaseEntity>();
                Vis.Entities(ent.transform.position, 10f, corpseBuffer, Layers.Mask.Deployed | Layers.Mask.Default, QueryTriggerInteraction.Ignore);
                foreach (var nearby in corpseBuffer)
                {
                    if (nearby != null && IsTargetCorpse(nearby))
                    {
                        nearby.Kill();
                        Debug($"OnEntityKill: destroyed corpse near {GetAnimalShortName(nearby)}");
                    }
                }

                return true;
            }

            return null;
        }

        private void OnServerSave()
        {
            // Update saved positions for all tamed animals before saving
            foreach (var kvp in _storedData.TamedPanthers)
            {
                var pantherId = new NetworkableId(kvp.Key);
                BaseEntity animal = BaseNetworkable.serverEntities.Find(pantherId) as BaseEntity;
                if (animal != null && !animal.IsDestroyed)
                {
                    var pos = animal.transform.position;
                    kvp.Value.SavePosX = pos.x;
                    kvp.Value.SavePosY = pos.y;
                    kvp.Value.SavePosZ = pos.z;
                }
            }
            SaveData();
        }

        private object OnEntityDeath(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (entity == null) return null;
            if (!IsTargetAnimal(entity as BaseEntity)) return null;

            var comp = entity.GetComponent<PantherComponent>();
            _plugin?.Debug($"OnEntityDeath fired for {entity.ShortPrefabName}, comp={(comp != null ? "yes" : "no")}, tamed={(comp != null && comp.IsTamed)}, tranquilized={(comp != null && comp.IsTranquilized)}");

            if (comp != null && (comp.IsTamed || comp.IsTranquilized))
            {
                _plugin.Debug($"OnEntityDeath blocked for tamed/tranquilized animal!");
                entity.health = entity._maxHealth;
                entity.SendNetworkUpdate();

                // Destroy any corpse that may have spawned
                NextTick(() =>
                {
                    var corpseBuffer = new List<BaseEntity>();
                    Vis.Entities(entity.transform.position, 5f, corpseBuffer, Layers.Mask.Deployed | Layers.Mask.Default, QueryTriggerInteraction.Ignore);
                    foreach (var nearby in corpseBuffer)
                    {
                        if (nearby != null && IsTargetCorpse(nearby))
                        {
                            nearby.Kill();
                            _plugin.Debug($"OnEntityDeath: destroyed corpse near {GetAnimalShortName(nearby)}");
                        }
                    }
                });

                return true;
            }

            return null;
        }

        private object OnEntityTakeDamage(BaseCombatEntity entity, HitInfo hitInfo)
        {
            if (entity == null || hitInfo == null) return null;

            if (IsTargetAnimal(entity))
            {
                var comp = entity.GetComponent<PantherComponent>();

                var weapon = hitInfo.WeaponPrefab as BaseEntity;
                var weaponName = weapon != null ? weapon.ShortPrefabName : "";
                var hitWeapon = hitInfo.Weapon != null ? hitInfo.Weapon.ShortPrefabName : "";
                var attacker = hitInfo.InitiatorPlayer;

                // Broadened dart detection: check weapon, weapon prefab, and projectile names
                bool isDartHit = weaponName.Contains("dart") || weaponName.Contains("incapacitate") || weaponName.Contains("blow") || weaponName.Contains("pipe") ||
                    hitWeapon.Contains("dart") || hitWeapon.Contains("incapacitate") || hitWeapon.Contains("blow") || hitWeapon.Contains("pipe") ||
                    weaponName.Contains("syringe") || hitWeapon.Contains("syringe") ||
                    weaponName.Contains("tranq") || hitWeapon.Contains("tranq");

                if (comp == null)
                {
                    if (isDartHit)
                    {
                        if (attacker != null)
                        {
                            comp = PantherComponent.Install(entity as BaseEntity, false);
                            comp.Tranquilize(attacker);
                        }
                    }
                    else if (attacker != null)
                    {
                        float now = Time.realtimeSinceStartup;
                        if (!_weaponMsgCooldown.TryGetValue(attacker.userID, out float lastMsg) || now - lastMsg >= 5f)
                        {
                            _weaponMsgCooldown[attacker.userID] = now;
                            SendReply(attacker, "Shoot it with a blowpipe dart or pipe pistol to tranquilize it, then hold R to tame.");
                        }
                    }
                }
                else if (!comp.IsTamed && !comp.IsTranquilized)
                {
                    if (isDartHit)
                    {
                        if (attacker != null)
                        {
                            comp.Tranquilize(attacker);
                        }
                    }
                }

                hitInfo.damageTypes.ScaleAll(0f);
                return true;
            }

            if (!(entity is BasePlayer player)) return null;

            var attackerEnt = hitInfo.Initiator as BaseEntity;
            if (attackerEnt == null) return null;
            if (IsTargetAnimal(attackerEnt))
            {
                var attackerComp = attackerEnt.GetComponent<PantherComponent>();
                if (attackerComp != null && attackerComp.IsTamed)
                {
                    hitInfo.damageTypes = new DamageTypeList();
                    hitInfo.DoHitEffects = false;
                    hitInfo.DoDecals = false;
                    return true;
                }
                hitInfo.damageTypes.ScaleAll(0f);
                return true;
            }

            return null;
        }

        #endregion

        #region Commands

        private void CmdMenuAction(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            if (arg.Args == null || arg.Args.Length < 2)
            {
                Debug($"CmdMenuAction: missing args, len={arg.Args?.Length ?? 0}");
                return;
            }

            int action;
            uint pantherNetId;
            if (!int.TryParse(arg.Args[0].ToString(), out action)) return;
            if (!uint.TryParse(arg.Args[1].ToString(), out pantherNetId)) return;

            Debug($"CmdMenuAction: action={action}, pantherNetId={pantherNetId}, player={player.displayName}");

            AnimalMenu.Hide(player);

            // Reset menu state on ALL components - not just nearby ones.
            // If any component keeps _menuShown = true, it will block the menu from re-showing.
            foreach (var c in _panthers)
            {
                if (c == null || c._panther == null || c._panther.IsDestroyed) continue;
                c._menuShown = false;
                c._menuCloseTime = Time.realtimeSinceStartup + 5f;
                c._eHoldTime = 0f;
            }

            // Find the component for this animal
            var pantherId = new NetworkableId(pantherNetId);
            BaseEntity pantherEnt = BaseNetworkable.serverEntities.Find(pantherId) as BaseEntity;
            var comp = pantherEnt?.GetComponent<PantherComponent>();

            if (action < 0)
                return;

            if (comp == null) return;

            switch (action)
            {
                case 0:
                    comp.Pet(player);
                    break;
                case 1:
                    comp.OpenInventory(player);
                    break;
                case 2:
                    comp.ToggleLead(player);
                    break;
            }
        }

        private void CmdCloseMenu(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();

            // Destroy ALL possible stuck UI for the player (or all players if from RCON)
            var targets = player != null ? new List<BasePlayer> { player } : BasePlayer.activePlayerList.ToList();
            foreach (var p in targets)
            {
                if (p == null || !p.IsConnected) continue;
                CuiHelper.DestroyUi(p, "rp-menu-root");
                CuiHelper.DestroyUi(p, "rp-tame-menu-root");
                CuiHelper.DestroyUi(p, "rp-menu-title");
                CuiHelper.DestroyUi(p, "rp-menu-close");
                CuiHelper.DestroyUi(p, "rp-menu-feed");
                CuiHelper.DestroyUi(p, "rp-menu-inv");
                CuiHelper.DestroyUi(p, "rp-menu-lead");
            }

            // Reset ALL component menu states
            foreach (var c in _panthers)
            {
                if (c == null || c._panther == null || c._panther.IsDestroyed) continue;
                c._menuShown = false;
                c._menuCloseTime = Time.realtimeSinceStartup + 5f;
                c._eHoldTime = 0f;
            }

            if (player != null)
                SendReply(player, "Animal menu force-closed.");
            else
                Puts("Animal menu force-closed for all players.");
        }

        private void CmdTameMenu(BasePlayer player, string command, string[] args)
        {
            TameMenu.Show(player);
        }

        private void CmdTameAction(ConsoleSystem.Arg arg)
        {
            var player = arg.Player();
            if (player == null) return;

            if (arg.Args == null || arg.Args.Length < 1)
                return;

            int action;
            if (!int.TryParse(arg.Args[0].ToString(), out action)) return;

            TameMenu.Hide(player);

            if (action < 0)
                return;

            if (action == 0)
            {
                GiveItemToPlayer(player, "blowpipe", 1);
                GiveItemToPlayer(player, "dart.incapacitate", 3);
                SendReply(player, "You received a blowpipe with 3 incapacitate darts!");
            }
            else if (action >= 10)
            {
                // Release animal: action 10 = slot 0, action 11 = slot 1
                int slot = action - 10;
                var animals = TameMenu.GetPlayerAnimals(player);
                if (slot < animals.Count)
                {
                    var animalInfo = animals[slot];
                    var animalId = new NetworkableId(animalInfo.NetId);
                    BaseEntity animal = BaseNetworkable.serverEntities.Find(animalId) as BaseEntity;
                    if (animal != null && IsTargetAnimal(animal))
                    {
                        ReleaseAnimal(player, animal);
                        SendReply(player, $"Released your {animalInfo.Type}.");
                    }
                    else
                    {
                        // Animal not in world - just remove from data
                        _storedData.TamedPanthers.Remove(animalInfo.NetId);
                        SaveData();
                        SendReply(player, $"Released your {animalInfo.Type} (data cleared).");
                    }
                }
            }

            NextTick(() =>
            {
                if (player != null && player.IsConnected)
                    TameMenu.Show(player);
            });
        }

        private void GiveItemToPlayer(BasePlayer player, string shortname, int amount)
        {
            var itemDef = ItemManager.FindItemDefinition(shortname);
            if (itemDef == null)
            {
                Debug($"GiveItemToPlayer: item '{shortname}' not found!");
                return;
            }
            var item = ItemManager.Create(itemDef, amount);
            if (item != null)
                player.GiveItem(item);
        }

        private void CmdRelease(BasePlayer player, string command, string[] args)
        {
            BaseEntity target = null;

            // First check if player is riding an animal
            var mounted = player.GetMounted();
            if (mounted != null)
            {
                target = mounted.GetParentEntity();
                if (target != null && IsTargetAnimal(target))
                {
                    ReleaseAnimal(player, target);
                    return;
                }
            }

            // Raycast to find animal player is looking at
            var eyes = player.eyes;
            if (eyes != null)
            {
                var ray = new Ray(eyes.position, eyes.BodyForward());
                if (Physics.Raycast(ray, out var hit, 10f, ~0, QueryTriggerInteraction.Ignore))
                {
                    target = hit.GetEntity() as BaseEntity;
                }
            }

            if (target == null || !IsTargetAnimal(target))
            {
                SendReply(player, "Look at or ride an animal to release it.");
                return;
            }

            ReleaseAnimal(player, target);
        }

        private void ReleaseAnimal(BasePlayer player, BaseEntity target)
        {
            ulong pantherId = target.net != null ? (ulong)target.net.ID.Value : 0;
            if (pantherId == 0) return;

            if (!_storedData.TamedPanthers.TryGetValue(pantherId, out var data))
            {
                SendReply(player, "This animal is already wild.");
                return;
            }

            if (data.OwnerId != player.userID)
            {
                SendReply(player, "You don't own this animal.");
                return;
            }

            _storedData.TamedPanthers.Remove(pantherId);
            SaveData();

            var comp = target.GetComponent<PantherComponent>();
            if (comp != null)
            {
                // Drop storage contents at animal's position before destroying
                if (comp._storage != null && !comp._storage.IsDestroyed)
                {
                    var storageInv = comp._storage.inventory;
                    if (storageInv != null && storageInv.itemList.Count > 0)
                    {
                        var dropPos = target.transform.position + Vector3.up * 1f;
                        foreach (var item in storageInv.itemList.ToList())
                        {
                            item.RemoveFromContainer();
                            var dropped = ItemManager.Create(item.info, item.amount, item.skin);
                            if (dropped != null)
                            {
                                var entity = dropped.CreateWorldObject(dropPos, Quaternion.identity);
                                if (entity != null)
                                    entity.GetComponent<Rigidbody>()?.WakeUp();
                            }
                            item.Remove(0f);
                        }
                    }
                }
                comp.DestroySelf();
            }

            // Kill the animal - its AI was destroyed when tamed, can't be restored.
            // A new wild animal will spawn naturally via the spawn system.
            var combat = target.GetComponent<BaseCombatEntity>();
            if (combat != null)
            {
                combat._maxHealth = 1000f;
                combat.health = 1000f;
                combat.SendNetworkUpdate();
            }

            target.Kill();
            SendReply(player, "Animal released. It has returned to the wild.");
        }

        private void CmdCallPanther(BasePlayer player, string command, string[] args)
        {
            int called = 0;
            int rebuilt = 0;
            var toRemove = new List<ulong>();

            foreach (var kvp in _storedData.TamedPanthers.ToList())
            {
                if (kvp.Value.OwnerId != player.userID) continue;

                var pantherId = new NetworkableId(kvp.Key);
                BaseEntity panther = BaseNetworkable.serverEntities.Find(pantherId) as BaseEntity;

                // Check if panther needs rebuild: null, destroyed, or visually broken
                bool needsRebuild = panther == null || panther.IsDestroyed;
                if (!needsRebuild)
                {
                    var pc = panther.GetComponent<PantherComponent>();
                    if (pc != null && pc.WasKilled)
                        needsRebuild = true;
                    var combat = panther.GetComponent<BaseCombatEntity>();
                    if (combat != null && combat.health <= 0f)
                        needsRebuild = true;
                    if ((panther.flags & BaseEntity.Flags.Disabled) != 0)
                        needsRebuild = true;
                }

                if (needsRebuild)
                {
                    // Remove old ID from stored data FIRST so OnEntityKill doesn't protect it
                    _storedData.TamedPanthers.Remove(kvp.Key);
                    SaveData();

                    // Kill old broken panther entity if it still exists
                    if (panther != null && !panther.IsDestroyed)
                    {
                        var oldComp = panther.GetComponent<PantherComponent>();
                        if (oldComp != null)
                            oldComp.DestroySelf();
                        try { panther.Kill(); } catch { }
                        Debug($"Killed old broken animal entity {kvp.Key}");
                    }

                    // Rebuild it as tamed
                    var playerPos = player.transform.position;
                    var forward = player.eyes != null ? player.eyes.BodyForward() : Vector3.forward;
                    var targetPos = playerPos - forward * 2f;
                    float terrainH = TerrainMeta.HeightMap.GetHeight(targetPos);
                    var spawnPos = new Vector3(targetPos.x, Mathf.Max(terrainH, targetPos.y), targetPos.z);

                    // Determine which animal type to rebuild
                    string rebuildPrefab = AnimalPrefabs["panther"];
                    if (panther != null)
                        rebuildPrefab = GetAnimalPrefab(panther);

                    var newPanther = GameManager.server.CreateEntity(rebuildPrefab, spawnPos, Quaternion.identity);
                    if (newPanther == null)
                    {
                        Debug($"Failed to spawn new animal for rebuild!");
                        continue;
                    }

                    // Pre-neutralize: disable ALL non-visual components before Spawn()
                    string mainCompName = GetAnimalComponentName(newPanther);
                    var preBehaviours = newPanther.GetComponents<Component>();
                    foreach (var c in preBehaviours)
                    {
                        if (c == null) continue;
                        var tn = c.GetType().Name;
                        if (tn == "Spawnable" || tn == "NPCNetworking" || tn == "Model" || tn.Contains("Renderer")) continue;
                        try
                        {
                            if (c is MonoBehaviour mb)
                            { mb.CancelInvoke(); mb.StopAllCoroutines(); mb.enabled = false; }
                            else
                            {
                                var p = c.GetType().GetProperty("enabled");
                                if (p != null && p.CanWrite) p.SetValue(c, false, null);
                            }
                        }
                        catch { }
                    }
                    var preAnim = newPanther.GetComponent("Animator") as Behaviour;
                    if (preAnim != null) preAnim.enabled = false;
                    var preNav = newPanther.GetComponent("NavMeshAgent") as Behaviour;
                    if (preNav != null) { try { preNav.enabled = false; } catch { } }
                    // Also disable RustNavMeshAgent (new navmesh system) — keep alive but disabled
                    foreach (var c in newPanther.GetComponents<Component>())
                    {
                        if (c == null) continue;
                        var cn = c.GetType().Name;
                        if (cn == "RustNavMeshAgent" || (cn.Contains("NavMesh") && cn != "NavMeshAgent"))
                        {
                            try { if (c is MonoBehaviour mb) mb.enabled = false; } catch { }
                        }
                        // Disable FSM pre-spawn and null state — don't destroy or BudgetedUpdate NREs
                        if (cn.Contains("FSM") || cn.Contains("Fsm"))
                        {
                            try
                            {
                                if (c is MonoBehaviour mb) mb.enabled = false;
                                var ft = c.GetType();
                                var ffl = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic;
                                var csf = ft.GetField("<CurrentState>k__BackingField", ffl);
                                if (csf == null) csf = ft.GetField("CurrentState", ffl);
                                if (csf != null) csf.SetValue(c, null);
                                var af = ft.GetField("_agent", ffl);
                                if (af != null) af.SetValue(c, null);
                            }
                            catch { }
                        }
                    }
                    var preBody = newPanther.GetComponent<Rigidbody>();
                    if (preBody != null) { preBody.isKinematic = true; preBody.useGravity = false; }

                    // Spawn first to get the net ID
                    newPanther.Spawn();

                    // Post-spawn: re-disable ALL non-visual components (Spawn re-enables them)
                    var postComps = newPanther.GetComponents<Component>();
                    foreach (var c in postComps)
                    {
                        if (c == null) continue;
                        var tn = c.GetType().Name;
                        if (tn == "Spawnable" || tn == "NPCNetworking" || tn == "Model" || tn.Contains("Renderer")) continue;
                        try
                        {
                            if (c is MonoBehaviour mb)
                            { mb.CancelInvoke(); mb.StopAllCoroutines(); mb.enabled = false; }
                            else
                            {
                                var p = c.GetType().GetProperty("enabled");
                                if (p != null && p.CanWrite) p.SetValue(c, false, null);
                            }
                        }
                        catch { }
                    }
                    var postAnim = newPanther.GetComponent("Animator") as Behaviour;
                    if (postAnim != null) postAnim.enabled = false;
                    var postNav = newPanther.GetComponent("NavMeshAgent") as Behaviour;
                    if (postNav != null) { try { postNav.enabled = false; } catch { } }
                    // Also disable RustNavMeshAgent and FSM — keep alive but disabled
                    foreach (var c in newPanther.GetComponents<Component>())
                    {
                        if (c == null) continue;
                        var cn = c.GetType().Name;
                        if (cn == "RustNavMeshAgent" || (cn.Contains("NavMesh") && cn != "NavMeshAgent"))
                        {
                            try { if (c is MonoBehaviour mb) mb.enabled = false; } catch { }
                        }
                        // Disable FSM post-spawn too — null state to stop all AI behavior
                        if (cn.Contains("FSM") || cn.Contains("Fsm"))
                        {
                            try
                            {
                                if (c is MonoBehaviour mb) mb.enabled = false;
                                var ft = c.GetType();
                                var ffl = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic;
                                var csf = ft.GetField("<CurrentState>k__BackingField", ffl);
                                if (csf == null) csf = ft.GetField("CurrentState", ffl);
                                if (csf != null) csf.SetValue(c, null);
                                var af = ft.GetField("_agent", ffl);
                                if (af != null) af.SetValue(c, null);
                            }
                            catch { }
                        }
                    }

                    // Add new ID to stored data so OnEntitySpawned's NextTick sees it as tamed
                    var newId = (ulong)newPanther.net.ID.Value;
                    _storedData.TamedPanthers[newId] = new PantherData
                    {
                        OwnerId = kvp.Value.OwnerId,
                        TamedAt = kvp.Value.TamedAt,
                        Prefab = kvp.Value.Prefab ?? AnimalPrefabs["panther"],
                        SavePosX = spawnPos.x,
                        SavePosY = spawnPos.y,
                        SavePosZ = spawnPos.z
                    };

                    // OnEntitySpawned's NextTick will install PantherComponent as tamed
                    // But also install now in case OnEntitySpawned already fired
                    if (newPanther.GetComponent<PantherComponent>() == null)
                    {
                        PantherComponent.Install(newPanther, true);
                    }

                    Debug($"Rebuilt animal {kvp.Key} -> {newId} for {player.displayName}");
                    rebuilt++;
                    called++;
                    continue;
                }

                var existingComp = panther.GetComponent<PantherComponent>();
                if (existingComp == null)
                {
                    PantherComponent.Install(panther, true);
                }

                // Teleport panther near player
                var pos = player.transform.position;
                var fwd = player.eyes != null ? player.eyes.BodyForward() : Vector3.forward;
                var tPos = pos - fwd * 2f;
                float tH = TerrainMeta.HeightMap.GetHeight(tPos);
                panther.transform.position = new Vector3(tPos.x, Mathf.Max(tH, tPos.y), tPos.z);
                panther.SendNetworkUpdate();

                called++;
                Debug($"Called animal {kvp.Key} to {player.displayName} at {panther.transform.position}");
            }

            // Save data with new panther IDs
            SaveData();

            if (called > 0)
                SendReply(player, rebuilt > 0 ? $"Called {called} animal(s) ({rebuilt} rebuilt) to your location!" : $"Called {called} animal(s) to your location!");
            else
                SendReply(player, "You don't have any tamed animals to call.");
        }

        #endregion

        #region Rehydration

        private void RehydratePanthers()
        {
            var staleIds = new List<ulong>();
            var newEntries = new List<KeyValuePair<ulong, PantherData>>();
            int restored = 0;
            int respawned = 0;

            foreach (var kvp in _storedData.TamedPanthers)
            {
                var pantherId = new NetworkableId(kvp.Key);
                BaseEntity animal = BaseNetworkable.serverEntities.Find(pantherId) as BaseEntity;

                if (animal != null && IsTargetAnimal(animal))
                {
                    // Entity still exists — just install component
                    if (animal.gameObject.GetComponent<PantherComponent>() == null)
                        PantherComponent.Install(animal, true);
                    restored++;
                    continue;
                }

                // Entity ID no longer valid (server restart) — respawn from saved data
                if (string.IsNullOrEmpty(kvp.Value.Prefab)) 
                {
                    staleIds.Add(kvp.Key);
                    continue;
                }

                var savePos = new Vector3(kvp.Value.SavePosX, kvp.Value.SavePosY, kvp.Value.SavePosZ);
                float terrainH = TerrainMeta.HeightMap.GetHeight(savePos);
                savePos.y = Mathf.Max(terrainH, savePos.y);

                var newAnimal = GameManager.server.CreateEntity(kvp.Value.Prefab, savePos, Quaternion.identity);
                if (newAnimal == null)
                {
                    Puts($"[Rehydrate] Failed to spawn {kvp.Value.Prefab} for owner {kvp.Value.OwnerId}");
                    staleIds.Add(kvp.Key);
                    continue;
                }

                // Pre-neutralize: disable non-visual components before Spawn()
                var preComps = newAnimal.GetComponents<Component>();
                foreach (var c in preComps)
                {
                    if (c == null) continue;
                    var tn = c.GetType().Name;
                    if (tn == "Spawnable" || tn == "NPCNetworking" || tn == "Model" || tn.Contains("Renderer")) continue;
                    try
                    {
                        if (c is MonoBehaviour mb)
                        { mb.CancelInvoke(); mb.StopAllCoroutines(); mb.enabled = false; }
                        else
                        {
                            var p = c.GetType().GetProperty("enabled");
                            if (p != null && p.CanWrite) p.SetValue(c, false, null);
            
// ... [Code preview truncated due to length. Full source in attached file] ...

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