Modding Help How to stack items in an "itemSlot"

Discussion in 'Starbound Modding' started by AlbertoRota, Jul 8, 2019.

  1. AlbertoRota

    AlbertoRota Scruffy Nerf-Herder

    So, if you use a "Container" object, you can call "world.containerItemApply(containerId, itemDescriptor, offset)" and it will take care of all the stacking logic (Max stack, filled capture pods, food, blueprints, etc.)

    But for "itemSlot" only thing we have is "widget.setItemSlotItem(widgetName, itemDescriptor)", that overwrites everything that previously was in the slot without stacking anything.

    The question is simple, how can I reproduce the behavior of "world.containerItemApply" for an "itemSlot"?

    Thank you!
     
  2. Wh1t3_rabbit

    Wh1t3_rabbit Seal Broken

    To reproduce the same behaviour you to have do it yourself, handle picking up and dropping the items, combining stacks, etc

    Listen for when an item is dropped in the slot, and you have to handle is it the same type of item, if so it can stack, if it is a different item then swap the items. If it is the same item, will stacking go over the max stack size? And handle picking up items the same, if you pick up too much will it go over the max stack size in your hand? Also, because the item slot doesn't keep its contents, you need to handle giving the items back when the panel is closed, otherwise they are lost forever.

    I had to do something similar so I mocked up this code. The GUI window has 2 item slots in it, and the Lua is fully commented to hopefully make it easier to understand.


    This the GUI, the main thing is just setting the callbacks on the itemslots, and the custom property refundOnClose which is a list of item slots to be emptied back to the player when the panel closes. Change scripts to point to the Lua file below

    Code:
    {
        "gui" : {
            "background" : {
                "type" : "background",
                "fileHeader" : "/interface/popup/header.png",
                "fileBody" : "/interface/popup/body.png",
                "fileFooter" : "/interface/popup/footer.png"
            },
            "windowtitle" : {
                "type" : "title",
                "title" : "  Debug Testing Station",
                "subtitle" : "  Input objects to test",
                "icon" : {
                    "type" : "image",
                    "file" : "/interface/confirmation/confirmationicon.png",
                    "position" : [0, 0],
                    "zlevel" : -1
                }
            },
            "close" : {
                "type" : "button",
                "base" : "/interface/x.png",
                "hover" : "/interface/xhover.png",
                "pressed" : "/interface/xpress.png",
                "pressedOffset" : [0, 1],
                "position" : [250, 103],
                "zlevel" : 100
            },
            "itemslot" : {
                "type" : "itemslot",
                "position" : [15, 52],
                "backingImage": "/interface/inventory/empty.png",
                "callback" : "slotleftclick",
                "rightClickCallback" : "slotrightclick"
            },
            "itemslot2" : {
                "type" : "itemslot",
                "position" : [50, 52],
                "backingImage": "/interface/inventory/empty.png",
                "callback" : "slotleftclick",
                "rightClickCallback" : "slotrightclick"
            }
          
        },
    
        "scriptWidgetCallbacks" : [
            "slotleftclick",
            "slotrightclick"
        ],
    
        "scripts" : ["/interface/wtdebugstation/debugui.lua"],
        "scriptDelta" : 0,
      
        "refundOnClose" : ["itemslot","itemslot2"]
    }
    

    And this is The Lua file to handle the UI interactions
    Code:
    local_storage = {} --this could just be called 'storage' since ScriptPanes don't have access to the normal storage variable
    
    function init()
        --When the panel inits we read in the list of slots that need to be emptied on close
        local_storage.slotsToRefund = config.getParameter("refundOnClose",{})
    end
    
    function uninit()
        --When the panel closes we want to give the player back any items they left in the slot(s)
        --For each slot we have configured
        for _,widget_name in pairs(local_storage.slotsToRefund or {}) do
            --Get the slotted item
            local slottedItem = widget.itemSlotItem(widget_name)
            --If it wasn't empty
            if slottedItem then
                --Give the player the item
                player.giveItem(slottedItem)
            end
        end
    end
    
    --Callback for left click on a slot
    function slotleftclick(slot_widget)
    
        --Get (a copy of) the item descriptor the player was holding
        local heldItem = player.swapSlotItem()
        --Get (a copy of) the current slotted item
        local slottedItem = widget.itemSlotItem(slot_widget)
      
        --If the current slotted item is blank we just drop in the held item
        if not slottedItem then
      
            --If we aren't dropping anything in then we just end now
            if not heldItem then
                return
            end
      
            --Update the slotted item
            widget.setItemSlotItem(slot_widget,heldItem)
            --Empty what the player was holding
            player.setSwapSlotItem(nil)
            --we are done and can stop here
            return
        end
    
        --If we reach here then we have an item already in the slot
      
        --If the heldItem is empty then we want to pick up what was in the slot
        if not heldItem then
            --Update the held item
            player.setSwapSlotItem(slottedItem)
            --Empty the slot
            widget.setItemSlotItem(slot_widget,nil)
            --we are done and can stop here
            return
        end
      
        --If we reach here then we are dropping an item onto the slotted item
      
        --Does the item being dropped match the slotted item?
        local itemsMatch = root.itemDescriptorsMatch(heldItem,slottedItem,true) --third arg is true to prevent stacking two weapons with the same name but different secondary attacks
      
        --If they don't match then we just swap them around
        if not itemsMatch then
            --Give the player the slotted item
            player.setSwapSlotItem(slottedItem)
            --Give the slot the players item
            widget.setItemSlotItem(slot_widget,heldItem)
            --Done
            return
        end
      
        --If we reach here the items do match, we need to check the max stack size and handle what happens if we try to go over the max stack
      
        --Get max stack size either from the item or the default max stack
        local itemConfig = root.itemConfig(slottedItem).config
        local rootConfig = root.assetJson("/items/defaultParameters.config")
        local maxStack = itemConfig.maxStack and itemConfig.maxStack or rootConfig.defaultMaxStack
      
        --Combine the quantities
        local slotQty = slottedItem.count
        local heldQty = heldItem.count
        local combinedQty = slotQty+heldQty
      
        --If we are below max stack size we are good to go
        if combinedQty <= maxStack then
            --Adjust quantity of slotted item
            slottedItem.count = combinedQty
            widget.setItemSlotItem(slot_widget,slottedItem)
            --Clear held item
            player.setSwapSlotItem(nil)
            --Done
            return
        end
      
        --If we reach here we would go over the max stack size
      
        --Adjust quantity of slotted item to max size
        slottedItem.count = maxStack
        widget.setItemSlotItem(slot_widget,slottedItem)
        --Find how many didn't fit in the stack, these will remain in hand
        local remaining = combinedQty-maxStack
        --Adjust quantity of held item
        heldItem.count = remaining
        player.setSwapSlotItem(heldItem)
      
    end
    
    --Callback for right click on a slot
    function slotrightclick(slot_widget)
      
        --Get (a copy of) the item descriptor the player was holding
        local heldItem = player.swapSlotItem()
        --Get (a copy of) the current slotted item
        local slottedItem = widget.itemSlotItem(slot_widget)
      
        --If the current slotted item is blank we drop in one of the held item
        if not slottedItem then
      
            --If we aren't dropping anything in then we just end now
            if not heldItem then
                return
            end
          
            local heldQty = heldItem.count
          
            --If we are only holding one item then drop it in and empty our hand
            if heldQty==1 then
                --Put the item in the slot
                widget.setItemSlotItem(slot_widget,heldItem)
                --Empty the players hand
                player.setSwapSlotItem(nil)
                --Done
                return
            end
      
            --If we reach here we are holding more than 1, we need to drop 1 and decrease held by 1
          
            --Adjust quantity of item and put in slot
            heldItem.count = 1
            widget.setItemSlotItem(slot_widget,heldItem)
            --Adjust quantity of item and update held
            heldItem.count = heldQty-1
            player.setSwapSlotItem(heldItem)
            --we are done and can stop here
            return
          
        end
      
        --If we reach here there is currently a slotted item
      
        local slotQty = slottedItem.count
      
        --If we are aren't holding anything we want to pick up 1 quantity
        if not heldItem then
          
            --If there is only 1 in the slot we just pick it up
            if slotQty==1 then
                --Give player the item
                player.setSwapSlotItem(slottedItem)
                --Empty the slot
                widget.setItemSlotItem(slot_widget,nil)
                --Done
                return
            end
          
            --If we reach here there is more than 1 slotted, we want to pick up only 1
          
            --Adjust quantity of item and put in hand
            slottedItem.count =1
            player.setSwapSlotItem(slottedItem)
            --Adjust quantity of item and update slot
            slottedItem.count = slotQty-1
            widget.setItemSlotItem(slot_widget,slottedItem)
            --we are done and can stop here
            return
          
        end
      
        --If we reach here we right clicked a slotted item while carrying an item
        --Are we trying to pick 1 up, or put 1 down?
        --I am going to assume if the items are different, we want to swap them
        --If the items are the same, we are trying to pick 1 up
      
        local heldQty = heldItem.count
      
        --Do the items match
        local itemsMatch = root.itemDescriptorsMatch(heldItem,slottedItem,true) --third arg is true to prevent stacking two weapons with the same name but different secondary attacks
      
        --If they don't match then we just swap them around
        if not itemsMatch then
            --Give the player the slotted item
            player.setSwapSlotItem(slottedItem)
            --Give the slot the players item
            widget.setItemSlotItem(slot_widget,heldItem)
            --Done
            return
        end
      
        --If we reach here the items do match, we need to check the max stack size and handle what happens if we try to go over the max stack
      
        --Calculate new quantities
        local newHeldQty = heldQty+1
        local newSlotQty = slotQty-1
      
        --Get max stack size either from the item or the default max stack
        local itemConfig = root.itemConfig(slottedItem).config
        local rootConfig = root.assetJson("/items/defaultParameters.config")
        local maxStack = itemConfig.maxStack and itemConfig.maxStack or rootConfig.defaultMaxStack
      
        --If we are below max stack size we are good to go
        if newHeldQty <= maxStack then
            --Adjust quantity of held item
            heldItem.count = newHeldQty
            player.setSwapSlotItem(heldItem)
            --If slot qty dropped to zero then empty slot
            if newSlotQty==0 then
                widget.setItemSlotItem(slot_widget,nil)
            else
                --Still have some quantity, update item in slot
                slottedItem.count = newSlotQty
                widget.setItemSlotItem(slot_widget,slottedItem)
            end
            --Done
            return
        end
      
        --If we reach here we would go over the max stack size
        --Since we are only trying to pick 1 up, there is nothing more we can do
        return
      
    end
     
    Lucretia and AlbertoRota like this.
  3. AlbertoRota

    AlbertoRota Scruffy Nerf-Herder

    Super clear and super useful! Thank you very much!

    Have you considered to create a "Tutorial" thread with just that?
    You have helped me a lot, and I'm quite sure that other people will need the same in the future.
     

Share This Page