Recommended Posts

zetake    4

I have "dropitem" done same way as "itemget".

And "itemlose" trigger when it's putted to mouse.

The problem is that I get these events only from container component...

Edited by zetake

Share this post


Link to post
Share on other sites
zetake    4

I honestly don't have f...ing idea WHY/HOW, BUT it works. OMG (Even with stack items, when you take half[AGAIN IDK WHY/HOW])

	inst:ListenForEvent("itemget", function(inst, data)
		local item = data and data.item
		local perishable = item.components.perishable or nil
		if perishable then
			item.components.perishable:StartISRPerishing()
		end
		local slot = data and data.slot
		inst:ListenForEvent("itemlose", function(inst, data2)
			if slot == data2.slot then
				if perishable then
					item.components.perishable:StartPerishing()
				end
			end
		end)
	end)

On drop from container should work with ListenForEvent("dropitem",
but not sure about saving or after some time, or if this bug/slow game...
Require testing...

Anyway thanks for help.

P.S: Unorthodox thinking: 100 point for me. XD

Edited by zetake

Share this post


Link to post
Share on other sites
Ultroman    399

With that code, you're going to end up with duplicate listeners for the "itemlose" event. You need to remove the listener again when it's called, which sadly requires you to also pass the function you hooked up to the listener, so we have to store it in a local variable. Also, "slot" needs to be stored, as well, since simply making it local for the inventory would break everything if you put in one item and take out another. You'd have to do something like this:

local myFunction = function(inst, slot)
	if slot == nil then
		return
	end
	
	local tempfunc = function(inst, data2)
		if slot == data2.slot then
			if perishable then
				item.components.perishable:StartPerishing()
				inst:RemoveEventCallback("itemlose", tempfunc)
			end
		end
	end
	
	inst:ListenForEvent("itemlose", tempfunc)
end

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

I'm not 100% sure this'll work, and there are definitely still some edge-cases you need to take care of. 

There are many ways in which an item can be put into, or taken out of, an inventory. There are also the overflow containers to test with, and putting things into a backpack sitting on the ground. Sooo many things. Break out your be print-statements printing everything, and set up a slew of test-cases and go through them one by one, checking that your print-statements and the game say and do as you expect. It's pretty important, because it's difficult to tell while playing the game whether the mod is doing what it's supposed to. At some point, someone may message you "hey, my food suddenly lasts forever" or "My game now lags every time I click an item in your container."

Edited by Ultroman

Share this post


Link to post
Share on other sites
zetake    4

My script is just better?

This part

if slot == data2.slot then

prevents to do fn on all same items in container...

And this one

inst:ListenForEvent("itemlose", function(inst, data2)

shouldn't be ended if you take only half of a stack...

#EDIT: This part of yours

	if slot == nil then
		return
	end

is pointless, because there will be always slot when you put item to slot in container... logic, right?

Only problem is to not duplicate listener "itemlose" up to max container slots...

Is there fn to detect if lister is already running? Or I need to save item and slot from "itemget" somewhere...

Edited by zetake

Share this post


Link to post
Share on other sites
Ultroman    399

True. But the problem with duplicate event listeners is still there. You have to take care of that. It's a memory/performance leak, not to mention that it'll call StartPerishing() once for every event listener. You can test this by putting a print-statement in your "itemlose" function, and then continuously putting the same stack into your container and taking it out again. You should get duplicate prints the more times you do it.

Share this post


Link to post
Share on other sites
zetake    4

Yes, every "itemget" was creating new "itemlose" event...
Oh, well. I just overrided Container:RemoveItemBySlot(slot) and Container:RemoveItem(item, wholestack).
Now it returns item... and works perfect? Probably...
This override is better? Sure...

Share this post


Link to post
Share on other sites
Ultroman    399

As long as you chained the original functions, instead of copying and overwriting them, then it's probably fine (barring any problems with the way you've done this whole thing).

Share this post


Link to post
Share on other sites
zetake    4

I didn't, I don't see how I could accomplish this...

Spoiler

AddClassPostConstruct("components/container", function(self, inst)

	function self:RemoveItemBySlot(slot)
		if slot and self.slots[slot] then
			local item = self.slots[slot]
			if item then
				self.slots[slot] = nil
				if item.components.inventoryitem then
					item.components.inventoryitem:OnRemoved()
				end
				
				self.inst:PushEvent("itemlose", {item = item, slot = slot}) -- changed here
			end
			item.prevcontainer = self
			item.prevslot = slot
			return item        
		end
	end

	function self:RemoveItem(item, wholestack)
		local dec_stack = not wholestack and item and item.components.stackable and item.components.stackable:IsStack() and item.components.stackable:StackSize() > 1
		local slot = self:GetItemSlot(item)
		if dec_stack then
			local dec = item.components.stackable:Get()
			dec.prevslot = slot
			dec.prevcontainer = self
			return dec
		else
			for k,v in pairs(self.slots) do
				if v == item then
					self.slots[k] = nil
					self.inst:PushEvent("itemlose", {item = item, slot = k, boatequipslot = nil}) -- changed here

					if item.components.inventoryitem then
						item.components.inventoryitem:OnRemoved()
					end

					item.prevslot = slot
					item.prevcontainer = self
					return item
				end
			end
			if self.hasboatequipslots then 
				for k,v in pairs(self.boatequipslots) do
					if v == item then
						--print("found item equipped!!!")
						--self.boatequipslots[k] = nil
						self.inst:PushEvent("itemlose", {item = item, slot = nil, boatequipslot = k}) -- changed here

						if item.components.inventoryitem then
							item.components.inventoryitem:OnRemoved()
						end

						self:Unequip(k)
						item.prevslot = slot
						item.prevcontainer = self
						return item
					end
				end
			end 

		end
		
		return item

	end
end)

 

#EDIT: Maybe with detecting if item is in my container, but what's the point? Will someone edit this part too?

Edited by zetake

Share this post


Link to post
Share on other sites
Ultroman    399

You don't need AddClassPostConstruct for any of this. You have your own container prefab, right? Within its fn() function, you can easily overwrite or chain the public functions in its components. And I don't see why you need to change the "itemlose" event pushes. The functions always return the item.

This is how you chain/extend the functions. Put this in your container prefab's fn() function.

local oldRemoveItem = inst.components.container.RemoveItem

inst.components.container.RemoveItem = function(self, item, wholestack)
	local returnedItem = oldRemoveItem(self, item, wholestack)
	-- do whatever you want with the item, which MIGHT have been removed from the container.
end


local oldRemoveItemBySlot = inst.components.container.RemoveItemBySlot

inst.components.container.RemoveItemBySlot = function(self, slot)
	local returnedItem = oldRemoveItemBySlot(self, slot)
	-- do whatever you want with the item, which MIGHT have been removed from the container.
end

 

Share this post


Link to post
Share on other sites
zetake    4

Because "itemlose" doesn't push item for event listener... (only slot)

Well I knew this "chain/extend", but I wasn't aware I can use it like that...

Ok now works wonderfull.

Spoiler

Fns

Spoiler


local function removeitembyslot(self, slot)
	if slot and self.slots[slot] then
		local item = self.slots[slot]
		if item then
			self.slots[slot] = nil
			if item.components.inventoryitem then
				item.components.inventoryitem:OnRemoved()
			end
			self.inst:PushEvent("itemlose", {item = item, slot = slot})
		end
		item.prevcontainer = self
		item.prevslot = slot
		return item        
	end
end

local function removeitem(self, item, wholestack)
	local dec_stack = not wholestack and item and item.components.stackable and item.components.stackable:IsStack() and item.components.stackable:StackSize() > 1
	local slot = self:GetItemSlot(item)
	if dec_stack then
		local dec = item.components.stackable:Get()
		dec.prevslot = slot
		dec.prevcontainer = self
		return dec
	else
		for k,v in pairs(self.slots) do
			if v == item then
				self.slots[k] = nil
				self.inst:PushEvent("itemlose", {item = item, slot = k, boatequipslot = nil})
				if item.components.inventoryitem then
					item.components.inventoryitem:OnRemoved()
				end
				item.prevslot = slot
				item.prevcontainer = self
				return item
			end
		end
		if self.hasboatequipslots then 
			for k,v in pairs(self.boatequipslots) do
				if v == item then
					self.inst:PushEvent("itemlose", {item = item, slot = nil, boatequipslot = k}) 
					if item.components.inventoryitem then
						item.components.inventoryitem:OnRemoved()
					end
					self:Unequip(k)
					item.prevslot = slot
					item.prevcontainer = self
					return item
				end
			end
		end 
	end
	return item
end

 

Fn()

Spoiler


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

	local container = inst.components.container
	container.RemoveItemBySlot = removeitembyslot
	container.RemoveItem = removeitem

 

and Custom Perish

Spoiler


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

local function Update(inst, dt)

    if inst.components.perishable then
        
        local modifier = 300
        
        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:StartISRPerishing()
		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)

 

 

Thanks a lot..

But I wonder what happens if 

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

kicks in, if kicks in?

When LongUpdate is used???

And what means dt in it?

Edited by zetake

Share this post


Link to post
Share on other sites
Ultroman    399

Yes, that is a problem. Right now, if someone uses LongUpdate() on the server, it'll use the original Update() function. Since it IS a public function in the perishable component, you CAN extend this, as well, check if there is an updatetask, also check whether the owner of the perishable item is your container (self.inst.components.inventoryitem.owner.prefab, obviously with a bunch of checks to make sure the component and owner is there), and if it is your container, then do your Update function instead and return immediately, and if not, call the original function.

dt is the deltatime, the time between last frame and this frame.

This was one of the many reasons I suggested you just make your mod put a modifier on the deltatime (dt) in both LongUpdate() AND StartPerishing(), which both get dt as a parameter. It would be super easy, and aaalmost what you want.

Share this post


Link to post
Share on other sites
zetake    4

Something like this?

	local LongUpdate = self.LongUpdate
	function self:LongUpdate(dt)
		local owner = inst.components.inventoryitem and inst.components.inventoryitem.owner or nil
		if owner:HasTag("isr") then
			if self.updatetask then
				Update(self.inst, dt or 0)
			end
		else
			LongUpdate(self, inst, dt)
		end
	end

But, it didn't work for custom perishing, because it didn't get/refresh owner...

And I'm doing mod only for Don't Starve as it is now...

Edited by zetake

Share this post


Link to post
Share on other sites
Ultroman    399

No, you're still overwriting functions.

-- I do not know exactly where you are in the code right now, but for you to
-- be able to use "self" in this way, the code has to either be inside a
-- AddClassPostConstruct or a new version of the class, or a public function
-- which is passed a self as a hidden parameter.
-- Otherwise, you have to get the component on the instance you are changing.
-- I've rewritten the code, so you can stick it in an AddComponentPostInit function.

-- Store the old function.
local oldLongUpdate = inst.components.inventoryitem.LongUpdate
-- Overwrite the original function. Since it's originally declared with :, we need to add self as the first parameter.
inst.components.inventoryitem.LongUpdate = function(self, dt)
	local owner = self.inst.components.inventoryitem and self.inst.components.inventoryitem.owner or nil
	-- You have to check for owner being nil before using it.
	if owner ~= nil and owner:HasTag("isr") then
		if self.updatetask then
			Update(self.inst, dt or 0)
		end
	else
		-- Call the original function. (how did you figure that it should have "inst" in there?)
		oldLongUpdate(self, dt)
	end
end

 

Edited by Ultroman

Share this post


Link to post
Share on other sites
zetake    4

I don't how this works, but shouldn't it be inst.components.inventoryitem.OnLongUpdate?

Edited by zetake

Share this post


Link to post
Share on other sites
Ultroman    399

Sorry, we're messing with so many different classes, I got a bit confused. That code should be applied to all perishables, so put this in your modmain.lua

-- I have rewritten the code, so you can stick it in an AddComponentPostInit function.
AddComponentPostInit("perishable", function(comp)
    -- Store the old function.
    local oldLongUpdate = comp.LongUpdate
    -- Overwrite the original function. Since it's originally declared with :, we need to add self as the first parameter.
    comp.LongUpdate = function(self, dt)
        local owner = self.inst.components.inventoryitem and self.inst.components.inventoryitem.owner or nil
        -- You have to check for owner being nil before using it.
        if owner ~= nil and owner:HasTag("isr") then
            if self.updatetask then
                Update(self.inst, dt or 0)
            end
        else
            -- Call the original function.
            oldLongUpdate(self, dt)
        end
    end
end)

 

Edited by Ultroman

Share this post


Link to post
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