using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BTD_Mod_Helper; using BTD_Mod_Helper.Api; using BTD_Mod_Helper.Api.ModOptions; using BTD_Mod_Helper.Extensions; using CopyPasteTowers; using HarmonyLib; using Il2CppAssets.Scripts; using Il2CppAssets.Scripts.Models; using Il2CppAssets.Scripts.Models.Entities; using Il2CppAssets.Scripts.Models.Powers; using Il2CppAssets.Scripts.Models.Towers; using Il2CppAssets.Scripts.Models.Towers.Upgrades; using Il2CppAssets.Scripts.Simulation; using Il2CppAssets.Scripts.Simulation.Input; using Il2CppAssets.Scripts.Simulation.Objects; using Il2CppAssets.Scripts.Simulation.SMath; using Il2CppAssets.Scripts.Simulation.Towers; using Il2CppAssets.Scripts.Simulation.Towers.Behaviors; using Il2CppAssets.Scripts.Unity; using Il2CppAssets.Scripts.Unity.Bridge; using Il2CppAssets.Scripts.Unity.UI_New.InGame; using Il2CppInterop.Runtime.InteropTypes; using Il2CppNinjaKiwi.Localization; using Il2CppSystem.Collections.Generic; using MelonLoader; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(CopyPasteTowersMod), "Copy/Paste Towers", "1.0.11", "doombubbles", null)] [assembly: MelonGame("Ninja Kiwi", "BloonsTD6")] [assembly: MelonGame("Ninja Kiwi", "BloonsTD6-Epic")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("CopyPasteTowers")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a6c20ab1e0febea499b152f7eef6d357f462fca7")] [assembly: AssemblyProduct("CopyPasteTowers")] [assembly: AssemblyTitle("CopyPasteTowers")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CopyPasteTowers { public class CopyPasteTowersMod : BloonsTD6Mod { public static readonly ModSettingHotkey CopyTower = new ModSettingHotkey((KeyCode)99, (HotkeyModifier)3); public static readonly ModSettingHotkey PasteTower = new ModSettingHotkey((KeyCode)118, (HotkeyModifier)3); public static readonly ModSettingHotkey CutTower = new ModSettingHotkey((KeyCode)120, (HotkeyModifier)3); public static Instance MelonLogger => ((MelonBase)ModContent.GetInstance()).LoggerInstance; public override void OnUpdate() { CopyPasteTowersUtility.Update(); } } public class CopyPasteTowersUtility { [HarmonyPatch(typeof(Tower), "OnPlace")] private static class Tower_OnPlace { [HarmonyPrefix] private static void Prefix(Tower __instance) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) overrideNonPower = false; bool flag = false; if (!nextPlaceIsPaste) { return; } nextPlaceIsPaste = false; foreach (BloonsMod mod in ModHelper.Mods) { mod.Call("OnTowerPasted", new object[2] { __instance, flag }); } __instance.worth = CalculateCost(__instance); InGameExt.AddCash(InGame.instance, (double)(0f - __instance.worth + __instance.towerModel.cost)); justPastedTower = true; if (lastCopyWasCut) { TowerToSimulation towerToSim = TowerExt.GetTowerToSim(__instance); TowerSelectionMenuExt.instance.SelectTower(towerToSim); } if (targetType != null) { __instance.SetTargetType(targetType); } if (__instance.towerModel.isParagon && lastDegree.HasValue) { ParagonTower behavior = ((RootBehavior)__instance).entity.GetBehavior(); behavior.investmentInfo = lastDegree.Value; behavior.UpdateDegree(); behavior.PlayParagonUpgradeSound(); behavior.Finish(); } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] internal static class TowerModel_isPowerTower { [HarmonyPostfix] internal static void Postfix(TowerModel __instance) { if (overrideNonPower) { lastCheckedPower = __instance; } } } [HarmonyPatch(typeof(GameModel), "GetPowerWithId")] internal static class GameModel_GetPowerWithId { [HarmonyPostfix] internal static void Postfix(ref PowerModel __result) { if (overrideNonPower && lastCheckedPower != null) { __result = ModelExt.Duplicate(__result); __result.tower = lastCheckedPower; overrideNonPower = false; lastCheckedPower = null; } } } [HarmonyPatch(typeof(TimeManager), "ResetNow")] internal static class TimeManager_ResetNow { [HarmonyPostfix] internal static void Postfix() { clipboard = null; rootClipboard = null; foreach (BloonsMod mod in ModHelper.Mods) { mod.Call("OnClipboardCleared", Array.Empty()); } } } private static TowerModel? clipboard; private static TowerModel? rootClipboard; private static double baseCost; private static double cost; private static bool nextPlaceIsPaste; private static bool justPastedTower; private static bool lastCopyWasCut; private static TargetType? targetType; private static InvestmentInfo? lastDegree; private static bool overrideNonPower; private static TowerModel? lastCheckedPower; public static void Update() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)InGame.instance) || InGame.Bridge == null || InGame.instance.ReviewMapMode || InGame.Bridge.IsSpectatorMode || (int)InGame.instance.GameType == 13) { return; } if (Object.op_Implicit((Object)(object)TowerSelectionMenuExt.instance)) { TowerToSimulation selectedTower = TowerSelectionMenuExt.instance.selectedTower; TowerModel val = ((selectedTower != null) ? selectedTower.Def : null); if (val != null && !val.isSubTower && (ModHelper.HasMod("Unlimited5thTiers") || (!val.IsHero() && !val.isParagon)) && ((Model)val).name.StartsWith(((EntityModel)val).baseId)) { lastCopyWasCut = CopyPasteTowersMod.CutTower.JustPressed(); if (CopyPasteTowersMod.CutTower.JustPressed() || CopyPasteTowersMod.CopyTower.JustPressed()) { Copy(selectedTower.tower); if (CopyPasteTowersMod.CutTower.JustPressed()) { TowerSelectionMenuExt.instance.Sell(); lastCopyWasCut = true; } else { lastCopyWasCut = false; } } } } if (CopyPasteTowersMod.PasteTower.JustPressed() || justPastedTower) { Paste(); } justPastedTower = false; } private static void Copy(Tower tower) { //IL_014e: Unknown result type (might be due to invalid IL or missing references) cost = CalculateCost(tower.towerModel) + (double)ModifyClipboardCost(tower); clipboard = tower.towerModel; rootClipboard = ((Il2CppObjectBase)((Mutable)tower).rootModel).Cast(); TowerModel tower2 = InGameExt.GetGameModel(InGame.instance).GetTower(((EntityModel)clipboard).baseId, 0, 0, 0); baseCost = tower2.cost + (float)ModifyClipboardCost(tower); foreach (BloonsMod mod in ModHelper.Mods) { mod.Call("OnTowerCopied", new object[1] { tower }); } string text = LocalizationManager.Instance.GetText(((Model)tower.towerModel).name); string text2 = $"Copied {text}\n\nTotal Cost is ${(int)cost}"; GameExt.ShowMessage(Game.instance, text2, (string)null); targetType = ((CommonBehaviorProxy)(object)tower).TargetType; InvestmentInfo? obj; if (!tower.towerModel.isParagon) { obj = null; } else { ParagonTower behavior = ((RootBehavior)tower).entity.GetBehavior(); obj = ((behavior != null) ? new InvestmentInfo?(behavior.investmentInfo) : ((InvestmentInfo?)null)); } lastDegree = obj; } private static void Paste() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) InputManager inputManager = InGameExt[InGame.instance]; if (clipboard == null || rootClipboard == null || inputManager.IsInPlacementMode || InGameExt.GetCash(InGame.instance) < cost) { return; } inputManager.EnterPlacementMode(InGameExt.GetGameModel(InGame.instance).GetTowerWithName(((Model)rootClipboard).name), PositionDelegate.op_Implicit((Action)delegate(Vector2 pos) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) try { nextPlaceIsPaste = true; overrideNonPower = clipboard.isPowerProTower; inputManager.CreatePlacementTower(pos); } catch (Exception ex) { overrideNonPower = false; CopyPasteTowersMod.MelonLogger.Error((object)ex); } }), new ObjectId { data = (uint)InGame.instance.bridge.GetInputId() }, false, 0); } private static int ModifyClipboardCost(Tower tower) { return ModHelper.Mods.Aggregate(0, (int i, BloonsMod mod) => i + Convert.ToInt32(mod.Call("ModifyClipboardCost", new object[1] { tower }) ?? ((object)0))); } private static int BaseUpgradeCost(string name) { return Math.RoundToNearestInt((float)InGameExt.GetGameModel(InGame.instance).GetUpgrade(name).cost, 5); } private static double CalculateCost(TowerModel tower) { return ((IEnumerable)tower.appliedUpgrades).Aggregate(tower.cost, (float i, string upgrade) => i + (float)BaseUpgradeCost(upgrade)); } private static int CalculateCost(Tower tower) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) Simulation sim = ((RootObject)tower).Sim; TowerManager towerManager = sim.towerManager; TowerInventory towerInventory = sim.GetTowerInventory(((CommonBehaviorProxy)(object)tower).PlayerOwnerId); float num = 0f; float num2 = 0f; towerInventory.GetTowerDiscount(tower.towerModel, ref num, ref num2, false); float areaDiscount = towerManager.GetAreaDiscount(((CommonBehaviorProxy)(object)tower).Position.ToVector2()); Dictionary> zoneDiscount = towerManager.GetZoneDiscount(tower.towerModel, Vector3Boxed.op_Implicit(((CommonBehaviorProxy)(object)tower).Position), -1, 0, tower.owner, ""); float discountMultiplier = towerManager.GetDiscountMultiplier(zoneDiscount); int num3 = Math.RoundToNearestInt((1f - (areaDiscount + discountMultiplier + num)) * (tower.towerModel.cost - num2), 5); num3 += CalculateUpgradeCosts(tower); return num3 + ModifyClipboardCost(tower); } private static int CalculateUpgradeCosts(Tower tower) { return ((IEnumerable)tower.towerModel.appliedUpgrades).Sum((string u) => CalculateCost(tower, ((RootObject)tower).Sim.model.GetUpgrade(u))); } private static int CalculateCost(Tower tower, UpgradeModel upgrade) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Simulation sim = ((RootObject)tower).Sim; TowerManager towerManager = sim.towerManager; int path = upgrade.path; int num = upgrade.tier + 1; if (towerManager.GetFreeUpgrade(Vector3Boxed.op_Implicit(((CommonBehaviorProxy)(object)tower).Position), tower, path, num)) { return 0; } float areaDiscount = towerManager.GetAreaDiscount(((CommonBehaviorProxy)(object)tower).Position.ToVector2()); Dictionary> zoneDiscount = towerManager.GetZoneDiscount(tower.towerModel, Vector3Boxed.op_Implicit(((CommonBehaviorProxy)(object)tower).Position), path, num + 1, tower.owner, ""); float discountMultiplier = towerManager.GetDiscountMultiplier(zoneDiscount); float simulationBehaviorDiscount = sim.GetSimulationBehaviorDiscount(tower, path, num, areaDiscount + discountMultiplier); return Math.RoundToNearestInt((float)upgrade.cost * (1f - simulationBehaviorDiscount), 5); } } public static class ModHelperData { public const string WorksOnVersion = "54.3"; public const string Version = "1.0.11"; public const string Name = "Copy/Paste Towers"; public const string Description = "When you press Ctrl+C while selecting a tower you will copy it, showing you its total cost. Then, pressing Ctrl+V will start the placement for a copy of it with the same upgrades, paying the full combined price. Helpful for doing paragon sacrifices."; public const string RepoOwner = "doombubbles"; public const string PrevRepoName = "copy-paste-towers"; public const string RepoName = "CopyPasteTowers"; } }