sorcery  sorcery.md at [e76a15b19e]

File sorcery.md artifact 4ebea5ce76 part of check-in e76a15b19e


# sorcery

`sorcery` is a wide-ranging mod that adds many features, devices, powers, and mechanics to the minetest default game. it aims to add a distinctive cosmology, or world-system, to the game, based around mese, alchemy, spellcasting, and worship. it is intended to be pro-social, lore-flexible, and to interoperate well with other mods wherever possible.

# dependencies

## first-party
 * **default**
 * **stairs** for slabs, used in crafting recipes
 * **screwdriver** for several crafting recipes
 * **vessels** for potions, ink bottles, etc.
 * **tnt** for the flamebolt spell impact effect

## third-party
 * **xdecor** for various tools and ingredients, especially honey and the hammer
 * **basic_materials** for crafting ingredients
 * **instant_ores** for ore generation. temporary, will be removed and replaced with home-grown mechanism soon
 * **farming redo** for potion ingredients
 * **late** for spell, potion, and gravitator effects
   * **note**: in order for the gravitator to work, the late condition interval must be lowered from its default of 1.0 to 0.1. this currently can only be done by altering a variable at the top of `late/conditions.lua`, though a note in the source suggests a configuration option will be added eventually. hopefully this is so.

# interoperability
sorcery has special functionality to ensure it can cooperate with various other modules, although they are not necessarily required for it to function.

## xdecor
by default, `sorcery` disables the xdecor enchanter, since `sorcery` offers its own, much more sophisticated enchantment mechanism. however, the two can coexist if you really want; a configuration flag can be used to prevent `sorcery` disabling the xdecor enchanter.

## hopper
many `sorcery` devices support interactions with the hopper. devices that produce outputs after a period of time like the infuser will automatically inject their output into a hopper if one is placed below it, and if the device has multiple slots, they can usually be accessed by configuring a hopper to insert from the side or from above.

## others
* if `moreores` is in use, `sorcery` will use its silver rather than creating its own. mithril is not used by `sorcery`.
* `sorcery` will use `new_campfire`'s ash if it's available, otherwise creating its own.

# lore
`sorcery` supplies a default system of lore (that is, the arbitrary objects that the basic principles of the setting operate over) but this can be augmented or replaced on a per-world basis. for instance, you can substitute your own gods for the Harvest Goddess, Blood God, change their names, and so on, or simply make your own additions to the pantheon. since lore overrides are stored outside the minetest tree, it can be updated without destroying your changes.

lore is stored separately from the rest of the game logic, in the 'data' directory of the `sorcery` mod. it is arranged in a hierarchy of thematically-organized tables. for instance, the table of gods can be found in data/gods.lua. ideally, lore tables should contain plain data, though they can also contain lambdas if necessary. lore files are evaluated in the second stage of the init process, after library code has been loaded but before any game logic has been instantiated. lore can thus depend on libraries where reasonable (though e.g. colors should be stored as 3-tuples rather than `sorcery.lib.color` objects, and images as texture strings, unless there is a very good reason to do otherwise).

lore files should never depend on functions in the `minetest` or `core` namespace! if you really need such functionality, gate it off with an if statement and be sure to return something useful in the event that the namespace isn't defined. *lore is just data:* as a general principle, non-minetest code should be able to evaluate and make use of the lore files, for instance to produce an HTML file tabulating the various potions and how to create them. lore should also not mutate the global environment: while there is currently nothing preventing it from doing so, steps will likely be taken in the future to give each lore module a clean environment to keep it from contaminating the global namespace. right now, all functions and variables declared in a lore file should be defined `local`. the only job of a lore file is to return a table.

`sorcery` looks in a directory called `sorcery` in the world-data directory for world-specific lore and other overrides. below are three example lore modifications to give you a sense for how this system works.

## replacing the gods
this is probably the most common action you'll want to take -- if your world has its own defined setting and already has its own gods, or you just want to give it a bit of a local flavor, you probably won't want the default pantheon that `sorcery` comes with.

to do this, we'll need to create a file called `$world/sorcery/lore-gods.lua` where $world is the world's root data directory, e.g. `/srv/mt/geographica`

```
-- /srv/mt/geographica/sorcery/lore-gods.lua

return {
	fire = {
		name = 'Aduram the Scorching';

		-- personality
		laziness = 3; -- controls how frequently the god takes action upon the mortal world
		stinginess = 5; -- the higher the stinginess value, the less frequently the god will give gifts to worshippers
		generosity = 7; -- likeliness to overlook disfavored behavior and give items of high value
		color = {255,0,0};

		idol = {
			desc = 'Fiery Idol';
			width = 0.5, height = 1;
			tex = { ‹list of textures› };
		};
		sacrifice = {
			['tnt:gunpowder'] = 2; -- players can leave gunpowder on his altars to gain 2 points of favor
		};
		gifts = {
			['fire:flint'] = {30, 3}; -- one-in-three likelihood of gifting flint at idols that have reached 30 favor
		};
		consecrate = {
			['default:steel_ingot'] = {5, 'fire:flint_and_steel'} -- transform steel ingots on his altar into firestarter at a cost of 5 favor
		};
		bless = {
			potions = {
				Force = {favor=70, cost=5, chance=15} -- 1-in-15 chance of applying the Elixir of Force effect to a potion on his altar once the attached idol has reached 30 favor, at a cost of 5 favor
			};
			tools = {
				speed = {favor=120, cost=25, chance=5} -- apply speed enchantment
			};
		}
	};
	water = {
		name = 'Uskaluth the Drenched';
		‹···›
	};
}
```

when you define gods, be mindful of the personality you wish to create and worship practices you wish to provoke. how attentive must their worshippers be to maintain a useful relationship? how often are sacrifices required? how many sacrifices are necessary before the god will start performing miracles? it's easy to make gods completely overpowered gamebreakers, so be cautious, and don't gift anything terribly valuable -- or if you do, give it very rarely.

note that you would also need to supply idol models called `sorcery-idol-fire.obj` and `sorcery-idol-water.obj`. gods with the ids `harvest` or `blood` will use predefined idols.

## adding gods
if you're happy with the default pantheon, but your spiritual life just isn't complete without the fiery rage of Aduram, you can change the name of `sorcery/lore-gods.lua` to `sorcery/lore-gods-extra.lua`. this way, the table it returns will be merged preferentially into the table of default gods.

## modifying gods
more complex operations can also be performed. let's say the Blood God is ruining your vibe and you want to delete him (her? it?) from the pantheon. or the Harvest Goddess needs a name more in line with your local cosmology. these are both easy:

```
-- /srv/mt/geographica/sorcery/lore-gods.lua
local gods = ...
gods.harvest.name = 'Disastra bel-Malphegor'
gods.blood = nil
return gods
```

this has the handy property that any changes made upstream to the Harvest Goddess will be respected if you update the mod, without overriding the changes made in lore-gods.lua.

or, if you want to extract a specific god from the default pantheon and include it with your own:

```
-- /srv/mt/geographica/sorcery/lore-gods.lua
local gods = ...
local mygods = dofile('pantheon.lua')
mygods.harvest = gods.harvest
mygods.harvest.name = 'Disastra bel-Malphegor'
return mygods
```

## compatibility tables
if you use a mod that neither supports `sorcery` nor is supported by it, the proper solution is of course to open a ticket in the former's bug tracker, but as a quick fix you can also tweak the `sorcery` compatibility tables with a `$world/sorcery/lore-compat.lua` file.

```
-- /srv/mt/geographica/sorcery/lore-compat.lua
return sorcery.lib.tbl.deepmerge((...), {
	grindables = {
		['bonemeal:bone'] = {
			powder = 'bonemeal:bonemeal';
			hardness = 3;
			value = 3;
			grindcost = 1;
		};
	};
})
```

note that we have to use deepmerge instead of naming the file `lore-compat-extra.lua` and returning a simple table because the init routine only performs a shallow merge, meaning that it would wipe out the entire provided version of `sorcery.data.compat.grindables`! no bueno.

## local tweaks
if you need to change `sorcery`'s behavior in a way that isn't possible through modifying the lore, you can create a file `$world/sorcery/finalize.lua` which will be run at the end of the init process and which will have access to all of the machinery created by the various modules of the mod. `worldbuilding.lua` is like `finalize.lua` but it is run before any lore is loaded. finally, `bootstrap.lua` will be run before anything else, including library code, is loaded, but after the core `sorcery` structure and its loading functions have been created.

in the unlikely event that the lore-loading process itself is incompatible with the changes you're trying to make, you can create a `loadlore.lua` file that will be run instead of the usual routine. you'll then be responsible for loading all of the game's lore; the default lore will not even be read! this will be most useful if you're loading or generating your own lore from an unusual source, such as somewhere on the network.

if you want to write a lore loader but include some of the default lore, you can use the loading function passed to `loadlore.lua`:

```
-- /srv/mt/geographica/sorcery/loadlore.lua
local load_lore, load_module = ...
sorcery.data = dofile('load-remote-lore.lua')
load_lore {'enchants', 'spells'}
```

as you can see here, once the lore is loaded, it is stored in the variable `data`. there is a subtle distinction you need to bear in mind: `data` represents the full set of lore-derived information `sorcery` has to work with, which is not necessarily the same as the lore itself. for example, certain modules could generate extra information and attach them to the entries in the `data` table, which can be (and is) written to at runtime. the one invariant that the `data` table should observe is that once a record is added, it may neither be mutated nor removed (though additional sub-records can always be added) -- `sorcery` is not written to handle such eventualities and it may produce anything from glitches to crashes. the term `lore` references specifically the core records stored on disk from which the `data` table is generated, and a `lorepack` is a full, integrated collection of lore. (there are cases where modifying or deleting from `data` may be necessary for a `sorcery`-aware mod, for instance to prevent players from creating a certain default potion, but this is very fraught and you should only ever do it if you thoroughly understand the internals and exactly what you're doing, with the caveat that what works in this version may break the next.)

# writing sorcery-aware mods
`sorcery` exposes an API that is usable by other mods. to use it, you need to understand a little about how `sorcery` handles lore. local tweaks are simple because they inject changes into the pipeline before `sorcery` makes use of any of the lore. part of the setup stage is the creation of various lore-derived recipes, items, nodes, and so on. so if your mod loads after `sorcery` is finished loading (which it must, if it expects to call into the `sorcery` API), it can't take advantage of the same mechanism that world tweaks can, as it needs to not just insert the data but notify `sorcery` that something has been added so it can create the appropriate objects.

to handle this complex situation, `sorcery` uses *registers.* a register is set of functions and data used for tracking actions and dependencies between them. registers are automatically created for each lore category, except those specifically excluded by the bootstrapping routine. for instance, the extract register can be found at `sorcery.register.extract`. a register has several functions, only three of which are relevant to you: `link`, `meld`, and `foreach`.

## link
`link([key,] value)` inserts a new entry into the database the register manages. so to add a new extract for, say, sand, we could call `sorcery.register.extracts.link('sand',{'default:sand',{255,255,0})`. this would insert the record `sand => {'default:sand, {255,255,0}}` into `sorcery.data.extracts`. it would also take all of the actions that would have been taken if the entry had been present in `sorcery.data.extracts` when the `sorcery` mod bootstrapped itself.

```
sorcery.register.infusion.link {
	infuse = 'farming:sugar';
	into = 'sorcery:potion_serene';
	output = 'mymod:sweet_potion';
}
sorcery.register.residue.link('farming:sugar','sorcery:ash')
```

this will permit use of the infuser to create a **Sweet Potion** (presumably defined by your own mod) by infusing **sugar** into a **Serene Potion**, leaving behind **ash**.

new alloys can also be registered for use with the smelter:

```
sorcery.register.alloys.link {
	output = {
		[1] = 'mymod:excelsium_fragment';
		[4] = 'mymod:excelsium_ingot';
		[4 * 9] = 'mymod:excelsium_block';
	};
	cooktime = 69;
	metals = {
		lithium = 1;
		silver = 4;
		aluminum = 4;
	};
}
```

this defines an alloy called Excelsium that can be produced by mixing four parts silver, four parts aluminum, and one part lithium -- e.g. one lithium fragment, one aluminum ingot, and one silver ingot. `metals` must specify metals registered with the `sorcery` mod; `output` can be either the name of a registered metal or a table of items and part-values that can be produced. at least output[1] must always be present!

the kiln is not currently functional, but once it is, it will be possible to create kiln recipes with the `sorcery.register.kilnrecs.link` function.

## foreach
`foreach(id,deps,fn)` should be called for any registration actions that need to take place for a registry's contents. let's say you want to create a container than holds large volumes of an extract, like an extract keg. to do this, you'd need to create a node for each extract. but you can't just iterate `sorcery.data.extracts` because new extracts might be added long after your code is run. the solution is `foreach`: you give it an identifier, a list of dependency identifiers, and a function, and it then ensures that function will be called once for everything that winds up in `sorcery.data.extracts`.

it is crucial to understand the difference between a data `for` loop and registry `foreach`, because they are not interchangeable. a function passed to `foreach` may be called immediately, ten minutes later, both, or never (if there are missing dependencies). for instance, you cannot write a function that uses `foreach` to tally the mass of all metals, because it would run whenever a new metal was created, long after your function had stopped running, and it would only run once ever for each metal. in that case, you would need to use `for`.

let's consider our keg storage mod. we would want to create several things: first, the individual per-extract keg nodes; second, a node that takes an empty keg and, say, 99 of an extract, and transforms them into an extract keg. to register the kegs, we might use code like

```
minetest.register_node('keg:empty', { description = 'Empty Keg'; ‹···›})
sorcery.register.extracts.foreach('keg:generate',{},function(name,data)
	minetest.register_node('keg:extract_' .. name, {
		description = sorcery.lib.str.capitalize(name) .. ' Extract Keg';
		color = sorcery.lib.color(data[2]):hex();
		‹···›
	})
end)
```

but in the `on_metadata_inventory_put` code for keg-filling node, to identify the proper keg, we might instead use code like

```
local inv = minetest.get_meta(pos):get_inventory()
local fluid = inv:get_stack('fluid',1)
if fluid:get_count() < 99 then return end

local fname = fluid:get_name()
if minetest.get_item_group(fname, 'sorcery_extract') ~= 0 then
	for k,v in pairs(sorcery.data.extracts)
		if fname == 'sorcery:extract_' .. k then
			inv:set_stack('preview',1,ItemStack('keg:extract_' .. k))
			return
		end
	end
end
```

`foreach` could NOT be used in this case.

every time you register a `foreach`, you need to give it a unique identifier and list its dependencies. the identifier need only be unique among functions linked to that specific registry. dependencies are used so that when new items are linked to a registry, the queued functions are executed in the correct order. for instance, if you had a foreach function in the extracts registry that modified the definition table of the keg nodes, you would need to list `'keg:generate'` in its dependency array.

spelling dependencies correctly is crucial. if you call `foreach` with the name of a dependency that is not yet known to the registry, it will not be executed until that dependency is added. so if you misspell a dependency, the `foreach` function you add will never execute!

it's helpful therefore to insert calls to `sorcery.registry.defercheck()` at code boundaries, e.g. the end of your init file. this will print a warning to the logs and return false if there are any deferred iterators that have not yet been run. otherwise it will silently return true. `sorcery` checks its own iterators this way.

## meld
registries also have the `meld(tbl)` convenience function, which is useful if you have a `sorcery`-like lore table you want to merge in, say a table of metals your mod adds. in this case, you could simply call `sorcery.register.metals.meld(mymod.data.metals)` instead of faffing around with for loops.

## creating registries
you're not restricted to the registries already in existence. you can create registries of your own with the function `sorcery.registry.mk(name[,db])`. this will create (or overwrite!) registries in the `sorcery.register` table, and return a reference to that registry. the db argument can be absent, in which case it will look for a database under `sorcery.data[name]` or fail if one cannot be found. this will generally not be what you want if you're using it from another mod, so you'll want to either pass a reference to an existing table under your control, or the argument `false` which will tell `mk` to create a store in the registry itself under the table `db`, which is useful for ephemeral data that is generated entirely at runtime from existing lore entries like alloys or infusion residue.