UnderwearApp Posted March 6, 2014 Share Posted March 6, 2014 (edited) Hey all,I am trying to make a custom creature spawn from grass. I will use FIndEntities or iterate through ents (although I don't quite know how yet). My question is how do I set the function to only run once per game day for example?Also for a spawn function, would it be better placed in modmain or a component?It will basically work like this:Find grass tuftsLook for entities near tuftsIf none, spawn one entityendEDIT: Obviously I don't know what I am doing. ie:local function populate() if GLOBAL.GetSeasonManager() and not GLOBAL.GetSeasonManager():IsWinter() then --local pos = Vector3(0,0,0) local ents = GLOBAL.TheSim:FindEntities(0,0,0, 10000,"cutgrass") for k,v in pairs(ents) do local grasspos = v.Transform:GetWorldPosition() local found = FindEntities(grasspos.x, grasspos.y, grasspos.z, 200, {"grasshopper"}) if not found then local hopper = Global.SpawnPrefab("grasshopper") if hopper then hopper.Transform:SetPosition(grasspos.x, grasspos.y, grasspos.z) end end end else local ents = GLOBAL.TheSim:FindEntities(0, 0, 0, 10000 "grasshopper") for k,v in pairs(ents) do v:Remove() end endendAddSimPostInit(populate)which is crashing repeatedly attempting to index grasspos, which is a number.Thanks for any help in advance.Prof Edited March 6, 2014 by ProfFarnsworth Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/ Share on other sites More sharing options...
Heavenfall Posted March 6, 2014 Share Posted March 6, 2014 (edited) Instead of going through the Ents table, you can assign this function straight to the tuft of grass. function listenforday(inst) inst:ListenForEvent( "daycomplete", function(inst) -- now all you need is to search for x nearby inst and then run whatever functions you want end, GetWorld())endAddPrefabPostInit("grass", listenforday) Edited March 6, 2014 by Heavenfall Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425187 Share on other sites More sharing options...
UnderwearApp Posted March 6, 2014 Author Share Posted March 6, 2014 Instead of going through the Ents table, you can assign this function straight to the tuft of grass. function listenforday(inst) inst:ListenForEvent( "daycomplete", function(inst) -- now all you need is to search for x nearby inst and then run whatever functions you want end, GetWorld())endAddPrefabPostInit("grass", listenforday) Thanks @Heavenfall, just a couple of questions. Would "inst" end up being the grass by default due to the PostInit function? Also, what function does GetWorld() provide, do I not need to identify what I am looking for, or that ties in to the PostInit as well? I appreciate the help. Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425228 Share on other sites More sharing options...
Heavenfall Posted March 6, 2014 Share Posted March 6, 2014 (edited) yes, the inst in this case is each individual grass piece that is running the function inside the listenforevent. The GetWorld is a paramenter for the listenforevent function, saying the inst should listen for the "daycomplete" in the world instead of in itself (default for listenforevent). In other words, it is exactly the same as this local function mynewfunction(inst) -- now all you need is to search for x nearby inst and then run whatever functions you want end function listenforday(inst) inst:ListenForEvent( "daycomplete", mynewfunction(inst), GetWorld())endAddPrefabPostInit("grass", listenforday) Edited March 6, 2014 by Heavenfall Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425230 Share on other sites More sharing options...
UnderwearApp Posted March 6, 2014 Author Share Posted March 6, 2014 yes, the inst in this case is each individual grass piece that is running the function inside the listenforevent. The GetWorld is a paramenter for the listenforevent function, saying the inst should listen for the "daycomplete" in the world instead of in itself (default for listenforevent). In other words, it is exactly the same as this local function mynewfunction(inst) -- now all you need is to search for x nearby inst and then run whatever functions you want end function listenforday(inst) inst:ListenForEvent( "daycomplete", mynewfunction(inst), GetWorld())endAddPrefabPostInit("grass", listenforday)Alright, I gave that a try and it got past my initial stumbling point but returns this error: attempt to call global 'GetWorld' (a nil value). My modified code:function dayover(inst) inst:ListenForEvent("daycomplete", function(inst) if GLOBAL.GetSeasonManager() and not GLOBAL.GetSeasonManager():IsWinter() then local pos = inst.Transform:GetWorldPosition() local ents = GLOBAL.TheSim:FindEntities(pos.x, pos.y, pos.z, 200,"grasshopper") for k,v in pairs(ents) do if ents[0] == nil then local hopper = Global.SpawnPrefab("grasshopper") if hopper then hopper.Transform:SetPosition(pos.x, pos.y, pos.z) end end end else local ents = GLOBAL.TheSim:FindEntities(0, 0, 0, "grasshopper") for k,v in pairs(ents) do v:Remove() end end end, GetWorld())endAny idea why this would be nil? Thanks again. Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425243 Share on other sites More sharing options...
squeek Posted March 6, 2014 Share Posted March 6, 2014 (edited) Only certain things get put into the mod environment (the environment that the modmain.lua file gets run in). To access anything from the global environment that's not included in the mod environment, you'll have to pull from the GLOBAL table.So, it should be GLOBAL.GetWorld()Here's the list of things that get put into the mod environment (see mods.lua's function CreateEnvironment): TUNING=TUNING, modname = modname, pairs = pairs, ipairs = ipairs, print = print, math = math, table = table, type = type, string = string, tostring = tostring, Class = Class, GLOBAL = _G, MODROOT = "../mods/"..modname.."/", Prefab = Prefab, Asset = Asset, Ingredient = Ingredient, Edited March 6, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425250 Share on other sites More sharing options...
Heavenfall Posted March 6, 2014 Share Posted March 6, 2014 (edited) Also, there is a lot of grass in the game. If you find your game freezing for a second or two when a new day beckons, consider not running this calculation for EVERY piece of grass. For example you could have a 1 in 10 chance of running the check, or even lower. Edited March 6, 2014 by Heavenfall Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425256 Share on other sites More sharing options...
squeek Posted March 6, 2014 Share Posted March 6, 2014 (edited) You should probably take a look at components/lureplantspawner.lua and components/penguinspawner.lua. If you only want to spawn one grasshopper per day, it makes more sense to only listen for the event in the world prefab and have a component take care of the spawning than to listen for the event on each and every grass and only spawn one grasshopper in one event (also, your code won't work; pairs will never enter a loop on an empty table, you'd need to do that nil check outside the loop [and you can check the size of an array-like table by doing # tablename, so you could do if # ents == 0 then]).To do this, you'd want to do something like:-- "forest" is the overworld prefabAddPrefabPostInit("forest", function(inst) inst:AddComponent("grasshopperspawner")end)components/grasshopperspawner.lua:GrasshopperSpawner = Class(function(self, inst) self.inst = inst self.inst:ListenForEvent("daycomplete", self.SpawnGrasshopper)end)function GrasshopperSpawner:SpawnGrasshopper() if not GetSeasonManager():IsWinter() then local loc = self:FindSpawnLocation() if loc then local grasshopper = SpawnPrefab("grasshopper") grasshopper.Transform:SetPosition(loc.x, loc.y, loc.z) end endendreturn GrasshopperSpawner Edited March 6, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425270 Share on other sites More sharing options...
UnderwearApp Posted March 6, 2014 Author Share Posted March 6, 2014 I probably should have explained myself better since you two are way more proficient with this code than I am . Ideally, I would only have it run on the first day of spring and the first day of winter. I check for nearby grasshoppers because if there is one, I do not want it to spawn another (they will reproduce), and I want to kill them off in winter. Only when spring hits do I want it to spawn another grasshopper ONLY if there are not already some or even one in the vicinity. @Heavenfall - 1 in 10 would be fine as well as this is only going in so people do not need to start a new world to have them spawn. If I kill them off in winter, they will need to respawn in spring. Thanks to both of you. I will have a look at the reference material you provided. Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425275 Share on other sites More sharing options...
squeek Posted March 6, 2014 Share Posted March 6, 2014 (edited) GrasshopperSpawner = Class(function(self, inst) self.inst = inst self.has_spawned = false self.only_spawn_offscreen = true self.inst:ListenForEvent("seasonChange", OnSeasonChange) end) -- calling member functions of components directly from listeners doesn't work too well, so use a proxy local function OnSeasonChange(inst, data) inst.components.grasshopperspawner:OnSeasonChange(inst, data) end function GrasshopperSpawner:SpawnGrasshopper() if not self.has_spawned and not GetSeasonManager():IsWinter() then local loc = self:FindSpawnLocation() if loc then local grasshopper = SpawnPrefab("grasshopper") grasshopper.Transform:SetPosition(loc.x, loc.y, loc.z) self.has_spawned = true end end end function GrasshopperSpawner:FindSpawnLocation() local allvalidgrass = {} for guid,ent in pairs(Ents) do if ent.entity:IsValid() and ent.prefab == "grass" and (not self.only_spawn_offscreen or ent:IsAsleep()) then table.insert(allvalidgrass, ent) end end -- choose a random grass local spawngrass = GetRandomItem(allvalidgrass) return Vector3(spawngrass.Transform:GetWorldPosition()) end function GrasshopperSpawner:KillAllGrasshoppers() for guid,ent in pairs(Ents) do if ent.entity:IsValid() and ent.prefab == "grasshopper" then -- remove offscreen/unkillable grasshoppers immediately if ent:IsAsleep() or not ent.components.health then ent:Remove() -- kill onscreen grasshoppers else ent.components.health:Kill() end end end self.has_spawned = false end function GrasshopperSpawner:OnSeasonChange(inst, data) local season = data.season if season == SEASONS.WINTER then self:KillAllGrasshoppers() -- support both vanilla's 2 seasons and RoG's 4 seasons elseif (SEASONS.SPRING ~= nil and season == SEASONS.SPRING) or (SEASONS.SPRING == nil and season == SEASONS.SUMMER) then self:SpawnGrasshopper() end end function GrasshopperSpawner:OnSave() if self.has_spawned then return {has_spawned = self.has_spawned} end end function GrasshopperSpawner:OnLoad(data) if data then self.has_spawned = data.has_spawned end self:SpawnGrasshopper() end return GrasshopperSpawnerUntested code, but it should give you an idea. Also, for the KillAllGrasshoppers function, you could keep track of all grasshoppers that spawn by adding them to a list in the component and then just iterate through that instead of the global Ents table. However, it might get a tiny bit tricky because you'll have to make sure to keep that list up to date and persist it across saves. Edited March 6, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425299 Share on other sites More sharing options...
UnderwearApp Posted March 6, 2014 Author Share Posted March 6, 2014 LOL, it will take me longer to go through the code than it took you to write it. xD I assume "Ents" is a global variable which is why you do not use "FindEntities". If I create a component like this, could I just add it to the grasshopper prefab, or would it be better placed elsewhere, like "forest" in your post above? Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425308 Share on other sites More sharing options...
squeek Posted March 6, 2014 Share Posted March 6, 2014 (edited) LOL, it will take me longer to go through the code than it took you to write it. xD I assume "Ents" is a global variable which is why you do not use "FindEntities". If I create a component like this, could I just add it to the grasshopper prefab, or would it be better placed elsewhere, like "forest" in your post above?The component will only work when it's added to the world prefab (for the overworld, that would be "forest") because the "seasonChange" event will only get pushed from the world prefab, and the component assumes that self.inst is the world. I'm not sure I explained that right, but, basically, the component should only be added to the world prefab and it should be treated as a singleton (only one should exist at a time).For grasshoppers reproducing, you should use the "periodicspawner" component instead. Here's how it's used in beefalo reproduction (see prefabs/beefaloherd.lua): inst:AddComponent("periodicspawner") inst.components.periodicspawner:SetRandomTimes(TUNING.BEEFALO_MATING_SEASON_BABYDELAY, TUNING.BEEFALO_MATING_SEASON_BABYDELAY_VARIANCE) inst.components.periodicspawner:SetPrefab("babybeefalo") inst.components.periodicspawner:SetOnSpawnFn(OnSpawned) inst.components.periodicspawner:SetSpawnTestFn(CanSpawn) inst.components.periodicspawner:SetDensityInRange(20, 6) inst.components.periodicspawner:SetOnlySpawnOffscreen(true)The Ents table is used instead of TheSim:FindEntities for the reasons Heavenfall stated here (no unnecessary distance checks). Edited March 6, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425318 Share on other sites More sharing options...
seronis Posted March 7, 2014 Share Posted March 7, 2014 (edited) Suggestion: To avoid any chance of causing lag with a ton of grass objects in an arbitrarily large world I would probably do things in the following order. 1. only have the World object listen for season changes 2. in the callback that respond to a season change:2.a cycle through all entities to find grass objects2.b schedule a delayed task with a random time span covering the daylight hours of the first day to run on each grass object found 3. in that task handler:3.a do your 'find nearby grasshoppers check'3.b spawn a grasshopper if not found The reason for this slightly altered setup would be that running tons of 'find nearby' checks can be computationally expensive. You wouldnt want to risk doing too many of them in the exact same update tick. You can actually optimize it further by not cycling through the global entity list and instead maintain your own table of all grass instances. This would make the list navigation faster at the cost of slightly higher RAM requirement. I actually recommend NOT doing this though. The memory requirement, while not huge, would still be constant. And it would only be speeding up an action that occurs on 2 days out of a game year. Edited March 7, 2014 by seronis Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425437 Share on other sites More sharing options...
squeek Posted March 7, 2014 Share Posted March 7, 2014 (edited) seronis you might have missed a few posts. He actually wants to spawn a single grasshopper from a single (random) grass once per year. The code I posted does function very similarly to your suggested method though (only listen for season changes from the world prefab). Edited March 7, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425476 Share on other sites More sharing options...
seronis Posted March 7, 2014 Share Posted March 7, 2014 Even rereading it now I was understanding it as still wanting to check all grass but just not spawn a grasshopper if any are already 'in the vacinity' (i would assume that was limited to 1 screen distance, not the entire world). Which would make it so that a huge field of grass didnt spawn a field of grasshoppers without limiting it more than that. Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425619 Share on other sites More sharing options...
UnderwearApp Posted March 7, 2014 Author Share Posted March 7, 2014 @seronis - Thank you for the suggestions, it is a bit over my head right now apparently. you have the right idea though. (I just found "Oh poop", great mod, very original) @squeek - I tried your code above and am getting an unusual error (unusual to me anyway) components/grasshopperspawner.lua:13: function arguments expected near 'then' in this function:function GrasshopperSpawner:SpawnGrasshopper() if not self.has_spawned and not GetSeasonManager():IsWinter then --error here apparently local loc = self:FindSpawnLocation() if loc then local grasshopper = SpawnPrefab("grasshopper") grasshopper.Transform:SetPosition(loc.x, loc.y, loc.z) self.has_spawned = true end endendwhich is odd as I thought I had arguments on both sides of "then". I do appreciate all the help as this is my first time writing in Lua. Any ideas? Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425647 Share on other sites More sharing options...
squeek Posted March 7, 2014 Share Posted March 7, 2014 (edited) Any ideas? It's missing the parenthesis on the IsWinter function call. The line should be: if not self.has_spawned and not GetSeasonManager():IsWinter() then Edited March 7, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425656 Share on other sites More sharing options...
UnderwearApp Posted March 7, 2014 Author Share Posted March 7, 2014 (edited) Can't thank you enough squeek, but up rep is all I can do. Will let you know how it goes. EDIT: Ok, so I had to change the proxy OnSeasonChange from a local to a global function, otherwise it was crashing saying it was an undeclared variable, and it works beautifully. However, it only spawns one grasshopper in the whole world. I am going to try to add a loop around the body of SpawnGrasshopper to spawn more around the world. If you guys know a better way, please let me know. Edited March 7, 2014 by ProfFarnsworth Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425657 Share on other sites More sharing options...
squeek Posted March 7, 2014 Share Posted March 7, 2014 (edited) No problem. By the way, if you don't understand something or are unsure of how something works, make liberal use of the print function.For example, if you modify GrasshopperSpawner:FindSpawnLocation like so:function GrasshopperSpawner:FindSpawnLocation() print("Finding spawn location for Grasshopper...") local allvalidgrass = {} for guid,ent in pairs(Ents) do if ent.entity:IsValid() and ent.prefab == "grass" and (not self.only_spawn_offscreen or ent:IsAsleep()) then table.insert(allvalidgrass, ent) end end print("Found "..(# allvalidgrass).." valid grass entities to spawn from") -- choose a random grass local spawngrass = GetRandomItem(allvalidgrass) print("Chosen spawn: "..tostring(spawngrass)) return Vector3(spawngrass.Transform:GetWorldPosition())endit'll give you a better idea of how and when it's choosing spawn points. Just make sure to use tostring() often when appending variables to strings, because if a variable is ever nil/a boolean/etc it'll give you an error if you don't use tostring() on it.EDIT: Should probably mention just in case that you can bring up the console log (where that stuff will be printed to) by using Ctrl + L and you can bring up the console itself using the ~ keyEDIT#2: Oh, and take those prints out when you release your mod. Don't want to spam people's consoles with random information that's only useful for debugging. Edited March 7, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425668 Share on other sites More sharing options...
UnderwearApp Posted March 7, 2014 Author Share Posted March 7, 2014 (edited) Well, you guys rock. I would have given up and just told people to start a new world, and had grasshoppers jumping around all winter, as evidenced by the code in my OP. I had no idea this simple feature would be so involved, so huge thanks to each of you. @squeek - I have been using the console extremely liberally to start seasons, change time, etc. Your code showed me a ton of functions I didn't even know existed. I just had to move "self.has_spawned = true" outside the loop, and come spring, grasshoppers everywhere! Too funny to see them keel over on the first day of winter too. @seronis, @Heavenfall, @squeek: if you guys want co-author, just send me your steam ID. You're getting credit on the mod page anyway unless expressly forbidden. So happy to have this mod wrapped up (except for some animations getting cutoff, which I may or may not fix). This mod was made for me to learn about modding don't starve, and now I know so much more. So appreciative!! Edited March 7, 2014 by ProfFarnsworth Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425683 Share on other sites More sharing options...
squeek Posted March 7, 2014 Share Posted March 7, 2014 (edited) @seronis, @Heavenfall, @squeek: if you guys want co-author, just send me your steam ID. You're getting credit on the mod page anyway unless expressly forbidden.Not bothered about credit. Glad I could help. Edited March 7, 2014 by squeek Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425686 Share on other sites More sharing options...
Heavenfall Posted March 7, 2014 Share Posted March 7, 2014 Please keep me off the list, people have been getting nasty in PMs since I stopped updating my mods. Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425828 Share on other sites More sharing options...
UnderwearApp Posted March 7, 2014 Author Share Posted March 7, 2014 (edited) @Heavenfall Sure thing, I'll edit it now. I saw your post when you passed on your mods. Tell them they can pick it up if they want. Jerks Edited March 7, 2014 by ProfFarnsworth Link to comment https://forums.kleientertainment.com/forums/topic/32363-help-setting-frequency-of-a-function/#findComment-425933 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