diff --git a/BreakableWallRandomizer.cs b/BreakableWallRandomizer.cs index 614375e..a38fb61 100644 --- a/BreakableWallRandomizer.cs +++ b/BreakableWallRandomizer.cs @@ -9,7 +9,7 @@ namespace BreakableWallRandomizer public class BreakableWallRandomizer : Mod, IGlobalSettings { new public string GetName() => "Breakable Wall Randomizer"; - public override string GetVersion() => "3.0.0.0"; + public override string GetVersion() => "3.0.0.1"; public BWR_Settings GS { get; set; } = new(); private static BreakableWallRandomizer _instance; public BreakableWallRandomizer() : base() diff --git a/BreakableWallRandomizer.csproj b/BreakableWallRandomizer.csproj index af07fff..bcb39ae 100644 --- a/BreakableWallRandomizer.csproj +++ b/BreakableWallRandomizer.csproj @@ -8,8 +8,8 @@ BreakableWallRandomizer A Randomizer add-on for wall and floor objects. Copyright ©2023 - 3.0.0.0 - 3.0.0.0 + 3.0.0.1 + 3.0.0.1 bin\$(Configuration)\ latest diff --git a/IC/BreakableWallItem.cs b/IC/BreakableWallItem.cs index 3327026..1db5c91 100644 --- a/IC/BreakableWallItem.cs +++ b/IC/BreakableWallItem.cs @@ -46,6 +46,8 @@ private InteropTag BreakableWallItemTag() sprite = "wood_plank_02"; if (name.StartsWith("Dive_Floor-")) sprite = "break_floor_glass"; + if (name.StartsWith("Wall_Group-")) + sprite = "mine_break_wall_03_0deg"; InteropTag tag = new(); tag.Properties["ModSource"] = "BreakableWallRandomizer"; @@ -62,8 +64,8 @@ public override void GiveImmediate(GiveInfo info) { foreach (CondensedWallObject wall in groupWalls) { - if (!BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(name)) - BreakableWallModule.Instance.UnlockedBreakableWalls.Add(name); + if (!BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(wall.name)) + BreakableWallModule.Instance.UnlockedBreakableWalls.Add(wall.name); if (wall.name.StartsWith("Wall") && !BreakableWallModule.Instance.UnlockedWalls.Contains(name)) BreakableWallModule.Instance.UnlockedWalls.Add(wall.name); if (wall.name.StartsWith("Plank") && !BreakableWallModule.Instance.UnlockedPlanks.Contains(name)) @@ -78,7 +80,8 @@ public override void GiveImmediate(GiveInfo info) } else { - BreakableWallModule.Instance.UnlockedBreakableWalls.Add(name); + if (!BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(name)) + BreakableWallModule.Instance.UnlockedBreakableWalls.Add(name); if (name.StartsWith("Wall") && !BreakableWallModule.Instance.UnlockedWalls.Contains(name)) BreakableWallModule.Instance.UnlockedWalls.Add(name); if (name.StartsWith("Plank") && !BreakableWallModule.Instance.UnlockedPlanks.Contains(name)) diff --git a/IC/BreakableWallLocation.cs b/IC/BreakableWallLocation.cs index 027b234..44bb500 100644 --- a/IC/BreakableWallLocation.cs +++ b/IC/BreakableWallLocation.cs @@ -68,7 +68,7 @@ protected override void OnLoad() if (groupWalls.Count > 0) { foreach (CondensedWallObject wall in groupWalls) - Events.AddFsmEdit(wall.sceneName, new(wall.gameObject, wall.fsmType ?? fsmType), ModifyWallBehaviour); + Events.AddFsmEdit(wall.sceneName, new(wall.gameObject, wall.fsmType), ModifyWallBehaviour); } else { @@ -85,7 +85,7 @@ protected override void OnUnload() if (groupWalls.Count > 0) { foreach (CondensedWallObject wall in groupWalls) - Events.RemoveFsmEdit(wall.sceneName, new(wall.gameObject, wall.fsmType ?? fsmType), ModifyWallBehaviour); + Events.RemoveFsmEdit(wall.sceneName, new(wall.gameObject, wall.fsmType), ModifyWallBehaviour); } else { @@ -154,177 +154,201 @@ private void Recursive_MakeWallPassable(GameObject go) private void ModifyWallBehaviour(PlayMakerFSM fsm) { - // If a location is present, it means that it's not vanilla - BreakableWallModule.Instance.vanillaWalls.RemoveAll(wall => wall.name == name); + List wallList = []; - if (name == "Wall-Shade_Soul_Shortcut") - GameObject.Destroy(GameObject.Find("/Breakable Wall Ruin Lift/Masks")); - - // The wall will delete itself based on its state if we don't do this. - if (fsmType == "break_floor" || fsmType == "FSM") + if (groupWalls.Count > 0) { - fsm.ChangeTransition("Initiate", "ACTIVATE", "Idle"); + foreach (CondensedWallObject wallObject in groupWalls) + wallList.Add(wallObject); } - else if (fsmType == "breakable_wall_v2") - { - // Shade Soul Shortcut is the only example of a two-way wall that can be accessed from both sides but - // destroyed only from one of them. TC made stuff happen to prevent players from destroying it from - // the left end - behaviour we'll preserve but only if the item isn't obtained. - if (name == "Wall-Shade_Soul_Shortcut" && BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(name)) - { - fsm.ChangeTransition("Activated?", "ACTIVATE", "Initiate"); - fsm.ChangeTransition("Activated?", "FINISHED", "Initiate"); - } - else - fsm.ChangeTransition("Activated?", "ACTIVATE", "Ruin Lift?"); - } else if (fsmType == "quake_floor") - { - fsm.ChangeTransition("Init", "ACTIVATE", "Solid"); - fsm.RemoveAction("Transient", 0); // Sets the floor to a trigger - if (fsm.GetState("Solid").GetActions().Length >= 1) - { - fsm.RemoveAction("Solid", 0); // Sets the floor to a triggern't - } - - var collider = fsm.gameObject.GetComponent(); - collider.isTrigger = true; // Make the first collider always a trigger - - // Add our own collider for physics collision. - var newCollider = fsm.gameObject.AddComponent(); - newCollider.offset = collider.offset; - newCollider.size = collider.size; - } else if (fsmType == "Detect Quake") + else { - fsm.ChangeTransition("Init", "ACTIVATE", "Detect"); + wallList.Add(new(name, sceneName, objectName, fsmType)); } - fsm.AddState("GiveItem"); - fsm.AddCustomAction("GiveItem", () => + foreach (CondensedWallObject wall in wallList) { - ItemUtility.GiveSequentially(Placement.Items, Placement, new GiveInfo() - { - FlingType = FlingType.Everywhere, - MessageType = MessageType.Corner, - }); + if (wall.fsmType != fsm.FsmName) + continue; - Placement.AddVisitFlag(VisitState.Opened); + // If a location is present, it means that it's not vanilla + BreakableWallModule.Instance.vanillaWalls.RemoveAll(wall => wall.name == name); - if (BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(name)) + if (wall.name == "Wall-Shade_Soul_Shortcut") + GameObject.Destroy(GameObject.Find("/Breakable Wall Ruin Lift/Masks")); + + // The wall will delete itself based on its state if we don't do this. + if (wall.fsmType == "break_floor" || wall.fsmType == "FSM") { - // Delete the wall entirely. - if (fsmType == "quake_floor") - fsm.SetState("Destroy"); - else if (fsmType == "Detect Quake") - fsm.SetState("Break 2"); - else - fsm.SetState("Break"); + fsm.ChangeTransition("Initiate", "ACTIVATE", "Idle"); } - }); + else if (wall.fsmType == "breakable_wall_v2") + { + // Shade Soul Shortcut is the only example of a two-way wall that can be accessed from both sides but + // destroyed only from one of them. TC made stuff happen to prevent players from destroying it from + // the left end - behaviour we'll preserve but only if the item isn't obtained. + if (wall.name == "Wall-Shade_Soul_Shortcut" && BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(wall.name)) + { + fsm.ChangeTransition("Activated?", "ACTIVATE", "Initiate"); + fsm.ChangeTransition("Activated?", "FINISHED", "Initiate"); + } + else + fsm.ChangeTransition("Activated?", "ACTIVATE", "Ruin Lift?"); + } else if (wall.fsmType == "quake_floor") + { + fsm.ChangeTransition("Init", "ACTIVATE", "Solid"); + fsm.RemoveAction("Transient", 0); // Sets the floor to a trigger + if (fsm.GetState("Solid").GetActions().Length >= 1) + { + fsm.RemoveAction("Solid", 0); // Sets the floor to a triggern't + } - // If we already unlocked this wall, and items are still left there, make it passable. - if (BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(name)) - { - // If items are left, make wall semi-transparent and passable - if (!Placement.AllObtained()) + var collider = fsm.gameObject.GetComponent(); + collider.isTrigger = true; // Make the first collider always a trigger + + // Add our own collider for physics collision. + var newCollider = fsm.gameObject.AddComponent(); + newCollider.offset = collider.offset; + newCollider.size = collider.size; + } else if (wall.fsmType == "Detect Quake") { - MakeWallPassable(fsm.gameObject); + fsm.ChangeTransition("Init", "ACTIVATE", "Detect"); } - else + + fsm.AddState("GiveItem"); + fsm.AddCustomAction("GiveItem", () => { - // Ensure the wall deletes on-load. - if (fsmType == "quake_floor") + ItemUtility.GiveSequentially(Placement.Items, Placement, new GiveInfo() { - fsm.ChangeTransition("Init", "FINISHED", "Activate"); - fsm.ChangeTransition("Init", "ACTIVATE", "Activate"); - } else if (fsmType == "Detect Quake") { - fsm.ChangeTransition("Init", "ACTIVATE", "Activate !!!"); - fsm.ChangeTransition("Init", "FINISHED", "Activate !!!"); - } else + FlingType = FlingType.Everywhere, + MessageType = MessageType.Corner, + }); + + Placement.AddVisitFlag(VisitState.Opened); + + if (BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(wall.name)) { - fsm.ChangeTransition("Initiate", "FINISHED", "Activated"); - fsm.ChangeTransition("Initiate", "ACTIVATE", "Activated"); + // Delete the wall entirely. + if (fsmType == "quake_floor") + fsm.SetState("Destroy"); + else if (fsmType == "Detect Quake") + fsm.SetState("Break 2"); + else + fsm.SetState("Break"); } - } - } - else - // If we didn't unlock this door yet... - { - // ...and we already obtained the item at this location, set the wall to an unhittable state: - if (Placement.AllObtained()) + }); + + // If we already unlocked this wall, and items are still left there, make it passable. + if (BreakableWallModule.Instance.UnlockedBreakableWalls.Contains(wall.name)) { - fsm.SetState("GiveItem"); + // If items are left, make wall semi-transparent and passable + if (!Placement.AllObtained()) + { + foreach (CondensedWallObject w in wallList) + MakeWallPassable(GameObject.Find(w.gameObject)); + } + else + { + // Ensure the wall deletes on-load. + if (wall.fsmType == "quake_floor") + { + fsm.ChangeTransition("Init", "FINISHED", "Activate"); + fsm.ChangeTransition("Init", "ACTIVATE", "Activate"); + } else if (wall.fsmType == "Detect Quake") { + fsm.ChangeTransition("Init", "ACTIVATE", "Activate !!!"); + fsm.ChangeTransition("Init", "FINISHED", "Activate !!!"); + } else + { + fsm.ChangeTransition("Initiate", "FINISHED", "Activated"); + fsm.ChangeTransition("Initiate", "ACTIVATE", "Activated"); + } + } } - // ...and there are items left to collect: else + // If we didn't unlock this door yet... { - var originalIdleStateName = fsmType switch + // ...and we already obtained the item at this location, set the wall to an unhittable state: + if (Placement.AllObtained()) + { + fsm.SetState("GiveItem"); + } + // ...and there are items left to collect: + else { - "quake_floor" => "Solid", + var originalIdleStateName = wall.fsmType switch + { + "quake_floor" => "Solid", - "Detect Quake" => "Detect", + "Detect Quake" => "Detect", - _ => "Idle" - }; + _ => "Idle" + }; - // Copy sound and particles from original - var originalBreakStateName = fsmType switch - { - "quake_floor" => "Glass", + // Copy sound and particles from original + var originalBreakStateName = wall.fsmType switch + { + "quake_floor" => "Glass", - "Detect Quake" => "Break 2", + "Detect Quake" => "Break 2", - _ => "Break" - }; + _ => "Break" + }; - foreach (var action in fsm.GetState(originalBreakStateName).Actions) - { - if (action is AudioPlayerOneShotSingle or PlayParticleEmitter or AudioPlayerOneShot) + foreach (var action in fsm.GetState(originalBreakStateName).Actions) { - fsm.AddAction("GiveItem", action); + if (action is AudioPlayerOneShotSingle or PlayParticleEmitter or AudioPlayerOneShot) + { + fsm.AddAction("GiveItem", action); + } } - } - // In case we're in the same scene when it breaks, check if there are items left, - // and then set states accordingly + // In case we're in the same scene when it breaks, check if there are items left, + // and then set states accordingly - fsm.AddState("BreakSameScene"); + fsm.AddState("BreakSameScene"); - fsm.InsertCustomAction("BreakSameScene", () => - { - if (Placement.AllObtained()) - { - MakeWallPassable(fsm.gameObject); - fsm.SetState(originalIdleStateName); - } - else + fsm.InsertCustomAction("BreakSameScene", () => { - if (fsmType == "quake_floor") { MakeWallPassable(fsm.gameObject); } // ensure everything is passable. - fsm.SetState(originalBreakStateName); - } + if (Placement.AllObtained()) + { + foreach (CondensedWallObject w in wallList) + MakeWallPassable(GameObject.Find(w.gameObject)); + fsm.SetState(originalIdleStateName); + } + else + { + if (wall.fsmType == "quake_floor") + { + foreach (CondensedWallObject w in wallList) + MakeWallPassable(GameObject.Find(w.gameObject)); + } // ensure everything is passable. + fsm.SetState(originalBreakStateName); + } - Placement.AddVisitFlag(VisitState.Opened); - }, 0); + Placement.AddVisitFlag(VisitState.Opened); + }, 0); + } } - } - if (fsmType == "breakable_wall_v2") - { - fsm.ChangeTransition("PD Bool?", "FINISHED", "GiveItem"); - } - else if (fsmType == "FSM") - { - fsm.ChangeTransition("Pause Frame", "FINISHED", "GiveItem"); - fsm.ChangeTransition("Spell Destroy", "FINISHED", "GiveItem"); - } - else if (fsmType == "break_floor") - { - fsm.ChangeTransition("Hit", "HIT 3", "GiveItem"); - } else if (fsmType == "quake_floor") - { - fsm.ChangeTransition("PD Bool?", "FINISHED", "GiveItem"); - } else if (fsmType == "Detect Quake") - { - fsm.ChangeTransition("Quake Hit", "FINISHED", "GiveItem"); + if (wall.fsmType == "breakable_wall_v2") + { + fsm.ChangeTransition("PD Bool?", "FINISHED", "GiveItem"); + } + else if (wall.fsmType == "FSM") + { + fsm.ChangeTransition("Pause Frame", "FINISHED", "GiveItem"); + fsm.ChangeTransition("Spell Destroy", "FINISHED", "GiveItem"); + } + else if (wall.fsmType == "break_floor") + { + fsm.ChangeTransition("Hit", "HIT 3", "GiveItem"); + } else if (wall.fsmType == "quake_floor") + { + fsm.ChangeTransition("PD Bool?", "FINISHED", "GiveItem"); + } else if (wall.fsmType == "Detect Quake") + { + fsm.ChangeTransition("Quake Hit", "FINISHED", "GiveItem"); + } } } diff --git a/IC/ShopLocation.cs b/IC/ShopLocation.cs index 737605a..8f3b5b1 100644 --- a/IC/ShopLocation.cs +++ b/IC/ShopLocation.cs @@ -57,61 +57,37 @@ protected override void OnUnload() private void PreventMylaZombie(On.DeactivateIfPlayerdataFalse.orig_OnEnable orig, DeactivateIfPlayerdataFalse self) { - bool AllObtained = true; - foreach (AbstractItem item in Placement.Items) - { - if (!item.WasEverObtained()) - AllObtained = false; - } - - if (AllObtained && (self.gameObject.name.Contains("Zombie Myla") || string.Equals(self.gameObject.name, "Myla Crazy NPC"))) + bool AllObtained = Placement.Items.All(x => x.GetTag()?.Cost?.Paid ?? true); + if (!AllObtained && (self.gameObject.name.Contains("Zombie Myla") || string.Equals(self.gameObject.name, "Myla Crazy NPC"))) { self.gameObject.SetActive(false); return; } - else if (self.gameObject.name.Contains("Zombie Myla") || string.Equals(self.gameObject.name, "Myla Crazy NPC")) - { - GameObject myla = self.gameObject; - if (Placement.CheckVisitedAny(VisitState.Accepted) && !Placement.AllObtained()) - { - Container c = Container.GetContainer(Container.Shiny); - - ContainerInfo info = new(c.Name, Placement, flingType, (Placement as ItemChanger.Placements.ISingleCostPlacement)?.Cost); - GameObject shiny = c.GetNewContainer(info); - - c.ApplyTargetContext(shiny, myla.transform.position.x, myla.transform.position.y, 0f); - ShinyUtility.FlingShinyRandomly(shiny.LocateMyFSM("Shiny Control")); - } - } + else if (AllObtained && (self.gameObject.name.Contains("Zombie Myla") || string.Equals(self.gameObject.name, "Myla Crazy NPC"))) + RespawnItems(); orig(self); } private void ForceMyla(On.DeactivateIfPlayerdataTrue.orig_OnEnable orig, DeactivateIfPlayerdataTrue self) { - bool AllObtained = true; - foreach (AbstractItem item in Placement.Items) - { - if (!item.WasEverObtained()) - AllObtained = false; - } - - if (string.Equals(self.gameObject.name, "Miner") && GameManager.instance.sceneName == sceneName && AllObtained) + bool AllObtained = Placement.Items.All(x => x.GetTag()?.Cost?.Paid ?? true); + if (string.Equals(self.gameObject.name, "Miner") && !AllObtained) return; orig(self); } - private void MakeShinyForRespawnedItems(GameObject myla) + private void RespawnItems() { - if (Placement.CheckVisitedAny(VisitState.Accepted) && !Placement.AllObtained()) + foreach (AbstractItem item in Placement.Items) { - Container c = Container.GetContainer(Container.Shiny); - - ContainerInfo info = new(c.Name, Placement, flingType, (Placement as ItemChanger.Placements.ISingleCostPlacement)?.Cost); - GameObject shiny = c.GetNewContainer(info); - - c.ApplyTargetContext(shiny, myla.transform.position.x, myla.transform.position.y, 0f); - ShinyUtility.FlingShinyLeft(shiny.LocateMyFSM("Shiny Control")); + if (!item.IsObtained() && (item.GetTag()?.Cost?.Paid ?? true)) + { + Container c = Container.GetContainer(Container.Shiny); + GameObject shiny = c.GetNewContainer(new(c.Name, Placement, flingType)); + shiny.transform.position = new(Random.Range(28.0f, 42.0f), 3.4f); + shiny.SetActive(true); + } } } } diff --git a/Interop/MoreLocations.cs b/Interop/MoreLocations.cs index ddc1255..24f465b 100644 --- a/Interop/MoreLocations.cs +++ b/Interop/MoreLocations.cs @@ -27,7 +27,7 @@ public class WallCostProvider : ICostProvider public LogicCost Next(LogicManager lm, Random rng) { - int wallCount = 53; + int wallCount = BWR_Manager.TotalWalls; int minCost = (int)(wallCount * BWR_Manager.Settings.MylaShop.MinimumCost); int maxCost = (int)(wallCount * BWR_Manager.Settings.MylaShop.MaximumCost); return new WallLogicCost(lm.GetTermStrict("Broken_Walls"), rng.Next(minCost, maxCost), amount => new WallCost(amount)); @@ -42,7 +42,7 @@ internal class PlankCostProvider : ICostProvider public LogicCost Next(LogicManager lm, Random rng) { - int wallCount = 49; + int wallCount = BWR_Manager.TotalPlanks; int minCost = (int)(wallCount * BWR_Manager.Settings.MylaShop.MinimumCost); int maxCost = (int)(wallCount * BWR_Manager.Settings.MylaShop.MaximumCost); return new WallLogicCost(lm.GetTermStrict("Broken_Planks"), rng.Next(minCost, maxCost), amount => new PlankCost(amount)); @@ -57,7 +57,7 @@ internal class DiveCostProvider : ICostProvider public LogicCost Next(LogicManager lm, Random rng) { - int wallCount = 44; + int wallCount = BWR_Manager.TotalDives; int minCost = (int)(wallCount * BWR_Manager.Settings.MylaShop.MinimumCost); int maxCost = (int)(wallCount * BWR_Manager.Settings.MylaShop.MaximumCost); return new WallLogicCost(lm.GetTermStrict("Broken_Dive_Floors"), rng.Next(minCost, maxCost), amount => new DiveCost(amount)); diff --git a/Manager/ItemHandler.cs b/Manager/ItemHandler.cs index 892dfda..29e1a06 100644 --- a/Manager/ItemHandler.cs +++ b/Manager/ItemHandler.cs @@ -18,6 +18,7 @@ internal static class ItemHandler { internal static void Hook() { + ProgressionInitializer.OnCreateProgressionInitializer += AddTolerance; RequestBuilder.OnUpdate.Subscribe(-500f, DefineShopRef); RequestBuilder.OnUpdate.Subscribe(-400f, DefineGroups); RequestBuilder.OnUpdate.Subscribe(-100f, RandomizeShopCost); @@ -131,7 +132,7 @@ private static void RandomizeShopCost(RequestBuilder builder) int termNo = rng.Next(3); if (termNo == 0 && !usedTerms.Contains("Walls")) // Walls { - int wallCount = 53; + int wallCount = BWR_Manager.TotalWalls; int minCost = Math.Max((int)(wallCount * BWR_Manager.Settings.MylaShop.MinimumCost), 1); int maxCost = Math.Max((int)(wallCount * BWR_Manager.Settings.MylaShop.MaximumCost), 1); usedTerms.Add("Walls"); @@ -140,7 +141,7 @@ private static void RandomizeShopCost(RequestBuilder builder) if (termNo == 1 && !usedTerms.Contains("Planks")) // Planks { - int wallCount = 49; + int wallCount = BWR_Manager.TotalPlanks; int minCost = Math.Max( (int)(wallCount * BWR_Manager.Settings.MylaShop.MinimumCost), 1); int maxCost = Math.Max( (int)(wallCount * BWR_Manager.Settings.MylaShop.MaximumCost), 1); usedTerms.Add("Planks"); @@ -149,7 +150,7 @@ private static void RandomizeShopCost(RequestBuilder builder) if (termNo == 2 && !usedTerms.Contains("Dives")) // Dives { - int wallCount = 44; + int wallCount = BWR_Manager.TotalDives; int minCost = Math.Max( (int)(wallCount * BWR_Manager.Settings.MylaShop.MinimumCost), 1); int maxCost = Math.Max( (int)(wallCount * BWR_Manager.Settings.MylaShop.MaximumCost), 1); usedTerms.Add("Dives"); @@ -299,6 +300,27 @@ private static void AddWalls(RequestBuilder rb) } } + private static void AddTolerance(LogicManager lm, GenerationSettings gs, ProgressionInitializer pi) + { + if (!BWR_Manager.Settings.Enabled || !BWR_Manager.Settings.MylaShop.Enabled) + return; + + int wallCount = BWR_Manager.TotalWalls; + int wallCost = Math.Max((int)(wallCount * BWR_Manager.Settings.MylaShop.MaximumCost), 1); + int wallTolerance = Math.Min((int)(wallCost * BWR_Manager.Settings.MylaShop.Tolerance), wallCount - wallCost); + pi.Setters.Add(new RandomizerCore.TermValue(lm.GetTermStrict("Broken_Walls"), -wallTolerance)); + + int plankCount = BWR_Manager.TotalPlanks; + int plankCost = Math.Max((int)(plankCount * BWR_Manager.Settings.MylaShop.MaximumCost), 1); + int plankTolerance = Math.Min((int)(plankCost * BWR_Manager.Settings.MylaShop.Tolerance), plankCount - plankCost); + pi.Setters.Add(new RandomizerCore.TermValue(lm.GetTermStrict("Broken_Planks"), -plankTolerance)); + + int diveCount = BWR_Manager.TotalDives; + int diveCost = Math.Max((int)(diveCount * BWR_Manager.Settings.MylaShop.MaximumCost), 1); + int diveTolerance = Math.Min((int)(diveCost * BWR_Manager.Settings.MylaShop.Tolerance), diveCount - diveCost); + pi.Setters.Add(new RandomizerCore.TermValue(lm.GetTermStrict("Broken_Dive_Floors"), -diveTolerance)); + } + private static void AddFileSettings(LogArguments args, TextWriter tw) { // Log settings into the settings file diff --git a/Manager/Manager.cs b/Manager/Manager.cs index ffcff33..f7ff4ed 100644 --- a/Manager/Manager.cs +++ b/Manager/Manager.cs @@ -12,6 +12,9 @@ namespace BreakableWallRandomizer.Manager internal static class BWR_Manager { public static BWR_Settings Settings => BreakableWallRandomizer.Instance.GS; + public static int TotalWalls = 53; + public static int TotalPlanks = 49; + public static int TotalDives = 44; public static void Hook() { DefineObjects(); diff --git a/Modules/BreakableWallModule.cs b/Modules/BreakableWallModule.cs index d138581..b5719d0 100644 --- a/Modules/BreakableWallModule.cs +++ b/Modules/BreakableWallModule.cs @@ -142,11 +142,11 @@ public void CompletedChallenges() completed.Add("All Buried Geo Dives"); if (sanctumWallCount == 7) completed.Add("All Inner Soul Sanctum Dives"); - if (UnlockedWalls.Count == 53) + if (UnlockedWalls.Count == BWR_Manager.TotalWalls) completed.Add("All Walls broken."); - if (UnlockedPlanks.Count == 49) + if (UnlockedPlanks.Count == BWR_Manager.TotalPlanks) completed.Add("All Walls broken."); - if (UnlockedDives.Count == 44) + if (UnlockedDives.Count == BWR_Manager.TotalDives) completed.Add("All Walls broken."); OnAchievedBreakableWall?.Invoke(completed); diff --git a/ReadMe.md b/ReadMe.md index a72123f..d215cfa 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -34,8 +34,9 @@ These are left out by design. Might add as an optional setting at some point. ### Myla Shop - Enabled --> Boolean to define if the Godhome Shop should appear or not. -- Minimum/Maximum Cost --> A range from 0 to 1, where 0 is 0% and 1 is 100% (176) of all available wall objects. Default is 25% to 75%. +- Minimum/Maximum Cost --> A range from 0 to 1, where 0 is 0% and 1 is 100% of all available wall objects. Default is 25% to 75%. - Include In Junk Shop --> Allows the defined costs to appear as currency in More Locations' Junk Shop, regardless of if Myla's Shop is enabled or not. +- Tolerance --> A range from 0 to 1, where 0 is no tolerance and 1 is 100% of the used maximum cost. If maximum cost + tolerance should be over the total existing walls, the tolerance will automatically be capped. For reference, most mods have tolerance be a 10-25% of the maximum costs. ## Dependencies: - ItemChanger diff --git a/Resources/Data/BreakableWallObjects.json b/Resources/Data/BreakableWallObjects.json index 2060fc9..6fad9a4 100644 --- a/Resources/Data/BreakableWallObjects.json +++ b/Resources/Data/BreakableWallObjects.json @@ -546,6 +546,7 @@ "alsoDestroy": [], "exit": false, "groupWalls": [], + "group": "", "logic": "Deepnest_03[left2] | Deepnest_03 + (LEFTSUPERDASH | WINGS | LEFTCLAW + ($SHADESKIP | $SLOPEBALL[before:ROOMSOUL]))", "logicOverrides": { "Deepnest_03[left2]": "Deepnest_03[left2] | (ORIG) + Wall-Deepnest_Springs_Grub" @@ -590,7 +591,7 @@ "exit": false, "groupWalls": [], "group": "Nosk_Walls", - "logic": "Deepnest_31[right1] + (LEFTSLASH | UPSLASH | SCREAM | LEFTFIREBALL | ANYCLAW | WINGS | LEFTDASH | (RIGHTSPELLAIRSTALL?SPELLAIRSTALL) + $CASTSPELL[3]) | Deepnest_31[right2] + ((RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) + Plank-Nosk_Exit | (ANYCLAW | WINGS) + Wall-Nosk_Inner)", + "logic": "Deepnest_31[right1] + (LEFTSLASH | UPSLASH | SCREAM | LEFTFIREBALL | ANYCLAW | WINGS | LEFTDASH | (FIREBALLSKIPS + (RIGHTFIREBALL | SCREAM)) + $CASTSPELL[3]) | Deepnest_31[right2] + ((RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) + Plank-Nosk_Exit | (ANYCLAW | WINGS) + Wall-Nosk_Inner)", "logicOverrides": {}, "logicSubstitutions": {} }, @@ -609,7 +610,7 @@ "exit": false, "groupWalls": [], "group": "Nosk_Walls", - "logic": "Deepnest_31[right1] + (ANYCLAW | WINGS | LEFTDASH | (RIGHTSPELLAIRSTALL?SPELLAIRSTALL) + $CASTSPELL[3]) + Wall-Nosk_Outer | Deepnest_31[right2] + (ANYCLAW | WINGS)", + "logic": "Deepnest_31[right1] + (ANYCLAW | WINGS | LEFTDASH | (FIREBALLSKIPS + (RIGHTFIREBALL | SCREAM)) + $CASTSPELL[3]) + Wall-Nosk_Outer | Deepnest_31[right2] + (ANYCLAW | WINGS)", "logicOverrides": {}, "logicSubstitutions": {} }, @@ -1094,7 +1095,7 @@ "exit": false, "groupWalls": [], "group": "", - "logic": "Deepnest_Spider_Town[left1] + WINGS + (RIGHTCLAW + (LEFTDASH | LEFTSUPERDASH | RIGHTSPELLAIRSTALL?SPELLAIRSTALL + $CASTSPELL[1]) | LEFTCLAW + RIGHTSUPERDASH) | Bench-Beast's_Den + ANYCLAW + Plank-Den_Secret_Entrance | Deepnest_Spider_Town[left1] + (WINGS + ANYCLAW | FULLCLAW + (COMBAT[Left_Devout] | COMBAT[Any_Devout])) + Plank-Den_Secret_Entrance", + "logic": "Deepnest_Spider_Town[left1] + WINGS + (RIGHTCLAW + (LEFTDASH | LEFTSUPERDASH | FIREBALLSKIPS + (RIGHTFIREBALL | SCREAM) + $CASTSPELL[1]) | LEFTCLAW + RIGHTSUPERDASH) | Bench-Beast's_Den + ANYCLAW + Plank-Den_Secret_Entrance | Deepnest_Spider_Town[left1] + (WINGS + ANYCLAW | FULLCLAW + (COMBAT[Left_Devout] | COMBAT[Any_Devout])) + Plank-Den_Secret_Entrance", "logicOverrides": {}, "logicSubstitutions": {} }, @@ -1142,8 +1143,8 @@ "gameObject": "/Break Floor 1", "fsmType": "break_floor", "sceneName": "Fungus2_29", - "x": 0.0, - "y": 0.0, + "x": 0.14, + "y": -0.02, "persistentBool": "", "sprite": "fungd_mush_01_0001_a_break_floor_270deg", "alsoDestroy": [ @@ -1361,8 +1362,8 @@ "gameObject": "/One Way Wall", "fsmType": "break_floor", "sceneName": "Fungus3_05", - "x": 0.0, - "y": 0.0, + "x": 0.3, + "y": -0.15, "persistentBool": "", "sprite": "crumble_floor_fungus_270deg", "alsoDestroy": [ @@ -1371,7 +1372,7 @@ "exit": true, "groupWalls": [], "group": "", - "logic": "(Fungus3_05[left1] | Fungus3_05[right1]) + ((WINGS | (LEFTDASH | RIGHTCLAW + LEFTSUPERDASH | RIGHTSPELLAIRSTALL?AIRSTALL + $CASTSPELL[2,before:ROOMSOUL,after:ROOMSOUL]) + (RIGHTDASH | RIGHTSUPERDASH | LEFTSPELLAIRSTALL?AIRSTALL + (LEFTCLAW + $CASTSPELL[1] | $CASTSPELL[2,1]))) + (RIGHTCLAW | LEFTCLAW + WINGS) | Plank-Petra_Arena)", + "logic": "(Fungus3_05[left1] | Fungus3_05[right1]) + ((WINGS | (LEFTDASH | RIGHTCLAW + LEFTSUPERDASH | FIREBALLSKIPS + (RIGHTFIREBALL | SCREAM) + $CASTSPELL[2,before:ROOMSOUL,after:ROOMSOUL]) + (RIGHTDASH | RIGHTSUPERDASH | FIREBALLSKIPS + (LEFTFIREBALL | SCREAM) + (LEFTCLAW + $CASTSPELL[1] | $CASTSPELL[2,1]))) + (RIGHTCLAW | LEFTCLAW + WINGS) | Plank-Petra_Arena)", "logicOverrides": {}, "logicSubstitutions": {} }, @@ -1380,8 +1381,8 @@ "gameObject": "/One Way Wall", "fsmType": "break_floor", "sceneName": "Fungus3_39", - "x": 0.0, - "y": 0.0, + "x": 0.04, + "y": -0.32, "persistentBool": "", "sprite": "one_way_fung_266deg", "alsoDestroy": [], @@ -1416,7 +1417,11 @@ "group": "", "logic": "Fungus3_44[door1]", "logicOverrides": {}, - "logicSubstitutions": {} + "logicSubstitutions": { + "Fungus3_44": { + "Fungus3_44[door1]": "Fungus3_44[door1] + Plank-Outside_Overgrown_Mound" + } + } }, { "name": "Plank-Archives_Outer", @@ -1435,7 +1440,11 @@ "logicOverrides": { "Fungus3_47[left1]": "(ORIG) + Plank-Archives_Outer" }, - "logicSubstitutions": {} + "logicSubstitutions": { + "Fungus3_47": { + "Fungus3_47[left1]": "Fungus3_47[left1] + Plank-Archives_Outer" + } + } }, { "name": "Plank-Hive_Knight", @@ -2037,8 +2046,8 @@ "gameObject": "/Quake Floor", "fsmType": "quake_floor", "sceneName": "Abyss_17", - "x": 0.0, - "y": 0.0, + "x": 0.5, + "y": -0.1, "persistentBool": "", "sprite": "break_floor", "alsoDestroy": [ @@ -2058,8 +2067,8 @@ "gameObject": "/Quake Floor", "fsmType": "quake_floor", "sceneName": "Cliffs_04", - "x": 0.0, - "y": 0.0, + "x": 0.05, + "y": -0.05, "persistentBool": "", "sprite": "break_floor", "alsoDestroy": [], diff --git a/Resources/Data/WallGroups.json b/Resources/Data/WallGroups.json index 649d4d8..f800c3d 100644 --- a/Resources/Data/WallGroups.json +++ b/Resources/Data/WallGroups.json @@ -51,11 +51,11 @@ "exit": false, "groupWalls": [], "group": "Nosk_Walls", - "logic": "Deepnest_31[right1] + (LEFTSLASH | UPSLASH | SCREAM | LEFTFIREBALL | ANYCLAW | WINGS | LEFTDASH | (RIGHTSPELLAIRSTALL?SPELLAIRSTALL) + $CASTSPELL[3] | Plank-Nosk_Exit) | Deepnest_31[right2] + (ANYCLAW | WINGS)", + "logic": "Deepnest_31[right1] + (LEFTSLASH | UPSLASH | SCREAM | LEFTFIREBALL | ANYCLAW | WINGS | LEFTDASH | (FIREBALLSKIPS + (RIGHTFIREBALL | SCREAM)) + $CASTSPELL[3]) | Deepnest_31[right2] + ((RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) | (ANYCLAW | WINGS))", "logicOverrides": { - "Deepnest_31[right2]": "Deepnest_31[right2] | ((ORIG) + Wall-Nosk_Outer + Wall-Nosk_Inner) | ((ORIG) + Plank-Nosk_Exit)", - "Geo_Rock-Deepnest_Nosk_1": "Deepnest_31[right1] + (ANYCLAW | WINGS | LEFTDASH | (RIGHTSPELLAIRSTALL?SPELLAIRSTALL) + $CASTSPELL[3]) + Wall-Nosk_Outer | Deepnest_31[right2] + ((ANYCLAW | WINGS) + Wall-Nosk_Inner | (RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) + Plank-Nosk_Exit + Wall-Nosk_Outer)", - "Geo_Rock-Deepnest_Nosk_2": "Deepnest_31[right1] + (ANYCLAW | WINGS | LEFTDASH | (RIGHTSPELLAIRSTALL?SPELLAIRSTALL) + $CASTSPELL[3]) + Wall-Nosk_Outer | Deepnest_31[right2] + ((ANYCLAW | WINGS) + Wall-Nosk_Inner | (RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) + Plank-Nosk_Exit + Wall-Nosk_Outer)", + "Deepnest_31[right2]": "Deepnest_31[right2] | ((ORIG) + (Wall-Nosk_Outer + Wall-Nosk_Inner | Plank-Nosk_Exit))", + "Geo_Rock-Deepnest_Nosk_1": "Deepnest_31[right1] + (ANYCLAW | WINGS | LEFTDASH | (FIREBALLSKIPS + (RIGHTFIREBALL | SCREAM)) + $CASTSPELL[3]) + Wall-Nosk_Outer | Deepnest_31[right2] + ((ANYCLAW | WINGS) + Wall-Nosk_Inner | (RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) + Plank-Nosk_Exit + Wall-Nosk_Outer)", + "Geo_Rock-Deepnest_Nosk_2": "Deepnest_31[right1] + (ANYCLAW | WINGS | LEFTDASH | (FIREBALLSKIPS + (RIGHTFIREBALL | SCREAM)) + $CASTSPELL[3]) + Wall-Nosk_Outer | Deepnest_31[right2] + ((ANYCLAW | WINGS) + Wall-Nosk_Inner | (RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) + Plank-Nosk_Exit + Wall-Nosk_Outer)", "Geo_Rock-Deepnest_Nosk_3": "Deepnest_31[right2]", "Grub-Deepnest_Nosk": "Deepnest_31[right1] | Deepnest_31[right2] + ((RIGHTCLAW | LEFTCLAW + (WINGS | BACKGROUNDPOGOS)) + Plank-Nosk_Exit | (ANYCLAW | WINGS) + Wall-Nosk_Outer + Wall-Nosk_Inner)" }, diff --git a/Settings/ConnectionMenu.cs b/Settings/ConnectionMenu.cs index 18905c5..d2711ad 100644 --- a/Settings/ConnectionMenu.cs +++ b/Settings/ConnectionMenu.cs @@ -59,6 +59,7 @@ private MenuPage DisplayShop() itemPanel.Add(shopMEF.ElementLookup[nameof(MylaShopSettings.Enabled)]); itemPanel.Add(shopMEF.ElementLookup[nameof(MylaShopSettings.IncludeInJunkShop)]); itemPanel.Add(costPanel); + itemPanel.Add(shopMEF.ElementLookup[nameof(MylaShopSettings.Tolerance)]); itemPanel.ResetNavigation(); itemPanel.SymSetNeighbor(Neighbor.Up, shopPage.backButton); diff --git a/Settings/ShopSettings.cs b/Settings/ShopSettings.cs index f857179..0642868 100644 --- a/Settings/ShopSettings.cs +++ b/Settings/ShopSettings.cs @@ -11,6 +11,8 @@ public class MylaShopSettings [MenuRange(0f, 1f)] [DynamicBound(nameof(MinimumCost), false)] public float MaximumCost { get; set; } = 0.75f; + [MenuRange(0f, 1f)] + public float Tolerance { get; set; } = 0.2f; public bool IncludeInJunkShop { get; set; } = false; } } \ No newline at end of file