sorcery  Check-in [94064fe5c9]

Overview
Comment:add cool and informative visuals for taps, add more capacity to rune forge, many bug fixes, fixed some bugs, and fixed some bugs
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 94064fe5c99fad4567c598b2e72cf80aa2c3bae24012a997d69b6a659a47fe30
User & Date: lexi on 2021-07-04 20:27:42
Other Links: manifest | tags
Context
2021-07-05
00:59
add Mundanity spell, bug fixes check-in: 1276138728 user: lexi tags: trunk
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
Changes

Modified altar.lua from [080406f8d2] to [7e265da854].

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
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
101
102
103
104
105
...
234
235
236
237
238
239
240

241
242
243
244
245
246
247
...
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
		local props = object:get_properties()
		local node = minetest.get_node(pos)
		props.wield_item = itemstring
		object:set_properties(props)
		object:set_yaw(math.pi*2 - node.param2*(math.pi / 2))
	end
end













for name, god in pairs(sorcery.data.gods) do
	local hitbox = {
		0-(god.idol.width / 2.0), 0-(god.idol.height / 2.0), -0.15,
		   god.idol.width / 2.0,     god.idol.height / 2.0,   0.15
	} -- {xmin, ymin, zmin,
	  -- xmax, ymax, zmax} in nodes from node center.
	paramtype = "light";
	minetest.register_node('sorcery:idol_' .. name, {
		description = god.idol.desc;
		drawtype = "mesh";
		mesh = 'sorcery-idol-' .. name .. '.obj';
		paramtype = 'light';
		paramtype2 = 'facedir';
		sunlight_propagates = true;
		stack_max = 1;
		tiles = god.idol.tex;
		selection_box = { type = "fixed"; fixed = {hitbox}; };
		collision_box = { type = "fixed"; fixed = {hitbox}; };
		groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1};

		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 = {
				{ items = {'sorcery:idol_' .. name} }
			};
		};

		preserve_metadata = function(pos, node, meta, newstack)
			newstack[1]:get_meta():from_table(meta)
		end;

		on_timer = function(pos, elapsed)
			local altar = minetest.find_node_near(pos, 3, "sorcery:altar")
			-- TODO even without an altar, an idol with high favor could still be the source of miracles

			if not altar then return true end

			local altarmeta = minetest.get_meta(altar)
			local inv = altarmeta:get_inventory()
			local idolmeta = minetest.get_meta(pos)
			local divine_favor = idolmeta:get_int('favor')
			local bestow = function(item,color)
................................................................................
							}
						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(
................................................................................
						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::
			idolmeta:set_int('favor', divine_favor)
			update_altar(altar,nil)
			return true
		end;







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








|












|
<
<
<
<
<



<
<
<
<
<
<
<
<
<
<
<
<
<



>







 







>







 







|







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
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
...
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
...
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
		local props = object:get_properties()
		local node = minetest.get_node(pos)
		props.wield_item = itemstring
		object:set_properties(props)
		object:set_yaw(math.pi*2 - node.param2*(math.pi / 2))
	end
end

-- remove unknown gifts
minetest.register_on_mods_loaded(function()
	for name, god in pairs(sorcery.data.gods) do
		local bad = {}
		for g in pairs(god.gifts) do
			-- can't mutate table while we're iterating it
			if not minetest.registered_nodes[g] then bad[#bad+1] = g end
		end
		for _, g in ipairs(bad) do god.gifts[g] = nil end
	end
end)

for name, god in pairs(sorcery.data.gods) do
	local hitbox = {
		0-(god.idol.width / 2.0), 0-(god.idol.height / 2.0), -0.15,
		   god.idol.width / 2.0,     god.idol.height / 2.0,   0.15
	} -- {xmin, ymin, zmin,
	  -- xmax, ymax, zmax} in nodes from node center.
	paramtype = "light";
	sorcery.lib.node.reg_autopreserve('sorcery:idol_' .. name, {
		description = god.idol.desc;
		drawtype = "mesh";
		mesh = 'sorcery-idol-' .. name .. '.obj';
		paramtype = 'light';
		paramtype2 = 'facedir';
		sunlight_propagates = true;
		stack_max = 1;
		tiles = god.idol.tex;
		selection_box = { type = "fixed"; fixed = {hitbox}; };
		collision_box = { type = "fixed"; fixed = {hitbox}; };
		groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1};

		on_construct = function(pos)





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














		on_timer = function(pos, elapsed)
			local altar = minetest.find_node_near(pos, 3, "sorcery:altar")
			-- TODO even without an altar, an idol with high favor could still be the source of miracles
			-- refills nearby partly empty troughs at cost to favor?
			if not altar then return true end

			local altarmeta = minetest.get_meta(altar)
			local inv = altarmeta:get_inventory()
			local idolmeta = minetest.get_meta(pos)
			local divine_favor = idolmeta:get_int('favor')
			local bestow = function(item,color)
................................................................................
							}
						end
						-- preserve wear
						local gift
						if type(tx) == 'string' then
							gift = ItemStack(tx)
						else gift = tx end
						if not gift:is_known() then goto skip 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(
................................................................................
						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
				::skip::end
			end

			::refresh::
			idolmeta:set_int('favor', divine_favor)
			update_altar(altar,nil)
			return true
		end;

Modified astrolabe.lua from [cc031237ac] to [21ab3e0c25].

77
78
79
80
81
82
83


84
85
86
87
88
89
90
	drawtype = 'mesh';
	mesh = 'sorcery-astrolabe.obj';
	groups = {
		cracky = 2, choppy = 2;
		dig_immediate = 2;
		sorcery_tech = 1;
	};


	selection_box = albox, collision_box = albox;
	after_dig_node = sorcery.lib.node.purge_containers;
	tiles = {
		'default_steel_block.png';
		'default_bronze_block.png';
		'default_copper_block.png';
		'default_aspen_wood.png';







>
>







77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
	drawtype = 'mesh';
	mesh = 'sorcery-astrolabe.obj';
	groups = {
		cracky = 2, choppy = 2;
		dig_immediate = 2;
		sorcery_tech = 1;
	};
	sunlight_propagates = true;
	paramtype = 'light';
	selection_box = albox, collision_box = albox;
	after_dig_node = sorcery.lib.node.purge_containers;
	tiles = {
		'default_steel_block.png';
		'default_bronze_block.png';
		'default_copper_block.png';
		'default_aspen_wood.png';

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

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
		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)







|







|







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
		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 quickly 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 qualities are diminished or eliminated'
		end;
		infusion = 'sorcery:oil_purifying';
	};
	Beauty = {
		color = {255,20,226}; qual = 'beauty';
		apply = inc('beauty');
		describe = function(potion)

Modified gems.lua from [68440cd05e] to [12e0882fa3].

54
55
56
57
58
59
60

61
62
63
64
65
66
67
	end
	if not gem.foreign_amulet then
		local img = sorcery.lib.image
		local img_stone = img('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone))
		local img_sparkle = img('sorcery_amulet_sparkle.png')
		local useamulet = function(stack,user,target)
			local sp = sorcery.amulet.getspell(stack)

			if not sp or not sp.cast then return nil end

			local usedamulet if stack:get_count() == 1 then
				usedamulet = stack
			else
				usedamulet = ItemStack(stack)
				usedamulet:set_count(1)







>







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
	end
	if not gem.foreign_amulet then
		local img = sorcery.lib.image
		local img_stone = img('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone))
		local img_sparkle = img('sorcery_amulet_sparkle.png')
		local useamulet = function(stack,user,target)
			local sp = sorcery.amulet.getspell(stack)
			print('got spell',dump(sp))
			if not sp or not sp.cast then return nil end

			local usedamulet if stack:get_count() == 1 then
				usedamulet = stack
			else
				usedamulet = ItemStack(stack)
				usedamulet:set_count(1)

Modified infuser.lua from [0b21397e89] to [fedc837bad].

204
205
206
207
208
209
210

211
212
213
214

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
...
268
269
270
271
272
273
274
275
276
277
278
279


280
281
282
283
284
285
286
287
288

	local inv = meta:get_inventory()
	local infusion = inv:get_list('infusion')
	local potions = inv:get_list('potions')
	local elixir = infusion[1]:get_definition()
	local probe = sorcery.spell.probe(pos)
	local fx = infuser_mods(pos)

	if probe.disjunction then return true end

	local potionct = 0


	do
		local ingredient -- *eyeroll*
		if infusion[1]:is_empty() then goto cancel end
		ingredient = infusion[1]:get_name()
		for i = 1,#potions do
			if potions[i]:is_empty() then goto skip end
			potionct = potionct + 1
			local base = potions[i]:get_name()
			local potion = potions[i]:get_definition()
			if elixir_can_apply(elixir._proto,potion) then
				-- at least one combination makes a valid potion;
				-- we can start the infuser
				goto start



			end
			for _,v in pairs(sorcery.register.infusions.db) do
				if v.infuse == ingredient and v.into == base then
					-- at least one combination makes a valid
					-- potion; we can start the infuser
					goto start



				end
			end
		::skip:: end

		::cancel:: do
			infuser_stop(pos)
			return false
		end

		::start::
	end

................................................................................
				type = "vertical_frames";
				aspect_h = 16;
				aspect_w = 16;
				length = 4.1;
			};
		}
	end
	-- for i=0,4 do
		spawn('sorcery_spark.png^[multiply:#FF8FDD', 1, 32 * 4)
	-- end
	-- for i=0,4 do
		spawn('sorcery_spark.png^[multiply:#FFB1F6', 0.5, 64 * 4)


	-- end
	
	local discharge = sorcery.lib.node.discharger(pos)
	
	if newtime >= infusion_time then
		-- finished
		local ingredient = infusion[1]:get_name()
		local result, residue = sorcery.alchemy.infuse(infusion[1], potions)
		for i, r in pairs(result) do







>




>












|
>
>
>





|
>
>
>




|







 







|
|
|
|
|
>
>
|
|







204
205
206
207
208
209
210
211
212
213
214
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
...
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298

	local inv = meta:get_inventory()
	local infusion = inv:get_list('infusion')
	local potions = inv:get_list('potions')
	local elixir = infusion[1]:get_definition()
	local probe = sorcery.spell.probe(pos)
	local fx = infuser_mods(pos)
	local sparkle_color = {sorcery.lib.color(255, 0, 145)};
	if probe.disjunction then return true end

	local potionct = 0

	local cancel = true
	do
		local ingredient -- *eyeroll*
		if infusion[1]:is_empty() then goto cancel end
		ingredient = infusion[1]:get_name()
		for i = 1,#potions do
			if potions[i]:is_empty() then goto skip end
			potionct = potionct + 1
			local base = potions[i]:get_name()
			local potion = potions[i]:get_definition()
			if elixir_can_apply(elixir._proto,potion) then
				-- at least one combination makes a valid potion;
				-- we can start the infuser
				if elixir._proto.color then
					sparkle_color[#sparkle_color+1] = sorcery.lib.color(elixir._proto.color)
				end
				cancel = false
			end
			for _,v in pairs(sorcery.register.infusions.db) do
				if v.infuse == ingredient and v.into == base then
					-- at least one combination makes a valid
					-- potion; we can start the infuser
					if v.output.data and v.output.data.color then
						sparkle_color[#sparkle_color+1] = sorcery.lib.color(v.output.data.color)
					end
					cancel = false
				end
			end
		::skip:: end

		::cancel:: if cancel then
			infuser_stop(pos)
			return false
		end

		::start::
	end

................................................................................
				type = "vertical_frames";
				aspect_h = 16;
				aspect_w = 16;
				length = 4.1;
			};
		}
	end
	local spark = sorcery.lib.image('sorcery_spark.png')
	for i = 1,4 do
		local fac = 1 / i
		local _, spc = sorcery.lib.tbl.pick(sparkle_color)
		local sp = spark:glow(spc)

		spawn(sp:render(), fac, (32/fac) * 4)
	end

	local discharge = sorcery.lib.node.discharger(pos)
	
	if newtime >= infusion_time then
		-- finished
		local ingredient = infusion[1]:get_name()
		local result, residue = sorcery.alchemy.infuse(infusion[1], potions)
		for i, r in pairs(result) do

Modified keg.lua from [5c653d450d] to [59c6df911a].

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
...
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
					m:set_int('charge',0)
				else m:set_int('charge', amtleft) end
				sorcery.liquid.sound_dip(chg,avail,pos)
				update()

				-- fancy visuals
				local color = sorcery.lib.color(liq.color or {255,255,255})
				local spritz = sorcery.lib.image('sorcery_droplet.png')
				local drop = sorcery.lib.image('sorcery_drop.png')
				spritz = spritz:blit(spritz:multiply(color))
				drop   = drop:blit  (drop:multiply  (color))
				local facing = minetest.facedir_to_dir(minetest.get_node(pos).param2)
				local noz = vector.add(pos, vector.rotate(
					vector.new(0.0,0,-0.48),
					vector.dir_to_rotation(facing)
				))
				local minnoz = vector.offset(noz, -0.03, -0.32, -0.03);
				local maxnoz = vector.offset(noz,  0.03, -0.32,  0.03);
................................................................................
					minsize = 0.4, maxsize = 1;
					glow = 14; -- FIXME liquid glow prop
					minexptime = 0.5, maxexptime = 0.5;
					animation = {
						type = 'sheet_2d';
						frames_w = 14;
						frames_h = 1;
						frame_length = 0.5/14;
					}
				}
				minetest.after(0.2, function()
					minetest.add_particlespawner {
						amount = math.random(5,11) * chg, time = 0.13 * chg;
						texture = drop:render();
						minpos = vector.offset(minnoz, 0,-0.05,0);
................................................................................
						minsize = 0.3, maxsize = 0.5;
						glow = 14; -- FIXME liquid glow prop
						minexptime = 1, maxexptime = 1.5;
						animation = {
							type = 'sheet_2d';
							frames_w = 10;
							frames_h = 1;
							frame_length = 1.5/10;
						}
					}
				end)

				return filled
			end
		end







|
|
<
<







 







|







 







|







123
124
125
126
127
128
129
130
131


132
133
134
135
136
137
138
...
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
					m:set_int('charge',0)
				else m:set_int('charge', amtleft) end
				sorcery.liquid.sound_dip(chg,avail,pos)
				update()

				-- fancy visuals
				local color = sorcery.lib.color(liq.color or {255,255,255})
				local spritz = sorcery.lib.image('sorcery_droplet.png'):glow(color)
				local drop = sorcery.lib.image('sorcery_drop.png'):glow(color)


				local facing = minetest.facedir_to_dir(minetest.get_node(pos).param2)
				local noz = vector.add(pos, vector.rotate(
					vector.new(0.0,0,-0.48),
					vector.dir_to_rotation(facing)
				))
				local minnoz = vector.offset(noz, -0.03, -0.32, -0.03);
				local maxnoz = vector.offset(noz,  0.03, -0.32,  0.03);
................................................................................
					minsize = 0.4, maxsize = 1;
					glow = 14; -- FIXME liquid glow prop
					minexptime = 0.5, maxexptime = 0.5;
					animation = {
						type = 'sheet_2d';
						frames_w = 14;
						frames_h = 1;
						frame_length = (0.5/14) + 0.02;
					}
				}
				minetest.after(0.2, function()
					minetest.add_particlespawner {
						amount = math.random(5,11) * chg, time = 0.13 * chg;
						texture = drop:render();
						minpos = vector.offset(minnoz, 0,-0.05,0);
................................................................................
						minsize = 0.3, maxsize = 0.5;
						glow = 14; -- FIXME liquid glow prop
						minexptime = 1, maxexptime = 1.5;
						animation = {
							type = 'sheet_2d';
							frames_w = 10;
							frames_h = 1;
							frame_length = (1.5/10) + 0.02;
						}
					}
				end)

				return filled
			end
		end

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

358
359
360
361
362
363
364
365























				end
				return i, false
			end
		else
			return function(i) return i, false end
		end
	end;
}






























|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
				end
				return i, false
			end
		else
			return function(i) return i, false end
		end
	end;

	autopreserve = function(id, tbl)
		tbl.drop = tbl.drop or {
			max_items = 1;
			items = {
				{ items = {id} };
			};
		}
		local next_apn = tbl.after_place_node
		tbl.after_place_node = function(...) local pos, who, stack = ...
			minetest.get_meta(pos):from_table(stack:get_meta():to_table())
			if next_apn then return next_apn(...) end
		end
		local next_pm = tbl.preserve_metadata
		tbl.preserve_metadata = function(...) local pos, node, meta, drops = ...
			drops[1]:get_meta():from_table({fields = meta})
			if next_pm then return next_pm(...) end
		end
		return tbl
	end;
	reg_autopreserve = function(id, tbl)
		minetest.register_node(id, sorcery.lib.node.autopreserve(id, tbl))
	end;
}

Modified liquid.lua from [6a40bd16e3] to [02b94765c1].

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
...
244
245
246
247
248
249
250

251
252
253
254
255
256
257
...
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
				L.image(fmt('sorcery_liquid_%s.png', liq.imgvariant or 'dull'))
					:multiply(L.color(liq.color))):render()

		-- local img_glass = L.image('vessels_drinking_glass.png'):blit(
		-- 		L.image(fmt('sorcery_liquid_glass_%s.png', liq.imgvariant or 'dull'))
		-- 			:multiply(L.color(liq.color)))

		minetest.register_node(':'..bottle, {
			description = liq.desc_bottle or fmt('%s Bottle', L.str.capitalize(liq.name));
			inventory_image = img_bottle;
			drawtype = 'plantlike', tiles = {img_bottle};
			is_ground_content = false, walkable = false;
			sunlight_propagates = true, paramtype = 'light';
			light_source = liq.glow or 0;
			selection_box = { type = 'fixed', fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} };
................................................................................
		pos = pos;
	}, true)
end;

sorcery.liquid.sound_dip = function(amt_output, amt_basin, pos)
	sorcery.liquid.sound_pour(amt_output, amt_basin, pos)
end;


-- pre-register basic liquids used in Sorcery and common ones sorcery depends on

sorcery.liquid.register{
	id = 'default:water';
	name = 'water';
	kind = 'default:drink';
................................................................................
		['vessels:glass_bottle'] = 'sorcery:blood';
	};
}

minetest.register_abm {
	label = 'Rainfall';
	nodenames = {'group:sorcery_collect_rainwater'};
	interval =  230;
	chance = 40;
	min_y = -400;
	catch_up = true;
	action = function(pos, node)
		-- TODO vary by season and biome?
		if minetest.get_natural_light(vector.offset(pos,0,1,0), 0.5) >= 15 then
			if node.name == 'sorcery:trough' then
				node.name = 'default:trough_water_1'







|







 







>







 







|
|







194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
...
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
...
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
				L.image(fmt('sorcery_liquid_%s.png', liq.imgvariant or 'dull'))
					:multiply(L.color(liq.color))):render()

		-- local img_glass = L.image('vessels_drinking_glass.png'):blit(
		-- 		L.image(fmt('sorcery_liquid_glass_%s.png', liq.imgvariant or 'dull'))
		-- 			:multiply(L.color(liq.color)))

		sorcery.lib.node.reg_autopreserve(':'..bottle, {
			description = liq.desc_bottle or fmt('%s Bottle', L.str.capitalize(liq.name));
			inventory_image = img_bottle;
			drawtype = 'plantlike', tiles = {img_bottle};
			is_ground_content = false, walkable = false;
			sunlight_propagates = true, paramtype = 'light';
			light_source = liq.glow or 0;
			selection_box = { type = 'fixed', fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} };
................................................................................
		pos = pos;
	}, true)
end;

sorcery.liquid.sound_dip = function(amt_output, amt_basin, pos)
	sorcery.liquid.sound_pour(amt_output, amt_basin, pos)
end;


-- pre-register basic liquids used in Sorcery and common ones sorcery depends on

sorcery.liquid.register{
	id = 'default:water';
	name = 'water';
	kind = 'default:drink';
................................................................................
		['vessels:glass_bottle'] = 'sorcery:blood';
	};
}

minetest.register_abm {
	label = 'Rainfall';
	nodenames = {'group:sorcery_collect_rainwater'};
	interval =  120;
	chance = 27;
	min_y = -400;
	catch_up = true;
	action = function(pos, node)
		-- TODO vary by season and biome?
		if minetest.get_natural_light(vector.offset(pos,0,1,0), 0.5) >= 15 then
			if node.name == 'sorcery:trough' then
				node.name = 'default:trough_water_1'

Modified potions.lua from [0b54227d82] to [1dfd3579b4].

24
25
26
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
		drawtype = "plantlike";
		tiles = {image};
		inventory_image = image;
		paramtype = "light";
		is_ground_content = false;
		light_source = glow and math.min(minetest.LIGHT_MAX,glow) or 0;
		drop = 'sorcery:' .. name;
		preserve_metadata = function(pos,node,meta,newstack)
			newstack[1]:get_meta():from_table(meta)
		end;
		walkable = false;
		selection_box = {
			type = "fixed",
			fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25}
		};
		on_construct = function(pos)
			minetest.get_meta(pos):set_string('infotext',label)
................................................................................
	}
	if extra then for k,v in pairs(extra) do node[k] = v end end
	if not node.groups then node.groups = {} end
	node.groups.dig_immediate = 3;
	node.groups.attached_node = 1;
	node.groups.vessel = 1;
	node.groups.not_in_creative_inventory = 1;
	minetest.register_node("sorcery:"..name, node)
end

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;







<
<
<







 







|







24
25
26
27
28
29
30



31
32
33
34
35
36
37
..
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
		drawtype = "plantlike";
		tiles = {image};
		inventory_image = image;
		paramtype = "light";
		is_ground_content = false;
		light_source = glow and math.min(minetest.LIGHT_MAX,glow) or 0;
		drop = 'sorcery:' .. name;



		walkable = false;
		selection_box = {
			type = "fixed",
			fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25}
		};
		on_construct = function(pos)
			minetest.get_meta(pos):set_string('infotext',label)
................................................................................
	}
	if extra then for k,v in pairs(extra) do node[k] = v end end
	if not node.groups then node.groups = {} end
	node.groups.dig_immediate = 3;
	node.groups.attached_node = 1;
	node.groups.vessel = 1;
	node.groups.not_in_creative_inventory = 1;
	sorcery.lib.node.reg_autopreserve("sorcery:"..name, node)
end

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;

Modified runeforge.lua from [65aa0a1ed6] to [b7d5d6135c].

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
..
89
90
91
92
93
94
95



96
97
98
99
100
101
102
...
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
...
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334



335
336
337
338
339
340
341
342
343
...
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
-- passively consumes ley-current
--  -- are phials & rune-wrenches enough for this now?

local constants = {
	rune_mine_interval = 240;
	-- how often a powered forge rolls for new runes

	rune_cache_max = 4;
	-- how many runes a runeforge can hold at a time
	
	rune_grades = {'Fragile', 'Weak', 'Ordinary', 'Pristine', 'Sublime'};
	-- how many grades of rune quality/power there are
	
	amulet_grades = {'Slight', 'Minor', 'Major', 'Grand', 'Ultimate' };
	-- what kind of amulet each rune grade translates to
................................................................................
			dist = { Fragile = 0, Weak = 1,    Ordinary = 0.9, Pristine = 0.5,  Sublime = 0.25 };
		};
		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)
	minetest.register_craftitem(id, {
................................................................................
			_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;



	}
end

local register_rune_wrench = function(w)
	local mp = sorcery.data.metals[w.metal].parts
	minetest.register_tool(w.name, {
		description = w.desc;
................................................................................
	local base_spell = true

	if proto.frame and spell.frame and spell.frame[proto.frame] then
		local sp = spell.frame[proto.frame]
		if not sp.mingrade or rg >= sp.mingrade then
			title = sp.name or title
			desc = sp.desc or desc
			cast = sp.desc or cast
			apply = sp.apply or apply
			remove = sp.remove or remove
			mingrade = sp.mingrade or mingrade
			base_spell = false
		end
	end
	
................................................................................
			else break end
		end
	end

	has_phial = has_phial()
	local spec = string.format([[
		formspec_version[3] size[10.25,8] real_coordinates[true]
		list[context;cache;%f,0.25;%u,1;]
		list[context;amulet;3.40,1.50;1,1;]
		list[context;active;5.90,1.50;1,1;]

		list[context;wrench;1.25,1.75;1,1;]
		list[context;phial;7.25,1.75;1,1;]
		list[context;refuse;8.50,1.75;1,1;]

		list[current_player;main;0.25,3;8,4;]




		image[0.25,0.50;1,1;sorcery_statlamp_%s.png]
	]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max,
		((not (has_phial and pow_min)) and 'off'  ) or
		( probe.disjunction            and 'blue' ) or
	    ((has_phial and pow_max)       and 'green') or 'yellow')

	local ghost = function(slot,x,y,img)
		if i:is_empty(slot) then spec = spec .. string.format([[
			image[%f,%f;1,1;%s.png]
................................................................................
				local max,min = 0
				for _,r in pairs(sorcery.data.runes) do
					if r.minpower > max then max = r.minpower end
					if min == nil or r.minpower < min then min = r.minpower end
				end
				-- high-quality phials reduce power usage
				local fac = select(2, calc_phial_props(phial))
				min = min * fac  max = max * fac
				return min*time,max*time
			end;
		};
		on_leychange = runeforge_update;
		recipe = {
			note = 'Periodically creates runes when sufficiently powered and can be used to imbue them into an amulet, giving it a powerful magical effect';
		};







|







 







|




|
|







 







>
>
>







 







|







 







<









>
>
>

|







 







|







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
..
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
...
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
...
321
322
323
324
325
326
327

328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
...
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
-- passively consumes ley-current
--  -- are phials & rune-wrenches enough for this now?

local constants = {
	rune_mine_interval = 240;
	-- how often a powered forge rolls for new runes

	rune_cache_max = 6;
	-- how many runes a runeforge can hold at a time
	
	rune_grades = {'Fragile', 'Weak', 'Ordinary', 'Pristine', 'Sublime'};
	-- how many grades of rune quality/power there are
	
	amulet_grades = {'Slight', 'Minor', 'Major', 'Grand', 'Ultimate' };
	-- what kind of amulet each rune grade translates to
................................................................................
			dist = { Fragile = 0, Weak = 1,    Ordinary = 0.9, Pristine = 0.5,  Sublime = 0.25 };
		};
		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, power 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.2 * m:get_int('speed')
	return math.max(3,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)
	minetest.register_craftitem(id, {
................................................................................
			_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;
		_proto = {
			data = { color = color };
		};
	}
end

local register_rune_wrench = function(w)
	local mp = sorcery.data.metals[w.metal].parts
	minetest.register_tool(w.name, {
		description = w.desc;
................................................................................
	local base_spell = true

	if proto.frame and spell.frame and spell.frame[proto.frame] then
		local sp = spell.frame[proto.frame]
		if not sp.mingrade or rg >= sp.mingrade then
			title = sp.name or title
			desc = sp.desc or desc
			cast = sp.cast or cast
			apply = sp.apply or apply
			remove = sp.remove or remove
			mingrade = sp.mingrade or mingrade
			base_spell = false
		end
	end
	
................................................................................
			else break end
		end
	end

	has_phial = has_phial()
	local spec = string.format([[
		formspec_version[3] size[10.25,8] real_coordinates[true]

		list[context;amulet;3.40,1.50;1,1;]
		list[context;active;5.90,1.50;1,1;]

		list[context;wrench;1.25,1.75;1,1;]
		list[context;phial;7.25,1.75;1,1;]
		list[context;refuse;8.50,1.75;1,1;]

		list[current_player;main;0.25,3;8,4;]

		style_type[list;size=0.8]
		list[context;cache;%f,0.25;%u,1;]

		image[0.25,0.50;1,1;sorcery_statlamp_%s.png]
	]], (10.5 - 0.8*(constants.rune_cache_max*1.25))/2, constants.rune_cache_max,
		((not (has_phial and pow_min)) and 'off'  ) or
		( probe.disjunction            and 'blue' ) or
	    ((has_phial and pow_max)       and 'green') or 'yellow')

	local ghost = function(slot,x,y,img)
		if i:is_empty(slot) then spec = spec .. string.format([[
			image[%f,%f;1,1;%s.png]
................................................................................
				local max,min = 0
				for _,r in pairs(sorcery.data.runes) do
					if r.minpower > max then max = r.minpower end
					if min == nil or r.minpower < min then min = r.minpower end
				end
				-- high-quality phials reduce power usage
				local fac = select(2, calc_phial_props(phial))
				min = min / fac  max = max / fac
				return min*time,max*time
			end;
		};
		on_leychange = runeforge_update;
		recipe = {
			note = 'Periodically creates runes when sufficiently powered and can be used to imbue them into an amulet, giving it a powerful magical effect';
		};

Modified tap.lua from [17fb78473a] to [9cfe073fce].

1






2
3
4
5
6
7
8
..
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
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
...
101
102
103
104
105
106
107


108
109
110
111
112
113
114
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';
................................................................................
	paramtype = 'light', paramtype2 = 'wallmounted';
	selection_box = { type='fixed', fixed = {-0.2,-0.5,-0.35; 0.3,0.1,0.4} };
	collision_box = { type='fixed', fixed = {-0.2,-0.5,-0.35; 0.3,0.1,0.4} };
	node_placement_prediction = '';
	on_place = function(stack,who,where)
		if where.type ~= 'node' then return end
		local bl = minetest.get_node(where.under)
		-- FIXME prevent tapping 'dead' non-tree wood blocks

		local tree = sorcery.tree.get(where.under)
		if not tree or tree.sap == false then return end;

		-- disallow vertical attachment
		if vector.subtract(where.under,where.above).y ~= 0 then return end

		minetest.set_node(where.above, {
			name = 'sorcery:tap';
			param2 = minetest.dir_to_wallmounted(vector.subtract(where.under,where.above))
		})






		stack:take_item(1)
		return stack
	end;

	_sorcery = {
		recipe = {
			note = 'Extract syrups and oils from trees';
		};
	};
})

................................................................................
	recipe = {
		{'','sorcery:screw_steel','basic_materials:steel_bar'};
		{'sorcery:pipe','sorcery:valve','sorcery:screw_steel'};
		{'','sorcery:pipe',''};
	};
}

local sap_interval = 60;
local abm_cache
local abm_cache_time
minetest.register_abm {
	label = 'Sap drip';
	nodenames = {'sorcery:tap'};
	neighbors = {'group:tree'};
	interval = sap_interval;
	chance = 7;
	catch_up = true;
	action = function(pos, node)
		local now = os.time()
		if abm_cache_time == nil or now > abm_cache_time + (sap_interval-1) then
			abm_cache = { treehash = {} }
			abm_cache_time = now
		end
................................................................................
			end
		end

		if (not live)
			or tree.sap == false
			or not tree.sapliq then return end
		if mass_trunk < 12*3 then return end -- too small



		local mass = mass_leaves + mass_trunk
		local max_mass = 400
		local ltratio = mass_leaves / mass_trunk
		local mratio = mass / max_mass
		local outof = 15 / mratio
		local chance = math.max(1, math.floor(outof - (25 * ltratio))) / 3

>
>
>
>
>
>







 







<


|

|






>
>
>
>
>




>







 







<







|







 







>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
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
53
54
55
56
..
59
60
61
62
63
64
65

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
...
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
local log = sorcery.logger('tap')
local sap_interval = 20;

local function tapdrip(liq, pos)
	return sorcery.vfx.drip(liq, vector.offset(pos, 0, -0.3, 0), math.random(5,12), sap_interval, 2)
end

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';
................................................................................
	paramtype = 'light', paramtype2 = 'wallmounted';
	selection_box = { type='fixed', fixed = {-0.2,-0.5,-0.35; 0.3,0.1,0.4} };
	collision_box = { type='fixed', fixed = {-0.2,-0.5,-0.35; 0.3,0.1,0.4} };
	node_placement_prediction = '';
	on_place = function(stack,who,where)
		if where.type ~= 'node' then return end
		local bl = minetest.get_node(where.under)


		local tree = sorcery.tree.get(where.under)
		if not tree or tree.def.sap == false then return end;

		-- disallow vertical attachment, bc that makes no sense
		if vector.subtract(where.under,where.above).y ~= 0 then return end

		minetest.set_node(where.above, {
			name = 'sorcery:tap';
			param2 = minetest.dir_to_wallmounted(vector.subtract(where.under,where.above))
		})
		
		if sorcery.lib.node.tree_is_live(where.under) then
			-- start dripping immediately to indicate the tree is alive
			tapdrip(tree.def.sapliq, where.above)
		end

		stack:take_item(1)
		return stack
	end;
	on_screwdriver = function() return false end;
	_sorcery = {
		recipe = {
			note = 'Extract syrups and oils from trees';
		};
	};
})

................................................................................
	recipe = {
		{'','sorcery:screw_steel','basic_materials:steel_bar'};
		{'sorcery:pipe','sorcery:valve','sorcery:screw_steel'};
		{'','sorcery:pipe',''};
	};
}


local abm_cache
local abm_cache_time
minetest.register_abm {
	label = 'Sap drip';
	nodenames = {'sorcery:tap'};
	neighbors = {'group:tree'};
	interval = sap_interval;
	chance = 4;
	catch_up = true;
	action = function(pos, node)
		local now = os.time()
		if abm_cache_time == nil or now > abm_cache_time + (sap_interval-1) then
			abm_cache = { treehash = {} }
			abm_cache_time = now
		end
................................................................................
			end
		end

		if (not live)
			or tree.sap == false
			or not tree.sapliq then return end
		if mass_trunk < 12*3 then return end -- too small

		tapdrip(tree.sapliq,pos)

		local mass = mass_leaves + mass_trunk
		local max_mass = 400
		local ltratio = mass_leaves / mass_trunk
		local mratio = mass / max_mass
		local outof = 15 / mratio
		local chance = math.max(1, math.floor(outof - (25 * ltratio))) / 3

Modified vfx.lua from [8567a33693] to [48046fb0bd].

148
149
150
151
152
153
154


























			animation = {
				type = 'vertical_frames', length = far/vel;
				aspect_w = 16, aspect_h = 16;
			};
		}
	end
end

































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
148
149
150
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
			animation = {
				type = 'vertical_frames', length = far/vel;
				aspect_w = 16, aspect_h = 16;
			};
		}
	end
end

function sorcery.vfx.drip(liquid, noz, amt, time, exp)
	if type(liquid) == 'string' then liquid = sorcery.register.liquid.db[liquid] end
	local minnoz = vector.offset(noz, -0.03, 0.0, -0.03);
	local maxnoz = vector.offset(noz,  0.03, 0.0,  0.03);
	local drop = sorcery.lib.image('sorcery_drop.png'):multiply(liquid.color)
	return minetest.add_particlespawner {
		amount = amt, time = time;
		texture = drop:render();
		minpos = minnoz, maxpos = maxnoz;
		minvel = vector.new(0,0,0);
		maxvel = vector.new(0,-0.2,0);
		minacc = vector.new(0,-0.2,0);
		maxacc = vector.new(0,-0.23,0);
		minsize = 0.4, maxsize = 1;
		glow = liquid.glow or 2;
		minexptime = exp, maxexptime = exp;
		animation = {
			type = 'sheet_2d';
			frames_w = 10;
			frames_h = 1;
			frame_length = (exp/10) + 0.01;
		};
		vertical = true;
	}
end