IcyTheWhite Posted August 6, 2015 Share Posted August 6, 2015 (edited) 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) endendlocal 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) endendlocal 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 August 6, 2015 by IcyTheWhite Link to comment Share on other sites More sharing options...
ZupaleX Posted August 6, 2015 Share Posted August 6, 2015 Hello. Wouldn't a simple AddPrefabPostInit do the job? I am not sure I understand what is your actual problem. Link to comment Share on other sites More sharing options...
Maris Posted August 6, 2015 Share Posted August 6, 2015 These are just local functions. Override them at that place there they are actually used. Link to comment Share on other sites More sharing options...
ZupaleX Posted August 6, 2015 Share Posted August 6, 2015 (edited) 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 August 6, 2015 by ZupaleX Link to comment Share on other sites More sharing options...
Maris Posted August 6, 2015 Share Posted August 6, 2015 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 More sharing options...
Maris Posted August 6, 2015 Share Posted August 6, 2015 I just don't know how the mod is injected into the game. Link to comment Share on other sites More sharing options...
Cyde042 Posted August 6, 2015 Share Posted August 6, 2015 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.luaWhat does the priority do? Load order? Link to comment Share on other sites More sharing options...
IcyTheWhite Posted August 6, 2015 Author Share Posted August 6, 2015 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 More sharing options...
Kzisor Posted August 6, 2015 Share Posted August 6, 2015 @IcyTheWhite, Maris already provided you with that code in post #5 in this topic. Link to comment Share on other sites More sharing options...
Maris Posted August 7, 2015 Share Posted August 7, 2015 @IcyTheWhite, you should copy-past the whole function, change its parts, then override it the same way as other mod do. Link to comment Share on other sites More sharing options...
StarmanUltra Posted August 8, 2015 Share Posted August 8, 2015 (edited) 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 August 8, 2015 by StarmanUltra Link to comment Share on other sites More sharing options...
DarkXero Posted August 8, 2015 Share Posted August 8, 2015 if ThePlayer == wogmaw ThePlayer returns the entity of the guy playing the game.And wogmaw like that is a global variable which is, most likely, nil.if inst.prefab == "wogmaw" thenis what you want. Link to comment Share on other sites More sharing options...
CarlZalph Posted August 9, 2015 Share Posted August 9, 2015 From what I am understanding from your post OP, you want to:1) Hook a game function2) Change the logic in the function to suit your mod3) 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 More sharing options...
Maris Posted August 9, 2015 Share Posted August 9, 2015 It'd be much easier to have a differencing program check for differences in your functions on updates and just update the files. 4) Be compatible with other mods.So it's wrong way. Link to comment Share on other sites More sharing options...
IcyTheWhite Posted August 10, 2015 Author Share Posted August 10, 2015 (edited) @CarlZalphNo, 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 August 10, 2015 by IcyTheWhite Link to comment Share on other sites More sharing options...
DarkXero Posted August 11, 2015 Share Posted August 11, 2015 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 More sharing options...
IcyTheWhite Posted August 11, 2015 Author Share Posted August 11, 2015 *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 ) Link to comment Share on other sites More sharing options...
Maris Posted August 11, 2015 Share Posted August 11, 2015 @IcyTheWhite, try to delete Klei folder from Documents folder Link to comment Share on other sites More sharing options...
IcyTheWhite Posted August 11, 2015 Author Share Posted August 11, 2015 (edited) 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 August 11, 2015 by IcyTheWhite Link to comment Share on other sites More sharing options...
DarkXero Posted August 12, 2015 Share Posted August 12, 2015 @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 More sharing options...
IcyTheWhite Posted August 12, 2015 Author Share Posted August 12, 2015 (edited) 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 August 12, 2015 by IcyTheWhite Link to comment Share on other sites More sharing options...
DarkXero Posted August 12, 2015 Share Posted August 12, 2015 @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 endWe 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 lootbecause if I block it, then the game would crash for guano, trying to scale a nil entity. Link to comment Share on other sites More sharing options...
IcyTheWhite Posted August 12, 2015 Author Share Posted August 12, 2015 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 More sharing options...
DarkXero Posted August 12, 2015 Share Posted August 12, 2015 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) endIf 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 More sharing options...
IcyTheWhite Posted August 12, 2015 Author Share Posted August 12, 2015 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 More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now