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 93%
rename from Automate/Framework/JunimoMachineGroup.cs
rename to Automate/Framework/GlobalMachineGroup.cs
index 7bc9b5909..dd3426264 100644
--- a/Automate/Framework/JunimoMachineGroup.cs
+++ b/Automate/Framework/GlobalMachineGroup.cs
@@ -9,7 +9,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 +38,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 +48,7 @@ public JunimoMachineGroup(Func, IEnumerable> sor
monitor: monitor
)
{
- this.IsJunimoGroup = true;
+ this.IsGlobalGroup = true;
this.SortMachines = sortMachines;
}
@@ -64,12 +64,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 +96,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..b942714ca 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,26 @@ 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);
+ globalChanged.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 +284,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.OfType())
+ {
+ 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;