private void Log(string text, LogLevel level)
 {
     if (Configuration?.Current.LogLevel <= level)
     {
         ModApi.Log(text);
     }
 }
Example #2
0
        private void Application_OnPlayfieldUnloading(IPlayfield playfield)
        {
            ScriptingModScriptsInfoData.TryRemove(playfield.Name, out _);

            if (!PlayfieldData.TryRemove(playfield.Name, out var data))
            {
                return;
            }

            data.Playfield.OnEntityLoaded   -= data.Playfield_OnEntityLoaded;
            data.Playfield.OnEntityUnloaded -= data.Playfield_OnEntityUnloaded;

            ModApi.Log($"PauseScripts for {playfield.Name} {(data.PauseScripts ? "always stopped" : "scripts running")}");
            data.PauseScripts = true;

            DisplayScriptInfos();
            data.ScriptExecQueue?.Clear();
            data.LcdCompileCache?.Clear();
            data.PersistendData?.Clear();

            var stores = data.EventStore?.Values.ToArray();

            data.EventStore?.Clear();
            stores?.ForEach(S => ((EventStore)S).Dispose());
        }
Example #3
0
        private void Application_OnPlayfieldLoaded(IPlayfield playfield)
        {
            PlayfieldScriptData data = null;

            InitGameDependedData(ModApi.Application.Mode == ApplicationMode.SinglePlayer);

            PlayfieldData.TryAdd(playfield.Name, data = new PlayfieldScriptData(this)
            {
                PlayfieldName = playfield.Name,
                Playfield     = playfield,
            });

            UpdateScriptingModInfoData();

            ModApi.Log($"StartScripts for {playfield.Name} pending");
            TaskTools.Delay(Configuration.Current.DelayStartForNSecondsOnPlayfieldLoad, () => {
                ModApi.Log($"StartScripts for {playfield.Name}");
                data.PauseScripts = false;

                if (ModApi.Application.Mode == ApplicationMode.SinglePlayer)
                {
                    ModApi.Log(playfield.Entities?.Aggregate($"Player:{playfield.Players.FirstOrDefault().Value?.Name} PlayerDriving:{playfield.Players.FirstOrDefault().Value?.DrivingEntity?.Name}", (L, E) => L + $" {E.Key}:{E.Value.Name}"));

                    data.AddEntity(playfield.Players.FirstOrDefault().Value?.DrivingEntity);
                    playfield.Entities?.ForEach(E => data.AddEntity(E.Value));
                }
            });

            data.Playfield.OnEntityLoaded   += data.Playfield_OnEntityLoaded;
            data.Playfield.OnEntityUnloaded += data.Playfield_OnEntityUnloaded;
        }
        private void OnGameLoopGameLaunched(object s, EventArgs e)
        {
            this.Helper.Events.GameLoop.GameLaunched -= this.OnGameLoopGameLaunched;
            try
            {
                this.Monitor.StartTimer("TypeId", "Initializing TypeId...");
                // Register typeId's, both for vanilla and any mods that we explicitly support (if loaded)
                var euApi = EntoUtilsApi.Instance;
                Data.Init();
                this.Monitor.Log("Initializing SDV TypeId support...");
                euApi.RegisterItemTypeHandler("sdv.object", TypeHandlers.StardewObject);
                euApi.RegisterItemTypeHandler("sdv.craftable", TypeHandlers.StardewCraftable);
                euApi.RegisterItemTypeHandler("sdv.boots", TypeHandlers.StardewBoots);
                euApi.RegisterItemTypeHandler("sdv.furniture", TypeHandlers.StardewFurniture);
                euApi.RegisterItemTypeHandler("sdv.hat", TypeHandlers.StardewHat);
                euApi.RegisterItemTypeHandler("sdv.ring", TypeHandlers.StardewRing);
                euApi.RegisterItemTypeHandler("sdv.floor", TypeHandlers.StardewFloor);
                euApi.RegisterItemTypeHandler("sdv.wallpaper", TypeHandlers.StardewWallpaper);
                euApi.RegisterItemTypeHandler("sdv.weapon", TypeHandlers.StardewWeapon);
                euApi.RegisterItemTypeHandler("sdv.clothing", TypeHandlers.StardewClothing);
                euApi.RegisterItemTypeHandler("sdv.tool", TypeHandlers.StardewTool);
                euApi.RegisterItemTypeHandler("sdv.artisan", TypeHandlers.StardewArtisan);
                euApi.RegisterItemTypeResolver(TypeResolvers.StardewResolver);

                /**
                 * Build-in compatibility for mods with public API's that the TypeId registry can hook into
                 * Mods which do not provide a public API will need to provide TypeId support from their end
                 */
                if (ModApi.IsLoadedAndApiAvailable <IJsonAssetsApi>("spacechase0.JsonAssets", out var jaApi))
                {
                    this.Monitor.Log("JsonAssets detected, initializing TypeId support...");
                    Data.JaApi = jaApi;
                    euApi.RegisterItemTypeHandler("ja.object", TypeHandlers.JsonAssetsObject);
                    euApi.RegisterItemTypeHandler("ja.craftable", TypeHandlers.JsonAssetsCraftable);
                    euApi.RegisterItemTypeHandler("ja.hat", TypeHandlers.JsonAssetsHat);
                    euApi.RegisterItemTypeHandler("ja.weapon", TypeHandlers.JsonAssetsWeapon);
                    euApi.RegisterItemTypeHandler("ja.clothing", TypeHandlers.JsonAssetsClothing);
                    euApi.RegisterItemTypeResolver(TypeResolvers.JsonAssetsResolver);
                }
                if (ModApi.IsLoadedAndApiAvailable <ICustomFarmingApi>("Platonymous.CustomFarming", out var cfApi))
                {
                    this.Monitor.Log("CustomFarmingRedux detected, initializing TypeId support...");
                    Data.CfApi = cfApi;
                    euApi.RegisterItemTypeHandler("cf.machine", TypeHandlers.CustomFarmingMachine);
                }
                if (ModApi.IsLoadedAndApiAvailable <IPrismaticToolsApi>("stokastic.PrismaticTools", out var ptApi))
                {
                    this.Monitor.Log("PrismaticTools detected, initializing TypeId support...");
                    Data.PtApi = ptApi;
                    euApi.RegisterItemTypeHandler("pt.item", TypeHandlers.PrismaticToolsItem);
                    euApi.RegisterItemTypeResolver(TypeResolvers.PrismaticToolsResolver);
                }
                this.Monitor.StopTimer("TypeId", "TypeId initialization took {{TIME}} milliseconds.");
            }
            catch (Exception err)
            {
                this.Monitor.Log("TypeId failed to load due to a exception being thrown: \n" + err, LogLevel.Error);
            }
            this.OnLocationListChanged(null, null);
        }
 public static void RestartAllScriptsForPlayfieldServer()
 {
     StopScriptsEvent?.Invoke(EmpyrionScriptingInstance, EventArgs.Empty);
     EmpyrionScriptingInstance.PauseScripts = false;
     Configuration.Load();
     ModApi?.Log($"EmpyrionScripting Mod.Restart Threads: {EmpyrionScriptingInstance.LastAlive} <-> {DateTime.Now} : {Configuration.Current.LogLevel}");
     EmpyrionScriptingInstance.StartAllScriptsForPlayfieldServer();
 }
 private void Application_OnPlayfieldLoaded(string playfieldName)
 {
     ModApi.Log($"StartScripts for {playfieldName} pending");
     TaskTools.Delay(Configuration.Current.DelayStartForNSecondsOnPlayfieldLoad, () => {
         ModApi.Log($"StartScripts for {playfieldName}");
         PauseScripts = false;
     });
 }
Example #7
0
 private void Application_GameEntered(bool hasEntered)
 {
     ModApi.Log($"Application_GameEntered {hasEntered}");
     if (hasEntered)
     {
         InitGameDependedData(false);
     }
     ModApi.Log("Application_GameEntered init finish");
 }
        /// <summary>Sets up initial values needed for Animal Skinner.</summary>
        private void Setup(object sender, SaveLoadedEventArgs e)
        {
            ModApi.RegisterDefaultTypes();
            IntegrateMods();

            LoadAssets();

            // Remove the Setup from the loop, so that it isn't done twice when the player returns to the title screen and loads again
            this.Helper.Events.GameLoop.SaveLoaded -= this.Setup;
        }
        private void Application_OnPlayfieldUnloaded(string playfieldName)
        {
            ModApi.Log($"PauseScripts for {playfieldName} {(PauseScripts ? "always stopped" : "scripts running")}");
            PauseScripts = true;

            DisplayScriptInfos();
            ScriptExecQueue.Clear();
            LcdCompileCache.Clear();
            PersistendData.Clear();
        }
Example #10
0
 protected override async Task <ApiModInfo?> ResolveCacheMiss(string key)
 {
     try
     {
         return(await ModApi.RequestModInfoAsync(key));
     }
     catch (ResourceNotFoundException)
     {
         return(null);
     }
 }
Example #11
0
        private void CsCompiler_ConfigurationChanged(object sender, EventArgs e)
        {
            PlayfieldData.ForEach(P =>
            {
                ModApi.Log($"CsCompiler_ConfigurationChanged: {P.Key} {(P.Value == null ? "null" : (P.Value.PauseScripts ? "always stopped" : "scripts running"))}");

                DisplayScriptInfos();
                P.Value?.ScriptExecQueue?.Clear();
                P.Value?.LcdCompileCache?.Clear();
            });
        }
Example #12
0
        public void StartAllScriptsForPlayfieldServer()
        {
            ModApi.Log($"StartAllScriptsForPlayfieldServer: InGame:{Configuration.Current.InGameScriptsIntervallMS}ms SaveGame:{Configuration.Current.SaveGameScriptsIntervallMS}ms ");

            StartScriptIntervall(Configuration.Current.InGameScriptsIntervallMS, () =>
            {
                PlayfieldData.Values.ForEach(PF => {
                    Log($"InGameScript: {PF.PauseScripts}", LogLevel.Debug);
                    LastAlive = DateTime.Now;
                    if (PF.PauseScripts)
                    {
                        return;
                    }

                    InGameScriptsCount = UpdateScripts(PF, ProcessAllInGameScripts, "InGameScript");
                    PF.ScriptExecQueue.ScriptsCount = InGameScriptsCount + SaveGameScriptsCount;
                });
            }, "InGameScript");

            StartScriptIntervall(Configuration.Current.SaveGameScriptsIntervallMS, () =>
            {
                PlayfieldData.Values.ForEach(PF => {
                    Log($"SaveGameScript: {PF.PauseScripts}", LogLevel.Debug);
                    LastAlive = DateTime.Now;
                    if (PF.PauseScripts)
                    {
                        return;
                    }

                    SaveGameScriptsCount            = UpdateScripts(PF, ProcessAllSaveGameScripts, "SaveGameScript");
                    PF.ScriptExecQueue.ScriptsCount = InGameScriptsCount + SaveGameScriptsCount;
                });
            }, "SaveGameScript");

            StartScriptIntervall(60000, () =>
            {
                PlayfieldData.Values.ForEach(PF => {
                    Log($"ScriptInfos: Pause:{PF.PauseScripts} {PF.ScriptExecQueue.ScriptRunInfo.Count} ExecQueue:{PF.ScriptExecQueue.ExecQueue.Count} WaitForExec:{PF.ScriptExecQueue.WaitForExec.Count}", LogLevel.Debug);
                    LastAlive = DateTime.Now;
                    if (PF.PauseScripts || Configuration.Current.LogLevel > LogLevel.Message)
                    {
                        return;
                    }

                    DisplayScriptInfos();
                    PF.ScriptExecQueue.CheckForEmergencyRestart(PF);
                });
            }, "ScriptInfos");
        }
Example #13
0
        private int UpdateScripts(PlayfieldScriptData playfieldData, Func <PlayfieldScriptData, IEntity, int> process, string name)
        {
            try
            {
                if (playfieldData.Playfield == null)
                {
                    ModApi.Log($"UpdateScripts no Playfield"); return(0);
                }
                if (playfieldData.Playfield.Entities == null)
                {
                    ModApi.Log($"UpdateScripts no Entities"); return(0);
                }

                var timer = new Stopwatch();
                timer.Start();

                playfieldData.AllEntities     = playfieldData.Playfield.Entities.Values.ToArray();
                playfieldData.CurrentEntities = playfieldData.AllEntities
                                                .Where(E => E.Type == EntityType.BA ||
                                                       E.Type == EntityType.CV ||
                                                       E.Type == EntityType.SV ||
                                                       E.Type == EntityType.HV)
                                                .ToArray();

                Log($"CurrentEntities: {playfieldData.CurrentEntities.Length}", LogLevel.Debug);
                playfieldData.EntityCultureInfo.Clear();

                int count = 0;
                playfieldData.CurrentEntities.ForEach(E => count += process(playfieldData, E));

                timer.Stop();
                if (timer.Elapsed.TotalSeconds > 30)
                {
                    Log($"UpdateScripts: {name} RUNS {timer.Elapsed} !!!!", LogLevel.Message);
                }
                else
                {
                    Log($"UpdateScripts: {name} take {timer.Elapsed}", LogLevel.Debug);
                }

                return(count);
            }
            catch (Exception error)
            {
                ModApi.LogWarning("Next try because: " + ErrorFilter(error));

                return(0);
            }
        }
Example #14
0
        public static void Log(string text, LogLevel level)
        {
            if (Configuration?.Current.LogLevel <= level)
            {
                switch (level)
                {
                case LogLevel.Debug: ModApi?.Log(text);        break;

                case LogLevel.Message: ModApi?.Log(text);        break;

                case LogLevel.Error: ModApi?.LogError(text);   break;

                default: ModApi?.Log(text);        break;
                }
            }
        }
        public void StartAllScriptsForPlayfieldServer()
        {
            ModApi.Log($"StartAllScriptsForPlayfieldServer: InGame:{Configuration.Current.InGameScriptsIntervallMS}ms SaveGame:{Configuration.Current.SaveGameScriptsIntervallMS}ms ");

            StartScriptIntervall(Configuration.Current.InGameScriptsIntervallMS, () =>
            {
                Log($"InGameScript: {PauseScripts} #{CycleCounter}", LogLevel.Debug);
                LastAlive = DateTime.Now;
                if (PauseScripts)
                {
                    return;
                }

                Interlocked.Increment(ref CycleCounter);
                InGameScriptsCount           = UpdateScripts(ProcessAllInGameScripts, "InGameScript");
                ScriptExecQueue.ScriptsCount = InGameScriptsCount + SaveGameScriptsCount;
            }, "InGameScript");

            StartScriptIntervall(Configuration.Current.SaveGameScriptsIntervallMS, () =>
            {
                Log($"SaveGameScript: {PauseScripts}", LogLevel.Debug);
                LastAlive = DateTime.Now;
                if (PauseScripts)
                {
                    return;
                }

                SaveGameScriptsCount         = UpdateScripts(ProcessAllSaveGameScripts, "SaveGameScript");
                ScriptExecQueue.ScriptsCount = InGameScriptsCount + SaveGameScriptsCount;
            }, "SaveGameScript");

            StartScriptIntervall(60000, () =>
            {
                Log($"ScriptInfos: {ScriptExecQueue.ScriptRunInfo.Count} ExecQueue:{ScriptExecQueue.ExecQueue.Count} WaitForExec:{ScriptExecQueue.WaitForExec.Count}", LogLevel.Debug);
                LastAlive = DateTime.Now;
                if (PauseScripts || Configuration.Current.LogLevel > LogLevel.Message)
                {
                    return;
                }

                DisplayScriptInfos();
            }, "ScriptInfos");
        }
Example #16
0
        private static void InitGameDependedData(bool forceInit)
        {
            if (forceInit || !AppFoldersLogged)
            {
                AppFoldersLogged = true;
                ModApi.Log($"InitGameDependedData [ForceInit:{forceInit}]:\n" +
                           $"AppFolder.Content:{ModApi.Application?.GetPathFor(AppFolder.Content)}\n" +
                           $"AppFolder.Mod:{ModApi.Application?.GetPathFor(AppFolder.Mod)}\n" +
                           $"AppFolder.Root:{ModApi.Application?.GetPathFor(AppFolder.Root)}\n" +
                           $"AppFolder.SaveGame:{ModApi.Application?.GetPathFor(AppFolder.SaveGame)}\n" +
                           $"AppFolder.Dedicated:{ModApi.Application?.GetPathFor(AppFolder.Dedicated)}\n" +
                           $"AppFolder.Cache:{ModApi.Application?.GetPathFor(AppFolder.Cache)}\n" +
                           $"AppFolder.ActiveScenario:{ModApi.Application?.GetPathFor(AppFolder.ActiveScenario)} -> CurrentScenario:{CurrentScenario}");
            }

            if (forceInit || Localization == null)
            {
                Localization = new Localization(ModApi.Application?.GetPathFor(AppFolder.Content), CurrentScenario);
            }
            if (forceInit || ConfigEcfAccess == null)
            {
                InitEcfConfigData();
            }
        }
        /// <summary>Load skin assets into AnimalSkinner</summary>
        private void LoadAssets()
        {
            // Gather handled types
            string validTypes = string.Join(", ", ModApi.GetHandledAllTypes());

            foreach (FileInfo file in new DirectoryInfo(Path.Combine(this.Helper.DirectoryPath, "assets", "skins")).EnumerateFiles())
            {
                // Check extension of file is handled by AnimalSkinner
                string extension = Path.GetExtension(file.Name);
                if (!this.ValidExtensions.Contains(extension))
                {
                    this.Monitor.Log($"Ignored skin `assets/skins/{file.Name}` with invalid extension (extension must be one of type {string.Join(", ", this.ValidExtensions)})", LogLevel.Warn);
                    continue;
                }

                // Parse file name
                string[] nameParts = Path.GetFileNameWithoutExtension(file.Name).Split(new[] { '_' }, 2);
                string   type      = Sanitize(nameParts[0]);
                // Ensure creature type is handled by AnimalSkinner
                if (!PetAssets.ContainsKey(type) && !HorseAssets.ContainsKey(type) && !AnimalAssets.ContainsKey(type))
                {
                    this.Monitor.Log($"Ignored skin `assets/skins/{file.Name}` with invalid naming convention (can't parse {nameParts[0]} as an animal, pet, or horse. Expected one of type: {validTypes})", LogLevel.Warn);
                    continue;
                }
                // Ensure both a type and skin ID can be found in the file name
                if (nameParts.Length != 2)
                {
                    this.Monitor.Log($"Ignored skin `assets/skins/{file.Name} with invalid naming convention (no skin ID found)", LogLevel.Warn);
                    continue;
                }
                // Ensure the skin ID is a number
                int skinID = 0;
                if (nameParts.Length == 2 && !int.TryParse(nameParts[1], out skinID))
                {
                    this.Monitor.Log($"Ignored skin `assets/skins/{file.Name}` with invalid skin ID (can't parse {nameParts[1]} as a number)", LogLevel.Warn);
                    continue;
                }
                // Ensure the skin ID is not 0 or negative
                if (skinID <= 0)
                {
                    this.Monitor.Log($"Ignored skin `assets/skins/{file.Name}` with skin ID of less than or equal to 0. Skins must have an ID of at least 1.");
                }

                // File naming is valid, add asset into system
                string assetKey = this.Helper.Content.GetActualAssetKey(Path.Combine("assets", "skins", extension.Equals("xnb") ? Path.Combine(Path.GetDirectoryName(file.Name), Path.GetFileNameWithoutExtension(file.Name)) : file.Name));
                if (AnimalAssets.ContainsKey(type))
                {
                    AnimalAssets[type].Add(new AnimalSkin(type, skinID, assetKey));
                }
                else if (HorseAssets.ContainsKey(type))
                {
                    HorseAssets[type].Add(new AnimalSkin(type, skinID, assetKey));
                }
                else
                {
                    PetAssets[type].Add(new AnimalSkin(type, skinID, assetKey));
                }
            }


            // Sort each list
            AnimalSkin.Comparer comp = new AnimalSkin.Comparer();
            foreach (string type in AnimalAssets.Keys)
            {
                AnimalAssets[type].Sort((p1, p2) => comp.Compare(p1, p2));
            }
            foreach (string type in PetAssets.Keys)
            {
                PetAssets[type].Sort((p1, p2) => comp.Compare(p1, p2));
            }
            foreach (string type in HorseAssets.Keys)
            {
                HorseAssets[type].Sort((p1, p2) => comp.Compare(p1, p2));
            }


            // Print loaded assets to console
            StringBuilder summary = new StringBuilder();

            summary.AppendLine(
                "Statistics:\n"
                + "\n  Registered types: " + validTypes
                + "\n  Animal Skins:"
                );
            foreach (KeyValuePair <string, List <AnimalSkin> > pair in ModEntry.AnimalAssets)
            {
                if (pair.Value.Count > 0)
                {
                    summary.AppendLine($"    {pair.Key}: {pair.Value.Count} skins ({string.Join(", ", pair.Value.Select(p => Path.GetFileName(p.AssetKey)).OrderBy(p => p))})");
                }
            }
            summary.AppendLine("  Pet Skins:");
            foreach (KeyValuePair <string, List <AnimalSkin> > pair in ModEntry.PetAssets)
            {
                if (pair.Value.Count > 0)
                {
                    summary.AppendLine($"    {pair.Key}: {pair.Value.Count} skins ({string.Join(", ", pair.Value.Select(p => Path.GetFileName(p.AssetKey)).OrderBy(p => p))})");
                }
            }
            summary.AppendLine("  Horse Skins:");
            foreach (KeyValuePair <string, List <AnimalSkin> > pair in ModEntry.HorseAssets)
            {
                if (pair.Value.Count > 0)
                {
                    summary.AppendLine($"    {pair.Key}: {pair.Value.Count} skins ({string.Join(", ", pair.Value.Select(p => Path.GetFileName(p.AssetKey)).OrderBy(p => p))})");
                }
            }
            this.Monitor.Log(summary.ToString(), LogLevel.Trace);
        }
Example #18
0
    public Support()
    {
        var a = new ModApi();

        a.Thing();
    }
Example #19
0
    public MyMod()
    {
        var a = new ModApi();

        a.DoIt();
    }
 public void Dispose()
 {
     ModApi?.Log("EmpyrionScripting Mod: Dispose");
 }
Example #21
0
 protected override Task <ApiModInfo> ResolveCacheMiss(string key)
 => ModApi.RequestModInfoAsync(key);
 public void Game_Exit()
 {
     ModApi.Log("Mod exited:Game_Exit");
     StopScriptsEvent?.Invoke(this, EventArgs.Empty);
 }
        private int ProcessAllInGameScripts(IEntity entity)
        {
            Log($"ProcessAllInGameScripts: {entity.Name}:{entity.Type} Pause:{PauseScripts}", LogLevel.Debug);
            if (entity.Type == EntityType.Proxy || PauseScripts)
            {
                return(0);
            }

            try
            {
                var entityScriptData = new ScriptRootData(CurrentEntities, ModApi.Playfield, entity, DeviceLockAllowed, PersistendData);

                var deviceNames = entityScriptData.E.S.AllCustomDeviceNames.Where(N => N.StartsWith(ScriptKeyword)).ToArray();
                Log($"ProcessAllInGameScripts: #{deviceNames.Length}", LogLevel.Debug);

                int count = 0;
                Parallel.ForEach(deviceNames, N =>
                {
                    if (PauseScripts)
                    {
                        return;
                    }

                    var lcd = entity.Structure.GetDevice <ILcd>(N);
                    if (lcd == null)
                    {
                        return;
                    }

                    try
                    {
                        Log($"ProcessAllInGameScripts: {N}", LogLevel.Debug);

                        var data = new ScriptRootData(entityScriptData)
                        {
                            Script = lcd.GetText(),
                            Error  = L,
                        };

                        AddTargetsAndDisplayType(data, N.Substring(ScriptKeyword.Length));

                        if (Configuration.Current.ScriptTracking)
                        {
                            var trackfile = GetTrackingFileName(entity, data.Script.GetHashCode().ToString());
                            if (!File.Exists(trackfile))
                            {
                                File.WriteAllText(trackfile, data.Script);
                            }
                        }


                        data.ScriptId = entity.Id + "/" + N;
                        ScriptExecQueue.Add(data);

                        Interlocked.Increment(ref count);
                    }
                    catch (Exception lcdError)
                    {
                        if (Configuration.Current.LogLevel >= EmpyrionNetAPIDefinitions.LogLevel.Debug)
                        {
                            ModApi.Log($"UpdateLCDs ({entity.Id}/{entity.Name}):LCD: {lcdError}");
                        }
                    }
                });

                return(count);
            }
            catch (Exception error)
            {
                File.WriteAllText(GetTrackingFileName(entity, string.Empty) + ".error", error.ToString());
                return(0);
            }
        }
        /// <summary>
        /// Given the ID for an animal, pet, or horse, and that creature's type, return the AnimalSkin mapped to that creature.
        /// Return null if the creature type isn't handled.
        /// </summary>
        internal AnimalSkin GetSkin(Character creature)
        {
            switch (creature)
            {
            case Horse horse:
                // No horse skins are loaded
                if (HorseAssets[Sanitize(horse.GetType().Name)].Count == 0)
                {
                    return(null);
                }

                // A wild horse is being checked
                if (horse.Manners == WildHorse.WildID)
                {
                    this.Monitor.Log($"Wild ID: {Creator.HorseInfo.SkinID}", LogLevel.Info);
                    return(GetSkinFromID(horse.GetType().Name, Creator.HorseInfo.SkinID));
                }
                // Horse is not in system
                else if (horse.Manners == 0 || !HorseSkinMap.ContainsKey(horse.Manners))
                {
                    this.Monitor.Log($"Horse not in system: {horse.Name}", LogLevel.Error);
                    return(null);
                }

                // Ensure skin ID given is a valid number for the given horse type
                int horseSkinID = HorseSkinMap[horse.Manners];
                if (horseSkinID < 1 || horseSkinID > HorseAssets[Sanitize(horse.GetType().Name)].Count)
                {
                    this.Monitor.Log($"{horse.Name}'s skin ID no longer exists in `/assets/skins`. Skin will be randomized.", LogLevel.Alert);
                    horseSkinID = SetRandomSkin(horse);
                }

                // Horse has a skin. Return it.
                return(GetSkinFromID(horse.GetType().Name, horseSkinID));

            case Pet pet:
                string petType = Sanitize(pet.GetType().Name);

                // Break out of unhandled types
                if (!ModApi.GetHandledPetTypes().Contains(petType))
                {
                    break;
                }
                else if (PetAssets[Sanitize(pet.GetType().Name)].Count == 0)
                {
                    return(null);
                }

                // Pet is not in system
                if (pet.Manners == 0 || !PetSkinMap.ContainsKey(pet.Manners))
                {
                    this.Monitor.Log($"Pet not in system: {pet.Name}", LogLevel.Error);
                    return(null);
                }

                // Ensure skin ID given is a current valid number for the given pet type
                int petSkinID = PetSkinMap[pet.Manners];
                if (petSkinID < 1 || petSkinID > PetAssets[petType].Count)
                {
                    this.Monitor.Log($"{pet.Name}'s skin ID no longer exists in `/assets/skins`. Skin will be randomized.", LogLevel.Alert);
                    petSkinID = SetRandomSkin(pet);
                }
                return(GetSkinFromID(petType, petSkinID));

            case FarmAnimal animal:
                string animalType = Sanitize(animal.type.Value);

                // Break out of unhandled types
                if (!ModApi.GetHandledAnimalTypes().Contains(animalType))
                {
                    break;
                }
                else if (AnimalAssets[Sanitize(animal.type.Value)].Count == 0)
                {
                    return(null);
                }

                // Set sub-type if applicable
                if (ModApi.HasBabySprite(animalType) && animal.age.Value < animal.ageWhenMature.Value)
                {
                    animalType = "baby" + animalType;
                }
                else if (ModApi.HasShearedSprite(animalType) && animal.currentProduce.Value <= 0)
                {
                    animalType = "sheared" + animalType;
                }

                // Animal is not in system
                if (!AnimalSkinMap.ContainsKey(animal.myID.Value))
                {
                    return(null);
                }

                // Ensure skin ID given is a current valid number for the given animal type
                int animalSkinID = AnimalSkinMap[animal.myID.Value];
                if (animalSkinID < 1 || animalSkinID > AnimalAssets[animalType].Count)
                {
                    this.Monitor.Log($"{animal.Name}'s skin ID is no longer exists in `/assets/skins`. Skin will be randomized.", LogLevel.Alert);
                    animalSkinID = SetRandomSkin(animal);
                }
                return(GetSkinFromID(animalType, animalSkinID));

            default:
                break;
            }
            return(null);
        }
 public void Shutdown()
 {
     ModApi.Log("Mod exited:Shutdown");
     StopScriptsEvent.Invoke(this, EventArgs.Empty);
 }