[Code] A Thread On Lambdas And Other Epsilons


simplex

Recommended Posts

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.

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

That is more along the lines of what I imagined, but it isn't necessary (and like you said, it's also more intensive than its worth.). This way, they have to keep their lights within reasonable ranges of one another for it to affect larger areas more efficiently. As long as they keep the lamps reasonably close to each other, they should all light up.

Link to comment
Share on other sites

@debugman18

Hooowever, a radius of 100 means 25 tiles. That's quite big, so those "big enough"'s I mentioned would have to be quite big indeed.

Though I think the radius should be reduced, that's way too big of a range. I don't think a lamp should be able to affect another one further than 2 tiles away (as a rough estimate).

Link to comment
Share on other sites

@debugman18

Hooowever, a radius of 100 means 25 tiles. That's quite big, so those "big enough"'s I mentioned would have to be quite big indeed.

Though I think the radius should be reduced, that's way too big of a range. I don't think a lamp should be able to affect another one further than 2 tiles away (as a rough estimate).

100 was for the purposes of testing. I think a maximum radius of 12 would be reasonable, although if it seems a bit much still, we can bring it down to 8.

Link to comment
Share on other sites

I explain here how the recipe system I implemented for brewing tea works, to ease its general use (sorry for the massive wall of text). It refers to the code in lib/brewing.lua. For the following, assume all code has been preceded by

local Brewing = modrequire 'lib.brewing'
The implementation of brewing.lua has a strong functional flavour: all the types can be used as functions (they can be called), and that's their primary use.

The final type defined in this module is RecipeBook, with the constructor

Brewing.RecipeBook(recipes)
where recipes is a table of Brewing.Recipe objects (to be explained shortly). That table doesn't need to have just numerical keys, all values in it will be taken. If book is a Brewing.RecipeBook object, you use it by calling it as

local chosen_recipe = book(ings, kettle, dude)
Where ings is a list (array, i.e. table with consecutive numerical keys) of the ingredient entities (the entities themselves, not their prefab names), kettle is the entity in which the recipe will be made and dude is the entity using it (kettle and dude are not used directly by anything in Brewing, they are just passed along to the conditions you specify when defining recipes, so in truth they could be anything and the system would still work, provided the recipe definitions match what they are). The return value, chosen_recipe, is (quite evidently) the recipe chosen based on those parameters. If there is no match, the return value will be nil, however in the case of tea brewing I included a recipe for Wet Goop which never fails, so for the particular case of the tea RecipeBook the return value will never be nil.

A Brewing.Recipe object is constructed as

Brewing.Recipe(name, condition, priority, brewing_time)
where name is the name of the recipe (the name of the product prefab, such as "sweet_blacktea"), condition is a Brewing.Condition object (to be explained shortly), priority is an optional number (defaulting to 0) and brewing_time is an optional number (defaulting to 1) indicating how long it takes to make that recipe. The priority defines which recipe gets chosen by the RecipeBook when more than one is valid: the one with the highest priority (is there's a draw, one gets randomly chosen). The brewing_time parameter is not used internally, it's just stored inside the recipe to be accessed externally. For tea brewing, I'm adopting the convention used by the crockpot, in which there's a base brewing time (defined in tuning/items.lua) which gets multiplied with the value stored in the recipe (that's why it defaults to 1), but that logic is applied by the Brewer component, not by the recipe system itself, that brewing_time parameter could be used to mean anything.

Internally (within the logic of Brewing.RecipeBook), a recipe R is used by calling it:

local product = R(ings, kettle, dude)
This will return the name of the recipe (the product prefab) if the recipe's condition is satisfied, and nil otherwise. Externally, however, you'll mostly use

R:GetProduct()  -- Equivalently, R:GetName(), or even tostring( R ).R:GetBrewingTime()  -- Equivalently, R:GetTime()R:GetCondition()  -- Returns the associated Brewing.Condition object.

Conditions:

The Brewing.Condition type is really the backbone of the implementation, but it will usually not be used directly. There's a class hierarchy descending from it, where the derived classes (most notably Brewing.Ingredient) are the most likely ones to be used. The Brewing.Condition type is the most flexible one, however, so it allows for greater flexibility when needed. The class hierarchy is (where A <- B means that B is derived from A):

Brewing.Condition <- Brewing.AtomicCondition <- Brewing.Ingredient
Conditions are mostly manipulated using arithmetical operators, such as '+', as explained below. Operators defined for a class (such as Brewing.Condition) remain valid for subclasses (such as Brewing.Ingredient).

The Brewing.Condition type:

It essentially wraps a predicate (a function returning true or false/nil) in a convenient way. It's constructed as

Brewing.Condition(fn)
where fn is a predicate called as fn(ings, kettle, dude), determining whether the condition will pass or fail. A condition C is used by calling it:

C(ings, kettle, dude)
which in turn just calls the fn associated with it, giving its return value.

The reason for wrapping predicates in Brewing.Condition objects is to manipulate them in arithmetical expressions. For the following, let A and B be Brewing.Condition objects. Then

C = A + B
gives a condition corresponding to A and B being simultaneously valid;

C = A / B
gives a condition corresponding to A or B (or both) being valid;

C = A ^ B
gives a condition corresponding to A or B (but not both) being valid;

C = -A
gives a condition corresponding to A not being valid.

Subtraction is defined in terms of addition and the unary minus: A - B is the same as A + (-B).

The order in which the operations are performed is the same as with numbers (so, in particular, '/' is done before '+'), and likewise affected by parentheses grouping.

This is how I'm building the condition for sweet tea: I just take the condition for regular tea and add a condition representing the presence of honey (and give the resulting recipe a higher priority).

The Brewing.AtomicCondition type:

This is just a convenience class, constructed as

Brewing.AtomicCondition(fn, count)
where fn is a predicate called as fn(single_ingredient, kettle, dude), in which single_ingredient is one element of (one entity in) the ingredient list, and count is an optional parameter (defaulting to 1) indicating the minimum amount of ingredients which should satisfy the fn for the condition to be considered valid. It just wraps that fn in a function applied to the whole ingredient list, and uses that to build the underlying Brewing.Condition object. An atomic condition A is called just like a regular condition:

A(ings, kettle, dude)
where as before ings is the whole ingredient list.

Note that adding two atomic conditions with the same fn does not mean getting a condition with their counts added, since addition means "and". You'll just get a condition that works like an atomic condition with a count equal to the maximum of the counts of the factors.

To obtain an atomic condition corresponding to an fn being satisfied by exactly n ingredients, you can write

A = Brewing.AtomicCondition(fn, n) - Brewing.AtomicCondition(fn, n + 1)
This will give you a condition corresponding to "fn must be satisfied by at least n ingredients and must not be satisfied by n + 1 or more ingredients".

In addition to the previous arithmetical operators, you can multiply an atomic condition by a number

B = 2*A
which gives you an atomic condition with the same fn as A, but with the count parameter of A multiplied by 2. So, in particular, instead of writing

A = Brewing.AtomicCondition(fn, n)
you can instead write

A = n*Brewing.AtomicCondition(fn)
An immediate use for atomic conditions would be checking for tags in ingredients. To obtain a condition corresponding to at least one ingredient having the tag "some_tag", you could write

A = Brewing.AtomicCondition(function(inst) return inst:HasTag("some_tag") end)

The Brewing.Ingredient type:

A Brewing.Ingredient object is constructed as

Brewing.Ingredient(prefabname, count)
which just builds the underlying Brewing.AtomicCondition object using as fn a function checking for the prefab of the ingredient being equal to prefabname. As in Brewing.AtomicCondition, the count parameter is optional, defaulting to 1 (and you can also multiply ingredients by a number). So, for example, the condition

Brewing.Ingredient("honey", 2)
checks for the presence of at least 2 honeys in the ingredient list. Like before, you can get a condition for an exact amount by

A = 2*Brewing.Ingredient("honey") - 3*Brewing.Ingredient("honey")

Pitfalls:

I chose '+' to mean "and" and '/' to mean "or" because that's an intuitive meaning (for the '/' case, it comes from it being seen as a bar separating alternatives), however many arithmetical identities fail. For example, if A is any condition then A - A is a condition that always fails, so, if B is a condition, B + A - A is also a condition that always fails, and thus it is not the same as B (unless B itself is a condition that always fails). Having the operator for "or" with higher precedence than the one for "and" also means more parentheses will usually be required than if it were the other way around.

Since a condition is just a wrapped function, I made functions automatically convert into conditions when used in operations with them, as well as when passed as the condition for a recipe (for example, I use Lambda.True, a function always returning true, as the condition parameter for the Wet Goop recipe, which gets converted into a proper Brewing.Condition). However, if using raw functions in expressions with conditions, care must be taken to prevent the operations from being applied to a pair of functions (or, in the case of the unary '-', to a single function). For example, if A is a condition and f and g are functions, A + f + g is valid, as well as f + A + g, but f + g + A is not, because then the two functions would be added to each other.

Final remarks:

In resources/brewing_recipebook.lua, where I define the Brewing.RecipeBook with the tea recipes, I wrote

BindModModule 'lib.brewing'
instead of

local Brewing = modrequire 'lib.brewing'
That line, just like BindTheMod(), makes the following code run "inside" the lib.brewing module, so instead of writing things like Brewing.AtomicCondition I can write just AtomicCondition. In particular, this means when I write Ingredient and Recipe I'm using Brewing.Ingredient and Brewing.Recipe, not the vanilla variables of the same name.

Link to comment
Share on other sites

@debugman18

I put the Assets and PrefabFiles tables in their own files (assets.lua and prefabfiles.lua, at the base mod directory).

Is there a reason why you're doing this in modmain?

GLOBAL.require "screens/popupdialog"GLOBAL.require "screens/newgamescreen"GLOBAL.require "widgets/statusdisplays"

EDIT:

And should we really keep a development branch? I don't see much point in having one until release 1.0 (maaaaybe in beta). The master branch is just dead, since all we do is development, we don't have a stable, reasonably complete code base to keep on the side.

Link to comment
Share on other sites

@debugman18

I put the Assets and PrefabFiles tables in their own files (assets.lua and prefabfiles.lua, at the base mod directory).

Is there a reason why you're doing this in modmain?

GLOBAL.require "screens/popupdialog"GLOBAL.require "screens/newgamescreen"GLOBAL.require "widgets/statusdisplays"

Not that I can tell. I think they may have been left behind from when I was implementing the main screen adjustments, though.

Link to comment
Share on other sites

Not that I can tell. I think they may have been left behind from when I was implementing the main screen adjustments, though.

Alright. I commented them out and the UI tweaks remained fully functional. So I erased them and pushed the changes.

And see my previous edit.

Link to comment
Share on other sites

Alright. I commented them out and the UI tweaks remained fully functional. So I erased them and pushed the changes.

And see my previous edit.

I suppose we don't need it, so I guess we should push it to master and save it for much later? You're right, at this point, the mod isn't stable enough to warrant separate branches. I maybe jumped the gun there, I guess.

Link to comment
Share on other sites

I suppose we don't need it, so I guess we should push it to master and save it for much later? You're right, at this point, the mod isn't stable enough to warrant separate branches. I maybe jumped the gun there, I guess.

Ok, so I merged development into master and deleted development (there's no point keeping a dead branch, when need arises we can create a dev branch).

I'm not sure what's your workflow with git under Windows and Mac, but if you're using the command line tool, to delete your local copy of the development branch type

$ git branch -D development
(but, of course, make sure there are no uncommited changes in it, in which case merge them into master first)

(my edit count in GitHub is terribly bloated because of doc, sorry)

Link to comment
Share on other sites

Ok, so I merged development into master and deleted development (there's no point keeping a dead branch, when need arises we can create a dev branch).

I'm not sure what's your workflow with git under Windows and Mac, but if you're using the command line tool, to delete your local copy of the development branch type

$ git branch -D development
(but, of course, make sure there are no uncommited changes in it, in which case merge them into master first)

(my edit count in GitHub is terribly bloated because of doc, sorry)

 

Git for Windows and Git for Mac both can open a shell, so that works fine. Either version is fairly flexible.

Link to comment
Share on other sites

Git for Windows and Git for Mac both can open a shell, so that works fine. Either version is fairly flexible.

That's good to know!

For the sake of reference, this is what I did to merge the branches and delete the development one (not having unpushed changes in my local development branch):

$ git checkout master  # change to master branch$ git pull origin development  # merge the remote development branch into it$ git push origin master  # push the merged branch$ git branch -D development  # delete local development branch$ git push origin --delete development  # delete remote development branch
p.s.: If I were merging the local development branch instead of the remote one, after checking out the master branch I'd write

$ git merge development
instead of the git pull.

p.s. 2: In a more general setting (unlike this one, where there were no divergent changes between the two branches, since development was strictly ahead of master), it'd have been better to sync the local development branch and then do a local merge, with git merge, instead of using that git pull. And of course, if we ever need to delete a remote branch again please check if the merge happened successfully before doing that :razz:.

Link to comment
Share on other sites

@debugman18

Testing the refiner recipes in the kettle? :razz:

To define a recipe book just for refining recipes*, just create a file resources/refining_recipes.lua with the contents

--[[-- List of refining recipes.--]]--@@ENVIRONMENT BOOTUPlocal _modname = assert( (assert(..., 'This file should be loaded through require.')):match('^[%a_][%w_%s]*') , 'Invalid path.' )module( ..., require(_modname .. '.booter') )--@@END ENVIRONMENT BOOTUP-- Syntax conveniences.BindModModule 'lib.brewing'return RecipeBook {	Recipe("rocks", Ingredient("cloud_coral_fragment"), 0),	Recipe("cutgrass", Ingredient("cloud_algae_fragment"), 0),	Recipe("rocks", Ingredient("beanstalk_chunk"), 0),}
That whole "squash" thing I'm doing at brewing_recipes.lua is just to first handle the recipes in some tables, manipulate them and then merge everything when feeding RecipeBook.

* Shouldn't refining be done in the crafting tab, with just proper alchemy recipes being done this way?

Link to comment
Share on other sites

@debugman18

Testing the refiner recipes in the kettle? :razz:

To define a recipe book just for refining recipes*, just create a file resources/refining_recipes.lua with the contents

--[[-- List of refining recipes.--]]--@@ENVIRONMENT BOOTUPlocal _modname = assert( (assert(..., 'This file should be loaded through require.')):match('^[%a_][%w_%s]*') , 'Invalid path.' )module( ..., require(_modname .. '.booter') )--@@END ENVIRONMENT BOOTUP-- Syntax conveniences.BindModModule 'lib.brewing'return RecipeBook {	Recipe("rocks", Ingredient("cloud_coral_fragment"), 0),	Recipe("cutgrass", Ingredient("cloud_algae_fragment"), 0),	Recipe("rocks", Ingredient("beanstalk_chunk"), 0),}
That whole "squash" thing I'm doing at brewing_recipes.lua is just to first handle the recipes in some tables, manipulate them and then merge everything when feeding RecipeBook.

* Shouldn't refining be done in the crafting tab, with just proper alchemy recipes being done this way?

 

I thought refining was designed to be done through the refiner to reduce crafting clutter? (Also, what do you mean through the kettle? Is it taking refiner ingredients?)

I have plans to expand refining further at some point (if we continue down the refining structure route), which is why I changed it to use the brewing component. The previous way I did refining was pretty simple, but it would've been messy to add more refinables that way.

Link to comment
Share on other sites

I thought refining was designed to be done through the refiner to reduce crafting clutter?

Sorry about that, the reason behind it had completely slipped me. Arctic does have a point, it's better this way.

Also, what do you mean through the kettle? Is it taking refiner ingredients?

No, because of its itemtest function, but that was the only impediment. By putting the refiner recipes inside the brewing recipe book (resources/brewing_recipebook.lua) they were being included as kettle recipes.

While I had written lib/brewing.lua to be very flexible, I hadn't thought of the Brewer component the same way. Though I suppose it is quite flexible and serves that purpose :razz:.

I had hardcoded the use of the brewing recipe book in the Brewer component through a local variable, but I changed that now. The recipe book to be used should now be set through inst.components.brewer:SetRecipeBook(book). I changed kettle.lua and refiner.lua to work that way.

I split brewing_recipebook.lua into three recipe book, brewing_recipebook.lua, refining_recipebook.lua and alchemy_recipebook.lua. Take a look at how they're organized.

I also removed that squash() function from brewing_recipebook.lua. Now, when passing recipes to the Brewing.RecipeBook constructor the "flattening" will be done automatically, in the following sense:

  • If the argument to the constructor is a single recipe, that recipe is added.
  • If the argument to the constructor is a table, this logic is applied recursively to each of its elements.
So you can pass it a single recipe, a table of recipes, a table of tables of recipes, a table mixing tables of recipes with single recipes, etc. Now the notation is much neater and shorter.
Link to comment
Share on other sites

@simplex

Do you have any idea why the black staff isn't charging things? If I set it to be a weapon, and point the attack function to black_activate(), it works. If I set the oncast function to the same thing, it doesn't function. The cast test seems to be working, since it only lets me target things that have the staticchargeable component. I'm stumped.

Link to comment
Share on other sites

@simplex

Do you have any idea why the black staff isn't charging things? If I set it to be a weapon, and point the attack function to black_activate(), it works. If I set the oncast function to the same thing, it doesn't function. The cast test seems to be working, since it only lets me target things that have the staticchargeable component. I'm stumped.

It's because the spell fn has a different signature: function(staff_inst, target_inst, target_pos).

I'll fix it. I have to extend the staticchargeable component anyway.

Link to comment
Share on other sites

@debugman18

Done. Now the StaticChargeable component has a method called HoldState(time), which makes the current state persist regardless of the environment (i.e., of static changes in the environment) for the given time.

So the black staff is charging the entity and holding its state for the duration specified by STAFF.BLACK.EFFECT_DURATION in tuning/items.lua.

Link to comment
Share on other sites

@debugman18

As I mentioned in General Development, I finished the packaging system. I'll explain here how it's organized and how to use it.

First of all, since rc.defaults.lua was getting filled with configuration values relevant to development, but not the end-user (and I added new ones), I split that part into dev.rc.defaults.lua, which is the (default) configuration file for development. It will only be loaded if present, since it should not be (and is not) included in the "official" mod packaging. Like with rc.defaults.lua, you can copy it to dev.rc.lua to make personal changes (and dev.rc.lua is in .gitignore, so it's not shared in the repo).

I implemented the idea I had discussed before, where the asset list would be gathered automatically, and other files and directories would be explicitly specified in a file. However, there was the issue that prefab assets are not placed in the Assets table, but in the prefab files themselves, and since those can interact with the game code in arbitrary ways it's not viable to run them outside of the game. So what I did was write an "asset compiler" which generates a file listing all mod (including prefab) assets when the game runs. So I'm not running assets.lua at all, since this approach is much more general, and takes care of things like wicker's AddTile including assets dynamically for tiles, not being present in the mod's Assets table. This is controlled by the following in dev.rc.defaults.lua:

-- Name of the file in which to put the list of mod assets. If nil,-- it is not generated.ASSET_COMPILER.OUTPUT_FILE = "asset_compilation.lua"
(asset_compilation.lua was added to .gitignore as well)

I hooked it as a MainScreen post construct. We could add a button which triggers it instead, but since generating that file is quite fast we may as well leave it as is. asset_compilation.lua looks like this:

return {	-- magnet	Asset("ANIM", "anim/void_placeholder.zip"),	-- flying_fish_pond	-- datura_petals	-- kettle	-- dragonblood_tree	-- jellyshroom_blue	Asset("ANIM", "anim/jelly shrooms.zip"),	Asset("ANIM", "anim/jelly caps.zip"),	-- skyflower	Asset("ANIM", "anim/skyflowers.zip"),	Asset("ANIM", "anim/datura.zip"),	-- marshmallow	Asset("ANIM", "anim/cloudcotton.zip"),	Asset("ATLAS", "images/inventoryimages/cloud_cotton.xml"),	--[[ More stuff below ]]--}
Each asset block is preceded by a comment listing the prefab which uses it (this is just to make it easier to read it, if necessary, the file is processed as Lua code, by running it, so the comments are entirely discarded). Core mod assets (those specified in the Assets table) are listed under the dummy prefab modbaseprefabs/MOD_UpAndAway (this is actually what the game does, it creates a dummy prefab of the same name which lists all mod prefabs in its assets table and all mod prefabs in its prefabs table). asset_compilation.lua will list each asset only once, so don't find it strange if some prefabs look out of assets (they have just been added already under a previous one).

So the first step in packaging the mod is launching the game (with the mod enabled) to generate/update asset_compilation.lua.

I renamed the tools/ folder in the repo, which consists of wicker-related scripts, to wicker_tools/, and added a tools/ folder with tools specific to Up and Away. Currently, it consists of the 2 scripts used for mod packaging and that Perl build renamer I wrote (kbr.pl).

Packaging is automated by the Makefile. Just enter

$ make dist
in a shell. Also, since "dist" is the default target of the Makefile, you can just type "make" instead of "make dist". Entering

$ make clean
will erase the zip archive. The name of the zip archive is specified in the Makefile. I set it up as

ZIPNAME=UpAndAway-$(MOD_VERSION).zip
where MOD_VERSION is determined by peeking into modinfo.lua (that's why the zip I posted in General is called UpAndAway-prealpha.zip).

The actual packaging consits of two parts: a Lua script (tools/pkgfilelist_gen.lua) which generates the list of files/directories to include and a Perl script (tools/pkg_archiver.pl) which reads the first script's output and generates the zip.

pkgfilelist_gen.lua is driven by a file specifying what to include in the archive and its structure, called pkginfo.lua (in the base mod directory). It is a part of the git repo, but not of the mod distribution. It is currently the following:

return {	--[[	-- Defines the name of the mod directory to be used inside the zip archive.	-- (it doesn't need to match the actual directory name being used)	--]]	moddir = "UpAndAway_test",	--[[	-- Names of the files returning asset tables.	--]]	asset_files = {		--[[		-- This file lists "regular" as well as prefab assets.		-- It is automatically generated when the game runs.		--]]		"asset_compilation.lua",	},	--[[	-- File extensions to never include.	--	-- Only the image extensions really have a reason to be listed, the others	-- shouldn't be included anyway.	--]]	exclude_extensions = {		"png",		"psd",		"xcf",		"svg",		"bin",		"scml",	},	--[[	-- Extra files/directories to include.	--]]	extra = {		"modinfo.lua",		"modmain.lua",		"modworldgenmain.lua",		"favicon",		"assets.lua",		"asset_utils.lua",		"prefabfiles.lua",		"credits.lua",		"LICENSE",		"NEWS.md",		"README.md",		"rc.defaults.lua",		"tuning.lua",		"tuning",		"scripts",	},	--[[	-- Directories to include as empty directories.	--	-- They are not required to exist (being placed anyway in the zip).	--]]	empty_directories = {		"log",	},}

That moddir entry is the name of the mod folder as represented inside the zip. I set it as UpAndAway_test for now to avoid it clashing with the git version of U&A when uncompressed, to make it easier to test if the package is really working. It should be changed to just "UpAndAway" soon(ish), though. Note that directories consisting of assets should not be included in the extra table, since they are automatically included by inspecting asset_compilation.lua.

Changes in the packaging of the mod should consist only of tweaking pkginfo.lua. Changing the scripts doing the actual work should not be necessary.

Link to comment
Share on other sites

@debugman18

I made a big structural change in wicker (in its framework part, its use as a library remains the same).

Now the mod code (the code that was in UpAndAway/scripts/upandaway) is in UpAndAway/code, and the wicker code is in UpAndAway/wicker. No more abusively long paths like scripts/upandaway/map/static_layouts/octocopter_wreckage.lua. Just like before, mod code is loaded with modrequire() and wicker code with wickerrequire(), so the actual mod code didn't have to change at all (I kept the library interface, only the inner workings changed).

Files no longer use wicker headers (such as "ENVIRONMENT BOOTUP"), they are automagically placed in the wicker environment without having to add anything. To access the global environment directly, like with the old "GLOBAL ENVIRONMENT BOOTUP", just write BindGlobal() at the top of the file. All mod files had their wicker headers erased (and the ones which used "GLOBAL ENVIRONMENT BOOTUP" had a BindGlobal() added).

There is no longer a "dummy filesystem" in scripts, all redirection to code/ is virtualized and automatic. Just write code directly inside code/, like you would in scripts/upandaway/, but without worrying about replicating it inside scripts. The only folder remaining in scripts/ is prefabs/, because it is not loaded by require(), but dummy versions of prefabs should no longer be put there manually. If a prefab file is missing there, it will be automatically generated when the mod starts (and the contents of scripts/prefabs/ are in .gitignore, since it's generated code, though it is of course included in the mod zip). These automatically generated prefab files look like (taking beanstalk.lua as an example):

local ret = require("upandaway.modrequire")("prefabs.beanstalk")if getmetatable(ret) == Prefab then	return retelse	return unpack(ret)end
"upandaway.modrequire" is a virtual package, there is nothing in scripts/upandaway. It just returns modrequire(), which is then used to load the prefab. The remaining code takes care of checking whether the prefab file in code/ is returning a single prefab or a table of prefabs, and acts accordingly. So no manual tweak is necessary, just return either a single prefab or a table of several prefabs inside the file in code/prefabs/ and the dummy prefab file will work.

To load code outside of scripts/, I'm using as a backend that use() function/object I wrote and posted in the forums not long ago, which acts just like require (the new wicker system is compatible both with it and plain require without changing its code).

Since the internal changes were quite massive, let me know if some horrible mod-breaking bugs pop up (though at least superficially all seems where it should be).

Link to comment
Share on other sites

@debugman18

I made a big structural change in wicker (in its framework part, its use as a library remains the same).

Now the mod code (the code that was in UpAndAway/scripts/upandaway) is in UpAndAway/code, and the wicker code is in UpAndAway/wicker. No more abusively long paths like scripts/upandaway/map/static_layouts/octocopter_wreckage.lua. Just like before, mod code is loaded with modrequire() and wicker code with wickerrequire(), so the actual mod code didn't have to change at all (I kept the library interface, only the inner workings changed).

Files no longer use wicker headers (such as "ENVIRONMENT BOOTUP"), they are automagically placed in the wicker environment without having to add anything. To access the global environment directly, like with the old "GLOBAL ENVIRONMENT BOOTUP", just write BindGlobal() at the top of the file. All mod files had their wicker headers erased (and the ones which used "GLOBAL ENVIRONMENT BOOTUP" had a BindGlobal() added).

There is no longer a "dummy filesystem" in scripts, all redirection to code/ is virtualized and automatic. Just write code directly inside code/, like you would in scripts/upandaway/, but without worrying about replicating it inside scripts. The only folder remaining in scripts/ is prefabs/, because it is not loaded by require(), but dummy versions of prefabs should no longer be put there manually. If a prefab file is missing there, it will be automatically generated when the mod starts (and the contents of scripts/prefabs/ are in .gitignore, since it's generated code, though it is of course included in the mod zip). These automatically generated prefab files look like (taking beanstalk.lua as an example):

local ret = require("upandaway.modrequire")("prefabs.beanstalk")if getmetatable(ret) == Prefab then	return retelse	return unpack(ret)end
"upandaway.modrequire" is a virtual package, there is nothing in scripts/upandaway. It just returns modrequire(), which is then used to load the prefab. The remaining code takes care of checking whether the prefab file in code/ is returning a single prefab or a table of prefabs, and acts accordingly. So no manual tweak is necessary, just return either a single prefab or a table of several prefabs inside the file in code/prefabs/ and the dummy prefab file will work.

To load code outside of scripts/, I'm using as a backend that use() function/object I wrote and posted in the forums not long ago, which acts just like require (the new wicker system is compatible both with it and plain require without changing its code).

Since the internal changes were quite massive, let me know if some horrible mod-breaking bugs pop up (though at least superficially all seems where it should be).

That's awesome; no more dummy files. :grin: I'll test the mod soon to see if it explodes.

 

Oh, the packaging system works well on my mac; I had to install Archive::Zip from cpan first, but it's all working properly now.

 

Edit: The black staff is causing a crash; it's something to do with the postinit() bit. It's pointing to lines 123 and 79 of "ua_staves" and says it's getting a nil value when doing arithmetic for the current uses.

 

scripts/components/finiteuses.lua:62: attempt to perform arithmetic on field 'current' (a nil value)LUA ERROR stack traceback:        scripts/components/finiteuses.lua(62,1) in function 'GetPercent'        scripts/components/finiteuses.lua(36,1) in function 'SetUses'        ../mods/UpAndAway/code/prefabs/ua_staves.lua(123,1) in function 'postinit'        ../mods/UpAndAway/code/prefabs/ua_staves.lua(79,1) in function 'fn'        scripts/mainfunctions.lua(123,1)        =[C] in function 'SpawnPrefab'        scripts/mainfunctions.lua(153,1)        =(tail call) ?        scripts/mainfunctions.lua(160,1) in function 'SpawnSaveRecord'        scripts/components/inventory.lua(118,1) in function 'OnLoad'        scripts/entityscript.lua(1161,1) in function 'SetPersistData'    ...        =[C] in function 'GetPersistentString'        scripts/saveindex.lua(66,1) in function 'Load'        scripts/gamelogic.lua(1098,1) in function 'callback'        scripts/playerprofile.lua(418,1) in function 'Set'        scripts/playerprofile.lua(315,1)        =[C] in function 'GetPersistentString'        scripts/playerprofile.lua(313,1) in function 'Load'        scripts/gamelogic.lua(1097,1) in main chunk        =[C] in function 'require'        scripts/mainfunctions.lua(637,1)scripts/frontend.lua(707,1) SCRIPT ERROR! Showing error screen    scripts/mainfunctions.lua(188,1) SpawnSaveRecord [nil, blackstaff] FAILED  
Link to comment
Share on other sites

Oh, the packaging system works well on my mac; I had to install Archive::Zip from cpan first, but it's all working properly now.

That's odd, I figured since Dana had it (without installing it), then it should be default. But good to know it's working. It should work under Windows too: using Cygwin would be the easiest way to do it, but it should work on "plain Windows" as well, it's just more cumbersome to use GNU Make/Perl/Lua that way.

 

Edit: The black staff is causing a crash; it's something to do with the postinit() bit. It's pointing to lines 123 and 79 of "ua_staves" and says it's getting a nil value when doing arithmetic for the current uses.

I did some minor testing, and I can safely say this is caused by a faulty fix of wicker's Configurable class (after Craig's discovery of a bug in it). I'll take care of it later today.

EDIT: Fixed it. Self-expanding tables are convenient for configuration files, but they are tricky: you can't check for the presence of a field by indexing it, because if not existent it will just be created. This was causing an issue deep inside wicker, since it was treating self-expanding tables as objects of a class (since it has a metatable and the field is_a after indexing it).

Link to comment
Share on other sites

@simplex

Something that is a roadblock of sorts is that U&A makes Don't Starve crash, if Reign of Giants is installed. I didn't notice anything useful in the log, but I could have easily missed it.

 

That's a heads up for our testers, as well; the mod currently does not work with the DLC.

 

I noticed that, and I'm meaning to look into it.

However, the way RoG is plugged into the game right now is quite messy (see this). I'd say that's likely the cause of the incompatibility, although I need to check to be sure. If so, I'd much rather have Klei clean up their approach instead of having to juggle two cases (with and without DLC) all the time due to the effects it has on prefab loading.

It seems the crash is gone. So I guess I was right about the cause.

However, the custom worldgen screen is not working and there's something off about the worldgen: prefab density in the cloud realm is really low. And I did run into a crash eventually (after successfully spawning and climbing a beanstalk, and then running around for a while):

Could not find anim [death] in bank [marble]scripts/stategraphs/commonstates.lua:548: attempt to index field 'lootdropper' (a nil value)LUA ERROR stack traceback:        scripts/stategraphs/commonstates.lua(548,1) in function 'onenter'        scripts/stategraph.lua(415,1) in function 'GoToState'        scripts/stategraphs/commonstates.lua(56,1) in function 'fn'         scripts/stategraph.lua(361,1) in function 'HandleEvents'        scripts/stategraph.lua(131,1) in function 'Update'        scripts/update.lua(121,1)scripts/frontend.lua(712,1) SCRIPT ERROR! Showing error screen
The line right above the error seems to indicate it was triggered from a prefab currently using the marble bank as a placeholder (of which there are many :razz:).
Link to comment
Share on other sites

 

It seems the crash is gone. So I guess I was right about the cause.

However, the custom worldgen screen is not working and there's something off about the worldgen: prefab density in the cloud realm is really low. And I did run into a crash eventually (after successfully spawning and climbing a beanstalk, and then running around for a while):

Could not find anim [death] in bank [marble]scripts/stategraphs/commonstates.lua:548: attempt to index field 'lootdropper' (a nil value)LUA ERROR stack traceback:        scripts/stategraphs/commonstates.lua(548,1) in function 'onenter'        scripts/stategraph.lua(415,1) in function 'GoToState'        scripts/stategraphs/commonstates.lua(56,1) in function 'fn'         scripts/stategraph.lua(361,1) in function 'HandleEvents'        scripts/stategraph.lua(131,1) in function 'Update'        scripts/update.lua(121,1)scripts/frontend.lua(712,1) SCRIPT ERROR! Showing error screen
The line right above the error seems to indicate it was triggered from a prefab currently using the marble bank as a placeholder (of which there are many :razz:).

 

Something worth noting is that cloud waves don't have giant missing spaces anymore. (Unless that was already fixed, but I recall it still being slightly buggy. Maybe not?)

 

What's odd about the worldgen screen is that the text displays fine, but our custom world anim doesn't. Maybe they changed the bank we're using?

 

As for the anim crash, here's a list from log.txt with things which are having issues with anims:

Could not find anim [idle_loop] in bank [scarecrow]Could not find anim [idle_side] in bank [sky_octopus]Could not find anim [berries_more] in bank [berrybush]Could not find anim [berries_more] in bank [berrybush]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [trinkets_3] in bank [trinkets]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]Could not find anim [idle_loop] in bank [marble]

The evil flower bank seems to be completely borked. Man, the DLC is like a cannonball right through the clouds. :razz:

 

I'll check it out. I also wonder why the marble bank is giving us issues now...

 

Edit: This is probably important. If I recall correctly, you had a pull request for the linux version of the mod tools? I'm on Debian, any chance you explain if there's anything I need to do to run those?

Link to comment
Share on other sites

What's odd about the worldgen screen is that the text displays fine, but our custom world anim doesn't. Maybe they changed the bank we're using?

The log claimed the worldgen screen asset was not found, so it looks like this is a very small and easy to fix problem (unless RoG and vanilla are now using different asset loading systems, preventing the mod from being incompatible with both, but let's hope not).

 

As for the anim crash, here's a list from log.txt with things which are having issues with anims:

--snip--

Yes, there are quite a few of those. But this is not an issue: before, if we attempted to play an animation incompatible with a bank, the prefab would simply go invisible with no output to the log, now it's just logged. It's a good thing. The reason for them is the incompatibility of a placeholder bank with the intended animations, which is to be expected, since it's a placeholder.

You'll see that in the log snippet I posted the crash itself had nothing to do with animations. It was an issue with the lootdropper component. I just included the line above that (claiming the missing "death" animation in the "marble" bank) because it shows that the prefab that died (hence triggering loot dropping) uses the marble bank, which helps to narrow it down a bit (though not by much, since marble is being heavily used as a placeholder).

 

The evil flower bank seems to be completely borked. Man, the DLC is like a cannonball right through the clouds. :razz:

Ohhh, so the prefab density isn't screwed, it's just that most flowers have gone invisible due to an incompatible bank, I guess. Again, this is not an issue, we can just use the old bank and build (renamed, of course) by bundling it with the mod.

 

I'll check it out. I also wonder why the marble bank is giving us issues now...

Again, ignore these marble warnings, they are not an issue.

 

Edit: This is probably important. If I recall correctly, you had a pull request for the linux version of the mod tools? I'm on Debian, any chance you explain if there's anything I need to do to run those?

You can clone the repo from here:

https://github.com/nsimplex/ds_mod_tools

Done that, go into the ds_mod_tools/src and run premake.sh, which will generate the build files inside ds_mod_tools/build. Then go into ds_mod_tools/build/proj and run

$ make
which should compile everything. Then it should be good to go, except that Python 2.7 and ktech are both requirements which are not currently bundled in, since I left packaging details for Klei to decide how they prefer doing it. You very likely already have Python 2.7 on Debian, but if not just run

$ sudo apt-get install python
(or use whatever UI you prefer, like synaptic). This will place the compiled files in ds_mod_tools/build/linux/mod_tools.

You can get ktech here

https://github.com/nsimplex/ktech

but you'll need the CMake branch. It has as dependencies libmagick++-dev and cmake:

$ sudo apt-get install libmagick++-dev cmake
To compile it, enter the ktech directory with a terminal and then type

$ cmake .
$ make
(note the "." after "cmake")

which should place the compiled ktech into the current directory. The mod tools currently assume ktech is in your PATH (again, as an open packaging detail, especially since I never got a confirmation from Klei on whether ktech would actually be used by the mod tools or not). The easiest way to accomplish that is to do a system wide installation:

$ sudo make install
though you could also place it anywhere and change the PATH environment variable accordingly, though that's more cumbersome and takes more work.

I wish this process were more streamlined, but the open packaging details complicate that (and the mod tools should be distributed precompiled via Steam, anyway). If you run into any problems, let me know.

You can use most tools directly from the output folder (ds_mod_tools/build/linux/mod_tools), but the autocompiler assumes the mod_tools folder is in the same directory as the DS installation folder (as is the case in Steam).

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.