From f9fd530479eba8613979de777ec8fbc470ca1916 Mon Sep 17 00:00:00 2001
From: Matthew <3005344+LeFauxMatt@users.noreply.github.com>
Date: Tue, 11 Feb 2025 11:51:01 -0800
Subject: [PATCH 1/2] Refactor to support global inventory chests
Replaced all references to "Junimo chests" with "global inventory chests."
Renamed `JunimoMachineGroup` to `GlobalMachineGroup` and updated related properties and methods.
Introduced a `GlobalInventoryId` property in the `IContainer` interface for improved management of global inventory chests.
These changes enhance the system's capability to handle various inventory types while ensuring compatibility with existing functionality.
---
 .../Commands/Summary/GroupContainerStats.cs   |   9 +-
 .../Framework/Commands/Summary/GroupStats.cs  |   4 +-
 Automate/Framework/Commands/SummaryCommand.cs |   2 +-
 ...oMachineGroup.cs => GlobalMachineGroup.cs} |  11 +-
 Automate/Framework/IMachineGroup.cs           |  13 ++-
 Automate/Framework/MachineDataForLocation.cs  |   4 +-
 Automate/Framework/MachineGroup.cs            |  18 +++-
 Automate/Framework/MachineManager.cs          | 101 +++++++++++++-----
 Automate/Framework/OverlayMenu.cs             |  19 ++--
 Automate/Framework/Storage/ChestContainer.cs  |   5 +-
 Automate/Framework/StorageManager.cs          |   4 +-
 Automate/IContainer.cs                        |   9 +-
 Automate/ModEntry.cs                          |   8 +-
 13 files changed, 143 insertions(+), 64 deletions(-)
 rename Automate/Framework/{JunimoMachineGroup.cs => GlobalMachineGroup.cs} (92%)
diff --git a/Automate/Framework/Commands/Summary/GroupContainerStats.cs b/Automate/Framework/Commands/Summary/GroupContainerStats.cs
index b41fc5b2f..fbe787766 100644
--- a/Automate/Framework/Commands/Summary/GroupContainerStats.cs
+++ b/Automate/Framework/Commands/Summary/GroupContainerStats.cs
@@ -28,7 +28,7 @@ internal class GroupContainerStats
     public int TotalSlots { get; }
 
     /// Whether the container is a Junimo chest.
-    public bool IsJunimoChest { get; }
+    public HashSet GlobalInventoryChests { get; } = new(StringComparer.OrdinalIgnoreCase);
 
 
     /*********
@@ -47,12 +47,11 @@ public GroupContainerStats(string name, AutomateContainerPreference storagePrefe
 
         foreach (IContainer container in containers)
         {
-            // only track Junimo chests once
-            if (container.IsJunimoChest)
+            // only track same global inventory chest once
+            if (container.IsGlobalChest)
             {
-                if (this.IsJunimoChest)
+                if (this.GlobalInventoryChests.Add(container.GlobalInventoryId))
                     continue;
-                this.IsJunimoChest = true;
             }
 
             // track stats
diff --git a/Automate/Framework/Commands/Summary/GroupStats.cs b/Automate/Framework/Commands/Summary/GroupStats.cs
index 3f11e391c..c648aceee 100644
--- a/Automate/Framework/Commands/Summary/GroupStats.cs
+++ b/Automate/Framework/Commands/Summary/GroupStats.cs
@@ -35,7 +35,7 @@ public GroupStats(IMachineGroup machineGroup)
     {
         this.MachineGroup = machineGroup;
 
-        if (machineGroup.IsJunimoGroup)
+        if (machineGroup.IsGlobalGroup)
             this.Name = "Distributed group";
         else
         {
@@ -43,7 +43,7 @@ public GroupStats(IMachineGroup machineGroup)
             this.Name = $"Group at ({tile.X}, {tile.Y})";
         }
 
-        this.IsJunimoGroup = machineGroup.IsJunimoGroup;
+        this.IsJunimoGroup = machineGroup.IsGlobalGroup;
 
         this.Machines = machineGroup.Machines
             .GroupBy(p => p.MachineTypeID)
diff --git a/Automate/Framework/Commands/SummaryCommand.cs b/Automate/Framework/Commands/SummaryCommand.cs
index 8e1e7cd4e..941d41d95 100644
--- a/Automate/Framework/Commands/SummaryCommand.cs
+++ b/Automate/Framework/Commands/SummaryCommand.cs
@@ -189,7 +189,7 @@ public override void Handle(string[] args)
                                             .ThenBy(p => p.TileArea.Y)
                                             .ToArray();
 
-                                        IContainer[] junimoChests = chests.Where(p => p.IsJunimoChest).ToArray();
+                                        IContainer[] junimoChests = chests.Where(p => p.IsGlobalChest).ToArray();
                                         return junimoChests.Any()
                                             ? junimoChests
                                             : chests; // special case: no Junimo chests in this location, but we're still connected somehow. This is most likely a custom connected chest from another mod, so just list all of them.
diff --git a/Automate/Framework/JunimoMachineGroup.cs b/Automate/Framework/GlobalMachineGroup.cs
similarity index 92%
rename from Automate/Framework/JunimoMachineGroup.cs
rename to Automate/Framework/GlobalMachineGroup.cs
index 7bc9b5909..bea34f4c5 100644
--- a/Automate/Framework/JunimoMachineGroup.cs
+++ b/Automate/Framework/GlobalMachineGroup.cs
@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Linq;
+using System.Text.RegularExpressions;
 using Microsoft.Xna.Framework;
 using Pathoschild.Stardew.Common;
 using StardewModdingAPI;
@@ -9,7 +10,7 @@
 namespace Pathoschild.Stardew.Automate.Framework;
 
 /// An aggregate collection of machine groups linked by Junimo chests.
-internal class JunimoMachineGroup : MachineGroup
+internal class GlobalMachineGroup : MachineGroup
 {
     /*********
     ** Fields
@@ -38,7 +39,7 @@ internal class JunimoMachineGroup : MachineGroup
     /// Sort machines by priority.
     /// Build a storage manager for the given containers.
     /// Encapsulates monitoring and logging.
-    public JunimoMachineGroup(Func, IEnumerable> sortMachines, Func buildStorage, IMonitor monitor)
+    public GlobalMachineGroup(Func, IEnumerable> sortMachines, Func buildStorage, IMonitor monitor)
         : base(
             locationKey: null,
             machines: [],
@@ -48,7 +49,7 @@ public JunimoMachineGroup(Func, IEnumerable> sor
             monitor: monitor
         )
     {
-        this.IsJunimoGroup = true;
+        this.IsGlobalGroup = true;
         this.SortMachines = sortMachines;
     }
 
@@ -64,12 +65,14 @@ public IEnumerable GetAll()
     public void Add(IList groups)
     {
         this.MachineGroups.AddRange(groups);
+        this.GlobalContainerKeys.UnionWith(groups.SelectMany(p => p.GlobalContainerKeys));
     }
 
     /// Remove all machine groups in the collection.
     public void Clear()
     {
         this.MachineGroups.Clear();
+        this.GlobalContainerKeys.Clear();
 
         this.StorageManager.SetContainers([]);
 
@@ -94,6 +97,8 @@ public void Rebuild()
         this.Machines = this.SortMachines(this.MachineGroups.SelectMany(p => p.Machines)).ToArray();
         this.Tiles = null;
 
+        this.GlobalContainerKeys.Clear();
+        this.GlobalContainerKeys.UnionWith(this.MachineGroups.SelectMany(p => p.GlobalContainerKeys));
         this.StorageManager.SetContainers(this.Containers);
     }
 
diff --git a/Automate/Framework/IMachineGroup.cs b/Automate/Framework/IMachineGroup.cs
index 187e5492a..05db6ac39 100644
--- a/Automate/Framework/IMachineGroup.cs
+++ b/Automate/Framework/IMachineGroup.cs
@@ -8,20 +8,23 @@ namespace Pathoschild.Stardew.Automate.Framework;
 internal interface IMachineGroup
 {
     /*********
-    ** Accessors
-    *********/
+     ** Accessors
+     *********/
     /// The main location containing the group (as formatted by ), unless this is an aggregate machine group.
     string? LocationKey { get; }
 
-    /// The machines in the group.
+    /// The keys for all containers which are linked to global inventories.
+    HashSet GlobalContainerKeys { get; }
+
+/// The machines in the group.
     IMachine[] Machines { get; }
 
     /// The containers in the group.
     IContainer[] Containers { get; }
 
-    /// Whether the machine group is linked to a Junimo chest.
+    /// Whether the machine group is linked to a global inventory.
     [MemberNotNullWhen(false, nameof(IMachineGroup.LocationKey))]
-    bool IsJunimoGroup { get; }
+    bool IsGlobalGroup { get; }
 
     /// Whether the group has the minimum requirements to enable internal automation (i.e., at least one chest and one machine).
     bool HasInternalAutomation { get; }
diff --git a/Automate/Framework/MachineDataForLocation.cs b/Automate/Framework/MachineDataForLocation.cs
index 47d5eb806..618d9a054 100644
--- a/Automate/Framework/MachineDataForLocation.cs
+++ b/Automate/Framework/MachineDataForLocation.cs
@@ -43,7 +43,7 @@ internal record MachineDataForLocation(string LocationKey, IReadOnlyCollectionGet whether the tile area intersects a machine group which meets the minimum requirements for automation (regardless of whether the machines are currently running).
     /// The tile area to check.
-    /// This is the normal-chest equivalent of .
+    /// This is the normal-chest equivalent of .
     public bool IntersectsAutomatedGroup(Rectangle tileArea)
     {
         var activeTiles = this.ActiveTiles;
@@ -59,7 +59,7 @@ public bool IntersectsAutomatedGroup(Rectangle tileArea)
 
     /// Get whether a tile area contains or is adjacent to a tracked automateable.
     /// The tile area to check.
-    /// This is the normal-chest equivalent of .
+    /// This is the normal-chest equivalent of .
     public bool ContainsOrAdjacent(Rectangle tileArea)
     {
         var activeTiles = this.ActiveTiles;
diff --git a/Automate/Framework/MachineGroup.cs b/Automate/Framework/MachineGroup.cs
index 7e26f416e..33c45eba4 100644
--- a/Automate/Framework/MachineGroup.cs
+++ b/Automate/Framework/MachineGroup.cs
@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
 using System.Linq;
 using Microsoft.Xna.Framework;
 using Pathoschild.Stardew.Common;
@@ -59,6 +60,9 @@ internal class MachineGroup : IMachineGroup
     /// 
     public string? LocationKey { get; }
 
+    /// 
+    public HashSet GlobalContainerKeys { get; } = new(StringComparer.OrdinalIgnoreCase);
+
     /// 
     public IMachine[] Machines { get; protected set; }
 
@@ -67,10 +71,10 @@ internal class MachineGroup : IMachineGroup
 
     /// 
     [MemberNotNullWhen(false, nameof(IMachineGroup.LocationKey))]
-    public bool IsJunimoGroup { get; protected set; }
+    public bool IsGlobalGroup { get; protected set; }
 
     /// 
-    public virtual bool HasInternalAutomation => this.IsJunimoGroup || (this.Machines.Length > 0 && this.Containers.Any(p => !p.IsJunimoChest));
+    public virtual bool HasInternalAutomation => this.IsGlobalGroup || (this.Machines.Length > 0 && this.Containers.Any(p => !p.IsGlobalChest));
 
 
     /*********
@@ -91,7 +95,15 @@ public MachineGroup(string? locationKey, IEnumerable machines, IEnumer
         this.Tiles = [.. tiles];
         this.Monitor = monitor;
 
-        this.IsJunimoGroup = this.Containers.Any(p => p.IsJunimoChest);
+        foreach (IContainer container in this.Containers)
+        {
+            if (container.IsGlobalChest)
+            {
+                this.GlobalContainerKeys.Add(container.GlobalInventoryId);
+            }
+        }
+
+        this.IsGlobalGroup = this.GlobalContainerKeys.Count > 0;
         this.StorageManager = buildStorage(this.GetUniqueContainers(this.Containers));
     }
 
diff --git a/Automate/Framework/MachineManager.cs b/Automate/Framework/MachineManager.cs
index 461742e0f..b60c8c475 100644
--- a/Automate/Framework/MachineManager.cs
+++ b/Automate/Framework/MachineManager.cs
@@ -47,7 +47,7 @@ internal class MachineManager
     public MachineGroupFactory Factory { get; }
 
     /// An aggregate collection of machine groups linked by Junimo chests.
-    public JunimoMachineGroup JunimoMachineGroup { get; }
+    public List GlobalMachineGroups { get; } = new();
 
 
     /*********
@@ -67,7 +67,7 @@ public MachineManager(Func config, DataModel data, IAutomationFactory
         this.Factory = new(this.GetMachineOverride, this.BuildStorage, monitor);
         this.Factory.Add(defaultFactory);
 
-        this.JunimoMachineGroup = new(this.Factory.SortMachines, this.BuildStorage, this.Monitor);
+        //this.GlobalMachineGroups = new(this.Factory.SortMachines, this.BuildStorage, this.Monitor);
     }
 
     /****
@@ -76,8 +76,9 @@ public MachineManager(Func config, DataModel data, IAutomationFactory
     /// Get the machine groups in every location.
     public IEnumerable GetActiveMachineGroups()
     {
-        if (this.JunimoMachineGroup.HasInternalAutomation)
-            yield return this.JunimoMachineGroup;
+        foreach (IMachineGroup group in this.GlobalMachineGroups)
+            if (group.HasInternalAutomation)
+                yield return group;
 
         foreach (IMachineGroup group in this.ActiveMachineGroups)
             yield return group;
@@ -92,7 +93,7 @@ public IEnumerable GetForApi(GameLocation location)
         return this
             .ActiveMachineGroups
             .Concat(this.DisabledMachineGroups)
-            .Concat(this.JunimoMachineGroup.GetAll())
+            .Concat(this.GlobalMachineGroups.SelectMany(group => group.GetAll()))
             .Where(p => p.LocationKey == locationKey);
     }
 
@@ -141,7 +142,7 @@ public void Clear()
         this.MachineData.Clear();
         this.ActiveMachineGroups = [];
         this.DisabledMachineGroups = [];
-        this.JunimoMachineGroup.Clear();
+        this.GlobalMachineGroups.Clear();
     }
 
     /// Clear all registered machines and add all locations to the reload queue.
@@ -149,7 +150,8 @@ public void Reset()
     {
         this.Clear();
 
-        this.JunimoMachineGroup.Rebuild();
+        foreach (GlobalMachineGroup group in this.GlobalMachineGroups)
+            group.Rebuild();
 
         this.ReloadQueue.AddRange(CommonHelper.GetLocations());
     }
@@ -213,7 +215,8 @@ private StorageManager BuildStorage(IContainer[] containers)
     /// The locations which have been removed, and whose machines should be reloaded if they still exist.
     private void ReloadMachinesIn(ISet locations, ISet removedLocations)
     {
-        bool junimoGroupChanged = false;
+        List globalAdded = [];
+        HashSet globalChanged = [];
         bool anyChanged = false;
 
         // remove old groups
@@ -225,10 +228,13 @@ private void ReloadMachinesIn(ISet locations, ISet r
             foreach (string locationKey in locationKeys)
                 anyChanged |= this.MachineData.Remove(locationKey);
 
-            if (this.JunimoMachineGroup.RemoveLocations(locationKeys))
+            foreach (GlobalMachineGroup globalGroup in this.GlobalMachineGroups)
             {
-                anyChanged = true;
-                junimoGroupChanged = true;
+                if (globalGroup.RemoveLocations(locationKeys))
+                {
+                    anyChanged = true;
+                    globalChanged.Add(globalGroup);
+                }
             }
         }
 
@@ -240,31 +246,23 @@ private void ReloadMachinesIn(ISet locations, ISet r
             // collect new groups
             List active = [];
             List disabled = [];
-            List junimo = [];
             foreach (IMachineGroup group in this.Factory.GetMachineGroups(location, this.Monitor))
             {
                 if (!group.HasInternalAutomation)
                     disabled.Add(group);
 
-                else if (group.IsJunimoGroup)
-                    junimo.Add(group);
+                else if (!group.IsGlobalGroup)
+                    active.Add(group);
 
                 else
-                    active.Add(group);
+                    globalAdded.Add(group);
             }
 
             // add groups
             this.MachineData[locationKey] = new MachineDataForLocation(locationKey, active, disabled);
 
             // track change
-            if (junimo.Any())
-            {
-                this.JunimoMachineGroup.Add(junimo);
-                junimoGroupChanged = true;
-                anyChanged = true;
-            }
-            else if (active.Any())
-                anyChanged = true;
+            anyChanged |= active.Any();
         }
 
         // rebuild caches
@@ -283,7 +281,60 @@ private void ReloadMachinesIn(ISet locations, ISet r
             this.DisabledMachineGroups = disabled.ToArray();
         }
 
-        if (junimoGroupChanged)
-            this.JunimoMachineGroup.Rebuild();
+        if (!globalChanged.Any())
+            return;
+
+        // determine distinct groups
+        List> distinctGlobalGroups = [];
+        foreach (HashSet groupKeys in globalChanged.Select(p => p.GlobalContainerKeys))
+        {
+            HashSet? existing = distinctGlobalGroups.FirstOrDefault(p => p.Overlaps(groupKeys));
+            if (existing != null)
+                existing.UnionWith(groupKeys);
+            else
+                distinctGlobalGroups.Add(groupKeys);
+        }
+
+        foreach (HashSet groupKeys in distinctGlobalGroups)
+        {
+            GlobalMachineGroup? selectedGroup = null;
+            int total = this.GlobalMachineGroups.Count;
+
+            for (int i = 0; i < total; i++)
+            {
+                GlobalMachineGroup globalGroup = this.GlobalMachineGroups[i];
+                if (!globalGroup.GlobalContainerKeys.Overlaps(groupKeys))
+                    continue;
+
+                selectedGroup ??= globalGroup;
+                if (selectedGroup == globalGroup)
+                    globalChanged.Add(selectedGroup);
+
+                else
+                {
+                    selectedGroup.Add([.. globalGroup.GetAll()]);
+                    this.GlobalMachineGroups.Remove(globalGroup);
+                    total--;
+                }
+            }
+
+            // create new group
+            if (selectedGroup == null)
+            {
+                selectedGroup = new GlobalMachineGroup(this.Factory.SortMachines, this.BuildStorage, this.Monitor);
+                this.GlobalMachineGroups.Add(selectedGroup);
+            }
+
+            // add groups to selected
+            IList groups = [.. globalAdded.Where(p => p.GlobalContainerKeys.Overlaps(groupKeys))];
+            if (groups.Any())
+                selectedGroup.Add(groups);
+        }
+
+        // rebuild groups
+        foreach (GlobalMachineGroup globalGroup in globalChanged)
+        {
+            globalGroup.Rebuild();
+        }
     }
 }
diff --git a/Automate/Framework/OverlayMenu.cs b/Automate/Framework/OverlayMenu.cs
index 7a4d8deca..03b87337f 100644
--- a/Automate/Framework/OverlayMenu.cs
+++ b/Automate/Framework/OverlayMenu.cs
@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
+using System.Linq;
 using Microsoft.Xna.Framework;
 using Microsoft.Xna.Framework.Graphics;
 using Pathoschild.Stardew.Common;
@@ -25,8 +26,8 @@ internal class OverlayMenu : BaseOverlay
     /// The machine data for the current location.
     private readonly MachineDataForLocation? MachineData;
 
-    /// The machine group for machines connected to Junimo chests.
-    private readonly JunimoMachineGroup JunimoGroup;
+    /// The machine groups for machines connected to global inventories.
+    private readonly List GlobalGroups;
 
 
     /*********
@@ -38,13 +39,13 @@ internal class OverlayMenu : BaseOverlay
     /// Simplifies access to private code.
     /// The unique key for the current location.
     /// The machine groups to display.
-    /// The machine group for machines connected to Junimo chests.
-    public OverlayMenu(IModEvents events, IInputHelper inputHelper, IReflectionHelper reflection, string locationKey, MachineDataForLocation? machineData, JunimoMachineGroup junimoGroup)
+    /// The machine group for machines connected to global inventories.
+    public OverlayMenu(IModEvents events, IInputHelper inputHelper, IReflectionHelper reflection, string locationKey, MachineDataForLocation? machineData, List globalGroups)
         : base(events, inputHelper, reflection)
     {
         this.LocationKey = locationKey;
         this.MachineData = machineData;
-        this.JunimoGroup = junimoGroup;
+        this.GlobalGroups = globalGroups;
     }
 
 
@@ -59,7 +60,7 @@ protected override void DrawWorld(SpriteBatch spriteBatch)
             return;
 
         // draw each tile
-        IReadOnlySet junimoChestTiles = this.JunimoGroup.GetTiles(this.LocationKey);
+        IReadOnlySet globalInventoryTiles = new HashSet(this.GlobalGroups.SelectMany(p => p.GetTiles(this.LocationKey)));
         foreach (Vector2 tile in TileHelper.GetVisibleTiles(expand: 1))
         {
             // get tile's screen coordinates
@@ -70,12 +71,12 @@ protected override void DrawWorld(SpriteBatch spriteBatch)
             // get machine group
             IMachineGroup? group = null;
             Color? color = null;
-            if (junimoChestTiles.Contains(tile))
+            if (globalInventoryTiles.Contains(tile))
             {
-                color = this.JunimoGroup.HasInternalAutomation
+                group = this.GlobalGroups.FirstOrDefault(p => p.GetTiles(this.LocationKey).Contains(tile));
+                color = group?.HasInternalAutomation == true
                     ? Color.Green * 0.2f
                     : Color.Red * 0.2f;
-                group = this.JunimoGroup;
             }
             else if (this.MachineData is not null)
             {
diff --git a/Automate/Framework/Storage/ChestContainer.cs b/Automate/Framework/Storage/ChestContainer.cs
index ab646cbe1..9280f30d3 100644
--- a/Automate/Framework/Storage/ChestContainer.cs
+++ b/Automate/Framework/Storage/ChestContainer.cs
@@ -33,11 +33,14 @@ internal class ChestContainer : IContainer
     /// 
     public string Name => this.Chest.Name;
 
+    /// 
+    public string? GlobalInventoryId => this.Chest.GlobalInventoryId ?? (this.Chest.SpecialChestType == Chest.SpecialChestTypes.JunimoChest ? "JunimoChests" : null);
+
     /// 
     public ModDataDictionary ModData => this.Chest.modData;
 
     /// 
-    public bool IsJunimoChest => this.Chest.SpecialChestType == Chest.SpecialChestTypes.JunimoChest;
+    public bool IsGlobalChest => this.Chest.SpecialChestType == Chest.SpecialChestTypes.JunimoChest || this.Chest.GlobalInventoryId != null;
 
     /// 
     public bool IsLocked => this.Chest.GetMutex().IsLocked();
diff --git a/Automate/Framework/StorageManager.cs b/Automate/Framework/StorageManager.cs
index 8be655787..f91b8c690 100644
--- a/Automate/Framework/StorageManager.cs
+++ b/Automate/Framework/StorageManager.cs
@@ -38,13 +38,13 @@ public void SetContainers(IEnumerable containers)
 
         this.InputContainers = containerCollection
             .Where(p => p.StorageAllowed())
-            .OrderBy(p => p.IsJunimoChest) // push items into Junimo chests last
+            .OrderBy(p => p.IsGlobalChest) // push items into Junimo chests last
             .ThenByDescending(p => p.StoragePreferred())
             .ToArray();
 
         this.OutputContainers = containerCollection
             .Where(p => p.TakingItemsAllowed())
-            .OrderByDescending(p => p.IsJunimoChest) // take items from Junimo chests first
+            .OrderByDescending(p => p.IsGlobalChest) // take items from Junimo chests first
             .ThenByDescending(p => p.TakingItemsPreferred())
             .ToArray();
     }
diff --git a/Automate/IContainer.cs b/Automate/IContainer.cs
index 8ee581e08..c319d53fb 100644
--- a/Automate/IContainer.cs
+++ b/Automate/IContainer.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using StardewValley;
 using StardewValley.Inventories;
 using StardewValley.Mods;
@@ -15,11 +16,15 @@ public interface IContainer : IAutomatable, IEnumerable
     /// The container name (if any).
     string Name { get; }
 
+    /// The global inventory id (if any).
+    string? GlobalInventoryId { get; }
+
     /// The raw mod data for the container.
     ModDataDictionary ModData { get; }
 
-    /// Whether this is a Junimo chest, which shares a global inventory with all other Junimo chests.
-    bool IsJunimoChest { get; }
+    /// Whether this is a global inventory chest, which shares an inventory with all other chests with the same Global Inventory Id.
+    [MemberNotNullWhen(true, nameof(IContainer.GlobalInventoryId))]
+    bool IsGlobalChest { get; }
 
     /// Whether this chest is locked (e.g. because a player has it open).
     bool IsLocked { get; }
diff --git a/Automate/ModEntry.cs b/Automate/ModEntry.cs
index 9bfb27ade..2f0477a8d 100644
--- a/Automate/ModEntry.cs
+++ b/Automate/ModEntry.cs
@@ -430,7 +430,7 @@ private void EnableOverlay()
             reflection: this.Helper.Reflection,
             locationKey: this.MachineManager.Factory.GetLocationKey(Game1.currentLocation),
             machineData: this.MachineManager.GetMachineDataFor(Game1.currentLocation),
-            junimoGroup: this.MachineManager.JunimoMachineGroup
+            globalGroups: this.MachineManager.GlobalMachineGroups
         );
     }
 
@@ -453,7 +453,7 @@ private bool ReloadIfNeeded(GameLocation location, IEnumerable globalData = this.MachineManager.GlobalMachineGroups;
 
         bool shouldReload = false;
         foreach ((Rectangle tileArea, TEntity entity, bool isAdded) in entities)
@@ -479,7 +479,7 @@ private bool ReloadIfNeeded(GameLocation location, IEnumerable p.ContainsOrAdjacent(locationKey, tileArea))
                     || (automateable is IContainer ? data.ContainsOrAdjacent(tileArea) : data.IsConnectedToChest(tileArea));
 
                 if (shouldReload)
@@ -487,7 +487,7 @@ private bool ReloadIfNeeded(GameLocation location, IEnumerable p.IntersectsAutomatedGroup(locationKey, tileArea)))
             {
                 shouldReload = true;
                 break;
From 54e93a81f57a404026ad6738c8d860741db2080e Mon Sep 17 00:00:00 2001
From: Matthew <3005344+LeFauxMatt@users.noreply.github.com>
Date: Thu, 13 Feb 2025 16:59:11 -0800
Subject: [PATCH 2/2] Refactor machine group handling in MachineManager
- Removed unnecessary `using` directive from `GlobalMachineGroup.cs`.
- Changed `globalChanged` type to `HashSet` for broader compatibility.
- Introduced `globalAdded` to track newly added global machine groups.
- Updated logic to utilize `globalAdded` for adding groups.
- Filtered `globalChanged` in the rebuild loop to include only `GlobalMachineGroup` types.
---
 Automate/Framework/GlobalMachineGroup.cs | 1 -
 Automate/Framework/MachineManager.cs     | 7 +++++--
 2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/Automate/Framework/GlobalMachineGroup.cs b/Automate/Framework/GlobalMachineGroup.cs
index bea34f4c5..dd3426264 100644
--- a/Automate/Framework/GlobalMachineGroup.cs
+++ b/Automate/Framework/GlobalMachineGroup.cs
@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Linq;
-using System.Text.RegularExpressions;
 using Microsoft.Xna.Framework;
 using Pathoschild.Stardew.Common;
 using StardewModdingAPI;
diff --git a/Automate/Framework/MachineManager.cs b/Automate/Framework/MachineManager.cs
index b60c8c475..b942714ca 100644
--- a/Automate/Framework/MachineManager.cs
+++ b/Automate/Framework/MachineManager.cs
@@ -216,7 +216,7 @@ private StorageManager BuildStorage(IContainer[] containers)
     private void ReloadMachinesIn(ISet locations, ISet removedLocations)
     {
         List globalAdded = [];
-        HashSet globalChanged = [];
+        HashSet globalChanged = [];
         bool anyChanged = false;
 
         // remove old groups
@@ -255,7 +255,10 @@ private void ReloadMachinesIn(ISet locations, ISet r
                     active.Add(group);
 
                 else
+                {
                     globalAdded.Add(group);
+                    globalChanged.Add(group);
+                }
             }
 
             // add groups
@@ -332,7 +335,7 @@ private void ReloadMachinesIn(ISet locations, ISet r
         }
 
         // rebuild groups
-        foreach (GlobalMachineGroup globalGroup in globalChanged)
+        foreach (GlobalMachineGroup globalGroup in globalChanged.OfType())
         {
             globalGroup.Rebuild();
         }