sorcery  sorcery.md at [612f10a00d]

File sorcery.md artifact 9b54d0ccbf part of check-in 612f10a00d


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, farming, gathering resources from nature, and worship. it is intended to be pro-social, lore-flexible, to lend itself well towards a vibrant in-game economy, and to interoperate well with other mods wherever possible.

sorcery is still alpha-quality. it is far from feature-complete, and many important spells have not been written yet.

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
  • beds for the escape amulet (optional)

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.

libraries

  • luajit, because sorcery's code uses modern features not available in the ancient lua version bundled with minetest. alternately, it may be possible to build minetest against a more recent lua version if you're feeling masochistic; luajit will probably be faster tho and has first-party support

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.

mechanics

sorcery adds many new mechanics to the game. among them:

  • extracts can be crafted using crushed excess seeds and alcohol
  • oils, greases, and other substances can be crafted by mixing together various ingredients, including extracts
  • magical elixirs can be made by infusing oils and the like into bottles of water at an Infuser, and can be drunk to gain temporary powers
  • liquids can be carried around in buckets, bottles, and glasses, depending on the liquid.
  • kegs can store large volumes of liquid, making for easy retrieval when you, say, need a specific extract or potion to brew an elixir
  • troughs can be used to gather liquid from nature. set out under the open sky, for instance, they will slowly collect rainwater (depending on the biome). they have twice the volume of buckets.
  • taps can be attached to trees to extract saps, syrups, and resins from under the bark; these can be caught by placing a trough below. you can then fill bottles from the trough, or simply pick the whole trough up and bring it home to deposit the liquid in a keg for later use.
  • wands, made at an Wandworking Station out of a wood, gems, bands, and cores, can be enchanted by soaking them in philters, special potions used for imbuing wood with ley-force (though i'm trying to come up with a better mechanism). these fire off powerful spells with a flick of the wrist, but will break when they run out of power (unless you recharge them…)
  • if you want to get into advanced arcanism, you can build an Enchanter. equipped with the proper wands, this powerful artifact can be used to imbue tools with useful supernatural properties, though what enchantments are available and how much ley-charge a tool can hold depend on what metal the tool is made of. but Enchanters can be used for more subtle purposes as well. with the right ingredients and a Divination Wand, you can conjure up recipes for delicacies or schematics for powerful magitech devices you never imagined were possible. Melding Wands can merge the mystic essences of items on an Enchanter to create something new, including some things that can't be made any other way, though the stars may need to be right (an Astrolabe can help you check) for certain recipes to work, and some may come down to plain old luck. Division Wands are very similar, but work the other way around.
  • you can also build a Disassembly Kit, which you can use with pen, ink, and paper to create schematics for any object you come across that you want to be able to craft yourself.
  • you can collect your assorted recipes into cookbooks, and you can build yourself a Writing Stand to insert, delete, re-order, or (if you have some Scissors handy) cut out parts of the book.
  • you can make a Mill for grinding up solid materials into powder or pulp. it makes flour from grain more efficiently than a mortar and pestle, can produce various metal powders of great utility, and can reduce books, paper, and punchcards down to a pulp, suitable for cleaning with Erasure Fluid and cooking back into clean paper. but you'll need some way to power it. a Generator will produce plenty of energy in a pinch, but it may be more rewarding to build yourself a real power grid, carrying current from place to place using cables (Vidrium Cables are sufficient for homes and studios, but industrial facilities may need Iridium Cables or the even more conductive Conduit). Mese Blocks always emit a small but constant amount of ley-current, but if you're near a strong leyline, building Condensers to harvest its power will be much more efficient. (Leyspark Wands are crucial tools for testing the source and affinity of the ambient leyscape.)
  • some new alloys can be made by sintering metal powders; others need to be melted down in a Smelter. these alloys have the most remarkable properties of any metal, and are especially valued by toolsmiths and enchanters, though they may be less broadly useful than the lesser metals.
  • if you need to travel quickly between two distant places, and you're wealthy enough to afford it, you can build yourself one of the most powerful and complex of magitech devices — the Teleporter. it's no mean feat: even the smallest teleporter requires a teleport pad with a reflector above it and a portal node connected to one or the other. the teleporter will then need to be connected to its destination with cables or conduits, and if where you're travelling is very far away, you'll have to build two separate ley nets and bridge them by using an Attunement Wand on a pair of Raycasters — or perhaps even Farcasters. the power required to operate all of these devices is not trivial, and while a Farcaster's signal can pierce through any substance and cross any distance to reach its destination, the farther away each is from the other, the more power each side will consume. and casters can't send current, they can only send signals, so you may need a sizable power plant on both sides of the portal.
  • if all you need to do is send small items, of course, a Displacer is much cheaper, and more flexible. if you're feeling particularly ambitious, you could use a Displacer net to connect your whole kingdom with instantaneous package service.
  • stop your foes in their tracks by flipping a switch to turn on your Force Field Emitters, generating an impenetrable barrier wherever they aim.
  • who needs elevators when you have Gravitators? float gently up a vast borehole, bring attackers crashing down to earth, or slow a fatal plunge to a soft and easy landing.
  • build graven Idols to your gods and set sacrifices to them upon an altar. if they're feeling generous, they might start sending you presents of their own, or consecrating your offerings. but beware: different gods have different tastes (and different powers), and get bored quickly with repetitive offerings.
  • to bend the ultimate arcane forces of the cosmos to your will, you'll need a Rune Forge. with a strong ley-current and a steady supply of Phials from an Infuser, a Rune Forge will crystallize thaumic impurities into Runes that can you can imbue into a gemstone Amulet with a Rune Wrench. each amulet can only be used once before it loses its charge, and it may be a long time before the same kind of rune happens to coalesce in your forge again, but the spells they unleash are unique and priceless boons — or weapons. teleport without a teleporter! purge your environs of all spellcraft no matter how fearsomely potent! surround yourself with a shimmering Disjunction Field that, for a short while, will snuff out any spell it touches and, rendering enemy mages utterly helpless and piercing otherwise impenetrable defenses! stride through solid stone like open air! carve huge tunnels deep into the rock with only a snap of your fingers! rain obliteration down upon a target of your choosing! send forth a titanic bolt of flame with the power to blast open mountainsides! tear the very life force straight from an enemy's body and use it to fortify your being! all this and more can be done with the power of rune magic.

there's more as well. users are intended to discover recipes through trade with others, but if you're playing in singleplayer or you're the first person on a new server, that won't work. if you feel like cheating, you can enable the MTG Craftguide or read the Sorcery source code (and there's some recipes on the wiki) but the "correct" way of gaining new recipes is through worship. you can find instructions for building idols and altars in "lore.md".

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('my-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 over extras, 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.