From 29599dfa6977657beb75a4416c3c257f5112eaef Mon Sep 17 00:00:00 2001 From: Tyfon <29051038+tyfon7@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:09:43 -0700 Subject: [PATCH] Returning tools to containers; server config --- Patches/PutToolsBackPatch.cs | 69 ++++++++++++++++++++++++ Plugin.cs | 1 + server/config/config.json | 3 ++ server/src/mod.ts | 101 +++++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 Patches/PutToolsBackPatch.cs create mode 100644 server/config/config.json diff --git a/Patches/PutToolsBackPatch.cs b/Patches/PutToolsBackPatch.cs new file mode 100644 index 0000000..2605b61 --- /dev/null +++ b/Patches/PutToolsBackPatch.cs @@ -0,0 +1,69 @@ +using Aki.Reflection.Patching; +using EFT; +using EFT.InventoryLogic; +using HarmonyLib; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace UIFixes +{ + public class PutToolsBackPatch : ModulePatch + { + protected override MethodBase GetTargetMethod() + { + return AccessTools.Method(typeof(GClass1841), nameof(GClass1841.method_8)); + } + + // The patched method can't handle new items that aren't in stash root. + // Find items that are in subcontainers and handle them first - the patched method will ignore items that have a CurrentAddress + // This is a subset of the original method - doesn't handle slots, equipment containers, etc. + [PatchPrefix] + public static void Prefix(ref GClass1189[] newItems, Profile ___profile_0, ItemFactory ___gclass1486_0, GClass2764 ___gclass2764_0) + { + Inventory inventory = ___profile_0.Inventory; + StashClass stash = inventory.Stash; + if (inventory != null && stash != null) + { + var handledContainers = new ContainerCollection[] { inventory.Stash, inventory.Equipment, inventory.QuestRaidItems, inventory.QuestStashItems, inventory.SortingTable }; + var unhandledItems = newItems.Where(i => !handledContainers.Select(c => c.Id).Contains(i.parentId)).ToArray(); + + if (!unhandledItems.Any()) + { + return; + } + + // Change the parameter to remove the items handled here + newItems = newItems.Except(unhandledItems).ToArray(); + + List stashItems = stash.GetNotMergedItems().ToList(); + + ItemFactory.GStruct134 tree = ___gclass1486_0.FlatItemsToTree(unhandledItems, true, null); + foreach (Item item in tree.Items.Values.Where(i => i.CurrentAddress == null)) + { + GClass1189 newItem = unhandledItems.First(i => i._id == item.Id); + if (newItem.parentId != null || newItem.slotId != null) + { + // Assuming here that unhandled items are trying to go into containers in the stash - find that container + Item parent = stashItems.FirstOrDefault(i => i.Id == newItem.parentId); + if (parent is ContainerCollection containerCollection) + { + if (containerCollection.GetContainer(newItem.slotId) is StashGridClass grid) + { + LocationInGrid location = GClass1485.CreateItemLocation(newItem.location); + ItemAddress itemAddress = new GClass2769(grid, location); + + GStruct414 operation = InteractionsHandlerClass.Add(item, itemAddress, ___gclass2764_0, false); + if (operation.Succeeded) + { + operation.Value.RaiseEvents(___gclass2764_0, CommandStatus.Begin); + operation.Value.RaiseEvents(___gclass2764_0, CommandStatus.Succeed); + } + } + } + } + } + } + } + } +} diff --git a/Plugin.cs b/Plugin.cs index 8d362be..e57e4c3 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -55,6 +55,7 @@ namespace UIFixes UnloadAmmoPatches.Enable(); new FixTraderControllerSimulateFalsePatch().Enable(); LoadMultipleMagazinesPatches.Enable(); + new PutToolsBackPatch().Enable(); } public static bool InRaid() diff --git a/server/config/config.json b/server/config/config.json new file mode 100644 index 0000000..b5f1467 --- /dev/null +++ b/server/config/config.json @@ -0,0 +1,3 @@ +{ + "putToolsBack": true +} diff --git a/server/src/mod.ts b/server/src/mod.ts index c361eb7..3ca8be1 100644 --- a/server/src/mod.ts +++ b/server/src/mod.ts @@ -1,9 +1,13 @@ import type { DependencyContainer } from "tsyringe"; import type { InventoryController } from "@spt-aki/controllers/InventoryController"; +import type { HideoutHelper } from "@spt-aki/helpers/HideoutHelper"; import type { InRaidHelper } from "@spt-aki/helpers/InRaidHelper"; +import type { InventoryHelper } from "@spt-aki/helpers/InventoryHelper"; +import type { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import type { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import type { RagfairSortHelper } from "@spt-aki/helpers/RagfairSortHelper"; +import type { IHideoutSingleProductionStartRequestData } from "@spt-aki/models/eft/hideout/IHideoutSingleProductionStartRequestData"; import type { IRagfairOffer } from "@spt-aki/models/eft/ragfair/IRagfairOffer"; import { Money } from "@spt-aki/models/enums/Money"; import type { IPreAkiLoadMod } from "@spt-aki/models/external/IPreAkiLoadMod"; @@ -12,6 +16,8 @@ import type { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import type { StaticRouterModService } from "@spt-aki/services/mod/staticRouter/StaticRouterModService"; import type { JsonUtil } from "@spt-aki/utils/JsonUtil"; +import config from "../config/config.json"; + class UIFixes implements IPreAkiLoadMod { private databaseServer: DatabaseServer; private logger: ILogger; @@ -21,6 +27,7 @@ class UIFixes implements IPreAkiLoadMod { this.logger = container.resolve("WinstonLogger"); const profileHelper = container.resolve("ProfileHelper"); + const itemHelper = container.resolve("ItemHelper"); const staticRouterModService = container.resolve("StaticRouterModService"); const jsonUtil = container.resolve("JsonUtil"); @@ -83,6 +90,100 @@ class UIFixes implements IPreAkiLoadMod { { frequency: "Always" } ); + // Better tool return - starting production + if (config.putToolsBack) { + container.afterResolution( + "HideoutHelper", + (_, hideoutHelper: HideoutHelper) => { + const original = hideoutHelper.registerProduction; + + hideoutHelper.registerProduction = (pmcData, body, sessionID) => { + const result = original.call(hideoutHelper, pmcData, body, sessionID); + + // The items haven't been deleted yet, augment the list with their parentId + const bodyAsSingle = body as IHideoutSingleProductionStartRequestData; + if (bodyAsSingle && bodyAsSingle.tools?.length > 0) { + const requestTools = bodyAsSingle.tools; + const tools = pmcData.Hideout.Production[body.recipeId].sptRequiredTools; + for (let i = 0; i < tools.length; i++) { + const originalTool = pmcData.Inventory.items.find(x => x._id === requestTools[i].id); + tools[i]["uifixes.returnTo"] = [originalTool.parentId, originalTool.slotId]; + } + } + + return result; + }; + }, + { frequency: "Always" } + ); + + // Better tool return - returning the tool + container.afterResolution( + "InventoryHelper", + (_, inventoryHelper: InventoryHelper) => { + const original = inventoryHelper.addItemToStash; + + inventoryHelper.addItemToStash = (sessionId, request, pmcData, output) => { + const itemWithModsToAddClone = jsonUtil.clone(request.itemWithModsToAdd); + + // If a tool marked with uifixes is there, try to return it to its original container + const tool = itemWithModsToAddClone[0]; + if (tool["uifixes.returnTo"]) { + const [containerId, slotId] = tool["uifixes.returnTo"]; + + const container = pmcData.Inventory.items.find(x => x._id === containerId); + if (container) { + const containerTemplate = itemHelper.getItem(container._tpl)[1]; + const containerFS2D = inventoryHelper.getContainerMap( + containerTemplate._props.Grids[0]._props.cellsH, + containerTemplate._props.Grids[0]._props.cellsV, + pmcData.Inventory.items, + containerId + ); + + const canPlaceResult = inventoryHelper.canPlaceItemInContainer( + jsonUtil.clone(containerFS2D), // will change the array so clone it + itemWithModsToAddClone + ); + + // In 3.8.3 canPlaceItemInContainer is wonky and returns undefined when the answer is yes + if (canPlaceResult === undefined) { + // At this point everything should succeed + inventoryHelper.placeItemInContainer( + containerFS2D, + itemWithModsToAddClone, + containerId, + slotId + ); + + // protected function, bypass typescript + inventoryHelper["setFindInRaidStatusForItem"]( + itemWithModsToAddClone, + request.foundInRaid + ); + + // Add item + mods to output and profile inventory + output.profileChanges[sessionId].items.new.push(...itemWithModsToAddClone); + pmcData.Inventory.items.push(...itemWithModsToAddClone); + + this.logger.debug( + `Added ${itemWithModsToAddClone[0].upd?.StackObjectsCount ?? 1} item: ${ + itemWithModsToAddClone[0]._tpl + } with: ${itemWithModsToAddClone.length - 1} mods to ${containerId}` + ); + + return; + } + } + } + + return original.call(inventoryHelper, sessionId, request, pmcData, output); + }; + }, + { frequency: "Always" } + ); + } + staticRouterModService.registerStaticRouter( "UIFixesRoutes", [