Jump to content

[Help] How to override in-game function?


Recommended Posts

Hello everyone,

I'm creator of DST Food Overhaul mod and after the recent update that modified the way birds in the birdcage work, I need your help. Let me head straight to the point.

 

This is part of the code in the game I need to work with:

local function DigestFood(inst, food)    if food.components.edible.foodtype == FOODTYPE.MEAT then        --If the food is meat:            --Spawn an egg.        inst.components.lootdropper:SpawnLootPrefab("bird_egg")    else        local seed_name = string.lower(food.prefab .. "_seeds")        if Prefabs[seed_name] ~= nil then            --If the food has a relavent seed type:                --Spawn 1 or 2 of those seeds.            local num_seeds = math.random(2)            for k = 1, num_seeds do                inst.components.lootdropper:SpawnLootPrefab(seed_name)            end                --Spawn regular seeds on a 50% chance.            if math.random() < 0.5 then                inst.components.lootdropper:SpawnLootPrefab("seeds")            end        else            --Otherwise...                --Spawn a poop 1/3 times.            if math.random() < 0.33 then                local loot = inst.components.lootdropper:SpawnLootPrefab("guano")                loot.Transform:SetScale(.33, .33, .33)            end        end    end    --Refill bird stomach.    local bird = GetBird(inst)    if bird and bird:IsValid() and bird.components.perishable then        bird.components.perishable:SetPercent(1)    endend
local function ShouldAcceptItem(inst, item)    local seed_name = string.lower(item.prefab .. "_seeds")        --Item should be:    --Edible        --Seeds        --Meat    local can_accept = item.components.edible        and (Prefabs[seed_name]         or item.prefab == "seeds"        or item.components.edible.foodtype == FOODTYPE.MEAT)    --Item should NOT be:        --Monster Meat        --Eggs        --Bird Eggs        --Rotton Eggs    if table.contains(invalid_foods, item.prefab) then        can_accept = false    end    return can_acceptend 

And this is how I directly tweaked it so it would work the way I want:

local function DigestFood(inst, food)    if food.components.edible.foodtype == FOODTYPE.FRUIT or FOODTYPE.VEGGIE then        --If the food is fruit or veggie:            --Spawn an egg 1/3 times.		if math.random() < 0.33 then			inst.components.lootdropper:SpawnLootPrefab("bird_egg")		end    end	    local seed_name = string.lower(food.prefab .. "_seeds")    if Prefabs[seed_name] ~= nil then        --If the food has a relavent seed type:            --Spawn 2 or 3 of those seeds.        local num_seeds = math.random(2,3)        for k = 1, num_seeds do            inst.components.lootdropper:SpawnLootPrefab(seed_name)        end    else        --Otherwise...            --Spawn a poop 1/3 times.        if math.random() < 0.33 then            local loot = inst.components.lootdropper:SpawnLootPrefab("guano")            loot.Transform:SetScale(.33, .33, .33)        end    end    --Refill bird stomach.    local bird = GetBird(inst)    if bird and bird:IsValid() and bird.components.perishable then        bird.components.perishable:SetPercent(1)    endend
local function ShouldAcceptItem(inst, item)    local seed_name = string.lower(item.prefab .. "_seeds")        --Item should be:    --Edible        --Seeds        --Meat    local can_accept = item.components.edible        and (Prefabs[seed_name]         or item.prefab == "seeds")    --Item should NOT be:        --Monster Meat        --Eggs        --Bird Eggs        --Rotton Eggs    if table.contains(invalid_foods, item.prefab) then        can_accept = false    end    return can_acceptend

(What this should do is to make the bird refuse meats and drop an egg at 33% chance from any kind of food that it consumes.)

 

Now my question is - what is the easiest way to implement this tweak to my mod to with the same results, while preserving the future compatibility?

 

Please help me out, I'm not very good with this stuff on my own.

Thanks for your time, any kind of help is greatly appreciated!

Edited by IcyTheWhite
Link to comment
Share on other sites

What I would do in the modmain.lua

 

local function overridemystuffs()

 

    local function DigestFood(inst, food)

      do your stuffs

   end

 

   local function ShouldAcceptItem(inst, item)

      do your stuffs

   end

 

   Call here all the functions that call back these two local functions

 

end

 

And do an AddPrefabPostInit("birdcage", overridemystuff)

 
Edited by ZupaleX
Link to comment
Share on other sites

AddPrefabPostInit("birdcage",function(inst)    inst.components.trader:SetAcceptTest(ShouldAcceptItem)end)

​for example. So you override ShouldAcceptItem function. Your mod should have lower priority. Set priority = -1 in modinfo.lua

Link to comment
Share on other sites

AddPrefabPostInit("birdcage",function(inst)    inst.components.trader:SetAcceptTest(ShouldAcceptItem)end)
​for example. So you override ShouldAcceptItem function. Your mod should have lower priority. Set priority = -1 in modinfo.lua

What does the priority do? Load order?

Link to comment
Share on other sites

ZupaleX didn't get what I meant. I don't want to override part of my mod's code, but part of game's code.

 

Maris did, but I still don't understand how to execute it.

I mean I get what you're saying, I don't have to copy-past the whole function, only the parts I want to modify.

 

But could you please make a complete example of what would have to be in modmain.lua for it to work as a mod?

That would help me understand a whole lot more..

Link to comment
Share on other sites

I'm having a lot of trouble in this area. I'm trying to make my mod character use a custom attack state. My thought was to override the attack state via this code in my modmain.lua:

 

local attack = State(    {        name = "attack",        tags = { "attack", "notalking", "abouttoattack" },        onenter = function(inst)            if ThePlayer == wogmaw                my code            else                default code            end        end    })AddStategraphState("wilson", attack)

It's not working, however. I feel like this isn't the most mod-compatible way to do this, but I've tried a lot of things with no results.

 

What I can do is rename the state to something like "rangedattack" and in-game I can use the console command ThePlayer.sg:GoToState("rangedattack") and it works.

 

Really the one thing I need to change most is the attack speed (increase the cooldown variable) for game balance (can ranged stun-lock enemies otherwise). Could I impose a delay without the need to modify a state? I already have a function run when I miss an attack inst:ListenForEvent("onmissother", rangedattack) (which should actually be always since my melee attacks never hit, it'll be a spawned-in projectile).

Edited by StarmanUltra
Link to comment
Share on other sites

From what I am understanding from your post OP, you want to:

1) Hook a game function

2) Change the logic in the function to suit your mod

3) Be robust enough to not need to be updated if the original function is changed due to a game patch

 

As far as I know being able to do mid-function hooking and editing based on certain lines is a real hack job in LUA, and relies on the debugging library to pull off.

 

 

It'd be much easier to have a differencing program check for differences in your functions on updates and just update the files.

 

Though this won't help if you decide to not update your mod anymore, but people who use a particular mod that has a unique functionality tend to get forks when the original died.

Link to comment
Share on other sites

@CarlZalph

No, I doesn't actually have to be working for the next 10 upcoming updates. I'm trying to keep the mod active.

 

But what actually happened is that the code stopped working after they updated the birdcage.lua.

What I'm asking for is someone to help me write a working code that would do the same things I did in the 1st post, but as a mod and not a direct tweak to the game.

 

Also I want to see the simplest possible way to do this so I can learn from it and use the experience in future tweaks.

Edited by IcyTheWhite
Link to comment
Share on other sites

AddPrefabPostInit("birdcage", function(inst)	-- Host only needs the postinit, so, we put this	if not GLOBAL.TheWorld.ismastersim then		return	end	-- ShouldAcceptItem is a function hooked to the trader component	-- We can access the component, we can retrieve the function	local old_sai = inst.components.trader.test	-- Now, do I need to overwrite something very specific or not?	-- Can I work with the old result? Can I work with the old arguments?	-- Yes	local function MyAccept(inst, item)		local old_return = old_sai(inst, item)		-- Well, I can intercept the result and switch it if I don't like it		-- After all, I don't need any local variables inside the function		-- So if the item given is edible, and it's meat, we make it return false		-- Else, the old value		if item.components.edible and item.components.edible.foodtype == GLOBAL.FOODTYPE.MEAT then			return false		else			return old_return		end	end	-- Now we just have to set up the new function	inst.components.trader:SetAcceptTest(MyAccept)		-- The DigestFood function is tricky, it's a local function called by another function, OnGetItem	-- OnGetItem we do can access it, via the trader component	-- We would need to make a new DigestFood function, and a new OnGetItem, completely overwriting both	-- Doable, but maybe we can avoid it?	-- What's the difference between my new DigestFood and the old DigestFood?	-- I just edited the items that get accepted, so meat is off the table in the if sentence of the old function	-- Meaning that I can just keep the old behaviour and then make extra code to make it similar to my behaviour	-- I just want a random egg, 1 more possible special seed, and no normal seeds		-- First we will modify something to edit the old behaviour	local old_slp = inst.components.lootdropper.SpawnLootPrefab	inst.components.lootdropper.SpawnLootPrefab = function(self, lootprefab, pt)		if lootprefab == "seeds" then			return		end		return old_slp(self, lootprefab, pt)	end	-- Now, if the birdcage tries to spawn the "seeds" prefab, nothing will happen	-- Next, I retrieve the old OnGetItem	local old_ona = inst.components.trader.onaccept	-- This will generate one extra seed	-- This way, if 1-2 seeds are generated, then 2-3 will be generated	-- Also, random egg	local function ExtraDigest(inst, food)        local seed_name = string.lower(food.prefab .. "_seeds")        if GLOBAL.Prefabs[seed_name] then			inst.components.lootdropper:SpawnLootPrefab(seed_name)		end		if math.random() < 0.33 then			inst.components.lootdropper:SpawnLootPrefab("bird_egg")		end	end	-- We will combine two behaviours	local function MyGet(inst, giver, item)		-- The old one, this one will generate 1-2 special seeds		-- Remember, meat won't be accepted, and normal seeds can't be spawned		old_ona(inst, giver, item)		-- And now, something extra to make it similar to the behaviour we want		-- We use our test accept function to recheck		if inst.components.trader.test(inst, item) then			inst:DoTaskInTime(60 * GLOBAL.FRAMES, ExtraDigest, item)		end	end	inst.components.trader.onaccept = MyGetend)

Now with 33% more comment per comment.

Link to comment
Share on other sites

*facepalm*

After something like 4 months I came back to check up on my mod and to port it over to DS single player, just to find out that the birdcage code isn't working. I tried doing many fixes and tweaks to it, but nothing seemed to affect the game. What is this?

I was checking the mod menu over and over again making sure if it's truly turned on or whatnot, but there was one thing that didn't make sense to me.

Why can't I turn off any of the server mods?

 

Then after implementing your code into my mod still nothing actually happen. I was like - this can't be true. He knows what's he doing with the code, there has to be some other reason why it's not working. And then I realized - I'm sure server mods will be turned on or off on a different place. And once I carefully observed the "Host" menu, I finally found it.

 

Dammit Klei! Y do u do dis to me?

Turns out, that my old code was still working, overriding all the new functions that were implemented. Although that code was made by me, half of it I didn't understand.

 

Now about your post.. Wow. Just wow. You are the true MVP DarkXero!

Not only is the code perfectly working, but you also explained every single line in it.

I will make sure to go over it over and over again, until I finally understand how it works so I won't be so helpless next time. :)

Thank you!

 

(Also, I want to make a little tweak to the code, where there won't be an option of guano+seed dropping at once - either one or other, but that I want to try on my own with the knowledge you gave me :D)

Link to comment
Share on other sites

Why?

 

Edit:

@DarkXero - Well I went through the code and I must admit, you've used the cleanest way, without having any duplicate and pointless code. You've mixed your code with the games running code, but that's definitely not the easiest way. Although, it's that way it should've been done.

 

Although, I've learned quite a lot from all your explanations and finally understand at least a little how thing work in LUA. Thanks a lot once again. Cheers!

 

Edit 2:

Now my question is - is there a way to change:

if math.random() < 0.33 theninst.components.lootdropper:SpawnLootPrefab("bird_egg")end

...to make it so that if Guano drops, seeds won't drop at all, else there will be a 50% chance for the seed to drop? How can we get the information that the Guano dropped out of the game's function and add it to the condition?

Edited by IcyTheWhite
Link to comment
Share on other sites

@IcyTheWhite, that's a pretty intrusive change.

 

Here:

AddPrefabPostInit("birdcage", function(inst)	if not GLOBAL.TheWorld.ismastersim then		return	end	local old_sai = inst.components.trader.test	local function MyAccept(inst, item)		local old_return = old_sai(inst, item)		if item.components.edible and item.components.edible.foodtype == GLOBAL.FOODTYPE.MEAT then			return false		else			return old_return		end	end	inst.components.trader:SetAcceptTest(MyAccept)	local old_slp = inst.components.lootdropper.SpawnLootPrefab	local old_ona = inst.components.trader.onaccept		local function VoidDigest(inst, food)		-- With this, the birdcage will delete what it spawns... for now		inst.components.lootdropper.SpawnLootPrefab = function(self, lootprefab, pt)			-- We spawn the loot			local loot = old_slp(self, lootprefab, pt)			-- We make a delayed task so the birdcage can do what it wants with it			-- Like inst.Transform:SetScale of the guano loot			-- Then it deletes itself			self.inst:DoTaskInTime(0.1, function() loot:Remove() end)			return loot		end	end	local function ExtraDigest(inst, food)		-- We return the dropping functionality		inst.components.lootdropper.SpawnLootPrefab = old_slp				-- Now we do whatever we want		local foodtype = food.components.edible.foodtype		if foodtype == GLOBAL.FOODTYPE.FRUIT or foodtype == GLOBAL.FOODTYPE.VEGGIE then			-- If the food is fruit or veggie:            -- Spawn an egg 1/3 times.			if math.random() < 0.33 then				inst.components.lootdropper:SpawnLootPrefab("bird_egg")			end		end     		local seed_name = string.lower(food.prefab .. "_seeds")		if GLOBAL.Prefabs[seed_name] ~= nil then			-- If the food has a relevant seed type:			-- Spawn 2 or 3 of those seeds.			local num_seeds = math.random(2, 3)			for k = 1, num_seeds do				inst.components.lootdropper:SpawnLootPrefab(seed_name)			end		else			-- Otherwise...			-- Spawn a poop 1/2 times.			-- If no guano, spawn a seed 1/2 times.			if math.random() < 0.5 then				local loot = inst.components.lootdropper:SpawnLootPrefab("guano")				loot.Transform:SetScale(0.33, 0.33, 0.33)			else				if math.random() < 0.5 then					inst.components.lootdropper:SpawnLootPrefab("seeds")				end			end		end    end	local function MyGet(inst, giver, item)		old_ona(inst, giver, item)		if inst.components.trader.test(inst, item) then			inst:DoTaskInTime(55 * GLOBAL.FRAMES, VoidDigest, item)			-- 60 * GLOBAL.FRAMES Digest got voided by VoidDigest			inst:DoTaskInTime(65 * GLOBAL.FRAMES, ExtraDigest, item)		end	end	inst.components.trader.onaccept = MyGetend)
Link to comment
Share on other sites

So what you did with the function is that you've removed what it drops and made a new loot dropping algorithm?

 

Edit:

Hmm.. I'm not so sure about this solution.

Since you basically drop the loot, remove it and then drop a new one again.

(also the 0.1 delay makes the previous loot drop visible for a split second)

 

Can't we just block out the guano drop the same way you did block the regular seed drop?

Edited by IcyTheWhite
Link to comment
Share on other sites

@IcyTheWhite:

Yes, it pretty much overrides the old behaviour.

At least this makes you avoid copy pasting a lot of unnecessary code, which you would have to keep updated.

 

it's 0.1, it isn't really seen.

But if that is your concern, then for good mesasure, use this:

    local function VoidDigest(inst, food)        inst.components.lootdropper.SpawnLootPrefab = function(self, lootprefab, pt)            local loot = old_slp(self, lootprefab, pt)			loot:Hide()            self.inst:DoTaskInTime(0.1, function() loot:Remove() end)            return loot        end    end

We hide the loot, so it's invisible. Then it's deleted, no victims here.

 

I explicitly stated:

            -- We make a delayed task so the birdcage can do what it wants with it            -- Like inst.Transform:SetScale of the guano loot

because if I block it, then the game would crash for guano, trying to scale a nil entity.

Link to comment
Share on other sites

Thank you. You're the best! That should do it.

 

But I'm still curious.

I realized on my own that I can't just block it as the seed (tried it) and it did exactly what you said, trying to scale a nil value.

So I was thinking after, could we give it some fake value that would affect nothing in the game? Wonder if that's possible.

 

But there really isn't a way to get from the game the information whether the guano dropped or not and than act based on that?

Wouldn't that save a lot of troubles the whole function has to go through? I mean, it has to store the information somewhere. Or is it just local information?

Link to comment
Share on other sites

could we give it some fake value that would affect nothing in the game?

 

You can't generate a prefab from a "fake value". SpawnPrefab("oogabooga") returns nothing.

With no prefab, then the game would crash, there must be a prefab returned for the guano.

 

But there really isn't a way to get from the game the information whether the guano dropped or not
    inst.components.lootdropper.SpawnLootPrefab = function(self, lootprefab, pt)        if lootprefab == "guano" then			self.inst.guanodropped = true		end        return old_slp(self, lootprefab, pt)    end

If guano is spawned, then you set up a flag that will be checked in ExtraDigest.

 

than act based on that?

 

Then you change what you wanted, like:

if math.random() < 0.33 then	inst.components.lootdropper:SpawnLootPrefab("bird_egg")endif inst.guanodropped == true then	if math.random() < 0.5 then		inst.components.lootdropper:SpawnLootPrefab("seeds")	end	inst.guanodropped = nilend
Wouldn't that save a lot of troubles the whole function has to go through?

 

A lot of trouble would be saved by not doing anything at all.

 

I mean, it has to store the information somewhere.

 

Without editing the SpawnLootPrefab to save it, it doesn't.

 

Or is it just local information?

 

Not even in a local there, the chance is on the IF, and the SpawnLootPrefab is inside the IF, and that's that.

 

 

 

Final:

AddPrefabPostInit("birdcage", function(inst)    if not GLOBAL.TheWorld.ismastersim then        return    end    local old_sai = inst.components.trader.test     local function MyAccept(inst, item)        local old_return = old_sai(inst, item)        if item.components.edible and item.components.edible.foodtype == GLOBAL.FOODTYPE.MEAT then            return false        else            return old_return        end    end     inst.components.trader:SetAcceptTest(MyAccept)    local old_slp = inst.components.lootdropper.SpawnLootPrefab    inst.components.lootdropper.SpawnLootPrefab = function(self, lootprefab, pt)        if lootprefab == "seeds" then            return        end        if lootprefab == "guano" then			self.inst.guanodropped = true		end        return old_slp(self, lootprefab, pt)    end    local old_ona = inst.components.trader.onaccept    local function ExtraDigest(inst, food)        local seed_name = string.lower(food.prefab .. "_seeds")        if GLOBAL.Prefabs[seed_name] then            inst.components.lootdropper:SpawnLootPrefab(seed_name)        end        if math.random() < 0.33 then            inst.components.lootdropper:SpawnLootPrefab("bird_egg")        end		if inst.guanodropped == true then			if math.random() < 0.5 then				inst.components.lootdropper:SpawnLootPrefab("seeds")			end			inst.guanodropped = nil		end    end    local function MyGet(inst, giver, item)        old_ona(inst, giver, item)        if inst.components.trader.test(inst, item) then            inst:DoTaskInTime(63 * GLOBAL.FRAMES, ExtraDigest, item)        end    end    inst.components.trader.onaccept = MyGet end)
Link to comment
Share on other sites

Oh my, now I feel silly. All the time I was talking about an egg and not a seed. Ah. This happens to me sometimes that I say or write something different than I have on mind.

 

All along I was satisfied with the 1st code you gave me, with the only exception where there wouldn't be situation where guano + egg drops at once when fed regular seeds. Either one or the other.

 

- So the final result should have been 33% change guano, 33% chance egg or 33% nothing at all.

That's why I was constantly talking about the 50% egg drop chance IF the guano doesn't drop, which would results in the above percentage chance.

 

I'm so sorry..

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...