sorcery  Check-in [41fdb5b0b8]

Overview
Comment:ui tweaks, rework enchantment slightly
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 41fdb5b0b80615f7ccde49d5172525b73cd4c4511fe2a317b7c00523c4d65deb
User & Date: lexi on 2021-04-21 01:32:38
Other Links: manifest | tags
Context
2021-06-23
09:31
look i don't fucking remember okay check-in: a08f21c56c user: lexi tags: trunk
2021-04-21
01:32
ui tweaks, rework enchantment slightly check-in: 41fdb5b0b8 user: lexi tags: trunk
2020-10-31
19:49
add background noise for condensers (temporary hack, need to write a proper environment sound framework as the fucking env_sounds module is completely impossible to extend), fix a couple of really stupid bugs, make higher-quality phials increase the chance of getting good runes so it's not a complete waste to burn iridium or levitanium powder on making them, add targeted disjunction and some other amulet spells check-in: 15f176a7fe user: lexi tags: trunk
Changes

Modified cookbook.lua from [604c53eb6c] to [e643a56c3c].

    29     29   		dye    = { caption = 'Any Dye',    cnitem = 'dye:black'      };
    30     30   		bone   = { caption = 'Any Bone',   cnitem = 'bonemeal:bone'  };
    31     31   		vessel = { caption = 'Any Bottle', cnitem = 'vessels:glass_bottle' };
    32     32   		flower = { caption = 'Any Flower', cnitem = 'flowers:rose' };
    33     33   		mushroom = { caption = 'Any Mushroom', cnitem = 'flowers:mushroom_brown' };
    34     34   		water_bucket = { caption = 'Water Bucket', cnitem = 'bucket:bucket_water' };
    35     35   		sorcery_ley_cable = { caption = 'Cable', cnitem = 'sorcery:cable_vidrium' };
           36  +		scissors = { caption = 'Scissors', cnitem = 'sorcery:scissors_steel' };
    36     37   	};
    37     38   }
    38     39   sorcery.cookbook.constants = constants
    39     40   
    40     41   local slot3x3 = {
    41     42   	{0,0}, {1,0}, {2,0};
    42     43   	{0,1}, {1,1}, {2,1};
................................................................................
   152    153   		desc = string.format("%s (%u)",desc,s:get_count())
   153    154   	end
   154    155   	return desc
   155    156   end;
   156    157   
   157    158   local bookadjs = { -- sets are in reverse order!
   158    159   	{'Celestial', 'Divine', 'Inspired', 'Heavenly';
   159         -	 'Mystic', 'Diabolic', 'Luminous', 'Forsaken'};
          160  +	 'Mystic', 'Diabolic', 'Luminous', 'Forsaken',
          161  +	 'Ethereal'};
   160    162   
   161    163   	{'Dark', 'Perfected', 'Flawless', 'Unthinkable';
   162         -	 'Impossible', 'Worrisome', 'Unimpeachable'};
          164  +	 'Impossible', 'Worrisome', 'Unimpeachable', 'Fulsome',
          165  +	 'Wise'};
   163    166   
   164    167   	{'Splendid', 'Magnificent', 'Sublime', 'Grand';
   165    168   	 'Beneficent', 'Mysterious', 'Peculiar', 'Eerie';
   166    169   	 'Fulsome', 'Fearsome', 'Curious', 'Fascinating';
   167         -     'Notorious', 'Infamous'};
          170  +     'Notorious', 'Infamous', 'Wondrous'};
   168    171   }
   169    172   
   170    173   local cache = {
   171    174   	populate_grindables = function(cache)
   172    175   		if not cache.grindables then
   173    176   			cache.grindables = {}
   174    177   			for k,v in pairs(minetest.registered_items) do
................................................................................
   312    315   			local rec = {}
   313    316   			local en = sorcery.data.enchants[name]
   314    317   			if not en then return nil end
   315    318   			en = en.recipe
   316    319   			for i,e in pairs(en) do
   317    320   				if e.lens then
   318    321   					rec[i] = 'sorcery:lens_' .. e.lens .. '_' .. e.gem
          322  +				elseif e.item then
          323  +					rec[i] = e.item
          324  +				end
          325  +				if e.consume or (e.item and not e.dmg) then
          326  +					rec[i] = rec[i] .. ' ' .. tostring(e.consume or 1) -- :/
   319    327   				end
   320    328   			end
   321    329   			return rec
   322    330   		end;
   323    331   		props = function(name)
   324         -			return sorcery.data.enchants[name].info or {}
          332  +			local ench = sorcery.data.enchants[name]
          333  +			local p = ench.info
          334  +			local desc = ''
          335  +			if ench.cost ~= 0 then
          336  +				desc = string.format('%s <b>%i</b> thaum-second%s of charge when tool is used',
          337  +					ench.cost > 0 and 'Consumes' or 'Generates',
          338  +					math.abs(ench.cost),
          339  +					ench.cost ~= 1 and 's' or ''
          340  +				)
          341  +			end
          342  +				
          343  +			if p == nil then return {note = desc} end
          344  +			if p.note   then return p end
          345  +			return sorcery.lib.tbl.proto({note = desc},p)	
   325    346   		end;
   326    347   		slots = {
   327    348   				{0.5,0};
   328    349   			{0,1},   {1,1}
   329    350   		};
   330    351   		title = function(name) return sorcery.data.enchants[name].name end;
   331    352   		outdesc = function(name,suffix)
   332    353   			local e = sorcery.data.enchants[name]
          354  +			local cap = sorcery.lib.str.capitalize
          355  +			local aff = sorcery.data.affinities[e.affinity]
   333    356   			return sorcery.lib.ui.tooltip {
   334    357   				title = e.name;
   335         -				desc = sorcery.lib.str.capitalize(e.desc);
   336         -				color = sorcery.lib.color(e.tone):readable();
          358  +				desc = cap(e.desc);
          359  +				color = sorcery.lib.color(e.tone);
          360  +				props = {
          361  +					{
          362  +						title = string.format('%s affinity', cap(e.affinity));
          363  +						desc = aff.desc;
          364  +						color = sorcery.lib.color(aff.color);
          365  +					};
          366  +				};
   337    367   			}
   338    368   		end;
   339    369   	};
   340    370   	-- spells = {
   341    371   	--  booksuf = 'Spellbook';
   342    372   	--	slots = {
   343    373   	--		{0,0}, {1,0};
................................................................................
   398    428   			end
   399    429   		end
   400    430   	end
   401    431   	local img, ot
   402    432   	if props.note then
   403    433   		local nx, ny, nw, nh
   404    434   		if notes_right then
   405         -			nx = 5.25 ny = 0
   406         -			nw = 4 nh = 3
          435  +			nx = 5.25 - (3 - k.w) -- :/
          436  +			ny = 0
          437  +			nw = 4 nh = k.h
   407    438   		else
   408    439   			nx = 0 ny = 3
   409         -			nw = 4 nh = 1
          440  +			nw = 4 nh = k,h
   410    441   		end
   411    442   		t = t .. string.format([[
   412    443   			hypertext[%f,%f;%f,%f;note;<global valign=middle halign=justify size=20>%s]
   413    444   		]], nx,ny,nw,nh, minetest.formspec_escape(props.note))
   414    445   	end
   415    446   	if k.icon then img = k.icon(result) end
   416    447   	if k.outdesc then ot = k.outdesc(result) else ot = desc_builtin(result) end
   417    448   		-- image[%f,%f;1,1;gui_furnace_arrow_bg.png^[transformR270]
   418    449   	return t .. string.format([[
   419    450   		item_image[%f,%f;1,1;%s]tooltip[%f,%f;1,1;%s]
   420         -		]] --[[box[%f,%f;1,1;#850083A0]] .. [[
          451  +		box[%f,%f;1.1,1.1;#1a001650]
   421    452   		%s[%f,%f;1,1;%s]
   422    453   		tooltip[%f,%f;1,1;%s]
   423    454   	]], k.w, k.h/2 - 0.5, k.node,
   424    455   		k.w, k.h/2 - 0.5, minetest.formspec_escape(minetest.registered_nodes[k.node].description),
   425         -			-- k.w+1, k.h/2 - 0.5,
          456  +			 k.w+1.05, k.h/2 - 0.55,
   426    457   		img and 'image' or 'item_image',
   427    458   			k.w+1.1, k.h/2 - 0.5, minetest.formspec_escape(img or result),
   428    459   			k.w+1.1, k.h/2 - 0.5, minetest.formspec_escape(ot))
   429    460   end;
   430    461   
   431    462   local retrieve_recipe = function(kind,out,notes_right)
   432    463   	local rec = recipe_kinds[kind]

Modified data/enchants.lua from [5670d710ce] to [f66e8dbb2f].

    16     16   		cost = 1;
    17     17   		tone = {232,102,255};
    18     18   		desc = 'tools last longer before wearing out';
    19     19   		affinity = 'counterpraxic';
    20     20   		groups = allgroups;
    21     21   		recipe = {
    22     22   			{lens = 'convex',    gem = 'amethyst', dmg = 2};
    23         -			{lens = 'rectifier', gem = 'emerald',  dmg = 4};
           23  +			{item = 'default:obsidian_shard'};
    24     24   			{lens = 'convex',    gem = 'emerald',  dmg = 2};
    25     25   		};
    26     26   		apply = function(stack,power,base)
    27     27   			local caps = table.copy(stack:get_definition().tool_capabilities)
    28     28   			for g,v in pairs(caps.groupcaps) do
    29     29   				local unit = base.groupcaps[g].uses * 0.6
    30     30   				caps.groupcaps[g].uses = v.uses + unit*power
................................................................................
    42     42   		groups = digtools;
    43     43   		affinity = 'cognic';
    44     44   		cost = 1;
    45     45   		tone = {255,235,195};
    46     46   		desc = 'Leave a trail of light hanging in the air as you dig';
    47     47   		recipe = {
    48     48   			{lens = 'convex',    gem = 'sapphire', dmg = 2};
    49         -			{lens = 'concave',   gem = 'ruby',     dmg = 1};
           49  +			{item = 'sorcery:gem_luxite_shard'};
    50     50   			{lens = 'concave',   gem = 'sapphire', dmg = 1};
    51     51   		};
    52     52   		on_dig = function(ctx)
    53     53   			local chance = 10 -- make dependent on power somehow?
    54     54   			if math.random(chance) == 1 then
    55     55   				local lightlevel = math.floor(math.min(minetest.LIGHT_MAX,4*ctx.power))
    56     56   				-- spawn a light block
................................................................................
    69     69   		cost = 0; -- energy is only depleted when repair takes place
    70     70   		tone = {255,84,187};
    71     71   		affinity = 'syncretic';
    72     72   		groups = {
    73     73   			'pick'; 'pickaxe'; 'sword';
    74     74   		};
    75     75   		recipe = {
    76         -			{lens = 'amplifier', gem = 'ruby',     dmg = 5};
    77         -			{lens = 'concave',   gem = 'mese',     dmg = 1};
    78         -			{lens = 'concave',   gem = 'sapphire', dmg = 1};
           76  +			{lens = 'amplifier', gem = 'ruby', dmg = 5};
           77  +			{item = 'sorcery:powder_tungsten'};
           78  +			{item = 'sorcery:extract_rye'};
    79     79   		};
    80     80   		desc = 'some damage is repaired when used to mine ore or kill an attacker';
           81  +		info = {
           82  +			note = 'Consumes <b>3</b> thaum-seconds of charge when repair takes place';
           83  +		};
    81     84   		on_dig = function(ctx)
    82     85   			local orepfx = "stone_with_" -- }:<
    83     86   			-- local oredrop = ' lump'
    84     87   			local barename = string.sub(ctx.node.name, string.find(ctx.node.name, ':') + 1)
    85     88   			if sorcery.itemclass.get(ctx.node.name,'ore') then
    86     89   				ctx.tool:add_wear(-(sorcery.enchant.strength(ctx.tool,'harvest') * 2000))
    87     90   				ctx.cost = 3
................................................................................
    92     95   		name = 'Conserve';
    93     96   		tone = {84,255,144};
    94     97   		cost = 0;
    95     98   		desc = 'enchantments last longer before running out of power to sustain them';
    96     99   		groups = allgroups;
    97    100   		affinity = 'syncretic';
    98    101   		recipe = {
    99         -			{lens = 'rectifier', gem = 'mese',     dmg = 7};
          102  +			{item = 'default:mese_crystal_fragment'};
   100    103   			{lens = 'rectifier', gem = 'sapphire', dmg = 2};
   101    104   			{lens = 'rectifier', gem = 'amethyst', dmg = 2};
   102    105   		};
   103    106   		-- implemented in sorcery/enchanter.lua:register_on_dig
   104    107   	};
   105    108   	dowse = { -- send up flare when valuable ores are nearby
   106    109   		name = 'Dowse';
   107    110   		tone = {241,251,113};
   108    111   		cost = 1;
   109    112   		desc = 'strike colored sparks when used to dig near valuable ore.';
   110    113   		groups = {'pick','pickaxe'};
   111    114   		affinity = 'cognic';
   112    115   		recipe = {
   113         -			{lens = 'concave', gem = 'ruby',     dmg = 3};
          116  +			{item = 'sorcery:gem_luxite'};
   114    117   			{lens = 'concave', gem = 'emerald',  dmg = 3};
   115    118   			{lens = 'concave', gem = 'sapphire', dmg = 3};
   116    119   		};
   117    120   		on_dig = function(ctx)
   118    121   			local range = 4*sorcery.enchant.strength(ctx.tool,'dowse')
   119    122   			local colors = {
   120    123   				['default:stone_with_gold'    ] = {255,234,182};
................................................................................
   150    153   		name = 'Glitter';
   151    154   		cost = 10;
   152    155   		tone = {255,50,60};
   153    156   		desc = 'dramatically improve your chances of finding gems while mining veins';
   154    157   		groups = {'pick','pickaxe'};
   155    158   		affinity = 'entropic';
   156    159   		recipe = {
          160  +			{item = 'sorcery:oil_luck'};
   157    161   			{lens = 'amplifier', gem = 'diamond',  dmg = 12};
   158    162   			{lens = 'rectifier', gem = 'sapphire', dmg = 9};
   159         -			{lens = 'convex',    gem = 'ruby',     dmg = 7};
   160    163   		};
   161    164   	};
   162    165   	pierce = { -- faster mining speed
   163    166   		name = 'Pierce';
   164    167   		cost = 3;
   165    168   		tone = {113,240,251};
   166    169   		groups = digtools;
   167         -		{
   168         -			'pick';'pickaxe';'axe';'shovel';'sickle';
   169         -		};
   170    170   		desc = 'rip through solid stone or wood like a hot knife through butter';
   171    171   		recipe = {
   172    172   			{lens = 'amplifier', gem = 'diamond',  dmg = 4};
   173    173   			{lens = 'amplifier', gem = 'ruby',     dmg = 4};
   174         -			{lens = 'rectifier', gem = 'diamond',  dmg = 2};
          174  +			{item = 'default:flint'};
   175    175   		};
   176    176   		affinity = 'praxic';
   177    177   		apply = function(stack,power,base)
   178    178   			local caps = table.copy(stack:get_definition().tool_capabilities)
   179    179   			for g,v in pairs(caps.groupcaps) do
   180    180   				for i,t in pairs(v.times) do
   181    181   					local unit = base.groupcaps[g].times[i] * 0.15
................................................................................
   188    188   	};
   189    189   	rend = { -- more damage / mine higher level blocks
   190    190   		name = 'Rend';
   191    191   		affinity = 'praxic';
   192    192   		tone = {251,203,113};
   193    193   		groups = {'sword';'pick';'pickaxe';};
   194    194   		recipe = {
   195         -			{lens = 'convex',    gem = 'mese',    dmg = 3};
   196    195   			{lens = 'amplifier', gem = 'emerald', dmg = 7};
   197         -			{lens = 'amplifier', gem = 'diamond', dmg = 7};
          196  +			{item = 'flowers:flower_rose'};
          197  +			{item = 'sorcery:powder_silver'};
   198    198   		};
   199    199   		cost = 5;
   200    200   		desc = 'cleave through sturdy ores and tear mortal flesh with fearsome ease';
   201    201   		apply = function(stack,power,base)
   202    202   			local caps = table.copy(stack:get_definition().tool_capabilities)
   203    203   			for g,v in pairs(caps.groupcaps) do
   204    204   				local unit = 2
................................................................................
   217    217   	sanctify = {
   218    218   		desc = 'prolong the blessings of the heavens';
   219    219   		groups = {'sorcery_sanctify'};
   220    220   		affinity = 'entropic';
   221    221   		tone = {255,255,255};
   222    222   		cost = 7;
   223    223   		recipe = {
   224         -			{lens = 'amplifier', gem = 'ruby', dmg = 13};
          224  +			{item = 'sorcery:holy_water'};
   225    225   			{lens = 'amplifier', gem = 'ruby', dmg = 15};
   226    226   			{lens = 'amplifier', gem = 'ruby', dmg = 18};
   227    227   		};
   228    228   	};
   229    229   }

Modified data/metals.lua from [3560b06dd9] to [81e12129e0].

   392    392   		artificial=true;
   393    393   		meltpoint = 5;
   394    394   		cooktime = 120;
   395    395   		hardness = 8;
   396    396   		maxconduct = 15;
   397    397   		sharpness = 5;
   398    398   		level = 2;
   399         -		speed = 1.7;
          399  +		speed = 2.5;
   400    400   		maxenergy = 2200;
   401    401   		durability = 1500;
   402    402   		slots = {
   403    403   			{affinity={'praxic'},confluence=3};
   404    404   			{affinity={'syncretic'},confluence=2};
   405    405   		};
   406    406   		sinter = {

Modified data/oils.lua from [6619d22587] to [1e67dfd074].

   105    105   		mix = {
   106    106   			'sorcery:extract_greengrass';
   107    107   			'sorcery:extract_grape';
   108    108   			'farming:cocoa_beans';
   109    109   			'farming:sugar';
   110    110   			'farming:sugar';
   111    111   		};
          112  +	};
          113  +	luck = {
          114  +		color = {156,54,255};
          115  +		style = 'sparkle';
          116  +		mix = {
          117  +			'sorcery:extract_marram';
          118  +			'farming:hemp_leaf';
          119  +			'farming:hemp_oil';
          120  +			'xdecor:honey';
          121  +			'farming:salt';
          122  +			'farming:salt';
          123  +		};
   112    124   	};
   113    125   }

Modified data/runes.lua from [56e196edd6] to [92db18c27d].

   140    140   				cast = function(ctx)
   141    141   					local target = minetest.get_player_by_name(ctx.meta:get_string('rune_join_target'))
   142    142   					if not target then return false end
   143    143   
   144    144   					local subjects if ctx.amulet.frame == 'cobalt' then
   145    145   						if ctx.target.type ~= 'object' then return false end
   146    146   						subjects = {{ref=ctx.target.ref}}
          147  +					elseif ctx.amulet.frame == 'iridium' then
          148  +						subjects = {}
          149  +						for _,o in pairs(minetest.get_objects_inside_radius(ctx.caster:get_pos(), ctx.stats.power)) do
          150  +							subjects[#subjects+1] = {player = o}
          151  +						end
   147    152   					else subjects = {{ref=ctx.caster}} end
   148    153   
   149    154   					local delay = math.max(5,11 - ctx.stats.power) + 2.3*(math.random()*2-1)
   150    155   					local color = sorcery.lib.color(117,38,237)
   151    156   					teleport(ctx,subjects,delay,target:get_pos(),color)
   152    157   					if ctx.amulet.frame == 'gold' then
   153    158   						teleport(ctx,{{ref=target}},delay,ctx.caster:get_pos())
................................................................................
   864    869   				mingrade = 4;
   865    870   				name = 'Duplication';
   866    871   				desc = 'Bring an exact twin of any object or item into existence, no matter how common or rare it might be';
   867    872   				cast = function(ctx)
   868    873   					local color = sorcery.lib.color(255,61,205)
   869    874   					local dup, sndpos, anchor, sbj, ty
   870    875   					if ctx.target.type == 'object' and ctx.target.ref:get_luaentity().name == '__builtin:item' then
   871         -						-- sorcery.vfx.imbue(color, ctx.target.ref) -- causes graphics card problems???
          876  +						sorcery.vfx.imbue(color, ctx.target.ref) -- causes graphics card problems???
   872    877   						sndpos = 'subjects'
   873    878   						sbj = {{player = ctx.target.ref}}
   874    879   						local item = ItemStack(ctx.target.ref:get_luaentity().itemstring)
   875    880   						local r = function() return math.random() * 2 - 1 end
   876    881   						local putpos = vector.offset(ctx.target.ref:get_pos(), r(), 1, r())
   877    882   						dup = function()
   878    883   							item:set_count(1) -- nice try bouge-san

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

    27     27   	end
    28     28   	return r[math.random(#r)]
    29     29   end
    30     30   local anchorwand = function(aff,uses,recipe)
    31     31   	local affcolor = sorcery.lib.color(sorcery.data.affinities[aff].color)
    32     32   	return {
    33     33   		name = aff .. ' anchor';
    34         -		desc = 'With an enchanter, anchor ' .. aff .. ' spells into an object to enable it to produce preternatural effects';
           34  +		desc = 'Destroy items on an enchanter and channel their essence with enchanting lenses to anchor ' .. aff .. ' spells into an object, enabling it to produce preternatural effects';
    35     35   		uses = uses;
    36     36   		affinity = recipe;
    37     37   		color = affcolor;
    38     38   		sound = 'xdecor_enchanting'; -- FIXME make own
    39     39   		cast = function(ctx)
    40     40   			local node = target_node(ctx, 'sorcery:enchanter')
    41     41   			if not node then return false end
................................................................................
   112    112   					local proto = stack:get_definition()._proto
   113    113   					if proto.kind ~= spec.lens or proto.gem ~= spec.gem
   114    114   						then return false end
   115    115   				elseif spec.item then
   116    116   					default_mode = 'consume'
   117    117   					if stack:get_name() ~= spec.item then
   118    118   						return false end
          119  +					if spec.consume and stack:get_count() < spec.consume then
          120  +						return false end
   119    121   				else
   120    122   					return false
   121    123   				end
   122    124   
   123    125   				local mode
   124    126   				if spec.dmg then mode = 'dmg'
   125    127   				elseif spec.consume then mode = 'consume'
   126    128   				else mode = default_mode end
   127    129   
   128    130   				if mode == 'dmg' then
   129    131   					stack:add_wear((spec.dmg or 1) * 1000)
   130    132   					return stack
   131    133   				elseif mode == 'consume' then
          134  +					local r = sorcery.register.residue.db[stack:get_name()]
   132    135   					stack:take_item(spec.consume or 1)
          136  +					if r then
          137  +						local rs = ItemStack(r) 
          138  +						rs:set_count(rs:get_count() * (spec.consume or 1))
          139  +						if stack:is_empty()
          140  +							then stack = rs
          141  +							else minetest.add_item(ctx.target.above, rs)
          142  +						end
          143  +					end
   133    144   					return stack
   134    145   				end
   135    146   			end
   136    147   			for ench,data in pairs(sorcery.data.enchants) do
   137    148   				if data.affinity ~= aff or data.recipe == nil then goto skip end
   138    149   				local newinv = {}
   139    150   				for s,v in pairs(data.recipe) do
................................................................................
   191    202   			bolt:get_luaentity()._blastradius = radius
   192    203   			bolt:set_velocity(vel)
   193    204   		end;
   194    205   	};
   195    206   	seal = {
   196    207   		name = 'sealing';
   197    208   		color = {255,238,16};
   198         -		uses = 32;
          209  +		uses = 128;
   199    210   		desc = 'Bind an object to your spirit such that it will be rendered impregnable to others, or break a sealing created with this same wand';
   200    211   		leytype = 'imperic';
   201    212   		affinity = {'pine','dark'};
   202    213   		cast = function(ctx)
   203    214   			if ctx.target == nil or ctx.target.type ~= 'node' then return false end
   204    215   			local meta = minetest.get_meta(ctx.target.under)
   205    216   			-- first we need to check if the wand has an identifying 'key' yet,
................................................................................
   252    263   		uses = 128;
   253    264   		desc = 'Reveal the strength and affinities of the local leyline';
   254    265   		cast = function(ctx)
   255    266   			local color = ctx.base.gem == 'sapphire';
   256    267   			local duration = (ctx.base.gem == 'amethyst' and 4) or 2;
   257    268   			local ley = sorcery.ley.estimate(ctx.caster:get_pos())
   258    269   
   259         -			local strength = ley.force
          270  +			local strength = ley.force * 4 * ley.force
   260    271   			if color then
   261    272   				strength = strength / #ley.aff
   262    273   				for _,a in pairs(ley.aff) do
   263         -					cast_sparkle(ctx,sorcery.lib.color(sorcery.data.affinities[a].color):brighten(1.3), strength, duration * strength)
          274  +					cast_sparkle(ctx,sorcery.lib.color(sorcery.data.affinities[a].color):brighten(1.3), strength, duration * (strength*0.5))
   264    275   				end
   265    276   			else
   266    277   				cast_sparkle(ctx,sorcery.lib.color(250,255,185), strength, duration*strength)
   267    278   			end
   268    279   
   269    280   		end;
   270    281   	};
................................................................................
   583    594   			end
   584    595   			newenergy = math.min(maxcharge, newenergy * (ctx.stats.power or 1))
   585    596   
   586    597   			sorcery.ley.setcharge(rechargee,charge + newenergy)
   587    598   			e:set_stack('item',1,rechargee)
   588    599   
   589    600   			enchantment_sparkle(ctx, sorcery.lib.color(212,6,63))
          601  +			sorcery.enchant.update_enchanter(ctx.target.under)
   590    602   		end;
   591    603   	};
   592    604   	transfer = {
   593    605   		name = 'transfer';
   594    606   		uses = 65;
   595    607   		color = {6,212,121};
   596    608   		leytype = 'syncretic';
................................................................................
   624    636   				if ctx.base.gem == 'sapphire'
   625    637   					then e.spells = {} e.energy = 0
   626    638   					else table.remove(e.spells, math.random(#e.spells))
   627    639   				end
   628    640   			end
   629    641   			sorcery.enchant.set(item,e)
   630    642   			ei:set_stack('item',1,item)
          643  +			sorcery.enchant.update_enchanter(ctx.target.under)
   631    644   			enchantment_sparkle(ctx,sorcery.lib.color(255,154,44))
   632    645   			enchantment_sparkle(ctx,sorcery.lib.color(226,44,255))
   633    646   		end;
   634    647   	};
   635    648   	divine = {
   636    649   		name = 'divining';
   637    650   		desc = 'Steal away the secrets of the cosmos';

Modified entities.lua from [9e257faffc] to [5b6d6366ce].

     3      3   	age = u.marshal.t.u32;
     4      4   	lastemit = u.marshal.t.u32;
     5      5   }
     6      6   
     7      7   minetest.register_entity('sorcery:spell_projectile_flamebolt',{
     8      8   	initial_properties = {
     9      9   		visual = "sprite";
    10         -        use_texture_alpha = true;
           10  +        use_texture_alpha = 'blend';
    11     11   		textures = {'sorcery_fireball.png'};
    12     12   		visual_size = { x = 2, y = 2, z = 2 };
    13     13   		physical = true;
    14     14   		collide_with_objects = true;
    15     15   		pointable = false;
    16     16   		glow = 14;
    17     17   		static_save = false;

Deleted hotmetallurgy.lua version [ac0bc31ba3].

     1         --- alloying furnace
     2         ---
     3         --- there are several kinds of alloy furnace, with varying
     4         --- capabilities. the simplest can alloy two simple metals;
     5         --- the most complex can alloy four high-grade metals such
     6         --- as titanium, platinum, iridium, or levitanium.
     7         ---
     8         --- furnace recipes follow a pattern: the number of crucibles
     9         --- determines the input slots, the type of crucible determines
    10         --- how hot the furnace can get, and various other components
    11         --- (like a coolant circulator) can be added to allow the
    12         --- creation of exotic metals.
    13         ---
    14         --- alloy furnaces produce ingots that can later be melted down
    15         --- again and cast into a mold to produce items of particular
    16         --- shapes.
    17         -
    18         --- there are five kinds of crucibles: clay, aluminum, platinum,
    19         --- duridium, and impervium. clay crucibles are made by molding
    20         --- clay into the proper shape and then firing it in a furnace.
    21         --- others are made by casting.
    22         -
    23         -local fragments_per_ingot = 4 
    24         -
    25         -for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do
    26         -	minetest.register_craftitem('sorcery:crucible_' .. c, {
    27         -		description = sorcery.lib.str.capitalize(c .. ' crucible');
    28         -		inventory_image = 'sorcery_crucible_' .. c .. '.png';
    29         -	})
    30         -end
    31         -
    32         -minetest.register_craftitem('sorcery:crucible_clay_molding', {
    33         -	description = sorcery.lib.str.capitalize('Crucible molding');
    34         -	inventory_image = 'sorcery_crucible_clay_molding.png';
    35         -})
    36         -
    37         -minetest.register_craft {
    38         -	recipe = {
    39         -		{ 'default:clay_lump', '', 'default:clay_lump'};
    40         -		{ 'default:clay_lump', '', 'default:clay_lump'};
    41         -		{ 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'};
    42         -	};
    43         -	output = 'sorcery:crucible_clay_molding';
    44         -}
    45         -minetest.register_craft {
    46         -	type = 'shapeless';
    47         -	recipe = { 'sorcery:crucible_clay_molding' };
    48         -	output = 'default:clay_lump 7';
    49         -}
    50         -minetest.register_craft {
    51         -	type = 'cooking';
    52         -	recipe = 'sorcery:crucible_clay_molding';
    53         -	cooktime = 40;
    54         -	output = 'sorcery:crucible_clay';
    55         -}
    56         -
    57         -local burn_layout = function(v)
    58         -	local layouts = {
    59         -		[1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1};
    60         -		[4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2};
    61         -		[7] = {w = 4, h = 2}; [8] = {w = 4, h = 2}; [9] = {w = 3, h = 3};
    62         -	}
    63         -	local inpos  = { x = 2.8, y = 1 }
    64         -	local insize = layouts[v.in]
    65         -	local outpos = { x = 5.2, y = 2.4 }
    66         -	local outsize = layouts[v.out]
    67         -	local fuelpos = { x = 2.8, y = 3.4 }
    68         -	local fuelsize = layouts[v.fuel]
    69         -	return string.format([[
    70         -		list[context;input;%f,%f;%f,%f;]
    71         -		list[context;output;%f,%f;%f,%f;]
    72         -		list[context;fuel;%f,%f;%f,%f;]
    73         -		
    74         -		image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png]
    75         -		image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270]
    76         -
    77         -		listring[context;output] listring[current_player;main]
    78         -		listring[context;input]  listring[current_player;main]
    79         -		listring[context;fuel]   listring[current_player;main]
    80         -	]], 
    81         -
    82         -	--input
    83         -		inpos.x - insize.w/2, -- pos
    84         -		inpos.y - insize.h/2, 
    85         -		insize.w, insize.h, -- size
    86         -	--output
    87         -		outpos.x - outsize.w/2, -- pos
    88         -		outpos.y - outsize.h/2, 
    89         -		outsize.w, outsize.h, -- size
    90         -	--fuel
    91         -		fuelpos.x - fuelsize.w/2, -- pos
    92         -		fuelpos.y - fuelsize.h/2, 
    93         -		fuelsize.w, fuelsize.h, -- size
    94         -
    95         -		v.burn, v.prog
    96         -	)
    97         -end
    98         -
    99         -local kiln_formspec = function(kind, fuel_progress, cook_progress)
   100         -	return [[
   101         -		size[8,8]
   102         -		list[current_player;main;0,4.2;8,4]
   103         -		list[context;wax;0,2;1,1]
   104         -	]] .. burn_layout{
   105         -		in = kind.size^2;
   106         -		out = kind.outsize;
   107         -		fuel = kind.fuelsize;
   108         -		burn = fuel_progress;
   109         -		prog = cook_progress;
   110         -	}
   111         -end
   112         -
   113         -local smelter_formspec = function(kind, fuel_progress, smelt_progress)
   114         -	return [[
   115         -		size[8,8]
   116         -		list[current_player;main;0,4.2;8,4]
   117         -	]] .. burn_layout {
   118         -		in = kind.size;
   119         -		out = kind.outsize;
   120         -		fuel = kind.fuelsize;
   121         -		burn = fuel_progress;
   122         -		prog = smelt_progress;
   123         -	}
   124         -end
   125         -
   126         -local find_recipe = function(inv)
   127         -	local mix = {}
   128         -	local count = 0
   129         -	for i=1,inv:get_size('input') do
   130         -		local m = inv:get_stack('input',i)
   131         -		if m:is_empty() then goto skip end
   132         -		local l = sorcery.data.metallookup[m:get_name()]
   133         -		if not l then return false end
   134         -		mix[l.id] = (mix[l.id] or 0) + l.value
   135         -		count = count + l.value
   136         -	::skip::end
   137         -	-- everything is metal, we've finished summing it up.
   138         -	-- let's see if the assembled items match the ratio
   139         -	-- specified in any of the smelting recipes.
   140         -	local matches = 0
   141         -	for _,rec in pairs(sorcery.data.alloys) do
   142         -		local fac = nil
   143         -		local meltpoint = 1
   144         -		if rec.metals == nil then goto skip_recipe end
   145         -		for metal, ratio in pairs(rec.metals) do
   146         -			if mix[metal] and mix[metal] % ratio == 0 then
   147         -				if fac then
   148         -					if mix[metal] / ratio ~= fac then goto skip_recipe end
   149         -				else fac = math.floor(mix[metal] / ratio) end
   150         -				local m = sorcery.data.metals[metal]
   151         -				if m.meltpoint then
   152         -					meltpoint = math.max(meltpoint, m.meltpoint) end
   153         -			else goto skip_recipe end
   154         -		end
   155         -		do return rec, count, fac, meltpoint end
   156         -	::skip_recipe::end
   157         -	return false
   158         -end
   159         -
   160         -local update_smelter = function(pos)
   161         -	local meta = minetest.get_meta(pos)
   162         -	local inv = meta:get_inventory()
   163         -	local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto
   164         -	local recipe, count, factor, meltpoint = find_recipe(inv)
   165         -	if recipe --[[and proto.temp >= meltpoint]] then
   166         -		minetest.get_node_timer(pos):start(1)
   167         -	else
   168         -		meta:set_float('burntime',0)
   169         -	end
   170         -end
   171         -
   172         -local smelter_step = function(kind,active,pos,delta)
   173         -	local meta = minetest.get_meta(pos)
   174         -	local inv = meta:get_inventory()
   175         -	local recipe, count, factor = find_recipe(inv)
   176         -	local cooktime
   177         -	local elapsed = meta:get_float('burntime') + delta
   178         -	meta:set_float('burnleft',meta:get_float('burnleft') - delta)
   179         -	if (not active) and (not recipe) then return false end
   180         -	if meta:get_float('burnleft') <= 0 or not active then
   181         -		if recipe then
   182         -			local burn, frep = minetest.get_craft_result {
   183         -				method = 'fuel', width = 1;
   184         -				items = { inv:get_stack('fuel',1) };
   185         -			}
   186         -			if burn.time == 0 then goto nofuel end
   187         -			inv:set_stack('fuel', 1, frep.items[1])
   188         -			meta:set_float('burnleft',burn.time)
   189         -			meta:set_float('burnmax',burn.time)
   190         -			if not active then
   191         -				minetest.swap_node(pos,
   192         -					sorcery.lib.tbl.merge(minetest.get_node(pos), {
   193         -						name = kind.id .. '_active'
   194         -					}))
   195         -				active = true
   196         -			end
   197         -		else goto nofuel end
   198         -	end
   199         -
   200         -	if not recipe then goto update end
   201         -	
   202         -	cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor
   203         -	if elapsed >= cooktime then
   204         -		elapsed = 0
   205         -		-- remove used items
   206         -		for i=1,inv:get_size('input') do
   207         -			local s = inv:get_stack('input',i)
   208         -			if s:is_empty() then goto skip end
   209         -			s:take_item(1) inv:set_stack('input',i,s)
   210         -		::skip::end
   211         -
   212         -		local outstack
   213         -		if count % fragments_per_ingot == 0 then
   214         -			outstack = ItemStack {
   215         -				name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot';
   216         -				count = count / fragments_per_ingot;
   217         -			}
   218         -		else
   219         -			outstack = ItemStack {
   220         -				name = 'sorcery:fragment_' .. recipe.output;
   221         -				count = count;
   222         -			}
   223         -		end
   224         -
   225         -		local leftover = inv:add_item('output',outstack)
   226         -		if not leftover:is_empty() then
   227         -			minetest.add_item(pos, leftover)
   228         -		end
   229         -	end
   230         -
   231         -	::update::
   232         -		meta:set_float('burntime',elapsed)
   233         -		meta:set_string('formspec', smelter_formspec(kind,
   234         -			math.min(1, meta:get_float('burnleft') /
   235         -						meta:get_float('burnmax')
   236         -					) * 100, -- fuel
   237         -			(cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt
   238         -		))
   239         -		do return active end
   240         -
   241         -	::nofuel::
   242         -		if active then
   243         -			minetest.swap_node(pos,
   244         -				sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id }))
   245         -		end
   246         -		meta:set_float('burnleft',0) -- just in case
   247         -	::noburn::
   248         -		meta:set_float('burntime',0) -- just in case
   249         -		meta:set_string('formspec', smelter_formspec(kind, 0, 0))
   250         -		return false
   251         -end
   252         -local register_kiln = function(kind)
   253         -	local box = {
   254         -		open = {
   255         -			type = 'fixed';
   256         -			fixed = {
   257         -				-0.5,-0.5,-0.5,
   258         -				 0.5, 1.6, 0.5
   259         -			};
   260         -		};
   261         -		closed = {
   262         -			type = 'fixed';
   263         -			fixed = {
   264         -				-0.5,-0.5,-0.5,
   265         -				 0.5, 0.7, 0.5
   266         -			};
   267         -		};
   268         -	}
   269         -	local id = 'sorcery:kiln_' .. kind.material
   270         -	local metal = sorcery.data.metals[kind.material]
   271         -	-- use some iffy heuristics to guess the texture
   272         -	local metaltex
   273         -	if kind.material == 'clay' then
   274         -		metaltex = 'default_brick.png';
   275         -	else
   276         -		metaltex = (metal.img and metal.img.block) or
   277         -			(metal.ingot and 'default_' .. kind.material .. '_block.png') or
   278         -			sorcery.lib.image('default_steel_block.png'):multiply(sorcery.lib.color(metal.tone)):render();
   279         -	end
   280         -	local tex = {
   281         -		open = {
   282         -			'default_furnace_front.png';
   283         -			metaltex;
   284         -			'default_copper_block.png';
   285         -			'default_stone.png';
   286         -		};
   287         -		closed = {
   288         -			'default_furnace_front_active.png';
   289         -			'default_copper_block.png';
   290         -			metaltex;
   291         -			'default_stone.png';
   292         -		};
   293         -	};
   294         -	for _,state in pairs{'open','closed'} do
   295         -		local id_closed = id .. '_closed'
   296         -		local id_current = (state == 'closed' and id_closed) or id
   297         -		local desc = sorcery.lib.str.capitalize(kind.temp_name) .. ' kiln';
   298         -		minetest.register_node(id_current, {
   299         -			_active = (state == 'closed');
   300         -			_proto = kind;
   301         -			description = desc;
   302         -			drawtype = "mesh";
   303         -			mesh = 'sorcery-kiln-' .. state .. '.obj';
   304         -			drop = id;
   305         -			groups = { cracky = 2; sorcery_device_kiln = (state == 'closed') and 1 or 2; }
   306         -			sunlight_propagates = true;
   307         -			paramtype1 = 'light';
   308         -			paramtype2 = 'facedir';
   309         -			selection_box = box[state];
   310         -			collision_box = box[state];
   311         -			tiles = tex[state];
   312         -			light_source = (state == 'closed' and 7) or 0;
   313         -			on_construct = function(pos)
   314         -				local meta = minetest.get_meta(pos)
   315         -				local inv = meta:get_inventory()
   316         -				inv:set_size('input', kind.size^2)
   317         -				inv:set_size('output', kind.outsize)
   318         -				inv:set_size('fuel', kind.fuelsize)
   319         -				inv:set_size('wax', 1)
   320         -
   321         -				meta:set_float('burnleft',0)
   322         -				meta:set_float('burnmax',0)
   323         -				meta:set_float('burntime',0)
   324         -
   325         -				meta:set_string('infotext',desc)
   326         -				meta:set_string('formspec',kiln_formspec(kind,0,0))
   327         -			end;
   328         -			on_timer = function(pos,delta)
   329         -			end;
   330         -
   331         -			on_metadata_inventory_put = function(pos)
   332         -				minetest.get_node_timer(pos):start(1) end;
   333         -			on_metadata_inventory_move = function(pos)
   334         -				minetest.get_node_timer(pos):start(1) end;
   335         -			on_metadata_inventory_take = function(pos)
   336         -				minetest.get_node_timer(pos):start(1) end;
   337         -		})
   338         -	end
   339         -	local ingot
   340         -	if kind.material == 'clay' then
   341         -		ingot = 'default:clay_brick';
   342         -	else
   343         -		ingot = metal.ingot or 'sorcery:' .. kind.material .. '_ingot'
   344         -	end
   345         -	minetest.register_craft = {
   346         -		output = id_open;
   347         -		recipe = {
   348         -			{'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'};
   349         -			{ingot,'',ingot};
   350         -			{ingot,'default:furnace',ingot};
   351         -		};
   352         -	}
   353         -end
   354         -
   355         -local register_smelter = function(kind)
   356         -	local recipe = {{},{};
   357         -		{'default:stone','default:furnace','default:stone'};
   358         -	} do
   359         -		local on = kind.crucible
   360         -		local ti = 'default:tin_ingot'
   361         -		local cu = 'default:copper_ingot'
   362         -		local crucmap = {
   363         -			[2] = { {cu,cu,cu}, {on,ti,on} };
   364         -			[3] = { {cu,on,cu}, {on,ti,on} };
   365         -			[4] = { {on,cu,on}, {on,ti,on} };
   366         -			[5] = { {on,cu,on}, {on,on,on} };
   367         -			[6] = { {on,on,on}, {on,on,on} };
   368         -		};
   369         -		for y=1,2 do recipe[y] = crucmap[kind.size][y] end
   370         -	end
   371         -	
   372         -	local desc = 'smelter';
   373         -	if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end
   374         -	if kind.size_name then desc = kind.size_name .. ' ' .. desc end
   375         -	desc = sorcery.lib.str.capitalize(desc);
   376         -	local id = 'sorcery:smelter_' .. kind.material .. kind.size_name
   377         -	kind.id = id
   378         -	for _, active in pairs {false, true} do
   379         -		minetest.register_node((active and id .. '_active') or id, {
   380         -			_proto = kind;
   381         -			description = desc;
   382         -			drop = id;
   383         -			groups = {
   384         -				cracky = 2;
   385         -				sorcery_device_smelter = active and 1 or 2;
   386         -			};
   387         -			paramtype2 = 'facedir';
   388         -			light_source = (active and 9) or 0;
   389         -			on_construct = function(pos)
   390         -				local meta = minetest.get_meta(pos)
   391         -				local inv = meta:get_inventory()
   392         -				inv:set_size('input',kind.size)
   393         -				inv:set_size('output',kind.outsize)
   394         -				inv:set_size('fuel',kind.fuelsize)
   395         -				meta:set_string('infotext', desc)
   396         -				meta:set_string('formspec', smelter_formspec(kind, 0, 0))
   397         -				meta:set_float('burnleft',0)
   398         -				meta:set_float('burnmax',0)
   399         -				meta:set_float('burntime',0)
   400         -			end;
   401         -			on_metadata_inventory_put = update_smelter;
   402         -			on_metadata_inventory_move = update_smelter;
   403         -			on_metadata_inventory_take = update_smelter;
   404         -			on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end;
   405         -			-- allow_metadata_inventory_put = function(pos, listname, index, stack, player)
   406         -			-- end;
   407         -			tiles = {
   408         -				'sorcery_smelter_top_' .. tostring(kind.size) .. '.png';
   409         -				'sorcery_smelter_bottom.png';
   410         -				'sorcery_smelter_side.png';
   411         -				'sorcery_smelter_side.png';
   412         -				'sorcery_smelter_side.png';
   413         -				'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png';
   414         -			};
   415         -		})
   416         -	end
   417         -	minetest.register_craft {
   418         -		recipe = recipe;
   419         -		output = id;
   420         -	}
   421         -end
   422         -
   423         -for _, t in pairs {
   424         -	{1, nil, 'clay'};
   425         -	-- {2, 'hot','aluminum'};
   426         -	-- {3, 'volcanic','platinum'};
   427         -	-- {4, 'stellar','duridium'};
   428         -	-- {5, 'nova','impervium'};
   429         -} do register_kiln {
   430         -		temp = t[1], temp_name = t[2];
   431         -		material = t[3];
   432         -		size = 3, outsize = 4, fuelsize = 1; -- future-proofing
   433         -	}
   434         -
   435         -	for _, s in pairs {
   436         -		{2, 'small'};
   437         -		{3, 'large'};
   438         -		{4, 'grand'};
   439         -	} do register_smelter {
   440         -		size = s[1], size_name = s[2];
   441         -		temp = t[1], temp_name = t[2];
   442         -		material = t[3];
   443         -		crucible = 'sorcery:crucible_' .. t[3];
   444         -		outsize = 4, fuelsize = 1; -- future-proofing
   445         -	} end
   446         -end

Modified init.lua from [98e23bb4d1] to [b6f042d41b].

    99     99   root {'compat','matreg'}
   100    100   if not sorcery.stage('loadlore', data, root) then
   101    101   	data {
   102    102   		'compat';
   103    103   		'affinities'; 'gods';
   104    104   		'calendar', 'signs';
   105    105   		'resonance';
   106         -		'enchants', 'spells', 'runes';
   107    106   		'gems', 'metals';
          107  +		'enchants', 'spells', 'runes';
   108    108   		'potions', 'oils', 'greases',
   109    109   			'draughts', 'elixirs',
   110    110   			'philters', 'extracts';
   111    111   	}
   112    112   end
   113    113   
   114    114   sorcery.load('registration') do

Modified leylines.lua from [a8ee2829a5] to [3f1f974e2b].

   376    376   		name = 'Condenser sound effects';
   377    377   		nodenames = {'sorcery:condenser'};
   378    378   		neighbors = {'group:sorcery_ley_device'};
   379    379   		interval = 5.6, chance = 1, catch_up = false;
   380    380   		action = function(pos)
   381    381   			local force = sorcery.ley.estimate(pos).force
   382    382   			minetest.sound_play('sorcery_condenser_bg', {
   383         -				pos = pos, max_hear_distance = 5 + 8*force, gain = force*0.3;
          383  +				pos = pos, max_hear_distance = 5 + 4*force, gain = force*0.3;
   384    384   			})
   385    385   		end;
   386    386   	}
   387    387   end
   388    388   
   389    389   minetest.register_craft {
   390    390   	output = 'sorcery:condenser';

Modified lib/color.lua from [818d3ded97] to [9a1b399926].

   160    160   				-- print("r"..self.red.."g"..self.green.."b"..self.blue.." is h"..hue.."s"..saturation.."l"..luminosity)
   161    161   				local temp = from_hsl({hue=hue,saturation=saturation,luminosity=luminosity},self.alpha)
   162    162   				-- print("back is r"..temp.red.."g"..temp.green.."b"..temp.blue)
   163    163   				return { hue = hue, saturation = saturation, luminosity = luminosity }
   164    164   			end;
   165    165   
   166    166   			readable = function(self, target)
   167         -				target = target or 0.5
          167  +				target = target or 0.6
   168    168   				local hsl = self:to_hsl()
   169    169   				hsl.luminosity = target
          170  +				local worstHue = 230
          171  +				local nearness = math.abs(worstHue - hsl.hue)
          172  +				if nearness <= 70 then
          173  +					local boost = 1.0 - (nearness / 70)
          174  +					hsl.luminosity = math.min(1, hsl.luminosity * (1 + (boost*0.4)))
          175  +				end
          176  +
   170    177   				return from_hsl(hsl, self.alpha)
   171    178   			end;
   172    179   
   173    180   			bg = function(self, text) return
   174    181   				text .. minetest.get_background_escape_sequence(self:hex())
   175    182   			end;
   176    183   
................................................................................
   181    188   			brighten = function(self, fac)
   182    189   				-- Use HSL to brighten
   183    190   				-- To HSL
   184    191   				local hsl = self:to_hsl()
   185    192   				-- Do the calculation, clamp to 0-1 instead of the clamp fn
   186    193   				hsl.luminosity = math.min(math.max(hsl.luminosity * fac, 0), 1)
   187    194   				-- Turn back into RGB color
   188         -				local t = from_hsl(hsl, self.alpha)
          195  +				-- local t = from_hsl(hsl, self.alpha)
          196  +				-- print("darker is r"..hsl.red.."g"..hsl.green.."b"..hsl.blue)
   189    197   				-- print("brighten is r"..t.red.."g"..t.green.."b"..t.blue)
   190    198   				return from_hsl(hsl, self.alpha)
   191    199   			end;
   192    200   
   193    201   			darken = warp(function(new, fac)
   194    202   				-- TODO: is there any point to this being different than brighten? Probably especially not now.
   195    203   				new.red = clip(new.red - (new.red * fac))

Added lib/hud.lua version [a485497e38].

            1  +-- the HUD library provides an easy-to-use interface for create custom
            2  +-- item UIs. the idea is that an item <id> will define one or more UI layouts
            3  +-- in its _sorcery.hud property, then call lib.hud.open(id, layout, player, ...)
            4  +-- when it wishes to open <layout>, usually from its on_use handler. the library
            5  +-- will then iterate through the elements in its definition, transmit them to
            6  +-- the user, and store the resulting handles in a user context. it will also
            7  +-- register any timer callbacks needed to ensure a timely update of the item.
            8  +-- if the item is removed from the hotbar, any UI associated with it will be
            9  +-- closed.
           10  +--
           11  +-- each UI element can have a function that is called to set its value. if
           12  +-- this is present, it will be called at initialization and at update.
           13  +--
           14  +-- example:
           15  +--	_sorcery = {
           16  +--		hud = {
           17  +--			powerlevel = {
           18  +--				period = 0.1;
           19  +--				elements = {
           20  +--					{kind = 'text',
           21  +--						text = function(ctx)
           22  +--							return ctx.stack:get_meta():get_int("power")
           23  +--						end;
           24  +--					};
           25  +--				};
           26  +--			};
           27  +--		};
           28  +--	};

Modified lib/tbl.lua from [6f943d189b] to [d4f49ab351].

    51     51   			new[k] = fn.deepcopy(v)
    52     52   		else
    53     53   			new[k] = v
    54     54   		end
    55     55   	end
    56     56   	return new
    57     57   end
           58  +
           59  +fn.append = function(r1, r2)
           60  +	local new = fn.copy(r1)
           61  +	for i=1,#r2 do
           62  +		new[#new + 1] = r2[i]
           63  +	end
           64  +	return new
           65  +end
    58     66   
    59     67   fn.merge = function(base,override)
    60     68   	local new = fn.copy(base)
    61     69   	for k,v in pairs(override) do
    62     70   		new[k] = v
    63     71   	end
    64     72   	return new
    65     73   end
    66     74   
    67     75   fn.deepmerge = function(base,override,func)
    68     76   	local new = {}
    69         -	local keys = fn.merge(fn.keys(base),fn.keys(override))
           77  +	local keys = fn.append(fn.keys(base),fn.keys(override))
    70     78   	for _,k in pairs(keys) do
    71     79   		if type(base[k]) == 'table' and
    72     80   		   type(override[k]) == 'table' then
    73     81   			new[k] = fn.deepmerge(base[k], override[k], func)
    74     82   		elseif func and override[k] and base[k] then
    75     83   			new[k] = func(base[k],override[k], k)
    76     84   		elseif override[k] then
    77     85   			new[k] = override[k]
    78     86   		else
    79     87   			new[k] = base[k]
    80     88   		end
    81     89   	end
    82         -	return new
    83         -end
    84         -
    85         -fn.append = function(r1, r2)
    86         -	local new = fn.copy(r1)
    87         -	for i=1,#r2 do
    88         -		new[#new + 1] = r2[i]
    89         -	end
    90     90   	return new
    91     91   end
    92     92   
    93     93   fn.has = function(tbl,value,eqfn)
    94     94   	for k,v in pairs(tbl) do
    95     95   		if eqfn then
    96     96   			if eqfn(v,value,tbl) then return true, k end

Modified lib/ui.lua from [a001c55363] to [787cef01b1].

     1      1   local l = sorcery.lib
     2      2   local dui = sorcery.data.ui
     3      3   
     4      4   return {
     5      5   	tooltip = function(a)
     6         -		local color = a.color
            6  +		local color = a.color and a.color:readable()
     7      7   		if color == nil then color = l.color(136,158,177) end
     8      8   		local str = a.title
     9      9   		if a.desc then
    10     10   			str = str .. '\n' .. color:fmt(minetest.wrap_text(a.desc,60))
    11     11   		end
    12     12   		if a.props then
    13     13   			-- str = str .. '\n'
    14     14   			for _,prop in pairs(a.props) do
    15     15   				local c
    16     16   				if prop.color and l.color.id(prop.color) then
    17         -					c = prop.color
           17  +					c = prop.color:readable()
    18     18   				elseif dui.colors[prop.affinity] then
    19     19   					c = l.color(dui.colors[prop.affinity])
    20     20   				else
    21     21   					c = l.color(dui.colors.neutral)
    22     22   				end
    23     23   
    24     24   				str = str .. '\n ' .. c:fmt('* ')
    25     25   
    26     26   				if prop.title then
    27     27   					str = str .. c:brighten(1.3):fmt(prop.title) .. ': '
    28     28   				end
    29     29   
    30         -				local lines = minetest.wrap_text(prop.desc, 50, true)
           30  +				local lines = minetest.wrap_text(prop.desc, 55, true)
    31     31   				str = str .. c:fmt(lines[1])
    32     32   				for i=2,#lines do
    33     33   					str = str .. '\n' .. string.rep(' ',5) .. c:fmt(lines[i])
    34     34   				end
    35     35   			end
    36     36   		end
    37         -		return str
           37  +		return color:darken(0.8):bg(str)
    38     38   	end;
    39     39   }

Modified metallurgy-cold.lua from [0187673a26] to [3417cf14d8].

   129    129   	mp.torque     = constants.grind_torque_factor * mp.hardness
   130    130   	mp.grindvalue = mp.grindvalue or constants.default_grindvalue
   131    131   	mp.grindcost  = mp.grindcost  or constants.default_grindcost
   132    132   
   133    133   	if item:get_wear() ~= 0 then
   134    134   		-- prevent cheating by recovering metal from items before they
   135    135   		-- are destroyed
   136         -		local wearfac = (item:get_wear() / 65535)
          136  +		local wearfac = 1-(item:get_wear() / 65535)
   137    137   		mp.grindvalue = math.max(1,math.ceil(mp.grindvalue * wearfac))
   138    138   		mp.hardness = math.max(1,math.ceil(mp.grindcost * wearfac))
   139    139   		mp.torque = math.max(1,math.ceil(mp.torque * wearfac))
   140    140   	end
   141    141   
   142    142   	return mp
   143    143   end

Modified portal.lua from [12b64fac56] to [149a85d413].

   358    358   							time = 2;
   359    359   							amount = 500 * fac;
   360    360   							minpos = { x = -0.3, y =    0, z = -0.3 };
   361    361   							maxpos = { x =  0.3, y =  1.5, z =  0.3 };
   362    362   							minvel = { x = -0.3, y =  0.4, z = -0.3 };
   363    363   							maxvel = { x =  0.3, y =  0.6, z =  0.3 };
   364    364   							maxacc = { x =    0, y =  0.5, z =    0 };
   365         -							texture = sorcery.lib.image('sorcery_spark.png'):multiply(sorcery.lib.color(255,144,226)):render();
          365  +							texture = sorcery.lib.image('sorcery_sputter.png'):glow(sorcery.lib.color(255,144,226)):render();
   366    366   							minexptime = 1.5;
   367    367   							maxexptime = 2;
   368    368   							minsize = 0.4;
   369    369   							maxsize = 1.6 * fac;
   370    370   							glow = 14;
   371    371   							attached = u.object;
   372    372   							animation = {

Modified potions.lua from [f87e1ae861] to [2e6379fa76].

     8      8   sorcery.register_potion = function(name,label,desc,color,imgvariant,glow,extra)
     9      9   	local image = 'sorcery_liquid_'..(imgvariant or 'dull')..'.png' .. 
    10     10   		'^[multiply:'..tostring(color)..
    11     11   		'^vessels_glass_bottle.png'
    12     12   
    13     13   	sorcery.register.residue.link('sorcery:' .. name, 'vessels:glass_bottle')
    14     14   	local node = {
    15         -		description = color:darken(0.8):bg(
           15  +		description = --color:darken(0.8):bg(
    16     16   			sorcery.lib.ui.tooltip {
    17     17   				title = label;
    18     18   				desc = desc;
    19     19   				color = color:readable();
    20         -			}
           20  +			};
    21     21   			-- label .. (desc and ("\n" .. color:readable():fmt(desc)) or '')
    22         -		);
           22  +		--);
    23     23   		short_description = label;
    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;

Modified runeforge.lua from [4e5c236dfa] to [6c2e235473].

     1      1   -- TODO make some kind of disposable "filter" tool that runeforges require 
     2      2   -- to generate runes and that wears down over time, to make amulets more
     3      3   -- expensive than they currently are? the existing system is neat but
     4      4   -- i think amulets are a little overpowered for something that just
     5      5   -- passively consumes ley-current
            6  +--  -- are phials & rune-wrenches enough for this now?
     6      7   
     7      8   local constants = {
     8      9   	rune_mine_interval = 240;
     9     10   	-- how often a powered forge rolls for new runes
    10     11   
    11     12   	rune_cache_max = 4;
    12     13   	-- how many runes a runeforge can hold at a time
................................................................................
    44     45   	local fac = (g-1) / 5
    45     46   	return i - ((i*0.5) * fac), 0.5 * fac
    46     47   end
    47     48   sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
    48     49   	local id = 'sorcery:rune_' .. name
    49     50   	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
    50     51   	rune.item = id
           52  +	local c = sorcery.lib.color(rune.tone)
    51     53   	minetest.register_craftitem(id, {
    52         -		description = sorcery.lib.color(rune.tone):readable():fmt(rune.name .. ' Rune');
           54  +		description = c:darken(0.7):bg(c:readable():fmt(rune.name .. ' Rune'));
    53     55   		short_description = rune.name .. ' Rune';
    54     56   		inventory_image = rune.image;
    55     57   		stack_max = 1;
    56     58   		groups = {
    57     59   			sorcery_rune = 1;
    58     60   			not_in_creative_inventory = 1;
    59     61   		};
................................................................................
   145    147   		local rp = rune:get_definition()._proto
   146    148   		local rg = rune:get_meta():get_int('rune_grade')
   147    149   		m:set_string('amulet_rune', rp.id)
   148    150   		m:set_int('amulet_rune_grade', rg)
   149    151   		local spell = sorcery.amulet.getspell(stack)
   150    152   		if not spell then return nil end
   151    153   		local name
   152         -		if spell.minrune then -- indicating quality makes less sense if it's restricted
          154  +		if spell.mingrade and spell.mingrade > 0 then -- indicating quality makes less sense if it's restricted
   153    155   			name = string.format('Amulet of %s', spell.name)
   154    156   		else
   155    157   			name = string.format('Amulet of %s %s', constants.amulet_grades[rg], spell.name)
   156    158   		end
   157    159   		m:set_string('description', sorcery.lib.ui.tooltip {
   158    160   			title = name;
   159    161   			color = spell.tone;
................................................................................
   403    405   		local i = m:get_inventory()
   404    406   		i:set_size('cache',constants.rune_cache_max)
   405    407   		i:set_size('wrench',1) i:set_size('phial',1) i:set_size('refuse',1)
   406    408   		i:set_size('amulet',1) i:set_size('active',1)
   407    409   		m:set_string('infotext','Rune Forge')
   408    410   		runeforge_update(pos)
   409    411   	end;
   410         -	after_dig_node = sorcery.lib.node.purge_only {'amulet'};
          412  +	after_dig_node = sorcery.lib.node.purge_only {'amulet','wrench'};
   411    413   	on_timer = runeforge_update;
   412    414   	on_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)
   413    415   		local inv = minetest.get_meta(pos):get_inventory()
   414    416   		local wrench if not inv:is_empty('wrench') then
   415    417   			wrench = inv:get_stack('wrench',1):get_definition()._proto
   416    418   		end
   417    419   		local wwear = function(cap)

Modified vfx.lua from [423ee7c3cf] to [d0158e43bb].

    10     10   		and function(x) return vector.add(pos,x) end
    11     11   		or  function(x) return x end
    12     12   	local height = caster:get_properties().eye_height
    13     13   	minetest.add_particlespawner {
    14     14   		amount = 70 * strength;
    15     15   		time = duration or 1.5;
    16     16   		attached = caster;
    17         -		texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render();
           17  +		-- texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render();
           18  +		texture = sorcery.vfx.glowspark(color):render();
    18     19   		minpos = ofs({ x =  0.0, z =  0.6, y =  height*0.7});
    19     20   		maxpos = ofs({ x =  0.4, z =  0.2, y =  height*1.1});
    20     21   		minvel = { x = -0.5, z = -0.5, y = -0.5};
    21     22   		maxvel = { x =  0.5, z =  0.5, y =  0.5};
    22     23   		minacc = { x =  0.0, z =  0.0, y =  0.5};
    23     24   		maxacc = { x =  0.0, z =  0.0, y =  0.5};
    24     25   		minsize = 0.4, maxsize = 0.8;
................................................................................
    63     64   end
    64     65   
    65     66   sorcery.vfx.enchantment_sparkle = function(tgt,color)
    66     67   	local minvel, maxvel
    67     68   	if minetest.get_node(vector.add(tgt.under,{y=1,z=0,x=0})).name == 'air' then
    68     69   		minvel = {x=0,z=0,y= 0.3}  maxvel = {x=0,z=0,y= 1.5};
    69     70   	else
    70         -		local dir = vector.subtract(tgt.under,tgt.above)
           71  +		local dir = vector.subtract(tgt.above,tgt.under)
    71     72   		minvel = vector.multiply(dir, 0.3)
    72     73   		maxvel = vector.multiply(dir, 1.2)
    73     74   	end
    74     75   	return minetest.add_particlespawner {
    75     76   		amount = 50;
    76     77   		time = 0.5;
    77     78   		minpos = vector.subtract(tgt.under, 0.5);

Modified wands.lua from [e661ef77a3] to [3776b1d392].

   159    159   			end
   160    160   			return proto
   161    161   		end;
   162    162   		matprops = function(proto)
   163    163   			local matprops = {}
   164    164   			for k,v in pairs(proto) do
   165    165   				if sorcery.wands.materials[k] then
   166         -					local mp = sorcery.wands.materials[k].wandprops
          166  +					local mp = sorcery.wands.materials[k][v].wandprops
   167    167   					if mp then
   168    168   						matprops = sorcery.lib.tbl.deepmerge(matprops, mp,
   169         -							function(a,b,k)
          169  +							function(a,b,key)
   170    170   								if key == 'bond'
   171    171   									then return a+b
   172    172   									else return a*b
   173    173   								end
   174    174   							end)
   175    175   					end
   176    176   				end
................................................................................
   272    272   			angle = user:get_look_horizontal();
   273    273   			eyeheight = uprops.eye_height;
   274    274   		};
   275    275   		wearmult = 1;
   276    276   	}
   277    277   	local result = castfn(context)
   278    278   	if result ~= false then
   279         -		minetest.sound_play(sorcery.data.spells[spell].sound or "default_item_smoke", { --FIXME make own sounds
          279  +		minetest.sound_play(sorcery.data.spells[spell].sound or "sorcery_chime", { --FIXME make better sound
   280    280   			pos = user:get_pos();
   281    281   			gain = 0.8;
   282    282   		})
   283    283   		-- minetest.add_particle {
   284    284   		-- 	pos = vector.add(vector.add(user:get_pos(), vector.multiply(user:get_look_dir(),1.1)), {y=1.6,z=0,x=0});
   285    285   		-- 	velocity = user:get_velocity();
   286    286   		-- 	expirationtime = 0.5;
................................................................................
   378    378   		sunlight_propagates = true;
   379    379   		paramtype = 'light';
   380    380   		paramtype2 = 'facedir';
   381    381   		tiles = images;
   382    382   		selection_box = hitbox;
   383    383   		collision_box = hitbox;
   384    384   		after_dig_node = sorcery.lib.node.purge_container;
   385         -		use_texture_alpha = true;
          385  +		use_texture_alpha = 'blend';
   386    386   		on_construct = function(pos)
   387    387   			local meta = minetest.get_meta(pos)
   388    388   			local inv = meta:get_inventory()
   389    389   			inv:set_size('wand', 1)
   390    390   			update_stand_info(pos)
   391    391   		end;
   392    392   		_proto = {