[Code] A Thread On Lambdas And Other Epsilons


simplex

Recommended Posts

@simplex

Any ideas on making crops planted by Winnie grow in Winter?

 

I've looked at the related files, and it seems I need to counter MakeNotGrowableInWinter(). I'm not sure how to detect what is being planted at any given time. I suspect it has something to do with doer, however, since it involves ACTIONS.PLANT and plantables.lua.

 

MakeNotGrowableInWinter() detects the season and eventually does item.components.pickable:DoPause(), so even if I resume it, I'm assuming that would simply pause growing again.

What matters is the Crop component. The easiest way would be to change TUNING.MIN_CROP_GROW_TEMP when Winnie is being played (see Crop:DoGrow(dt) for details).

Link to comment
Share on other sites

What matters is the Crop component. The easiest way would be to change TUNING.MIN_CROP_GROW_TEMP when Winnie is being played (see Crop:DoGrow(dt) for details).

Well that was directed towards things like the berrybush, but that took care of farm plot crops. I'll see what I can do about them.

Link to comment
Share on other sites

Well that was directed towards things like the berrybush, but that took care of farm plot crops. I'll see what I can do about them.

Sorry, I thought we were just going to change farm plots. Changing things like berry bushes would have some strange implications as far as logic goes, since it wouldn't make much sense for a naturally spawning bush to have its behaviour changed (though that could be solved by checking if it was transplanted). I think changing MakeNoGrowInWinter would be the best way to do it, since its method of operation is quite hardcoded. This should do:

require "standardcomponents"local oldMakeNoGrowInWinter = _G.MakeNoGrowInWinterfunction _G.MakeNoGrowInWinter(inst)    -- We need to delay the actual work because spawning the player happens late.    inst:DoTaskInTime(0, function(inst)        if _G.GetPlayer().prefab ~= "winnie" then            return oldMakeNoGrowInWinter(inst)        end    end)end
I'm using _G instead of GLOBAL because that's the standard Lua way to reference the global environment (in the global environment, the standard variable _G just references the global environment itself) and Wicker aliases GLOBAL to it, not to mention it's shorter, so the code looks less verbose. I never quite liked the "GLOBAL" in the mod environment :razz:. but feel free to use GLOBAL if you prefer it.

EDIT:

To check for a player planted Pickable, the "if" should be

if _G.GetPlayer().prefab ~= "winnie" or not (inst.components.pickable and inst.components.pickable.transplanted) then
Link to comment
Share on other sites

@simplex

I'm using Heavenfall's tech tree code (there's a thread here about it) but I keep getting this error. I put the code into main.lua.

scripts/recipe.lua:25: attempt to index field 'level' (a number value)LUA ERROR stack traceback:        scripts/recipe.lua(25,1) in function '_ctor'        scripts/class.lua(28,1)        =(tail call) ?        ../mods/UpAndAway/scripts/upandaway/main.lua(398,1) in main chunk        =[C] in function 'require'        ../mods/UpAndAway/scripts/upandaway/wicker/api/core.lua(417,1) in function 'modrequire'        ../mods/UpAndAway/scripts/upandaway/wicker/api/themod.lua(273,1)        =(tail call) ?        ../mods/UpAndAway/scripts/upandaway/wicker/api/themod.lua(111,1) in function 'Run'        ../mods/UpAndAway/modmain.lua(296,1) in main chunk        =[C] in function 'xpcall'    ...        scripts/main.lua(254,1)        =[C] in function 'SetPersistentString'        scripts/modindex.lua(58,1)        =[C] in function 'GetPersistentString'        scripts/modindex.lua(45,1) in function 'BeginStartupSequence'        scripts/main.lua(253,1) in function 'callback'        scripts/modindex.lua(268,1)        =[C] in function 'GetPersistentString'        scripts/modindex.lua(248,1) in function 'Load'        scripts/main.lua(252,1) in main chunk
GLOBAL.TECH.NONE.FABLE = 0for k,v in pairs(GLOBAL.TUNING.PROTOTYPER_TREES) do    GLOBAL.TUNING.PROTOTYPER_TREES[k].FABLE = 0    GLOBAL.TUNING.PROTOTYPER_TREES.FABLE = {SCIENCE = 0,MAGIC = 0,ANCIENT = 0,FABLE=0}end    

Any idea what the issue is?

 

Edit: Nevermind, the problem is somewhere in the recipes list... I'll probably be able to solve it. :p

Link to comment
Share on other sites

@debugman18

If you attach your current main.lua code I can take a look.

The relevant section:

--This adds our 'Fable' tech tree. Thanks to @[member=Heavenfall] for the code.GLOBAL.TECH.NONE.FABLE = 0for k,v in pairs(GLOBAL.TUNING.PROTOTYPER_TREES) do    GLOBAL.TUNING.PROTOTYPER_TREES[k].FABLE = 0    GLOBAL.TUNING.PROTOTYPER_TREES.FABLE = {SCIENCE = 0,MAGIC = 0,ANCIENT = 0,FABLE=0}end    local function enteringradiusofskeleton(inst)  --entering proximity of skeletonendlocal function leavingradiusofskeleton(inst)  --left proximity of skeleton        local playervar = GLOBAL.GetPlayer()        if playervar then                playervar.components.builder.accessible_tech_trees = GLOBAL.TECH.NONE        endend-- add undeadskeleton prototype type to skeletonsfunctionlocal function addfableprototype(inst)        inst:AddTag("prototyper")        inst:AddComponent("prototyper")            inst.components.prototyper.trees = GLOBAL.TUNING.PROTOTYPER_TREES.FABLE            inst.components.prototyper.onactivate = function() end      inst.components.prototyper.onturnoff = leavingradiusofskeleton            inst.components.prototyper.onturnon = enteringradiusofskeletonend           --skeleton recipe was built        end        AddPrefabPostInit("research_lectern", addfableprototype)--]]local RECIPETABS = GLOBAL.RECIPETABSlocal TECH = GLOBAL.TECHlocal cotton_vest = Recipe("cotton_vest", { Ingredient("silk", 4), Ingredient("cloud_cotton", 4, "images/inventoryimages/cloud_cotton.xml") }, RECIPETABS.DRESS, TECH.SCIENCE_ONE)cotton_vest.atlas = "images/inventoryimages/cloud_cotton.xml"local cotton_hat = Recipe("cotton_hat", { Ingredient("silk", 2), Ingredient("cloud_cotton", 6, "images/inventoryimages/cloud_cotton.xml") }, RECIPETABS.DRESS, TECH.SCIENCE_ONE)cotton_hat.atlas = "images/inventoryimages/cloud_cotton.xml"local weather_machine = Recipe("weather_machine", { Ingredient("gears", 4), Ingredient("crystal_relic_fragment", 6, "images/inventoryimages/cloud_cotton.xml") }, RECIPETABS.SCIENCE, TECH.SCIENCE_ONE)weather_machine.atlas = "images/inventoryimages/cloud_cotton.xml"local research_lectern = Recipe("research_lectern", { Ingredient("silk", 2), Ingredient("cloud_cotton", 6, "images/inventoryimages/cloud_cotton.xml") }, RECIPETABS.SCIENCE, TECH.SCIENCE_TWO)research_lectern.atlas = "images/inventoryimages/cloud_cotton.xml"local cotton_candy = Recipe("cotton_candy", { Ingredient("cloud_cotton", 6, "images/inventoryimages/cloud_cotton.xml"), Ingredient("candy_fruit", 6, "images/inventoryimages/candy_fruit.xml") }, RECIPETABS.FOOD, TECH.SCIENCE_ONE)cotton_candy.atlas = "images/inventoryimages/cloud_cotton.xml"local grabber = Recipe("grabber", { Ingredient("magnet", 2, "images/inventoryimages/cloud_cotton.xml"), Ingredient("cane", 1), Ingredient("rubber", 4, "images/inventoryimages/cloud_cotton.xml") }, RECIPETABS.SURVIVAL, TECH.SCIENCE_ONE)grabber.atlas = "images/inventoryimages/cloud_cotton.xml"local magnet = Recipe("magnet", { Ingredient("gears", 2), Ingredient("crystal_fragment", 3, "images/inventoryimages/cloud_cotton.xml") }, RECIPETABS.FOOD, TECH.SCIENCE_ONE)magnet.atlas = "images/inventoryimages/cloud_cotton.xml"--]]

 

It gives me an error regarding updating the crafting tabs. I'm pretty sure it's something little.

Link to comment
Share on other sites

The current issue was that RECIPETABS.FOOD is actually called RECIPETABS.FARM.

However, that's not the same error you were getting before. That error was probably caused by the recipe level passed being nil (by an invalid index being used in TECH), since the game code in recipe.lua is actually bugged:

    self.level         = level or 0    self.level.ANCIENT = self.level.ANCIENT or 0    self.level.MAGIC   = self.level.MAGIC or 0    self.level.SCIENCE = self.level.SCIENCE or 0
As you can see, using 0 as the default value for self.level is wrong, since it'll just cause a crash in the following line (precisely where you were getting the crash) because it'll try to index a number. The correct default value for self.level would be {}, an empty table.

fixed_main.zip

Link to comment
Share on other sites

The current issue was that RECIPETABS.FOOD is actually called RECIPETABS.FARM.

However, that's not the same error you were getting before. That error was probably caused by the recipe level passed being nil (by an invalid index being used in TECH), since the game code in recipe.lua is actually bugged:

    self.level         = level or 0    self.level.ANCIENT = self.level.ANCIENT or 0    self.level.MAGIC   = self.level.MAGIC or 0    self.level.SCIENCE = self.level.SCIENCE or 0
As you can see, using 0 as the default value for self.level is wrong, since it'll just cause a crash in the following line (precisely where you were getting the crash) because it'll try to index a number. The correct default value for self.level would be {}, an empty table.

 

I knew it was something little. >_>

 

As for self.level, I'm not sure if that can be fixed through the mod. Maybe with AddClassPostConstruct, but that isn't certain, and who knows if that would cause issues with other mods. I'll experiment with it later if I get a chance.

Link to comment
Share on other sites

I knew it was something little. >_>

 

As for self.level, I'm not sure if that can be fixed through the mod. Maybe with AddClassPostConstruct, but that isn't certain, and who knows if that would cause issues with other mods. I'll experiment with it later if I get a chance.

It can be fixed by the mod, but not by a postconstruct. We'd need to implement a preconstruct by hand (which isn't hard).

But I don't see much point in doing it, because the level shouldn't be optional, in my opinion. If the modder wants to use TECH.NONE, he/she should specify it, otherwise silent bugs will start creeping into mods just because a mistyped TECH specification was returning nil and being treated as TECH.NONE.

EDIT: A proper "fix", in my opinion, would be doing something like:

assert( type(level) == "table", "Invalid recipe level specified. If you with to use TECH.NONE, do it explicitly." )
Link to comment
Share on other sites

@simplex

I pushed the jellyshroom textures, and a little bit of work on the crystal lamps.

 

Any idea why my code doesn't work consistently? Sometimes it does exactly what I wrote it for, and other times it's like, nope, not even going to try.

 

local function onLight(inst)    local partner = GLOBAL.GetClosestInstWithTag("crystal_lamp", inst, 100)    inst.Light:Enable(true)    if partner and not partner.components.machine.ison then        partner.components.machine.turnonfn = onLight        partner:DoTaskInTime(0.3, function(inst, data) partner.components.machine:TurnOn() end)            print "Partner lamp lit."    end    end

 

Basically, it finds the closest crystal lamp to itself, and lights it up. It then does the same thing. Sometimes it does do that, sometimes it just stops after one or two lamps.

Link to comment
Share on other sites

@debugman18

GetClosestInstWithTag is bugged. If you check it's definition in simutil.lua, you'll see it's not determining the closest entity at all, it's just returning some entity.

EDIT: Though the actual error cause is probably that you're getting back a lamp that's already been turned on, breaking the chain. So the way to do it would be to get the list of all lamps within the radius and determine the closest one, but excluding those that are already lit.

Link to comment
Share on other sites

@debugman18

GetClosestInstWithTag is bugged. If you check it's definition in simutil.lua, you'll see it's not determining the closest entity at all, it's just returning some entity.

If you put them in a line, it'll start with the closest one, and move further to the last.

 

That aside though, would using TheSim:FindEntities work better for this purpose? I like the appearance of it doing one at a time until the last one is lit.

Link to comment
Share on other sites

If you put them in a line, it'll start with the closest one, and move further to the last.

If it does, that's coincidental. Even if TheSim:FindEntities() returns entities in sorted order regarding distance, GetClosestInstWithTag iterates through it using pairs(), not ipairs(), so the order of the transversal is not guaranteed.

But anyway, see my previous edit for the probable actual cause of the issue you're having (though you still shouldn't use GetClosestInstWithTag* ;P).

* And it wouldn't work under this more general setting anyway. I just uploaded a mod doing something like this, you could copy-paste some of it, but it's not hard to do it by hand anyway.

Link to comment
Share on other sites

@debugman18

Actually, wicker has a function for that. Try this:

local game = wickerrequire "utils.game"local function onLight(inst)    local partner = game.FindClosestEntity(inst, 100, function(e)        return e.components.machine and not e.components.machine:IsOn()    end, {"crystal_lamp"})    inst.Light:Enable(true)    if partner and not partner.components.machine.ison then        partner.components.machine.turnonfn = onLight        partner:DoTaskInTime(0.3, function(inst, data) partner.components.machine:TurnOn() end)            print "Partner lamp lit."    end    end
EDIT:

All the wicker functions for finding entities (see wicker/utils/game.lua) receive a standard sequence of parameters:

center, radius, fn, tags
center is of course the center of the search, but it can be given either as an entity or as a Point (equivalently, as a Vector3). radius is the radius (duh). fn is an (optional, you may give nil) function applied to each entity, and only those for which it returns true are taken into account. tags is a (optional) table (array) of tags, like in TheSim:FindEntities().

Also, the wicker searching functions already exclude entities which are invalid (i.e., for which inst:IsValid() returns false) or that are not are "in limbo" (i.e., for which inst:IsInLimbo() returns true).

Link to comment
Share on other sites

@debugman18

Actually, wicker has a function for that. Try this:

local game = wickerrequire "utils.game"local function onLight(inst)    local partner = game.FindClosestEntity(inst, 100, function(e)        return e.components.machine and not e.components.machine:IsOn()    end, {"crystal_lamp"})    inst.Light:Enable(true)    if partner and not partner.components.machine.ison then        partner.components.machine.turnonfn = onLight        partner:DoTaskInTime(0.3, function(inst, data) partner.components.machine:TurnOn() end)            print "Partner lamp lit."    end    end
EDIT:

All the wicker functions for finding entities (see wicker/utils/game.lua) receive a standard sequence of parameters:

center, radius, fn, tags
center is of course the center of the search, but it can be given either as an entity or as a Point (equivalently, as a Vector3). radius is the radius (duh). fn is an (optional, you may give nil) function applied to each entity, and only those for which it returns true are taken into account. tags is a (optional) table (array) of tags, like in TheSim:FindEntities().

Also, the wicker searching functions already exclude entities which are invalid (i.e., for which inst:IsValid() returns false) or that are not are "in limbo" (i.e., for which inst:IsInLimbo() returns true).

 

Ah, I'm glad Wicker had the same functionality I was hoping for.

 

However, it's crashing for me with an arithmetic error.

scripts/util.lua:260: attempt to perform arithmetic on a nil valueLUA ERROR stack traceback:        scripts/util.lua(260,1) in function 'distsq'        ../mods/UpAndAway/scripts/upandaway/wicker/utils/game.lua(87,1) in function 'folder'        ../mods/UpAndAway/scripts/upandaway/wicker/paradigms/functional.lua(685,1) in function 'g'        ../mods/UpAndAway/scripts/upandaway/wicker/paradigms/functional.lua(106,1) in function 'p'        ../mods/UpAndAway/scripts/upandaway/wicker/paradigms/functional.lua(531,1)        =(tail call) ?        ../mods/UpAndAway/scripts/upandaway/wicker/paradigms/functional.lua(684,1) in function 'Fold'        ../mods/UpAndAway/scripts/upandaway/wicker/utils/game.lua(95,1) in function 'FindClosestEntity'        ../mods/UpAndAway/scripts/upandaway/prefabs/crystal_lamp.lua(14,1) in function 'turnonfn'        scripts/components/machine.lua(32,1) in function 'TurnOn'        scripts/components/machine.lua(20,1) in function 'OnLoad'    ...        =[C] in function 'GetPersistentString'        scripts/saveindex.lua(67,1) in function 'Load'        scripts/gamelogic.lua(1069,1) in function 'callback'        scripts/playerprofile.lua(378,1) in function 'Set'        scripts/playerprofile.lua(278,1)        =[C] in function 'GetPersistentString'        scripts/playerprofile.lua(276,1) in function 'Load'        scripts/gamelogic.lua(1068,1) in main chunk        =[C] in function 'require'        scripts/mainfunctions.lua(601,1) 
Link to comment
Share on other sites

@debugman18

Sorry about that, there was a bug in FindClosestEntity. I just fixed it, pushed the revision to the wicker repository, pulled the wicker repository into the U&A one and pushed it into GitHub.

:D

 

They still get confused if there's a bunch hobbled together, but that's not really an issue. It's working more consistently than the previous code did.

Link to comment
Share on other sites

:grin:

 

They still get confused if there's a bunch hobbled together, but that's not really an issue. It's working more consistently than the previous code did.

What do you mean by "confused"?

And oh, the previous code wasn't checking for the entity being different than the "center" entity. Use this instead:

local game = wickerrequire "utils.game"local function onLight(inst)    local partner = game.FindClosestEntity(inst, 100, function(e)        return e ~= inst and e.components.machine and not e.components.machine:IsOn()    end, {"crystal_lamp"})    inst.Light:Enable(true)    if partner and not partner.components.machine.ison then        partner.components.machine.turnonfn = onLight        partner:DoTaskInTime(0.3, function(inst, data) partner.components.machine:TurnOn() end)            print "Partner lamp lit."    end    end
EDIT:

Also, instead of using print(), consider using

TheMod:DebugSay "Partner lamp lit."
That way, if DEBUG is set to false in rc.lua nothing will be written. The message prefix in the log is also cleaner.

When a class (for example a component) inherits from Debuggable (wickerrequire "adjectives.debuggable") then you can use self:DebugSay() instead, which is better because it allows toggling debugging at a class level to avoid extra messages even when global debugging is on, but in a prefab case there's not much choice.

EDIT 2:

DebugSay() will also turn its arguments into strings (and concatenate them with nothing in between, so you can give it pieces), so you can pass the entity itself to help with debugging, for example:

TheMod:DebugSay("Partner lamp [", partner, "] lit.")
EDIT 3:

TheMod:Say() works the same way, except it always prints, regardless of debugging. Also, TheMod:Debug() returns true or false according to whether debugging is on or off. Any class inheriting from Debuggable has those methods.

Link to comment
Share on other sites

What do you mean by "confused"?

And oh, the previous code wasn't checking for the entity being different than the "center" entity. Use this instead:

local game = wickerrequire "utils.game"local function onLight(inst)    local partner = game.FindClosestEntity(inst, 100, function(e)        return e ~= inst and e.components.machine and not e.components.machine:IsOn()    end, {"crystal_lamp"})    inst.Light:Enable(true)    if partner and not partner.components.machine.ison then        partner.components.machine.turnonfn = onLight        partner:DoTaskInTime(0.3, function(inst, data) partner.components.machine:TurnOn() end)            print "Partner lamp lit."    end    end
EDIT:

Also, instead of using print(), consider using

TheMod:DebugSay "Partner lamp lit."
That way, if DEBUG is set to false in rc.lua nothing will be written. The message prefix in the log is also cleaner.

When a class (for example a component) inherits from Debuggable (wickerrequire "adjectives.debuggable") then you can use self:DebugSay() instead, which is better because it allows toggling debugging at a class level to avoid extra messages even when global debugging is on, but in a prefab case there's not much choice.

EDIT 2:

DebugSay() will also turn its arguments into strings (and concatenate them with nothing in between, so you can give it pieces), so you can pass the entity itself to help with debugging, for example:

TheMod:DebugSay("Partner lamp [", partner, "] lit.")
EDIT 3:

TheMod:Say() works the same way, except it always prints, regardless of debugging. Also, TheMod:Debug() returns true or false according to whether debugging is on or off. Any class inheriting from Debuggable has those methods.

 

By 'confused' I mean that in a large group they will not always light up all of the available lights.

 

That's fixed with that, though.

Link to comment
Share on other sites

By 'confused' I mean that in a large group they will not always light up all of the available lights.

 

That's fixed with that, though.

Hmm, well, by always getting the closest one it is possible for some lamps to not be lit up (the path of closest lamps could lead away from a group of nearby lamps).

We could light up all the close lamps, though, instead of just the closest one. The corresponding function in wicker is FindAllEntities (same parameters), returning a table.

Link to comment
Share on other sites

Hmm, well, by always getting the closest one it is possible for some lamps to not be lit up (the path of closest lamps could lead away from a group of nearby lamps).

We could light up all the close lamps, though, instead of just the closest one. The corresponding function in wicker is FindAllEntities (same parameters), returning a table.

Well, it lights them all up, given they're in the correct range. It works quite nicely, in my opinion.

 

iYCOkuT.gif

Link to comment
Share on other sites

Well, it lights them all up, given they're in the correct range. It works quite nicely, in my opinion.

 

iYCOkuT.gif

But if you arrange them in a (big enough) cross it won't work. It doesn't even need to be a cross, starting from the middle of a (big enough) line would do it, the cross pattern just ensures it won't work regardless of the starting point.

Link to comment
Share on other sites

It is possible to determine a sequence of which lamps to light up such that each "jump" happens within the specified radius, with all valid* lamps being covered. However, calculating such path is both slow and hard to implement.

* This depends on how you define "valid", though. ;P

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

Please be aware that the content of this thread may be outdated and no longer applicable.