Jump to content

Custom fueled machine not accepting existing prefab as fuel


Recommended Posts

I'm throwing in the towel on this one, but I'm sure it's something minor that I'm managing to overlook somehow... I've created a fueled machine which is supposed to accept a custom fuel type. Currently, this can be either a new custom item which provides a large amount of fuel, or plain Honey, which adds a small amount but is more readily accessible. The custom fuel is working perfectly as intended. However, the machine simply will not accept Honey as fuel, despite me adding the fueled component to Honey via AddPrefabPostInit and setting the fuel type accordingly.

I've pulled out some of the relevant code snippets below:

Fueled machine main prefab function

local function fn(Sim)
	local inst = CreateEntity()
	
	local modnameFancy = "Pandora"
	local modnameActual = KnownModIndex:GetModActualName(modnameFancy)
	local craftrate
	
	if modnameActual == nil then
		print(string.format("Mod '%s' not found", modnameFancy))
		craftrate = GUMMIMAKER_MAKE_RATE
	else
		craftrate = GetModConfigData("gummirate", modnameActual)
	end
	
	inst.entity:AddNetwork()
	inst.entity:AddTransform()
	inst.entity:AddAnimState()
	inst.entity:AddSoundEmitter()

	inst.AnimState:SetBank("gummimaker")
	inst.AnimState:SetBuild("gummimaker")

	MakeObstaclePhysics(inst, .4)

    inst:AddTag("structure")
	inst:AddTag("fridge")
	
	inst.entity:SetPristine()

    if not TheWorld.ismastersim then
		inst:DoTaskInTime(0.1, function(inst)
			inst.replica.container:WidgetSetup("treasurechest")
		end)
        return inst
    end

	inst:AddComponent("lootdropper")
	
	inst:AddComponent("machine")
	inst.components.machine.turnonfn = TurnOn
	inst.components.machine.turnofffn = TurnOff
	inst.components.machine.caninteractfn = CanInteract
	inst.components.machine.cooldowntime = 0.5
	
	inst:AddComponent("container")
	inst.components.container:WidgetSetup("treasurechest")
	inst.components.container.onopenfn = onopen
	inst.components.container.onclosefn = onclose

	inst:AddComponent("fueled")
	inst.components.fueled.fueltype = FUELTYPE.SWEETDREAM
	inst.components.fueled.maxfuel = GUMMIMAKER_FUEL_MAX
	inst.components.fueled.accepting = true
	inst.components.fueled:SetSections(4)
	
	inst.components.fueled:SetDepletedFn(OnFuelEmpty)
	inst.components.fueled:SetTakeFuelFn(ontakefuelfn)
	inst.components.fueled:InitializeFuelLevel(GUMMIMAKER_FUEL_MAX/2)
	inst.components.fueled:StartConsuming()

	inst:AddComponent("inspectable")
	inst.components.inspectable.getstatus = getstatus

	inst:AddComponent("workable")
	inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
	inst.components.workable:SetWorkLeft(4)
	inst.components.workable:SetOnFinishCallback(onhammered)
	inst.components.workable:SetOnWorkCallback(onhit)
	
	-- Set the amount of time it takes to create a gummi
	inst.craftrate = TUNING.TOTAL_DAY_TIME / craftrate

	inst:ListenForEvent("onbuilt", onbuilt)

	return inst
end

Working Custom Fuel

local assets = 
{
	Asset("ANIM", "anim/dreamfuel.zip"),
	
	Asset("ATLAS", "images/inventoryimages/dreamfuel.xml"),
    Asset("IMAGE", "images/inventoryimages/dreamfuel.tex"),
}

local prefabs = {}

local function takenap(inst, time)
	if inst:HasTag("player") then
		inst:PushEvent("yawn", { grogginess = 4, knockoutduration = time + math.random() })
	elseif inst.components.sleeper ~= nil then
		inst.components.sleeper:AddSleepiness(7, time + math.random())
	elseif inst.components.grogginess ~= nil then
		inst.components.grogginess:AddGrogginess(4, time + math.random())
	else
		ins:PushEvent("knockedout")
	end
end

local function oneat(inst, eater)
	eater:DoTaskInTime(0.5, function()
		takenap(eater, TUNING.MANDRAKE_SLEEP_TIME*2)
	end)
end

local function fn()
    local inst = CreateEntity()

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

    MakeInventoryPhysics(inst)

    inst.AnimState:SetBank("dreamfuel")
    inst.AnimState:SetBuild("dreamfuel")
    inst.AnimState:PlayAnimation("idle", true)

    inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        return inst
    end
	
	inst:AddComponent("edible")
	inst.components.edible.healthvalue = 0
	inst.components.edible.hungervalue = 0
	inst.components.edible.sanityvalue = TUNING.SANITY_SMALL
	inst.components.edible.foodtype = FOODTYPE.GENERIC
	inst.components.edible:SetOnEatenFn(oneat)

    inst:AddComponent("stackable")
    inst.components.stackable.maxsize = TUNING.STACK_SIZE_SMALLITEM
	
    inst:AddComponent("inspectable")
	
	inst:AddComponent("fuel")
    inst.components.fuel.fueltype = FUELTYPE.SWEETDREAM
    inst.components.fuel.fuelvalue = TUNING.LARGE_FUEL * 2
	print("Whipped Dream fuel type: "..FUELTYPE.SWEETDREAM)

    MakeHauntableLaunch(inst)

    inst:AddComponent("inventoryitem")
	inst.components.inventoryitem.atlasname = "images/inventoryimages/dreamfuel.xml"

    return inst
end

return Prefab("dreamfuel", fn, assets)

AddPrefabPostInit in modmain.lua

-- Allow honey to be used as fuel for the Confectionary Station
-- Also define new fuel here
GLOBAL.FUELTYPE.SWEETDREAM = "SWEETDREAM"

AddPrefabPostInit("honey", function(inst)
	--inst:AddTag(GLOBAL.FUELTYPE.SWEETDREAM.."_fuel")
    
	if not GLOBAL.TheWorld.ismastersim then
		return end
	
	--print("Attempting to add fuel component to honey")
	inst:AddComponent("fuel")
	inst.components.fuel.fueltype = GLOBAL.FUELTYPE.SWEETDREAM
	inst.components.fuel.fuelvalue = GLOBAL.TUNING.MED_FUEL
	--print("Fuel type added: "..inst.components.fuel.fueltype)
end)

It seems like the component is being added to Honey and the right fuel type is being verified with that last print statement, but it's still not being accepted as valid fuel for the machine. I don't think I'm doing anything differently here from the custom fuel item, so I have no idea where the hangup is! My only other guess was that adding fuel isn't registering as a valid action in componentactions.lua, which seems to check for a matching tag, but adding that in manually didn't work out either.

From componentactions.lua

for k, v in pairs(FUELTYPE) do
                    if inst:HasTag(v.."_fuel") then
                        if target:HasTag(v.."_fueled") then
                            table.insert(actions, inst:GetIsWet() and ACTIONS.ADDWETFUEL or ACTIONS.ADDFUEL)
                        end
                        return
                    end
                end

So... I'm stumped. Anyone have any insight? I've attached my modmain.lua and relevant prefab files if you need to see the big picture.

modmain.lua

dreamfuel.lua

gummimaker.lua

Link to comment
Share on other sites

At first look everything looks right.
you can add more prints/print some things ingame to.
So check if the fueltype and tags are really there while being ingame.
you can use c_select() and hover over the thing with mouse. Or use c_findnext(prefab) to use the things within console.

Link to comment
Share on other sites

Thanks! I never knew those console commands were available, that's helpful.

Print results were... as expected, I suppose? Component seems to have been added properly, necessary tags are there, the right fuel type is assigned... but the machine just doesn't want to take it. Is it possible that the container component is interfering somehow and getting prioritized as an action instead of adding fuel? Doesn't really make sense that it would only happen for Honey and not my custom fuel though, given that they can both be stored inside.

[00:05:46]: [(KU_ZFa8MGsw) shinymoogle] ReceiveRemoteExecute(print(c_select().components.fuel == nil)) @(370.42, -114.55)
[00:05:46]: Selected 115851 - honey    
[00:05:46]: false


[00:06:11]: [(KU_ZFa8MGsw) shinymoogle] ReceiveRemoteExecute(print(c_select().components.fuel.fueltype)) @(370.62, -114.53)
[00:06:11]: Selected 115851 - honey    
[00:06:11]: SWEETDREAM   


[00:06:34]: [(KU_ZFa8MGsw) shinymoogle] ReceiveRemoteExecute(print(c_select().components.fuel.fueltype)) @(369.95, -112.87)
[00:06:34]: Selected 116009 - dreamfuel    
[00:06:34]: SWEETDREAM   


[00:07:35]: [(KU_ZFa8MGsw) shinymoogle] ReceiveRemoteExecute(print(c_select().components.fueled.fueltype)) @(368.89, -117.79)
[00:07:35]: Selected 108929 - gummimaker    
[00:07:35]: SWEETDREAM   


[00:08:38]: [(KU_ZFa8MGsw) shinymoogle] ReceiveRemoteExecute(print(c_select():HasTag("SWEETDREAM_fuel"))) @(372.26, -116.00)
[00:08:38]: Selected 116305 - honey    
[00:08:38]: true 


[00:09:44]: [(KU_ZFa8MGsw) shinymoogle] ReceiveRemoteExecute(print(c_select():HasTag("SWEETDREAM_fueled"))) @(369.27, -117.78)
[00:09:44]: Selected 108929 - gummimaker    
[00:09:44]: true

Edited by ShinyMoogle
Spaced out the log for readability
Link to comment
Share on other sites

you can find all predefined consolecommands within consolecommands.lua in scripts folder of the game ;)


To make sure it is indeed caused by the componentactions you can add this to modmain:

AddComponentAction("USEITEM", "fuel", function(inst, doer, target, actions) -- this wont overwrite anything, it is just an additional check
    if not (doer.replica.rider ~= nil and doer.replica.rider:IsRiding())
                or (target.replica.inventoryitem ~= nil and target.replica.inventoryitem:IsGrandOwner(doer)) then
                if inst.prefab ~= "spoiled_food" and
                    inst:HasTag("quagmire_stewable") and
                    target:HasTag("quagmire_stewer") and
                    target.replica.container ~= nil and
                    target.replica.container:IsOpenedBy(doer) then
                        print("is returning without checking FUEL",inst)
                        return
                end
                for k, v in pairs(GLOBAL.FUELTYPE) do
                    if inst:HasTag(v.."_fuel") then
                        if target:HasTag(v.."_fueled") then
                            table.insert(actions, inst:GetIsWet() and ACTIONS.ADDWETFUEL or ACTIONS.ADDFUEL)
                            print("ADDFUEL action should was added",inst)
                        end
                        return
                    end
                end
            end
        print("end of function",inst)
end)

you can add more prints if neccessary.

In case the action is not added to the actions table and you can not fix it by changing your previous code, you can also use the code above to simply add the action yourself. So lets assume this is called "print("is returning without checking FUEL",inst)", then you could simply remove the first part of the code above, to only have the "for ... FUELTYPE..." function in it. This will then be executed after the checks written in componentactions.lua and will add your action. (so AddComponentAction is only adding, not overwriting)

Link to comment
Share on other sites

Alright, with that and a little further troubleshooting, I think I've managed to pinpoint the problem. The action does get added properly to the actions table...

[00:01:02]: ADDFUEL action should was added    100968 - honey    
[00:01:04]: ADDFUEL action should was added    103455 - dreamfuel   

...yet still doesn't show up when trying to use Honey on the machine.

So I took the nuclear option and temporarily cut out all the container component functions from the machine and tested again, and now it will accept Honey as fuel. If that means what I think it means, the STORE action is somehow taking precedent over ADDFUEL and preventing that action. Checking in actions.lua, neither STORE nor ADDFUEL have a priority set, so maybe they're just randomly jostling for main actionable action. I don't understand why my custom fuel would work though, considering that it's also able to be stored in the container. Weird.

Problem found! But... not solved. Since the machine is meant to automatically manufacture and deposit items in its storage compartment, I do want to keep that container component, which seems to be interfering with adding fuel.

What's the best thing to do in this case? Should I (can I?) override the existing ACTIONS.ADDFUEL and ACTIONS.ADDWETFUEL to give them +1 priority? Is that likely to cause unintended consequences in the game or other mods? I'm not sure if setting exclusions on what the container can accept will work, since STORE is still a valid action for putting random junk in iceboxes.

Edited by ShinyMoogle
Link to comment
Share on other sites

I have the nearly same problem here: https://github.com/Serpens66/DST-Evolving-Maxwell-Shadows (written in readme)
There I have the problem with the Feedplayer and store action. And I decided against modifying original priorities, because who know whcih side effects this will have (the priority is a problem although feed action is rightclick and store action is leftclick! This may be considered a game bug)

In my case the decision was to not allow storing nightmarefuel withing the container anymore, so feeding is working. Maybe this is also a valid solution for you?
In case you also have left and right click and it still does not work we could try to file a bugreport about it and hope Klei fixes this, so actions with same priority but one with left and one with right click can work simultanously.
Another way would be to design your own action. But both, store and addfuel have both alot of code you would need to copy... not sure which one might be the best choice.

Other than that I dont have an idea currently =/

Link to comment
Share on other sites

They're all left-click actions, I think. Though you'd think that with two different actions it would be possible to auto-map the second one to the secondary mouse button... Well, good to know it's not an isolated problem.

Not allowing storing Honey in the container would work great. I didn't really intend it to be used as general storage, anyway. Did you handle that in container.itemtestfn, or somewhere else?

Edited by ShinyMoogle
Link to comment
Share on other sites

I gave creating a custom widget with a specific itemtestfn a try, but unfortunately that doesn't look like it's working. I think that the action is being assigned to the mouse button before the the container checks to see if it's valid, which means the STORE action is still taking precedence over adding fuel - it'll just reject the item when attempting to store it. It's starting to look like if I want to make it work as fuel, I'll need to tweak how the action selection happens...

Hmm.

Well, maybe I can try something different. I'll have the machine not accept direct fuel input at all, but instead check to see if eligible fuel is stored inside its container and then automatically consume fuel directly from storage while it's on. Might be an easier solution that doesn't require messing with base game functions.

Edited by ShinyMoogle
Link to comment
Share on other sites

ah yes, I did not only used itemtstfn, sry , I also do:

    AddComponentAction("USEITEM", "inventoryitem", function(inst, doer, target, actions, right)
        if target.prefab=="shadowduelist" or helpers.isshadowworker(target.prefab) then
            for k, v in pairs(GLOBAL.FOODTYPE) do
                if inst:HasTag("edible_"..v) and target:HasTag(v.."_eater") then
                    GLOBAL.RemoveByValue(actions, GLOBAL.ACTIONS.STORE) -- the items that are edible, will not be stored in container!
                end
            end
        end
    end)

So everything my shadow prefab can eat, is not allowed to be stored inside of shadow, so I remove the STORE action from the actions list.

If you still want to go the other way with consuming what is inside, I also have a nice function for this :D
 

local function Findandconsume_prefab(inst,amount,prefab,onlyfind) -- tries to consume amount prefab from his container and returns the actual amount consumed
    local fuelconsumed = 0
    local item = nil
    if amount>0 then
        for i = 1, inst.components.container:GetNumSlots() do
            item = inst.components.container:GetItemInSlot(i)
            if item~=nil and item:IsValid() and item.prefab==prefab then                
                if onlyfind=="onlyfind" then
                    return item
                end
                if item.components.stackable~=nil and item.components.stackable:IsStack() then -- is a stack
                    if onlyfind==nil then
                        while fuelconsumed<amount and item.components.stackable:StackSize() >= 1 and item:IsValid() do
                            fuelconsumed = fuelconsumed + 1
                            if item.components.stackable:StackSize()==1 then
                                inst.components.container:RemoveItem(item):Remove() -- remove the last one
                            else
                                item.components.stackable:SetStackSize(item.components.stackable:StackSize() - 1) -- reduce the stacksize
                            end
                        end
                    elseif onlyfind=="findstackspace" and not item.components.stackable:IsFull() then
                        return item -- find a stack that is not full already
                    end
                elseif onlyfind==nil then -- is no stack 
                    fuelconsumed = fuelconsumed + 1
                    inst.components.container:RemoveItem(item):Remove()
                elseif item.components.stackable~=nil and onlyfind=="findstackspace" and not item.components.stackable:IsFull() then -- without "IsStack" because 1 cutgrass is no stack, but stackable
                    return item -- find a stack that is not full already
                end
            end
        end
    end
    if onlyfind==nil then
        return fuelconsumed
    else
        return false
    end
end

 

Link to comment
Share on other sites

Thanks for the sample code! I got things working (as far as I can tell), but actually ended up handling the fuel consumption a little bit differently due to a few specific considerations:

1: There are multiple valid fuel items, so I wanted the machine to be able to consume any valid fuel item when necessary. That meant using Fueled:TakeFuelItem with a direct reference to the fuel item to be consumed instead of just removing stuff from the container, and a custom search to check for a valid Fuel.fueltype instead of a specific prefab.

2: It turns out that Machine:CanInteract() does... absolutely nothing. It might have at some point in the game's development, but it's apparently not called anywhere in vanilla files outside of the Machine component, and the only thing that governs if a machine can be turned on or off is in componentactions.lua, which won't add the TURNON action if fuel has been depleted. Meaning that even if fuel is otherwise present in container storage, there's no way to get the machine to start consuming it without it already having fuel or modifying the componentaction.

 

So for (1), I created a custom function for Fueled.depleted that will search the container for valid fuel items, then consumes the first one it finds using Fueled.TakeFuelItem. For (2), rather than modifying componentactions, I instead opted to just manually set an "empty" fuel value of 1 for any case where the machine needs to check if it's empty. I'm still not sure when the "fueldepleted" tag gets added, but regardless, this seemed to work well enough.

local function HasFuel(inst)
	return inst.components.container:FindItem(function(item)
		return item ~= nil
			and item.components.fuel ~= nil
			and item.components.fuel.fueltype == FUELTYPE.SWEETDREAM
		end) ~= nil
end

-- Turn on machine and start consuming fuel
-- Set periodic task to generate gummis
local function TurnOn(inst, instant)
	-- A fuel value of 1 should be considered "empty" for confectionary station purposes
	-- Since 1 fuel is required to make the machine interactable
	if inst.components.fueled.currentfuel <= 1 and not HasFuel(inst) then
		inst.components.talker:Say("INSUFFICIENT SUGAR")
		inst:DoTaskInTime(0.1, function(inst)
			inst.components.machine:TurnOff()
		end)
		return
	end
	
	inst.machinestate = MACHINESTATES.ON
    inst.components.fueled:StartConsuming()
	
	inst.AnimState:PushAnimation("idle_on")
	inst.SoundEmitter:PlaySound("dontstarve_DLC001/common/firesupressor_on")
	
	inst.gummitask = inst:DoPeriodicTask(inst.craftrate, gummitaskfn)
end

local function TurnOff(inst, instant)
	inst.machinestate = MACHINESTATES.OFF
    inst.components.fueled:StopConsuming()
	
	inst.AnimState:PushAnimation("idle_off")
	inst.SoundEmitter:PlaySound("dontstarve_DLC001/common/firesupressor_off")
	
	if inst.gummitask ~= nil then
		inst.gummitask:Cancel()
		inst.gummitask = nil
	end	
	
	-- Workaround to avoid needing to modify actions
	-- Machine needs to be fueled in order to be interactable
	if inst.components.fueled.currentfuel < 1 then
		inst.components.fueled.currentfuel = 1
	end
end

local function OnFuelEmpty(inst)
	if HasFuel(inst) then
		local fuelitem = inst.components.container:FindItem(function(item)
			return item ~= nil
				and item.components.fuel ~= nil
				and item.components.fuel.fueltype == FUELTYPE.SWEETDREAM
			end)
		local singlefuel = inst.components.container:RemoveItem(fuelitem, false)
		inst.components.fueled:TakeFuelItem(singlefuel)
	else
		inst.components.machine:TurnOff()
	end
end

 

gummimaker.lua

Link to comment
Share on other sites

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
 Share

×
  • Create New...