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

FREE CustomEntities : 1.0.21 1.0.21

Нет прав для скачивания
C#:
using Facepunch;
using Facepunch.Extend;
using Network;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Rust;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;
namespace Oxide.Plugins {
    [Info("Custom Entities", "Nikedemos", "1.0.21")]
    [Description("A robust framework for registering, spawning, loading and saving entity prefabs")]
    public class CustomEntities : RustPlugin {
#region CONST
        public const string FORMAT_FILENAME = "{0}.{1}";
        public const string PREFAB_PREFIX = "assets/custom/";
        public const string PREFAB_SPHERE = "assets/prefabs/visualization/sphere.prefab";
        public const string PREFAB_WOOD_STORAGE_BOX = "assets/prefabs/deployable/woodenbox/woodbox_deployed.prefab";
        public const string PREFAB_FX_IMPACT_METAL_BLUNT = "assets/bundled/prefabs/fx/impacts/blunt/metal/metal1.prefab";
        public const string PREFAB_FX_IMPACT_METAL_SLASH = "assets/bundled/prefabs/fx/impacts/slash/metal/metal1.prefab";
        public const string PREFAB_FX_IMPACT_METAL_STAB = "assets/bundled/prefabs/fx/impacts/stab/metal/metal1.prefab";
        public const string PREFAB_FX_IMPACT_METAL_BULLET = "assets/bundled/prefabs/fx/impacts/bullet/metal/metal1.prefab";
        public const string PREFAB_PLAYER = "assets/prefabs/player/player.prefab";
        public const string PREFAB_FX_IMPACT_METAL_PHYSICAL = "assets/bundled/prefabs/fx/impacts/physics/phys-impact-metal-hollow-hard.prefab";
        public const string PREFAB_FX_EXPLOSION = "assets/bundled/prefabs/fx/impacts/additive/explosion.prefab";
        public const string PREFAB_FX_FIRE = "assets/bundled/prefabs/fx/impacts/additive/fire.prefab";
        public const string CMD_SPAWN_AT = "spawn_at";
        public const string CMD_PURGE_PREFAB = "purge_prefab";
        public const string CMD_PURGE_PLUGIN = "purge_plugin";
        public const string CMD_COUNT_PLUGIN = "count_plugin";
        public const string PREFIX_CUSTOMENTITIES = "customentities";
        public const string PERM_ADMIN = PREFIX_CUSTOMENTITIES + ".admin";
        //required for the commands above
        public const string CONVAR_VERBOSE_LOGGING = "verbose_logging";
#endregion
#region STATIC
        public static CustomEntities Instance;
        public static bool Unloading = false;
        public static Effect ReusableEffect;
        public static readonly ReadOnlyDictionary<DamageType, float> DEFAULT_PROTECTION_AMOUNTS = new ReadOnlyDictionary<DamageType, float>(new Dictionary<DamageType, float> {
            [DamageType.Generic]
            = 0F,  //Generic
            [DamageType.Hunger]
            = 0F,  //Hunger
            [DamageType.Thirst]
            = 0F,  //Thirst
            [DamageType.Cold]
            = 0F,  //Cold
            [DamageType.Drowned]
            = 0F,  //Drowned
            [DamageType.Heat]
            = 0F,  //Heat
            [DamageType.Bleeding]
            = 0F,  //Bleeding
            [DamageType.Poison]
            = 0F,  //Poison
            [DamageType.Suicide]
            = 0F,  //Suicide
            [DamageType.Bullet]
            = 0F,  //Bullet
            [DamageType.Slash]
            = 0F,  //Slash
            [DamageType.Blunt]
            = 0F,  //Blunt
            [DamageType.Fall]
            = 0F,  //Fall
            [DamageType.Radiation]
            = 0F,  //Radiation
            [DamageType.Bite]
            = 0F,  //Bite
            [DamageType.Stab]
            = 0F,  //Stab
            [DamageType.Explosion]
            = 0F,  //Explosion
            [DamageType.RadiationExposure]
            = 0F,  //RadiationExposure
            [DamageType.ColdExposure]
            = 0F,  //ColdExposure
            [DamageType.Decay]
            = 0F,  //Decay
            [DamageType.ElectricShock]
            = 0F,  //ElectricShock
            [DamageType.Arrow]
            = 0F,  //Arrow
            [DamageType.AntiVehicle]
            = 0F,  //AntiVehicle
            [DamageType.Collision]
            = 0F,  //Collision
            [DamageType.Fun_Water]
            = 0F  //Fun Water
        }
        );
#endregion
#region LANG
        public const string MSG_PREFAB_REGISTERING_CUSTOM = nameof(MSG_PREFAB_REGISTERING_CUSTOM);
        public const string MSG_PREFAB_REGISTERING_MODIFIED = nameof(MSG_PREFAB_REGISTERING_MODIFIED);
        public const string MSG_PREFAB_REGISTRATION_EXCEPTION = nameof(MSG_PREFAB_REGISTRATION_EXCEPTION);
        public const string MSG_BUNDLE_UNREGISTRATION_REMOVED_VANILLA = nameof(MSG_BUNDLE_UNREGISTRATION_REMOVED_VANILLA);
        public const string MSG_BUNDLE_UNREGISTRATION_REMOVED_CUSTOM = nameof(MSG_BUNDLE_UNREGISTRATION_REMOVED_CUSTOM);
        public const string MSG_SAVING_SAVEFILES = nameof(MSG_SAVING_SAVEFILES);
        public const string MSG_SAVED_ENTITIES_1 = nameof(MSG_SAVED_ENTITIES_1);
        public const string MSG_SAVING_DATAFILE_EXCEPTION = nameof(MSG_SAVING_DATAFILE_EXCEPTION);
        public const string MSG_LOADING_NO_SAVEFILE = nameof(MSG_LOADING_NO_SAVEFILE);
        public const string MSG_LOADING_YES_DATAFILE = nameof(MSG_LOADING_YES_DATAFILE);
        public const string MSG_LOADING_FRESH_WIPE_DETECTED = nameof(MSG_LOADING_FRESH_WIPE_DETECTED);
        public const string MSG_LOADING_EMPTY_SAVEFILE = nameof(MSG_LOADING_EMPTY_SAVEFILE);
        public const string MSG_LOADING_ENTITIES_FROM_SAVEFILE = nameof(MSG_LOADING_ENTITIES_FROM_SAVEFILE);
        public const string MSG_LOADING_ERROR_ENTITY_ALREADY_EXISTS = nameof(MSG_LOADING_ERROR_ENTITY_ALREADY_EXISTS);
        public const string MSG_LOADING_ERROR_ENTITY_COULDNT_SPAWN = nameof(MSG_LOADING_ERROR_ENTITY_COULDNT_SPAWN);
        public const string MSG_LOADING_SPAWNING_ENTITIES = nameof(MSG_LOADING_SPAWNING_ENTITIES);
        public const string MSG_LOADED_ENTITIES = nameof(MSG_LOADED_ENTITIES);
        public const string MSG_LOADING_DATAFILE_LOAD_EXCEPTION = nameof(MSG_LOADING_DATAFILE_LOAD_EXCEPTION);
        public const string MSG_LOADING_NEW_SAVEFILE = nameof(MSG_LOADING_NEW_SAVEFILE);
        public const string MSG_LOADING_DATAFILE_NEW_EXCEPTION = nameof(MSG_LOADING_DATAFILE_NEW_EXCEPTION);
        public const string MSG_SAVING_BACKUP_EXCEPTION = nameof(MSG_SAVING_BACKUP_EXCEPTION);
        public const string MSG_CMD_PROVIDE_PREFAB = nameof(MSG_CMD_PROVIDE_PREFAB);
        public const string MSG_CMD_PROVIDE_PLUGIN_NAME = nameof(MSG_CMD_PROVIDE_PLUGIN_NAME);
        public const string MSG_CMD_PURGE_PREFAB_KILLED_ENTITIES = nameof(MSG_CMD_PURGE_PREFAB_KILLED_ENTITIES);
        public const string MSG_CMD_PURGE_PREFAB_NO_ENTITIES_FOUND = nameof(MSG_CMD_PURGE_PREFAB_NO_ENTITIES_FOUND);
        public const string MSG_CMD_PURGE_PLUGIN_NO_PLUGINS_REGISTERED = nameof(MSG_CMD_PURGE_PLUGIN_NO_PLUGINS_REGISTERED);
        public const string MSG_CMD_PURGE_PLUGIN_MAKE_SURE_LOADED = nameof(MSG_CMD_PURGE_PLUGIN_MAKE_SURE_LOADED);
        public const string MSG_CMD_PURGE_PLUGIN_NOTHING_FOUND = nameof(MSG_CMD_PURGE_PLUGIN_NOTHING_FOUND);
        public const string MSG_CMD_PURGE_PLUGIN_NO_ENTITIES_FOUND = nameof(MSG_CMD_PURGE_PLUGIN_NO_ENTITIES_FOUND);
        public const string MSG_CMD_PURGE_PLUGIN_KILLED_ENTITIES = nameof(MSG_CMD_PURGE_PLUGIN_KILLED_ENTITIES);
        public const string MSG_TRY_COMPO_REPLACE_ERROR = nameof(MSG_TRY_COMPO_REPLACE_ERROR);
        public const string MSG_TRY_COMPO_REPLACE_ERROR_GAMEOBJECT_NULL = nameof(MSG_TRY_COMPO_REPLACE_ERROR_GAMEOBJECT_NULL);
        public const string MSG_TRY_COMPO_REPLACE_ERROR_NO_OLD_COMPO_ATTACHED = nameof(MSG_TRY_COMPO_REPLACE_ERROR_NO_OLD_COMPO_ATTACHED);
        public const string MSG_TRY_COMPO_REPLACE_ERROR_ALREADY_HAS_NEW_COMPO = nameof(MSG_TRY_COMPO_REPLACE_ERROR_ALREADY_HAS_NEW_COMPO);
        public const string MSG_TRY_COMPO_REPLACE_DEBUG_HEADER = nameof(MSG_TRY_COMPO_REPLACE_DEBUG_HEADER);
        public const string MSG_TRY_COMPO_REPLACE_DEBUG_FIELD_INFO = nameof(MSG_TRY_COMPO_REPLACE_DEBUG_FIELD_INFO);
        public const string MSG_TRY_COMPO_REPLACE_DEBUG_FOUND_SELF_REFERENCE_THIS_COMPO = nameof(MSG_TRY_COMPO_REPLACE_DEBUG_FOUND_SELF_REFERENCE_THIS_COMPO);
        public const string MSG_TRY_COMPO_REPLACE_DEBUG_FOUND_SELF_REFERENCE_OTHER_COMPO = nameof(MSG_TRY_COMPO_REPLACE_DEBUG_FOUND_SELF_REFERENCE_OTHER_COMPO);
        public const string MSG_TRY_COMPO_REPLACE_DEBUG_SUBCOMPO_NOT_FOUND_IN_DESTINATION = nameof(MSG_TRY_COMPO_REPLACE_DEBUG_SUBCOMPO_NOT_FOUND_IN_DESTINATION);
        private static readonly Dictionary<string, string> LangMessages = new Dictionary<string, string> {
            [MSG_PREFAB_REGISTERING_CUSTOM]
            = "Registering custom entity {0} : {1} as prefab \"{2}\"...", [MSG_PREFAB_REGISTERING_MODIFIED] = "Registering modified entity based on prefab \"{0}\" as prefab \"{1}\"...", [MSG_PREFAB_REGISTRATION_EXCEPTION] = "Exception while trying to register prefab: {0}\n{1}", [MSG_BUNDLE_UNREGISTRATION_REMOVED_VANILLA] = "Removed {0} instances of various vanilla entities handled by the savefile from world.", [MSG_BUNDLE_UNREGISTRATION_REMOVED_CUSTOM] = "Removed {0} instances of a custom prefab \"{1}\" from world.", [MSG_SAVING_SAVEFILES] = "Saving {0} binary savefiles...", [MSG_SAVED_ENTITIES_1] = "INFO: Saved {0} out of {1} entities ({2} custom, {3} vanilla. {4} removed because invalid) to \"{5}\"", [MSG_SAVING_DATAFILE_EXCEPTION] = "ERROR: {0} while trying to save \"{1}\": {2}\n{3}", [MSG_LOADING_NO_SAVEFILE] = "INFO: Savefile \"{0}\" doesn't exist, nothing to load.", [MSG_LOADING_YES_DATAFILE] = "Loading savefile from \"{0}\"...", [MSG_LOADING_FRESH_WIPE_DETECTED] = "INFO: The ID of the current Community Entity ({0}) is different than the one from the savefile ({1}) - this usually means a fresh wipe or switching between maps mid-wipes. A new savefile will be generated.", [MSG_LOADING_EMPTY_SAVEFILE] = "INFO: The savefile \"{0}\" contains no entities, nothing to load.", [MSG_LOADING_ENTITIES_FROM_SAVEFILE] = "Loading {0} entities from the savefile \"{1}\"...", [MSG_LOADING_ERROR_ENTITY_ALREADY_EXISTS] = "ERROR: The savefile \"{0}\" contains an entity with prefab ID {1} ({2}), Net.ID {3}, but another entity with that Net.ID already exists. Skipping to the next entity...", [MSG_LOADING_ERROR_ENTITY_COULDNT_SPAWN] = "ERROR: The savefile \"{0}\" contains an entity with prefab ID {1} ({2}), Net.ID {3} that could not be spawned. Skipping to the next entity...", [MSG_LOADING_SPAWNING_ENTITIES] = "Spawning {0} entities from the save file at {1}...", [MSG_LOADED_ENTITIES] = "INFO: Loaded {0} out of {1} entities ({2} custom, {3} vanilla) from \"{4}\"", [MSG_LOADING_DATAFILE_LOAD_EXCEPTION] = "ERROR: {0} while trying to load \"{1}\": {2}\n{3}", [MSG_LOADING_NEW_SAVEFILE] = "INFO: Generating default (blank) savefile at \"{0}\"...", [MSG_LOADING_DATAFILE_NEW_EXCEPTION] = "ERROR: {0} while trying to create a new datafile \"{1}\": {2}\n{3}", [MSG_CMD_PROVIDE_PREFAB] = "Please provide the short prefab name, or the initial part of it.", [MSG_CMD_PROVIDE_PLUGIN_NAME] = "Please provide the plugin name, or the initial part of it.", [MSG_CMD_PURGE_PREFAB_KILLED_ENTITIES] = "Purged {0} entities matching short prefab name \"{1}\"", [MSG_CMD_PURGE_PREFAB_NO_ENTITIES_FOUND] = "Could not find any entities matching short prefab name \"{0}\"", [MSG_SAVING_BACKUP_EXCEPTION] = "ERROR: {0} while trying to save backups of the datafile \"{1}\": {2}\n{3}", [MSG_CMD_PURGE_PLUGIN_NO_PLUGINS_REGISTERED] = "There's currently no Custom Entities plugins registered.", [MSG_CMD_PURGE_PLUGIN_MAKE_SURE_LOADED] = "Make sure it's loaded in. If it isn't, just delete the appropriate savefile from /oxide/data/CustomEntities/ and it will have the same effect.", [MSG_CMD_PURGE_PLUGIN_NOTHING_FOUND] = "Could not find any Plugins matching the provided name \"{0}\"", [MSG_CMD_PURGE_PLUGIN_NO_ENTITIES_FOUND] = "Could not find any entities from the Plugin \"{0}\"", [MSG_CMD_PURGE_PLUGIN_KILLED_ENTITIES] = "Purged {0} entities from the Plugin \"{1}\"", [MSG_TRY_COMPO_REPLACE_ERROR] = "ERROR WHILE TRYING TO REPLACE `{0}` with `{1}`:", [MSG_TRY_COMPO_REPLACE_ERROR_GAMEOBJECT_NULL] = "The GameObject is null!", [MSG_TRY_COMPO_REPLACE_ERROR_NO_OLD_COMPO_ATTACHED] = "There's no `{0}` component attached to the GameObject!", [MSG_TRY_COMPO_REPLACE_ERROR_ALREADY_HAS_NEW_COMPO] = "The GameObject already has a `{0}` component attached (and there's only one allowed!)", [MSG_TRY_COMPO_REPLACE_DEBUG_HEADER] = "`{0}` is a subclass of `{1}`, replacing matching fields:\n", [MSG_TRY_COMPO_REPLACE_DEBUG_FIELD_INFO] = "    set `{0}` to `{1}`", [MSG_TRY_COMPO_REPLACE_DEBUG_FOUND_SELF_REFERENCE_THIS_COMPO] = "    FOUND SELF REFERENCE IN THE OLD COMPONENT in the field `{0}`. SETTING NEW COMPONENT'S SELF REFERENCE TO ITSELF.", [MSG_TRY_COMPO_REPLACE_DEBUG_FOUND_SELF_REFERENCE_OTHER_COMPO] = "    FOUND REFERENCE TO THE OLD COMPONENT IN ANOTHER COMPONENT IN THE HIERARCHY ({0}) in the field `{1}`. SETTING THAT COMPONENT'S REFERENCE TO THE NEW COMPONENT.", [MSG_TRY_COMPO_REPLACE_DEBUG_SUBCOMPO_NOT_FOUND_IN_DESTINATION] = "    COULD NOT MATCH THE SOURCE SUB-COMPONENT WITH THE DESTINATION COMPONENT ({0}) in the field `{1}`. THE DESTINATION COMPONENT DOESN'T EXIST. THIS IS BAD."
        }
        ;
        private static string MSG(string msg, string userID = null, params object[] args) {
            if (args == null) {
                return Instance.lang.GetMessage(msg, Instance, userID);
            }
            else {
                return string.Format(Instance.lang.GetMessage(msg, Instance, userID), args);
            }
        }
#endregion
#region COVALENCE COMMANDS
        private void CommandCountPlugin(IPlayer iplayer, string command, string[] args) {
            if (args.Length == 0) {
                iplayer.Reply(MSG(MSG_CMD_PROVIDE_PLUGIN_NAME, iplayer.Id));
                return;
            }
            string pluginName = args[0].ToLower();
            BinaryData.PlayerRequestedPluginCount(iplayer, pluginName);
        }
        private void CommandPurgePlugin(IPlayer iplayer, string command, string[] args) {
            if (args.Length == 0) {
                iplayer.Reply(MSG(MSG_CMD_PROVIDE_PLUGIN_NAME, iplayer.Id));
                return;
            }
            string pluginName = args[0].ToLower();
            BinaryData.PlayerRequestedPluginPurge(iplayer, pluginName);
        }
        private void CommandPurgePrefab(IPlayer iplayer, string command, string[] args) {
            if (args.Length == 0) {
                iplayer.Reply(MSG(MSG_CMD_PROVIDE_PREFAB, iplayer.Id));
                return;
            }
            string prefabName = args[0].ToLower();
            BaseEntity[] iterateOver = BaseNetworkable.serverEntities.OfType<BaseEntity>().ToArray();
            int countKilled = 0;
            for (int i = 0; i < iterateOver.Length; i++) {
                BaseEntity entity = iterateOver[i];
                if (entity == null) {
                    continue;
                }
                if (entity.IsDestroyed) {
                    continue;
                }
                if (entity.net == null) {
                    continue;
                }
                if (!entity.ShortPrefabName.StartsWith(prefabName)) {
                    continue;
                }
                entity.Kill(BaseNetworkable.DestroyMode.None);
                countKilled++;
            }
            if (countKilled == 0) {
                iplayer.Reply(MSG(MSG_CMD_PURGE_PREFAB_NO_ENTITIES_FOUND, iplayer.Id, prefabName));
                return;
            }
            iplayer.Reply(MSG(MSG_CMD_PURGE_PREFAB_KILLED_ENTITIES, iplayer.Id, countKilled, prefabName));
        }
        private void CommandSpawnAtPlayerEyes(IPlayer iplayer, string command, string[] args) {
            BasePlayer player = iplayer.Object as BasePlayer;
            if (player == null) {
                return;
            }
            if (args.Length == 0) {
                iplayer.Reply(MSG(MSG_CMD_PROVIDE_PREFAB, iplayer.Id));
                return;
            }
            string prefabName = args[0].ToLower();
            string commandResponse = ConVar.Entity.svspawn(prefabName, player.eyes.position, Vector3.zero);
            iplayer.Reply(commandResponse);
        }
#endregion
#region HOOK SUBSCRIPTIONS
        void Init() {
            lang.RegisterMessages(LangMessages, this);
        }
        void OnServerInitialized() {
            Instance = this;
            ReusableEffect = new Effect();
            permission.RegisterPermission(PERM_ADMIN, this);
            AddCovalenceCommand(CMD_PURGE_PREFAB, nameof(CommandPurgePrefab), PERM_ADMIN);
            AddCovalenceCommand(CMD_PURGE_PLUGIN, nameof(CommandPurgePlugin), PERM_ADMIN);
            AddCovalenceCommand(CMD_SPAWN_AT, nameof(CommandSpawnAtPlayerEyes), PERM_ADMIN);
            AddCovalenceCommand(CMD_COUNT_PLUGIN, nameof(CommandCountPlugin), PERM_ADMIN);
            CustomConvars.Init();
            BinaryData.Init();
            CastingNonAlloc.Init();
            CustomPrefabs.Init();
        }
        void Unload() {
            if (Instance == null) {
                return;
            }
            Unloading = true;
            //after this point, no saving and unregistering possible
            CustomPrefabs.Unload();
            BinaryData.Unload();
            //doesn't do much either.
            CastingNonAlloc.Unload();
            CustomConvars.Unload();
            //unload top-level static
            Unloading = false;
            Instance = null;
            ReusableEffect = null;
        }
        void OnServerSave() {
            if (Instance == null) {
                return;
            }
            BinaryData.SaveAll();
        }
        void OnItemAddedToContainer(ItemContainer container, Item item) {
            if (Instance == null) {
                return;
            }
            List<BaseEntity> maybeCustomList = GetOwnerThingCustomSaveList(container);
            if (maybeCustomList == null) {
                return;
            }
            CustomPrefabs.EnsureMovedToCustomSaveListRecursively(item, maybeCustomList);
        }
        void OnItemRemovedFromContainer(ItemContainer container, Item item) {
            if (Instance == null) {
                return;
            }
            List<BaseEntity> maybeCustomList = GetOwnerThingCustomSaveList(container);
            if (maybeCustomList == null) {
                return;
            }
            CustomPrefabs.EnsureMovedToVanillaSaveListRecursively(item);
        }
        void OnEntityKill(BaseEntity entity) {
            if (Instance == null) {
                return;
            }
            if (Unloading) {
                return;
            }
            //only vanilla
            List<BaseEntity> checkForVanillaEnityList = CustomPrefabs.TryGetEntityCustomSaveList(entity, CustomPrefabs.SaveListEntityType.Vanilla);
            if (checkForVanillaEnityList == null) {
                return;
            }
            CustomPrefabs.ForgetEntityFromSaveListAndCache(entity, checkForVanillaEnityList);
        }
#endregion
#region CUSTOM PREFAB MANAGER
        public static class CustomPrefabs {
            private static GameObjectRef _emptyImpactGameObjectRef = null;
            private static Dictionary<string, GameObject> _prefabsPreProcessedCustom = null;
            private static Dictionary<GameObject, BaseEntity> _preProcessedGoToEntityCache = null;
            private static ListHashSet<GenericPrefabRecipe> _cachedRecipes = null;
            private static Dictionary<Plugin, BinaryData> _pluginToBinaryData = null;
            private static List<string> _gameManifestEntityList = null;
            //this is not vanilla, just for building the vanilla manifest array
            private static Dictionary<ulong, List<BaseEntity>> _vanillaEntityToCustomSaveList = null;
            public static Dictionary<string, uint> ModifiedPrefabFullNameToModifiedPrefabIDHandledByBundles = null;
            public static Dictionary<string, List<BaseEntity>> ModifiedPrefabFullNameToCustomSaveList = null;
            public static void Init() {
                _emptyImpactGameObjectRef = new GameObjectRef();
                _prefabsPreProcessedCustom = new Dictionary<string, GameObject>();
                _preProcessedGoToEntityCache = new Dictionary<GameObject, BaseEntity>();
                _gameManifestEntityList = new List<string>();
                _cachedRecipes = new ListHashSet<GenericPrefabRecipe>();
                _pluginToBinaryData = new Dictionary<Plugin, BinaryData>();
                _vanillaEntityToCustomSaveList = new Dictionary<ulong, List<BaseEntity>>();
                ModifiedPrefabFullNameToModifiedPrefabIDHandledByBundles = new Dictionary<string, uint>();
                ModifiedPrefabFullNameToCustomSaveList = new Dictionary<string, List<BaseEntity>>();
            }
            public static void Unload() {
                _emptyImpactGameObjectRef = null;
                _prefabsPreProcessedCustom = null;
                _preProcessedGoToEntityCache = null;
                _gameManifestEntityList = null;
                _cachedRecipes = null;
                _pluginToBinaryData = null;
                _vanillaEntityToCustomSaveList = null;
                ModifiedPrefabFullNameToModifiedPrefabIDHandledByBundles = null;
                ModifiedPrefabFullNameToCustomSaveList = null;
            }
            public static BaseEntity TryGetPreprocessedPrototypeFromVanilla(string prefabName) => TryGetPreprocessedPrototypeFrom(GameManager.server.preProcessed.prefabList, prefabName);
            public static BaseEntity TryGetPreprocessedPrototypeFromCustom(string prefabName) => TryGetPreprocessedPrototypeFrom(_prefabsPreProcessedCustom, prefabName);
            private static BaseEntity TryGetPreprocessedPrototypeFrom(Dictionary<string, GameObject> preProcessedDictionary, string prefabName) {
                GameObject goResult;
                if (!preProcessedDictionary.TryGetValue(prefabName, out goResult)) {
                    return null;
                }
                //is it in the cache?
                BaseEntity baseEntityResult;
                if (!_preProcessedGoToEntityCache.TryGetValue(goResult, out baseEntityResult)) {
                    //get the component... if non null, add to cache
                    baseEntityResult = goResult.GetComponent<BaseEntity>();
                    if (baseEntityResult != null) {
                        _preProcessedGoToEntityCache.Add(goResult, baseEntityResult);
                    }
                }
                //now you get it or a null
                return baseEntityResult;
            }
            //public API
            public static void ForgetEntityFromSaveListAndCache(BaseEntity vanillaEntity, List<BaseEntity> maybeCustomList) {
                maybeCustomList.Remove(vanillaEntity);
                _vanillaEntityToCustomSaveList.Remove(vanillaEntity.net.ID.Value);
            }
            public enum SaveListEntityType {
                Vanilla, Custom, VanillaAndCustom
            }
            public static List<BaseEntity> TryGetEntityCustomSaveList(BaseEntity vanillaOrCustomEntity, SaveListEntityType entityType) {
                if (!BaseNetworkableEx.IsValid(vanillaOrCustomEntity)) {
                    return null;
                }
                bool checkForVanilla = (entityType == SaveListEntityType.Vanilla) || (entityType == SaveListEntityType.VanillaAndCustom);
                bool checkForCustom = (entityType == SaveListEntityType.Custom) || (entityType == SaveListEntityType.VanillaAndCustom);
                if (checkForCustom) {
                    ICustomEntity asInterface = vanillaOrCustomEntity as ICustomEntity;
                    if (asInterface != null) {
                        //no need to check for vanilla any more
                        return asInterface.SaveListInDataFile;
                    }
                }
                if (checkForVanilla) {
                    List<BaseEntity> maybeCustomList;
                    if (!_vanillaEntityToCustomSaveList.TryGetValue(vanillaOrCustomEntity.net.ID.Value, out maybeCustomList)) {
                        //neither custom nor vanilla.
                        return null;
                    }
                    return maybeCustomList;
                }
                return null;
            }
            public static void EnsureMovedToVanillaSaveList(BaseEntity vanillaEntity) {
                ICustomEntity asInterface = vanillaEntity as ICustomEntity;
                if (asInterface != null) {
                    return;
                }
                List<BaseEntity> maybeAlreadyExistingList = TryGetEntityCustomSaveList(vanillaEntity, SaveListEntityType.Vanilla);
                if (maybeAlreadyExistingList != null) {
                    //it exists in some other custom save list, so let's forget it first...
                    ForgetEntityFromSaveListAndCache(vanillaEntity, maybeAlreadyExistingList);
                }
                vanillaEntity.EnableSaving(true);
                //this will add it back
            }
            public static void EnsureMovedToVanillaSaveListRecursively(Item item) {
                ForEachItemEntityRecursively(item, (entity) => EnsureMovedToVanillaSaveList(entity));
            }
            public static void EnsureMovedToCustomSaveListRecursively(Item item, List<BaseEntity> targetList) {
                ForEachItemEntityRecursively(item, (entity) => EnsureMovedToCustomSaveList(entity, targetList));
            }
            private static void EnsureMovedToCustomSaveListRecursivelyEntireContainer(ItemContainer itemContainer, List<BaseEntity> saveList) {
                if (itemContainer == null) {
                    return;
                }
                var itemListCount = itemContainer.itemList?.Count ?? 0;
                if (itemListCount == 0) {
                    return;
                }
                for (var i = 0; i < itemContainer.itemList.Count; i++) {
                    var item = itemContainer.itemList[i];
                    if (item == null) {
                        continue;
                    }
                    EnsureMovedToCustomSaveListRecursively(item, saveList);
                }
            }
            private static void ForEachEntityInTransformHierarchyRecursivelyAndAlsoConsideringItemsInInventories(BaseEntity entity, Action<BaseEntity> entityAction, List<BaseEntity> saveList) {
                entityAction(entity);
                //still!
                if (entity == null) {
                    return;
                }
                //so we have several "obvious" cases here
                //in fact, maybe we should re-visit MetaphysicsEnchantedItems to get everything that has a container, presumably?
                //first obvious case:
                var someSortOfContainer = entity as IItemContainerEntity;
                //this covers a broad range of things that have "ItemContainer inventory".
                if (someSortOfContainer != null) {
                    //this will do nothing if the inventory is null or empty
                    EnsureMovedToCustomSaveListRecursivelyEntireContainer(someSortOfContainer.inventory, saveList);
                }
                else {
                    var asDroppedItem = entity as WorldItem;
                    if (asDroppedItem != null) {
                        //this will do nothing if the asDroppedItem.item is null.
                        EnsureMovedToCustomSaveListRecursively(asDroppedItem.item, saveList);
                    }
                    else {
                        var asPlayer = entity as BasePlayer;
                        if (asPlayer != null) {
                            var playerInventory = asPlayer.inventory;
                            if (playerInventory != null) {
                                EnsureMovedToCustomSaveListRecursivelyEntireContainer(playerInventory.containerMain, saveList);
                                EnsureMovedToCustomSaveListRecursivelyEntireContainer(playerInventory.containerBelt, saveList);
                                EnsureMovedToCustomSaveListRecursivelyEntireContainer(playerInventory.containerWear, saveList);
                                //any backpacks? almost forgot!
                                var getBackpack = playerInventory.GetBackpackWithInventory();
                                if (getBackpack != null) {
                                    EnsureMovedToCustomSaveListRecursivelyEntireContainer(getBackpack.contents, saveList);
                                }
                            }
                        }
                        else {
                            var asDroppedItemContainer = entity as DroppedItemContainer;
                            if (asDroppedItemContainer != null) {
                                EnsureMovedToCustomSaveListRecursivelyEntireContainer(asDroppedItemContainer.inventory, saveList);
                            }
                            else {
                                //and last chance... so far, that I can see, as much as edge cases go.
                                var asLootableCorpse = entity as LootableCorpse;
                                if (asLootableCorpse != null) {
                                    var inventories = asLootableCorpse.containers;
                                    if (inventories != null) {
                                        int invLength = inventories.Length;
                                        for (var i = 0; i < invLength; i++) {
                                            var inventory = inventories[i];
                                            EnsureMovedToCustomSaveListRecursivelyEntireContainer(inventory, saveList);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if (entity.children == null) {
                    return;
                }
                var childrenCount = entity.children.Count;
                for (int i = 0; i < childrenCount; i++) {
                    ForEachEntityInTransformHierarchyRecursivelyAndAlsoConsideringItemsInInventories(entity.children[i], entityAction, saveList);
                }
            }
            private static void ForEachItemEntityRecursively(Item item, Action<BaseEntity> entityAction)  //so the action will be either move to custom, or move to vanilla... and keep passing the action
            {
                if (item == null) {
                    return;
                }
                //sub entity...
                if (item.instanceData != null) {
                    if (item.instanceData.subEntity.IsValid) {
                        BaseEntity subEnt = BaseNetworkable.serverEntities.Find(item.instanceData.subEntity) as BaseEntity;
                        if (subEnt != null) {
                            entityAction(subEnt);
                        }
                    }
                }
                //world entity...
                BaseEntity worldEntity = item.GetWorldEntity();
                if (worldEntity != null) {
                    entityAction(worldEntity);
                }
                //held entity...
                BaseEntity heldEntity = item.GetHeldEntity();
                if (heldEntity != null) {
                    entityAction(heldEntity);
                }
                //and then, if your item container is not null, keep going.
                if (item.contents == null) {
                    return;
                }
                if (item.contents.itemList.IsNullOrEmpty()) {
                    return;
                }
                int count = item.contents.itemList.Count;
                for (int i = 0; i < count; i++) {
                    Item containerItem = item.contents.itemList[i];
                    if (containerItem == null) {
                        continue;
                    }
                    ForEachItemEntityRecursively(containerItem, entityAction);
                }
            }
            /*
             * 
            public static void EnsureMovedToCustomSaveListRecursively(Item item, List<BaseEntity> targetList)
            {
                ForEachItemEntityRecursively(item, (entity) => EnsureMovedToCustomSaveList(entity, targetList));
            }

             */
            public static void EnsureMovedToCustomSaveListRecursivelyTopDown(BaseEntity vanillaEntity, List<BaseEntity> targetList) {
                bool first = true;
                ForEachEntityInTransformHierarchyRecursivelyAndAlsoConsideringItemsInInventories(vanillaEntity, (entity) => {
                    EnsureMovedToCustomSaveListMindingIfFirstOneIsForced(entity, targetList, ref first);
                }
                , targetList);
            }
            public static void EnsureMovedToCustomSaveListMindingIfFirstOneIsForced(BaseEntity vanillaEntity, List<BaseEntity> targetList, ref bool first) {
                //so. subentities, and stuff like that,
                //their "enableSaving" status WILL matter.
                //i.e. here, in this lambda thingy,
                //if entity.enableSaving is false, we should NOT do this.
                //HOWEVER. For the first one, i.e. the topmost one,
                //we already know that we WANT it there.
                //so it needs to be ignored for that one.
                //so keep passing a bool.
                //
                /*
                if (!first)
                {
                    if (vanillaEntity.enableSaving == false)
                    {
                        Instance.PrintWarning($">>> DEBUG ENSURE MOVED: NOT MOVING `{vanillaEntity.PrefabName}` TO SAVE LIST, BECAUSE IT'S ENABLE SAVING IS FALSE!");
                        return;
                    }
                }*/
                first = false;
                EnsureMovedToCustomSaveList(vanillaEntity, targetList);
                vanillaEntity.enableSaving = false;
            }
            public static void EnsureMovedToCustomSaveList(BaseEntity vanillaEntity, List<BaseEntity> targetList) {
                if (vanillaEntity == null) {
                    return;
                }
                if (vanillaEntity.net == null) {
                    return;
                }
                if (vanillaEntity.IsDestroyed) {
                    return;
                }
                ICustomEntity asInterface = vanillaEntity as ICustomEntity;
                if (asInterface != null) {
                    //check if it's something handled by bundles
                    var tryGetList = ModifiedPrefabFullNameToCustomSaveList.TryGetValue(vanillaEntity.PrefabName, out var maybeSaveList);
                    if (!tryGetList) {
                        return;
                    }
                    if (maybeSaveList != targetList) {
                        return;
                    }
                }
                if (targetList.Contains(vanillaEntity)) {
                    //already contains
                    return;
                }
                //already a part of a list?
                List<BaseEntity> maybeAlreadyExistingList = TryGetEntityCustomSaveList(vanillaEntity, SaveListEntityType.Vanilla);
                if (maybeAlreadyExistingList != null) {
                    if (maybeAlreadyExistingList == targetList) {
                        return;
                        //already where it needs to be
                    }
                    //it exists in some other custom save list, so let's forget it first...
                    ForgetEntityFromSaveListAndCache(vanillaEntity, maybeAlreadyExistingList);
                }
                vanillaEntity.EnableSaving(false);
                //this will remove from vanilla list if enabled - use after spawning
                targetList.Add(vanillaEntity);
                _vanillaEntityToCustomSaveList.Add(vanillaEntity.net.ID.Value, targetList);
            }
            public static List<BaseEntity> EnsureMovedToCustomSaveListSameAs(BaseEntity vanillaEntity, ICustomEntity sameAsCustomEntity) {
                if (sameAsCustomEntity.SaveListInDataFile == null) {
                    //null for some reason, so false just to be on the safe side
                    return null;
                }
                EnsureMovedToCustomSaveList(vanillaEntity, sameAsCustomEntity.SaveListInDataFile);
                return sameAsCustomEntity.SaveListInDataFile;
            }
            public static bool RegisterAndLoadBundle(CustomPrefabBundle bundle, string optionalSuffix = default(string)) {
                //step 0: ensure the data...
                BinaryData binaryData = BinaryData.SummonBinaryData(bundle.Owner, optionalSuffix);
                binaryData.PrefabBundle = bundle;
                List<CustomPrefabRecipe> customRecipes = Pool.Get<List<CustomPrefabRecipe>>();
                List<ModifiedPrefabRecipe> modifiedRecipes = Pool.Get<List<ModifiedPrefabRecipe>>();
                //step 1: separate the grain from the hull
                for (int i = 0; i < binaryData.PrefabBundle.Recipes.Length; i++) {
                    GenericPrefabRecipe recipe = binaryData.PrefabBundle.Recipes[i];
                    if (recipe is CustomPrefabRecipe customRecipe) {
                        customRecipes.Add(customRecipe);
                    }
                    else if (recipe is ModifiedPrefabRecipe modifiedRecipe) {
                        modifiedRecipes.Add(modifiedRecipe);
                    }
                }
                bool allGood = true;
                //step 2: register custom prefabs from recipes...
                for (int i = 0; i < customRecipes.Count; i++) {
                    CustomPrefabRecipe recipe = customRecipes[i];
                    Instance.PrintWarning(MSG(MSG_PREFAB_REGISTERING_CUSTOM, null, recipe.EntityType.Name, (recipe.BaseCombat == null ? nameof(BaseEntity) : nameof(BaseCombatEntity)), recipe.FullPrefabName));
                    try {
                        RegisterPrefabCustom(recipe, binaryData);
                    }
                    catch (Exception e) {
                        Instance.PrintError(MSG(MSG_PREFAB_REGISTRATION_EXCEPTION, null, e.Message, e.StackTrace));
                        allGood = false;
                    }
                }
                //step 3: register modified recipes. We're doing that second since we might wanna modify a custom prefab too
                for (int i = 0; i < modifiedRecipes.Count; i++) {
                    ModifiedPrefabRecipe recipe = modifiedRecipes[i];
                    Instance.PrintWarning(MSG(MSG_PREFAB_REGISTERING_MODIFIED, null, recipe.OriginalFullPrefabName, recipe.FullPrefabName));
                    try {
                        RegisterPrefabModified(recipe, binaryData);
                    }
                    catch (Exception e) {
                        Instance.PrintError(MSG(MSG_PREFAB_REGISTRATION_EXCEPTION, null, e.Message, e.StackTrace));
                        allGood = false;
                    }
                }
                Pool.FreeUnmanaged(ref customRecipes);
                Pool.FreeUnmanaged(ref modifiedRecipes);
                //step 4: now that everything is registered, load.
                //now they should all know their appropriate save lists.
                binaryData.Load();
                return allGood;
            }
            public static bool SaveAndUnregisterBundle(CustomPrefabBundle cookbook, string optionalSuffix = default(string)) {
                //and this should be the exact opposite.
                //first, save...
                //this will ensure the data wrapper, just in case it wasn't before
                BinaryData binaryData = BinaryData.SummonBinaryData(cookbook.Owner, optionalSuffix);
                binaryData.Save(true);
                //this will save everything
                List<CustomPrefabRecipe> customRecipes = Pool.Get<List<CustomPrefabRecipe>>();
                List<ModifiedPrefabRecipe> modifiedRecipes = Pool.Get<List<ModifiedPrefabRecipe>>();
                for (int i = 0; i < binaryData.PrefabBundle.Recipes.Length; i++) {
                    GenericPrefabRecipe recipe = binaryData.PrefabBundle.Recipes[i];
                    if (recipe is CustomPrefabRecipe customRecipe) {
                        customRecipes.Add(customRecipe);
                    }
                    else if (recipe is ModifiedPrefabRecipe modifiedRecipe) {
                        modifiedRecipes.Add(modifiedRecipe);
                    }
                }
                bool allGood = true;
                for (int i = 0; i < modifiedRecipes.Count; i++) {
                    ModifiedPrefabRecipe recipe = modifiedRecipes[i];
                    if (!UnregisterPrefabInternal(recipe)) {
                        allGood = false;
                    }
                }
                for (int i = 0; i < customRecipes.Count; i++) {
                    CustomPrefabRecipe recipe = customRecipes[i];
                    //caveat: this removes from _modified... cache!
                    if (!UnregisterPrefabInternal(recipe)) {
                        allGood = false;
                    }
                }
                Pool.FreeUnmanaged(ref customRecipes);
                Pool.FreeUnmanaged(ref modifiedRecipes);
                //and now we need to kill off vanilla entities that might still linger about
                int countKilled = 0;
                for (int i = 0; i < binaryData.CustomEntitySaveList.Count; i++) {
                    BaseEntity entity = binaryData.CustomEntitySaveList[i];
                    if (entity == null) {
                        continue;
                    }
                    if (entity.IsDestroyed) {
                        continue;
                    }
                    entity.Kill(BaseNetworkable.DestroyMode.None);
                    countKilled++;
                }
                if (countKilled > 0) {
                    if (CustomConvars.VerboseLogging) {
                        Instance.PrintWarning(MSG(MSG_BUNDLE_UNREGISTRATION_REMOVED_VANILLA, null, countKilled));
                    }
                }
                BinaryData.ForgetBinaryData(cookbook.Owner);
                return allGood;
            }
            //and the rest are private methods
            private static bool RegisterPrefabModified(ModifiedPrefabRecipe recipe, BinaryData data) {
                var tryFindPrototype = TryGetPreprocessedPrototypeFromVanilla(recipe.OriginalFullPrefabName);
                if (tryFindPrototype == null) {
                    tryFindPrototype = TryGetPreprocessedPrototypeFromCustom(recipe.OriginalFullPrefabName);
                }
                if (tryFindPrototype == null) {
                    return false;
                }
                var newGo = UnityEngine.Object.Instantiate(tryFindPrototype.gameObject, null, true);
                var newEntity = newGo.GetComponent<BaseEntity>();
                //we know entity won't be null because TryGetPreprocessed looks for an entity in first place
                if (recipe.ModificationFunction != null) {
                    if (!recipe.ModificationFunction(newEntity)) {
                        return false;
                    }
                }
                //and again - because new entity could've been replaced!
                newEntity = newGo.GetComponent<BaseEntity>();
                AddToGameManifest(recipe.FullPrefabName, newGo, recipe.EnableInSpawnCommand);
                AddToPreprocessed(recipe.FullPrefabName, newGo);
                if (recipe.BaseCombat != null) {
                    ApplyBaseCombatEntityProperties((BaseCombatEntity)newEntity, recipe.ShortPrefabName, recipe.BaseCombat);
                }
                var modifiedPrefabStringPoolID = AddToStringPool(recipe.FullPrefabName);
                //MUST DISABLE VANILLA SAVING BECAUSE NOW IT HAS A NON-EXISTING PREFAB ID!
                //in this case, it will help identify the modified prefab in the dictionary, given its full prefab name.
                newEntity.prefabID = modifiedPrefabStringPoolID;
                newEntity._prefabName = recipe.FullPrefabName;
                ICustomEntity asInterface = (newEntity as ICustomEntity);
                if (asInterface != null) {
                    //I think this is what we were missing!
                    asInterface.SaveListInDataFile = data.CustomEntitySaveList;
                }
                if (recipe.SaveHandling == ModifiedSaveHandling.SaveInVanillaSaveList) {
                    newEntity.EnableSaving(true);
                }
                else {
                    newEntity.EnableSaving(false);
                    if (recipe.SaveHandling == ModifiedSaveHandling.SaveInBundleSaveList) {
                        ModifiedPrefabFullNameToModifiedPrefabIDHandledByBundles.Add(recipe.FullPrefabName, modifiedPrefabStringPoolID);
                        ModifiedPrefabFullNameToCustomSaveList.Add(recipe.FullPrefabName, data.CustomEntitySaveList);
                        //add the compo only if needed
                        var newCompo = newGo.AddComponent<ModifiedBundleSaveBehaviour>();
                        newCompo.RecipeFullPrefabName = recipe.FullPrefabName;
                        newCompo.AddToCustomSaveList = true;
                    }
                }
                if (asInterface != null) {
                    asInterface.OnCustomPrefabPrototypeEntityRegistered();
                }
                newGo.SetActive(false);
                UnityEngine.Object.DontDestroyOnLoad(newGo);
                if (!_cachedRecipes.Contains(recipe)) {
                    _cachedRecipes.Add(recipe);
                }
                return true;
            }
            private static bool RegisterPrefabCustom(CustomPrefabRecipe recipe, BinaryData data) {
                GameObject newGo = new GameObject(recipe.ShortPrefabName) {
                    layer = (int)recipe.Layer
                }
                ;
                BaseEntity newEntity = newGo.AddComponent(recipe.EntityType) as BaseEntity;
                AddToGameManifest(recipe.FullPrefabName, newGo, recipe.EnableInSpawnCommand);
                AddToPreprocessed(recipe.FullPrefabName, newGo);
                if (recipe.BaseCombat != null) {
                    ApplyBaseCombatEntityProperties((BaseCombatEntity)newEntity, recipe.ShortPrefabName, recipe.BaseCombat);
                }
                newEntity.impactEffect = _emptyImpactGameObjectRef;
                newEntity.prefabID = AddToStringPool(recipe.FullPrefabName);
                //naughty reflection
                newEntity._prefabName = recipe.FullPrefabName;
// ... [Code preview truncated due to length. Full source in attached file] ...

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