diff --git a/MultiSelect.cs b/MultiSelect.cs index e1be398..1cee51a 100644 --- a/MultiSelect.cs +++ b/MultiSelect.cs @@ -47,7 +47,7 @@ namespace UIFixes public static void Clear() { - // ToList() because we'll be modifying the collection + // ToList() because modifying the collection foreach (GridItemView itemView in SelectedItemViews.Keys.ToList()) { Deselect(itemView); diff --git a/Patches/ContextMenuPatches.cs b/Patches/ContextMenuPatches.cs index b500f9e..927ed16 100644 --- a/Patches/ContextMenuPatches.cs +++ b/Patches/ContextMenuPatches.cs @@ -91,31 +91,36 @@ namespace UIFixes public class CreateSubInteractionsInventoryPatch : ModulePatch { + private static bool LoadingInsuranceActions = false; + protected override MethodBase GetTargetMethod() { return AccessTools.Method(InventoryRootInteractionsType, "CreateSubInteractions"); } - private static int GetPlayerRubles(ItemUiContext itemUiContext) - { - StashClass stash = itemUiContext.R().InventoryController.Inventory.Stash; - if (stash == null) - { - return 0; - } - - return R.Money.GetMoneySums(stash.Grid.ContainedItems.Keys)[ECurrencyType.RUB]; - } - [PatchPrefix] public static bool Prefix(EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, Item ___item_0, ItemUiContext ___itemUiContext_1) { + // Clear this, since something else should be active (even a different mouseover of the insurance button) + LoadingInsuranceActions = false; + if (parentInteraction == EItemInfoButton.Insure) { int playerRubles = GetPlayerRubles(___itemUiContext_1); CurrentInsuranceInteractions = new(___item_0, ___itemUiContext_1, playerRubles); - CurrentInsuranceInteractions.LoadAsync(() => subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions)); + + // Because this is async, need to protect against a different subInteractions activating before loading is done + // This isn't thread-safe at all but now the race condition is a microsecond instead of hundreds of milliseconds. + LoadingInsuranceActions = true; + CurrentInsuranceInteractions.LoadAsync(() => + { + if (LoadingInsuranceActions) + { + subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions); + LoadingInsuranceActions = false; + } + }); return false; } @@ -150,6 +155,8 @@ namespace UIFixes public class CreateSubInteractionsTradingPatch : ModulePatch { + private static bool LoadingInsuranceActions = false; + protected override MethodBase GetTargetMethod() { return AccessTools.Method(TradingRootInteractionsType, "CreateSubInteractions"); @@ -158,22 +165,40 @@ namespace UIFixes [PatchPrefix] public static bool Prefix(object __instance, EItemInfoButton parentInteraction, ISubInteractions subInteractionsWrapper, ItemUiContext ___itemUiContext_0) { - Dictionary playerCurrencies = R.Money.GetMoneySums(___itemUiContext_0.R().InventoryController.Inventory.Stash.Grid.ContainedItems.Keys); - int playerRubles = playerCurrencies[ECurrencyType.RUB]; - - // CreateSubInteractions is only on the base class here, which doesn't have an Item. But __instance is actually a GClass3032 - Item item = (Item)TradingRootInteractionsItemField.GetValue(__instance); + // Clear this, since something else should be active (even a different mouseover of the insurance button) + LoadingInsuranceActions = false; if (parentInteraction == EItemInfoButton.Insure) { + int playerRubles = GetPlayerRubles(___itemUiContext_0); + + // CreateSubInteractions is only on the base class here, which doesn't have an Item. But __instance is actually a GClass3032 + Item item = (Item)TradingRootInteractionsItemField.GetValue(__instance); + CurrentInsuranceInteractions = new(item, ___itemUiContext_0, playerRubles); - CurrentInsuranceInteractions.LoadAsync(() => subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions)); + + // Because this is async, need to protect against a different subInteractions activating before loading is done + // This isn't thread-safe at all but now the race condition is a microsecond instead of hundreds of milliseconds. + LoadingInsuranceActions = true; + CurrentInsuranceInteractions.LoadAsync(() => + { + if (LoadingInsuranceActions) + { + subInteractionsWrapper.SetSubInteractions(CurrentInsuranceInteractions); + LoadingInsuranceActions = false; + } + }); return false; } if (parentInteraction == EItemInfoButton.Repair) { + int playerRubles = GetPlayerRubles(___itemUiContext_0); + + // CreateSubInteractions is only on the base class here, which doesn't have an Item. But __instance is actually a GClass3032 + Item item = (Item)TradingRootInteractionsItemField.GetValue(__instance); + CurrentRepairInteractions = new(item, ___itemUiContext_0, playerRubles); subInteractionsWrapper.SetSubInteractions(CurrentRepairInteractions); @@ -276,5 +301,16 @@ namespace UIFixes return true; } } + + private static int GetPlayerRubles(ItemUiContext itemUiContext) + { + StashClass stash = itemUiContext.R().InventoryController.Inventory.Stash; + if (stash == null) + { + return 0; + } + + return R.Money.GetMoneySums(stash.Grid.ContainedItems.Keys)[ECurrencyType.RUB]; + } } } diff --git a/Patches/ContextMenuShortcutPatch.cs b/Patches/ContextMenuShortcutPatches.cs similarity index 100% rename from Patches/ContextMenuShortcutPatch.cs rename to Patches/ContextMenuShortcutPatches.cs diff --git a/Patches/FleaPrevSearchPatches.cs b/Patches/FleaPrevSearchPatches.cs index 9250872..36e53a4 100644 --- a/Patches/FleaPrevSearchPatches.cs +++ b/Patches/FleaPrevSearchPatches.cs @@ -113,7 +113,7 @@ namespace UIFixes return; } - // Restore scroll position now that the we're loaded + // Restore scroll position now that offers are loaded if (History.Any()) { offerViewList.R().Scroller.SetScrollPosition(History.Peek().scrollPosition); @@ -239,7 +239,7 @@ namespace UIFixes newRule.SortType = filterRule.SortType; newRule.SortDirection = filterRule.SortDirection; - // We can't set handbookId yet - it limits the result set and that in turn limits what categories even display + // Can't set handbookId yet - it limits the result set and that in turn limits what categories even display DelayedHandbookId = filterRule.HandbookId; ragfair.SetFilterRule(newRule, true, true); @@ -339,7 +339,7 @@ namespace UIFixes return AccessTools.Method(typeof(OfferViewList), nameof(OfferViewList.method_10)); } - // The firs thing this method does is set scrollposition to 0, so we need to grab it first + // The first thing this method does is set scrollposition to 0, so grab it first [PatchPrefix] public static void Prefix(LightScroller ____scroller) { diff --git a/Patches/LoadAmmoInRaidPatches.cs b/Patches/LoadAmmoInRaidPatches.cs index 834ba16..cb39240 100644 --- a/Patches/LoadAmmoInRaidPatches.cs +++ b/Patches/LoadAmmoInRaidPatches.cs @@ -72,7 +72,7 @@ namespace UIFixes inventoryController.LoadMagazine(bullets, magazine, count, false).HandleExceptions(); } - // The calling code in this instance doesn't do anything with the task, but it does await it, so if we don't return a Task it nullrefs + // The calling code in this instance doesn't do anything with the task, but it does await it, so if this doesn't return a Task it nullrefs __result = Task.CompletedTask; return false; } diff --git a/Patches/MultiSelectPatches.cs b/Patches/MultiSelectPatches.cs index a6c6a3a..5542756 100644 --- a/Patches/MultiSelectPatches.cs +++ b/Patches/MultiSelectPatches.cs @@ -240,7 +240,7 @@ namespace UIFixes } [PatchPrefix] - public static bool Prefix(GridView __instance, ItemContextClass itemContext, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result) + public static bool Prefix(GridView __instance, ItemContextClass itemContext, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result, ItemUiContext ___itemUiContext_0) { if (!Settings.EnableMultiSelect.Value || InPatch || !MultiSelect.Active) { @@ -311,6 +311,12 @@ namespace UIFixes } else { + // Wrap this error to display it + if (operation.Error is GClass3292 noRoomError) + { + operation = new(new DisplayableErrorWrapper(noRoomError)); + } + break; } @@ -326,7 +332,7 @@ namespace UIFixes HidePreviews(); } - // We didn't simulate so now we undo + // Didn't simulate so now undo while (operations.Any()) { operations.Pop().Value?.RollBack(); @@ -335,6 +341,21 @@ namespace UIFixes // result and operation are set to the last one that completed - so success if they all passed, or the first failure return false; } + + // GridView.HighlightItemViewPosition has a blacklist of errors it won't show, but it shows other types. + // Wrapping an error can get past that + private class DisplayableErrorWrapper(InventoryError error) : InventoryError + { + public override string ToString() + { + return error.ToString(); + } + + public override string GetLocalizedDescription() + { + return error.GetLocalizedDescription(); + } + } } public class GridViewAcceptItemPatch : ModulePatch @@ -470,7 +491,7 @@ namespace UIFixes } [PatchPrefix] - public static bool Prefix(SlotView __instance, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result, InventoryControllerClass ___InventoryController) + public static bool Prefix(SlotView __instance, ItemContextAbstractClass targetItemContext, ref GStruct413 operation, ref bool __result, InventoryControllerClass ___InventoryController, ItemUiContext ___ItemUiContext) { if (!Settings.EnableMultiSelect.Value || InPatch || !MultiSelect.Active) { @@ -500,7 +521,7 @@ namespace UIFixes } } - // We didn't simulate so now we undo + // Didn't simulate so now undo while (operations.Any()) { operations.Pop().Value?.RollBack(); @@ -599,7 +620,7 @@ namespace UIFixes HidePreviews(); } - // We didn't simulate so now we undo + // Didn't simulate so now undo while (operations.Any()) { operations.Pop().Value?.RollBack(); @@ -631,7 +652,7 @@ namespace UIFixes traderAssortmentController.PrepareToSell(itemContext.Item, locationInGrid); itemContext.CloseDependentWindows(); - // For the rest of the items we still need to use quickfind + // For the rest of the items, still need to use quickfind foreach (ItemContextClass selectedItemContext in MultiSelect.ItemContexts.Where(ic => ic.Item != itemContext.Item)) { GStruct413 operation = InteractionsHandlerClass.QuickFindAppropriatePlace(selectedItemContext.Item, traderAssortmentController.TraderController, [__instance.Grid.ParentItem as LootItemClass], InteractionsHandlerClass.EMoveItemOrder.Apply, true); diff --git a/Patches/SwapPatches.cs b/Patches/SwapPatches.cs index 99daccb..d99a959 100644 --- a/Patches/SwapPatches.cs +++ b/Patches/SwapPatches.cs @@ -15,10 +15,10 @@ namespace UIFixes { public static class SwapPatches { - // Source container for the drag - we have to grab this early to check it + // Source container for the drag - grab this early to check it private static IContainer SourceContainer; - // Whether we're being called from the "check every slot" loop + // Whether it's being called from the "check every slot" loop private static bool InHighlight = false; // The most recent CheckItemFilter result - needed to differentiate "No room" from incompatible @@ -237,7 +237,7 @@ namespace UIFixes // This is the location you're dragging it, including rotation LocationInGrid itemToLocation = __instance.CalculateItemLocation(itemContext); - // Target is a grid because we're in the GridView patch, i.e. you're dragging it over a grid + // Target is a grid because this is the GridView patch, i.e. you're dragging it over a grid var targetGridItemAddress = new R.GridItemAddress(targetItemAddress); ItemAddress itemToAddress = R.GridItemAddress.Create(targetGridItemAddress.Grid, itemToLocation); @@ -276,7 +276,7 @@ namespace UIFixes } } - // If we're coming from a grid, try rotating the target object + // If coming from a grid, try rotating the target object if (R.GridItemAddress.Type.IsInstanceOfType(itemAddress)) { var targetToLocation = new R.GridItemAddress(targetToAddress).LocationInGrid; @@ -286,7 +286,7 @@ namespace UIFixes var result = InteractionsHandlerClass.Swap(item, itemToAddress, targetItem, targetToAddress, traderControllerClass, true); if (result.Succeeded) { - // Only save this operation result if it succeeded, otherwise we return the non-rotated result from above + // Only save this operation result if it succeeded, otherwise return the non-rotated result from above operation = new R.SwapOperation(result).ToGridViewCanAcceptOperation(); __result = true; return; @@ -443,7 +443,7 @@ namespace UIFixes } // CanApply, when dealing with containers, eventually calls down into FindPlaceForItem, which calls CheckItemFilter. For reasons, - // if an item fails the filters, it returns the error "no space", instead of "no action". Try to detect this, so we can swap. + // if an item fails the filters, it returns the error "no space", instead of "no action". Try to detect this in order to swap. public class DetectFilterForSwapPatch : ModulePatch { protected override MethodBase GetTargetMethod() @@ -461,7 +461,7 @@ namespace UIFixes } // When dragging an item around, by default it updates an ItemSpecificationPanel when you drag an item on top of a slot - // It doesn't do anything when you drag an item from a slot onto some other item elsewhere. But with swap, we should update the item panel then too. + // It doesn't do anything when you drag an item from a slot onto some other item elsewhere. But with swap, update the item panel then too. public class InspectWindowUpdateStatsOnSwapPatch : ModulePatch { protected override MethodBase GetTargetMethod()