From 5e39e5751809d0464f0072e8627fdb31914bd0ed Mon Sep 17 00:00:00 2001 From: GrahamKracker <86011515+GrahamKracker@users.noreply.github.com> Date: Sun, 20 Oct 2024 20:09:03 -0400 Subject: [PATCH 1/6] Update ModHelperGithub.cs --- BloonsTD6 Mod Helper/Api/Internal/ModHelperGithub.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BloonsTD6 Mod Helper/Api/Internal/ModHelperGithub.cs b/BloonsTD6 Mod Helper/Api/Internal/ModHelperGithub.cs index 0c217b0a7..77560c628 100644 --- a/BloonsTD6 Mod Helper/Api/Internal/ModHelperGithub.cs +++ b/BloonsTD6 Mod Helper/Api/Internal/ModHelperGithub.cs @@ -334,7 +334,7 @@ public static async Task DownloadAsset(ModHelperData mod, ReleaseAsset r name = $"{mod.Mod.GetAssembly().GetName().Name}.dll"; } - var downloadFilePath = Path.Combine(mod.EnabledFolder, name); + var downloadFilePath = Path.Combine(mod.Enabled ? mod.EnabledFolder : ModHelper.DisabledModsDirectory, name); var oldModsFilePath = Path.Combine(ModHelper.OldModsDirectory, name); try @@ -446,4 +446,4 @@ public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo targ file.CopyTo(Path.Combine(target.FullName, file.Name), true); } } -} \ No newline at end of file +} From 034a1a0b63210fa989117df3a5048bdb23d7713e Mon Sep 17 00:00:00 2001 From: doombubbles Date: Tue, 22 Oct 2024 18:00:26 -0700 Subject: [PATCH 2/6] Add ModBloonOverlay class --- .../Api/Display/ModBloonOverlay.cs | 185 ++++++++++++++++++ .../Api/Helpers/GameModelExporter.cs | 23 ++- .../Api/Internal/ModContentInstances.cs | 17 +- BloonsTD6 Mod Helper/Api/ModContent.cs | 2 +- .../ModelExtensions/MiscModelExt.cs | 8 + ..._Mod_Helper.Api.Display.ModBloonOverlay.md | 168 ++++++++++++++++ .../BTD_Mod_Helper.Api.Display.ModDisplay.md | 1 + .../BTD_Mod_Helper.Extensions.MiscModelExt.md | 21 ++ Documentation/README.md | 1 + 9 files changed, 407 insertions(+), 19 deletions(-) create mode 100644 BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs create mode 100644 Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md diff --git a/BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs b/BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs new file mode 100644 index 000000000..a0b7e6e74 --- /dev/null +++ b/BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Il2CppAssets.Scripts.Data; +using Il2CppAssets.Scripts.Data.Bloons; +using Il2CppAssets.Scripts.Models.Towers.Projectiles; +using Il2CppNinjaKiwi.Common; +using Il2CppNinjaKiwi.Common.ResourceUtils; +using MelonLoader.Utils; +using UnityEngine; +namespace BTD_Mod_Helper.Api.Display; + +/// +/// A special ModDisplay for Bloon Overlays. Handles automatically loading different instances of itself for each BloonOverlayClass +/// +public abstract class ModBloonOverlay : ModDisplay +{ + /// + /// Whether the current MelonLoader version will support registering a BloonOverlay + /// + public static bool WorksOnCurrentMelonLoader => typeof(MelonEnvironment).Assembly.GetName().Version is + {Major: 0, Minor: 6, Build: >= 6} or {Major: 0, Minor: 6, Build: <= 1} or {Major: 0, Minor: > 6} or {Major: > 0}; + + /// + /// Quick getter for all overlays + /// + protected static SerializableDictionary AllOverlayTypes => + GameData.Instance.bloonOverlays.overlayTypes; + + /// + /// The overlay class that this is for + /// + public BloonOverlayClass OverlayClass { get; private set; } + + /// + /// The base Bloon Overlay to copy from. + ///
+ /// These come from the fields of certain projectile behavior models + ///
+ /// To not copy from any Base Overlay, override this to be null/empty and modify or instead + ///
+ public virtual string BaseOverlay => null; + + /// + /// + ///
+ /// If is defined, will automatically get the correct display for each OverlayClass from there + ///
+ public override string BaseDisplay => string.IsNullOrEmpty(BaseOverlay) + ? "" + : AllOverlayTypes[BaseOverlay].assets[BaseOverlayClass].guidRef; + + /// + /// The of the overlay + /// + public virtual int DisplayLayer => + !string.IsNullOrEmpty(BaseOverlay) && AllOverlayTypes.TryGetValue(BaseOverlay, out var o) ? o.displayLayer : 0; + + private bool hasOverlayClass; + private protected override string ID => hasOverlayClass ? BaseId + "-" + OverlayClass : BaseId; + private string BaseId => GetId(mod, Name); + + /// + /// Lets you control which BloonOverlayClass from the BaseOverlay is used for each BloonOverlayClass of your custom overlay. + /// By default, uses the same as the base. + ///
+ /// + /// To make the non regrow bloons use the same overlays as the regrow bloons + /// + /// public override BloonOverlayClass BaseOverlayClass => OverlayClass switch + /// { + /// BloonOverlayClass.Red => BloonOverlayClass.RedRegrow, + /// BloonOverlayClass.Blue => BloonOverlayClass.BlueRegrow, + /// BloonOverlayClass.Green => BloonOverlayClass.GreenRegrow, + /// BloonOverlayClass.Yellow => BloonOverlayClass.YellowRegrow, + /// BloonOverlayClass.Pink => BloonOverlayClass.PinkRegrow, + /// BloonOverlayClass.White => BloonOverlayClass.WhiteRegrow, + /// _ => OverlayClass + /// }; + /// + /// + ///
+ public virtual BloonOverlayClass BaseOverlayClass => OverlayClass; + + /// + /// Full list of BloonOverlayClasses to try to load this overlay for, defaults to all of them + /// + public virtual IEnumerable BloonOverlayClasses => Enum.GetValues(); + + /// + /// Which overlay type this Overlay uses + /// + public string OverlayType => WorksOnCurrentMelonLoader ? BaseId : BaseOverlay; + + /// + public override IEnumerable Load() => WorksOnCurrentMelonLoader + ? BloonOverlayClasses.Select(bc => + { + try + { + var overlay = (ModBloonOverlay) Activator.CreateInstance(GetType()); + if (overlay != null) + { + overlay.mod = mod; + overlay.OverlayClass = bc; + overlay.hasOverlayClass = true; + return overlay; + } + } + catch (Exception e) + { + ModHelper.Error(e); + } + + ModHelper.Error($"Unable to create {Id} {bc}, does it "); + return null; + }) + : []; + + /// + public override void Register() + { + if (!WorksOnCurrentMelonLoader) + { + ModHelper.Warning("Custom Bloon Overlays sadly can't be added on this version of MelonLoader due to a bug"); + return; + } + + if (!hasOverlayClass) + { + ModHelper.Error("Should not reach point of registering base ModBloonOverlay with no overlay class"); + return; + } + + if (!string.IsNullOrEmpty(BaseOverlay)) + { + if (!AllOverlayTypes.TryGetValue(BaseOverlay, out var baseOverlay)) + { + ModHelper.Error($"Failed to register {Id}, no base overlay {BaseOverlay}"); + return; + } + + if (!baseOverlay.assets.ContainsKey(BaseOverlayClass)) + { + // Not an error, just doesn't have one for this overlay class + return; + } + } + + try + { + var guid = BaseDisplayReference.guidRef; + if (string.IsNullOrEmpty(guid)) + { + ModHelper.Warning($"ModBloonOverlay {Id} has no base display"); + } + } + catch (Exception e) + { + ModHelper.Error($"Unable to get BaseDisplayReference for {Id}"); + ModHelper.Error(e); + return; + } + + base.Register(); + + if (!AllOverlayTypes.TryGetValue(BaseId, out var overlay)) + { + overlay = AllOverlayTypes[BaseId] = ScriptableObject.CreateInstance(); + overlay.displayLayer = DisplayLayer; + overlay.assets = new SerializableDictionary(); + } + + overlay.assets[OverlayClass] = CreatePrefabReference(Id); + } + + /// + /// Applies this overlay to a projectile behavior model + /// + /// model to add to + public virtual void Apply(ProjectileBehaviorWithOverlayModel projectileBehaviorWithOverlayModel) + { + projectileBehaviorWithOverlayModel.overlayType = OverlayType; + } +} \ No newline at end of file diff --git a/BloonsTD6 Mod Helper/Api/Helpers/GameModelExporter.cs b/BloonsTD6 Mod Helper/Api/Helpers/GameModelExporter.cs index 2b8ef12d3..33f99b134 100644 --- a/BloonsTD6 Mod Helper/Api/Helpers/GameModelExporter.cs +++ b/BloonsTD6 Mod Helper/Api/Helpers/GameModelExporter.cs @@ -177,8 +177,8 @@ internal static void ExportAll() } var resourcesPath = Path.Combine(FileIOHelper.sandboxRoot, "resources.json"); - - + + total = success = 0; foreach (var geraldoItem in Game.instance.model.geraldoItemModels) { @@ -187,11 +187,22 @@ internal static void ExportAll() } ModHelper.Log( $"Exported {success}/{total} GeraldoItemModels to {Path.Combine(FileIOHelper.sandboxRoot, "GeraldoItems")}"); - + + + total = success = 0; + foreach (var overlayType in GameData.Instance.bloonOverlays.overlayTypes.keys) + { + if (TryExport(GameData.Instance.bloonOverlays.overlayTypes[overlayType], + $"BloonOverlays/{overlayType}.json")) success++; + total++; + } + ModHelper.Log( + $"Exported {success}/{total} BloonOverlays to {Path.Combine(FileIOHelper.sandboxRoot, "BloonOverlays")}"); + Export(LocalizationManager.Instance.textTable, "textTable.json"); - + Export(Game.instance.model.paragonDegreeDataModel, "paragonDegreeData.json"); - + File.WriteAllText(resourcesPath, resourcesJson.ToString(Formatting.Indented)); ModHelper.Log($"Exported resources to {resourcesPath}"); } @@ -208,7 +219,7 @@ public static bool TryExport(Object data, string path) if (data != null && data.Is(out Model model)) { ModelSerializer.MakeConsistent(model); - } + } #endif FileIOHelper.SaveObject(path, data); return true; diff --git a/BloonsTD6 Mod Helper/Api/Internal/ModContentInstances.cs b/BloonsTD6 Mod Helper/Api/Internal/ModContentInstances.cs index d64c2eadc..fdda06386 100644 --- a/BloonsTD6 Mod Helper/Api/Internal/ModContentInstances.cs +++ b/BloonsTD6 Mod Helper/Api/Internal/ModContentInstances.cs @@ -5,7 +5,6 @@ namespace BTD_Mod_Helper.Api.Internal; internal static class ModContentInstances { - static ModContentInstances() { Instances = new Dictionary>(); @@ -18,19 +17,14 @@ static ModContentInstances() internal static void AddInstance(Type type, IModContent instance) { - AddInstances(type, new List {instance}); + AddInstances(type, [instance]); } - internal static void AddInstances(Type type, List instances) - { - AddInstances(type, instances.Cast().ToList()); - } - - internal static void AddInstances(Type type, List instances) + internal static void AddInstances(Type type, IEnumerable instances) { if (typeof(IModContent).IsAssignableFrom(type)) { - if (!Instances.TryGetValue(type, out var list)) list = Instances[type] = new List(); + if (!Instances.TryGetValue(type, out var list)) list = Instances[type] = []; list.AddRange(instances); if (Actions.TryGetValue(type, out var action)) { @@ -50,7 +44,6 @@ internal static void AddInstances(Type type, List instances) /// internal static class ModContentInstance where T : IModContent { - static ModContentInstance() { ModContentInstances.Actions[typeof(T)] = AddInstances; @@ -62,11 +55,11 @@ static ModContentInstance() internal static T Instance { get; private set; } - internal static List Instances { get; private set; } = new(); + internal static List Instances { get; private set; } = []; private static void AddInstances(IEnumerable instances) { - Instances ??= new List(); + Instances ??= []; Instances.AddRange(instances.Cast()); Instance ??= Instances[0]; } diff --git a/BloonsTD6 Mod Helper/Api/ModContent.cs b/BloonsTD6 Mod Helper/Api/ModContent.cs index 0f0d9c90d..64b583cd3 100644 --- a/BloonsTD6 Mod Helper/Api/ModContent.cs +++ b/BloonsTD6 Mod Helper/Api/ModContent.cs @@ -155,7 +155,7 @@ private static IEnumerable Load(ModContent instance) var content = new List(); try { - content.AddRange(instance.Load()); + content.AddRange(instance.Load().Where(mc => mc != null)); var instances = new List(); foreach (var modContent in content) { diff --git a/BloonsTD6 Mod Helper/Extensions/ModelExtensions/MiscModelExt.cs b/BloonsTD6 Mod Helper/Extensions/ModelExtensions/MiscModelExt.cs index 90c3f631a..a1d873475 100644 --- a/BloonsTD6 Mod Helper/Extensions/ModelExtensions/MiscModelExt.cs +++ b/BloonsTD6 Mod Helper/Extensions/ModelExtensions/MiscModelExt.cs @@ -81,4 +81,12 @@ public static void UpdateOffset(this ParallelEmissionModel parallelEmissionModel { parallelEmissionModel.offsetStart = (1 - parallelEmissionModel.count) * parallelEmissionModel.spreadLength * 0.5f; } + + /// + /// Applies the given ModBloonOverlay to this behavior + /// + public static void ApplyOverlay(this ProjectileBehaviorWithOverlayModel projectileBehaviorWithOverlayModel) where T : ModBloonOverlay + { + ModContent.GetInstance().Apply(projectileBehaviorWithOverlayModel); + } } \ No newline at end of file diff --git a/Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md b/Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md new file mode 100644 index 000000000..f4d52f9db --- /dev/null +++ b/Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md @@ -0,0 +1,168 @@ +#### [BloonsTD6 Mod Helper](README.md 'README') +### [BTD_Mod_Helper.Api.Display](README.md#BTD_Mod_Helper.Api.Display 'BTD_Mod_Helper.Api.Display') + +## ModBloonOverlay Class + +A special ModDisplay for Bloon Overlays. Handles automatically loading different instances of itself for each BloonOverlayClass + +```csharp +public abstract class ModBloonOverlay : BTD_Mod_Helper.Api.Display.ModDisplay +``` + +Inheritance [System.Object](https://docs.microsoft.com/en-us/dotnet/api/System.Object 'System.Object') 🡒 [ModContent](BTD_Mod_Helper.Api.ModContent.md 'BTD_Mod_Helper.Api.ModContent') 🡒 [ModDisplay](BTD_Mod_Helper.Api.Display.ModDisplay.md 'BTD_Mod_Helper.Api.Display.ModDisplay') 🡒 ModBloonOverlay +### Properties + + + +## ModBloonOverlay.AllOverlayTypes Property + +Quick getter for all overlays + +```csharp +protected static SerializableDictionary AllOverlayTypes { get; } +``` + +#### Property Value +[Il2CppNinjaKiwi.Common.SerializableDictionary](https://docs.microsoft.com/en-us/dotnet/api/Il2CppNinjaKiwi.Common.SerializableDictionary 'Il2CppNinjaKiwi.Common.SerializableDictionary') + + + +## ModBloonOverlay.BaseDisplay Property + +
+ If [BaseOverlay](BTD_Mod_Helper.Api.Display.ModBloonOverlay.md#BTD_Mod_Helper.Api.Display.ModBloonOverlay.BaseOverlay 'BTD_Mod_Helper.Api.Display.ModBloonOverlay.BaseOverlay') is defined, will automatically get the correct display for each OverlayClass from there + +```csharp +public override string BaseDisplay { get; } +``` + +#### Property Value +[System.String](https://docs.microsoft.com/en-us/dotnet/api/System.String 'System.String') + + + +## ModBloonOverlay.BaseOverlay Property + +The base Bloon Overlay to copy from. +
+These come from the [Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel.overlayType](https://docs.microsoft.com/en-us/dotnet/api/Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel.overlayType 'Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel.overlayType') fields of certain projectile behavior models +
+To not copy from any Base Overlay, override this to be null/empty and modify [BaseDisplay](BTD_Mod_Helper.Api.Display.ModDisplay.md#BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplay 'BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplay') or [BaseDisplayReference](BTD_Mod_Helper.Api.Display.ModDisplay.md#BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplayReference 'BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplayReference') instead + +```csharp +public virtual string BaseOverlay { get; } +``` + +#### Property Value +[System.String](https://docs.microsoft.com/en-us/dotnet/api/System.String 'System.String') + + + +## ModBloonOverlay.BaseOverlayClass Property + +Lets you control which BloonOverlayClass from the BaseOverlay is used for each BloonOverlayClass of your custom overlay. +By default, uses the same [OverlayClass](BTD_Mod_Helper.Api.Display.ModBloonOverlay.md#BTD_Mod_Helper.Api.Display.ModBloonOverlay.OverlayClass 'BTD_Mod_Helper.Api.Display.ModBloonOverlay.OverlayClass') as the base. +
+To make the non regrow bloons use the same overlays as the regrow bloons + +public override BloonOverlayClass BaseOverlayClass => OverlayClass switch +{ + BloonOverlayClass.Red => BloonOverlayClass.RedRegrow, + BloonOverlayClass.Blue => BloonOverlayClass.BlueRegrow, + BloonOverlayClass.Green => BloonOverlayClass.GreenRegrow, + BloonOverlayClass.Yellow => BloonOverlayClass.YellowRegrow, + BloonOverlayClass.Pink => BloonOverlayClass.PinkRegrow, + BloonOverlayClass.White => BloonOverlayClass.WhiteRegrow, + _ => OverlayClass +}; + + +```csharp +public virtual BloonOverlayClass BaseOverlayClass { get; } +``` + +#### Property Value +[Il2Cpp.BloonOverlayClass](https://docs.microsoft.com/en-us/dotnet/api/Il2Cpp.BloonOverlayClass 'Il2Cpp.BloonOverlayClass') + + + +## ModBloonOverlay.BloonOverlayClasses Property + +Full list of BloonOverlayClasses to try to load this overlay for, defaults to all of them + +```csharp +public virtual System.Collections.Generic.IEnumerable BloonOverlayClasses { get; } +``` + +#### Property Value +[System.Collections.Generic.IEnumerable<](https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IEnumerable-1 'System.Collections.Generic.IEnumerable`1')[Il2Cpp.BloonOverlayClass](https://docs.microsoft.com/en-us/dotnet/api/Il2Cpp.BloonOverlayClass 'Il2Cpp.BloonOverlayClass')[>](https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IEnumerable-1 'System.Collections.Generic.IEnumerable`1') + + + +## ModBloonOverlay.DisplayLayer Property + +The [Il2CppAssets.Scripts.Data.Bloons.BloonOverlayScriptable.displayLayer](https://docs.microsoft.com/en-us/dotnet/api/Il2CppAssets.Scripts.Data.Bloons.BloonOverlayScriptable.displayLayer 'Il2CppAssets.Scripts.Data.Bloons.BloonOverlayScriptable.displayLayer') of the overlay + +```csharp +public virtual int DisplayLayer { get; } +``` + +#### Property Value +[System.Int32](https://docs.microsoft.com/en-us/dotnet/api/System.Int32 'System.Int32') + + + +## ModBloonOverlay.OverlayClass Property + +The overlay class that this is for + +```csharp +public BloonOverlayClass OverlayClass { get; set; } +``` + +#### Property Value +[Il2Cpp.BloonOverlayClass](https://docs.microsoft.com/en-us/dotnet/api/Il2Cpp.BloonOverlayClass 'Il2Cpp.BloonOverlayClass') + + + +## ModBloonOverlay.OverlayType Property + +Which overlay type this Overlay uses + +```csharp +public string OverlayType { get; } +``` + +#### Property Value +[System.String](https://docs.microsoft.com/en-us/dotnet/api/System.String 'System.String') + + + +## ModBloonOverlay.WorksOnCurrentMelonLoader Property + +Whether the current MelonLoader version will support registering a BloonOverlay + +```csharp +public static bool WorksOnCurrentMelonLoader { get; } +``` + +#### Property Value +[System.Boolean](https://docs.microsoft.com/en-us/dotnet/api/System.Boolean 'System.Boolean') +### Methods + + + +## ModBloonOverlay.Apply(ProjectileBehaviorWithOverlayModel) Method + +Applies this overlay to a projectile behavior model + +```csharp +public virtual void Apply(ProjectileBehaviorWithOverlayModel projectileBehaviorWithOverlayModel); +``` +#### Parameters + + + +`projectileBehaviorWithOverlayModel` [Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel](https://docs.microsoft.com/en-us/dotnet/api/Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel 'Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel') + +model to add to \ No newline at end of file diff --git a/Documentation/BTD_Mod_Helper.Api.Display.ModDisplay.md b/Documentation/BTD_Mod_Helper.Api.Display.ModDisplay.md index 764e00700..7d0cbd5e1 100644 --- a/Documentation/BTD_Mod_Helper.Api.Display.ModDisplay.md +++ b/Documentation/BTD_Mod_Helper.Api.Display.ModDisplay.md @@ -13,6 +13,7 @@ Inheritance [System.Object](https://docs.microsoft.com/en-us/dotnet/api/System.O Derived ↳ [ModBloonDisplay](BTD_Mod_Helper.Api.Display.ModBloonDisplay.md 'BTD_Mod_Helper.Api.Display.ModBloonDisplay') +↳ [ModBloonOverlay](BTD_Mod_Helper.Api.Display.ModBloonOverlay.md 'BTD_Mod_Helper.Api.Display.ModBloonOverlay') ↳ [ModCustomDisplay](BTD_Mod_Helper.Api.Display.ModCustomDisplay.md 'BTD_Mod_Helper.Api.Display.ModCustomDisplay') ↳ [ModDisplay2D](BTD_Mod_Helper.Api.Display.ModDisplay2D.md 'BTD_Mod_Helper.Api.Display.ModDisplay2D') ↳ [ModTowerDisplay](BTD_Mod_Helper.Api.Display.ModTowerDisplay.md 'BTD_Mod_Helper.Api.Display.ModTowerDisplay') diff --git a/Documentation/BTD_Mod_Helper.Extensions.MiscModelExt.md b/Documentation/BTD_Mod_Helper.Extensions.MiscModelExt.md index 70834db5f..d3a65a038 100644 --- a/Documentation/BTD_Mod_Helper.Extensions.MiscModelExt.md +++ b/Documentation/BTD_Mod_Helper.Extensions.MiscModelExt.md @@ -54,6 +54,27 @@ public static void ApplyDisplay(this EffectModel effectModel) `effectModel` [Il2CppAssets.Scripts.Models.Effects.EffectModel](https://docs.microsoft.com/en-us/dotnet/api/Il2CppAssets.Scripts.Models.Effects.EffectModel 'Il2CppAssets.Scripts.Models.Effects.EffectModel') + + +## MiscModelExt.ApplyOverlay(this ProjectileBehaviorWithOverlayModel) Method + +Applies the given ModBloonOverlay to this behavior + +```csharp +public static void ApplyOverlay(this ProjectileBehaviorWithOverlayModel projectileBehaviorWithOverlayModel) + where T : BTD_Mod_Helper.Api.Display.ModBloonOverlay; +``` +#### Type parameters + + + +`T` +#### Parameters + + + +`projectileBehaviorWithOverlayModel` [Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel](https://docs.microsoft.com/en-us/dotnet/api/Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel 'Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel') + ## MiscModelExt.GetDamageMult(this DamageModifierModel, Bloon) Method diff --git a/Documentation/README.md b/Documentation/README.md index d095d0938..748ac1726 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -133,6 +133,7 @@ | [ModBloonCustomDisplay<T>](BTD_Mod_Helper.Api.Display.ModBloonCustomDisplay_T_.md 'BTD_Mod_Helper.Api.Display.ModBloonCustomDisplay') | A ModCustomDisplay that will automatically apply to a ModBloon | | [ModBloonDisplay](BTD_Mod_Helper.Api.Display.ModBloonDisplay.md 'BTD_Mod_Helper.Api.Display.ModBloonDisplay') | A ModDisplay that will automatically apply to a ModBloon | | [ModBloonDisplay<T>](BTD_Mod_Helper.Api.Display.ModBloonDisplay_T_.md 'BTD_Mod_Helper.Api.Display.ModBloonDisplay') | A convenient generic class for applying a ModBloonDisplay to a ModBloon | +| [ModBloonOverlay](BTD_Mod_Helper.Api.Display.ModBloonOverlay.md 'BTD_Mod_Helper.Api.Display.ModBloonOverlay') | A special ModDisplay for Bloon Overlays. Handles automatically loading different instances of itself for each BloonOverlayClass | | [ModBuffIcon](BTD_Mod_Helper.Api.Display.ModBuffIcon.md 'BTD_Mod_Helper.Api.Display.ModBuffIcon') | Class for adding a new buff icon that can be displayed for towers | | [ModCustomDisplay](BTD_Mod_Helper.Api.Display.ModCustomDisplay.md 'BTD_Mod_Helper.Api.Display.ModCustomDisplay') | The custom version of a ModDisplay that loads in a model from a unity assetbundle | | [ModDisplay](BTD_Mod_Helper.Api.Display.ModDisplay.md 'BTD_Mod_Helper.Api.Display.ModDisplay') | A custom Display that is a copy of an existing Display that can be modified | From a7818ec4e036316e5b9f2c77e7229c7459189d46 Mon Sep 17 00:00:00 2001 From: doombubbles Date: Tue, 22 Oct 2024 22:39:46 -0700 Subject: [PATCH 3/6] Fix AudioClip duration --- BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs | 10 ++++++---- BloonsTD6 Mod Helper/Api/Internal/ResourceHandler.cs | 8 ++++---- BloonsTD6 Mod Helper/LATEST.md | 6 +++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs b/BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs index a0b7e6e74..056da1736 100644 --- a/BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs +++ b/BloonsTD6 Mod Helper/Api/Display/ModBloonOverlay.cs @@ -26,7 +26,7 @@ public abstract class ModBloonOverlay : ModDisplay /// protected static SerializableDictionary AllOverlayTypes => GameData.Instance.bloonOverlays.overlayTypes; - + /// /// The overlay class that this is for /// @@ -37,7 +37,7 @@ public abstract class ModBloonOverlay : ModDisplay ///
/// These come from the fields of certain projectile behavior models ///
- /// To not copy from any Base Overlay, override this to be null/empty and modify or instead + /// To not copy from any Base Overlay, keep this as null/empty and modify or instead /// public virtual string BaseOverlay => null; @@ -91,8 +91,10 @@ public abstract class ModBloonOverlay : ModDisplay /// Which overlay type this Overlay uses /// public string OverlayType => WorksOnCurrentMelonLoader ? BaseId : BaseOverlay; - - /// + + /// + /// Load different instances of this type for each difference BloonOverlayClass within + /// public override IEnumerable Load() => WorksOnCurrentMelonLoader ? BloonOverlayClasses.Select(bc => { diff --git a/BloonsTD6 Mod Helper/Api/Internal/ResourceHandler.cs b/BloonsTD6 Mod Helper/Api/Internal/ResourceHandler.cs index 1666e1310..48fe42420 100644 --- a/BloonsTD6 Mod Helper/Api/Internal/ResourceHandler.cs +++ b/BloonsTD6 Mod Helper/Api/Internal/ResourceHandler.cs @@ -153,9 +153,9 @@ public static AudioClip CreateAudioClip(WaveFileReader reader, string id) var totalSamples = (int) (reader.SampleCount * waveFormat.Channels); var data = new float[totalSamples]; - reader.ToSampleProvider().Read(data, 0, totalSamples); + var readSamples = reader.ToSampleProvider().Read(data, 0, totalSamples); - var audioClip = AudioClip.Create(id, totalSamples, waveFormat.Channels, waveFormat.SampleRate, false); + var audioClip = AudioClip.Create(id, readSamples / 2, waveFormat.Channels, waveFormat.SampleRate, false); if (audioClip.SetData(data, 0)) { @@ -188,9 +188,9 @@ public static AudioClip CreateAudioClip(Mp3FileReader reader, string id) var totalSamples = (int) (reader.Length / (waveFormat.BitsPerSample / 8)) * waveFormat.Channels; var data = new float[totalSamples]; - reader.ToSampleProvider().Read(data, 0, totalSamples); + var readSamples = reader.ToSampleProvider().Read(data, 0, totalSamples); - var audioClip = AudioClip.Create(id, totalSamples, waveFormat.Channels, waveFormat.SampleRate, false); + var audioClip = AudioClip.Create(id, readSamples / 2, waveFormat.Channels, waveFormat.SampleRate, false); if (audioClip.SetData(data, 0)) { diff --git a/BloonsTD6 Mod Helper/LATEST.md b/BloonsTD6 Mod Helper/LATEST.md index 8ffaf9412..3451325d1 100644 --- a/BloonsTD6 Mod Helper/LATEST.md +++ b/BloonsTD6 Mod Helper/LATEST.md @@ -1,3 +1,3 @@ -- Fixed a TimeManager patch for BTD6 v45.2 (fixes Faster Forward) -- Made some methods in ResourceHandler public -- Fixed a bug with the Shift+Shift to open console setting \ No newline at end of file +- Added a ModBloonOverlay class for making custom Bloon Overlays + - NOTE: Due to a MelonLoader bug, these won't properly display unless you're on ML 0.6.6 or higher +- Accounted for a Unity bug that was affecting the internal durations of embedded AudioClips \ No newline at end of file From 5797a36ff63a18c93861a19e4629f7a832a48acb Mon Sep 17 00:00:00 2001 From: doombubbles Date: Wed, 23 Oct 2024 17:06:23 -0700 Subject: [PATCH 4/6] Update wiki sidebars --- Website/src/components/helmet.tsx | 25 ++++++------ Website/src/components/layout.tsx | 3 +- Website/src/components/markdown-layout.tsx | 44 +++++++++++----------- Website/src/components/markdown.tsx | 15 +++++++- Website/src/css/colors.module.scss | 0 Website/src/css/global.scss | 18 +++++++++ Website/src/pages/_app.tsx | 10 +++-- 7 files changed, 76 insertions(+), 39 deletions(-) delete mode 100644 Website/src/css/colors.module.scss diff --git a/Website/src/components/helmet.tsx b/Website/src/components/helmet.tsx index 18fdc6463..12d971315 100644 --- a/Website/src/components/helmet.tsx +++ b/Website/src/components/helmet.tsx @@ -15,20 +15,16 @@ const ModHelperHelmet: FunctionComponent = ({ {title && {title}} {description && ( - + )} = ({ rel={"icon"} href={`${process.env.NEXT_PUBLIC_BASE_PATH}/images/ModHelper.ico`} /> - {title && } + {title && } {description && ( )} - + + ); diff --git a/Website/src/components/layout.tsx b/Website/src/components/layout.tsx index 1c20c44d3..0c3cc5cd9 100644 --- a/Website/src/components/layout.tsx +++ b/Website/src/components/layout.tsx @@ -4,9 +4,7 @@ import React, { FunctionComponent, HTMLAttributes, PropsWithChildren, - useRef, } from "react"; -import BackgroundImage, { backgroundOnScroll } from "./background-image"; import { ScrollbarProps, Scrollbars } from "react-custom-scrollbars-2"; import { use100vh } from "react-div-100vh"; import { ModHelperFooter, ModHelperNavBar } from "./navbar"; @@ -25,6 +23,7 @@ export const ModHelperScrollBars = forwardRef( universal autoHeight autoHide + hideTracksWhenNotNeeded={true} autoHideTimeout={1000} autoHideDuration={200} renderTrackVertical={({ style, ...props }) => ( diff --git a/Website/src/components/markdown-layout.tsx b/Website/src/components/markdown-layout.tsx index 0d70f12f6..e3bc88bd7 100644 --- a/Website/src/components/markdown-layout.tsx +++ b/Website/src/components/markdown-layout.tsx @@ -1,5 +1,4 @@ import React, { - createElement, Fragment, FunctionComponent, PropsWithChildren, @@ -7,20 +6,16 @@ import React, { useMemo, useState, } from "react"; -import { Helmet } from "react-helmet"; import { Button, Container, Offcanvas } from "react-bootstrap"; import { List, ListUl } from "react-bootstrap-icons"; import { OffcanvasPlacement } from "react-bootstrap/Offcanvas"; -import Layout, { switchSize } from "./layout"; +import Layout, { ModHelperScrollBars, switchSize } from "./layout"; import { MainContentMarker } from "./skip-link"; -import { rehype } from "rehype"; -import rehypeParse from "rehype-parse"; -import rehypeReact, { Options } from "rehype-react"; -import Link from "next/link"; import { getMarkdownContent } from "../lib/markdown"; import { htmlToReact } from "./markdown"; import ModHelperHelmet from "./helmet"; import cx from "classnames"; +import { use100vh } from "react-div-100vh"; const ModHelperOffCanvas: FunctionComponent< PropsWithChildren<{ @@ -30,28 +25,35 @@ const ModHelperOffCanvas: FunctionComponent< placement: OffcanvasPlacement; }> > = ({ show, setShowing, title, placement, children }) => { + const maxHeight = use100vh() || "100vh"; + return ( setShowing(false)} scroll={true} - className={"main-panel w-auto btd6-panel blue"} + className={ + "main-panel w-auto btd6-panel blue p-0 overflow-hidden sticky-top-lg" + } restoreFocus={false} placement={placement} + style={{ maxHeight }} > - - {title} - - - {children} - + + + {title} + + + {children} + + ); }; @@ -148,7 +150,7 @@ export const MarkdownLayout: FunctionComponent<
- {data?.title} + {data?.title?.replace(/\./g, "\u200B.")}
{sidebar && ( diff --git a/Website/src/components/markdown.tsx b/Website/src/components/markdown.tsx index f7630b2fc..2d99bcbf4 100644 --- a/Website/src/components/markdown.tsx +++ b/Website/src/components/markdown.tsx @@ -17,6 +17,7 @@ import rehypeSanitize from "rehype-sanitize"; import { HtmlElementNode, TextNode, toc } from "@jsdevtools/rehype-toc"; import { Parent } from "unist"; import cx from "classnames"; +import { Link45deg } from "react-bootstrap-icons"; const rewrittenUrl = (href: string, basePath?: string) => { // Migrate Wiki links @@ -98,7 +99,11 @@ export const markdownToHtml = (linkBasePath?: string, imgBasePath?: string) => }) .use(rehypeSlug) .use(rehypeAutolinkHeadings, { - properties: { "aria-hidden": "true", tabIndex: -1, className: [""] }, + properties: { + "aria-hidden": "true", + tabIndex: -1, + className: ["position-relative"], + }, }) .use(rehypeRewrite, rewrite(linkBasePath, imgBasePath)) .use(rehypeStringify); @@ -121,6 +126,14 @@ export const htmlToReact = (sanitize?: boolean) => ) : ( ), + span: ({ className, children, ...props }) => + className === "icon icon-link" && !children ? ( + + ) : ( + + ), }, } as Options); diff --git a/Website/src/css/colors.module.scss b/Website/src/css/colors.module.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/Website/src/css/global.scss b/Website/src/css/global.scss index 5bcc8fe3e..b935bdd54 100644 --- a/Website/src/css/global.scss +++ b/Website/src/css/global.scss @@ -143,4 +143,22 @@ input::placeholder { .nav-link { user-select: none; +} + +@media (min-width: 992px) { + .sticky-top-lg { + //noinspection CssInvalidPropertyValue + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +.hide-unless-hover { + opacity: 0; +} + +:is(h1:hover,h2:hover,h3:hover,h4:hover,h5:hover,h6:hover) .hide-unless-hover { + opacity: 1; } \ No newline at end of file diff --git a/Website/src/pages/_app.tsx b/Website/src/pages/_app.tsx index 7837e3895..9abe3a267 100644 --- a/Website/src/pages/_app.tsx +++ b/Website/src/pages/_app.tsx @@ -10,11 +10,10 @@ import { use100vh } from "react-div-100vh"; import BackgroundImage, { backgroundOnScroll, } from "../components/background-image"; -import cx from "classnames"; -import SkipLink from "../components/skip-link"; -import { ModHelperFooter, ModHelperNavBar } from "../components/navbar"; import { ModHelperScrollBars, ScrollBarsContext } from "../components/layout"; import { Scrollbars } from "react-custom-scrollbars-2"; +import { usePathname } from "next/navigation"; +import { useUpdateEffect } from "react-use"; const DefaultTitle = "BTD Mod Helper"; const DefaultDescription = @@ -24,6 +23,11 @@ export default ({ Component, pageProps }: AppProps) => { const height = use100vh() ?? 1000; const scrollbars = useRef(null); const background = useRef(null); + const pathname = usePathname(); + + useUpdateEffect(() => { + scrollbars.current?.scrollToTop(); + }, [pathname]); return ( <> From 320588df29084095c07ef897e1766f5ac630e083 Mon Sep 17 00:00:00 2001 From: doombubbles Date: Wed, 23 Oct 2024 18:13:34 -0700 Subject: [PATCH 5/6] Sprite.TrySaveToPNG --- .../Api/Commands/ExportDisplayCommand.cs | 34 +++++++++++++++--- .../Extensions/UnityExtensions/RendererExt.cs | 31 +++++++++++++--- .../Extensions/UnityExtensions/SpriteExt.cs | 36 +++++++++++++++++-- .../UnityExtensions/Texture2DExt.cs | 5 ++- BloonsTD6 Mod Helper/LATEST.md | 3 +- 5 files changed, 94 insertions(+), 15 deletions(-) diff --git a/BloonsTD6 Mod Helper/Api/Commands/ExportDisplayCommand.cs b/BloonsTD6 Mod Helper/Api/Commands/ExportDisplayCommand.cs index ac7c2f0dd..7315b149f 100644 --- a/BloonsTD6 Mod Helper/Api/Commands/ExportDisplayCommand.cs +++ b/BloonsTD6 Mod Helper/Api/Commands/ExportDisplayCommand.cs @@ -5,14 +5,18 @@ using Il2CppAssets.Scripts.Models.GenericBehaviors; using Il2CppAssets.Scripts.Unity; using Il2CppAssets.Scripts.Unity.Display; +using Il2CppAssets.Scripts.Unity.Display.Animation; using Il2CppAssets.Scripts.Unity.UI_New.InGame.TowerSelectionMenu; using Il2CppNinjaKiwi.Common.ResourceUtils; +using UnityEngine; namespace BTD_Mod_Helper.Api.Commands; internal class ExportDisplayCommand : ModCommand { public override string Command => "display"; - public override string Help => "Exports the textures and UnityDisplayNode information for the selected tower / other GUIDs"; + + public override string Help => + "Exports the textures and UnityDisplayNode information for the selected tower / other GUIDs"; [Option('o', "open", Default = false, HelpText = "Also open the folder where pngs is exported")] public bool Open { get; set; } @@ -54,10 +58,32 @@ private void Export(UnityDisplayNode node) { if (renderer?.material?.mainTexture == null) continue; - var path = Path.Combine(FileIOHelper.sandboxRoot, renderer.name + ".png"); - renderer.material.mainTexture.TrySaveToPNG(path); + if (renderer.gameObject.HasComponent(out CustomSpriteFrameAnimator customSpriteFrameAnimator)) + { + var i = 0; + foreach (var frame in customSpriteFrameAnimator.frames) + { + var path = Path.Combine(FileIOHelper.sandboxRoot, renderer.name + ".png"); + frame.TrySaveToPNG(path); + ModHelper.Msg($"Saved {path}"); + i++; + } + } + else + { + var path = Path.Combine(FileIOHelper.sandboxRoot, renderer.name + ".png"); + if (renderer.Is(out SpriteRenderer spriteRenderer)) + { + spriteRenderer.sprite.texture?.TrySaveToPNG(path); + } + else + { + renderer.material.mainTexture.TrySaveToPNG(path); + } + + ModHelper.Msg($"Saved {path}"); + } - ModHelper.Msg($"Saved {path}"); } node.PrintInfo(); diff --git a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/RendererExt.cs b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/RendererExt.cs index 5980f42b9..ff1013ddd 100644 --- a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/RendererExt.cs +++ b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/RendererExt.cs @@ -151,16 +151,39 @@ public static void ApplyOutlineShader(this Renderer renderer) /// public static void ApplyCustomShader(this Renderer renderer, CustomShader customShader, - Action modifyMaterial = null) => + Action modifyMaterial = null) + { + if (renderer.material?.mainTexture == null) + { + ModHelper.Warning("Can't ApplyCustomShader, renderer has no material texture"); + return; + } renderer.material.mainTexture = renderer.material.mainTexture.ApplyCustomShader(customShader, modifyMaterial); + } /// public static void ReplaceColor(this Renderer renderer, Color targetColor, Color replacementColor, - float threshold = 0.05f) => + float threshold = 0.05f) + { + if (renderer.material?.mainTexture == null) + { + ModHelper.Warning("Can't ReplaceColor, renderer has no material texture"); + return; + } renderer.material.mainTexture = renderer.material.mainTexture.ReplaceColor(targetColor, replacementColor, threshold); + } /// public static void AdjustHSV(this Renderer renderer, float hueAdjust, float saturationAdjust, float valueAdjust, - Color? targetColor = null, float threshold = 0.05f) => renderer.material.mainTexture = - renderer.material.mainTexture.AdjustHSV(hueAdjust, saturationAdjust, valueAdjust, targetColor, threshold); + Color? targetColor = null, float threshold = 0.05f) + { + if (renderer.material?.mainTexture == null) + { + ModHelper.Warning("Can't AdjustHSV, renderer has no material texture"); + return; + } + renderer.material.mainTexture = + renderer.material.mainTexture.AdjustHSV(hueAdjust, saturationAdjust, valueAdjust, targetColor, threshold); + } + } \ No newline at end of file diff --git a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs index 2cc17350d..f2cd46d5d 100644 --- a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs +++ b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs @@ -1,4 +1,6 @@ -using UnityEngine; +using System; +using System.IO; +using UnityEngine; namespace BTD_Mod_Helper.Extensions; /// @@ -13,7 +15,35 @@ public static class SpriteExt /// public static void SetTexture(this Sprite sprite, Texture2D newTexture) { - var bytes = ImageConversion.EncodeToPNG(newTexture); - ImageConversion.LoadImage(sprite.texture, bytes); + sprite.texture.LoadImage(newTexture.EncodeToPNG()); } + + /// + /// Attempts to save a Sprite to a PNG at the given filePath, even if it isn't marked as readable + /// + public static void TrySaveToPNG(this Sprite sprite, string filePath) + { + try + { + var texture = sprite.texture; + var spriteRect = sprite.textureRect; + var tmp = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.Default, + RenderTextureReadWrite.Linear); + Graphics.Blit(texture, tmp); + var previous = RenderTexture.active; + RenderTexture.active = tmp; + var myTexture2D = new Texture2D((int) spriteRect.width, (int) spriteRect.height); + myTexture2D.ReadPixels(new Rect(spriteRect.x, spriteRect.y, spriteRect.width, spriteRect.height), 0, 0); + myTexture2D.Apply(); + RenderTexture.active = previous; + RenderTexture.ReleaseTemporary(tmp); + var bytes = myTexture2D.EncodeToPNG(); + File.WriteAllBytes(filePath, bytes); + } + catch (Exception e) + { + ModHelper.Warning(e); + } + } + } \ No newline at end of file diff --git a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/Texture2DExt.cs b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/Texture2DExt.cs index 2f6930e7a..16f3a2fb4 100644 --- a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/Texture2DExt.cs +++ b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/Texture2DExt.cs @@ -66,8 +66,7 @@ public static void TrySaveToPNG(this Texture texture, string filePath) } catch (Exception e) { - Console.WriteLine(e); - throw; + ModHelper.Warning(e); } } @@ -156,7 +155,7 @@ public static RenderTexture AdjustHSV(this Texture texture, float hueAdjust, flo material.SetFloat(SaturationAdjust, saturationAdjust); material.SetFloat(ValueAdjust, valueAdjust); material.SetColor(TargetColor, targetColor ?? Color.white); - material.SetFloat(Threshold, threshold); + material.SetFloat(Threshold, targetColor == null ? 1 :threshold); }); } \ No newline at end of file diff --git a/BloonsTD6 Mod Helper/LATEST.md b/BloonsTD6 Mod Helper/LATEST.md index 3451325d1..2758f5e68 100644 --- a/BloonsTD6 Mod Helper/LATEST.md +++ b/BloonsTD6 Mod Helper/LATEST.md @@ -1,3 +1,4 @@ - Added a ModBloonOverlay class for making custom Bloon Overlays - NOTE: Due to a MelonLoader bug, these won't properly display unless you're on ML 0.6.6 or higher -- Accounted for a Unity bug that was affecting the internal durations of embedded AudioClips \ No newline at end of file +- Accounted for a Unity bug that was affecting the internal durations of embedded AudioClips +- Added a Sprite.TrySaveToPNG() extension like the one for Textures except accounting for position and size within a larger texture atlas \ No newline at end of file From ac7d1c2478a96187bfb1fc416a3d161855189fd9 Mon Sep 17 00:00:00 2001 From: doombubbles Date: Wed, 23 Oct 2024 18:24:24 -0700 Subject: [PATCH 6/6] Add ExportImageCommand --- .../Api/Commands/ExportImageCommand.cs | 54 +++++++++++++++++++ .../Extensions/UnityExtensions/SpriteExt.cs | 4 +- BloonsTD6 Mod Helper/LATEST.md | 3 +- ..._Mod_Helper.Api.Display.ModBloonOverlay.md | 17 +++++- .../BTD_Mod_Helper.Extensions.SpriteExt.md | 24 ++++++++- 5 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 BloonsTD6 Mod Helper/Api/Commands/ExportImageCommand.cs diff --git a/BloonsTD6 Mod Helper/Api/Commands/ExportImageCommand.cs b/BloonsTD6 Mod Helper/Api/Commands/ExportImageCommand.cs new file mode 100644 index 000000000..7d560fd3d --- /dev/null +++ b/BloonsTD6 Mod Helper/Api/Commands/ExportImageCommand.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using BTD_Mod_Helper.Api.Helpers; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; +namespace BTD_Mod_Helper.Api.Commands; + +internal class ExportImageCommand : ModCommand +{ + public override string Command => "image"; + public override string Help => "Exports UI images raycasted from the current mouse position"; + + public override bool Execute(ref string resultText) + { + try + { + var exported = new List(); + var raycastResults = new Il2CppSystem.Collections.Generic.List(); + EventSystem.current.RaycastAll(new PointerEventData(EventSystem.current) + { + position = Input.mousePosition + }, raycastResults); + + var imagesFolder = Path.Combine(FileIOHelper.sandboxRoot, "Images"); + + foreach (var result in raycastResults) + { + if (result.gameObject.Is(out var gameObject) && + gameObject.HasComponent(out Image image) && + image.sprite != null) + { + var path = Path.Combine(imagesFolder, image.sprite.name + ".png"); + if (image.sprite.TrySaveToPNG(path)) + { + ModHelper.Msg($"Exported {path}"); + exported.Add(image.sprite.name); + } + } + } + + resultText = $"Exported {exported.Join()} to {imagesFolder}"; + + } + catch (Exception e) + { + resultText = e.Message; + ModHelper.Warning(e); + } + + return true; + } +} \ No newline at end of file diff --git a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs index f2cd46d5d..115a44f08 100644 --- a/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs +++ b/BloonsTD6 Mod Helper/Extensions/UnityExtensions/SpriteExt.cs @@ -21,7 +21,7 @@ public static void SetTexture(this Sprite sprite, Texture2D newTexture) /// /// Attempts to save a Sprite to a PNG at the given filePath, even if it isn't marked as readable /// - public static void TrySaveToPNG(this Sprite sprite, string filePath) + public static bool TrySaveToPNG(this Sprite sprite, string filePath) { try { @@ -39,10 +39,12 @@ public static void TrySaveToPNG(this Sprite sprite, string filePath) RenderTexture.ReleaseTemporary(tmp); var bytes = myTexture2D.EncodeToPNG(); File.WriteAllBytes(filePath, bytes); + return true; } catch (Exception e) { ModHelper.Warning(e); + return false; } } diff --git a/BloonsTD6 Mod Helper/LATEST.md b/BloonsTD6 Mod Helper/LATEST.md index 2758f5e68..16f7e01fd 100644 --- a/BloonsTD6 Mod Helper/LATEST.md +++ b/BloonsTD6 Mod Helper/LATEST.md @@ -1,4 +1,5 @@ - Added a ModBloonOverlay class for making custom Bloon Overlays - NOTE: Due to a MelonLoader bug, these won't properly display unless you're on ML 0.6.6 or higher - Accounted for a Unity bug that was affecting the internal durations of embedded AudioClips -- Added a Sprite.TrySaveToPNG() extension like the one for Textures except accounting for position and size within a larger texture atlas \ No newline at end of file +- Added a Sprite.TrySaveToPNG() extension like the one for Textures except accounting for position and size within a larger texture atlas +- Added `export image` console commands that exports all UI images underneath your mouse cursor to png files \ No newline at end of file diff --git a/Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md b/Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md index f4d52f9db..8a448dd0e 100644 --- a/Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md +++ b/Documentation/BTD_Mod_Helper.Api.Display.ModBloonOverlay.md @@ -47,7 +47,7 @@ The base Bloon Overlay to copy from.
These come from the [Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel.overlayType](https://docs.microsoft.com/en-us/dotnet/api/Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel.overlayType 'Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel.overlayType') fields of certain projectile behavior models
-To not copy from any Base Overlay, override this to be null/empty and modify [BaseDisplay](BTD_Mod_Helper.Api.Display.ModDisplay.md#BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplay 'BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplay') or [BaseDisplayReference](BTD_Mod_Helper.Api.Display.ModDisplay.md#BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplayReference 'BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplayReference') instead +To not copy from any Base Overlay, keep this as null/empty and modify [BaseDisplay](BTD_Mod_Helper.Api.Display.ModDisplay.md#BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplay 'BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplay') or [BaseDisplayReference](BTD_Mod_Helper.Api.Display.ModDisplay.md#BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplayReference 'BTD_Mod_Helper.Api.Display.ModDisplay.BaseDisplayReference') instead ```csharp public virtual string BaseOverlay { get; } @@ -165,4 +165,17 @@ public virtual void Apply(ProjectileBehaviorWithOverlayModel projectileBehaviorW `projectileBehaviorWithOverlayModel` [Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel](https://docs.microsoft.com/en-us/dotnet/api/Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel 'Il2CppAssets.Scripts.Models.Towers.Projectiles.ProjectileBehaviorWithOverlayModel') -model to add to \ No newline at end of file +model to add to + +
+ +## ModBloonOverlay.Load() Method + +Load different instances of this type for each difference BloonOverlayClass within [BloonOverlayClasses](BTD_Mod_Helper.Api.Display.ModBloonOverlay.md#BTD_Mod_Helper.Api.Display.ModBloonOverlay.BloonOverlayClasses 'BTD_Mod_Helper.Api.Display.ModBloonOverlay.BloonOverlayClasses') + +```csharp +public override System.Collections.Generic.IEnumerable Load(); +``` + +#### Returns +[System.Collections.Generic.IEnumerable<](https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IEnumerable-1 'System.Collections.Generic.IEnumerable`1')[ModContent](BTD_Mod_Helper.Api.ModContent.md 'BTD_Mod_Helper.Api.ModContent')[>](https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.IEnumerable-1 'System.Collections.Generic.IEnumerable`1') \ No newline at end of file diff --git a/Documentation/BTD_Mod_Helper.Extensions.SpriteExt.md b/Documentation/BTD_Mod_Helper.Extensions.SpriteExt.md index 033324879..8d41d23a6 100644 --- a/Documentation/BTD_Mod_Helper.Extensions.SpriteExt.md +++ b/Documentation/BTD_Mod_Helper.Extensions.SpriteExt.md @@ -29,4 +29,26 @@ public static void SetTexture(this Sprite sprite, Texture2D newTexture); -`newTexture` [UnityEngine.Texture2D](https://docs.microsoft.com/en-us/dotnet/api/UnityEngine.Texture2D 'UnityEngine.Texture2D') \ No newline at end of file +`newTexture` [UnityEngine.Texture2D](https://docs.microsoft.com/en-us/dotnet/api/UnityEngine.Texture2D 'UnityEngine.Texture2D') + + + +## SpriteExt.TrySaveToPNG(this Sprite, string) Method + +Attempts to save a Sprite to a PNG at the given filePath, even if it isn't marked as readable + +```csharp +public static bool TrySaveToPNG(this Sprite sprite, string filePath); +``` +#### Parameters + + + +`sprite` [UnityEngine.Sprite](https://docs.microsoft.com/en-us/dotnet/api/UnityEngine.Sprite 'UnityEngine.Sprite') + + + +`filePath` [System.String](https://docs.microsoft.com/en-us/dotnet/api/System.String 'System.String') + +#### Returns +[System.Boolean](https://docs.microsoft.com/en-us/dotnet/api/System.Boolean 'System.Boolean') \ No newline at end of file