Jump to content

[Solved] Changing perishable.lua


Recommended Posts

Hi,

How can I add my own tag to local function Update in perishable.lua component?

I tried adding AddClassPostConstruct function with doing my new local Update funcion, and calling it in new LongUpdate and new StartPerishing in AddClassPostConstruct, but it break the game whatever i try.

But when I copy whole perishable.lua to my mod folder it works then, but I don't think I should do this like this.

Link to comment
Share on other sites

2 hours ago, zetake said:

But when I copy whole perishable.lua to my mod folder it works then, but I don't think I should do this like this.

Nope. Bad practice.

2 hours ago, zetake said:

How can I add my own tag to local function Update in perishable.lua component?

You cannot change local variables or functions, unless you employ Upvalue Hacker, which should be a last resort.

What is it you're trying to accomplish? Perhaps we can find a less intrusive way.

Link to comment
Share on other sites

I want this to work in mainlua.

Spoiler

 


local function Update(inst, dt)

	if inst.components.perishable then
		
		local seasonmanager = GetSeasonManager()
		local modifier = 1
		local owner = inst.components.inventoryitem and inst.components.inventoryitem.owner or nil
		if owner then
			if owner:HasTag("my_own_tag") then
				modifier = 200 -- my value | 200 for testing
			elseif owner:HasTag("fridge") then
				if inst:HasTag("frozen") and not owner:HasTag("nocool") and not owner:HasTag("lowcool") then
					modifier = TUNING.PERISH_COLD_FROZEN_MULT
				else
					modifier = TUNING.PERISH_FRIDGE_MULT 
				end
			elseif owner:HasTag("spoiler") and owner:HasTag("poison") then
				modifier = TUNING.PERISH_POISON_MULT
			elseif owner:HasTag("spoiler") then
				modifier = TUNING.PERISH_GROUND_MULT 
			end
		else
			modifier = TUNING.PERISH_GROUND_MULT
		end

		-- Cool off hot foods over time (faster if in a fridge)
		if inst.components.edible and inst.components.edible.temperaturedelta and inst.components.edible.temperaturedelta > 0 then
			if owner and owner:HasTag("fridge") then
				if not owner:HasTag("nocool") then
					inst.components.edible.temperatureduration = inst.components.edible.temperatureduration - 1
				end
			elseif seasonmanager and seasonmanager:GetCurrentTemperature() < TUNING.OVERHEAT_TEMP - 5 then
				inst.components.edible.temperatureduration = inst.components.edible.temperatureduration - .25
			end
			if inst.components.edible.temperatureduration < 0 then inst.components.edible.temperatureduration = 0 end
		end

		local mm = GetWorld().components.moisturemanager
		if mm:IsEntityWet(inst) then
			modifier = modifier * TUNING.PERISH_WET_MULT
		end
		
		if seasonmanager and seasonmanager:GetCurrentTemperature() < 0 then
			if inst:HasTag("frozen") and not inst.components.perishable.frozenfiremult then
				modifier = TUNING.PERISH_COLD_FROZEN_MULT
			else
				modifier = modifier * TUNING.PERISH_WINTER_MULT
			end
		end

		if inst.components.perishable.frozenfiremult then
			modifier = modifier * TUNING.PERISH_FROZEN_FIRE_MULT
		end

		if seasonmanager and seasonmanager:GetCurrentTemperature() > TUNING.OVERHEAT_TEMP then
			modifier = modifier * TUNING.PERISH_SUMMER_MULT
		end		

		local aporkalypse = GetAporkalypse()
		if aporkalypse and aporkalypse:IsActive() then
			modifier = modifier * TUNING.PERISH_APORKALYPSE_MULT
		end

		modifier = modifier * TUNING.PERISH_GLOBAL_MULT
		
		local old_val = inst.components.perishable.perishremainingtime
		local delta = dt or (10 + math.random()*FRAMES*8)
		inst.components.perishable.perishremainingtime = inst.components.perishable.perishremainingtime - delta*modifier
		if math.floor(old_val*100) ~= math.floor(inst.components.perishable.perishremainingtime*100) then
			inst:PushEvent("perishchange", {percent = inst.components.perishable:GetPercent()})
		end
		
		--trigger the next callback
		if inst.components.perishable.perishremainingtime <= 0 then
			inst.components.perishable:Perish()
		end
	end
end

AddClassPostConstruct("components/perishable", function( self, owner )

	function self:LongUpdate(dt)
		if self.updatetask then
			Update(self.inst, dt or 0)
		end
	end

	function self:StartPerishing()
		if self.updatetask then
			self.updatetask:Cancel()
			self.updatetask = nil
		end

		local dt = 10 + math.random()*FRAMES*8--math.max( 4, math.min( self.perishtime / 100, 10)) + ( math.random()* FRAMES * 8)

		if dt > 0 then
			self.updatetask = self.inst:DoPeriodicTask(dt, Update, math.random()*2, dt)
		else
			Update(self.inst, 0)
		end
	end
end)

 

 

 

Or is there better way to change local function Update?

Or is there other way to change perishing time of items in some container?

Link to comment
Share on other sites

I think I understand what you're trying to do, but to be sure, could you explain what it is you're trying to do? How many items are we talking? Do you want it to be different depending on which container the item is in? I'd like to know exactly what you're trying to accomplish, without "thinking in code".

Link to comment
Share on other sites

Wow. This is probably the first time I've seen something that needs the Upvalue Hacker. You need it to access the Update function, so you can extend it, and basically copy much of the existing code, in order to check whether there is an owner, and if that owner is your container prefab, then you do your special code, and if not, you run the original function.

Link to comment
Share on other sites

Eh, my poor, poor, poor coding...

Spoiler

I forgot about:


local FRAMES = GLOBAL.FRAMES
local TUNING = GLOBAL.TUNING

Didn't change GetSeasonManager(), GetWorld() and GetAporkalypse() to GLOBAL.
And small typos in (self, owner), should be (self, inst).

With this, it finally works. ;D

In my excuse I'm modding only for a couple days with 0 lua knowledge...
And I'm learning modding by klei code... not by tutorials, well I seems I don't need vanilla tutorials ;D

Link to comment
Share on other sites

Didn't you read it in my previous post?

How? By statements not global, but I guess thanks to your post a looked into log.txt.

Because game didn't crash, but only froze and I couldn't see crash log from game.

Spoiler

local FRAMES = GLOBAL.FRAMES
local TUNING = GLOBAL.TUNING

local function Update(inst, dt)

    if inst.components.perishable then
        
        local seasonmanager = GLOBAL.GetSeasonManager()
        local modifier = 1
        local owner = inst.components.inventoryitem and inst.components.inventoryitem.owner or nil
        if owner then
            if owner:HasTag("my_own_tag") then
                modifier = 200 -- my own modifer
            elseif owner:HasTag("fridge") then
                if inst:HasTag("frozen") and not owner:HasTag("nocool") and not owner:HasTag("lowcool") then
                    modifier = TUNING.PERISH_COLD_FROZEN_MULT
                else
                    modifier = TUNING.PERISH_FRIDGE_MULT 
                end
            elseif owner:HasTag("spoiler") and owner:HasTag("poison") then
                modifier = TUNING.PERISH_POISON_MULT
            elseif owner:HasTag("spoiler") then
                modifier = TUNING.PERISH_GROUND_MULT 
            end
        else
            modifier = TUNING.PERISH_GROUND_MULT
        end

        -- Cool off hot foods over time (faster if in a fridge)
        if inst.components.edible and inst.components.edible.temperaturedelta and inst.components.edible.temperaturedelta > 0 then
            if owner and owner:HasTag("fridge") then
                if not owner:HasTag("nocool") then
                    inst.components.edible.temperatureduration = inst.components.edible.temperatureduration - 1
                end
            elseif seasonmanager and seasonmanager:GetCurrentTemperature() < TUNING.OVERHEAT_TEMP - 5 then
                inst.components.edible.temperatureduration = inst.components.edible.temperatureduration - .25
            end
            if inst.components.edible.temperatureduration < 0 then inst.components.edible.temperatureduration = 0 end
        end

        local mm = GLOBAL.GetWorld().components.moisturemanager
        if mm:IsEntityWet(inst) then
            modifier = modifier * TUNING.PERISH_WET_MULT
        end
        
        if seasonmanager and seasonmanager:GetCurrentTemperature() < 0 then
            if inst:HasTag("frozen") and not inst.components.perishable.frozenfiremult then
                modifier = TUNING.PERISH_COLD_FROZEN_MULT
            else
                modifier = modifier * TUNING.PERISH_WINTER_MULT
            end
        end

        if inst.components.perishable.frozenfiremult then
            modifier = modifier * TUNING.PERISH_FROZEN_FIRE_MULT
        end

        if seasonmanager and seasonmanager:GetCurrentTemperature() > TUNING.OVERHEAT_TEMP then
            modifier = modifier * TUNING.PERISH_SUMMER_MULT
        end        

        local aporkalypse = GLOBAL.GetAporkalypse()
        if aporkalypse and aporkalypse:IsActive() then
            modifier = modifier * TUNING.PERISH_APORKALYPSE_MULT
        end

        modifier = modifier * TUNING.PERISH_GLOBAL_MULT
        
        local old_val = inst.components.perishable.perishremainingtime
        local delta = dt or (10 + math.random()*FRAMES*8)
        inst.components.perishable.perishremainingtime = inst.components.perishable.perishremainingtime - delta*modifier
        if math.floor(old_val*100) ~= math.floor(inst.components.perishable.perishremainingtime*100) then
            inst:PushEvent("perishchange", {percent = inst.components.perishable:GetPercent()})
        end
        
        --trigger the next callback
        if inst.components.perishable.perishremainingtime <= 0 then
            inst.components.perishable:Perish()
        end
    end
end

AddClassPostConstruct("components/perishable", function(self, inst)
    
    function self:LongUpdate(dt)
        if self.updatetask then
            Update(self.inst, dt or 0)
        end
    end

    function self:StartPerishing()
        if self.updatetask then
            self.updatetask:Cancel()
            self.updatetask = nil
        end

        local dt = 10 + math.random()*FRAMES*8--math.max( 4, math.min( self.perishtime / 100, 10)) + ( math.random()* FRAMES * 8)

        if dt > 0 then
            self.updatetask = self.inst:DoPeriodicTask(dt, Update, math.random()*2, dt)
        else
            Update(self.inst, 0)
        end
    end
end)

And in container prefab.


inst:AddTag("my_own_tag")

 

 

Link to comment
Share on other sites

1 hour ago, zetake said:

Didn't you read it in my previous post?

I did, but I also stated earlier that you would have to employ Upvalue Hacker in order to get this to work properly, which is why I was curious as to how you got it to work so quickly.

What you've done is essentially overwriting the LongUpdate and StartPerishing functions, which means that any changes made to those functions by any mod loaded before your mod, are completely overwritten. Since we're talking about perishables here, and people love to make changes to perishables, that's going to be a sizable amount of mods which your mod will be incompatible with (as in, your mod will make the other mod crash or not work properly).

That's one thing. Another problem: Though I doubt many modders have employed the Upvalue Hacker to extend the local Update function, if they have, you are effectively side-stepping their changes, substituting their altered version of the function with your function.

These things may not sound very problematic, but some of the content mods porting things from DS do use the Upvalue Hacker, in order to make the DST components able to handle the DS content. Since many of these contain perishables, it is likely that they have altered the Update function. And A LOT of people use these content mods, because, why not? More content.

This is where the Upvalue Hacker would shine, since you can take the current version of the Update function and extend it, instead of completely overwriting it. (I am not 100% sure about this, since I haven't used the Upvalue Hacker, but I think it can do it).

I won't tell you what to do with your mod. Just know that you may be in for a storm of bug reports from players, and they'll be confused, because DST will report the conflicting mods as the perpetrators, instead of yours.

Link to comment
Share on other sites

It doesn't matter...

One way or another...

I mean my mod or other mod, some will have to overrite things...

If I could and should do it other way without overriting, then someelse can do it too, and it will be just fine.

 

EDIT:

BUUUT wait, I think I can do it like you said...

I can check if container is my own tag and then do changed update function, but I'm not sure if I'll be albe to get cointainer...

I'll tried it later...

Link to comment
Share on other sites

19 minutes ago, zetake said:

It doesn't matter...

One way or another...

I mean my mod or other mod, some will have to overrite things...

If I could and should do it other way without overriting, then someelse can do it too, and it will be just fine.

Not true. If everyone extends instead of overwriting, then the chances of incompatibilities are kept at their absolute minimum, while if one person overwrites, the chances are quite high.

Link to comment
Share on other sites

Well, I need function to check if container is custom.if so, call changed update.

But, I can't get container or it can't be done from AddClassPostConstruct.

 

EDIT: Well, I don't know why this work in local funtion Update, but not in the funcions directly...

Spoiler

AddClassPostConstruct("components/perishable", function(self, inst)
	
	local _l = self.LongUpdate
	function self:LongUpdate(dt)
		local custom = false
		if self.inst.components.perishable then
			local owner = self.inst.components.inventoryitem and self.inst.components.inventoryitem.owner or nil
			if owner then
				if owner:HasTag("my_own_tag") then
					custom = true
					if self.updatetask then
						Update(self.inst, dt or 0)
					end
				else
					custom = false
				end
			else
				custom = false
			end
		else
			custom = false
		end
		
		if not custom then
			_l(self, inst, dt)
		end
	end

	local _s = self.StartPerishing
	function self:StartPerishing()
		local custom = false
		if self.inst.components.perishable then
			local owner = self.inst.components.inventoryitem and self.inst.components.inventoryitem.owner or nil
			if owner then
				if owner:HasTag("my_own_tag") then
					custom = true
					if self.updatetask then
						self.updatetask:Cancel()
						self.updatetask = nil
					end

					local dt = 10 + math.random()*FRAMES*8--math.max( 4, math.min( self.perishtime / 100, 10)) + ( math.random()* FRAMES * 8)

					if dt > 0 then
						self.updatetask = self.inst:DoPeriodicTask(dt, Update, math.random()*2, dt)
					else
						Update(self.inst, 0)
					end
				else
					custom = false
				end
			else
				custom = false
			end
		else
			custom = false
		end

		if not custom then
			_s(self, inst)
		end
	end
end)

 

So, it's owner issue or AddClassPostConstruct?

P.S: Script works, but it doesn't update in game or doesn't get owner, IDK.

Link to comment
Share on other sites

Find alternative, but it requries modyfing local function too... (Almost non used function)

So, I got idea how this can be done, but I don't know where to put this and how get container and item for this.

Spoiler

	inst:ListenForEvent("containergotitem", function(inst, data)
		if data.item.components.perishable then
			data.item.components.perishable:StartCustomPerishing()
		end
	end)

	inst:ListenForEvent("itemlose", function(inst, data)
		if data.item.components.perishable then
			data.item.components.perishable:StartPerishing()
		end
	end)

 

Tried this in container but it doesn't get item...

Link to comment
Share on other sites

That is a great entry point you've found there. Both of those eventlisteners should just be put in your own container's fn() function.

I, too, have an update...pun intended. I've found a way to use the original Update function for your code! You can retrieve the current Update() function from the updatetask variable on the perishable component. So, what you'll want to do, is make your StartCustomPerishing() function like this:

if inst.components.perishable and inst.components.perishable.updatetask then
	local oldUpdateFunction = inst.components.perishable.updatetask.fn
	inst.components.perishable.updatetask.fn = function(inst, dt)
		dt = dt * your_modifier
		oldUpdateFunction(inst, dt)
	end
end

That way you're using the original (or modded) code, and the only thing you're affecting is the dt (deltaTime). The deltaTime directly controls how much the item perishes each frame, so if you apply a modifier to that, you can change it to your liking. The your_modifier variable could be 1.20, which would make all the food perish 20% faster, while 0.80 will make all the food perish 20% slower.

Now, you still need to have your two event listeners, because when the "itemlose" listener is triggered, the original StartPerishing() function will restart the perishing with the original code, which is what you want. And your "containergotitem" event listener will correctly trigger your StartCustomPerishing() function.

These things are all important, because many things call StartPerishing, e.g., the dryer component (drying rack) and the stewer component (Crockpot). They all need to work properly. We only want to affect things when they're in your container, and leave everything else alone. It is also important to check in your StartCustomPerishin() function whether there is an updatetask running, because if there isn't, then there's probably a reason why the item is not perishing, and so, your container should not make it start perishing.

I think all of this should be possible for you to figure out.

Link to comment
Share on other sites

I don't get what you writed.

And I need to change whole update, because my container shouldn't be affected by weather or anything else, I need modifer to be edited just by my own value.

 

Function doesn't need to be something else, custom StartPerishing() with calling own update is enough.

Because it's stops perishing on start, then do own perishing, that's why there wasn't StopPerishing().

But I don't know how to get for these event listeners an item to change it.

 

I know that this method can work, because this work.

Spoiler

local function OnPutInInventory(inst, owner)
    if owner.prefab == "my_own_container" then
        inst.components.perishable:StartCustomPerishing()
    end
end

local function OnRemoved(inst, owner)
    if owner.prefab == "my_own_container" then
        inst.components.perishable:StartPerishing()
    end
end

 

But it's only item function and it also shouldn't be overrided, even when it's rarely used.

Link to comment
Share on other sites

Well, if you insist on having your own custom Update() function, I can't stop you. As long as you make sure to store your update task in the updatetask variable, so StartPerishing() automatically stops yours and starts the original, it should be fine.

2 minutes ago, zetake said:

But I don't know how to get for these event listeners an item to change it.

I think you're missing something here. You don't need to "get an item to change it". The perishable component is on the item, and StartPerishing() is called when the item is created. Your event listeners are triggered whenever any item is put into the container, and that item is passed to your event listener function as data.item. With your listeners and your StartCustomPerishing(), all you need to do, is to make sure that your StartCustomPerishing() is called properly, and thus, it has to be made available. The way you're calling it now, requires you to add that function to all perishable components, which you can do like this (in your modmain):

GLOBAL.require "components/perishable"
Perishable:StartCustomPerishing()
	-- Your code
end

 

You can also just put the function in your own container prefab as a local function, like this:

local function StartCustomPerishing(self)
	-- your code
end

And then call it like this:

inst:ListenForEvent("containergotitem", function(inst, data)
	if data.item.components.perishable then
		StartCustomPerishing(data.item.components.perishable)
	end
end)

 

Link to comment
Share on other sites

I used your listenforevent code and:

attempt to index field 'item' (a nil value)

And my StartCustomPerish is in components/perishable as a new function:

Spoiler

AddClassPostConstruct("components/perishable", function(self, inst)
    function self:StartCustomPerishing()
		if self.updatetask then
			self.updatetask:Cancel()
			self.updatetask = nil
		end

		local dt = 10 + math.random()*FRAMES*8--math.max( 4, math.min( self.perishtime / 100, 10)) + ( math.random()* FRAMES * 8)

		if dt > 0 then
			self.updatetask = self.inst:DoPeriodicTask(dt, Update, math.random()*2, dt)
		else
			Update(self.inst, 0)
		end
    end
end)

 

 

Link to comment
Share on other sites

Where is you custom Update function?

And where did you put the event listener code? And did you check it after copy/pasting it? Please post the whole log. The stacktrace is important, but in this case, what's even more important is that I can see the code you've written, otherwise I don't have a chance.

Link to comment
Share on other sites

Custom Update functon is in modmain and StartCustomPerish is there too...

I put listener in local function fn(Sim) of my custom container prefab.

Well, I didn't have crash this time, but when I exit game it's sucks all of my ram... -.-

Perishing doesn't work, here's a log.txt.

Spoiler

	inst:ListenForEvent("containergotitem", function(inst, data)
		if data.item.components.perishable then
			data.item.components.perishable:StartCustomPerishing()
		end
	end)

 

P.S: Maybe I should just add tag fridge and frozen to container to disable perishing?

Because I need much slower perishing that shouldn't be noticeable in decaded, which means turning it off is ok, but I wanted to do it in don't starve style and do only less spoiling than fridge.

But my custom container will be fueled, so it's not much different, only small thing.

Link to comment
Share on other sites

I can't see anything wrong. It's difficult to help without being able to debug the mod. I can't simulate this code in my head. If you just want lower perishing than the original fridge, using the original function and modifying the incoming deltatime is the easiest way to go. I know you don't want moisture etc. to be influencing the perishing, but sometimes you just gotta take what you can get, unless you're ok with spending a lot of time getting something else to work.

Link to comment
Share on other sites

I was doing bar thing, decided to try with ListenForEvent, and with frustration idk how it works I used print.
Turnout itemget triger when container gets item.
SOOOOO I MADE THIS WORK LUUUUUUL

	inst:ListenForEvent("itemget", function(inst, data)
		local item = data and data.item
		if item.components.perishable then
			item.components.perishable:StartISRPerishing()
		end
	end)


BUUUUTTTT it doesn't change back after removing item from container....

Tried inst:ListenForEvent("itemlose"...

That's good, but it gives only slot. >.<

I know that I can get item by slot by components.container:GetItemInSlot(slot).

But I can't get this component with inst or anything else...

Link to comment
Share on other sites

35 minutes ago, zetake said:

That's good, but it gives only slot. >.<

I know that I can get item by slot by components.container:GetItemInSlot(slot).

But I can't get this component with inst or anything else...

The DropItem() function in the inventory component pushes the "dropitem" event, and DropItem() is called when an item is dropped for whatever reason, and I think this is also the case when having an item in the mouse and dropping it on the ground. It's called in many places, so you might have to use it somehow as a way to make sure that if an item is removed, you call StartPerishing() on it. The "dropitem" event always passes along the item being dropped.

The "itemlose" event is pushed by both RemoveItem() (called when an item is removed, e.g. consumed, but in very, very many different cases, though not when the item is put into the mouse or subsequently dropped) and SelectActiveItemFromSlot() (when you click an item in your inventory to put it in the mouse). You always get the item in the data passed along with the event, in addition to EITHER a bool called "activeitem", saying whether the item being removed was the active item (item in the mouse) OR you get a "slot" variable telling you which slot the item came from, but you always get the item variable. For reference, these are the ways in which the event is pushed in the code, and one of these are pushed in three places in the inventory component code.

self.inst:PushEvent("itemlose", {activeitem = true, item = item})
self.inst:PushEvent("itemlose", {slot = k, item = item})

You have a lot of cases to cover here. I wish you the best of luck trying to find the best combination of checks and events to make this work.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

Please be aware that the content of this thread may be outdated and no longer applicable.

×
  • Create New...