sorcery  Check-in [3354e2aa29]

Overview
Comment:add support for instantiation callbacks, god tweaks and bug fixes
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 3354e2aa299c7c61b777ae0c5bc0303381c95fa066568ffd1b5dac51e8659dc4
User & Date: lexi on 2021-07-05 20:43:40
Other Links: manifest | tags
Context
2021-07-05
23:59
add more recipes and god gifts check-in: 049207a61e user: lexi tags: trunk
20:43
add support for instantiation callbacks, god tweaks and bug fixes check-in: 3354e2aa29 user: lexi tags: trunk
19:17
remove debugging shim oops check-in: d08d3d2bd8 user: lexi tags: trunk
Changes

Modified altar.lua from [e0cdcfa7d5] to [dfbf2cd360].

    60     60   			-- can't mutate table while we're iterating it
    61     61   			if not minetest.registered_nodes[g] then bad[#bad+1] = g end
    62     62   		end
    63     63   		for _, g in ipairs(bad) do god.gifts[g] = nil end
    64     64   	end
    65     65   end)
    66     66   
    67         -local std_god_interval = 70
           67  +local std_god_interval = 40
    68     68   
    69     69   for name, god in pairs(sorcery.data.gods) do
    70     70   	local hitbox = {
    71     71   		0-(god.idol.width / 2.0), 0-(god.idol.height / 2.0), -0.15,
    72     72   		   god.idol.width / 2.0,     god.idol.height / 2.0,   0.15
    73     73   	} -- {xmin, ymin, zmin,
    74     74   	  -- xmax, ymax, zmax} in nodes from node center.
................................................................................
    76     76   	if god.idol.craft then
    77     77   		minetest.register_craft {
    78     78   			output = 'sorcery:idol_' .. name;
    79     79   			recipe = god.idol.craft;
    80     80   		};
    81     81   	end
    82     82   	local god_interval = std_god_interval * (god.freq or 1)
    83         -	sorcery.lib.node.reg_autopreserve('sorcery:idol_' .. name, {
    84         -		description = god.idol.desc;
    85         -		drawtype = "mesh";
    86         -		mesh = 'sorcery-idol-' .. name .. '.obj';
    87         -		paramtype = 'light';
    88         -		paramtype2 = 'facedir';
    89         -		sunlight_propagates = true;
    90         -		stack_max = 1;
    91         -		tiles = god.idol.tex;
    92         -		selection_box = { type = "fixed"; fixed = {hitbox}; };
    93         -		collision_box = { type = "fixed"; fixed = {hitbox}; };
    94         -		groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1};
    95         -
    96         -		on_construct = function(pos)
    97         -			minetest.get_node_timer(pos):start(god_interval)
    98         -		end;
    99         -
   100         -		on_timer = function(pos, elapsed)
   101         -			local altar = minetest.find_node_near(pos, 3, "sorcery:altar")
   102         -			-- TODO even without an altar, an idol with high favor could still be the source of miracles
   103         -			-- refills nearby partly empty troughs at cost to favor?
   104         -			if not altar then return true end
   105         -
   106         -			local altarmeta = minetest.get_meta(altar)
   107         -			local inv = altarmeta:get_inventory()
   108         -			local idolmeta = minetest.get_meta(pos)
   109         -			local divine_favor = idolmeta:get_int('favor')
           83  +	local add_sparkles = function(pos,divine_favor)
           84  +		divine_favor = divine_favor or minetest.get_meta(pos):get_int('favor')
   110     85   			if divine_favor > 5 then
   111     86   				local ct = divine_favor / 5
   112     87   				minetest.add_particlespawner {
   113     88   					texture = L.image('sorcery_glitter.png'):glow(L.color(god.color)):render();
   114     89   					glow = 14;
   115     90   					amount = ct / (1 / god_interval), time = god_interval;
   116     91   					minpos = pos:offset(-0.4, -0.5, -0.4);
................................................................................
   124     99   					animation = {
   125    100   						type = 'vertical_frames';
   126    101   						length = 0.1;
   127    102   						aspect_w = 16, aspect_h = 16;
   128    103   					}
   129    104   				}
   130    105   			end
          106  +	end
          107  +	sorcery.lib.node.reg_autopreserve('sorcery:idol_' .. name, {
          108  +		description = god.idol.desc;
          109  +		drawtype = "mesh";
          110  +		mesh = 'sorcery-idol-' .. name .. '.obj';
          111  +		paramtype = 'light';
          112  +		paramtype2 = 'facedir';
          113  +		sunlight_propagates = true;
          114  +		stack_max = 1;
          115  +		tiles = god.idol.tex;
          116  +		selection_box = { type = "fixed"; fixed = {hitbox}; };
          117  +		collision_box = { type = "fixed"; fixed = {hitbox}; };
          118  +		groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1, sorcery_instantiate = 1};
          119  +		_sorcery = {
          120  +			idol_god = name;
          121  +			on_load = function(pos) add_sparkles(pos) end;
          122  +		};
          123  +
          124  +		on_construct = function(pos)
          125  +			minetest.get_node_timer(pos):start(god_interval)
          126  +			add_sparkles(pos)
          127  +		end;
          128  +
          129  +		on_timer = function(pos, elapsed)
          130  +			local altar = minetest.find_node_near(pos, 3, "sorcery:altar")
          131  +			-- TODO even without an altar, an idol with high favor could still be the source of miracles
          132  +			-- refills nearby partly empty troughs at cost to favor?
          133  +			if not altar then return true end
          134  +
          135  +			local altarmeta = minetest.get_meta(altar)
          136  +			local inv = altarmeta:get_inventory()
          137  +			local idolmeta = minetest.get_meta(pos)
          138  +			local divine_favor = idolmeta:get_int('favor')
          139  +			add_sparkles(pos,divine_favor)
   131    140   			local bestow = function(item,color)
   132    141   				if type(item) == 'string' then
   133    142   					item = ItemStack(item)
   134    143   				end
   135    144   				if color == nil then
   136    145   					color = sorcery.lib.color(god.color)
   137    146   				end
................................................................................
   193    202   					-- we pick a random gift and roll against its rarity
   194    203   					-- to determine if the god is feeling generous
   195    204   					local gift = sorcery.lib.tbl.pick(god.gifts)
   196    205   					local data = god.gifts[gift]
   197    206   					local value, rarity = data[1], data[2]
   198    207   					if value <= divine_favor and math.random(rarity) == 1 then
   199    208   						bestow(gift)
   200         -						log.act(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift')
          209  +						log.act(god.name,'has produced',gift,'upon an altar as a gift')
   201    210   						if math.random(god.generosity) == 1 then
   202    211   							-- unappreciated gifts may incur divine
   203    212   							-- irritation
   204    213   							divine_favor = divine_favor - 1
   205    214   						end
   206    215   					end
   207    216   				end
................................................................................
   231    240   								-- the gods are getting bored
   232    241   								value = math.floor(value / 2)
   233    242   							end
   234    243   							bestow(nil)
   235    244   						end
   236    245   						divine_favor = divine_favor + value
   237    246   						
   238         -						print(god.name.." has accepted a sacrifice of "..s..", raising divine favor by "..value.." points to "..divine_favor)
          247  +						log.actf("%s has accepted a sacrifice of %s, raising divine favor by %u points to %u at altar %s", god.name, s, value, divine_favor, minetest.pos_to_string(pos))
          248  +
   239    249   						idolmeta:set_string('last_sacrifice', s)
   240    250   
   241    251   						goto refresh
   242    252   					end
   243    253   				end
   244    254   
   245    255   				-- loop through the list of things this god will consecrate and

Modified data/gods.lua from [6a04c7ee36] to [39659f8d92].

    30     30   				{'default:obsidian_shard','stairs:slab_goldblock','default:bronze_ingot'};
    31     31   			};
    32     32   		};
    33     33   		bless = {
    34     34   			potions = {};
    35     35   			tools = {};
    36     36   		};
    37         -		gifts = {};
           37  +		gifts = {
           38  +			['sorcery:screw_steel'] = {7,1};
           39  +			['sorcery:pipe'] = {16,2};
           40  +			['sorcery:valve'] = {18,3};
           41  +			['sorcery:fragment_vidrium'] = {20,4};
           42  +			['sorcery:fragment_lithium'] = {23,5};
           43  +			['sorcery:screw_platinum'] = {31,5};
           44  +			['sorcery:screw_tungsten'] = {33,5};
           45  +			['sorcery:powder_firestorm'] = {48,7};
           46  +		};
    38     47   		consecrate = {
    39     48   			["sorcery:dagger"] = {17, "sorcery:dagger_consecrated"};
    40     49   			["sorcery:oil_mystic"] = {9, "sorcery:oil_purifying"};
    41     50   			["sorcery:potion_water"] = {4, "sorcery:holy_water"};
    42     51   			["default:paper"] = function(ctx)
    43     52   				local stack = ItemStack('sorcery:recipe')
    44         -				local mode = select(2,L.tbl.pick{'cook','craft','infuse','grind','enchant'})
           53  +				local mode = select(2,L.tbl.pick{'cook','craft','grind','enchant'})
    45     54   				sorcery.cookbook.setrecipe(stack, mode, nil, {
    46     55   					pred = function(c)
    47     56   						local me = ctx.god
           57  +						local g = function(n)
           58  +							return minetest.get_item_group(c.item, n) ~= 0
           59  +						end
    48     60   						if (mode == 'enchant' or
    49         -							minetest.get_item_group(c.item, 'sorcery_magical') ~= 0 or
    50         -							minetest.get_item_group(c.item, 'sorcery_magitech') ~= 0 or
    51         -							minetest.get_item_group(c.item, 'sorcery_ley_device') ~= 0 or
    52         -							minetest.get_item_group(c.item, 'sorcery_tech') ~= 0 or
    53         -							minetest.get_item_group(c.item, 'crafttool') ~= 0 or
    54         -							me.sacrifice [c.item] or
    55         -							me.consecrate[c.item]) and
    56         -							mod ~= 'farming'
           61  +							(ctx.favor > 35 and (g 'sorcery_magical'
           62  +											or g 'sorcery_magitech'
           63  +											or g 'sorcery_ley_device'))
           64  +							or g 'sorcery_tech'
           65  +							or g 'crafttool'
           66  +							or me.sacrifice [c.item]
           67  +							or me.consecrate[c.item])
           68  +							and mod ~= 'farming'
    57     69   								then return true end
    58     70   					end;
    59     71   				})
    60         -				return 6, stack
           72  +				return 3, stack
    61     73   			end;
    62     74   			-- ["default:gold_ingot"] = {15, "sorcery:holy_token_magic"};
    63     75   		};
    64     76   		sacrifice = {
    65         -			['sorcery:essence_frost'] = 25;
    66         -			['sorcery:essence_flame'] = 25;
    67         -			['sorcery:essence_force'] = 30;
           77  +			['sorcery:essence_frost'] = 15;
           78  +			['sorcery:essence_flame'] = 15;
           79  +			['sorcery:essence_force'] = 20;
    68     80   
    69         -			['sorcery:gem_luxite'] = 6;
    70         -			['sorcery:gem_ruby'] = 10;
    71         -			['sorcery:gem_amethyst'] = 16;
    72         -			['sorcery:gem_sapphire'] = 25;
    73         -			['sorcery:gem_emerald'] = 34;
    74         -			['default:mese_crystal'] = 42;
    75         -			['default:diamond'] = 50;
           81  +			['sorcery:gem_luxite'] = 4;
           82  +			['sorcery:gem_ruby'] = 7;
           83  +			['sorcery:gem_amethyst'] = 9;
           84  +			['sorcery:gem_sapphire'] = 12;
           85  +			['sorcery:gem_emerald'] = 14;
           86  +			['default:mese_crystal'] = 18;
           87  +			['default:diamond'] = 25;
    76     88   
    77         -			['sorcery:gem_luxite_amulet'] = 20;
    78         -			['sorcery:gem_ruby_amulet'] = 35;
    79         -			['sorcery:gem_amethyst_amulet'] = 48;
    80         -			['sorcery:gem_sapphire_amulet'] = 56;
    81         -			['sorcery:gem_emerald_amulet'] = 63;
    82         -			['sorcery:gem_mese_amulet'] = 78;
    83         -			['sorcery:gem_diamond_amulet'] = 91;
           89  +			['sorcery:gem_luxite_amulet'] = 8;
           90  +			['sorcery:gem_ruby_amulet'] = 14;
           91  +			['sorcery:gem_amethyst_amulet'] = 18;
           92  +			['sorcery:gem_sapphire_amulet'] = 23;
           93  +			['sorcery:gem_emerald_amulet'] = 14;
           94  +			['sorcery:gem_mese_amulet'] = 36;
           95  +			['sorcery:gem_diamond_amulet'] = 50;
    84     96   
    85     97   			['sorcery:oil_mystic'] = 2;
    86         -			['sorcery:oil_berry'] = 4;
    87         -			['sorcery:oil_wind'] = 6;
    88         -			['sorcery:oil_bleak'] = 6;
    89         -			['sorcery:oil_stone'] = 7;
    90         -			['sorcery:oil_mushroom'] = 8;
    91         -			['sorcery:oil_flame'] = 8;
    92         -			['sorcery:oil_dawn'] = 11;
    93         -			['sorcery:oil_luscious'] = 12;
    94         -			['sorcery:oil_luck'] = 16;
           98  +			['sorcery:oil_berry'] = 3;
           99  +			['sorcery:oil_wind'] = 4;
          100  +			['sorcery:oil_bleak'] = 4;
          101  +			['sorcery:oil_stone'] = 5;
          102  +			['sorcery:oil_mushroom'] = 6;
          103  +			['sorcery:oil_flame'] = 7;
          104  +			['sorcery:oil_dawn'] = 8;
          105  +			['sorcery:oil_luscious'] = 9;
          106  +			['sorcery:oil_luck'] = 11;
    95    107   			['sorcery:oil_sagnuine'] = -15;
    96    108   
    97         -			['sorcery:grease_fog'] = 17;
    98         -			['sorcery:grease_pine'] = 18;
    99         -			['sorcery:grease_storm'] = 20;
   100         -			['sorcery:grease_whisper'] = 21;
   101         -			['sorcery:grease_thunder'] = 22;
   102         -			['sorcery:grease_enchanting'] = 24;
   103         -			['sorcery:grease_lift'] = 32;
          109  +			['sorcery:grease_fog'] = 15;
          110  +			['sorcery:grease_pine'] = 16;
          111  +			['sorcery:grease_storm'] = 18;
          112  +			['sorcery:grease_whisper'] = 19;
          113  +			['sorcery:grease_thunder'] = 20;
          114  +			['sorcery:grease_enchanting'] = 22;
          115  +			['sorcery:grease_lift'] = 28;
   104    116   			['sorcery:grease_war'] = -5;
   105    117   
   106         -			['sorcery:warding_plate'] = 6;
          118  +			['sorcery:warding_plate'] = 5;
   107    119   			['sorcery:ley_puncture'] = 8;
   108    120   			['sorcery:pulse_rectifier'] = 8;
   109    121   			['sorcery:current_felicitator'] = 12;
   110         -			['sorcery:infuser_concentrator'] = 15;
   111         -			['sorcery:infuser_tube'] = 23;
   112         -			['sorcery:inverter_coil'] = 31;
   113         -			['sorcery:inversion_matrix'] = 70;
   114         -			['sorcery:inferno_crystal'] = 75;
   115         -			['sorcery:beam_generator'] = 83;
   116         -			['sorcery:field_emitter'] = 92;
   117         -			['sorcery:catalytic_converter'] = 95;
   118         -			['sorcery:gravity_manipulator'] = 97;
          122  +			['sorcery:infuser_concentrator'] = 7;
          123  +			['sorcery:infuser_tube'] = 9;
          124  +			['sorcery:inverter_coil'] = 10;
          125  +			['sorcery:inversion_matrix'] = 22;
          126  +			['sorcery:inferno_crystal'] = 24;
          127  +			['sorcery:beam_generator'] = 27;
          128  +			['sorcery:field_emitter'] = 30;
          129  +			['sorcery:catalytic_converter'] = 30;
          130  +			['sorcery:gravity_manipulator'] = 50;
   119    131   
   120         -			['sorcery:core_syncretic'] = 64;
   121         -			['sorcery:core_mandatic'] = 53;
   122         -			['sorcery:core_praxic'] = 72;
   123         -			['sorcery:core_counterpraxic'] = 31;
          132  +			['sorcery:core_syncretic'] = 25;
          133  +			['sorcery:core_mandatic'] = 27;
          134  +			['sorcery:core_praxic'] = 29;
          135  +			['sorcery:core_counterpraxic'] = 17;
   124    136   
   125    137   			['sorcery:sap'] = 1;
   126    138   			['sorcery:sap_apple'] = 2;
   127    139   			['sorcery:sap_aspen'] = 3;
   128    140   			['sorcery:sap_pine'] = 3;
   129    141   			['sorcery:sap_jungle'] = 4;
   130    142   			['sorcery:sap_acacia'] = 5;

Added global.lua version [3ad50b0db8].

            1  +-- abuses the engine to provide additional generic features useful to multiple units
            2  +local log = sorcery.logger 'global'
            3  +
            4  +minetest.register_lbm {
            5  +	name = 'sorcery:activate_nodes';
            6  +	label = 'trigger instantiation-time callbacks';
            7  +	nodenames = { 'group:sorcery_instantiate' };
            8  +	run_at_every_load = true;
            9  +	action = function(pos,node)
           10  +		local s = minetest.registered_nodes[node.name]._sorcery
           11  +		if not s or not s.on_load then
           12  +			log.errf('node type "%s" marked for instantiation-time callback, but no callback specified', node.name)
           13  +			return
           14  +		end
           15  +
           16  +		s.on_load(pos,node)
           17  +	end;
           18  +}

Modified init.lua from [3b59932455] to [1fc924b85b].

   163    163   		end
   164    164   	end
   165    165   	sorcery.registry.mk('residue',false)
   166    166   end
   167    167   
   168    168   sorcery.stage('startup',data)
   169    169   for _,u in pairs {
   170         -	'vfx'; 'context'; 'attunement'; 'itemclass'; 'craft'; 'spell';
          170  +	'global'; 'vfx'; 'context'; 'attunement'; 'itemclass'; 'craft'; 'spell';
   171    171   	'liquid'; 'tree'; 'potions'; 'metal', 'gems'; 'leylines';
   172    172   	'infuser'; 'altar'; 'wands'; 'tools', 'crafttools';
   173    173   	'enchanter'; 'harvester'; 'metallurgy-hot', 'metallurgy-cold';
   174    174   	'entities'; 'recipes'; 'coins'; 'interop';
   175    175   	'tnodes'; 'forcefield'; 'farcaster'; 'portal';
   176    176   	'cookbook', 'writing'; 'disassembly'; 'displacer';
   177    177   	'gravitator'; 'precipitator'; 'calendar', 'astrolabe';

Modified liquid.lua from [02b94765c1] to [7230727dfc].

   132    132   			node_box = { type = 'fixed', fixed = mkbox(i) };
   133    133   			tiles = {
   134    134   				top:render();
   135    135   				'sorcery_trough_side.png';
   136    136   				'sorcery_trough_bottom.png';
   137    137   			};
   138    138   			_sorcery = {
          139  +				material = liq == nil and {
          140  +					metal = true;
          141  +					name = 'aluminum';
          142  +					data = sorcery.data.metals.aluminum;
          143  +					value = 7*4;
          144  +				} or nil;
   139    145   				container = {
   140    146   					type = 'bucket';
   141    147   					hold = 'liquid';
   142    148   					has = liq and liq.id;
   143    149   					charge = liq and Q * i;
   144    150   					empty = 'sorcery:trough';
   145    151   					max = constants.bottles_per_trough * Q;

Modified recipes.lua from [993e34739f] to [22986aed9f].

   300    300   	groups = {
   301    301   		sorcery_magitech = 1;
   302    302   		sorcery_tech_component = 1;
   303    303   	};
   304    304   })
   305    305   
   306    306   
   307         -local regtech = function(id, desc, groups, recipe, qty, replacements)
          307  +local regtech = function(id, desc, groups, recipe, qty, replacements, props)
   308    308   	minetest.register_craftitem('sorcery:' .. id,{
   309    309   		description = desc;
   310    310   		inventory_image = 'sorcery_'..id..'.png';
   311    311   		groups = sorcery.lib.tbl.merge({
   312    312   			sorcery_magitech = 1;
   313    313   			sorcery_tech_component = 1;
   314    314   		}, groups or {});
          315  +		_sorcery = props;
   315    316   	})
   316    317   	if recipe then
   317    318   		minetest.register_craft {
   318    319   			output = string.format('sorcery:%s %u', id, qty or 1);
   319    320   			recipe = recipe;
   320    321   			replacements = replacements;
   321    322   		}
................................................................................
   322    323   	end
   323    324   end
   324    325   
   325    326   local regcore = function(core,name)
   326    327   	regtech('core_'..core, name .. ' Core', {sorcery_magitech_core = 1})
   327    328   end
   328    329   
          330  +local mprop = function(metal, amt)
          331  +	local gc, gv
          332  +	amt = amt or 1
          333  +	if math.floor(amt) ~= amt then
          334  +		if amt < 1 then
          335  +			gc = math.floor(1 / amt)
          336  +		else
          337  +			local n = 0
          338  +			for i = 2,10 do
          339  +				if math.floor(amt * i) == amt * i then
          340  +					n = i
          341  +					break
          342  +				end
          343  +			end
          344  +			if n == 0 then error "can't determine metal value for item" end
          345  +			gc = i
          346  +			gv = amt * i
          347  +		end
          348  +	else
          349  +		gc = 1
          350  +		gv = amt
          351  +	end
          352  +	return { material = {
          353  +		metal = true; id = metal;
          354  +		data = sorcery.data.metals[metal];
          355  +		grindcost = gc;
          356  +		grindvalue = gv;
          357  +		value = amt;
          358  +	}}
          359  +end
   329    360   
   330    361   regtech('field_emitter', 'Field Emitter', {metal = 1})
   331    362   regtech('leyline_stabilizer', 'Leyline Stabilizer', {metal = 1})
   332    363   regtech('beam_generator', 'Beam Generator', {metal = 1})
   333    364   regtech('inversion_matrix', 'Inversion Matrix', {metal = 1})
   334    365   regtech('inverter_coil', 'Inverter Coil', {metal = 1})
   335    366   regtech('suppression_matrix', 'Suppression Matrix', {metal = 1})
................................................................................
   336    367   regtech('tuning_disc', 'Tuning Disc', {metal = 1})
   337    368   	-- used in constructing devices that are subject to attunement wand
   338    369   regtech('gravity_manipulator', 'Gravity Manipulator', {metal = 1})
   339    370   regtech('valve','Valve', {metal = 1}, {
   340    371   	{'','default:bronze_ingot',''};
   341    372   	{'basic_materials:plastic_sheet','basic_materials:steel_bar','basic_materials:plastic_sheet'};
   342    373   	{'','default:bronze_ingot',''};
   343         -},3)
          374  +},3,nil, mprop('bronze',2*4,1,2*4))
   344    375   regtech('pipe','Pipe', {metal = 1}, {
   345    376   	{ingot('aluminum'),'',ingot('aluminum')};
   346    377   	{ingot('aluminum'),'',ingot('aluminum')};
   347    378   	{ingot('aluminum'),'',ingot('aluminum')};
   348         -}, 6)
          379  +}, 6, nil, mprop('aluminum', 4))
   349    380   
   350    381   minetest.register_craft {
   351    382   	output = 'sorcery:trough';
   352    383   	recipe = {
   353    384   		{ingot('aluminum'),'',ingot('aluminum')};
   354    385   		{ingot('aluminum'),'',ingot('aluminum')};
   355    386   		{ingot('aluminum'),ingot('aluminum'),ingot('aluminum')};

Modified wands.lua from [bc18a463a6] to [110b3cfc3f].

   223    223   	if matprops.bond then
   224    224   		local userct, found = 0, false
   225    225   		for i=1,matprops.bond do
   226    226   			local prop = 'bound_user_' .. tostring(i)
   227    227   			if meta:contains(prop) then
   228    228   				userct = i
   229    229   				local name = meta:get_string(prop)
   230         -				print('wand bound to',name,i)
          230  +				-- print('wand bound to',name,i)
   231    231   				if name == user:get_player_name() then found = true break end
   232    232   			else break end
   233    233   		end
   234    234   		
   235    235   		if not found then
   236    236   			if userct < matprops.bond then
   237         -				print('binding wand to caster')
          237  +				-- print('binding wand to caster')
   238    238   				minetest.sound_play("xdecor_enchanting", { --FIXME make own sounds
   239    239   					pos = user:get_pos();
   240    240   					gain = 0.8;
   241    241   				})
   242    242   				sorcery.vfx.cast_sparkle(user, sorcery.lib.color(25,129,255), 2)
   243    243   				meta:set_string('bound_user_' .. tostring(userct+1), user:get_player_name())
   244    244   				return stack