sorcery  Check-in [c71731cf58]

Overview
Comment:many bug fixers, some minor refactoring, allow non-drinkable potions to be empowered in various ways, allow gods to be petitioned for recipes (next up: cookbooks!)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: c71731cf588f3c6b783992fc2455d64564ae464caf8c79e06f449dc772309e88
User & Date: lexi on 2021-07-03 02:25:42
Other Links: manifest | tags
Context
2021-07-04
20:27
add cool and informative visuals for taps, add more capacity to rune forge, many bug fixes, fixed some bugs, and fixed some bugs check-in: 94064fe5c9 user: lexi tags: trunk
2021-07-03
02:25
many bug fixers, some minor refactoring, allow non-drinkable potions to be empowered in various ways, allow gods to be petitioned for recipes (next up: cookbooks!) check-in: c71731cf58 user: lexi tags: trunk
2021-06-28
18:31
defuckulate markdown check-in: fa442a5d6a user: lexi tags: trunk
Changes

Modified altar.lua from [a7a1197203] to [080406f8d2].

1
2
3
4
5
6
7
8
9
10
11
..
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
...
215
216
217
218
219
220
221

222
223

224








225
226


227

228
229
230
231
232



233



234
235
236
237
238
239
240
241
242
243
244

245
246
247
248
249
250
251
252
253
254
255
local altar_item_offset = {
	x = 0, y = -0.3, z = 0
}
local log = function(...) sorcery.log('altar',...) end

local range = function(min, max)
	local span = max - min
	local val = math.random() * span
	return val + min
end

................................................................................

		after_place_node = function(pos, placer, stack, pointat)
			local meta = minetest.get_meta(pos)
			local stackmeta = stack:get_meta()
			meta:set_int('favor', stackmeta:get_int('favor'))
			meta:set_string('last_sacrifice', stackmeta:get_string('last_sacrifice'))

			minetest.get_node_timer(pos):start(60)
		end;

		drop = {
			-- for some idiot reason this is necessary for
			-- preserve_metadata to work right
			max_items = 1;
			items = {
................................................................................
					-- we pick a random gift and roll against its rarity
					-- to determine if the god is feeling generous
					local gift = sorcery.lib.tbl.pick(god.gifts)
					local data = god.gifts[gift]
					local value, rarity = data[1], data[2]
					if value <= divine_favor and math.random(rarity) == 1 then
						bestow(gift)
						log(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
						if math.random(god.generosity) == 1 then
							-- unappreciated gifts may incur divine
							-- irritation
							divine_favor = divine_favor - 1
						end
					end
				end
................................................................................
						goto refresh
					end
				end

				-- loop through the list of things this god will consecrate and
				-- check whether the item on the altar is any of them
				for s, cons in pairs(god.consecrate) do

					local cost, tx = cons[1], cons[2]
					if type(tx) == "table" then

						tx = tx[math.random(#tx)]








					end
					-- preserve wear


					local gift = ItemStack(tx)

					local wear = stack:get_wear()
					if wear > 0 then
						gift:set_wear(wear)
					end
					-- preserve meta



					gift:get_meta():from_table(stack:get_meta():to_table())



					-- reflash enchantments to ensure label is accurate
					local ench = sorcery.enchant.get(gift)
					if #ench.spells > 0 then
						-- add a bit of energy as a gift?
						if math.random(math.ceil(god.stinginess * 0.5)) == 1 then
							local max = 0.05 * god.generosity
							ench.energy = ench.energy * range(0.7*max,max)
						end
						sorcery.enchant.set(gift,ench)
					end
					if itemname == s then

						if divine_favor >= cost then
							bestow(gift)
							divine_favor = divine_favor - cost
							print(god.name..'has consecrated ' ..s.. ' into ' ..tx.. ', for the cost of ' ..cost.. ' points of divine favor')
							goto refresh
						end
					end
				end
			end

			::refresh::



|







 







|







 







|







 







>
|
|
>
|
>
>
>
>
>
>
>
>
|
|
>
>
|
>
|
|
|
|
|
>
>
>
|
>
>
>
|
|
|
|
|
|
|
|
|
|
<
>



|







1
2
3
4
5
6
7
8
9
10
11
..
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
...
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262

263
264
265
266
267
268
269
270
271
272
273
274
local altar_item_offset = {
	x = 0, y = -0.3, z = 0
}
local log = sorcery.logger('altar')

local range = function(min, max)
	local span = max - min
	local val = math.random() * span
	return val + min
end

................................................................................

		after_place_node = function(pos, placer, stack, pointat)
			local meta = minetest.get_meta(pos)
			local stackmeta = stack:get_meta()
			meta:set_int('favor', stackmeta:get_int('favor'))
			meta:set_string('last_sacrifice', stackmeta:get_string('last_sacrifice'))

			minetest.get_node_timer(pos):start(1)
		end;

		drop = {
			-- for some idiot reason this is necessary for
			-- preserve_metadata to work right
			max_items = 1;
			items = {
................................................................................
					-- we pick a random gift and roll against its rarity
					-- to determine if the god is feeling generous
					local gift = sorcery.lib.tbl.pick(god.gifts)
					local data = god.gifts[gift]
					local value, rarity = data[1], data[2]
					if value <= divine_favor and math.random(rarity) == 1 then
						bestow(gift)
						log.act(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
						if math.random(god.generosity) == 1 then
							-- unappreciated gifts may incur divine
							-- irritation
							divine_favor = divine_favor - 1
						end
					end
				end
................................................................................
						goto refresh
					end
				end

				-- loop through the list of things this god will consecrate and
				-- check whether the item on the altar is any of them
				for s, cons in pairs(god.consecrate) do
					if itemname == s then
						local cost, tx
						if type(cons) == "table" then
							cost, tx = cons[1], cons[2]
							tx = tx[math.random(#tx)]
						elseif type(cons) == 'function' then
							cost, tx = cons {
								favor = divine_favor;
								pos = pos;
								altar = altarmeta;
								idol = idolmeta;
								god = god;
							}
						end
						-- preserve wear
						local gift
						if type(tx) == 'string' then
							gift = ItemStack(tx)
						else gift = tx end
						local wear = stack:get_wear()
						if wear > 0 then
							gift:set_wear(wear)
						end
						-- preserve meta
						local gm = gift:get_meta()
						gm:from_table(
							sorcery.lib.tbl.merge(
								stack:get_meta():to_table(),
								gm:to_table()
							)
						) -- oof
						-- reflash enchantments to ensure label is accurate
						local ench = sorcery.enchant.get(gift)
						if #ench.spells > 0 then
							-- add a bit of energy as a gift?
							if math.random(math.ceil(god.stinginess * 0.5)) == 1 then
								local max = 0.05 * god.generosity
								ench.energy = ench.energy * range(0.7*max,max)
							end
							sorcery.enchant.set(gift,ench)
						end


						if divine_favor >= cost then
							bestow(gift)
							divine_favor = divine_favor - cost
							log.act(god.name, 'has consecrated', s, 'into', tx, 'for the cost of', cost, 'points of divine favor')
							goto refresh
						end
					end
				end
			end

			::refresh::

Modified astrolabe.lua from [a9316d6c59] to [cc031237ac].

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	local r = {}
	for i,v in ipairs(k) do
		r[i] = {
			id = k[i];
			name = sorcery.data.calendar.styles[v].name;
		}
	end
	print(dump(r))
	return r
end
local astrolabe_formspec = function(pos)
	local m = minetest.get_meta(pos)
	local i = m:get_inventory()
	local datestamp = minetest.get_day_count()
	local date = sorcery.calendar.date(datestamp)







<







4
5
6
7
8
9
10

11
12
13
14
15
16
17
	local r = {}
	for i,v in ipairs(k) do
		r[i] = {
			id = k[i];
			name = sorcery.data.calendar.styles[v].name;
		}
	end

	return r
end
local astrolabe_formspec = function(pos)
	local m = minetest.get_meta(pos)
	local i = m:get_inventory()
	local datestamp = minetest.get_day_count()
	local date = sorcery.calendar.date(datestamp)

Modified cookbook.lua from [e643a56c3c] to [aae72d93b8].

1
2
3
4
5
6


7
8
9
10
11
12
13
..
51
52
53
54
55
56
57



























58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
...
219
220
221
222
223
224
225














226

227
228
229
230
231
232
233
...
254
255
256
257
258
259
260










261
262
263

264
265
266
267
268
269
270
...
400
401
402
403
404
405
406



407
408
409
410
411
412
413
...
464
465
466
467
468
469
470

471
472
473
474
475
476
477
-- by use of the enchanter, it is possible to reveal a random
-- recipe and enscribe it on a sheet of paper. these sheets of
-- paper can then bound together into books, combining like
-- recipes

sorcery.cookbook = {}


local constants = {
	-- do not show recipes for items in these groups
	exclude_groups = {
	};
	exclude_names = {
		'stairs';
		'slab';
................................................................................
	return {}
end
local modofname = function(id)
	local sep = string.find(id,':')
	if sep == nil then return nil end -- uh oh
	return string.sub(id, 1, sep - 1)
end



























local pick_builtin = function(kind) return function(restrict)
	-- ow ow ow ow ow ow ow
	local names = {}
	for k in pairs(minetest.registered_items) do
		local rec = minetest.get_craft_recipe(k)
		if rec.items ~= nil and (rec.method == kind or (rec.method == 'shapeless' and kind == 'normal')) then -- is this last bit necessary?
			local excluded = false
			for _,n in pairs(constants.exclude_names) do
				if string.find(k,n) ~= nil then
					excluded = true break end
			end
			if not excluded then for _,g in pairs(constants.exclude_groups) do
				if minetest.get_item_group(k, g) > 0 then
					excluded = true break end
			end end
			local props = minetest.registered_items[k]._sorcery
			local module = modofname(k)
			if not (excluded
				or sorcery.lib.tbl.has(constants.blacklist_mods,module)
				or (props and props.recipe and props.recipe.secret)
				or (restrict and (
				    (restrict.mod   and module ~= restrict.mod)
				 or (restrict.group and (minetest.get_item_group(k, restrict.group) == 0))
			))) then names[#names + 1] = k end
		end
	end
	return names[math.random(#names)]
end end
local find_builtin = function(method,kind)
	return function(out)
		local rec = {}
................................................................................
		chance = 4;
		slots = {
			{0,0};
			{0,1};
		};
		pick = function(restrict)
			-- TODO make sure affinity restrictions match














			return sorcery.register.infusions.db[math.random(#sorcery.register.infusions.db)].output

		end;
		title = function(output)
			for _,i in pairs(sorcery.register.infusions.db) do
				if i.output == output then
					if i._proto and i._proto.name
						then return i._proto.name
						else break end
................................................................................
		name = 'Milling Guide';
		node = 'sorcery:mill';
		booksuf = 'Manual';
		chance = 1;
		w = 1, h = 2;
		pick = function(restrict)
			cache:populate_grindables()










			local i = cache.grindables[math.random(#cache.grindables)]
			local pd = sorcery.itemclass.get(i, 'grindable')
			return pd.powder

		end;
		props = props_builtin;
		slots = {
			{0,1},
			{0,0};
		};
		find = function(out)
................................................................................
		end
		if kind == nil then -- oh well, we tried
			local rks = sorcery.lib.tbl.keys(recipe_kinds)
			kind = rks[math.random(#rks)]
		end
	end




	return recipe_kinds[kind].pick(restrict), kind
end

local render_recipe = function(kind,ingredients,result,notes_right)
	local k = recipe_kinds[kind]
	local t = ''
	local props = k.props(result)
................................................................................
	local ing = rec.find(out)
	return render_recipe(kind,ing,out,notes_right), rec.w, rec.h
end

sorcery.cookbook.setrecipe = function(stack,k,r,restrict)
	local meta = stack:get_meta()
	if not r then r,k = sorcery.cookbook.pickrecipe(k,restrict) end

	local t = recipe_kinds[k]
	meta:set_string('recipe_kind', k)
	meta:set_string('recipe_name', r)
	meta:set_string('description',
		(t.title and t.title(r) or desc_builtin(r)) .. ' ' .. t.name)
end







>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>







 







>
>
>
>
>
>
>
>
>
>
|
|
|
>







 







>
>
>







 







>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

















93
94
95
96
97
98
99
100
...
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
...
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
...
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
...
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
-- by use of the enchanter, it is possible to reveal a random
-- recipe and enscribe it on a sheet of paper. these sheets of
-- paper can then bound together into books, combining like
-- recipes

sorcery.cookbook = {}
local log = sorcery.logger('cookbook')

local constants = {
	-- do not show recipes for items in these groups
	exclude_groups = {
	};
	exclude_names = {
		'stairs';
		'slab';
................................................................................
	return {}
end
local modofname = function(id)
	local sep = string.find(id,':')
	if sep == nil then return nil end -- uh oh
	return string.sub(id, 1, sep - 1)
end
local item_restrict_eval = function(name, restrict)
	for _,n in pairs(constants.exclude_names) do
		if string.find(name,n) ~= nil then
			return false
		end
	end
	for _,g in pairs(constants.exclude_groups) do
		if minetest.get_item_group(name, g) > 0 then
			return false
		end
	end

	local props = minetest.registered_items[name]._sorcery
	local module = modofname(name)

	return not (excluded
		or sorcery.lib.tbl.has(constants.blacklist_mods,module)
		or (props and props.recipe and props.recipe.secret)
		or (restrict and (
			(restrict.pred and restrict.pred {
				mod = module, item = name, props = props
			} ~= true)
		 or (restrict.mod   and module ~= restrict.mod)
		 or (restrict.group and (minetest.get_item_group(name, restrict.group) == 0))
	))) 
end

local pick_builtin = function(kind) return function(restrict)
	-- ow ow ow ow ow ow ow
	local names = {}
	for k in pairs(minetest.registered_items) do
		local rec = minetest.get_craft_recipe(k)
		if rec.items ~= nil and (rec.method == kind or (rec.method == 'shapeless' and kind == 'normal')) then -- is this last bit necessary?

















			if item_restrict_eval(k, restrict) then names[#names + 1] = k end
		end
	end
	return names[math.random(#names)]
end end
local find_builtin = function(method,kind)
	return function(out)
		local rec = {}
................................................................................
		chance = 4;
		slots = {
			{0,0};
			{0,1};
		};
		pick = function(restrict)
			-- TODO make sure affinity restrictions match
			if restrict then
				local t = {}
				for _, i in pairs(sorcery.register.infusions.db) do
					if item_restrict_eval(i.output, restrict) and not (
						-- conditions which cause failure of restriction test
						(restrict.ipred and restrict.ipred {
							mod = module;
							infusion = i;
							output = i.output;
						} ~= true)
					) then t[#t+1] = i.output end
				end
				return select(2, sorcery.lib.tbl.pick(t))
			else
				return sorcery.register.infusions.db[math.random(#sorcery.register.infusions.db)].output
			end
		end;
		title = function(output)
			for _,i in pairs(sorcery.register.infusions.db) do
				if i.output == output then
					if i._proto and i._proto.name
						then return i._proto.name
						else break end
................................................................................
		name = 'Milling Guide';
		node = 'sorcery:mill';
		booksuf = 'Manual';
		chance = 1;
		w = 1, h = 2;
		pick = function(restrict)
			cache:populate_grindables()
			if restrict then
				local t = {}
				for _, i in pairs(cache.grindables) do
					local pd = sorcery.itemclass.get(i, 'grindable')
					if item_restrict_eval(pd.powder, restrict) then
						t[#t+1] = pd.powder
					end
				end
				return select(2, sorcery.lib.tbl.pick(t))
			else
				local gd = cache.grindables[math.random(#cache.grindables)]
				local pd = sorcery.itemclass.get(gd, 'grindable')
				return pd.powder
			end
		end;
		props = props_builtin;
		slots = {
			{0,1},
			{0,0};
		};
		find = function(out)
................................................................................
		end
		if kind == nil then -- oh well, we tried
			local rks = sorcery.lib.tbl.keys(recipe_kinds)
			kind = rks[math.random(#rks)]
		end
	end

	if not recipe_kinds[kind] then
		log.fatalf('attempted to pick recipe of unknown kind "%s"', kind)
	end
	return recipe_kinds[kind].pick(restrict), kind
end

local render_recipe = function(kind,ingredients,result,notes_right)
	local k = recipe_kinds[kind]
	local t = ''
	local props = k.props(result)
................................................................................
	local ing = rec.find(out)
	return render_recipe(kind,ing,out,notes_right), rec.w, rec.h
end

sorcery.cookbook.setrecipe = function(stack,k,r,restrict)
	local meta = stack:get_meta()
	if not r then r,k = sorcery.cookbook.pickrecipe(k,restrict) end
	if not r then return false end
	local t = recipe_kinds[k]
	meta:set_string('recipe_kind', k)
	meta:set_string('recipe_name', r)
	meta:set_string('description',
		(t.title and t.title(r) or desc_builtin(r)) .. ' ' .. t.name)
end

Modified data/draughts.lua from [ab3b6e4e1b] to [0a84465667].

6
7
8
9
10
11
12

13
14
15
16
17
18
19
..
27
28
29
30
31
32
33

34
35
36
37
38
39
40
..
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
..
61
62
63
64
65
66
67

68
69
70
71
72
73
74
..
85
86
87
88
89
90
91

92
93
94
95
96
97
98
...
108
109
110
111
112
113
114

115
116
117
118
119
120
121
...
129
130
131
132
133
134
135

136
137
138
139
140
141
142
...
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
175
176
177
178
179
180

181
182
183
184
185
186

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
...
206
207
208
209
210
211
212

213
214
215
216
217
218
219
		style = 'sparkle';
		desc = "A potion that amps up your body's natural\nhealing abilities, causing you to heal rapidly\neven if you're starving";
		infusion = 'sorcery:blood';
		basis = 'sorcery:potion_luminous';
		duration = function(self,meta)
			return 10 + meta:get_int('duration')*2
		end;

		effect = function(self, user, proto)
			local meta = self:get_meta()
			local force = 1 + meta:get_int('force')
			late.new_effect(user, {
				duration = proto:duration(meta);
				raise = 4; fall = 4;
				impacts = {
................................................................................
		color = {79,228,243}; style = 'sparkle';
		basis = 'sorcery:potion_luminous';
		desc = "Conserve your precious supply of oxygen when diving down into the ocean's depths";
		infusion = 'sorcery:extract_kelp';
		duration = function(self,meta)
			return 20 + meta:get_int('duration')*30
		end;

		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 1 + 2 * (meta:get_int('force'))
			late.new_effect(user, {
				duration = proto:duration(meta);
				raise = 2; fall = 5;
				impacts = {
................................................................................
			})
		end;
	};
	heal = {
		name = 'Healing';
		color = {243,44,58};
		style = 'sparkle';
		no_duration = true;
		desc = 'This blood-red liquid glitters with an enchantment that rapidly knits torn flesh and broken bones';
		infusion = 'sorcery:oil_sanguine';
		basis = 'sorcery:potion_luminous';
		effect = function(self, user)
			local meta = self:get_meta()
			user:set_hp(user:get_hp() + (2 * (2 + meta:get_int('force'))))
		end;
................................................................................

	stealth = {
		name = 'Stealth';
		color = {184,106,224}; style = 'sparkle';
		infusion = 'default:coal_lump';
		basis = 'sorcery:potion_soft';
		desc = 'Drinking this dark, swirling draught will shelter you from the power of mortal perception for a time, even rendering you entirely invisible at full strength.';

		duration = function(self,meta)
			return 30 + meta:get_int('duration')*30
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 1 + 1 * (meta:get_int('force'))
			local opacity = 1.0 - (1.0 * (force / 4)) 
................................................................................
	nightsight = {
		name = 'Nightsight';
		color = {91,0,200}; style = 'sparkle';
		desc = 'While this potion flows through your veins, your vision will be strengthened against the darkness of the night';
		maxforce = 3;
		infusion = 'sorcery:oil_dawn';
		basis = 'sorcery:potion_soft';

		duration = function(self,meta)
			return 50 + meta:get_int('duration')*70
		end;
		effect = function(self,user,proto)
			--TODO ensure it can only be drunk at night
			--TODO ensure it can't last more than one night
			local meta = self:get_meta()
................................................................................
	};
	antigravity = {
		name = 'Antigravity';
		color = {240,59,255}; style = 'sparkle';
		desc = 'Loosen the crushing grip of the earth upon your tender mortal form with a few sips from this glittering phial';
		infusion = 'sorcery:oil_stone';
		basis = 'sorcery:potion_soft';

		duration = function(self,meta)
			return 20 + meta:get_int('duration')*25
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 1 - 0.3 * (meta:get_int('force') + 1)
			late.new_effect(user, {
................................................................................
	};
	gale = {
		name = 'Gale';
		color = {187,176,203};
		desc = 'Move and strike with the speed of a hurricane as this enchanted fluid courses through your veins';
		infusion = 'sorcery:grease_storm';
		basis = 'sorcery:potion_soft';

		duration = function(self,meta)
			return 10 + meta:get_int('duration')*15
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 2 + 0.7 * (meta:get_int('force'))
			late.new_effect(user, {
................................................................................
	obsidian = {
		name = 'Obsidian';
		infusion = 'default:obsidian_shard';
		color = {76,0,121}; style = 'sparkle';
		desc = 'Walk untroubled through volleys of arrows and maelstroms of swinging blades, for all will batter uselessly against skin protected by spellwork mightier than the doughtiest armor';
		infusion = 'default:obsidian_shard';
		basis = 'sorcery:potion_luminous';
		no_force = true;
		duration = function(self,meta)
			return 5 + meta:get_int('duration')*7
		end;
	};
	lavabreathing = {
		name = 'Lavabreathing';
		color = {243,118,79}; style = 'sparkle'; glow = 12;
		basis = 'sorcery:potion_soft';
		desc = "Wade through seas of roiling lava as easily as though it were but a babbling brook";

	};
	-- mighty = {
	-- 	name = 'Mighty';
	-- 	color = {255,0,119}; style = 'sparkle'; glow = 5;
	-- 	infusion = 'sorcery:grease_war';
	-- 	basis = 'sorcery:potion_soft';
	-- 	desc = 'Amplify the power of your blows and crack steel armor with the force of your bare hands';
	-- };
	resilient = {
		name = 'Resilient';
		color = {124,124,124}; style = 'dull';
		basis = 'sorcery:potion_soft';
		desc = 'Withstand greater damage and hold your ground even in face of tremendous force';

	};
	hover = {
		name = 'Hover';
		color = {164,252,55}; style = 'sparkle';
		desc = 'Rise into the air for a time and stay there until the potion wears off';
		basis = 'sorcery:potion_soft';

	}; 
	flight = {
		name = 'Flight';
		color = {143,35,255}; style = 'sparkle';
		desc = 'Free yourself totally from the shackles of gravity and soar through the air however you should will';
		basis = 'sorcery:potion_soft';
		infusion = 'sorcery:grease_lift';
		no_force = true;
		duration = function(self,meta)
			return 40 + meta:get_int('duration')*55
		end;
		effect = function(self,user,proto)
			late.new_effect(user, {
				duration = proto:duration(self:get_meta());
				impacts = {
................................................................................
	};
	leap = {
		name = 'Leap';
		color = {164,252,55};
		desc = 'Soar high into the air each time you jump (but may risk damage if used without a Feather Potion)';
		infusion = 'sorcery:oil_wind';
		basis = 'sorcery:potion_soft';

		duration = function(self,meta)
			 return 5 + meta:get_int('duration')*7
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 2 + (0.5 * meta:get_int('force'))
			late.new_effect(user, {







>







 







>







 







|







 







>







 







>







 







>







 







>







 







|









>













>






>







|







 







>







6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
..
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
..
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
..
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
...
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
...
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
...
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
		style = 'sparkle';
		desc = "A potion that amps up your body's natural\nhealing abilities, causing you to heal rapidly\neven if you're starving";
		infusion = 'sorcery:blood';
		basis = 'sorcery:potion_luminous';
		duration = function(self,meta)
			return 10 + meta:get_int('duration')*2
		end;
		quals = { force = true, duration = true };
		effect = function(self, user, proto)
			local meta = self:get_meta()
			local force = 1 + meta:get_int('force')
			late.new_effect(user, {
				duration = proto:duration(meta);
				raise = 4; fall = 4;
				impacts = {
................................................................................
		color = {79,228,243}; style = 'sparkle';
		basis = 'sorcery:potion_luminous';
		desc = "Conserve your precious supply of oxygen when diving down into the ocean's depths";
		infusion = 'sorcery:extract_kelp';
		duration = function(self,meta)
			return 20 + meta:get_int('duration')*30
		end;
		quals = { force = true, duration = true };
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 1 + 2 * (meta:get_int('force'))
			late.new_effect(user, {
				duration = proto:duration(meta);
				raise = 2; fall = 5;
				impacts = {
................................................................................
			})
		end;
	};
	heal = {
		name = 'Healing';
		color = {243,44,58};
		style = 'sparkle';
		quals = { force = true };
		desc = 'This blood-red liquid glitters with an enchantment that rapidly knits torn flesh and broken bones';
		infusion = 'sorcery:oil_sanguine';
		basis = 'sorcery:potion_luminous';
		effect = function(self, user)
			local meta = self:get_meta()
			user:set_hp(user:get_hp() + (2 * (2 + meta:get_int('force'))))
		end;
................................................................................

	stealth = {
		name = 'Stealth';
		color = {184,106,224}; style = 'sparkle';
		infusion = 'default:coal_lump';
		basis = 'sorcery:potion_soft';
		desc = 'Drinking this dark, swirling draught will shelter you from the power of mortal perception for a time, even rendering you entirely invisible at full strength.';
		quals = { force = true, duration = true };
		duration = function(self,meta)
			return 30 + meta:get_int('duration')*30
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 1 + 1 * (meta:get_int('force'))
			local opacity = 1.0 - (1.0 * (force / 4)) 
................................................................................
	nightsight = {
		name = 'Nightsight';
		color = {91,0,200}; style = 'sparkle';
		desc = 'While this potion flows through your veins, your vision will be strengthened against the darkness of the night';
		maxforce = 3;
		infusion = 'sorcery:oil_dawn';
		basis = 'sorcery:potion_soft';
		quals = { force = true, duration = true };
		duration = function(self,meta)
			return 50 + meta:get_int('duration')*70
		end;
		effect = function(self,user,proto)
			--TODO ensure it can only be drunk at night
			--TODO ensure it can't last more than one night
			local meta = self:get_meta()
................................................................................
	};
	antigravity = {
		name = 'Antigravity';
		color = {240,59,255}; style = 'sparkle';
		desc = 'Loosen the crushing grip of the earth upon your tender mortal form with a few sips from this glittering phial';
		infusion = 'sorcery:oil_stone';
		basis = 'sorcery:potion_soft';
		quals = { force = true, duration = true };
		duration = function(self,meta)
			return 20 + meta:get_int('duration')*25
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 1 - 0.3 * (meta:get_int('force') + 1)
			late.new_effect(user, {
................................................................................
	};
	gale = {
		name = 'Gale';
		color = {187,176,203};
		desc = 'Move and strike with the speed of a hurricane as this enchanted fluid courses through your veins';
		infusion = 'sorcery:grease_storm';
		basis = 'sorcery:potion_soft';
		quals = { force = true, duration = true };
		duration = function(self,meta)
			return 10 + meta:get_int('duration')*15
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 2 + 0.7 * (meta:get_int('force'))
			late.new_effect(user, {
................................................................................
	obsidian = {
		name = 'Obsidian';
		infusion = 'default:obsidian_shard';
		color = {76,0,121}; style = 'sparkle';
		desc = 'Walk untroubled through volleys of arrows and maelstroms of swinging blades, for all will batter uselessly against skin protected by spellwork mightier than the doughtiest armor';
		infusion = 'default:obsidian_shard';
		basis = 'sorcery:potion_luminous';
		quals = { duration = true };
		duration = function(self,meta)
			return 5 + meta:get_int('duration')*7
		end;
	};
	lavabreathing = {
		name = 'Lavabreathing';
		color = {243,118,79}; style = 'sparkle'; glow = 12;
		basis = 'sorcery:potion_soft';
		desc = "Wade through seas of roiling lava as easily as though it were but a babbling brook";
		quals = { duration = true };
	};
	-- mighty = {
	-- 	name = 'Mighty';
	-- 	color = {255,0,119}; style = 'sparkle'; glow = 5;
	-- 	infusion = 'sorcery:grease_war';
	-- 	basis = 'sorcery:potion_soft';
	-- 	desc = 'Amplify the power of your blows and crack steel armor with the force of your bare hands';
	-- };
	resilient = {
		name = 'Resilient';
		color = {124,124,124}; style = 'dull';
		basis = 'sorcery:potion_soft';
		desc = 'Withstand greater damage and hold your ground even in face of tremendous force';
		quals = { force = true, duration = true };
	};
	hover = {
		name = 'Hover';
		color = {164,252,55}; style = 'sparkle';
		desc = 'Rise into the air for a time and stay there until the potion wears off';
		basis = 'sorcery:potion_soft';
		quals = { force = true, duration = true };
	}; 
	flight = {
		name = 'Flight';
		color = {143,35,255}; style = 'sparkle';
		desc = 'Free yourself totally from the shackles of gravity and soar through the air however you should will';
		basis = 'sorcery:potion_soft';
		infusion = 'sorcery:grease_lift';
		quals = { duration = true };
		duration = function(self,meta)
			return 40 + meta:get_int('duration')*55
		end;
		effect = function(self,user,proto)
			late.new_effect(user, {
				duration = proto:duration(self:get_meta());
				impacts = {
................................................................................
	};
	leap = {
		name = 'Leap';
		color = {164,252,55};
		desc = 'Soar high into the air each time you jump (but may risk damage if used without a Feather Potion)';
		infusion = 'sorcery:oil_wind';
		basis = 'sorcery:potion_soft';
		quals = { force = true, duration = true };
		duration = function(self,meta)
			 return 5 + meta:get_int('duration')*7
		end;
		effect = function(self,user,proto)
			local meta = self:get_meta()
			local force = 2 + (0.5 * meta:get_int('force'))
			late.new_effect(user, {

Modified data/elixirs.lua from [b08a2041a5] to [ba37716134].

1
2
3
4
5
6

7






8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23







24



















return {
	Force = {
		color = {255,165,85}; flag = 'force';
		apply = function(potion, kind)
			local meta = potion:get_meta()
			meta:set_int('force', meta:get_int('force') + 1)

		end;






		describe = function(potion)
			return 'good', 'empowered', "The strength of this potion's effect has been alchemically amplified"
		end;
		infusion = 'sorcery:grease_thunder';
	};
	Longevity = {
		color = {255,85,216}; flag = 'duration';
		apply = function(potion, kind)
			local meta = potion:get_meta()
			meta:set_int('duration', meta:get_int('duration') + 1)
		end;
		describe = function(potion)
			return 'good', 'prolonged', 'The effects of this potion will last longer than normal'
		end;
		infusion = 'sorcery:grease_pine';
	};







}



















|
<
<
|
|
<
>
|
>
>
>
>
>
>






|
|
<
<
<





>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1


2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
local inc = function(prop, val)


	return function(potion, kind)
		local meta = potion:get_meta()

		meta:set_int(prop, meta:get_int(prop) + (val or 1))
	end
end

return {
	Force = {
		color = {255,165,85}; qual = 'force';
		apply = inc('force');
		describe = function(potion)
			return 'good', 'empowered', "The strength of this potion's effect has been alchemically amplified"
		end;
		infusion = 'sorcery:grease_thunder';
	};
	Longevity = {
		color = {255,85,216}; qual = 'duration';
		apply = inc('duration');



		describe = function(potion)
			return 'good', 'prolonged', 'The effects of this potion will last longer than normal'
		end;
		infusion = 'sorcery:grease_pine';
	};
	Rapidity = {
		color = {183,28,238}; qual = 'speed';
		apply = inc('speed');
		describe = function(potion)
			return 'good', 'Quickened', 'This potion will take effect more quiclkly and easily'
		end;
		infusion = 'sorcery:liquid_sap_acacia_bottle';
	};
	Purity = {
		color = {244,255,255}; qual = 'purity';
		apply = inc('purity');
		describe = function(potion)
			return 'good', 'purified', 'This potion\'s impurities and undesirable side effects are diminished or eliminated'
		end;
		infusion = 'sorcery:oil_purifying';
	};
	Beauty = {
		color = {255,20,226}; qual = 'beauty';
		apply = inc('beauty');
		describe = function(potion)
			return 'good', 'beautified', 'The effects of this potion will be more vivid and spectacular than normal'
		end;
		infusion = 'sorcery:liquid_sap_apple_bottle';
	};
	-- Glory?
	-- Clarity?
}

Modified data/gods.lua from [4ff6f034b6] to [fc9658990d].


1
2
3
4
5
6



7
8
9
10
11
12
13
..
33
34
35
36
37
38
39




















40
41
42
43
44
45
46
...
130
131
132
133
134
135
136

137
138
139
140
141
142
143

return {
	harvest = {
		name = "Irix Irimentari" --[[
			an old Elerian harvest goddess, Irix Irimentari has watched vigilantly over the fields of her worshippers since before the Second Age. she favors alcoholic beverages as tribute, and has been known to perform blessings for sorcerers when sufficiently inebriated. she harbors a particular hatred of daemons and those who spill the blood of farmers.

			legend says that a barbarian lord high on opium once wandered into a temple of Irix and left the severed head of a local shepherd on the her altar. this desecration so enraged the goddess that the barbarian's entire tribe soon starved horribly to death, their crops refusing to take root, and their stolen breads turning to ash in their mouths.



		]];
		laziness = 5;
		stinginess = 3;
		generosity = 10;
		color = {214, 255, 146};
		idol = {
			desc = "Harvest Idol";
................................................................................
				"flowerpot:flowers_chrysanthemum_green";
				"flowerpot:flowers_dandelion_white";
				"flowerpot:flowers_dandelion_yellow";
			}};
			["sorcery:dagger"] = {17, "sorcery:dagger_consecrated"};
			["sorcery:oil_mystic"] = {9, "sorcery:oil_purifying"};
			["sorcery:potion_water"] = {4, "sorcery:holy_water"};




















			-- ["default:steel_ingot"] = {15, "sorcery:holy_token_harvest"};
		};
		sacrifice = {
			-- beattitudes
			["farming:straw"           ] = 1;
			["farming:bread_slice"     ] = 1;
			["farming:bread"           ] = 2;
................................................................................
				Force = {favor=50, cost=5, chance=7};
				Longevity = {favor=65, cost=7, chance=3};
			};
			tools = {
				rend = {favor=80, cost=15, chance=20};
			};
		};

		generosity = 4;
		stinginess = 9;
		idol = {
			desc = "Blood Idol";
			width = 0.7;
			height = 1.3;
			tex = {
>






>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
...
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
local L = sorcery.lib
return {
	harvest = {
		name = "Irix Irimentari" --[[
			an old Elerian harvest goddess, Irix Irimentari has watched vigilantly over the fields of her worshippers since before the Second Age. she favors alcoholic beverages as tribute, and has been known to perform blessings for sorcerers when sufficiently inebriated. she harbors a particular hatred of daemons and those who spill the blood of farmers.

			legend says that a barbarian lord high on opium once wandered into a temple of Irix and left the severed head of a local shepherd on the her altar. this desecration so enraged the goddess that the barbarian's entire tribe soon starved horribly to death, their crops refusing to take root, and their stolen breads turning to ash in their mouths.

			in the mystic arts, she is the patron of Alchemy. it is said that Irix
			Irimentari herself invented alchemy when she brewed the first mead.
		]];
		laziness = 5;
		stinginess = 3;
		generosity = 10;
		color = {214, 255, 146};
		idol = {
			desc = "Harvest Idol";
................................................................................
				"flowerpot:flowers_chrysanthemum_green";
				"flowerpot:flowers_dandelion_white";
				"flowerpot:flowers_dandelion_yellow";
			}};
			["sorcery:dagger"] = {17, "sorcery:dagger_consecrated"};
			["sorcery:oil_mystic"] = {9, "sorcery:oil_purifying"};
			["sorcery:potion_water"] = {4, "sorcery:holy_water"};
			["default:paper"] = function(ctx)
				local stack = ItemStack('sorcery:recipe')
				local mode = select(2,L.tbl.pick{'cook','craft','infuse','grind'})
				sorcery.cookbook.setrecipe(stack, mode, nil, {
					pred = function(c)
						local me = ctx.god
						if c.mod == 'farming' or
							minetest.get_item_group(c.item, 'sorcery_potion') ~= 0 or
							minetest.get_item_group(c.item, 'sorcery_oil') ~= 0 or
							minetest.get_item_group(c.item, 'sorcery_grease') ~= 0 or
							minetest.get_item_group(c.item, 'sorcery_extract') ~= 0 or
							me.sacrifice [c.item] or
							me.consecrate[c.item] then
							print(' !! accepted')
							return true end
						print(' -- rejected')
					end;
				})
				return 1, stack
			end;
			-- ["default:steel_ingot"] = {15, "sorcery:holy_token_harvest"};
		};
		sacrifice = {
			-- beattitudes
			["farming:straw"           ] = 1;
			["farming:bread_slice"     ] = 1;
			["farming:bread"           ] = 2;
................................................................................
				Force = {favor=50, cost=5, chance=7};
				Longevity = {favor=65, cost=7, chance=3};
			};
			tools = {
				rend = {favor=80, cost=15, chance=20};
			};
		};
		laziness = 2;
		generosity = 4;
		stinginess = 9;
		idol = {
			desc = "Blood Idol";
			width = 0.7;
			height = 1.3;
			tex = {

Modified data/spells.lua from [2d770c6227] to [f6d0b8e638].

466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
			if div.restrict and not div.restrict(ctx) then
				return false
			end

			local dst
			local bonus = math.floor(ctx.stats.power or 1) 
			if div.mode == 'any' then
				local lst = sorcery.lib.tbl.cshuf(div.give)
				dst = function(i) return lst[i] end
			elseif div.mode == 'random' then
				dst = function() return tblroll(bonus,div.give) end
			elseif div.mode == 'set' then
				dst = function(i) return div.give[i] end
			elseif div.mode == 'all' then
				dst = function() return div.give end







|







466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
			if div.restrict and not div.restrict(ctx) then
				return false
			end

			local dst
			local bonus = math.floor(ctx.stats.power or 1) 
			if div.mode == 'any' then
				local lst = sorcery.lib.tbl.scramble(div.give)
				dst = function(i) return lst[i] end
			elseif div.mode == 'random' then
				dst = function() return tblroll(bonus,div.give) end
			elseif div.mode == 'set' then
				dst = function(i) return div.give[i] end
			elseif div.mode == 'all' then
				dst = function() return div.give end

Modified entities.lua from [5b6d6366ce] to [76a0b3b59c].

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
		::collcheck:: do
			-- if no collision then return end
			-- local nname = minetest.get_node(pos).name 
			-- if nname == 'air' or minetest.registered_nodes[nname].walkable ~= true then return
			-- elseif nname == 'ignore' then goto destroy end
			-- else fall through to explode
			if collision then -- since 5.3 only!!
				print('collision detected!',dump(collision))
				if collision.collides == false then return end
				if #collision.collisions > 0 then
					local col = collision.collisions[1]
					if col.node_pos then
						pos = col.node_pos
					elseif col.object then
						pos = col.object:get_pos()







<







78
79
80
81
82
83
84

85
86
87
88
89
90
91
		::collcheck:: do
			-- if no collision then return end
			-- local nname = minetest.get_node(pos).name 
			-- if nname == 'air' or minetest.registered_nodes[nname].walkable ~= true then return
			-- elseif nname == 'ignore' then goto destroy end
			-- else fall through to explode
			if collision then -- since 5.3 only!!

				if collision.collides == false then return end
				if #collision.collisions > 0 then
					local col = collision.collisions[1]
					if col.node_pos then
						pos = col.node_pos
					elseif col.object then
						pos = col.object:get_pos()

Modified infuser.lua from [6c01c64259] to [0b21397e89].

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
...
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
...
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
...
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
end

local elixir_can_apply = function(elixir, potion)
	-- accepts an elixir def and potion def
	if elixir        == nil or
	   potion        == nil then return false end

	if elixir.apply and potion.on_use then
		-- the ingredient is an elixir and at least one potion
		-- is a fully enchanted, usable potion
		if elixir.flag and potion._proto and
		   potion._proto['no_' .. elixir.flag] == true then
			-- does the elixir have a property used to denote
			-- compatibility? if so, check the potion to see if it's
			-- marked as incompatible
			return false
		else
			return true
		end
................................................................................
	return false
end

local effects_table = function(potion)
	local meta = potion:get_meta()
	local tbl = {}
	for k,v in pairs(sorcery.data.elixirs) do
		if not v.flag then goto skip end
		local val = meta:get_int(v.flag)
		if val > 0 then
			local aff, title, desc = v.describe(potion)
			if val > 3 then title = title .. ' x' .. val
			elseif val == 3 then title = 'thrice-' .. title
			elseif val == 2 then title = 'twice-' .. title
			end
			tbl[#tbl + 1] = {
................................................................................

sorcery.alchemy.elixir_apply = function(elixir, potion)
	if not potion then return end
	local pdef = potion:get_definition()
	if elixir_can_apply(elixir, pdef) then
		elixir.apply(potion, pdef._proto)
		potion:get_meta():set_string('description', sorcery.lib.ui.tooltip {
			title = pdef._proto.name .. ' Draught';
			desc = pdef._proto.desc;
			color = sorcery.lib.color(pdef._proto.color):readable();
			props = effects_table(potion);
		});
	end
	return potion
end
................................................................................
			elseif r.enhance then
				if fx.onenhance then out = fx.onenhance {
					pos = pos;
					stack = out;
					potion = r.proto;
					elixir = r.elixir;
				} end
				log.act(dump(r))
				log.act(string.format('an infuser at %s has enhanced a %s potion with a %s elixir',
					minetest.pos_to_string(pos), out:get_name(), infusion[1]:get_name()))
			end
			inv:set_stack('potions',i,discharge(out))
		end

		inv:set_stack('infusion',1,residue)







|
|
|
|
<







 







|
|







 







|







 







<







101
102
103
104
105
106
107
108
109
110
111

112
113
114
115
116
117
118
...
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
...
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
...
300
301
302
303
304
305
306

307
308
309
310
311
312
313
end

local elixir_can_apply = function(elixir, potion)
	-- accepts an elixir def and potion def
	if elixir        == nil or
	   potion        == nil then return false end

	if elixir.apply and potion._proto and potion._proto.quals then
		-- the ingredient is an elixir and at least one potion has a
		-- quality that can be enhanced
		if elixir.qual and potion._proto and not potion._proto.quals[elixir.qual] then

			-- does the elixir have a property used to denote
			-- compatibility? if so, check the potion to see if it's
			-- marked as incompatible
			return false
		else
			return true
		end
................................................................................
	return false
end

local effects_table = function(potion)
	local meta = potion:get_meta()
	local tbl = {}
	for k,v in pairs(sorcery.data.elixirs) do
		if not v.qual then goto skip end
		local val = meta:get_int(v.qual)
		if val > 0 then
			local aff, title, desc = v.describe(potion)
			if val > 3 then title = title .. ' x' .. val
			elseif val == 3 then title = 'thrice-' .. title
			elseif val == 2 then title = 'twice-' .. title
			end
			tbl[#tbl + 1] = {
................................................................................

sorcery.alchemy.elixir_apply = function(elixir, potion)
	if not potion then return end
	local pdef = potion:get_definition()
	if elixir_can_apply(elixir, pdef) then
		elixir.apply(potion, pdef._proto)
		potion:get_meta():set_string('description', sorcery.lib.ui.tooltip {
			title = string.format('%s %s', pdef._proto.name, pdef._proto.kind.label);
			desc = pdef._proto.desc;
			color = sorcery.lib.color(pdef._proto.color):readable();
			props = effects_table(potion);
		});
	end
	return potion
end
................................................................................
			elseif r.enhance then
				if fx.onenhance then out = fx.onenhance {
					pos = pos;
					stack = out;
					potion = r.proto;
					elixir = r.elixir;
				} end

				log.act(string.format('an infuser at %s has enhanced a %s potion with a %s elixir',
					minetest.pos_to_string(pos), out:get_name(), infusion[1]:get_name()))
			end
			inv:set_stack('potions',i,discharge(out))
		end

		inv:set_stack('infusion',1,residue)

Modified init.lua from [58ff591804] to [3b59932455].

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40





41
42
43
44
45
46
47
	argjoin(arg, nxt, ...)
		if arg and not nxt then return tostring(arg) end
		if not arg then return "(nil)" end
		return tostring(arg) .. ' ' .. argjoin(nxt, ...)
	end

	local logger = function(module)
		local emit = function(lvl)
			return function(...)
				if module then
					minetest.log(lvl,string.format('[%s :: %s] %s',selfname,module,argjoin(...)))
				else
					minetest.log(lvl,string.format('[%s] %s',selfname,argjoin(...)))
				end
			end
		end
		return {
			info = emit('info');
			warn = emit('warning');
			err = emit('error');
			act = emit('action');
		}





	end;

	local stage = function(s,...)
		logger().info('entering stage',s)
		local f = sorcery.cfg(s .. '.lua')
		if test(f) then return loadfile(f)(...) or true end
		return false







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
	argjoin(arg, nxt, ...)
		if arg and not nxt then return tostring(arg) end
		if not arg then return "(nil)" end
		return tostring(arg) .. ' ' .. argjoin(nxt, ...)
	end

	local logger = function(module)
		local lg = {}
		local setup = function(fn, lvl)
			lvl = lvl or fn
			local function emit(...)
				local call = (fn == 'fatal') and error
					or function(str) minetest.log(lvl, str) end
				if module
					then call(string.format('[%s :: %s] %s',selfname,module,argjoin(...)))
					else call(string.format('[%s] %s',selfname,argjoin(...)))
				end
			end
			lg[fn       ] = function(...) emit(...)                end
			lg[fn .. 'f'] = function(...) emit(string.format(...)) end -- convenience fn
		end
		setup('info')
		setup('warn','warning')
		setup('err','error')
		setup('act','action')
		setup('fatal')
		return lg
	end;

	local stage = function(s,...)
		logger().info('entering stage',s)
		local f = sorcery.cfg(s .. '.lua')
		if test(f) then return loadfile(f)(...) or true end
		return false

Modified keg.lua from [7838002de4] to [5c653d450d].

185
186
187
188
189
190
191
192
193
194
195
196
197
})

minetest.register_craft {
	output = "sorcery:keg";
	recipe = {
		{'','screwdriver:screwdriver',''};
		{'sorcery:screw_bronze', 'sorcery:tap', 'sorcery:screw_bronze'};
		{'', 'xdecor:barrel', ''};
	};
	replacements = {
		{'screwdriver:screwdriver', 'screwdriver:screwdriver'};
	};
}







|





185
186
187
188
189
190
191
192
193
194
195
196
197
})

minetest.register_craft {
	output = "sorcery:keg";
	recipe = {
		{'','screwdriver:screwdriver',''};
		{'sorcery:screw_bronze', 'sorcery:tap', 'sorcery:screw_bronze'};
		{'sorcery:screw_bronze', 'xdecor:barrel', 'sorcery:screw_bronze'};
	};
	replacements = {
		{'screwdriver:screwdriver', 'screwdriver:screwdriver'};
	};
}

Modified lib/node.lua from [d8278e1811] to [087023acf7].

315
316
317
318
319
320
321


322
323
324
325
326
327
328
			if n.name == 'ignore' then
				minetest.load_area(sum)
				n = minetest.get_node(sum)
			end
			fn(sum, n)
		end
	end;


	
	force = force;

	-- when items have already been removed; notify cannot be relied on
	-- to reach the entire network; this function accounts for the gap
	notifyneighbors = function(pos)
		sorcery.lib.node.forneighbor(pos, sorcery.ley.txofs, function(sum,node)







>
>







315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
			if n.name == 'ignore' then
				minetest.load_area(sum)
				n = minetest.get_node(sum)
			end
			fn(sum, n)
		end
	end;

	amass = amass;
	
	force = force;

	-- when items have already been removed; notify cannot be relied on
	-- to reach the entire network; this function accounts for the gap
	notifyneighbors = function(pos)
		sorcery.lib.node.forneighbor(pos, sorcery.ley.txofs, function(sum,node)

Modified lib/tbl.lua from [990a6e8caf] to [0fb14e1ea3].

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
..
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
	for i = #list, 2, -1 do
		local j = math.random(i)
		list[i], list[j] = list[j], list[i]
	end
	return list
end

fn.cshuf = function(list)
	return fn.shuffle(table.copy(list))
end

fn.urnd = function(min,max)
	local r = {}
	for i=min,max do r[1 + (i - min)] = i end
	fn.shuffle(r)
................................................................................
			hash[v] = true
			new[#new+1] = v
		end
	end
	return new
end

fn.scramble = function(list)
	local new = table.copy(list)
	fn.shuffle(new)
	return new
end

fn.copy = function(t)
	local new = {}
	for i,v in pairs(t) do new[i] = v end
	setmetatable(new,getmetatable(t))
	return new
end








|







 







<
<
<
<
<
<







4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
..
27
28
29
30
31
32
33






34
35
36
37
38
39
40
	for i = #list, 2, -1 do
		local j = math.random(i)
		list[i], list[j] = list[j], list[i]
	end
	return list
end

fn.scramble = function(list)
	return fn.shuffle(table.copy(list))
end

fn.urnd = function(min,max)
	local r = {}
	for i=min,max do r[1 + (i - min)] = i end
	fn.shuffle(r)
................................................................................
			hash[v] = true
			new[#new+1] = v
		end
	end
	return new
end







fn.copy = function(t)
	local new = {}
	for i,v in pairs(t) do new[i] = v end
	setmetatable(new,getmetatable(t))
	return new
end

Modified liquid.lua from [04ca2e071b] to [6a40bd16e3].

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
...
161
162
163
164
165
166
167










168
169
170
171
172
173
174

sorcery.liquid.mktrough = function(liq)
	-- troughs are used for collecting liquid from the environment,
	-- like rainwater and tree sap. they hold twice as much as a bucket
	local Q = constants.glasses_per_bottle
	local trough_mkid = function(l,i)
		if type(l) == 'string' then l = sorcery.register.liquid.db[l] end
		if not l or not i then return 'sorcery:trough' end
		return string.format('%s:trough_%s_%u', l.mod,l.sid,i)
	end
	local lid = function(l) return trough_mkid(liq, l) end

	local M = constants.bottles_per_trough
	local mkbox = function(lvl)
		local pxl = function(tbl) -- for mapping to txcoords
................................................................................
	end
end
sorcery.liquid.mktrough()

sorcery.liquid.measure_default = function(amt)
	return string.format('%s drams', amt*constants.drams_per_glass)
end










sorcery.liquid.register = function(liq)
	local fmt = string.format
	local Q = constants.glasses_per_bottle
	liq.sid = liq.sid or liq.id:gsub('^[^:]+:','')
	liq.mod = liq.mod or liq.id:gsub('^([^:]+):.*','%1')
	if not liq.measure then
		liq.measure = sorcery.liquid.measure_default







|







 







>
>
>
>
>
>
>
>
>
>







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
...
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

sorcery.liquid.mktrough = function(liq)
	-- troughs are used for collecting liquid from the environment,
	-- like rainwater and tree sap. they hold twice as much as a bucket
	local Q = constants.glasses_per_bottle
	local trough_mkid = function(l,i)
		if type(l) == 'string' then l = sorcery.register.liquid.db[l] end
		if (not l) or (not i) or i < 1 then return 'sorcery:trough' end
		return string.format('%s:trough_%s_%u', l.mod,l.sid,i)
	end
	local lid = function(l) return trough_mkid(liq, l) end

	local M = constants.bottles_per_trough
	local mkbox = function(lvl)
		local pxl = function(tbl) -- for mapping to txcoords
................................................................................
	end
end
sorcery.liquid.mktrough()

sorcery.liquid.measure_default = function(amt)
	return string.format('%s drams', amt*constants.drams_per_glass)
end

sorcery.liquid.container = function(liq, ctr)
	return liq.containers[({
		bottle = 'vessels:glass_bottle';
		glass = 'vessels:drinking_glass';
		keg = 'sorcery:keg';
		trough = 'sorcery:trough';
	})[ctr] or ctr]
end

sorcery.liquid.register = function(liq)
	local fmt = string.format
	local Q = constants.glasses_per_bottle
	liq.sid = liq.sid or liq.id:gsub('^[^:]+:','')
	liq.mod = liq.mod or liq.id:gsub('^([^:]+):.*','%1')
	if not liq.measure then
		liq.measure = sorcery.liquid.measure_default

Modified potions.lua from [a4fa9f9b09] to [0b54227d82].

52
53
54
55
56
57
58

59
60
61
62
63
64
65
...
102
103
104
105
106
107
108




109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

129
130
131
132
133
134
135
...
142
143
144
145
146
147
148




149
150

151
152
153
154
155
156
157
...
201
202
203
204
205
206
207




208
209
210
211

212
213
214
215
216
217
218
...
236
237
238
239
240
241
242




243
244
245
246

247

248
249
250
251
252
253
254
255

256
257
258
259
260
261
262
...
302
303
304
305
306
307
308

309
310


sorcery.register_oil = function(name,label,desc,color,imgvariant,extra)
	local image = 'xdecor_bowl.png^(sorcery_oil_' .. (imgvariant or 'dull') .. '.png^[colorize:'..tostring(color)..':140)'
	sorcery.register.residue.link('sorcery:' .. name, 'xdecor:bowl')
	extra.description = label;
	extra.inventory_image = image;
	if not extra.groups then extra.groups = {} end

	minetest.register_craftitem('sorcery:' .. name, extra)
end

sorcery.register_potion('blood', 'Blood', 'A bottle of sacrificial blood, imbued with stolen (or perhaps donated) life force', u.color(219,19,14), nil, nil, {
	_sorcery = {
		life_store = 4;
		container = {
................................................................................
			output = 'sorcery:' .. id;
			_proto = proto;
		}
	end
end

-- for n,v in pairs(sorcery.data.potions) do




sorcery.register.potions.foreach('sorcery:mknodes',{},function(n,v)
	local color = u.color(v.color)
	local kind = v.style
	local glow = v.glow
	local id = 'potion_' .. string.lower(n)
	local desc = 'A ' .. ((glow and 'glowing ') or '') ..
		'bottle of ' .. string.lower(n) .. 
		((kind == 'sparkle' and ', fiercely bubbling') or '') ..
		' liquid'
	local fullname = n .. ' Potion'
	sorcery.register.liquid.link('sorcery:'..id, {
		name = 'Serene Potion';
		color = v.color;
		proto = v;
		kind = 'sorcery:potion';
		measure = function(amt) return string.format('%s draughts', amt / 3) end;
		containers = {
			['vessels:glass_bottle'] = 'sorcery:' .. id;
		};
	})

	sorcery.register_potion(id, fullname, desc, color, kind, glow, {
		groups = {
			sorcery_potion = 1;
			sorcery_magical = 1;
		};
		_proto = v;
		_sorcery = {
................................................................................
			};
		};
	})
	create_infusion_recipe(id,v,'sorcery:potion_serene',{data=v,name=fullname})
end)

-- for n,potion in pairs(sorcery.data.draughts) do




sorcery.register.draughts.foreach('sorcery:mknodes',{},function(n,potion)
	local name = 'draught_' .. n

	local behavior = {
		_proto = potion;
		groups = {
			sorcery_potion = 2;
			sorcery_draught = 1;
			sorcery_magical = 1;
			sorcery_usable_magic = 1;
................................................................................
		potion.style or 'dull',
		potion.glow or 0,
		behavior)
	create_infusion_recipe(name,potion,'sorcery:potion_luminous',{data=potion,name=fullname})
end)

-- for n,elixir in pairs(sorcery.data.elixirs) do




sorcery.register.elixirs.foreach('sorcery:mknodes',{},function(n,elixir)
	local color = u.color(elixir.color)
	local id = 'elixir_' .. string.lower(n)
	local fullname = 'Elixir of ' .. n

	sorcery.register_potion(id, fullname, nil, color, 'dull', false, {
		_proto = elixir;
		groups = {
			sorcery_elixir = 1;
			sorcery_magical = 1;
		};
	})
................................................................................
	local kind = v.style
	sorcery.register_oil('grease_' .. n, u.str.capitalize(n) .. ' Grease', nil, color, kind, {
		groups = { sorcery_grease = 1 }
	})
end)

-- for n,v in pairs(sorcery.data.philters) do




sorcery.register.philters.foreach('sorcery:mknodes',{},function(n,v)
	local color = u.color(v.color)
	local id = 'philter_' .. n
	local name = v.name or u.str.capitalize(n)

	local fullname = name .. ' Philter'

	sorcery.register_potion(id, fullname, v.desc, color, 'sparkle',v.glow or 4, {
		_proto = v;
		_protoname = n;
		groups = {
			sorcery_magical = 1;
			sorcery_philter = 1;
		};
	})

	create_infusion_recipe(id,v,'sorcery:potion_viscous',{data=v,name=fullname})
end)

-- for n,v in pairs(sorcery.data.extracts) do
sorcery.register.extracts.foreach('sorcery:mknodes',{},function(n,v)
	local item = v[1]
	local color = u.color(v[2])
................................................................................
				{"farming:mortar_pestle", "farming:mortar_pestle"};
			};
		}
	end
	-- need a relatively pure alcohol for this, tho other alcohols can be used
	-- for potionmaking in other ways
	add_alcohol('farming:bottle_ethanol')

	add_alcohol('wine:glass_vodka')
end)








>







 







>
>
>
>











|








>







 







>
>
>
>


>







 







>
>
>
>




>







 







>
>
>
>




>

>








>







 







>
|
|
>
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
...
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
...
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
...
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
...
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
...
325
326
327
328
329
330
331
332
333
334
335

sorcery.register_oil = function(name,label,desc,color,imgvariant,extra)
	local image = 'xdecor_bowl.png^(sorcery_oil_' .. (imgvariant or 'dull') .. '.png^[colorize:'..tostring(color)..':140)'
	sorcery.register.residue.link('sorcery:' .. name, 'xdecor:bowl')
	extra.description = label;
	extra.inventory_image = image;
	if not extra.groups then extra.groups = {} end
	extra.groups.sorcery_oil = 1
	minetest.register_craftitem('sorcery:' .. name, extra)
end

sorcery.register_potion('blood', 'Blood', 'A bottle of sacrificial blood, imbued with stolen (or perhaps donated) life force', u.color(219,19,14), nil, nil, {
	_sorcery = {
		life_store = 4;
		container = {
................................................................................
			output = 'sorcery:' .. id;
			_proto = proto;
		}
	end
end

-- for n,v in pairs(sorcery.data.potions) do
local kind_potion = {
	label = 'Potion';
	kind = 'A mystical liquid crucial to the art of alchemy';
}
sorcery.register.potions.foreach('sorcery:mknodes',{},function(n,v)
	local color = u.color(v.color)
	local kind = v.style
	local glow = v.glow
	local id = 'potion_' .. string.lower(n)
	local desc = 'A ' .. ((glow and 'glowing ') or '') ..
		'bottle of ' .. string.lower(n) .. 
		((kind == 'sparkle' and ', fiercely bubbling') or '') ..
		' liquid'
	local fullname = n .. ' Potion'
	sorcery.register.liquid.link('sorcery:'..id, {
		name = fullname;
		color = v.color;
		proto = v;
		kind = 'sorcery:potion';
		measure = function(amt) return string.format('%s draughts', amt / 3) end;
		containers = {
			['vessels:glass_bottle'] = 'sorcery:' .. id;
		};
	})
	v.kind = kind_potion;
	sorcery.register_potion(id, fullname, desc, color, kind, glow, {
		groups = {
			sorcery_potion = 1;
			sorcery_magical = 1;
		};
		_proto = v;
		_sorcery = {
................................................................................
			};
		};
	})
	create_infusion_recipe(id,v,'sorcery:potion_serene',{data=v,name=fullname})
end)

-- for n,potion in pairs(sorcery.data.draughts) do
local kind_draught = {
	label = 'Draught';
	desc = 'A drink that will suffuse your body and spirit with mystic energies';
}
sorcery.register.draughts.foreach('sorcery:mknodes',{},function(n,potion)
	local name = 'draught_' .. n
	potion.kind = kind_draught
	local behavior = {
		_proto = potion;
		groups = {
			sorcery_potion = 2;
			sorcery_draught = 1;
			sorcery_magical = 1;
			sorcery_usable_magic = 1;
................................................................................
		potion.style or 'dull',
		potion.glow or 0,
		behavior)
	create_infusion_recipe(name,potion,'sorcery:potion_luminous',{data=potion,name=fullname})
end)

-- for n,elixir in pairs(sorcery.data.elixirs) do
local kind_elixir = {
	label = 'Elixir';
	desc = 'A special kind of potion that enhances the particular qualities of other alchemical brews';
}
sorcery.register.elixirs.foreach('sorcery:mknodes',{},function(n,elixir)
	local color = u.color(elixir.color)
	local id = 'elixir_' .. string.lower(n)
	local fullname = 'Elixir of ' .. n
	elixir.kind = kind_elixir;
	sorcery.register_potion(id, fullname, nil, color, 'dull', false, {
		_proto = elixir;
		groups = {
			sorcery_elixir = 1;
			sorcery_magical = 1;
		};
	})
................................................................................
	local kind = v.style
	sorcery.register_oil('grease_' .. n, u.str.capitalize(n) .. ' Grease', nil, color, kind, {
		groups = { sorcery_grease = 1 }
	})
end)

-- for n,v in pairs(sorcery.data.philters) do
local kind_philter = {
	label = 'Philter';
	desc = 'A special kind of potion that wooden rods can be soaked in to imbue them with special powers and transform them into wands';
}
sorcery.register.philters.foreach('sorcery:mknodes',{},function(n,v)
	local color = u.color(v.color)
	local id = 'philter_' .. n
	local name = v.name or u.str.capitalize(n)
	if not v.name then v.name = name end
	local fullname = name .. ' Philter'
	v.kind = kind_philter
	sorcery.register_potion(id, fullname, v.desc, color, 'sparkle',v.glow or 4, {
		_proto = v;
		_protoname = n;
		groups = {
			sorcery_magical = 1;
			sorcery_philter = 1;
		};
	})
	v.quals = {force = true};
	create_infusion_recipe(id,v,'sorcery:potion_viscous',{data=v,name=fullname})
end)

-- for n,v in pairs(sorcery.data.extracts) do
sorcery.register.extracts.foreach('sorcery:mknodes',{},function(n,v)
	local item = v[1]
	local color = u.color(v[2])
................................................................................
				{"farming:mortar_pestle", "farming:mortar_pestle"};
			};
		}
	end
	-- need a relatively pure alcohol for this, tho other alcohols can be used
	-- for potionmaking in other ways
	add_alcohol('farming:bottle_ethanol')
	if minetest.get_modpath('wine') then
		add_alcohol('wine:glass_vodka')
	end
end)

Modified runeforge.lua from [69ba246df3] to [65aa0a1ed6].

36
37
38
39
40
41
42

43
44
45

46
47
48
49
50
51
52
..
55
56
57
58
59
60
61
62
63
64





65
66
67
68
69
70


71
72
73
74

75
76
77
78
79
80
81
82
83
84
85
86
87
...
255
256
257
258
259
260
261

262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
...
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
		};
		supreme  = {grade = 6, name = 'Supreme';  infusion = 'sorcery:powder_levitanium';
			dist = { Fragile = 0, Weak = 0,    Ordinary = 1,   Pristine = 0.7,  Sublime = 0.4 };
		};
	};
}
local calc_phial_props = function(phial) --> mine interval: float, time factor: float

	local g = phial:get_definition()._proto.data.grade
	local i = constants.rune_mine_interval 
	local fac = (g-1) / 5

	return i - ((i*0.5) * fac), 0.5 * fac
end
sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
	local id = 'sorcery:rune_' .. name
	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
	rune.item = id
	local c = sorcery.lib.color(rune.tone)
................................................................................
		short_description = rune.name .. ' Rune';
		inventory_image = rune.image;
		stack_max = 1;
		groups = {
			sorcery_rune = 1;
			not_in_creative_inventory = 1;
		};
		_proto = { id = name, data = rune; };
	})
end)






for name,p in pairs(constants.phial_kinds) do
	local f = string.format
	local color = sorcery.lib.color(142,232,0)
	local fac = p.grade / 6
	local id = f('phial_%s', name);


	sorcery.register_potion_tbl {
		name = id;
		label = f('%s Phial',p.name);
		desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed and how much energy is required by the process, and affects your odds of getting a high-quality rune.";

		color = color:brighten(1 + fac*0.5);
		imgvariant = (fac >= 5) and 'sparkle' or 'dull';
		glow = 5+p.grade;
		extra = {
			groups = { sorcery_phial = p.grade };
			_proto = { id = name, data = p };
		};
	}
	sorcery.register.infusions.link {
		infuse = p.infusion;
		into = 'sorcery:potion_subtle';
		output = 'sorcery:'..id;
	}
................................................................................
	local pow_min = l.self.powerdraw >= l.self.minpower
	local pow_max = l.self.powerdraw >= l.self.maxpower
	local has_phial = function() return not i:is_empty('phial') end

	if time and has_phial() and pow_min and not probe.disjunction then -- roll for runes
		local phial = i:get_stack('phial',1)
		local int, powerfac = calc_phial_props(phial)

		local rolls = math.floor(time/int)
		local newrunes = {}
		for _=1,rolls do
			local choices = {}
			for name,rune in pairs(sorcery.data.runes) do
				-- print('considering',name)
				-- print('-- power',rune.minpower,(rune.minpower*powerfac)*time,'//',l.self.powerdraw,l.self.powerdraw/time,'free',l.freepower,'max',l.maxpower)
				if (rune.minpower*powerfac)*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then
					choices[#choices + 1] = rune
				end
			end
			if #choices > 0 then
				-- if multiple runes were rolled up, be nice to the player
				-- and pick the rarest one to give them
				local rare, choice = 0
................................................................................
		local wrench if not inv:is_empty('wrench') then
			wrench = inv:get_stack('wrench',1):get_definition()._proto
		end
		if fl == 'cache' then
			if probe.disjunction then return 0 end
			if tl == 'cache' then return 1 end
			if tl == 'active' and inv:is_empty('active') then
				print(dump(wrench))
				if wrench and wrench.powers.imbue and not inv:is_empty('amulet') then
					local amulet = inv:get_stack('amulet',1)
					local rune = inv:get_stack(fl,fi)
					local runeid = rune:get_definition()._proto.id
					local runegrade = rune:get_meta():get_int('rune_grade')
					if sorcery.data.runes[runeid].amulets[amulet:get_definition()._sorcery.amulet.base] then
						local spell do -- haaaack







>



>







 







|


>
>
>
>
>






>
>


|
<
>





|







 







>







|







 







<







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
..
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

83
84
85
86
87
88
89
90
91
92
93
94
95
96
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
...
501
502
503
504
505
506
507

508
509
510
511
512
513
514
		};
		supreme  = {grade = 6, name = 'Supreme';  infusion = 'sorcery:powder_levitanium';
			dist = { Fragile = 0, Weak = 0,    Ordinary = 1,   Pristine = 0.7,  Sublime = 0.4 };
		};
	};
}
local calc_phial_props = function(phial) --> mine interval: float, time factor: float
	local m = phial:get_meta()
	local g = phial:get_definition()._proto.data.grade
	local i = constants.rune_mine_interval 
	local fac = (g-1) / 5
	fac = fac + 0.4 * m:get_int('speed')
	return i - ((i*0.5) * fac), 0.5 * fac
end
sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
	local id = 'sorcery:rune_' .. name
	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
	rune.item = id
	local c = sorcery.lib.color(rune.tone)
................................................................................
		short_description = rune.name .. ' Rune';
		inventory_image = rune.image;
		stack_max = 1;
		groups = {
			sorcery_rune = 1;
			not_in_creative_inventory = 1;
		};
		_proto = { id = name, data = rune };
	})
end)

local phkind = {
	label = 'Phial';
	desc = 'An alchemical substance which rune forges consume while coalescing new runes';
}

for name,p in pairs(constants.phial_kinds) do
	local f = string.format
	local color = sorcery.lib.color(142,232,0)
	local fac = p.grade / 6
	local id = f('phial_%s', name);
	local fname = f('%s Phial',p.name);
	local desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed and how much energy is required by the process, and affects your odds of getting a high-quality rune."
	sorcery.register_potion_tbl {
		name = id;
		label = fname;

		desc = desc;
		color = color:brighten(1 + fac*0.5);
		imgvariant = (fac >= 5) and 'sparkle' or 'dull';
		glow = 5+p.grade;
		extra = {
			groups = { sorcery_phial = p.grade };
			_proto = { id = name, desc = desc, name = p.name, kind = phkind, data = p, quals = {force = true, speed = true}, color = color };
		};
	}
	sorcery.register.infusions.link {
		infuse = p.infusion;
		into = 'sorcery:potion_subtle';
		output = 'sorcery:'..id;
	}
................................................................................
	local pow_min = l.self.powerdraw >= l.self.minpower
	local pow_max = l.self.powerdraw >= l.self.maxpower
	local has_phial = function() return not i:is_empty('phial') end

	if time and has_phial() and pow_min and not probe.disjunction then -- roll for runes
		local phial = i:get_stack('phial',1)
		local int, powerfac = calc_phial_props(phial)
		local pf = phial:get_meta():get_int('force')
		local rolls = math.floor(time/int)
		local newrunes = {}
		for _=1,rolls do
			local choices = {}
			for name,rune in pairs(sorcery.data.runes) do
				-- print('considering',name)
				-- print('-- power',rune.minpower,(rune.minpower*powerfac)*time,'//',l.self.powerdraw,l.self.powerdraw/time,'free',l.freepower,'max',l.maxpower)
				if (rune.minpower*powerfac)*time <= l.self.powerdraw and math.random(rune.rarity - pf) == 1 then
					choices[#choices + 1] = rune
				end
			end
			if #choices > 0 then
				-- if multiple runes were rolled up, be nice to the player
				-- and pick the rarest one to give them
				local rare, choice = 0
................................................................................
		local wrench if not inv:is_empty('wrench') then
			wrench = inv:get_stack('wrench',1):get_definition()._proto
		end
		if fl == 'cache' then
			if probe.disjunction then return 0 end
			if tl == 'cache' then return 1 end
			if tl == 'active' and inv:is_empty('active') then

				if wrench and wrench.powers.imbue and not inv:is_empty('amulet') then
					local amulet = inv:get_stack('amulet',1)
					local rune = inv:get_stack(fl,fi)
					local runeid = rune:get_definition()._proto.id
					local runegrade = rune:get_meta():get_int('rune_grade')
					if sorcery.data.runes[runeid].amulets[amulet:get_definition()._sorcery.amulet.base] then
						local spell do -- haaaack

Modified sorcery.md from [5a3ebfda49] to [17b053c9d3].

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 * 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. i have yet to figure out how i want to go about introducing users to the lore, but for now there's some information on the wiki and some things you can glean from creative mode; otherwise you'll have to read the source code.

# 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.







|







57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
 * 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. i have yet to figure out how i want to go about introducing users to the lore (although you can occasionally find random recipes in dungeon chests, and gods can be coaxed to bestow recipes and cookbooks), but for now there's some information on the wiki and some things you can glean from creative mode; otherwise you'll have to read the source code.

# 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.

Modified spell.lua from [92125c4f2c] to [4f39d5643f].

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
...
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
--    disjunction is cast on one of them, they will be removed from the
--    table. each entry should have at least a 'player' field; they can
--    also contain any other data useful to the spell. if a subject has
--    a 'disjoin' field it must be a function called when they are removed
--    from the list of spell targets.
--  * caster is the individual who cast the spell, if any. a disjunction
--    against their person will totally disrupt the spell.
local log = function(...) sorcery.log('spell',...) end

-- FIXME saving object refs is iffy, find a better alternative
sorcery.spell = {
	active = {}
}

local get_spell_positions = function(spell)
................................................................................
		local t if type(when) == 'number' then
			t = s.duration * when
		else
			t = (s.duration * (when.whence or 0)) + when.secs
		end
		if t then return math.min(s.duration,math.max(0,t)) end

		log('invalid timespec ' .. dump(when))
		return 0
	end
	s.queue = function(when,fn)
		local elapsed = s.starttime and minetest.get_server_uptime() - s.starttime or 0
		local timepast = interpret_timespec(when)
		if not timepast then timepast = 0 end
		local timeleft = s.duration - timepast
		local howlong = (s.delay + timepast) - elapsed
		if howlong < 0 then
			log('cannot time-travel! queue() called with `when` specifying timepoint that has already passed')
			howlong = 0
		end
		s.jobs[#s.jobs+1] = minetest.after(howlong, function()
			-- this is somewhat awkward. since we're using a non-polling approach, we
			-- need to find a way to account for a caster or subject walking into an
			-- existing antimagic field, or someone with an existing antimagic aura
			-- walking into range of the anchor. so every time a spell effect would
................................................................................
					termqueued = true
					s.queue(1,function(s,...)
						what(s,...)
						if s.terminate then s:terminate() end
						sorcery.spell.active[myid] = nil
					end)
				else
					log('multiple final timeline events not possible, ignoring')
				end
			elseif when == 0 and s.disjunction then
				startqueued = true
				s.queue(when_raw,function(...)
					perform_disjunction_calls()
					what(...)
				end)







|







 







|









|







 







|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
...
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
--    disjunction is cast on one of them, they will be removed from the
--    table. each entry should have at least a 'player' field; they can
--    also contain any other data useful to the spell. if a subject has
--    a 'disjoin' field it must be a function called when they are removed
--    from the list of spell targets.
--  * caster is the individual who cast the spell, if any. a disjunction
--    against their person will totally disrupt the spell.
local log = sorcery.logger 'spell'

-- FIXME saving object refs is iffy, find a better alternative
sorcery.spell = {
	active = {}
}

local get_spell_positions = function(spell)
................................................................................
		local t if type(when) == 'number' then
			t = s.duration * when
		else
			t = (s.duration * (when.whence or 0)) + when.secs
		end
		if t then return math.min(s.duration,math.max(0,t)) end

		log.err('invalid timespec ' .. dump(when))
		return 0
	end
	s.queue = function(when,fn)
		local elapsed = s.starttime and minetest.get_server_uptime() - s.starttime or 0
		local timepast = interpret_timespec(when)
		if not timepast then timepast = 0 end
		local timeleft = s.duration - timepast
		local howlong = (s.delay + timepast) - elapsed
		if howlong < 0 then
			log.err('cannot time-travel! queue() called with `when` specifying timepoint that has already passed')
			howlong = 0
		end
		s.jobs[#s.jobs+1] = minetest.after(howlong, function()
			-- this is somewhat awkward. since we're using a non-polling approach, we
			-- need to find a way to account for a caster or subject walking into an
			-- existing antimagic field, or someone with an existing antimagic aura
			-- walking into range of the anchor. so every time a spell effect would
................................................................................
					termqueued = true
					s.queue(1,function(s,...)
						what(s,...)
						if s.terminate then s:terminate() end
						sorcery.spell.active[myid] = nil
					end)
				else
					log.warn('multiple final timeline events not possible, ignoring')
				end
			elseif when == 0 and s.disjunction then
				startqueued = true
				s.queue(when_raw,function(...)
					perform_disjunction_calls()
					what(...)
				end)

Modified tap.lua from [7b8c107461] to [17fb78473a].

1
2
3
4
5

6
7
8
9
10
11
12
local log = sorcery.logger('tap')
minetest.register_node('sorcery:tap',{
	description = 'Tree Tap';
	drawtype = 'mesh';
	mesh = 'sorcery-tap.obj';

	tiles = {
		'default_copper_block.png';
		'default_steel_block.png';
	};
	groups = {
		dig_immediate = 2;
		attached_node = 1;





>







1
2
3
4
5
6
7
8
9
10
11
12
13
local log = sorcery.logger('tap')
minetest.register_node('sorcery:tap',{
	description = 'Tree Tap';
	drawtype = 'mesh';
	mesh = 'sorcery-tap.obj';
	inventory_image = 'sorcery_tap_inv.png';
	tiles = {
		'default_copper_block.png';
		'default_steel_block.png';
	};
	groups = {
		dig_immediate = 2;
		attached_node = 1;

Added textures/sorcery_tap_inv.png version [0ade756825].

cannot compute difference between binary files