Jump to content

Is it possible to attach some data to a plant?


Recommended Posts

I'm adding some extra data to buildings in the following way when a player places a structure ( below is oversimplified, do not use in actual products ):

AddPlayerPostInit(function(inst)
	inst.components.builder.onBuild = addMyExtraData
end)

and in my callback function ( again simplified ):

local function addMyExtraData(inst, prod)
    prod.customData = 'blabla'
    prod:AddTag('custom_tag')
end

This way I can access my extra data later, when certain actions are triggered on that buildings. Now, I want to do the same with plants. Let's say when a player deploys a berry bush or a pine cone, I would like to for example attach the placement time to it so I can access it later.

Is it possible?

Any help is appreciated.

Edited by JackJohnsn
Link to comment
Share on other sites

you are using the onbuild for builder.
So why not using the "ondeploy" for deploying? within deployable component

self.ondeploy(self.inst, pt, deployer, rot or 0)

(I know your code is simplified, but for everyone who want to use such a code: dont forget to save the old onbuild previously and call it within your new function, at least if you dont want to remove the original onbuild stuff (or stuff added by other modders))

Edited by Serpens
Link to comment
Share on other sites

Thanks, I found this function in the deployable.lua file, but didn't know what to do with it ( I'm not a lua programmer :D ). So, could you please make a simplified example?

The onBuild passed a `prod` value to the function that I can use to save data, but since the codes have no documentation I have no idea what are the values passed by the ondeploy.

What I tried was this:

AddComponentPostInit("deployable",function(self,inst)
	self.ondeploy = addDeployCustomData
end)

and then:

local function addDeployCustomData(inst, pt, deployer, rot)
	GLOBAL.TheNet:Announce("point reached")
end

But nothing happens.

Edited by JackJohnsn
Link to comment
Share on other sites

you can alter the deployable component directly, if you want it to work for everything that is deployable. If you only want it to work for a specific prefab, then better use AddPrefabPostInit and change inst.components.deployable.ondeploy , like you did above with onbuild. (and again the hint, do not overwrite it, save the old function before and call it within your new function)

Assuming you want to reach everything deployable:

AddComponentPostInit("deployable",function(self) -- this function ONLY has "self". this self is the same you see within the deployable.lua. self is the component itself. so you can also access the instance with self.inst or any function within the component, that is not local.
    local old_ondeploy = self.ondeploy
    local function new_ondeploy(inst, position, deployer, rotation,...)
        -- your code
        if old_ondeploy~=nil then
            return old_ondeploy(inst, position, deployer, rotation,...)
        end
    end
    self.ondeploy = new_ondeploy
end)

To find out what values the ondeploy passes, you simply look into the code from deployable.lua. Search for ondeploy to find out where it is called. And then you will find the line I already posted above:
self.ondeploy(self.inst, pt, deployer, rot or 0)
self.inst is always the instance that has this component, so eg. a pinecone.
pt is a position, usually in this format {x=x,y=y,z=z} while y is the height wich is most often 0.
deployer is the instance that deployed it, usually a player, but you should add a security check in case something else is deploying. (eg "if deployer:HasTag("payer") ). And rot it the rotation of the placer.

Although your try above uses wrong parameters, the function still should have been called I think... (not sure if this is correct english :D) At least if you defined your addDeployCustomData function ABOVE your AddComponentPostInit. The code is executed from top to bottom, at least the code that does not belong to a class.

Edited by Serpens
Link to comment
Share on other sites

Thanks for the time and the detailed answer, but this still is weird to me since it doesn't work. The code inside the callback function is never ran. Here's a very basic example:
 

local function runMyFunction(inst, position, deployer, rotation)
    TheNet:Announce("This is never triggered")
    if original_ondeploy~=nil then
        return original_ondeploy(inst, position, deployer, rotation)
    end
end

and the call:

AddComponentPostInit("deployable",function(self)
    local original_ondeploy = self.ondeploy
    self.ondeploy = runMyFunction
end)

However, if i put it inside the above function, it is called ( but of course has no use ):

AddComponentPostInit("deployable",function(self)
    TheNet:Announce("This works when deploying anything")
end)

 

Edited by JackJohnsn
Link to comment
Share on other sites

ah sry, this will work:

AddComponentPostInit("deployable",function(self)
    self.inst:DoTaskInTime(0,function(inst) -- do it after everything was complete initialized
        local original_ondeploy = self.ondeploy
        local function runMyFunction(inst, position, deployer, rotation) -- must be inside of same function, otherwise it does not know local original_ondeploy
            GLOBAL.TheNet:Announce("This is never triggered")
            if original_ondeploy~=nil then
                return original_ondeploy(inst, position, deployer, rotation)
            end
        end
        self.ondeploy = runMyFunction
    end)
end)

1) AddComponentPostInit runs as soon as the content of deployable was initialized. But this is before the prefab (eg pinecone) is fully initialized.
This means the previous code is setting ondeploy, but the pinecone will overwrite it again shortly after. So we have to put it into a DoTaskInTime(0,...), this means it gets executed after everything, so also all prefabs, are finally initialized.
2) "local" variables and functions are not downwards accessable. So in your code your runMyFunction would not know "original_ondeploy". So we either have to put the defintion of the function within DoTaskInTime, like I did above, or have to define original_ondeploy elsewhere. But the solution in code above it the better one.

Edited by Serpens
Link to comment
Share on other sites

Thank you that worked beautifully. Although I guess I go with the AddPrefabPostInit since I only need it to affect 4 prefabs.

One final question. The onBuild method would pass a 2nd argument, prod, which I used to store the extra information on it. However, the ondeploy method is just passing an instance and some other arguments that are not usable since they are not tied to the deployed object ( they're either coordinates or player info ). So where do I store this extra information? The AddComponentPostInit doesn't even pass that 2nd arg.

Edit:

I've stored the extra data into the passed instance itself, and it worked so far. Gotta try your onSave functions now! Thanks again!

Edited by JackJohnsn
Link to comment
Share on other sites

AddComponentPostInit itself will only pass "self" wich is the component itself, nothing more. ondeploy may pass more, like seen above.
Where to store the information depends on where you need it. the "inst" of ondeploy is the pinecone I think, and this is removed shortly after.
Do you want it to be stored in the new plant? It really depends on where you need this information and why.

Link to comment
Share on other sites

Actually this works:

	local function addCustomData(inst, pt, deployer, rot)
  		inst.mydata = 'some data'
  	end

	AddPrefabPostInit(prefab, function(inst)
		inst.components.deployable.ondeploy = addCustomData
	end)

And I'm able to access it on the plant itself. e.g, if I plant a dug berry bush, and then interact with the planted bush, I can access those data. I'm trying to record the planted time for certain plants.

Link to comment
Share on other sites

24 minutes ago, JackJohnsn said:

Actually this works:


	local function addCustomData(inst, pt, deployer, rot)
  		inst.mydata = 'some data'
  	end

	AddPrefabPostInit(prefab, function(inst)
		inst.components.deployable.ondeploy = addCustomData
	end)

And I'm able to access it on the plant itself. e.g, if I plant a dug berry bush, and then interact with the planted bush, I can access those data. I'm trying to record the planted time for certain plants.

yes, but inst is the dug_bush. And this is removed as soon as it turns into a planted bush. So when you try plantedbush.mydata it will be nil, isnt it?
edit: if it works fine. Although I dont understand why :D

Edited by Serpens
Link to comment
Share on other sites

2 minutes ago, Serpens said:

yes, but inst is the dug_bush. And this is removed as soon as it turns into a planted bush. So when you try plantedbush.mydata it will be nil, isnt it?

Not really. When I interact with the planted bush ( without even digging it, let's say by harvesting or burning it ) I can access the data that I've stored earlier. Maybe the game is smart enough to transfer the data to the plant?

Edited by JackJohnsn
Link to comment
Share on other sites

17 minutes ago, Serpens said:

yes, but inst is the dug_bush. And this is removed as soon as it turns into a planted bush. So when you try plantedbush.mydata it will be nil, isnt it?

Hi, I didn't read most of the thread, but basically, when an inst gets Remove()d, the object gets invalidated (inst:IsValid() == false, removed from registered global Ents list, etc.) but keeps existing in memory (until all references to the object are removed, at which point the Lua garbage collector makes the memory available to new data). This means you can access removed inst as long as you have a direct reference to the object. The reason you shouldn't rely on accessing removed inst is: As soon as the server shuts down, the data is lost. So make sure to transfer the data to the planted bush and save/load it properly there!

Link to comment
Share on other sites

try to save the data, but in case it is really only the already removed inst you are accessing, also onsave wont help.

In this case you might take a look at the plantables.lua, tthere is written:

local function ondeploy(inst, pt, deployer)
        local tree = SpawnPrefab(data.name)
        if tree ~= nil then
            tree.Transform:SetPosition(pt:Get())
            inst.components.stackable:Get():Remove()
            if tree.components.pickable ~= nil then
                tree.components.pickable:OnTransplant()
            end
            if deployer ~= nil and deployer.SoundEmitter ~= nil then
                deployer.SoundEmitter:PlaySound("dontstarve/common/plant")
            end
        end
    end

so whenever a plant is planted, components.pickable:OnTransplant() is called. So maybe you can use this instead, it will only contain the just planted plant instance.
But this will only work for things that are pickable, like berrybushes and so on (pick the berries), just look if your prefab has "pickable" component.

Edited by Serpens
Link to comment
Share on other sites

On 10/12/2019 at 2:35 PM, Serpens said:

But this will only work for things that are pickable, like berrybushes and so on (pick the berries), just look if your prefab has "pickable" component.

That's nice, because the plantables list at the bottom of plantables.lua is "exactly" the list I'm looking for.

On 10/12/2019 at 1:26 PM, Mobbstar said:

So make sure to transfer the data to the planted bush and save/load it properly there!

Thanks for the note. Although, any clue how to access the newly planted bush? Any event listener I should use? Because on the line 16 of plantables.lua:

local tree = SpawnPrefab(data.name)

This is just spawning the bush, without the deployer or anything. I'm not sure how to connect a dug bush that has been just planted, to the plant itself.

Edited by JackJohnsn
Link to comment
Share on other sites

4 hours ago, JackJohnsn said:

Thanks for the note. Although, any clue how to access the newly planted bush? Any event listener I should use? Because on the line 16 of plantables.lua:

The easiest way I can come up with to do this is to AddPrefabPostInit for all the planted plants ("berrybush", etc.) and wrap their inst.components.pickable.ontransplantfn (make a new ontransplantfn that calls the old one)

Code example (freely written and thus untested)

function ontransplantfn_new(inst)
  --your code here
  if inst._ontransplantfn_old then
    inst._ontransplantfn_old(inst)
  end
  --your code here
end

function MyPostinitFn(inst)
  if inst.components.pickable then
    inst._ontransplantfn_old = inst.components.pickable.ontransplantfn
    inst.components.pickable.ontransplantfn = ontransplantfn_new
  end
end

AddPrefabPostInit("sapling", MyPostinitFn)
AddPrefabPostInit("grass", MyPostinitFn)
--etc.

The catch is that this won't work for any plantables that might be added in future updates if they aren't pickable.

Link to comment
Share on other sites

2 hours ago, Mobbstar said:

The catch is that this won't work for any plantables that might be added in future updates if they aren't pickable.

Thanks, that would exactly fit my needs since I only need to do this for pickable plants.

By the way, you only passed the inst to the ontransplantfn_new function. Does it take another argument? Like, the deployer? Because that's the important part.

Link to comment
Share on other sites

1 hour ago, JackJohnsn said:

Thanks, that would exactly fit my needs since I only need to do this for pickable plants.

By the way, you only passed the inst to the ontransplantfn_new function. Does it take another argument? Like, the deployer? Because that's the important part.

I already showed you the code from ontrasplant, see my post above. It only does:

tree.components.pickable:OnTransplant()

The ":" means that the calling object is given to the function, so only inst==tree. There is no information about the deployer in this case. Currently I have no idea how to get the newly planted inst AND the deployer.

Edited by Serpens
Link to comment
Share on other sites

7 hours ago, JackJohnsn said:

Thanks, that would exactly fit my needs since I only need to do this for pickable plants.

By the way, you only passed the inst to the ontransplantfn_new function. Does it take another argument? Like, the deployer? Because that's the important part.

For that, you would need to wrap the ondeploy function of the item, I think.

Link to comment
Share on other sites

maybe tell us what exactly you want to achieve. Maybe there is another way to get the same result. As far as I can tell it is very difficult to get deploayer and the new planted plant. 
You could use ondeploy+ontransplant and see if both get executed within short time and assume that they belong together. but this is quite dirty.
Or you can use upvaluehacker to hack into every single prefabs ondeploy and plant functtion, but this is not cleaner and still not gurantee for success.
Or you could check all players near the plant, when doing OnTransplant and if your char is around, save it.

But maybe you dont even need the planted+deployer because maybe there is another way to chieve the same result.

Link to comment
Share on other sites

1 hour ago, Serpens said:

maybe tell us what exactly you want to achieve. Maybe there is another way to get the same result. As far as I can tell it is very difficult to get deploayer and the new planted plant.

I'm just trying to save the deployer / builder's name and deploy/build time into the placed plants / buildings.

Here's an idea:

I could try to attach the data using the "dug" prefab, and since it last until server is shut down, I could then try and and use the "planted" prefab ( using AddPrefabPostInit ) to see if that prefab has the custom data on save. If it does, then save it.

Edited by JackJohnsn
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...