/// <summary>The method invoked when the interface has finished rendering.</summary> private void ReceiveHudRendered() { // render update warning if (this.Config.CheckForUpdates && !this.HasSeenUpdateWarning && this.NewRelease != null) { try { this.HasSeenUpdateWarning = true; CommonHelper.ShowInfoMessage($"You can update Chests Anywhere from {this.CurrentVersion} to {this.NewRelease}."); } catch (Exception ex) { this.HandleError(ex, "showing the new version available"); } } // show chest label if (this.Config.ShowHoverTooltips) { ManagedChest cursorChest = ChestFactory.GetChestFromTile(Game1.currentCursorTile); if (cursorChest != null) { Vector2 tooltipPosition = new Vector2(Game1.getMouseX(), Game1.getMouseY()) + new Vector2(Game1.tileSize / 2f); CommonHelper.DrawHoverBox(Game1.spriteBatch, cursorChest.Name, tooltipPosition, Game1.viewport.Width - tooltipPosition.X - Game1.tileSize / 2f); } } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="menu">The underlying chest menu.</param> /// <param name="chest">The selected chest.</param> /// <param name="chests">The available chests.</param> /// <param name="config">The mod configuration.</param> /// <param name="keys">The configured key bindings.</param> /// <param name="events">The SMAPI events available for mods.</param> /// <param name="input">An API for checking and changing input state.</param> /// <param name="reflection">Simplifies access to private code.</param> /// <param name="showAutomateOptions">Whether to show Automate options.</param> public ShopMenuOverlay(ShopMenu menu, ManagedChest chest, ManagedChest[] chests, ModConfig config, ModConfigKeys keys, IModEvents events, IInputHelper input, IReflectionHelper reflection, bool showAutomateOptions) : base(menu, chest, chests, config, keys, events, input, reflection, showAutomateOptions, keepAlive: () => Game1.activeClickableMenu is ShopMenu, topOffset: Game1.pixelZoom * 6) { this.Menu = menu; this.DefaultPurchaseFilter = menu.canPurchaseCheck; this.DefaultInventoryHighlighter = menu.inventory.highlightMethod; }
/// <summary>Open the menu UI.</summary> private void OpenMenu() { if (this.Config.Range == ChestRange.None) { return; } // handle disabled location if (this.IsDisabledLocation(Game1.currentLocation)) { CommonHelper.ShowInfoMessage("Remote chest access is disabled here. :)", duration: 1000); return; } // get chests RangeHandler range = this.GetCurrentRange(); ManagedChest[] chests = this.ChestFactory.GetChests(range, excludeHidden: true).ToArray(); ManagedChest selectedChest = chests.FirstOrDefault(p => p.Container.IsSameAs(this.SelectedInventory)) ?? chests.FirstOrDefault(); // render menu if (selectedChest != null) { Game1.activeClickableMenu = selectedChest.OpenMenu(); } else { CommonHelper.ShowInfoMessage( "You don't have any chests " + (this.Config.Range == ChestRange.Unlimited ? "yet" : "in range") + ". :)", duration: 1000 ); } }
/// <summary>Open the menu UI.</summary> private void OpenMenu() { if (this.Config.Range == ChestRange.None) { return; } // handle disabled location if (this.IsDisabledLocation(Game1.currentLocation)) { CommonHelper.ShowInfoMessage(I18n.Errors_DisabledFromHere(), duration: 1000); return; } // get chests RangeHandler range = this.GetCurrentRange(); ManagedChest[] chests = this.ChestFactory.GetChests(range, excludeHidden: true).ToArray(); ManagedChest selectedChest = ChestFactory.GetBestMatch(chests, this.LastChest.Value) ?? chests.FirstOrDefault(p => object.ReferenceEquals(p.Location, Game1.currentLocation)) ?? chests.FirstOrDefault(); // show error if (selectedChest == null) { CommonHelper.ShowInfoMessage(this.GetNoChestsFoundError(), duration: 1000); return; } // render menu Game1.activeClickableMenu = selectedChest.OpenMenu(); }
/// <summary>The method invoked when the active menu changes.</summary> /// <param name="sender">The event sender.</param> /// <param name="e">The event arguments.</param> private void MenuEvents_MenuChanged(object sender, EventArgsClickableMenuChanged e) { // remove overlay if (e.PriorMenu is ItemGrabMenu) { this.ManageChestOverlay?.Dispose(); this.ManageChestOverlay = null; } // add overlay if (e.NewMenu is ItemGrabMenu chestMenu) { // get open chest ManagedChest chest = this.ChestFactory.GetChestFromMenu(chestMenu); if (chest == null) { return; } // reopen shipping box in standard chest UI if (chest.Container is ShippingBinContainer && !chestMenu.showReceivingMenu) { Game1.activeClickableMenu = chest.OpenMenu(); } // add overlay ManagedChest[] chests = this.ChestFactory.GetChestsForDisplay(selected: chest.Container).ToArray(); this.ManageChestOverlay = new ManageChestOverlay(chestMenu, chest, chests, this.Config, this.Helper.Translation); this.ManageChestOverlay.OnChestSelected += selected => { this.SelectedInventory = selected.Container.Inventory; Game1.activeClickableMenu = selected.OpenMenu(); }; } }
/// <summary>Open the menu UI.</summary> private void OpenMenu() { if (this.Config.Range == ChestRange.None) { return; } // handle disabled location if (this.IsDisabledLocation(Game1.currentLocation)) { CommonHelper.ShowInfoMessage(this.Helper.Translation.Get("errors.disabled-from-here"), duration: 1000); return; } // get chests RangeHandler range = this.GetCurrentRange(); ManagedChest[] chests = this.ChestFactory.GetChests(range, excludeHidden: true).ToArray(); ManagedChest selectedChest = chests.FirstOrDefault(p => p.Container.IsSameAs(this.SelectedInventory)) ?? chests.FirstOrDefault(); // show error if (selectedChest == null) { string translationKey = this.GetNoChestsFoundErrorKey(); CommonHelper.ShowInfoMessage(this.Helper.Translation.Get(translationKey), duration: 1000); return; } // render menu Game1.activeClickableMenu = selectedChest.OpenMenu(); }
/// <summary>Change the chest UI overlay if needed to match the current menu.</summary> /// <remarks>Since the menu gets reopened whenever the chest inventory changes, this method needs to be called before/after tick to avoid a visible UI flicker.</remarks> private void ChangeOverlayIfNeeded() { IClickableMenu menu = Game1.activeClickableMenu; // already matches menu if (this.CurrentOverlay?.ForMenuInstance == menu) { return; } // remove old overlay if (this.CurrentOverlay != null) { this.CurrentOverlay?.Dispose(); this.CurrentOverlay = null; } // get open chest ManagedChest chest = this.ChestFactory.GetChestFromMenu(menu); if (chest == null) { return; } // reopen shipping box in standard chest UI if needed // This is called in two cases: // - When the player opens the shipping bin directly, it opens the shipping bin view instead of the full chest view. // - When the player changes the items in the chest view, it reopens itself but loses the constructor args (e.g. highlight function). if (this.Config.EnableShippingBin && chest.Container is ShippingBinContainer) { if (menu is ItemGrabMenu chestMenu && (!chestMenu.showReceivingMenu || !(chestMenu.inventory.highlightMethod?.Target is ShippingBinContainer))) { menu = (ItemGrabMenu)chest.OpenMenu(); Game1.activeClickableMenu = menu; } } // add overlay RangeHandler range = this.GetCurrentRange(); ManagedChest[] chests = this.ChestFactory.GetChests(range, excludeHidden: true, alwaysIncludeContainer: chest.Container).ToArray(); bool isAutomateInstalled = this.Helper.ModRegistry.IsLoaded("Pathoschild.Automate"); switch (menu) { case ItemGrabMenu chestMenu: this.CurrentOverlay = new ChestOverlay(chestMenu, chest, chests, this.Config, this.Keys, this.Helper.Events, this.Helper.Input, this.Helper.Translation, showAutomateOptions: isAutomateInstalled && chest.CanConfigureAutomate); break; case ShopMenu shopMenu: this.CurrentOverlay = new ShopMenuOverlay(shopMenu, chest, chests, this.Config, this.Keys, this.Helper.Events, this.Helper.Input, this.Helper.Translation, showAutomateOptions: isAutomateInstalled && chest.CanConfigureAutomate); break; } this.CurrentOverlay.OnChestSelected += selected => { this.SelectedInventory = selected.Container.Inventory; Game1.activeClickableMenu = selected.OpenMenu(); }; }
/// <summary>The method invoked when the active menu changes.</summary> /// <param name="previousMenu">The previous menu (if any)</param> /// <param name="newMenu">The new menu (if any).</param> private void ReceiveMenuChanged(IClickableMenu previousMenu, IClickableMenu newMenu) { // remove overlay if (previousMenu is ItemGrabMenu) { this.ManageChestOverlay?.Dispose(); this.ManageChestOverlay = null; } // add overlay if (newMenu is ItemGrabMenu chestMenu) { // get open chest ManagedChest chest = this.ChestFactory.GetChestFromMenu(chestMenu); if (chest == null) { return; } // add overlay ManagedChest[] chests = this.ChestFactory.GetChestsForDisplay(selectedChest: chest.Chest).ToArray(); this.ManageChestOverlay = new ManageChestOverlay(chestMenu, chest, chests, this.Config, this.Helper.Translation); this.ManageChestOverlay.OnChestSelected += selected => { this.SelectedChest = selected.Chest; Game1.activeClickableMenu = selected.OpenMenu(); }; } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="menu">The underlying chest menu.</param> /// <param name="chest">The selected chest.</param> /// <param name="chests">The available chests.</param> /// <param name="config">The mod configuration.</param> public ManageChestOverlay(ItemGrabMenu menu, ManagedChest chest, ManagedChest[] chests, ModConfig config) : base(keepAlive: () => Game1.activeClickableMenu is ItemGrabMenu) { // menu this.Menu = menu; #if SDV_1_2 this.MenuInventoryMenu = ((ItemGrabMenu)Game1.activeClickableMenu).ItemsToGrabMenu; #else this.MenuInventoryMenu = (InventoryMenu)typeof(ItemGrabMenu).GetField("ItemsToGrabMenu", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(menu); if (this.MenuInventoryMenu == null) { throw new InvalidOperationException("The menu doesn't seem to have a player inventory."); } #endif this.DefaultChestHighlighter = menu.inventory.highlightMethod; this.DefaultInventoryHighlighter = this.MenuInventoryMenu.highlightMethod; // chests & config this.Chest = chest; this.Chests = chests; this.Groups = chests.Select(p => p.GetGroup()).Distinct().OrderBy(p => p).ToArray(); this.Config = config; // components this.ReinitialiseComponents(); }
/// <summary>Open the menu UI.</summary> private void OpenMenu() { if (this.Config.Range == ChestRange.None) { return; } // get chests RangeHandler rangeHandler = new RangeHandler(this.Data.WorldAreas, this.Config.Range, Game1.currentLocation); ManagedChest[] chests = this.ChestFactory.GetChests(rangeHandler, excludeHidden: true).ToArray(); ManagedChest selectedChest = chests.FirstOrDefault(p => p.Container.Inventory == this.SelectedInventory) ?? chests.FirstOrDefault(); // render menu if (selectedChest != null) { Game1.activeClickableMenu = selectedChest.OpenMenu(); } else { CommonHelper.ShowInfoMessage( "You don't have any chests " + (this.Config.Range == ChestRange.Unlimited ? "yet" : "in range") + ". :)", duration: 1000 ); } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="menu">The underlying chest menu.</param> /// <param name="chest">The selected chest.</param> /// <param name="chests">The available chests.</param> /// <param name="config">The mod configuration.</param> /// <param name="keys">The configured key bindings.</param> /// <param name="events">The SMAPI events available for mods.</param> /// <param name="input">An API for checking and changing input state.</param> /// <param name="translations">Provides translations stored in the mod's folder.</param> /// <param name="showAutomateOptions">Whether to show Automate options.</param> public ChestOverlay(ItemGrabMenu menu, ManagedChest chest, ManagedChest[] chests, ModConfig config, ModConfigKeys keys, IModEvents events, IInputHelper input, ITranslationHelper translations, bool showAutomateOptions) : base(menu, chest, chests, config, keys, events, input, translations, showAutomateOptions, keepAlive: () => Game1.activeClickableMenu is ItemGrabMenu, topOffset: -Game1.pixelZoom * 9) { this.Menu = menu; this.MenuInventoryMenu = menu.ItemsToGrabMenu; this.DefaultChestHighlighter = menu.inventory.highlightMethod; this.DefaultInventoryHighlighter = this.MenuInventoryMenu.highlightMethod; }
/// <summary>Notify Automate that a chest's automation options updated.</summary> /// <param name="chest">The chest that was updated.</param> private void NotifyAutomateOfChestUpdate(ManagedChest chest) { long hostId = Game1.MasterPlayer.UniqueMultiplayerID; var message = new AutomateUpdateChestMessage { LocationName = chest.Location.Name, Tile = chest.Tile }; this.Helper.Multiplayer.SendMessage(message, nameof(AutomateUpdateChestMessage), modIDs: new[] { "Pathoschild.Automate" }, playerIDs: new[] { hostId }); }
/// <summary>The method invoked when the interface has finished rendering.</summary> private void ReceiveHudRendered() { // show chest label if (this.Config.ShowHoverTooltips) { ManagedChest cursorChest = this.ChestFactory.GetChestFromTile(Game1.currentCursorTile); if (cursorChest != null) { Vector2 tooltipPosition = new Vector2(Game1.getMouseX(), Game1.getMouseY()) + new Vector2(Game1.tileSize / 2f); CommonHelper.DrawHoverBox(Game1.spriteBatch, cursorChest.Name, tooltipPosition, Game1.viewport.Width - tooltipPosition.X - Game1.tileSize / 2f); } } }
/// <summary>The method invoked when the interface has finished rendering.</summary> /// <param name="sender">The event sender.</param> /// <param name="e">The event arguments.</param> private void GraphicsEvents_OnPostRenderHudEvent(object sender, EventArgs e) { // show chest label if (this.Config.ShowHoverTooltips) { ManagedChest cursorChest = this.ChestFactory.GetChestFromTile(Game1.currentCursorTile); if (cursorChest != null && !cursorChest.HasDefaultName()) { Vector2 tooltipPosition = new Vector2(Game1.getMouseX(), Game1.getMouseY()) + new Vector2(Game1.tileSize / 2f); CommonHelper.DrawHoverBox(Game1.spriteBatch, cursorChest.DisplayName, tooltipPosition, Game1.viewport.Width - tooltipPosition.X - Game1.tileSize / 2f); } } }
/// <summary>Get the player chest from the specified menu (if any).</summary> /// <param name="menu">The menu to check.</param> public static ManagedChest GetChestFromMenu(ItemGrabMenu menu) { // from menu target Chest target = menu.behaviorOnItemGrab?.Target as Chest; ManagedChest chest = target != null ? ChestFactory.GetChests().FirstOrDefault(p => p.Chest == target) : null; if (chest != null) { return(chest); } // fallback to open chest return(ChestFactory.GetChests().FirstOrDefault(p => p.Chest.currentLidFrame == 135)); }
/// <summary>Open the menu UI.</summary> private void OpenMenu() { // get chests ManagedChest[] chests = this.ChestFactory.GetChestsForDisplay().ToArray(); ManagedChest selectedChest = chests.FirstOrDefault(p => p.Container.Inventory == this.SelectedInventory) ?? chests.FirstOrDefault(); // render menu if (selectedChest != null) { Game1.activeClickableMenu = selectedChest.OpenMenu(); } else { CommonHelper.ShowInfoMessage("You don't have any chests yet. :)", duration: 1000); } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="menu">The underlying chest menu.</param> /// <param name="chest">The selected chest.</param> /// <param name="chests">The available chests.</param> /// <param name="config">The mod configuration.</param> public ManageChestOverlay(ItemGrabMenu menu, ManagedChest chest, ManagedChest[] chests, ModConfig config) : base(keepAlive: () => Game1.activeClickableMenu is ItemGrabMenu) { // menu this.Menu = menu; this.MenuInventoryMenu = ((ItemGrabMenu)Game1.activeClickableMenu).ItemsToGrabMenu; this.DefaultChestHighlighter = menu.inventory.highlightMethod; this.DefaultInventoryHighlighter = this.MenuInventoryMenu.highlightMethod; // chests & config this.Chest = chest; this.Chests = chests; this.Groups = chests.Select(p => p.GetGroup()).Distinct().OrderBy(p => p).ToArray(); this.Config = config; // components this.ReinitialiseComponents(); }
/// <summary>Get the player chest from the specified menu (if any).</summary> /// <param name="menu">The menu to check.</param> public ManagedChest GetChestFromMenu(ItemGrabMenu menu) { // get from opened inventory { object target = menu.behaviorOnItemGrab?.Target; List <Item> inventory = (target as Chest)?.items ?? (target as IContainer)?.Inventory; if (inventory != null) { ManagedChest chest = this.GetChests().FirstOrDefault(p => p.Container.Inventory == inventory); if (chest != null) { return(chest); } } } // fallback to open chest return(this.GetChests().FirstOrDefault(p => p.Container.IsOpen())); }
/// <summary>The method invoked when the active menu changes.</summary> /// <param name="sender">The event sender.</param> /// <param name="e">The event arguments.</param> private void MenuEvents_MenuChanged(object sender, EventArgsClickableMenuChanged e) { // remove overlay if (e.PriorMenu is ItemGrabMenu) { this.ManageChestOverlay?.Dispose(); this.ManageChestOverlay = null; } // add overlay if (e.NewMenu is ItemGrabMenu chestMenu) { // get open chest ManagedChest chest = this.ChestFactory.GetChestFromMenu(chestMenu); if (chest == null) { return; } // reopen shipping box in standard chest UI if needed // This is called in two cases: // - When the player opens the shipping bin directly, it opens the shipping bin view instead of the full chest view. // - When the player changes the items in the chest view, it reopens itself but loses the constructor args (e.g. highlight function). if (this.Config.EnableShippingBin && chest.Container is ShippingBinContainer && (!chestMenu.showReceivingMenu || !(chestMenu.inventory.highlightMethod?.Target is ShippingBinContainer))) { chestMenu = chest.OpenMenu(); Game1.activeClickableMenu = chestMenu; } // add overlay RangeHandler range = this.GetCurrentRange(); ManagedChest[] chests = this.ChestFactory.GetChests(range, excludeHidden: true, alwaysIncludeContainer: chest.Container).ToArray(); bool isAutomateInstalled = this.Helper.ModRegistry.IsLoaded("Pathoschild.Automate"); this.ManageChestOverlay = new ManageChestOverlay(chestMenu, chest, chests, this.Config, this.Helper.Translation, showAutomateOptions: isAutomateInstalled); this.ManageChestOverlay.OnChestSelected += selected => { this.SelectedInventory = selected.Container.Inventory; Game1.activeClickableMenu = selected.OpenMenu(); }; } }
/// <summary>Get all player chests.</summary> /// <param name="range">Determines whether given locations are in range of the player for remote chest access.</param> /// <param name="excludeHidden">Whether to exclude chests marked as hidden.</param> /// <param name="alwaysInclude">A chest to include even if it would normally be hidden.</param> public IEnumerable <ManagedChest> GetChests(RangeHandler range, bool excludeHidden = false, ManagedChest alwaysInclude = null) { IEnumerable <ManagedChest> Search() { // get location info var locations = ( from GameLocation location in this.GetAccessibleLocations() select new { Location = location, Category = this.GetCategory(location) } ) .ToArray(); IDictionary <string, int> defaultCategories = locations .GroupBy(p => p.Category) .Where(p => p.Count() > 1) .ToDictionary(p => p.Key, p => 0); // find chests foreach (var entry in locations) { IDictionary <string, int> nameCounts = new Dictionary <string, int>(); // get info GameLocation location = entry.Location; string category = defaultCategories.ContainsKey(entry.Category) ? I18n.DefaultCategory_Duplicate(locationName: entry.Category, number: ++defaultCategories[entry.Category]) : entry.Category; // chests in location foreach (KeyValuePair <Vector2, SObject> pair in location.Objects.Pairs) { Vector2 tile = pair.Key; SObject obj = pair.Value; // chests if (obj is Chest chest && chest.playerChest.Value) { yield return(new ManagedChest( container: new ChestContainer(chest, context: chest, showColorPicker: this.CanShowColorPicker(chest, location), this.Reflection), location: location, tile: tile, mapEntity: chest, defaultDisplayName: this.GetDisambiguatedDefaultName(chest.DisplayName, nameCounts), defaultCategory: category )); }
/// <summary>The method invoked when the player left-clicks.</summary> /// <param name="x">The X-position of the cursor.</param> /// <param name="y">The Y-position of the cursor.</param> /// <returns>Whether the event has been handled and shouldn't be propagated further.</returns> protected override bool ReceiveLeftClick(int x, int y) { switch (this.ActiveElement) { // edit form case Element.EditForm: // name field if (this.EditNameField.GetBounds().Contains(x, y)) { this.EditNameField.Select(); } // category field else if (this.EditCategoryField.GetBounds().Contains(x, y)) { this.EditCategoryField.Select(); } // order field else if (this.EditOrderField.GetBounds().Contains(x, y)) { this.EditOrderField.Select(); } // 'hide chest' checkbox else if (this.EditHideChestField.GetBounds().Contains(x, y)) { this.EditHideChestField.Toggle(); } // save button else if (this.EditSaveButton.containsPoint(x, y)) { this.SaveEdit(); this.ActiveElement = Element.Menu; } // exit button else if (this.EditExitButton.containsPoint(x, y)) { this.ActiveElement = Element.Menu; } return(true); // handle all clicks while open // chest list case Element.ChestList: // close dropdown this.ActiveElement = Element.Menu; // select chest if (this.ChestSelector.containsPoint(x, y)) { ManagedChest chest = this.ChestSelector.Select(x, y); if (chest != null) { this.SelectChest(chest); this.ReinitialiseComponents(); } } return(true); // handle all clicks while open // group list case Element.GroupList: // close dropdown this.ActiveElement = Element.Menu; // select group if (this.GroupSelector.containsPoint(x, y)) { string group = this.GroupSelector.Select(x, y); if (group != null && group != this.SelectedGroup) { this.SelectChest(this.Chests.First(chest => chest.GetGroup() == group)); this.ReinitialiseComponents(); } } return(true); // handle all clicks while open // buttons & dropdown default: if (this.EditButton.containsPoint(x, y)) { this.OpenEdit(); } else if (this.SortInventoryButton.containsPoint(x, y)) { this.SortInventory(); } else if (this.ChestTab.containsPoint(x, y)) { this.ActiveElement = Element.ChestList; } else if (this.GroupTab?.containsPoint(x, y) == true) { this.ActiveElement = Element.GroupList; } else { return(false); } return(true); } }
/// <summary>Switch to the specified chest.</summary> /// <param name="chest">The chest to select.</param> public void SelectChest(ManagedChest chest) { this.OnChestSelected?.Invoke(chest); }