Jump to content

change onaccept function inside birdcage prefab


Recommended Posts

Hi,

i'm trying to make the game not crash when it calls my unmodified copy-pasted version of the its OnGetItem and DigestFood functions. This is the code:

local function DigestFood(inst, food)
--nothing changed
end

local function OnGetItem(inst, giver, item)
--nothing changed
end

AddPrefabPostInit("birdcage", function(inst) inst.components.trader.onaccept = OnGetItem end)

but this is the result:

Quote

[string "../mods/monster_meat_nerf/modmain.lua"]:96: attempt to index field 'trader' (a nil value)

I tried to print the content of the components table but nothing came out, so I'm clearly missing something. The ultimate goal of the mod is to make the bird die after eating too much monster meat in a single day.

This is my first time coding in lua and it shows I guess, help please?

Link to comment
Share on other sites

that line with trader you posted has no problem. I tested a modmain with only that code:

AddPrefabPostInit("birdcage", function(inst) inst.components.trader.onaccept = function() end end)

and it worked fine (since funciton is empty, nothing happend onaccept)

So you messed up the birdcage somewhere else in your code.
After you found the problem, you should save the old function, unless you want a completely different one.
So do sth like this:

AddPrefabPostInit("birdcage", function(inst)
    local old_onaccept = inst.components.trader.onaccept
    inst.components.trader.onaccept = function(inst, giver, item,...)
        if item.prefab=="monstermeat" then
            --kill bird
        elseif old_onaccept~=nil then
            return old_onaccept(inst,giver,item,...) -- call the old unchanged function otherwise. the "..." will make sure that this still works if the devs add a fourth param to the funciton.
        end
    end
end)

 

Edited by Serpens
Link to comment
Share on other sites

I tried to run your sample code

Spoiler

AddPrefabPostInit("birdcage", function(inst) inst.components.trader.onaccept = function() end end)

in a new mod folder but the game kept crashing anyway, so I made a clean install and everything started working again.

Thank you for the tips!

Edited by SakkeEU
Link to comment
Share on other sites

Hello again, I have a another couple of questions about birds and birdcages:

To make sure the bird dies after eating too much monster meat I added a new component to the birds prefabs:

local birds_prefabs = {"crow","robin","robin_winter","canary","quagmire_pigeon","puffin"}
for _,v in pairs(birds_prefabs) do 
	AddPrefabPostInit(v, function(inst) inst:AddComponent("weakstomach") end)
end

Everything works fine but to make sure that if a new bird is added the game doesn't crash I wrote this:

local function OnGetItem(inst, giver, item,...)
	local bird = inst.components.occupiable and inst.components.occupiable:GetOccupant()
	--add weakstomach to this bird prefab and this bird entity if its not defined
	if bird.components.weakstomach == nil then
		AddPrefabPostInit(bird.prefab, function(inst) inst:AddComponent("weakstomach") end) --	#1
		bird:AddComponent("weakstomach") -- #2
	end
	--check if monstermeat is eaten
	if item.prefab == "cookedmonstermeat" or item.prefab == "monstermeat_dried" then
		if bird.components.weakstomach:GetWS() > 0 then -- get weakstomach counter
			bird.components.weakstomach:DecWS() --decrese ws counter
		end
	end
	--if enough monstermeat is eaten, the bird dies
	if bird.components.weakstomach:GetWS() == 0 and (item.prefab == "cookedmonstermeat" or item.prefab == "monstermeat_dried") then
		BirdDeath(inst, bird)
	--return old function, the bird survives
	elseif old_onaccept~=nil then
		return old_onaccept(inst,giver,item,...)
	end
end

#1 should add the component to the new prefab while #2 adds it to the current entity.

#2 works fine but #1 doesn't, birds that are not present in the birds_prefabs always spawn without the custom component. It's not a big deal, the mod works but it doesn't feel right to leave it this way.

Another problem I found: if I keep feeding the bird while he's still "digesting" the first monstermeat it doesn't die and no loot is dropped, resulting in wasted monstermeats. I tried something like this:

local function OnGetItem(inst, giver, item,...)
	inst.components.trader:SetAbleToAcceptTest(function(...) return false end)
	inst.components.trader:Disable()
  	--
	--same same
  	--
	elseif old_onaccept~=nil then
		old_onaccept(inst,giver,item,...)
	end
	inst.components.trader:SetAbleToAcceptTest(nil)
	inst.components.trader:Enable()
end

But nothing changes.

Help again, please?

Edited by SakkeEU
Link to comment
Share on other sites

I did not yet look deeper ito the birds feeding mechanism, but AddPrefabPostInit should only be used at the very beginning of the game, so when the script is first executed. You dont use it inside a funciton like GetItem.
This is because AddPrefabPostInit, like the name suggests, changes a prefab, not an entity. So if you do AddPrefabPostInit in your modmain outside of any function, it will affect all entities that have this prefab, as soon as they spawn (so neither #1 nor #2 should be needed). It should be enough to add this component only once within AddPrefabPostInit and nowhere else. But I don't know exactly how the birdcage and the birds within interact.

Similar for your SetAbleToAcceptTest, you should define it elsewhere, eg in AddPRefabPostInit from the birdcage. And set it to a function that checks eg. a Tag or a value within bird or your component.

Link to comment
Share on other sites

13 minutes ago, Serpens said:

I did not yet look deeper ito the birds feeding mechanism, but AddPrefabPostInit should only be used at the very beginning of the game, so when the script is first executed. You dont use it inside a funciton like GetItem.
This is because AddPrefabPostInit, like the name suggests, changes a prefab, not an entity. So if you do AddPrefabPostInit in your modmain outside of any function, it will affect all entities that have this prefab, as soon as they spawn (so neither #1 nor #2 should be needed). It should be enough to add this component only once within AddPrefabPostInit and nowhere else. But I don't know exactly how the birdcage and the birds within interact.

The goal of #1 was to make the mod compatible with other mods that add new bird prefabs by adding the new component to the new prefabs once and for all, #2 works on the entity in the cage but it's kinda cheap. I really don't know how to check if there are custom bird prefabs without my component outside of any function.

 

39 minutes ago, Serpens said:

Similar for your SetAbleToAcceptTest, you should define it elsewhere, eg in AddPRefabPostInit from the birdcage. And set it to a function that checks eg. a Tag or a value within bird or your component.

The problem with SetAbleToAcceptTest is that it has priority over important stuff and needs to be set to nil again after the check is run (or that's how I read the following snippet of code), I don't know if I can do this from an AddPrefabPostInit. This is from trader.lua:

--This only comes into play after passing AbleToAccept,
--and does not trigger action fail or reason.
function Trader:SetAcceptTest(fn)
    self.test = fn
end

--This can be used to override AbleToAccept test to
--trigger custom action fail with reason.
function Trader:SetAbleToAcceptTest(fn)
    self.abletoaccepttest = fn
end

-- Able to accept refers to physical ability, i.e. am I in combat, or sleeping, or dead
function Trader:AbleToAccept(item, giver)
    if not self.enabled or item == nil then
        return false
    elseif self.abletoaccepttest ~= nil then
        return self.abletoaccepttest(self.inst, item, giver)
    elseif self.inst.components.health ~= nil and self.inst.components.health:IsDead() then
        return false, "DEAD"
    elseif self.inst.components.sleeper ~= nil and self.inst.components.sleeper:IsAsleep() then
        return false, "SLEEPING"
    elseif self.inst.sg ~= nil and self.inst.sg:HasStateTag("busy") then
        return false, "BUSY"
    end
    return true
end

 

Link to comment
Share on other sites

?

Use AddPrefabPostInitAny, which will run for every prefab. Then check inst:HasTag("bird") to add your component for all birds. This will also catch mod birds, if they have that tag (and I guess all of modded birds will have it).

I don't know the difference about self.test and self.abletoaccepttest , but use the one that suits your needs best. As always you can remember the old fn, if there was one. And of course you can set one of this test funtions to a function that returns false, if there is a bird and that bird has your component and according to your component has eaten too much meat. And return the old fn otherwise (if~=nil) or true.
There is no reason to change that function more than once, you only set it up and make sure that the function can react variable to the situation (by checking your component within the function)

Edited by Serpens
  • Thanks 1
Link to comment
Share on other sites

25 minutes ago, Serpens said:

Use AddPrefabPostInitAny, which will run for every prefab. Then check inst:HasTag("bird") to add your component for all birds. This will also catch mod birds, if they have that tag (and I guess all of modded birds will have it).

Oh I didn't know of this function, thank you very much!

 

27 minutes ago, Serpens said:

I don't know the difference about self.test and self.abletoaccepttest , but use the one that suits your needs best. As always you can remember the old fn, if there was one. And of course you can set one of this test funtions to a function that returns false, if there is a bird and that bird has your component and according to your component has eaten too much meat. And return the old fn otherwise (if~=nil) or true.
There is no reason to change that function more than once, you only set it up and make sure that the function can react variable to the situation (by checking your component within the function)

Ok, I'll see what I can do thank you!

Link to comment
Share on other sites

53 minutes ago, SakkeEU said:

Ok, I'll see what I can do thank you!

sry, my mistake. of course you are right that if the function self.abletoaccepttest is not nil, then the other stuff below, like sleeping/dead and so on is not checked. Without looking further into the code I would say this means you should use another function, maybe the self.test function instead, if this works better.

And about the birds:
You can also attach your component to the birdcage instead and count how often there was monstermeat. And reset this counter if the bird changed. This should be easier, although a user could exploit it with help of 2 birdcages and switching the birds all the time :D  But this is up to you.
 

Link to comment
Share on other sites

1 hour ago, Serpens said:

And about the birds:
You can also attach your component to the birdcage instead and count how often there was monstermeat. And reset this counter if the bird changed. This should be easier, although a user could exploit it with help of 2 birdcages and switching the birds all the time :D  But this is up to you.

I tried to make it this way before realizing how easy it was to exploit and changing route. It's actually very ugly to implement because the occupiable component of the cage is removed and readded when the bird dies, so it needs a lot of copy-pasting.

I'll make it work with the birds in one way or another! : )

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