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     46   		local props = object:get_properties()
    47     47   		local node = minetest.get_node(pos)
    48     48   		props.wield_item = itemstring
    49     49   		object:set_properties(props)
    50     50   		object:set_yaw(math.pi*2 - node.param2*(math.pi / 2))
    51     51   	end
    52     52   end
           53  +
           54  +-- remove unknown gifts
           55  +minetest.register_on_mods_loaded(function()
           56  +	for name, god in pairs(sorcery.data.gods) do
           57  +		local bad = {}
           58  +		for g in pairs(god.gifts) do
           59  +			-- can't mutate table while we're iterating it
           60  +			if not minetest.registered_nodes[g] then bad[#bad+1] = g end
           61  +		end
           62  +		for _, g in ipairs(bad) do god.gifts[g] = nil end
           63  +	end
           64  +end)
    53     65   
    54     66   for name, god in pairs(sorcery.data.gods) do
    55     67   	local hitbox = {
    56     68   		0-(god.idol.width / 2.0), 0-(god.idol.height / 2.0), -0.15,
    57     69   		   god.idol.width / 2.0,     god.idol.height / 2.0,   0.15
    58     70   	} -- {xmin, ymin, zmin,
    59     71   	  -- xmax, ymax, zmax} in nodes from node center.
    60     72   	paramtype = "light";
    61         -	minetest.register_node('sorcery:idol_' .. name, {
           73  +	sorcery.lib.node.reg_autopreserve('sorcery:idol_' .. name, {
    62     74   		description = god.idol.desc;
    63     75   		drawtype = "mesh";
    64     76   		mesh = 'sorcery-idol-' .. name .. '.obj';
    65     77   		paramtype = 'light';
    66     78   		paramtype2 = 'facedir';
    67     79   		sunlight_propagates = true;
    68     80   		stack_max = 1;
    69     81   		tiles = god.idol.tex;
    70     82   		selection_box = { type = "fixed"; fixed = {hitbox}; };
    71     83   		collision_box = { type = "fixed"; fixed = {hitbox}; };
    72     84   		groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1};
    73     85   
    74         -		after_place_node = function(pos, placer, stack, pointat)
    75         -			local meta = minetest.get_meta(pos)
    76         -			local stackmeta = stack:get_meta()
    77         -			meta:set_int('favor', stackmeta:get_int('favor'))
    78         -			meta:set_string('last_sacrifice', stackmeta:get_string('last_sacrifice'))
    79         -
           86  +		on_construct = function(pos)
    80     87   			minetest.get_node_timer(pos):start(1)
    81     88   		end;
    82     89   
    83         -		drop = {
    84         -			-- for some idiot reason this is necessary for
    85         -			-- preserve_metadata to work right
    86         -			max_items = 1;
    87         -			items = {
    88         -				{ items = {'sorcery:idol_' .. name} }
    89         -			};
    90         -		};
    91         -
    92         -		preserve_metadata = function(pos, node, meta, newstack)
    93         -			newstack[1]:get_meta():from_table(meta)
    94         -		end;
    95         -
    96     90   		on_timer = function(pos, elapsed)
    97     91   			local altar = minetest.find_node_near(pos, 3, "sorcery:altar")
    98     92   			-- TODO even without an altar, an idol with high favor could still be the source of miracles
           93  +			-- refills nearby partly empty troughs at cost to favor?
    99     94   			if not altar then return true end
   100     95   
   101     96   			local altarmeta = minetest.get_meta(altar)
   102     97   			local inv = altarmeta:get_inventory()
   103     98   			local idolmeta = minetest.get_meta(pos)
   104     99   			local divine_favor = idolmeta:get_int('favor')
   105    100   			local bestow = function(item,color)
................................................................................
   234    229   							}
   235    230   						end
   236    231   						-- preserve wear
   237    232   						local gift
   238    233   						if type(tx) == 'string' then
   239    234   							gift = ItemStack(tx)
   240    235   						else gift = tx end
          236  +						if not gift:is_known() then goto skip end
   241    237   						local wear = stack:get_wear()
   242    238   						if wear > 0 then
   243    239   							gift:set_wear(wear)
   244    240   						end
   245    241   						-- preserve meta
   246    242   						local gm = gift:get_meta()
   247    243   						gm:from_table(
................................................................................
   264    260   						if divine_favor >= cost then
   265    261   							bestow(gift)
   266    262   							divine_favor = divine_favor - cost
   267    263   							log.act(god.name, 'has consecrated', s, 'into', tx, 'for the cost of', cost, 'points of divine favor')
   268    264   							goto refresh
   269    265   						end
   270    266   					end
   271         -				end
          267  +				::skip::end
   272    268   			end
   273    269   
   274    270   			::refresh::
   275    271   			idolmeta:set_int('favor', divine_favor)
   276    272   			update_altar(altar,nil)
   277    273   			return true
   278    274   		end;

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

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

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

    22     22   		end;
    23     23   		infusion = 'sorcery:grease_pine';
    24     24   	};
    25     25   	Rapidity = {
    26     26   		color = {183,28,238}; qual = 'speed';
    27     27   		apply = inc('speed');
    28     28   		describe = function(potion)
    29         -			return 'good', 'Quickened', 'This potion will take effect more quiclkly and easily'
           29  +			return 'good', 'quickened', 'This potion will take effect more quickly and easily'
    30     30   		end;
    31     31   		infusion = 'sorcery:liquid_sap_acacia_bottle';
    32     32   	};
    33     33   	Purity = {
    34     34   		color = {244,255,255}; qual = 'purity';
    35     35   		apply = inc('purity');
    36     36   		describe = function(potion)
    37         -			return 'good', 'purified', 'This potion\'s impurities and undesirable side effects are diminished or eliminated'
           37  +			return 'good', 'purified', 'This potion\'s impurities and undesirable qualities are diminished or eliminated'
    38     38   		end;
    39     39   		infusion = 'sorcery:oil_purifying';
    40     40   	};
    41     41   	Beauty = {
    42     42   		color = {255,20,226}; qual = 'beauty';
    43     43   		apply = inc('beauty');
    44     44   		describe = function(potion)

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

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

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

   204    204   
   205    205   	local inv = meta:get_inventory()
   206    206   	local infusion = inv:get_list('infusion')
   207    207   	local potions = inv:get_list('potions')
   208    208   	local elixir = infusion[1]:get_definition()
   209    209   	local probe = sorcery.spell.probe(pos)
   210    210   	local fx = infuser_mods(pos)
          211  +	local sparkle_color = {sorcery.lib.color(255, 0, 145)};
   211    212   	if probe.disjunction then return true end
   212    213   
   213    214   	local potionct = 0
   214    215   
          216  +	local cancel = true
   215    217   	do
   216    218   		local ingredient -- *eyeroll*
   217    219   		if infusion[1]:is_empty() then goto cancel end
   218    220   		ingredient = infusion[1]:get_name()
   219    221   		for i = 1,#potions do
   220    222   			if potions[i]:is_empty() then goto skip end
   221    223   			potionct = potionct + 1
   222    224   			local base = potions[i]:get_name()
   223    225   			local potion = potions[i]:get_definition()
   224    226   			if elixir_can_apply(elixir._proto,potion) then
   225    227   				-- at least one combination makes a valid potion;
   226    228   				-- we can start the infuser
   227         -				goto start
          229  +				if elixir._proto.color then
          230  +					sparkle_color[#sparkle_color+1] = sorcery.lib.color(elixir._proto.color)
          231  +				end
          232  +				cancel = false
   228    233   			end
   229    234   			for _,v in pairs(sorcery.register.infusions.db) do
   230    235   				if v.infuse == ingredient and v.into == base then
   231    236   					-- at least one combination makes a valid
   232    237   					-- potion; we can start the infuser
   233         -					goto start
          238  +					if v.output.data and v.output.data.color then
          239  +						sparkle_color[#sparkle_color+1] = sorcery.lib.color(v.output.data.color)
          240  +					end
          241  +					cancel = false
   234    242   				end
   235    243   			end
   236    244   		::skip:: end
   237    245   
   238         -		::cancel:: do
          246  +		::cancel:: if cancel then
   239    247   			infuser_stop(pos)
   240    248   			return false
   241    249   		end
   242    250   
   243    251   		::start::
   244    252   	end
   245    253   
................................................................................
   268    276   				type = "vertical_frames";
   269    277   				aspect_h = 16;
   270    278   				aspect_w = 16;
   271    279   				length = 4.1;
   272    280   			};
   273    281   		}
   274    282   	end
   275         -	-- for i=0,4 do
   276         -		spawn('sorcery_spark.png^[multiply:#FF8FDD', 1, 32 * 4)
   277         -	-- end
   278         -	-- for i=0,4 do
   279         -		spawn('sorcery_spark.png^[multiply:#FFB1F6', 0.5, 64 * 4)
   280         -	-- end
   281         -	
          283  +	local spark = sorcery.lib.image('sorcery_spark.png')
          284  +	for i = 1,4 do
          285  +		local fac = 1 / i
          286  +		local _, spc = sorcery.lib.tbl.pick(sparkle_color)
          287  +		local sp = spark:glow(spc)
          288  +
          289  +		spawn(sp:render(), fac, (32/fac) * 4)
          290  +	end
          291  +
   282    292   	local discharge = sorcery.lib.node.discharger(pos)
   283    293   	
   284    294   	if newtime >= infusion_time then
   285    295   		-- finished
   286    296   		local ingredient = infusion[1]:get_name()
   287    297   		local result, residue = sorcery.alchemy.infuse(infusion[1], potions)
   288    298   		for i, r in pairs(result) do

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

   123    123   					m:set_int('charge',0)
   124    124   				else m:set_int('charge', amtleft) end
   125    125   				sorcery.liquid.sound_dip(chg,avail,pos)
   126    126   				update()
   127    127   
   128    128   				-- fancy visuals
   129    129   				local color = sorcery.lib.color(liq.color or {255,255,255})
   130         -				local spritz = sorcery.lib.image('sorcery_droplet.png')
   131         -				local drop = sorcery.lib.image('sorcery_drop.png')
   132         -				spritz = spritz:blit(spritz:multiply(color))
   133         -				drop   = drop:blit  (drop:multiply  (color))
          130  +				local spritz = sorcery.lib.image('sorcery_droplet.png'):glow(color)
          131  +				local drop = sorcery.lib.image('sorcery_drop.png'):glow(color)
   134    132   				local facing = minetest.facedir_to_dir(minetest.get_node(pos).param2)
   135    133   				local noz = vector.add(pos, vector.rotate(
   136    134   					vector.new(0.0,0,-0.48),
   137    135   					vector.dir_to_rotation(facing)
   138    136   				))
   139    137   				local minnoz = vector.offset(noz, -0.03, -0.32, -0.03);
   140    138   				local maxnoz = vector.offset(noz,  0.03, -0.32,  0.03);
................................................................................
   149    147   					minsize = 0.4, maxsize = 1;
   150    148   					glow = 14; -- FIXME liquid glow prop
   151    149   					minexptime = 0.5, maxexptime = 0.5;
   152    150   					animation = {
   153    151   						type = 'sheet_2d';
   154    152   						frames_w = 14;
   155    153   						frames_h = 1;
   156         -						frame_length = 0.5/14;
          154  +						frame_length = (0.5/14) + 0.02;
   157    155   					}
   158    156   				}
   159    157   				minetest.after(0.2, function()
   160    158   					minetest.add_particlespawner {
   161    159   						amount = math.random(5,11) * chg, time = 0.13 * chg;
   162    160   						texture = drop:render();
   163    161   						minpos = vector.offset(minnoz, 0,-0.05,0);
................................................................................
   169    167   						minsize = 0.3, maxsize = 0.5;
   170    168   						glow = 14; -- FIXME liquid glow prop
   171    169   						minexptime = 1, maxexptime = 1.5;
   172    170   						animation = {
   173    171   							type = 'sheet_2d';
   174    172   							frames_w = 10;
   175    173   							frames_h = 1;
   176         -							frame_length = 1.5/10;
          174  +							frame_length = (1.5/10) + 0.02;
   177    175   						}
   178    176   					}
   179    177   				end)
   180    178   
   181    179   				return filled
   182    180   			end
   183    181   		end

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

   358    358   				end
   359    359   				return i, false
   360    360   			end
   361    361   		else
   362    362   			return function(i) return i, false end
   363    363   		end
   364    364   	end;
          365  +
          366  +	autopreserve = function(id, tbl)
          367  +		tbl.drop = tbl.drop or {
          368  +			max_items = 1;
          369  +			items = {
          370  +				{ items = {id} };
          371  +			};
          372  +		}
          373  +		local next_apn = tbl.after_place_node
          374  +		tbl.after_place_node = function(...) local pos, who, stack = ...
          375  +			minetest.get_meta(pos):from_table(stack:get_meta():to_table())
          376  +			if next_apn then return next_apn(...) end
          377  +		end
          378  +		local next_pm = tbl.preserve_metadata
          379  +		tbl.preserve_metadata = function(...) local pos, node, meta, drops = ...
          380  +			drops[1]:get_meta():from_table({fields = meta})
          381  +			if next_pm then return next_pm(...) end
          382  +		end
          383  +		return tbl
          384  +	end;
          385  +	reg_autopreserve = function(id, tbl)
          386  +		minetest.register_node(id, sorcery.lib.node.autopreserve(id, tbl))
          387  +	end;
   365    388   }

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

   194    194   				L.image(fmt('sorcery_liquid_%s.png', liq.imgvariant or 'dull'))
   195    195   					:multiply(L.color(liq.color))):render()
   196    196   
   197    197   		-- local img_glass = L.image('vessels_drinking_glass.png'):blit(
   198    198   		-- 		L.image(fmt('sorcery_liquid_glass_%s.png', liq.imgvariant or 'dull'))
   199    199   		-- 			:multiply(L.color(liq.color)))
   200    200   
   201         -		minetest.register_node(':'..bottle, {
          201  +		sorcery.lib.node.reg_autopreserve(':'..bottle, {
   202    202   			description = liq.desc_bottle or fmt('%s Bottle', L.str.capitalize(liq.name));
   203    203   			inventory_image = img_bottle;
   204    204   			drawtype = 'plantlike', tiles = {img_bottle};
   205    205   			is_ground_content = false, walkable = false;
   206    206   			sunlight_propagates = true, paramtype = 'light';
   207    207   			light_source = liq.glow or 0;
   208    208   			selection_box = { type = 'fixed', fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} };
................................................................................
   244    244   		pos = pos;
   245    245   	}, true)
   246    246   end;
   247    247   
   248    248   sorcery.liquid.sound_dip = function(amt_output, amt_basin, pos)
   249    249   	sorcery.liquid.sound_pour(amt_output, amt_basin, pos)
   250    250   end;
          251  +
   251    252   
   252    253   -- pre-register basic liquids used in Sorcery and common ones sorcery depends on
   253    254   
   254    255   sorcery.liquid.register{
   255    256   	id = 'default:water';
   256    257   	name = 'water';
   257    258   	kind = 'default:drink';
................................................................................
   290    291   		['vessels:glass_bottle'] = 'sorcery:blood';
   291    292   	};
   292    293   }
   293    294   
   294    295   minetest.register_abm {
   295    296   	label = 'Rainfall';
   296    297   	nodenames = {'group:sorcery_collect_rainwater'};
   297         -	interval =  230;
   298         -	chance = 40;
          298  +	interval =  120;
          299  +	chance = 27;
   299    300   	min_y = -400;
   300    301   	catch_up = true;
   301    302   	action = function(pos, node)
   302    303   		-- TODO vary by season and biome?
   303    304   		if minetest.get_natural_light(vector.offset(pos,0,1,0), 0.5) >= 15 then
   304    305   			if node.name == 'sorcery:trough' then
   305    306   				node.name = 'default:trough_water_1'

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

    24     24   		drawtype = "plantlike";
    25     25   		tiles = {image};
    26     26   		inventory_image = image;
    27     27   		paramtype = "light";
    28     28   		is_ground_content = false;
    29     29   		light_source = glow and math.min(minetest.LIGHT_MAX,glow) or 0;
    30     30   		drop = 'sorcery:' .. name;
    31         -		preserve_metadata = function(pos,node,meta,newstack)
    32         -			newstack[1]:get_meta():from_table(meta)
    33         -		end;
    34     31   		walkable = false;
    35     32   		selection_box = {
    36     33   			type = "fixed",
    37     34   			fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25}
    38     35   		};
    39     36   		on_construct = function(pos)
    40     37   			minetest.get_meta(pos):set_string('infotext',label)
................................................................................
    43     40   	}
    44     41   	if extra then for k,v in pairs(extra) do node[k] = v end end
    45     42   	if not node.groups then node.groups = {} end
    46     43   	node.groups.dig_immediate = 3;
    47     44   	node.groups.attached_node = 1;
    48     45   	node.groups.vessel = 1;
    49     46   	node.groups.not_in_creative_inventory = 1;
    50         -	minetest.register_node("sorcery:"..name, node)
           47  +	sorcery.lib.node.reg_autopreserve("sorcery:"..name, node)
    51     48   end
    52     49   
    53     50   sorcery.register_oil = function(name,label,desc,color,imgvariant,extra)
    54     51   	local image = 'xdecor_bowl.png^(sorcery_oil_' .. (imgvariant or 'dull') .. '.png^[colorize:'..tostring(color)..':140)'
    55     52   	sorcery.register.residue.link('sorcery:' .. name, 'xdecor:bowl')
    56     53   	extra.description = label;
    57     54   	extra.inventory_image = image;

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

     5      5   -- passively consumes ley-current
     6      6   --  -- are phials & rune-wrenches enough for this now?
     7      7   
     8      8   local constants = {
     9      9   	rune_mine_interval = 240;
    10     10   	-- how often a powered forge rolls for new runes
    11     11   
    12         -	rune_cache_max = 4;
           12  +	rune_cache_max = 6;
    13     13   	-- how many runes a runeforge can hold at a time
    14     14   	
    15     15   	rune_grades = {'Fragile', 'Weak', 'Ordinary', 'Pristine', 'Sublime'};
    16     16   	-- how many grades of rune quality/power there are
    17     17   	
    18     18   	amulet_grades = {'Slight', 'Minor', 'Major', 'Grand', 'Ultimate' };
    19     19   	-- what kind of amulet each rune grade translates to
................................................................................
    35     35   			dist = { Fragile = 0, Weak = 1,    Ordinary = 0.9, Pristine = 0.5,  Sublime = 0.25 };
    36     36   		};
    37     37   		supreme  = {grade = 6, name = 'Supreme';  infusion = 'sorcery:powder_levitanium';
    38     38   			dist = { Fragile = 0, Weak = 0,    Ordinary = 1,   Pristine = 0.7,  Sublime = 0.4 };
    39     39   		};
    40     40   	};
    41     41   }
    42         -local calc_phial_props = function(phial) --> mine interval: float, time factor: float
           42  +local calc_phial_props = function(phial) --> mine interval: float, power factor: float
    43     43   	local m = phial:get_meta()
    44     44   	local g = phial:get_definition()._proto.data.grade
    45     45   	local i = constants.rune_mine_interval 
    46     46   	local fac = (g-1) / 5
    47         -	fac = fac + 0.4 * m:get_int('speed')
    48         -	return i - ((i*0.5) * fac), 0.5 * fac
           47  +	fac = fac + 0.2 * m:get_int('speed')
           48  +	return math.max(3,i - ((i*0.5) * fac)), 0.5 * fac
    49     49   end
    50     50   sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
    51     51   	local id = 'sorcery:rune_' .. name
    52     52   	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
    53     53   	rune.item = id
    54     54   	local c = sorcery.lib.color(rune.tone)
    55     55   	minetest.register_craftitem(id, {
................................................................................
    89     89   			_proto = { id = name, desc = desc, name = p.name, kind = phkind, data = p, quals = {force = true, speed = true}, color = color };
    90     90   		};
    91     91   	}
    92     92   	sorcery.register.infusions.link {
    93     93   		infuse = p.infusion;
    94     94   		into = 'sorcery:potion_subtle';
    95     95   		output = 'sorcery:'..id;
           96  +		_proto = {
           97  +			data = { color = color };
           98  +		};
    96     99   	}
    97    100   end
    98    101   
    99    102   local register_rune_wrench = function(w)
   100    103   	local mp = sorcery.data.metals[w.metal].parts
   101    104   	minetest.register_tool(w.name, {
   102    105   		description = w.desc;
................................................................................
   231    234   	local base_spell = true
   232    235   
   233    236   	if proto.frame and spell.frame and spell.frame[proto.frame] then
   234    237   		local sp = spell.frame[proto.frame]
   235    238   		if not sp.mingrade or rg >= sp.mingrade then
   236    239   			title = sp.name or title
   237    240   			desc = sp.desc or desc
   238         -			cast = sp.desc or cast
          241  +			cast = sp.cast or cast
   239    242   			apply = sp.apply or apply
   240    243   			remove = sp.remove or remove
   241    244   			mingrade = sp.mingrade or mingrade
   242    245   			base_spell = false
   243    246   		end
   244    247   	end
   245    248   	
................................................................................
   318    321   			else break end
   319    322   		end
   320    323   	end
   321    324   
   322    325   	has_phial = has_phial()
   323    326   	local spec = string.format([[
   324    327   		formspec_version[3] size[10.25,8] real_coordinates[true]
   325         -		list[context;cache;%f,0.25;%u,1;]
   326    328   		list[context;amulet;3.40,1.50;1,1;]
   327    329   		list[context;active;5.90,1.50;1,1;]
   328    330   
   329    331   		list[context;wrench;1.25,1.75;1,1;]
   330    332   		list[context;phial;7.25,1.75;1,1;]
   331    333   		list[context;refuse;8.50,1.75;1,1;]
   332    334   
   333    335   		list[current_player;main;0.25,3;8,4;]
   334    336   
          337  +		style_type[list;size=0.8]
          338  +		list[context;cache;%f,0.25;%u,1;]
          339  +
   335    340   		image[0.25,0.50;1,1;sorcery_statlamp_%s.png]
   336         -	]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max,
          341  +	]], (10.5 - 0.8*(constants.rune_cache_max*1.25))/2, constants.rune_cache_max,
   337    342   		((not (has_phial and pow_min)) and 'off'  ) or
   338    343   		( probe.disjunction            and 'blue' ) or
   339    344   	    ((has_phial and pow_max)       and 'green') or 'yellow')
   340    345   
   341    346   	local ghost = function(slot,x,y,img)
   342    347   		if i:is_empty(slot) then spec = spec .. string.format([[
   343    348   			image[%f,%f;1,1;%s.png]
................................................................................
   397    402   				local max,min = 0
   398    403   				for _,r in pairs(sorcery.data.runes) do
   399    404   					if r.minpower > max then max = r.minpower end
   400    405   					if min == nil or r.minpower < min then min = r.minpower end
   401    406   				end
   402    407   				-- high-quality phials reduce power usage
   403    408   				local fac = select(2, calc_phial_props(phial))
   404         -				min = min * fac  max = max * fac
          409  +				min = min / fac  max = max / fac
   405    410   				return min*time,max*time
   406    411   			end;
   407    412   		};
   408    413   		on_leychange = runeforge_update;
   409    414   		recipe = {
   410    415   			note = 'Periodically creates runes when sufficiently powered and can be used to imbue them into an amulet, giving it a powerful magical effect';
   411    416   		};

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

     1      1   local log = sorcery.logger('tap')
            2  +local sap_interval = 20;
            3  +
            4  +local function tapdrip(liq, pos)
            5  +	return sorcery.vfx.drip(liq, vector.offset(pos, 0, -0.3, 0), math.random(5,12), sap_interval, 2)
            6  +end
            7  +
     2      8   minetest.register_node('sorcery:tap',{
     3      9   	description = 'Tree Tap';
     4     10   	drawtype = 'mesh';
     5     11   	mesh = 'sorcery-tap.obj';
     6     12   	inventory_image = 'sorcery_tap_inv.png';
     7     13   	tiles = {
     8     14   		'default_copper_block.png';
................................................................................
    16     22   	paramtype = 'light', paramtype2 = 'wallmounted';
    17     23   	selection_box = { type='fixed', fixed = {-0.2,-0.5,-0.35; 0.3,0.1,0.4} };
    18     24   	collision_box = { type='fixed', fixed = {-0.2,-0.5,-0.35; 0.3,0.1,0.4} };
    19     25   	node_placement_prediction = '';
    20     26   	on_place = function(stack,who,where)
    21     27   		if where.type ~= 'node' then return end
    22     28   		local bl = minetest.get_node(where.under)
    23         -		-- FIXME prevent tapping 'dead' non-tree wood blocks
    24     29   
    25     30   		local tree = sorcery.tree.get(where.under)
    26         -		if not tree or tree.sap == false then return end;
           31  +		if not tree or tree.def.sap == false then return end;
    27     32   
    28         -		-- disallow vertical attachment
           33  +		-- disallow vertical attachment, bc that makes no sense
    29     34   		if vector.subtract(where.under,where.above).y ~= 0 then return end
    30     35   
    31     36   		minetest.set_node(where.above, {
    32     37   			name = 'sorcery:tap';
    33     38   			param2 = minetest.dir_to_wallmounted(vector.subtract(where.under,where.above))
    34     39   		})
           40  +		
           41  +		if sorcery.lib.node.tree_is_live(where.under) then
           42  +			-- start dripping immediately to indicate the tree is alive
           43  +			tapdrip(tree.def.sapliq, where.above)
           44  +		end
    35     45   
    36     46   		stack:take_item(1)
    37     47   		return stack
    38     48   	end;
           49  +	on_screwdriver = function() return false end;
    39     50   	_sorcery = {
    40     51   		recipe = {
    41     52   			note = 'Extract syrups and oils from trees';
    42     53   		};
    43     54   	};
    44     55   })
    45     56   
................................................................................
    48     59   	recipe = {
    49     60   		{'','sorcery:screw_steel','basic_materials:steel_bar'};
    50     61   		{'sorcery:pipe','sorcery:valve','sorcery:screw_steel'};
    51     62   		{'','sorcery:pipe',''};
    52     63   	};
    53     64   }
    54     65   
    55         -local sap_interval = 60;
    56     66   local abm_cache
    57     67   local abm_cache_time
    58     68   minetest.register_abm {
    59     69   	label = 'Sap drip';
    60     70   	nodenames = {'sorcery:tap'};
    61     71   	neighbors = {'group:tree'};
    62     72   	interval = sap_interval;
    63         -	chance = 7;
           73  +	chance = 4;
    64     74   	catch_up = true;
    65     75   	action = function(pos, node)
    66     76   		local now = os.time()
    67     77   		if abm_cache_time == nil or now > abm_cache_time + (sap_interval-1) then
    68     78   			abm_cache = { treehash = {} }
    69     79   			abm_cache_time = now
    70     80   		end
................................................................................
   101    111   			end
   102    112   		end
   103    113   
   104    114   		if (not live)
   105    115   			or tree.sap == false
   106    116   			or not tree.sapliq then return end
   107    117   		if mass_trunk < 12*3 then return end -- too small
          118  +
          119  +		tapdrip(tree.sapliq,pos)
   108    120   
   109    121   		local mass = mass_leaves + mass_trunk
   110    122   		local max_mass = 400
   111    123   		local ltratio = mass_leaves / mass_trunk
   112    124   		local mratio = mass / max_mass
   113    125   		local outof = 15 / mratio
   114    126   		local chance = math.max(1, math.floor(outof - (25 * ltratio))) / 3

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

   148    148   			animation = {
   149    149   				type = 'vertical_frames', length = far/vel;
   150    150   				aspect_w = 16, aspect_h = 16;
   151    151   			};
   152    152   		}
   153    153   	end
   154    154   end
          155  +
          156  +function sorcery.vfx.drip(liquid, noz, amt, time, exp)
          157  +	if type(liquid) == 'string' then liquid = sorcery.register.liquid.db[liquid] end
          158  +	local minnoz = vector.offset(noz, -0.03, 0.0, -0.03);
          159  +	local maxnoz = vector.offset(noz,  0.03, 0.0,  0.03);
          160  +	local drop = sorcery.lib.image('sorcery_drop.png'):multiply(liquid.color)
          161  +	return minetest.add_particlespawner {
          162  +		amount = amt, time = time;
          163  +		texture = drop:render();
          164  +		minpos = minnoz, maxpos = maxnoz;
          165  +		minvel = vector.new(0,0,0);
          166  +		maxvel = vector.new(0,-0.2,0);
          167  +		minacc = vector.new(0,-0.2,0);
          168  +		maxacc = vector.new(0,-0.23,0);
          169  +		minsize = 0.4, maxsize = 1;
          170  +		glow = liquid.glow or 2;
          171  +		minexptime = exp, maxexptime = exp;
          172  +		animation = {
          173  +			type = 'sheet_2d';
          174  +			frames_w = 10;
          175  +			frames_h = 1;
          176  +			frame_length = (exp/10) + 0.01;
          177  +		};
          178  +		vertical = true;
          179  +	}
          180  +end