485 lines
20 KiB
Lua
485 lines
20 KiB
Lua
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)
|