From 94511af1d663be026fde8fa57c2d031403128d36 Mon Sep 17 00:00:00 2001 From: Katy Fox Date: Tue, 27 Jun 2023 23:28:05 -0400 Subject: [PATCH 1/4] initial sim revisions --- SkinManagerMod/CommsRadioSkinSwitcher.cs | 9 +- .../ConfigurationManagerAttributes.cs | 149 +++++++++ SkinManagerMod/Main.cs | 158 ++++++---- SkinManagerMod/Remaps.cs | 59 ++++ SkinManagerMod/SaveLoadPatches.cs | 6 +- SkinManagerMod/SkinManager.cs | 291 ++++++------------ SkinManagerMod/SkinManagerMod.csproj | 14 +- SkinManagerMod/Skins.cs | 7 +- SkinManagerMod/TextureUtility.cs | 155 +++++++--- 9 files changed, 528 insertions(+), 320 deletions(-) create mode 100644 SkinManagerMod/ConfigurationManagerAttributes.cs create mode 100644 SkinManagerMod/Remaps.cs diff --git a/SkinManagerMod/CommsRadioSkinSwitcher.cs b/SkinManagerMod/CommsRadioSkinSwitcher.cs index e9957e3..eb0fa09 100644 --- a/SkinManagerMod/CommsRadioSkinSwitcher.cs +++ b/SkinManagerMod/CommsRadioSkinSwitcher.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using DV; +using DV.ThingTypes; using HarmonyLib; using UnityEngine; @@ -187,7 +188,7 @@ private void SetState( State newState ) break; case State.SelectSkin: - UpdateAvailableSkinsList(SelectedCar.carType); + UpdateAvailableSkinsList(SelectedCar.carLivery); SetSelectedSkin(SkinsForCarType?.FirstOrDefault()); ButtonBehaviour = ButtonBehaviourType.Override; @@ -332,7 +333,7 @@ public bool ButtonBCustomAction() #region Skin Shenanigans - private void UpdateAvailableSkinsList( TrainCarType carType ) + private void UpdateAvailableSkinsList(TrainCarLivery carType) { SkinsForCarType = SkinManager.GetSkinsForType(carType); SelectedSkinIdx = 0; @@ -350,10 +351,10 @@ private void ApplySelectedSkin() if( CarTypes.IsSteamLocomotive(SelectedCar.carType) && SelectedCar.rearCoupler.IsCoupled() ) { TrainCar attachedCar = SelectedCar.rearCoupler.coupledTo?.train; - if( (attachedCar != null) && CarTypes.IsTender(attachedCar.carType) ) + if( (attachedCar != null) && CarTypes.IsTender(attachedCar.carLivery) ) { // car attached behind loco is tender - Skin tenderSkin = SkinManager.FindSkinByName(attachedCar.carType, SelectedSkin.Name); + Skin tenderSkin = SkinManager.FindSkinByName(attachedCar.carLivery, SelectedSkin.Name); if (tenderSkin != null) { // found a matching skin for the tender :D diff --git a/SkinManagerMod/ConfigurationManagerAttributes.cs b/SkinManagerMod/ConfigurationManagerAttributes.cs new file mode 100644 index 0000000..7014c00 --- /dev/null +++ b/SkinManagerMod/ConfigurationManagerAttributes.cs @@ -0,0 +1,149 @@ +/// +/// Class that specifies how a setting should be displayed inside the ConfigurationManager settings window. +/// +/// Usage: +/// This class template has to be copied inside the plugin's project and referenced by its code directly. +/// make a new instance, assign any fields that you want to override, and pass it as a tag for your setting. +/// +/// If a field is null (default), it will be ignored and won't change how the setting is displayed. +/// If a field is non-null (you assigned a value to it), it will override default behavior. +/// +/// +/// +/// Here's an example of overriding order of settings and marking one of the settings as advanced: +/// +/// // Override IsAdvanced and Order +/// Config.Bind("X", "1", 1, new ConfigDescription("", null, new ConfigurationManagerAttributes { IsAdvanced = true, Order = 3 })); +/// // Override only Order, IsAdvanced stays as the default value assigned by ConfigManager +/// Config.Bind("X", "2", 2, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 1 })); +/// Config.Bind("X", "3", 3, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 2 })); +/// +/// +/// +/// +/// You can read more and see examples in the readme at https://github.com/BepInEx/BepInEx.ConfigurationManager +/// You can optionally remove fields that you won't use from this class, it's the same as leaving them null. +/// +#pragma warning disable 0169, 0414, 0649 +internal sealed class ConfigurationManagerAttributes +{ + /// + /// Should the setting be shown as a percentage (only use with value range settings). + /// + public bool? ShowRangeAsPercent; + + /// + /// Custom setting editor (OnGUI code that replaces the default editor provided by ConfigurationManager). + /// See below for a deeper explanation. Using a custom drawer will cause many of the other fields to do nothing. + /// + public System.Action CustomDrawer; + + /// + /// Custom setting editor that allows polling keyboard input with the Input (or UnityInput) class. + /// Use either CustomDrawer or CustomHotkeyDrawer, using both at the same time leads to undefined behaviour. + /// + public CustomHotkeyDrawerFunc CustomHotkeyDrawer; + + /// + /// Custom setting draw action that allows polling keyboard input with the Input class. + /// Note: Make sure to focus on your UI control when you are accepting input so user doesn't type in the search box or in another setting (best to do this on every frame). + /// If you don't draw any selectable UI controls You can use `GUIUtility.keyboardControl = -1;` on every frame to make sure that nothing is selected. + /// + /// + /// CustomHotkeyDrawer = (ConfigEntryBase setting, ref bool isEditing) => + /// { + /// if (isEditing) + /// { + /// // Make sure nothing else is selected since we aren't focusing on a text box with GUI.FocusControl. + /// GUIUtility.keyboardControl = -1; + /// + /// // Use Input.GetKeyDown and others here, remember to set isEditing to false after you're done! + /// // It's best to check Input.anyKeyDown and set isEditing to false immediately if it's true, + /// // so that the input doesn't have a chance to propagate to the game itself. + /// + /// if (GUILayout.Button("Stop")) + /// isEditing = false; + /// } + /// else + /// { + /// if (GUILayout.Button("Start")) + /// isEditing = true; + /// } + /// + /// // This will only be true when isEditing is true and you hold any key + /// GUILayout.Label("Any key pressed: " + Input.anyKey); + /// } + /// + /// + /// Setting currently being set (if available). + /// + /// + /// Set this ref parameter to true when you want the current setting drawer to receive Input events. + /// The value will persist after being set, use it to see if the current instance is being edited. + /// Remember to set it to false after you are done! + /// + public delegate void CustomHotkeyDrawerFunc(BepInEx.Configuration.ConfigEntryBase setting, ref bool isCurrentlyAcceptingInput); + + /// + /// Show this setting in the settings screen at all? If false, don't show. + /// + public bool? Browsable; + + /// + /// Category the setting is under. Null to be directly under the plugin. + /// + public string Category; + + /// + /// If set, a "Default" button will be shown next to the setting to allow resetting to default. + /// + public object DefaultValue; + + /// + /// Force the "Reset" button to not be displayed, even if a valid DefaultValue is available. + /// + public bool? HideDefaultButton; + + /// + /// Force the setting name to not be displayed. Should only be used with a to get more space. + /// Can be used together with to gain even more space. + /// + public bool? HideSettingName; + + /// + /// Optional description shown when hovering over the setting. + /// Not recommended, provide the description when creating the setting instead. + /// + public string Description; + + /// + /// Name of the setting. + /// + public string DispName; + + /// + /// Order of the setting on the settings list relative to other settings in a category. + /// 0 by default, higher number is higher on the list. + /// + public int? Order; + + /// + /// Only show the value, don't allow editing it. + /// + public bool? ReadOnly; + + /// + /// If true, don't show the setting by default. User has to turn on showing advanced settings or search for it. + /// + public bool? IsAdvanced; + + /// + /// Custom converter from setting type to string for the built-in editor textboxes. + /// + public System.Func ObjToStr; + + /// + /// Custom converter from string to setting type for the built-in editor textboxes. + /// + public System.Func StrToObj; +} diff --git a/SkinManagerMod/Main.cs b/SkinManagerMod/Main.cs index 55501e2..8be61af 100644 --- a/SkinManagerMod/Main.cs +++ b/SkinManagerMod/Main.cs @@ -1,73 +1,125 @@ -using HarmonyLib; +using BepInEx; +using BepInEx.Configuration; +using DV; +using DV.Localization; +using DV.ThingTypes; +using HarmonyLib; +using System.IO; using System.Reflection; using UnityEngine; -using UnityModManagerNet; namespace SkinManagerMod { - public static class Main + internal static class PluginInfo { - public static UnityModManager.ModEntry ModEntry { get; private set; } + public const string Guid = "SkinManagerMod"; + public const string Name = "Skin Manager"; + public const string Version = "3.0.0"; + + public const string ContentFolderName = "content"; + public const string SkinFolderName = "skins"; + public const string DefaultExportFolderName = "skin_export"; + } + + [BepInPlugin(PluginInfo.Guid, PluginInfo.Name, PluginInfo.Version)] + public class Main : BaseUnityPlugin + { + public static Main Instance { get; private set; } public static SkinManagerSettings Settings { get; private set; } - static bool Load(UnityModManager.ModEntry modEntry) + public static string SkinFolderPath { get; private set; } + public static string GetSkinFolder(string carId) + { + return Path.Combine(SkinFolderPath, carId); + } + + public static string ExportFolderPath => Settings.ExportPath.Value; + public static string GetExportFolder(string carId) + { + return Path.Combine(ExportFolderPath, carId); + } + + public void Awake() { - ModEntry = modEntry; + Instance = this; + SkinFolderPath = Path.Combine(Paths.BepInExRootPath, PluginInfo.ContentFolderName, PluginInfo.SkinFolderName); // Load the settings - Settings = SkinManagerSettings.Load(modEntry); + Settings = new SkinManagerSettings(this); - CCLPatch.Initialize(); + //CCLPatch.Initialize(); if (!SkinManager.Initialize()) { - modEntry.Logger.Error("Failed to initialize skin manager"); - return false; + Logger.LogError("Failed to initialize skin manager"); + enabled = false; + return; } - var harmony = new Harmony(modEntry.Info.Id); + var harmony = new Harmony(PluginInfo.Guid); harmony.PatchAll(Assembly.GetExecutingAssembly()); - modEntry.OnGUI = OnGUI; - modEntry.OnSaveGUI = OnSaveGUI; - QualitySettings.anisotropicFiltering = AnisotropicFiltering.ForceEnable; + } - return true; + public static void Error(string message) + { + Instance.Logger.LogError(message); } + } + + public enum DefaultSkinsMode + { + PreferReplacements, + AllowForCustomCars, + AllowForAllCars + } - static Vector2 scrollViewVector = Vector2.zero; - static TrainCarType trainCarSelected = TrainCarType.NotSet; - static bool showDropdown = false; + public class SkinManagerSettings + { + private const string DEFAULT_SECTION = "General"; - private static readonly string[] defaultSkinModeTexts = new[] - { - "Prefer Reskins", - "Random For Custom Cars", - "Random For All Cars" - }; + public readonly ConfigEntry ExtraAnisotropic; + public readonly ConfigEntry ParallelLoading; + public readonly ConfigEntry DefaultSkinsUsage; + public readonly ConfigEntry ExportPath; - static void OnGUI(UnityModManager.ModEntry modEntry) + public SkinManagerSettings(Main plugin) { - GUILayout.BeginVertical(); + ExtraAnisotropic = plugin.Config.Bind( + DEFAULT_SECTION, "IncreasedAniso", true, + "Increase Anisotropic Filtering (sharper textures from a distance)"); - bool newAniso = GUILayout.Toggle(Settings.aniso5, "Increase Anisotropic Filtering (Requires Manual Game Restart)"); - if (newAniso != Settings.aniso5) - { - Settings.aniso5 = newAniso; - } - Settings.parallelLoading = GUILayout.Toggle(Settings.parallelLoading, "Multi-threaded texture loading"); + ParallelLoading = plugin.Config.Bind( + DEFAULT_SECTION, "ParallelLoading", true, + "Multi-threaded texture loading"); + + DefaultSkinsUsage = plugin.Config.Bind( + DEFAULT_SECTION, "DefaultSkinsUsage", DefaultSkinsMode.AllowForCustomCars, + "PreferReplacements, AllowForCustomCars, AllowForAllCars"); + + string defaultExportPath = Path.Combine(Paths.BepInExRootPath, PluginInfo.ContentFolderName, PluginInfo.DefaultExportFolderName); + var exportDescription = new ConfigDescription( + "Directory for exported default textures", + null, + new ConfigurationManagerAttributes { CustomDrawer = DrawExporter }); + + ExportPath = plugin.Config.Bind(DEFAULT_SECTION, "ExportPath", defaultExportPath, exportDescription); + } - GUILayout.Label("Default skin usage:"); - Settings.defaultSkinsMode = (SkinManagerSettings.DefaultSkinsMode)GUILayout.SelectionGrid((int)Settings.defaultSkinsMode, defaultSkinModeTexts, 1, "toggle"); - GUILayout.Space(2); + private static Vector2 scrollViewVector = Vector2.zero; + private static TrainCarLivery trainCarSelected = null; + private static bool showDropdown = false; + private static void DrawExporter(ConfigEntryBase entry) + { GUILayout.Label("Texture Utility"); GUILayout.BeginHorizontal(GUILayout.Width(250)); GUILayout.BeginVertical(); - if (GUILayout.Button(trainCarSelected == TrainCarType.NotSet ? "Select Train Car" : SkinManager.EnabledCarTypes[trainCarSelected], GUILayout.Width(220))) + string typeLabel = (trainCarSelected != null) ? LocalizationAPI.L(trainCarSelected.localizationKey) : "Select Train Car"; + if (GUILayout.Button(typeLabel, GUILayout.Width(220))) { showDropdown = !showDropdown; } @@ -76,12 +128,12 @@ static void OnGUI(UnityModManager.ModEntry modEntry) { scrollViewVector = GUILayout.BeginScrollView(scrollViewVector, GUILayout.Height(350)); - foreach (var entry in SkinManager.EnabledCarTypes) + foreach (var livery in Globals.G.Types.Liveries) { - if (GUILayout.Button(entry.Value, GUILayout.Width(220))) + if (GUILayout.Button(LocalizationAPI.L(livery.localizationKey), GUILayout.Width(220))) { showDropdown = false; - trainCarSelected = entry.Key; + trainCarSelected = livery; } } @@ -92,7 +144,7 @@ static void OnGUI(UnityModManager.ModEntry modEntry) GUILayout.EndHorizontal(); - if (trainCarSelected != TrainCarType.NotSet) + if (trainCarSelected != null) { if (GUILayout.Button("Export Textures", GUILayout.Width(180))) { @@ -107,31 +159,5 @@ static void OnGUI(UnityModManager.ModEntry modEntry) GUILayout.EndVertical(); } - - static void OnSaveGUI(UnityModManager.ModEntry modEntry) - { - Settings.Save(modEntry); - } - } - - - // Mod settings - public class SkinManagerSettings : UnityModManager.ModSettings - { - public enum DefaultSkinsMode - { - PreferReplacements, - AllowForCustomCars, - AllowForAllCars - } - - public bool aniso5 = false; - public bool parallelLoading = true; - public DefaultSkinsMode defaultSkinsMode = DefaultSkinsMode.AllowForCustomCars; - - public override void Save(UnityModManager.ModEntry modEntry) - { - Save(this, modEntry); - } } } diff --git a/SkinManagerMod/Remaps.cs b/SkinManagerMod/Remaps.cs new file mode 100644 index 0000000..64fe693 --- /dev/null +++ b/SkinManagerMod/Remaps.cs @@ -0,0 +1,59 @@ +using DV.ThingTypes; +using System; +using System.Collections.Generic; + +namespace SkinManagerMod +{ + internal static class Remaps + { + public static Dictionary OldCarTypeIDs = new Dictionary() + { + { TrainCarType.LocoShunter, "loco_621" }, + { TrainCarType.LocoSteamHeavy, "loco_steam_H" }, + { TrainCarType.Tender, "loco_steam_tender" }, + //{ TrainCarType.LocoRailbus, "" }, + //{ TrainCarType.LocoDiesel, "" }, + //{ TrainCarType.LocoDH2, "" }, + //{ TrainCarType.LocoDM1, "" }, + + { TrainCarType.FlatbedEmpty, "car_flatbed_empty" }, + { TrainCarType.FlatbedStakes, "car_flatbed_stakes" }, + { TrainCarType.FlatbedMilitary, "car_flatbed_military_empty" }, + + { TrainCarType.AutorackRed, "CarAutorack_Red" }, + { TrainCarType.AutorackBlue, "CarAutorack_Blue" }, + { TrainCarType.AutorackGreen, "CarAutorack_Green" }, + { TrainCarType.AutorackYellow, "CarAutorack_Yellow" }, + + { TrainCarType.TankOrange, "CarTank_Orange" }, + { TrainCarType.TankWhite, "CarTank_White" }, + { TrainCarType.TankYellow, "CarTank_Yellow" }, + { TrainCarType.TankBlue, "CarTank_Blue" }, + { TrainCarType.TankChrome, "CarTank_Chrome" }, + { TrainCarType.TankBlack, "CarTank_Black" }, + + { TrainCarType.BoxcarBrown, "CarBoxcar_Brown" }, + { TrainCarType.BoxcarGreen, "CarBoxcar_Green" }, + { TrainCarType.BoxcarPink, "CarBoxcar_Pink" }, + { TrainCarType.BoxcarRed, "CarBoxcar_Red" }, + { TrainCarType.BoxcarMilitary, "CarBoxcarMilitary" }, + { TrainCarType.RefrigeratorWhite, "CarRefrigerator_White" }, + + { TrainCarType.HopperBrown, "CarHopper_Brown" }, + { TrainCarType.HopperTeal, "CarHopper_Teal" }, + { TrainCarType.HopperYellow, "CarHopper_Yellow" }, + + { TrainCarType.GondolaRed, "CarGondola_Red" }, + { TrainCarType.GondolaGreen, "CarGondola_Green" }, + { TrainCarType.GondolaGray, "CarGondola_Grey" }, + + { TrainCarType.PassengerRed, "CarPassenger_Red" }, + { TrainCarType.PassengerGreen, "CarPassenger_Green" }, + { TrainCarType.PassengerBlue, "CarPassenger_Blue" }, + + { TrainCarType.HandCar, "handcar" }, + { TrainCarType.CabooseRed, "CarCaboose_Red" }, + { TrainCarType.NuclearFlask, "CarNuclearFlask" }, + }; + } +} diff --git a/SkinManagerMod/SaveLoadPatches.cs b/SkinManagerMod/SaveLoadPatches.cs index ecbba30..0c00fba 100644 --- a/SkinManagerMod/SaveLoadPatches.cs +++ b/SkinManagerMod/SaveLoadPatches.cs @@ -14,7 +14,7 @@ static void Prefix( SaveGameManager __instance ) { JObject carsSaveData = SkinManager.GetCarsSaveData(); - SaveGameManager.data.SetJObject("Mod_Skins", carsSaveData); + SaveGameManager.Instance.data.SetJObject("Mod_Skins", carsSaveData); } } @@ -25,11 +25,11 @@ static void Prefix( JObject savedData ) { if( savedData == null ) { - Main.ModEntry.Logger.Error("Given save data is null, loading will not be performed"); + Main.Error("Given save data is null, loading will not be performed"); return; } - JObject carsSaveData = SaveGameManager.data.GetJObject("Mod_Skins"); + JObject carsSaveData = SaveGameManager.Instance.data.GetJObject("Mod_Skins"); if( carsSaveData != null ) { diff --git a/SkinManagerMod/SkinManager.cs b/SkinManagerMod/SkinManager.cs index 362bae5..e02f49e 100644 --- a/SkinManagerMod/SkinManager.cs +++ b/SkinManagerMod/SkinManager.cs @@ -1,5 +1,7 @@ -using DV.JObjectExtstensions; -using HarmonyLib; +using DV; +using DV.CabControls.Spec; +using DV.JObjectExtstensions; +using DV.ThingTypes; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -11,60 +13,41 @@ namespace SkinManagerMod { public static class SkinManager { - private static readonly string[] standardShaderUniqueTextures = new[] { "_MainTex", "_BumpMap", "_MetallicGlossMap", "_EmissionMap" }; - private static readonly string[] standardShaderAllTextures = new[] { "_MainTex", "_BumpMap", "_MetallicGlossMap", "_EmissionMap", "_OcclusionMap" }; - private const string METAL_GLOSS_TEXTURE = "_MetallicGlossMap"; - private const string OCCLUSION_TEXTURE = "_OcclusionMap"; + //private static Dictionary CustomCarTypes; - private static readonly Dictionary textureAliases = new Dictionary - { - { "exterior_d", "body" }, - { "LocoDiesel_exterior_d", "body" }, - { "SH_exterior_d", "body" }, - { "SH_tender_01d", "body" } - }; - - public static readonly TrainCarType[] DisabledCarTypes = new[] - { - TrainCarType.LocoSteamHeavyBlue, - TrainCarType.TenderBlue, - TrainCarType.LocoRailbus - }; - - public static Dictionary EnabledCarTypes { get; private set; } - private static Dictionary CustomCarTypes; - - private static readonly Dictionary skinGroups = new Dictionary(); + /// Livery ID to SkinGroup mapping + private static readonly Dictionary skinGroups = new Dictionary(); public static IEnumerable AllSkinGroups => skinGroups.Values; - private static readonly Dictionary defaultSkins = new Dictionary(); + /// Livery ID to default skin mapping + private static readonly Dictionary defaultSkins = new Dictionary(); private static readonly Dictionary carGuidToAppliedSkinMap = new Dictionary(); private static Skin lastSteamerSkin; - public static event Action SkinsLoaded; + public static event Action SkinsLoaded; - public static Skin FindSkinByName(TrainCarType carType, string name) + public static Skin FindSkinByName(TrainCarLivery carType, string name) { - if (skinGroups.TryGetValue(carType, out var group)) + if (skinGroups.TryGetValue(carType.id, out var group)) { return group.GetSkin(name); } return null; } - public static List GetSkinsForType(TrainCarType carType, bool includeDefault = true) + public static List GetSkinsForType(TrainCarLivery carType, bool includeDefault = true) { - if (skinGroups.TryGetValue(carType, out var group)) + if (skinGroups.TryGetValue(carType.id, out var group)) { var result = group.Skins; - return includeDefault ? result.Append(defaultSkins[carType]).ToList() : result; + return includeDefault ? result.Append(defaultSkins[carType.id]).ToList() : result; } - return includeDefault ? new List() { defaultSkins[carType] } : new List(); + return includeDefault ? new List() { defaultSkins[carType.id] } : new List(); } - public static Skin GetNewSkin(TrainCarType carType) + public static Skin GetNewSkin(TrainCarLivery carType) { if (CarTypes.IsTender(carType) && (lastSteamerSkin != null)) { @@ -75,11 +58,11 @@ public static Skin GetNewSkin(TrainCarType carType) } // random skin - if (skinGroups.TryGetValue(carType, out var group) && (group.Skins.Count > 0)) + if (skinGroups.TryGetValue(carType.id, out var group) && (group.Skins.Count > 0)) { bool allowRandomDefault = - (Main.Settings.defaultSkinsMode == SkinManagerSettings.DefaultSkinsMode.AllowForAllCars) || - (CustomCarTypes.ContainsKey(carType) && (Main.Settings.defaultSkinsMode == SkinManagerSettings.DefaultSkinsMode.AllowForCustomCars)); + (Main.Settings.DefaultSkinsUsage.Value == DefaultSkinsMode.AllowForAllCars); + // || (CustomCarTypes.ContainsKey(carType) && (Main.Settings.defaultSkinsMode == SkinManagerSettings.DefaultSkinsMode.AllowForCustomCars)); int nChoices = allowRandomDefault ? group.Skins.Count + 1 : group.Skins.Count; int choice = UnityEngine.Random.Range(0, nChoices); @@ -90,30 +73,34 @@ public static Skin GetNewSkin(TrainCarType carType) } // fall back to default skin - return defaultSkins[carType]; + return defaultSkins[carType.id]; } + /// Get the currently assigned skin for given car, or a new one if none is assigned public static Skin GetCurrentCarSkin(TrainCar car) { if (carGuidToAppliedSkinMap.TryGetValue(car.CarGUID, out var skinName)) { - if (defaultSkins.TryGetValue(car.carType, out Skin defaultSkin) && (skinName == defaultSkin.Name)) + if (defaultSkins.TryGetValue(car.carLivery.id, out Skin defaultSkin) && (skinName == defaultSkin.Name)) { return defaultSkin; } - if (FindSkinByName(car.carType, skinName) is Skin result) + if (FindSkinByName(car.carLivery, skinName) is Skin result) { return result; } } - return GetNewSkin(car.carType); + + return GetNewSkin(car.carLivery); } + /// Save the private static void SetAppliedCarSkin(TrainCar car, Skin skin) { carGuidToAppliedSkinMap[car.CarGUID] = skin.Name; + // TODO: support for CCL steam locos (this method only checks if == locosteamheavy) if (CarTypes.IsSteamLocomotive(car.carType)) { lastSteamerSkin = skin; @@ -124,46 +111,32 @@ private static void SetAppliedCarSkin(TrainCar car, Skin skin) } } + //==================================================================================================== + #region Skin Loading + public static bool Initialize() { - var fieldInfo = AccessTools.Field(typeof(CarTypes), "prefabMap"); - if (fieldInfo?.GetValue(null) is Dictionary defaultMap) - { - EnabledCarTypes = new Dictionary( - defaultMap.Where(t => !DisabledCarTypes.Contains(t.Key)).Concat(CCLPatch.CarList) - ); - CustomCarTypes = new Dictionary(CCLPatch.CarList); - LoadSkins(); - return true; - } - return false; + //CustomCarTypes = new Dictionary(CCLPatch.CarList); + LoadSkins(); + return true; } - //==================================================================================================== - #region Skin Loading - private static void LoadSkins() { - foreach ((TrainCarType carType, string carName) in EnabledCarTypes) + foreach (var livery in Globals.G.Types.Liveries) { - Skin defaultSkin = CreateDefaultSkin(carType, carType.DisplayName()); - defaultSkins.Add(carType, defaultSkin); - - skinGroups[carType] = new SkinGroup(carType); + Skin defaultSkin = CreateDefaultSkin(livery); + defaultSkins.Add(livery.id, defaultSkin); - var dir = Path.Combine(Main.ModEntry.Path, "Skins", carName); + skinGroups[livery.id] = new SkinGroup(livery); - if (Directory.Exists(dir)) - { - LoadSkinsForType(dir, carType); - } + LoadAllSkinsForType(livery); } - LoadCCLEmbeddedSkins(); - SkinsLoaded?.Invoke(null); } + /* /// /// Load any skins included in CCL car folders /// @@ -201,19 +174,15 @@ private static void LoadCCLEmbeddedSkins() } } } + */ - public static void ReloadSkins(TrainCarType carType) + public static void ReloadSkins(TrainCarLivery livery) { - string carName = EnabledCarTypes[carType]; - skinGroups[carType] = new SkinGroup(carType); - - var dir = Path.Combine(Main.ModEntry.Path, "Skins", carName); + skinGroups[livery.id] = new SkinGroup(livery); - if (Directory.Exists(dir)) - { - LoadSkinsForType(dir, carType, true); - } + LoadAllSkinsForType(livery, true); + /* if (CustomCarTypes.ContainsKey(carType)) { // also reload CCL embedded skins @@ -243,11 +212,12 @@ public static void ReloadSkins(TrainCarType carType) } } } + */ - SkinsLoaded?.Invoke(carType); + SkinsLoaded?.Invoke(livery); // reapply skins to any cars of this type - var carsInScene = UnityEngine.Object.FindObjectsOfType().Where(tc => tc.carType == carType); + var carsInScene = UnityEngine.Object.FindObjectsOfType().Where(tc => tc.carLivery == livery); foreach (var car in carsInScene) { var toApply = GetCurrentCarSkin(car); @@ -260,88 +230,71 @@ public static void ReloadSkins(TrainCarType carType) } } - private static void LoadSkinsForType(string skinsFolder, TrainCarType carType, bool forceSync = false) + private static void LoadAllSkinsForType(TrainCarLivery livery, bool forceSync = false) { - var skinGroup = skinGroups[carType]; - var renderers = GetAllCarRenderers(carType); + string folderPath = Main.GetSkinFolder(livery.id); - foreach (string subDir in Directory.GetDirectories(skinsFolder)) + if (Directory.Exists(folderPath)) + { + LoadSkinsFromFolder(folderPath, livery, forceSync); + } + + if (Remaps.OldCarTypeIDs.TryGetValue(livery.v1, out string overhauledId)) { - BeginLoadSkin(skinGroup, renderers, subDir, forceSync); + folderPath = Main.GetSkinFolder(overhauledId); + + if (Directory.Exists(folderPath)) + { + LoadSkinsFromFolder(folderPath, livery, forceSync); + } } } - private static IEnumerable GetAllCarRenderers(TrainCarType carType) + private static void LoadSkinsFromFolder(string skinsFolder, TrainCarLivery carType, bool forceSync = false) { - var carPrefab = CarTypes.GetCarPrefab(carType); - IEnumerable cmps = carPrefab.gameObject.GetComponentsInChildren(); + var skinGroup = skinGroups[carType.id]; + var renderers = TextureUtility.GetAllCarRenderers(carType); + var textures = TextureUtility.GetRendererTextureNames(renderers); - var trainCar = carPrefab.GetComponent(); - - if (trainCar.interiorPrefab != null) + foreach (string subDir in Directory.GetDirectories(skinsFolder)) { - var interiorCmps = trainCar.interiorPrefab.GetComponentsInChildren(); - cmps = cmps.Concat(interiorCmps); + BeginLoadSkin(skinGroup, textures, subDir, forceSync); } - - return cmps; } /// /// Create a skin containing the default/starting textures of a car /// - private static Skin CreateDefaultSkin(TrainCarType carType, string typeName) + private static Skin CreateDefaultSkin(TrainCarLivery carType) { - GameObject carPrefab = CarTypes.GetCarPrefab(carType); + GameObject carPrefab = carType.prefab; if (carPrefab == null) return null; string skinDir = null; - if (CCLPatch.IsCustomCarType(carType)) - { - skinDir = CCLPatch.GetCarFolder(carType); - } + //if (CCLPatch.IsCustomCarType(carType)) + //{ + // skinDir = CCLPatch.GetCarFolder(carType); + //} - Skin defSkin = new Skin($"Default_{typeName}", skinDir, isDefault: true); + var defaultSkin = new Skin($"Default_{carType.id}", skinDir, isDefault: true); - var renderers = carPrefab.gameObject.GetComponentsInChildren(); - foreach (var renderer in renderers) + foreach (var texture in TextureUtility.EnumerateTextures(carType)) { - if (!renderer.material) continue; - - foreach (string textureName in standardShaderUniqueTextures) + if (!defaultSkin.ContainsTexture(texture.name)) { - if (TextureUtility.GetMaterialTexture(renderer, textureName) is Texture2D texture) - { - defSkin.SkinTextures.Add(new SkinTexture(texture.name, texture)); - } + defaultSkin.SkinTextures.Add(new SkinTexture(texture.name, texture)); } } - var trainCar = carPrefab.GetComponent(); - - if (trainCar?.interiorPrefab) - { - foreach (var renderer in trainCar.interiorPrefab.GetComponentsInChildren()) - { - if (!renderer.material) continue; - - foreach (string textureName in standardShaderUniqueTextures) - { - if (TextureUtility.GetMaterialTexture(renderer, textureName) is Texture2D texture) - { - defSkin.SkinTextures.Add(new SkinTexture(texture.name, texture)); - } - } - } - } - - return defSkin; + return defaultSkin; } + + /// /// Create a skin from the given directory, load textures, and add it to the given group /// - private static void BeginLoadSkin(SkinGroup skinGroup, IEnumerable renderers, string subDir, bool forceSync = false) + private static void BeginLoadSkin(SkinGroup skinGroup, Dictionary textureNames, string subDir, bool forceSync = false) { var dirInfo = new DirectoryInfo(subDir); var files = dirInfo.GetFiles(); @@ -349,42 +302,27 @@ private static void BeginLoadSkin(SkinGroup skinGroup, IEnumerable var loading = new HashSet(); - bool Matches(Texture2D texture, string fileName) - { - return texture != null && - !loading.Contains(texture.name) && - (texture.name == fileName || - (textureAliases.TryGetValue(texture.name, out var alias) && alias == fileName)); - } - foreach (var file in files) { if (!StbImage.IsSupportedExtension(file.Extension)) continue; string fileName = Path.GetFileNameWithoutExtension(file.Name); - foreach (var renderer in renderers) + if (!loading.Contains(fileName) && textureNames.TryGetValue(fileName, out string textureProp)) { - foreach (var textureName in standardShaderUniqueTextures) - { - var texture = TextureUtility.GetMaterialTexture(renderer, textureName); - if (Matches(texture, fileName)) - { - var linear = textureName == "_BumpMap"; - loading.Add(texture.name); + var linear = textureProp == "_BumpMap"; + loading.Add(fileName); - if (!forceSync && Main.Settings.parallelLoading) - { - skin.SkinTextures.Add(new SkinTexture(fileName, TextureLoader.Add(file, linear))); - } - else - { - TextureLoader.BustCache(file); - var tex = new Texture2D(0, 0, textureFormat: TextureFormat.RGBA32, mipChain: true, linear: linear); - tex.LoadImage(File.ReadAllBytes(file.FullName)); - skin.SkinTextures.Add(new SkinTexture(fileName, tex)); - } - } + if (!forceSync && Main.Settings.ParallelLoading.Value) + { + skin.SkinTextures.Add(new SkinTexture(fileName, TextureLoader.Add(file, linear))); + } + else + { + TextureLoader.BustCache(file); + var tex = new Texture2D(0, 0, textureFormat: TextureFormat.RGBA32, mipChain: true, linear: linear); + tex.LoadImage(File.ReadAllBytes(file.FullName)); + skin.SkinTextures.Add(new SkinTexture(fileName, tex)); } } } @@ -403,7 +341,7 @@ public static void ApplySkin(TrainCar trainCar, Skin skin) //Main.ModEntry.Logger.Log($"Applying skin {skin.Name} to car {trainCar.ID}"); - ApplySkin(trainCar.gameObject.transform, skin, defaultSkins[trainCar.carType]); + ApplySkin(trainCar.gameObject.transform, skin, defaultSkins[trainCar.carLivery.id]); if (trainCar.IsInteriorLoaded) { ApplySkinToInterior(trainCar, skin); @@ -416,7 +354,7 @@ public static void ApplySkinToInterior(TrainCar trainCar, Skin skin) { if (skin == null) return; - ApplySkin(trainCar.interior, skin, defaultSkins[trainCar.carType]); + ApplySkin(trainCar.interior, skin, defaultSkins[trainCar.carLivery.id]); } private static void ApplySkin(Transform objectRoot, Skin skin, Skin defaultSkin) @@ -428,40 +366,7 @@ private static void ApplySkin(Transform objectRoot, Skin skin, Skin defaultSkin) continue; } - foreach (string textureID in standardShaderAllTextures) - { - var currentTexture = TextureUtility.GetMaterialTexture(renderer, textureID); - - if (currentTexture != null) - { - if (skin.ContainsTexture(currentTexture.name)) - { - var skinTexture = skin.GetTexture(currentTexture.name); - renderer.material.SetTexture(textureID, skinTexture.TextureData); - - if (textureID == METAL_GLOSS_TEXTURE) - { - if (!TextureUtility.GetMaterialTexture(renderer, OCCLUSION_TEXTURE)) - { - renderer.material.SetTexture(OCCLUSION_TEXTURE, skinTexture.TextureData); - } - } - } - else if ((defaultSkin != null) && defaultSkin.ContainsTexture(currentTexture.name)) - { - var skinTexture = defaultSkin.GetTexture(currentTexture.name); - renderer.material.SetTexture(textureID, skinTexture.TextureData); - - if (textureID == METAL_GLOSS_TEXTURE) - { - if (!TextureUtility.GetMaterialTexture(renderer, OCCLUSION_TEXTURE)) - { - renderer.material.SetTexture(OCCLUSION_TEXTURE, skinTexture.TextureData); - } - } - } - } - } + TextureUtility.ApplyTextures(renderer, skin, defaultSkin); } } diff --git a/SkinManagerMod/SkinManagerMod.csproj b/SkinManagerMod/SkinManagerMod.csproj index a0ae7a8..526f693 100644 --- a/SkinManagerMod/SkinManagerMod.csproj +++ b/SkinManagerMod/SkinManagerMod.csproj @@ -37,7 +37,9 @@ D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\Assembly-CSharp.dll - + + + False D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\DV.Utils.dll @@ -70,16 +72,15 @@ D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityEngine.IMGUIModule.dll - - D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\UnityModManager\UnityModManager.dll - - + + + @@ -89,6 +90,7 @@ - xcopy /Y "$(TargetPath)" "C:\Program Files (x86)\Steam\steamapps\common\Derail Valley\Mods\SkinManagerMod\" + + \ No newline at end of file diff --git a/SkinManagerMod/Skins.cs b/SkinManagerMod/Skins.cs index 7559bc7..2376462 100644 --- a/SkinManagerMod/Skins.cs +++ b/SkinManagerMod/Skins.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using DV.ThingTypes; +using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; @@ -74,10 +75,10 @@ public SkinTexture(string name, Task task) public class SkinGroup { - public readonly TrainCarType TrainCarType; + public readonly TrainCarLivery TrainCarType; public readonly List Skins = new List(); - public SkinGroup(TrainCarType trainCarType) + public SkinGroup(TrainCarLivery trainCarType) { TrainCarType = trainCarType; } diff --git a/SkinManagerMod/TextureUtility.cs b/SkinManagerMod/TextureUtility.cs index 617423e..31cd30f 100644 --- a/SkinManagerMod/TextureUtility.cs +++ b/SkinManagerMod/TextureUtility.cs @@ -1,78 +1,50 @@ -using System; +using DV.ThingTypes; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using UnityEngine; namespace SkinManagerMod { public static class TextureUtility { + private static readonly string[] standardShaderUniqueTextures = new[] { "_MainTex", "_BumpMap", "_MetallicGlossMap", "_EmissionMap" }; + private static readonly string[] standardShaderAllTextures = new[] { "_MainTex", "_BumpMap", "_MetallicGlossMap", "_EmissionMap", "_OcclusionMap" }; + private const string METAL_GLOSS_TEXTURE = "_MetallicGlossMap"; + private const string OCCLUSION_TEXTURE = "_OcclusionMap"; + /// /// Export all textures associated with the given car type /// - public static void DumpTextures(TrainCarType trainCarType) + public static void DumpTextures(TrainCarLivery trainCarType) { - MeshRenderer[] cmps; - Dictionary textureList; - - var obj = CarTypes.GetCarPrefab(trainCarType); - - var path = Path.Combine(Main.ModEntry.Path, "Exported", obj.name); + var path = Main.GetExportFolder(trainCarType.id); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } - cmps = obj.GetComponentsInChildren(); - textureList = new Dictionary(); + var renderers = GetAllCarRenderers(trainCarType); + var textureList = new Dictionary(); - foreach (var cmp in cmps) + foreach (var renderer in renderers) { - if (!cmp.material) + if (!renderer.material) { continue; } - var diffuse = GetMaterialTexture(cmp, "_MainTex"); - var normal = GetMaterialTexture(cmp, "_BumpMap"); - var specular = GetMaterialTexture(cmp, "_MetallicGlossMap"); - var emission = GetMaterialTexture(cmp, "_EmissionMap"); + var diffuse = GetMaterialTexture(renderer, "_MainTex"); + var normal = GetMaterialTexture(renderer, "_BumpMap"); + var specular = GetMaterialTexture(renderer, "_MetallicGlossMap"); + var emission = GetMaterialTexture(renderer, "_EmissionMap"); ExportTexture(path, diffuse, textureList); ExportTexture(path, normal, textureList, true); ExportTexture(path, specular, textureList); ExportTexture(path, emission, textureList); } - - var trainCar = obj.GetComponent(); - - if (trainCar.interiorPrefab != null) - { - cmps = trainCar.interiorPrefab.GetComponentsInChildren(); - textureList = new Dictionary(); - - foreach (var cmp in cmps) - { - if (!cmp.material) - { - continue; - } - - var diffuse = GetMaterialTexture(cmp, "_MainTex"); - var normal = GetMaterialTexture(cmp, "_BumpMap"); - var specular = GetMaterialTexture(cmp, "_MetallicGlossMap"); - var emission = GetMaterialTexture(cmp, "_EmissionMap"); - - ExportTexture(path, diffuse, textureList); - ExportTexture(path, normal, textureList, true); - ExportTexture(path, specular, textureList); - ExportTexture(path, emission, textureList); - } - } } private static void ExportTexture(string path, Texture2D texture, Dictionary alreadyExported, bool isNormal = false) @@ -171,13 +143,106 @@ public static Texture2D GetMaterialTexture(MeshRenderer cmp, string materialName return cmp.material.GetTexture(materialName) as Texture2D; } + public static IEnumerable EnumerateTextures(IEnumerable renderers) + { + foreach (var renderer in renderers) + { + if (!renderer.material) continue; + + foreach (string textureName in standardShaderUniqueTextures) + { + if (GetMaterialTexture(renderer, textureName) is Texture2D texture) + { + yield return texture; + } + } + } + } + + public static IEnumerable EnumerateTextures(TrainCarLivery livery) + { + var renderers = GetAllCarRenderers(livery); + return EnumerateTextures(renderers); + } + + public static IEnumerable GetAllCarRenderers(TrainCarLivery carType) + { + IEnumerable cmps = carType.prefab.gameObject.GetComponentsInChildren(); + + if (carType.interiorPrefab != null) + { + var interiorCmps = carType.interiorPrefab.GetComponentsInChildren(); + cmps = cmps.Concat(interiorCmps); + } + + return cmps; + } + + public static Dictionary GetRendererTextureNames(IEnumerable renderers) + { + var dict = new Dictionary(); + + foreach (var renderer in renderers) + { + if (!renderer.material) continue; + + foreach (string textureProperty in standardShaderUniqueTextures) + { + if (GetMaterialTexture(renderer, textureProperty) is Texture2D texture) + { + dict[texture.name] = textureProperty; + } + } + } + + return dict; + } + /// /// Set anisoLevel of imported textures to 5 for better visuals. (3 gives barely an improvement, anything over 9 is pointless) /// public static void SetTextureOptions(Texture2D tex) { - if (!Main.Settings.aniso5) return; + if (!Main.Settings.ExtraAnisotropic.Value) return; tex.anisoLevel = 5; } + + public static void ApplyTextures(MeshRenderer renderer, Skin skin, Skin defaultSkin) + { + foreach (string textureID in standardShaderAllTextures) + { + var currentTexture = GetMaterialTexture(renderer, textureID); + + if (currentTexture != null) + { + if (skin.ContainsTexture(currentTexture.name)) + { + var skinTexture = skin.GetTexture(currentTexture.name); + renderer.material.SetTexture(textureID, skinTexture.TextureData); + + if (textureID == METAL_GLOSS_TEXTURE) + { + if (!GetMaterialTexture(renderer, OCCLUSION_TEXTURE)) + { + renderer.material.SetTexture(OCCLUSION_TEXTURE, skinTexture.TextureData); + } + } + } + else if ((defaultSkin != null) && defaultSkin.ContainsTexture(currentTexture.name)) + { + var skinTexture = defaultSkin.GetTexture(currentTexture.name); + renderer.material.SetTexture(textureID, skinTexture.TextureData); + + if (textureID == METAL_GLOSS_TEXTURE) + { + if (!GetMaterialTexture(renderer, OCCLUSION_TEXTURE)) + { + renderer.material.SetTexture(OCCLUSION_TEXTURE, skinTexture.TextureData); + } + } + } + } + } + } } } From 63d0a54515eafe38ef10d8d56a82b73923ad380d Mon Sep 17 00:00:00 2001 From: Katy Fox Date: Wed, 28 Jun 2023 09:02:57 -0400 Subject: [PATCH 2/4] tweak config drawing --- SkinManagerMod/Main.cs | 17 ++++++++++++----- SkinManagerMod/SkinManager.cs | 5 +++++ SkinManagerMod/SkinManagerMod.csproj | 3 +-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/SkinManagerMod/Main.cs b/SkinManagerMod/Main.cs index 8be61af..c4ecf9e 100644 --- a/SkinManagerMod/Main.cs +++ b/SkinManagerMod/Main.cs @@ -4,6 +4,7 @@ using DV.Localization; using DV.ThingTypes; using HarmonyLib; +using System.ComponentModel; using System.IO; using System.Reflection; using UnityEngine; @@ -69,8 +70,13 @@ public static void Error(string message) public enum DefaultSkinsMode { + [Description("Prefer Reskins")] PreferReplacements, + + [Description("Random For Custom Cars")] AllowForCustomCars, + + [Description("Random For All Cars")] AllowForAllCars } @@ -94,14 +100,13 @@ public SkinManagerSettings(Main plugin) "Multi-threaded texture loading"); DefaultSkinsUsage = plugin.Config.Bind( - DEFAULT_SECTION, "DefaultSkinsUsage", DefaultSkinsMode.AllowForCustomCars, - "PreferReplacements, AllowForCustomCars, AllowForAllCars"); + DEFAULT_SECTION, "DefaultSkinsUsage", DefaultSkinsMode.AllowForCustomCars); string defaultExportPath = Path.Combine(Paths.BepInExRootPath, PluginInfo.ContentFolderName, PluginInfo.DefaultExportFolderName); var exportDescription = new ConfigDescription( "Directory for exported default textures", null, - new ConfigurationManagerAttributes { CustomDrawer = DrawExporter }); + new ConfigurationManagerAttributes { CustomDrawer = DrawExporter, Order = 9999 }); ExportPath = plugin.Config.Bind(DEFAULT_SECTION, "ExportPath", defaultExportPath, exportDescription); } @@ -112,9 +117,11 @@ public SkinManagerSettings(Main plugin) private static void DrawExporter(ConfigEntryBase entry) { - GUILayout.Label("Texture Utility"); + GUILayout.BeginVertical(); + + GUILayout.Label(entry.BoxedValue.ToString(), GUILayout.ExpandWidth(true)); - GUILayout.BeginHorizontal(GUILayout.Width(250)); + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); GUILayout.BeginVertical(); diff --git a/SkinManagerMod/SkinManager.cs b/SkinManagerMod/SkinManager.cs index e02f49e..7859dc9 100644 --- a/SkinManagerMod/SkinManager.cs +++ b/SkinManagerMod/SkinManager.cs @@ -238,6 +238,11 @@ private static void LoadAllSkinsForType(TrainCarLivery livery, bool forceSync = { LoadSkinsFromFolder(folderPath, livery, forceSync); } + else + { + // create default directories if not exist + Directory.CreateDirectory(folderPath); + } if (Remaps.OldCarTypeIDs.TryGetValue(livery.v1, out string overhauledId)) { diff --git a/SkinManagerMod/SkinManagerMod.csproj b/SkinManagerMod/SkinManagerMod.csproj index 526f693..a818c4d 100644 --- a/SkinManagerMod/SkinManagerMod.csproj +++ b/SkinManagerMod/SkinManagerMod.csproj @@ -33,7 +33,7 @@ 4 - + D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\Assembly-CSharp.dll @@ -44,7 +44,6 @@ False D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\DV.Utils.dll - False D:\Games\SteamLibrary\steamapps\common\Derail Valley\DerailValley_Data\Managed\Newtonsoft.Json.dll From 9b5c8e51e64a8b4637b116a524cfb9db2bb98972 Mon Sep 17 00:00:00 2001 From: Katy Fox Date: Wed, 28 Jun 2023 15:45:57 -0400 Subject: [PATCH 3/4] add mappings for some overhauled texture names --- SkinManagerMod/Remaps.cs | 87 ++++++++++++++++++++++++++++++++++- SkinManagerMod/SkinManager.cs | 21 ++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/SkinManagerMod/Remaps.cs b/SkinManagerMod/Remaps.cs index 64fe693..53829c8 100644 --- a/SkinManagerMod/Remaps.cs +++ b/SkinManagerMod/Remaps.cs @@ -1,12 +1,13 @@ using DV.ThingTypes; using System; +using System.Collections; using System.Collections.Generic; namespace SkinManagerMod { internal static class Remaps { - public static Dictionary OldCarTypeIDs = new Dictionary() + public static readonly Dictionary OldCarTypeIDs = new Dictionary() { { TrainCarType.LocoShunter, "loco_621" }, { TrainCarType.LocoSteamHeavy, "loco_steam_H" }, @@ -55,5 +56,89 @@ internal static class Remaps { TrainCarType.CabooseRed, "CarCaboose_Red" }, { TrainCarType.NuclearFlask, "CarNuclearFlask" }, }; + + private class TextureMapping : IEnumerable> + { + private readonly Dictionary _map = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + public static readonly char[] DE = { 'd', 'e' }; + public static readonly char[] DNS = { 'd', 'n', 's' }; + + public void Add(string oldName, string newName) + { + _map.Add(oldName, newName); + } + + public void Add(string oldBase, string newBase, char[] suffixes) + { + foreach (char suffix in suffixes) + { + _map.Add($"{oldBase}{suffix}", $"{newBase}{suffix}"); + } + } + + public IEnumerator> GetEnumerator() + { + return ((IEnumerable>)_map).GetEnumerator(); + } + + public bool TryGetUpdatedName(string oldName, out string newName) + { + return _map.TryGetValue(oldName, out newName); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_map).GetEnumerator(); + } + } + + /// Old texture name -> new texture name + private static readonly Dictionary _legacyTextureNameMap = + new Dictionary() + { + { + TrainCarType.LocoShunter, + new TextureMapping + { + { "exterior_", "LocoDE2_Body_01", TextureMapping.DNS }, + } + }, + + // 282 & tender new UVs -> no mappings :( + + { + TrainCarType.LocoDiesel, + new TextureMapping + { + { "LocoDiesel_bogies_", "LocoDE6_Body_01", TextureMapping.DNS }, + { "LocoDiesel_cab_", "LocoDE6_Interior_01", TextureMapping.DNS }, + { "LocoDiesel_engine_", "LocoDE6_Engine_01", TextureMapping.DNS }, + { "LocoDiesel_exterior_", "LocoDE6_Body_01", TextureMapping.DNS }, + { "LocoDiesel_gauges_01", "LocoDE6_Gauges_01", TextureMapping.DE }, + } + }, + + { + TrainCarType.CabooseRed, + new TextureMapping + { + { "CabooseExterior_", "CarCabooseRed_Body_01", TextureMapping.DNS }, + { "CabooseInterior_", "CarCabooseRed_Interior_01", TextureMapping.DNS }, + } + }, + }; + + public static bool TryGetUpdatedTextureName(TrainCarType carType, string oldName, out string newName) + { + if (_legacyTextureNameMap.TryGetValue(carType, out TextureMapping textureMapping)) + { + return textureMapping.TryGetUpdatedName(oldName, out newName); + } + + newName = null; + return false; + } } } diff --git a/SkinManagerMod/SkinManager.cs b/SkinManagerMod/SkinManager.cs index 7859dc9..3419745 100644 --- a/SkinManagerMod/SkinManager.cs +++ b/SkinManagerMod/SkinManager.cs @@ -294,7 +294,24 @@ private static Skin CreateDefaultSkin(TrainCarLivery carType) return defaultSkin; } - + private static bool TryGetTextureForFilename(TrainCarType carType, ref string filename, Dictionary textureNames, out string textureProp) + { + if (textureNames.TryGetValue(filename, out textureProp)) + { + return true; + } + + if ((carType != TrainCarType.NotSet) && Remaps.TryGetUpdatedTextureName(carType, filename, out string newName)) + { + if (textureNames.TryGetValue(newName, out textureProp)) + { + filename = newName; + return true; + } + } + + return false; + } /// /// Create a skin from the given directory, load textures, and add it to the given group @@ -313,7 +330,7 @@ private static void BeginLoadSkin(SkinGroup skinGroup, Dictionary Date: Thu, 29 Jun 2023 21:33:01 -0400 Subject: [PATCH 4/4] fix patches & null skin application --- SkinManagerMod/CarPatches.cs | 31 ++++++++++++------------------- SkinManagerMod/SkinManager.cs | 16 ++++++++++++---- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/SkinManagerMod/CarPatches.cs b/SkinManagerMod/CarPatches.cs index e29a596..410b9a8 100644 --- a/SkinManagerMod/CarPatches.cs +++ b/SkinManagerMod/CarPatches.cs @@ -7,13 +7,16 @@ namespace SkinManagerMod { - [HarmonyPatch(typeof(CarSpawner), "SpawnCar")] + [HarmonyPatch(typeof(CarSpawner))] class CarSpawner_SpawnCar_Patch { - static void Postfix(TrainCar __result) + [HarmonyPostfix] + [HarmonyPatch(nameof(CarSpawner.SpawnCar))] + [HarmonyPatch(nameof(CarSpawner.SpawnLoadedCar))] + static void SpawnCar(TrainCar __result) { var skin = SkinManager.GetCurrentCarSkin(__result); - if (!skin.IsDefault) + if ((skin != null) && !skin.IsDefault) { // only need to replace textures if not staying with default skin SkinManager.ApplySkin(__result, skin); @@ -21,26 +24,16 @@ static void Postfix(TrainCar __result) } } - [HarmonyPatch(typeof(CarSpawner), "SpawnLoadedCar")] - class CarSpawner_SpawnExistingCar_Patch - { - static void Postfix(TrainCar __result) - { - var skin = SkinManager.GetCurrentCarSkin(__result); - if (!skin.IsDefault) - { - SkinManager.ApplySkin(__result, skin); - } - } - } - - [HarmonyPatch(typeof(TrainCar), "LoadInterior")] + [HarmonyPatch(typeof(TrainCar))] class TrainCar_LoadInterior_Patch { - static void Postfix(TrainCar __instance) + [HarmonyPostfix] + [HarmonyPatch(nameof(TrainCar.LoadInterior))] + [HarmonyPatch(nameof(TrainCar.LoadExternalInteractables))] + static void LoadInterior(TrainCar __instance) { var skin = SkinManager.GetCurrentCarSkin(__instance); - if (!skin.IsDefault) + if ((skin != null) && !skin.IsDefault) { SkinManager.ApplySkinToInterior(__instance, skin); } diff --git a/SkinManagerMod/SkinManager.cs b/SkinManagerMod/SkinManager.cs index 3419745..2bbe505 100644 --- a/SkinManagerMod/SkinManager.cs +++ b/SkinManagerMod/SkinManager.cs @@ -73,7 +73,11 @@ public static Skin GetNewSkin(TrainCarLivery carType) } // fall back to default skin - return defaultSkins[carType.id]; + if (defaultSkins.TryGetValue(carType.id, out Skin skin)) + { + return skin; + } + return null; } /// Get the currently assigned skin for given car, or a new one if none is assigned @@ -221,11 +225,15 @@ public static void ReloadSkins(TrainCarLivery livery) foreach (var car in carsInScene) { var toApply = GetCurrentCarSkin(car); - ApplySkin(car, toApply); - if (car.IsInteriorLoaded) + if (toApply != null) { - ApplySkinToInterior(car, toApply); + ApplySkin(car, toApply); + + if (car.IsInteriorLoaded) + { + ApplySkinToInterior(car, toApply); + } } } }