Jump to content

Recommended Posts

Hi everyone,

I’m currently working on a custom chest for DST and trying to achieve the following functionality:

I want the chest to display one of three different ape face sprites depending on how many guycoins (custom item) are stored inside:

image.png.546b1c6841f6fbec4d5709301a75310a.png

I tried doing this by using the "keyhole" symbol and replacing it with my custom PNGs, which I placed into the keyhole symbol folder like this:

image.png.d7b3280fcd35cbd5cfbeb59473f80637.png

i thought it might work like this (where guychest is my bank/build name). But this approach doesn't seem to work.

local function UpdateKeyholeSymbol(inst)
    if not inst or not inst.components or not inst.components.container then return end
    local num_gold = 0
    local items = inst.components.container:FindItems(function(item)
        return item.prefab == "goldnugget"
    end)
    num_gold = #items
    local symbol = "keyhole-1"
    if num_gold >= 5 then
        symbol = "keyhole-3"
    elseif num_gold >= 2 then
        symbol = "keyhole-2"
    end
    inst.AnimState:OverrideSymbol("keyhole", "guychest", symbol)
end

 

my build and anim tables are a mess... i fixed all the animations frame by frame and the chest is working now, but i don't know how to set up the keyhole symbol and layers correctly. 🤕

image.png.858dee6f503c6f32c962c54f17e09cd6.png

image.png.acd68b6d0a03548375e1c37bfbbfa204.png

Any help is appreciated!

d_keyhole-1.png

d_keyhole-0.png

d_keyhole-2.png

image.png

Edited by Snoopy2016
doubled pngs

I'm sure there is a way to get the override symbol stuff to work but it's easier for me to test things having the files. But another suggestion could be having different idle states for each of the faces with the chest and playing those when the condition is met?

I tried working in that animtool shown in the screenshots but sometimes there are less issues using the previous method of just spriter

If memory serves me right, you have to put each keyhole into a separate folder.

Take the mighty_gym for instance that is a structure exclusive to Wolfgang, each meter on the gym has a single image inside each folder:

image.png.9acfbd40f19965b99f55d85f7027d460.png

So basically put keyhole-1, keyhole-2 and keyhole-3 to folders keyhole, keyhole2 and keyhole3 respectively then compile the .scml file.

Afterwards change your code to the following:

local function UpdateKeyholeSymbol(inst)
    if not inst or not inst.components or not inst.components.container then return end
    local num_gold = 0
    local items = inst.components.container:FindItems(function(item)
        return item.prefab == "goldnugget"
    end)
    num_gold = #items
    local symbol = "keyhole"
    if num_gold >= 5 then
        symbol = "keyhole3"
    elseif num_gold >= 2 then
        symbol = "keyhole2"
    end
    inst.AnimState:OverrideSymbol("keyhole", "guychest", symbol)
end

Do note, that each image in their respective folder should be named BASED on the folder's name with digits at the end (for example: folder keyhole1 should have its image named keyhole1-0)

If that doesn't work, does UpdateKeyholeSymbol get called each time the container (in this case the chest) gains an item or not?

Edited by mathem99
  • Like 1

 @maliblues you're right. i forgot to attach the files. there are two versions. the spriter one and the anim tools one. 

@
mathem99 thanks, im going to test this 😊

guychest_.scml guychest.zip

guychest_.zip

Edited by Snoopy2016
On 6/4/2025 at 7:49 PM, mathem99 said:

If memory serves me right, you have to put each keyhole into a separate folder.

Take the mighty_gym for instance that is a structure exclusive to Wolfgang, each meter on the gym has a single image inside each folder:

image.png.9acfbd40f19965b99f55d85f7027d460.png

So basically put keyhole-1, keyhole-2 and keyhole-3 to folders keyhole, keyhole2 and keyhole3 respectively then compile the .scml file.

Afterwards change your code to the following:

Do note, that each image in their respective folder should be named BASED on the folder's name with digits at the end (for example: folder keyhole1 should have its image named keyhole1-0)

If that doesn't work, does UpdateKeyholeSymbol get called each time the container (in this case the chest) gains an item or not?

@mathem99 I tested this by creating four folders (keyhole, keyhole1, keyhole2, and keyhole3), each containing the corresponding -0.png image. The keyhole folder holds the default "invisible" layer, which should be replaced by one of the other three keyhole images depending on the number of gold nuggets in the chest. In the .scml file, I added the keyhole symbol to the animations using keyhole-0.png.

However, keyhole-1 is the only ape face showing up...

Here’s what my chest code looks like:

require "prefabutil"

local assets =
{
    Asset("ANIM", "anim/guychest.zip"),
}

local prefabs_regular =
{
	"collapse_small",
	"collapsed_treasurechest",
}

local SUNKEN_PHYSICS_RADIUS = .45

local SOUNDS = {
    open  = "dontstarve/wilson/chest_open",
    close = "dontstarve/wilson/chest_close",
    built = "dontstarve/common/chest_craft",
}

local function UpdateKeyholeSymbol(inst)
    if not inst or not inst.components or not inst.components.container then return end
    local num_gold = 0
    local items = inst.components.container:FindItems(function(item)
        return item.prefab == "goldnugget"
    end)
    num_gold = #items
    local symbol = "keyhole"
    if num_gold >= 10 then
        symbol = "keyhole3"
    elseif num_gold >= 5 then
        symbol = "keyhole2"
    elseif num_gold < 5 then
        symbol = "keyhole1"
    end
    inst.AnimState:OverrideSymbol("keyhole", "guychest", symbol)
end

local function onopen(inst)
    if not inst:HasTag("burnt") then
        inst.AnimState:PlayAnimation("open")
        UpdateKeyholeSymbol(inst)
        inst.SoundEmitter:PlaySound(inst.sounds.open)
    end
end 

local function onclose(inst)
    if not inst:HasTag("burnt") then
        inst.AnimState:PlayAnimation("close")
        inst.AnimState:PushAnimation("closed", false)
        UpdateKeyholeSymbol(inst)
        inst.SoundEmitter:PlaySound(inst.sounds.close)
    end
end 

local function onhammered(inst, worker)
    if inst.components.burnable ~= nil and inst.components.burnable:IsBurning() then
        inst.components.burnable:Extinguish()
    end
    inst.components.lootdropper:DropLoot()
    if inst.components.container ~= nil then
        inst.components.container:DropEverything()
    end
    local fx = SpawnPrefab("collapse_small")
    fx.Transform:SetPosition(inst.Transform:GetWorldPosition())
    fx:SetMaterial("wood")
    inst:Remove()
end

local function onhit(inst, worker)
    if not inst:HasTag("burnt") then
        if inst.components.container ~= nil then
            inst.components.container:DropEverything()
            inst.components.container:Close()
        end
        inst.AnimState:PlayAnimation("hit")
        inst.AnimState:PushAnimation("closed", false)
    end
end

local function onbuilt(inst)
    inst.AnimState:PlayAnimation("rebuild")
    inst.AnimState:PushAnimation("closed", false)
    inst.SoundEmitter:PlaySound(inst.sounds.built)
end

local function onsave(inst, data)
    if inst:HasTag("burnt") or (inst.components.burnable ~= nil and inst.components.burnable:IsBurning()) then
        data.burnt = true
    end
end

local function onload(inst, data)
    if data ~= nil and data.burnt and inst.components.burnable ~= nil then
        inst.components.burnable.onburnt(inst)
    end
end

local function fn()
    local inst = CreateEntity()

    inst.entity:AddTransform()
    inst.entity:AddAnimState()
    inst.entity:AddSoundEmitter()
    inst.entity:AddMiniMapEntity()
    inst.entity:AddNetwork()

    MakeObstaclePhysics(inst, 0.7)

    inst.MiniMapEntity:SetIcon("chest.png")

    inst:AddTag("structure")
    inst:AddTag("chest")
    inst:AddTag("heavy")
    inst.AnimState:SetBank("guychest")
    inst.AnimState:SetBuild("guychest")
    inst.AnimState:PlayAnimation("closed")

    MakeSnowCoveredPristine(inst)

    inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        return inst
    end

    inst.sounds = SOUNDS

    inst:AddComponent("inspectable")
    inst:AddComponent("container")
    
    inst.components.container:WidgetSetup("guychest")
    inst.components.container.onopenfn = onopen
    inst.components.container.onclosefn = onclose
    inst.components.container.skipclosesnd = true
    inst.components.container.skipopensnd = true

    -- Keyhole-Update bei Itemänderungen (onput/ontake)
    inst:DoTaskInTime(0, function(task_inst)
        if task_inst and task_inst.components and task_inst.components.container then
            if task_inst.components.container.SetOnPutItemFn then
                task_inst.components.container:SetOnPutItemFn(function(container_owner_inst, item_put, slot)
                    UpdateKeyholeSymbol(container_owner_inst)
                end)
            else
                print("GUYCHEST_ERROR: SetOnPutItemFn is nil on container component for "..(task_inst.GUID or "UNKNOWN_GUID"))
            end

            if task_inst.components.container.SetOnItemTakenFn then
                task_inst.components.container:SetOnItemTakenFn(function(container_owner_inst, item_taken, slot)
                    UpdateKeyholeSymbol(container_owner_inst)
                end)
            else
                print("GUYCHEST_ERROR: SetOnItemTakenFn is nil on container component for "..(task_inst.GUID or "UNKNOWN_GUID"))
            end
        else
            local guid = (task_inst and task_inst.GUID) or "UNKNOWN_GUID_OR_NIL_TASK_INST"
            print("GUYCHEST_ERROR: Container component is nil for "..guid.." when trying to set item change listeners via DoTaskInTime.")
        end
    end)
    -- Initiales Setzen beim Spawn
    inst:DoTaskInTime(0, function() UpdateKeyholeSymbol(inst) end)

    inst:AddComponent("lootdropper")
    inst:AddComponent("workable")
    inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
    inst.components.workable:SetWorkLeft(10)
    inst.components.workable:SetOnFinishCallback(onhammered)
    inst.components.workable:SetOnWorkCallback(onhit) 

    AddHauntableDropItemOrWork(inst)

    inst:ListenForEvent("onbuilt", onbuilt)
    MakeSnowCovered(inst)   

    MakeSmallBurnable(inst, nil, nil, true)
    MakeMediumPropagator(inst)

    inst.OnSave = onsave 
    inst.OnLoad = onload
    
    return inst
end

return Prefab("guychest", fn, assets),
        MakePlacer("guychest_placer", "guychest", "guychest", "closed")

 

Edited by Snoopy2016
    local items = inst.components.container:FindItems(function(item)
        return item.prefab == "goldnugget"
    end)
    num_gold = #items
    

Are you sure you are counting the counts of goldnugget instead the amount?

Did you forget `item.components.stackable.stacksize`?

  • Thanks 1
9 hours ago, Snoopy2016 said:
    -- Keyhole-Update bei Itemänderungen (onput/ontake)
    inst:DoTaskInTime(0, function(task_inst)
        if task_inst and task_inst.components and task_inst.components.container then
            if task_inst.components.container.SetOnPutItemFn then
                task_inst.components.container:SetOnPutItemFn(function(container_owner_inst, item_put, slot)
                    UpdateKeyholeSymbol(container_owner_inst)
                end)
            else
                print("GUYCHEST_ERROR: SetOnPutItemFn is nil on container component for "..(task_inst.GUID or "UNKNOWN_GUID"))
            end

            if task_inst.components.container.SetOnItemTakenFn then
                task_inst.components.container:SetOnItemTakenFn(function(container_owner_inst, item_taken, slot)
                    UpdateKeyholeSymbol(container_owner_inst)
                end)
            else
                print("GUYCHEST_ERROR: SetOnItemTakenFn is nil on container component for "..(task_inst.GUID or "UNKNOWN_GUID"))
            end
        else
            local guid = (task_inst and task_inst.GUID) or "UNKNOWN_GUID_OR_NIL_TASK_INST"
            print("GUYCHEST_ERROR: Container component is nil for "..guid.." when trying to set item change listeners via DoTaskInTime.")
        end
    end)

From what I remember container component does not have SetOnPutItemFn or SetOnItemTakenFn functions.

Instead replace all that with this:

inst:ListenForEvent("itemget", function() UpdateKeyholeSymbol(inst) end)
inst:ListenForEvent("itelose", function() UpdateKeyholeSymbol(inst) end)

 

  • Thanks 1

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...