if SERVER then return end -- Docs: https://evilfactory.github.io/LuaCsForBarotrauma/lua-docs/manual/common-questions/ ---@class MyModGlobal ---@field CONFIG {QUICKSTACK_KEYS: Keys, FABRICATOR_KEY: Keys, MAX_BUY: Keys, NESTED_CONTAINERS: boolean, DEBUG_MODE: boolean} ---@field MOD_NAME string ---@field MOD_VERSION string ---@field DumpTable fun(table: table, depth?: number) ---@field debugPrint fun(message: string) MyModGlobal = { CONFIG = { QUICKSTACK_KEYS = Keys.F, FABRICATOR_KEY = Keys.V, MAX_BUY = Keys.B, NESTED_CONTAINERS = true, DEBUG_MODE = true, }, MOD_NAME = "Quick Stack To Containers", MOD_VERSION = "1.1.0", BAG_SLOT = 8, } ---@param table table ---@param depth number? MyModGlobal.DumpTable = function(table, depth) if depth == nil then depth = 0 end if (depth > 200) then print("Error: Depth > 200 in dumpTable()") return end for k, v in pairs(table) do if (type(v) == "table") then print(string.rep(" ", depth) .. k .. ":") MyModGlobal.DumpTable(v, depth + 1) else print(string.rep(" ", depth) .. k .. ": ", v) end end end -- Debugging helper function MyModGlobal.debugPrint = function(message) if MyModGlobal.CONFIG.DEBUG_MODE then print("[" .. MyModGlobal.MOD_NAME .. "] " .. message) end end require("Cyka.quickstack") print(MyModGlobal.MOD_NAME .. " v" .. MyModGlobal.MOD_VERSION .. " loaded!") -- Register necessary types and make fields accessible LuaUserData.RegisterType("Barotrauma.Items.Components.ItemContainer+SlotRestrictions") LuaUserData.RegisterType( 'System.Collections.Immutable.ImmutableArray`1[[Barotrauma.Items.Components.ItemContainer+SlotRestrictions, Barotrauma]]') LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.Items.Components.ItemContainer'], 'slotRestrictions') LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.ItemInventory'], 'slots') LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterInventory"], "slots") LuaUserData.RegisterType("Barotrauma.Store") LuaUserData.RegisterType("Barotrauma.GUIComponent") LuaUserData.RegisterType("Barotrauma.PurchasedItem") LuaUserData.RegisterType("Barotrauma.ItemPrefab") LuaUserData.RegisterType("Barotrauma.Location+StoreInfo") LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.CargoManager"], "GetConfirmedSoldEntities") -- -- Register necessary type to access VisualSlot -- LuaUserData.RegisterType("Barotrauma.VisualSlot") -- -- ---@return Barotrauma.VisualSlot|nil, Barotrauma.ItemInventory|nil, Barotrauma.Item|nil -- local function getInventorySlotUnderCursor() -- -- Make sure we have a controlled character -- local controlledCharacter = Character.Controlled -- if not controlledCharacter then return nil, nil, nil end -- -- -- Check player inventory first -- local charInventory = controlledCharacter.Inventory -- if charInventory and charInventory.visualSlots then -- for i, visualSlot in ipairs(charInventory.visualSlots) do -- -- Check if mouse is over this slot -- if visualSlot:MouseOn() then -- local slot = charInventory.slots[i] -- local item = nil -- if #slot.items > 0 then -- item = slot.items[1] -- end -- return visualSlot, charInventory, item -- end -- end -- end -- -- -- Check if selected item has inventory (containers, etc.) -- local selectedItem = controlledCharacter.SelectedItem -- if selectedItem and selectedItem.OwnInventory and selectedItem.OwnInventory.visualSlots then -- local itemInv = selectedItem.OwnInventory -- for i, visualSlot in ipairs(itemInv.visualSlots) do -- if visualSlot:MouseOn() then -- local slot = itemInv.slots[i] -- local item = nil -- if #slot.items > 0 then -- item = slot.items[1] -- end -- return visualSlot, itemInv, item -- end -- end -- end -- -- -- Check open containers or other items with visible inventories -- for item in Item.ItemList do -- if item and item.OwnInventory and item.OwnInventory.visualSlots then -- local itemInv = item.OwnInventory -- for i, visualSlot in ipairs(itemInv.visualSlots) do -- if visualSlot:MouseOn() then -- local slot = itemInv.slots[i] -- local slotItem = nil -- if #slot.items > 0 then -- slotItem = slot.items[1] -- end -- return visualSlot, itemInv, slotItem -- end -- end -- end -- end -- -- return nil, nil, nil -- end -- -- Register necessary types for detecting repairable objects -- LuaUserData.RegisterType("Barotrauma.Items.Components.Repairable") -- LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.Items.Components.Repairable"], "RepairButton") -- -- ---@return Barotrauma.Item|nil, Barotrauma.Items.Components.Repairable|nil -- local function getRepairableObjectInFocus() -- -- Make sure we have a controlled character -- local controlledCharacter = Character.Controlled -- if not controlledCharacter then return nil, nil end -- -- -- Check if we have a selected item - this is necessary for repair interfaces to show -- local selectedItem = controlledCharacter.SelectedItem -- if not selectedItem then return nil, nil end -- -- -- Check if the selected item is in fact the repairable object itself -- for _, component in pairs(selectedItem.Components) do -- if component.name == "Repairable" then -- -- Check if repair interface should be shown -- if component:ShouldDrawHUD(controlledCharacter) then -- return selectedItem, component -- end -- end -- end -- -- -- Nothing found -- return nil, nil -- end -- -- ---@return boolean -- local function isRepairButtonVisible() -- local _, repairableComponent = getRepairableObjectInFocus() -- if not repairableComponent then return false end -- -- -- Check if the repair button exists and is visible -- local repairButton = repairableComponent.RepairButton -- return repairButton ~= nil and repairButton.Visible -- end ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---@return {item: Barotrauma.Item, fabricator: Barotrauma.FabricatorComponent}, string? local function getOpenFabricator() -- Get the controlled character local controlledCharacter = Character.Controlled if not controlledCharacter then return nil, "No controlled character found" end -- Check if the character has a selected item local selectedItem = controlledCharacter.SelectedItem if not selectedItem then return nil, "No selected item found" end -- Check if the selected item has a Fabricator component local fabricator = Game.GetFabricatorComponent(selectedItem) if not fabricator then return nil, "No fabricator component found" end return { item = selectedItem, fabricator = fabricator } end --- Recipes can have multiple inputs, for example ammo --- Can be made either out of copper iron or steel, 1 of either ---@class RecipeInfo ---@field targetItem {identifier: string, name: string, amount: number} ---@field requiredItems {amount: number, minCondition: number, maxCondition: number, prefabs: string[]}[] ---@param fabricator Barotrauma.FabricatorComponent ---@return RecipeInfo, string? local function getSelectedRecipeRequirements(fabricator) -- local openFabricator, err = getOpenFabricator() -- if err then return nil, err end -- local fabricator = openFabricator.fabricator local selectedRecipe = fabricator.SelectedItem if not selectedRecipe then return nil, "No selected recipe found" end local requiredItems = {} for requiredItem in selectedRecipe.RequiredItems do local itemInfo = { amount = tonumber(requiredItem.Amount), minCondition = tonumber(requiredItem.MinCondition), maxCondition = tonumber(requiredItem.MaxCondition), prefabs = {} } for prefab in requiredItem.ItemPrefabs do itemInfo.prefabs[#itemInfo.prefabs + 1] = prefab.Identifier.Value end requiredItems[#requiredItems + 1] = itemInfo end return { targetItem = { identifier = selectedRecipe.TargetItem.Identifier, name = selectedRecipe.TargetItem.Name, amount = selectedRecipe.Amount }, requiredItems = requiredItems } end -- Hook into player control to listen for key press Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable) if not PlayerInput.KeyHit(CONFIG.FABRICATOR_KEY) then return end -- TODO: Maybe get items from entire sub...? -- There's no point getting recipes if we don't have all of this bullshit ---@type Barotrauma.Character local character = instance if not character then debugPrint("Character instance is nil.") return end ---@type Barotrauma.CharacterInventory local inventory = character.Inventory if not inventory then debugPrint("Character inventory is nil.") return end ---@type Barotrauma.ItemInventory.Slot local bagSlot = inventory.slots[BAG_SLOT] if not bagSlot then debugPrint("Bag slot not found.") return end if #bagSlot.items == 0 then debugPrint("Bag slot is empty.") return end ---@type Barotrauma.Item local bagItem = bagSlot.items[1] if not bagItem then debugPrint("Bag item not found.") return end local fabricator, err = getOpenFabricator() if err then print(string.format("Error getting open fabricator: %s", err)) return end local recipe recipe, err = getSelectedRecipeRequirements(fabricator.fabricator) if err then print(string.format("Error getting selected recipe requirements: %s", err)) return end -- DumpTable(recipe) -- TODO: Maybe make it so every press cycles the input -- For recipes that have multiple prefabs -- But then again what if it has 3 items with 4 prefabs each.. -- Is that 4 iterations or 3*4 iterations? -- We can not use #toFind because we can remove 0th item -- Which means it's no longer contiguous -- Which means #toFind returns 0 local toFind = recipe.requiredItems local remaining = #toFind ---@type Barotrauma.Item[] local toGet = {} ---@type fun(item: Barotrauma.Item): boolean, boolean local filter = function(item) local found = false -- DumpTable(toFind) -- toFind are all items we need to find for i, itemInfo in pairs(toFind) do -- prefabs are all items that satisfy the requirements for _, prefab in ipairs(itemInfo.prefabs) do -- debugPrint(string.format("Checking %s against %s", item.Prefab.Identifier.Value, prefab)) if item.Prefab.Identifier.Value == prefab then -- debugPrint(string.format("That'll do %s %s", item.Prefab.Identifier.Value, prefab)) toGet[#toGet + 1] = item itemInfo.amount = itemInfo.amount - 1 found = true break end end if itemInfo.amount <= 0 then -- debugPrint(string.format("Removing %s from toFind", itemInfo.prefabs[1])) toFind[i] = nil remaining = remaining - 1 end if found then break end end -- DumpTable(toFind) -- debugPrint(string.format("Found %s %s", item.Prefab.Identifier.Value, tostring(remaining))) return found, remaining == 0 end -- DumpTable(toGet) local items = enqueueInventory(bagItem.OwnInventory, {}, filter) -- DumpTable(items) -- TODO: This might explode... Oh well? local inputInventory = fabricator.item.OwnInventories[1] for iinventory in fabricator.item.OwnInventories do if #iinventory.slots > 1 then inputInventory = iinventory break end end local slot = -1 local previous = nil for _, item in ipairs(items) do if previous ~= item.Prefab.Identifier then slot = slot + 1 end inputInventory.TryPutItem(item, slot, false, true, nil) previous = item.Prefab.Identifier end DumpTable(items) end, Hook.HookMethodType.After) ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---@return Barotrauma.Location.StoreInfo[], string? local function getCurrentStore() if not Game or not Game.GameSession or not Game.GameSession.Campaign then return nil, "No game session found" end local map = Game.GameSession.Campaign.Map if not map or not map.CurrentLocation or not map.CurrentLocation.Stores then return nil, "No map found" end local location = map.CurrentLocation -- Otherwise, determine which store is active by checking the cargo manager local cargoManager = Game.GameSession.Campaign.CargoManager if not cargoManager then return nil, "No cargo manager found" end -- Find which store has items in the cart local stores = {} for _, store in pairs(location.Stores) do if #cargoManager:GetBuyCrateItems(store) > 0 then stores[#stores + 1] = store end end return stores, nil end -- Example: Add a key binding to buy all items in the current store -- when the 'B' key is pressed Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable) if not PlayerInput.KeyHit(CONFIG.MAX_BUY) then return end local cargoManager = Game.GameSession.Campaign.CargoManager if not cargoManager then print("No cargo manager available") return end local stores, err = getCurrentStore() if err then print(string.format("Error getting current store: %s", err)) return end for _, store in ipairs(stores) do local toAdd = {} -- Get items available at the store local items = cargoManager:GetBuyCrateItems(store) for item in items do -- We have already added this many of item toAdd[item.ItemPrefab.Identifier.Value] = { quantity = -item.Quantity, prefab = item.ItemPrefab -- Store the ItemPrefab object } end for item in store.Stock do -- So if we add the total amount available -- We get the amount we have to add to buy entire stock local idValue = item.ItemPrefab.Identifier.Value if toAdd[idValue] then toAdd[idValue].quantity = toAdd[idValue].quantity + item.Quantity end end for idValue, info in pairs(toAdd) do if info.quantity > 0 then print(string.format("Adding %d of %s to the buy crate", info.quantity, idValue)) -- Use the stored ItemPrefab object, not the string identifier cargoManager:ModifyItemQuantityInBuyCrate(store.Identifier, info.prefab, info.quantity) end end end end, Hook.HookMethodType.After) ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- local amountExperience = 600 local passiveExperienceDelay = 2 local passiveExperienceTimer = 0 Hook.Add("think", "examples.passiveExperience", function() if Timer.GetTime() < passiveExperienceTimer then return end for k, v in pairs(Character.CharacterList) do if not v.IsDead and v.Info ~= nil then v.Info.GiveExperience(amountExperience) end end passiveExperienceTimer = Timer.GetTime() + passiveExperienceDelay end)