sorcery  Diff

Differences From Artifact [f08588c5d9]:

To Artifact [2062a1f6be]:


     2      2   	type = 'fixed';
     3      3   	fixed = {
     4      4   		-0.5, -0.5, -0.5;
     5      5   		0.5, 0.1, 0.5;
     6      6   	};
     7      7   }
     8      8   
     9         -local enchantable_tools = {
    10         -	pickaxe = {}, pick = {};
    11         -	axe = {};
    12         -	sword = {};
    13         -	sickle = {};
    14         -	shovel = {};
    15         -	hoe = {};
    16         -};
    17         -
    18         -sorcery.enchant = {} do
    19         -	local m = sorcery.lib.marshal
    20         -	local ench_t = m.g.struct {
    21         -		id = m.t.str;
    22         -		slot = m.t.u8;
    23         -		boost = m.t.u8; -- every enchantment has an intrinsic force
    24         -		-- separate from the confluence of the slot, which is
    25         -		-- determined by the composition of the wand used to generate
    26         -		-- it (for instance, a gold-wired wand at low wear, or a wand
    27         -		-- with specific gemstones, may have a boost level above 0)
    28         -	}
    29         -	local pack, unpack = m.transcoder {
    30         -		spells = m.g.array(8, ench_t);
    31         -		energy = m.t.u16;
    32         -	}
    33         -	local key = 'sorcery_enchantment_recs'
    34         -	sorcery.enchant.set = function(stack, data)
    35         -		local meta = stack:get_meta()
    36         -		meta:set_string(key, pack(data))
    37         -	end
    38         -	sorcery.enchant.get = function(stack)
    39         -		local meta = stack:get_meta()
    40         -		if meta:contains(key) then
    41         -			local data = meta:get_string(key)
    42         -			return unpack(data)
    43         -		else
    44         -			return {
    45         -				spells = {};
    46         -				energy = 0;
    47         -			}
    48         -		end
    49         -	end
    50         -	sorcery.enchant.strength = function(stack,id)
    51         -		-- this functions should be used whenever you need to
    52         -		-- determine the power of a particular enchantment on
    53         -		-- an enchanted item.
    54         -		local e = sorcery.enchant.get(stack)
    55         -		local p = 0.0
    56         -		local slots = sorcery.matreg.lookup[stack:get_name()].data.slots
    57         -		-- TODO handle strength-boosting spells!
    58         -		for _,s in pairs(e.spells) do
    59         -			if s.id == id then p = p + slots[s.slot] end
    60         -		end
    61         -		return p
    62         -	end
    63         -	sorcery.enchant.stackup = function(stack)
    64         -		-- stack update function. this should be called whenever
    65         -		-- the enchantment status of a stack changes; it will
    66         -		-- alter/reset tool capabilities and tooltip as necessary
    67         -		local e = sorcery.enchant.get(stack)
    68         -		local meta = stack:get_meta()
    69         -		local def = stack:get_definition()
    70         -		meta:set_string('tool_capabilities','')
    71         -		local done = {}
    72         -		local props = {}
    73         -		for _,s in pairs(e.spells) do
    74         -			if done[s.id] then goto skip end
    75         -			done[s.id] = true
    76         -			local pwr = sorcery.enchant.strength(stack,s.id)
    77         -				-- somewhat wasteful…
    78         -			local name, color, desc = sorcery.data.enchants[s.id].apply(stack,pwr)
    79         -			props[#props+1] = {
    80         -				title = name;
    81         -				desc = desc;
    82         -				color = sorcery.lib.color(desc);
    83         -			}
    84         -		::skip::end
    85         -		if #e.spells > 0 then
    86         -			meta:set_string('description', sorcery.lib.ui.tooltip {
    87         -				title = 'Enchanted ' .. def.description;
    88         -				props = props;
    89         -			})
    90         -		else
    91         -			meta:set_string('description','')
    92         -		end
    93         -	end
    94         -end
    95         -
    96         -local enchanter_getsubj = function(item)
    97         -	if not item:is_empty() then
    98         -		for group, spells in pairs(enchantable_tools) do
    99         -			if minetest.get_item_group(item:get_name(), group) ~= 0 then
   100         -				return group, sorcery.matreg.lookup[item:get_name()]
   101         -			end
   102         -		end
   103         -	end
   104         -	return false
   105         -end
   106      9   local enchanter_update = function(pos)
   107     10   	local meta = minetest.get_meta(pos)
   108     11   	local inv = meta:get_inventory()
   109     12   	local item = inv:get_stack('item',1)
   110     13   	local slots = ''
   111         -	local itype, imat = enchanter_getsubj(item)
   112         -	if itype and imat and imat.data.slots then
           14  +	local imat = sorcery.enchant.getsubj(item)
           15  +	if imat and imat.data.slots then
   113     16   		local n = #imat.data.slots
   114     17   		local sw, sh = 2.1, 2.1;
   115     18   		local w = sw * n;
   116         -		local spells = sorcery.enchant.get(item).spells
           19  +		local item_enchantment = sorcery.enchant.get(item)
           20  +		local spells = item_enchantment.spells
   117     21   		for i=1,n do
   118     22   			local slot=imat.data.slots[i]
   119     23   			local x = (4 - (w/2) + (sw * (i-1))) + 0.2
   120     24   			local offtbl = {
   121     25   				[1] = {0};
   122     26   				[2] = {0.3, 0.3};
   123     27   				[3] = {0.3,   0, 0.3};
................................................................................
   139     43   					color = sorcery.lib.color(sorcery.data.affinities[aff].color);
   140     44   					desc = sorcery.data.affinities[aff].desc;
   141     45   				}
   142     46   			end
   143     47   			local hovertitle = 'Empty spell slot';
   144     48   			local conf = tostring(math.floor(slot.confluence*100)) .. '%'
   145     49   			local hoverdesc = 'An enchantment of one the following affinities can be anchored into this slot at ' .. conf .. ' confluence';
           50  +			local hovercolor = nil
   146     51   			for _,sp in pairs(spells) do
           52  +				local spdata = sorcery.data.enchants[sp.id]
   147     53   				if sp.slot == i then
           54  +					hovercolor = sorcery.lib.color(spdata.tone):readable()
   148     55   					hovertitle = sorcery.lib.str.capitalize(sp.id)
   149         -					hoverdesc = 'An enchantment is anchored in this slot at ' .. conf .. ' confluence'
           56  +					hoverdesc = sorcery.lib.str.capitalize(spdata.desc) .. '. Anchored in this slot at ' .. conf .. ' confluence'
           57  +					if sp.boost > 10 then
           58  +						hoverdesc = hoverdesc .. ' and boosted by ' .. tostring((sp.boost - 10) * 10) .. '%'
           59  +					elseif sp.boost < 10 then
           60  +						hoverdesc = hoverdesc .. ' but weakened by ' .. tostring((10 - sp.boost) * 10) .. '%'
           61  +					end
           62  +					hoverdesc = hoverdesc .. '.'
           63  +					local addrune = function(tex)
           64  +						pwr = pwr .. string.format([[
           65  +							image[%f,%f;%f,%f;%s]
           66  +						]], x+0.43,y+0.6,sw/2.7,sh/2.7, tex)
           67  +					end
           68  +					local rune = 'sorcery_enchant_' .. sp.id .. '.png'
           69  +					local energy = item_enchantment.energy
           70  +					local fullenergy = imat.data.maxenergy
           71  +					if energy <= fullenergy then
           72  +						addrune(rune .. '^[opacity:110^[lowpart:' .. tostring(math.floor((energy/fullenergy) * 100)) .. '%:' .. rune)
           73  +					elseif energy == fullenergy then addrune(rune)
           74  +					elseif energy >= fullenergy then
           75  +						addrune(rune .. '^[colorize:#ffffff:' ..
           76  +							tostring(255 * math.min(1,(energy / fullenergy * 2))))
           77  +					end
   150     78   					break
   151     79   				end
   152     80   			end
   153     81   			slots = slots .. string.format([[
   154     82   				image[%f,%f;%f,%f;sorcery_pentacle.png]
   155     83   				tooltip[%f,%f;%f,%f;%s;%s;%s]
   156     84   			]],
   157     85   				x,y, sw,sh,
   158     86   				x+0.20,y+0.16, sw-0.84,sh-0.76,
   159     87   				minetest.formspec_escape(sorcery.lib.ui.tooltip {
   160     88   					title = hovertitle;
   161     89   					desc = hoverdesc;
           90  +					color = hovercolor;
   162     91   					props = ap;
   163     92   				}),
   164     93   				'#37002C','#FFC8F5'
   165     94   			) .. pwr
   166     95   		end
   167     96   	end
   168     97   
................................................................................
   175    104   		list[context;foci;2.5,2;1,1;1]
   176    105   		list[context;foci;4.5,2;1,1;2]
   177    106   		list[current_player;main;0,4.7;8,4;]
   178    107   		listring[current_player;main]
   179    108   		listring[context;item]
   180    109   	]] .. slots)
   181    110   end
          111  +
          112  +sorcery.enchant = {} do
          113  +	sorcery.enchant.update_enchanter = enchanter_update
          114  +	local m = sorcery.lib.marshal
          115  +	local ench_t = m.g.struct {
          116  +		id = m.t.str;
          117  +		slot = m.t.u8;
          118  +		boost = m.t.u8; -- every enchantment has an intrinsic force
          119  +		-- separate from the confluence of the slot, which is
          120  +		-- determined by the composition of the wand used to generate
          121  +		-- it (for instance, a gold-wired wand at low wear, or a wand
          122  +		-- with specific gemstones, may have a boost level above 10)
          123  +		-- boost is divided by 10 to yield a float
          124  +		reliability = m.t.u8;
          125  +		-- reliability is a value between 0 and 100, where 0 means the
          126  +		-- spell fails every time and 100 means it never fails. not
          127  +		-- relevant for every spell
          128  +	}
          129  +	local pack, unpack = m.transcoder {
          130  +		spells = m.g.array(8, ench_t);
          131  +		energy = m.t.u16;
          132  +	}
          133  +	sorcery.enchant.getsubj = function(item)
          134  +		if not item:is_empty() then
          135  +			local eligible = {}
          136  +			for name, spell in pairs(sorcery.data.enchants) do
          137  +				for g,v in pairs(item:get_definition().groups) do
          138  +					if v~= 0 and sorcery.lib.tbl.has(spell.groups,g) then
          139  +						eligible[#eligible+1] = name
          140  +						goto skip
          141  +					end
          142  +				end
          143  +			::skip::end
          144  +			return sorcery.matreg.lookup[item:get_name()], eligible
          145  +		else return nil end
          146  +	end
          147  +	local key = 'sorcery_enchantment_recs'
          148  +	sorcery.enchant.set = function(stack, data, noup)
          149  +		local meta = stack:get_meta()
          150  +		meta:set_string(key, sorcery.lib.str.meta_armor(pack(data),true))
          151  +		if not noup then stack=sorcery.enchant.stackup(stack) end
          152  +	end
          153  +	sorcery.enchant.get = function(stack)
          154  +		local meta = stack:get_meta()
          155  +		if meta:contains(key) then
          156  +			local data = sorcery.lib.str.meta_dearmor(meta:get_string(key),true)
          157  +			return unpack(data)
          158  +		else
          159  +			return {
          160  +				spells = {};
          161  +				energy = 0;
          162  +			}
          163  +		end
          164  +	end
          165  +	sorcery.enchant.strength = function(stack,id)
          166  +		-- this functions should be used whenever you need to
          167  +		-- determine the power of a particular enchantment on
          168  +		-- an enchanted item.
          169  +		local e = sorcery.enchant.get(stack)
          170  +		local p = 0.0
          171  +		local slots = sorcery.matreg.lookup[stack:get_name()].data.slots
          172  +		-- TODO handle strength-boosting spells!
          173  +		for _,s in pairs(e.spells) do
          174  +			print(dump(s))
          175  +			if s.id == id then p = p + ((s.boost * slots[s.slot].confluence)/10) end
          176  +		end
          177  +		return p
          178  +	end
          179  +	sorcery.enchant.stackup = function(stack)
          180  +		-- stack update function. this should be called whenever
          181  +		-- the enchantment status of a stack changes; it will
          182  +		-- alter/reset tool capabilities and tooltip as necessary
          183  +		local e = sorcery.enchant.get(stack)
          184  +		local meta = stack:get_meta()
          185  +		local def = stack:get_definition()
          186  +		local mat = sorcery.enchant.getsubj(stack)
          187  +		local done = {}
          188  +		local props = {}
          189  +		local interference = {}
          190  +		-- meta:set_string('tool_capabilities','')
          191  +		meta:set_tool_capabilities(nil); -- TODO this probably only works
          192  +		-- in >5.3; maybe bring in the old JSON mechanism so it works in
          193  +		-- older versions as well?
          194  +		local basecaps = def.tool_capabilities
          195  +		for _,s in pairs(e.spells) do
          196  +			if done[s.id] then goto skip end
          197  +			done[s.id] = true
          198  +			local pwr = sorcery.enchant.strength(stack,s.id)
          199  +				-- somewhat wasteful…
          200  +			local e = sorcery.data.enchants[s.id]
          201  +			if e.apply then stack = e.apply(stack,pwr,basecaps) end
          202  +			props[#props+1] = {
          203  +				title = e.name;
          204  +				desc = e.desc;
          205  +				color = sorcery.lib.color(e.tone);
          206  +			}
          207  +			local inf = mat.data.slots[s.slot].interference
          208  +			if inf then for k,v in pairs(inf) do
          209  +				interference[k] = interference[k] + v
          210  +			end end
          211  +		::skip::end
          212  +		if #interference > 0 then
          213  +			if interference.speed then stack = sorcery.data.enchants.pierce.apply(stack,-interference.speed,basecaps) end
          214  +			if interference.durability then stack = sorcery.data.enchants.endure.apply(stack,-interference.durability,basecaps) end
          215  +		end
          216  +		meta = stack:get_meta() -- necessary? unclear
          217  +		if #e.spells > 0 then
          218  +			meta:set_string('description', sorcery.lib.ui.tooltip {
          219  +				title = 'Enchanted ' .. def.description;
          220  +				props = props;
          221  +			})
          222  +		else
          223  +			meta:set_string('description',def.description)
          224  +		end
          225  +		return stack
          226  +	end
          227  +end
   182    228   
   183    229   minetest.register_node('sorcery:enchanter', {
   184    230   	description = 'Enchanter';
   185    231   	drawtype = 'mesh';
   186    232   	mesh = 'sorcery-enchanter.obj';
   187    233   	paramtype = 'light';
   188    234   	paramtype2 = 'facedir';
................................................................................
   196    242   		"default_bronze_block.png";
   197    243   		"default_junglewood.png";
   198    244   		"default_gold_block.png";
   199    245   	};
   200    246   	on_construct = function(pos)
   201    247   		local meta = minetest.get_meta(pos)
   202    248   		local inv = meta:get_inventory()
          249  +		meta:set_string('infotext','Enchanter')
   203    250   		inv:set_size('item', 1)
   204    251   		inv:set_size('foci', 3)
   205    252   		enchanter_update(pos)
   206    253   	end;
   207    254   	on_metadata_inventory_put  = enchanter_update;
   208    255   	on_metadata_inventory_move = enchanter_update;
   209    256   	on_metadata_inventory_take = enchanter_update;
   210    257   })
   211    258   
          259  +minetest.register_craftitem('sorcery:enchanter_channeler',{
          260  +	inventory_image = 'sorcery_enchanter_channeler.png';
          261  +	description = 'Channeler';
          262  +})
          263  +minetest.register_craftitem('sorcery:enchanter_pedestal',{
          264  +	inventory_image = 'sorcery_enchanter_pedestal.png';
          265  +	description = 'Pedestal';
          266  +})
          267  +minetest.register_craft {
          268  +	output = 'sorcery:enchanter_channeler';
          269  +	recipe = {
          270  +		{'','default:bronze_ingot',''};
          271  +		{'basic_materials:gold_wire','basic_materials:steel_strip','basic_materials:gold_wire'};
          272  +		{'','sorcery:electrum_ingot',''};
          273  +	};
          274  +	replacements = {
          275  +		{'basic_materials:gold_wire','basic_materials:empty_spool'};
          276  +		{'basic_materials:gold_wire','basic_materials:empty_spool'};
          277  +	};
          278  +}
          279  +minetest.register_craft {
          280  +	output = 'sorcery:enchanter_pedestal';
          281  +	recipe = {
          282  +		{'basic_materials:copper_strip','group:wood','basic_materials:copper_strip'};
          283  +		{'','default:bronze_ingot',''};
          284  +		{'','group:wood',''};
          285  +	};
          286  +}
          287  +minetest.register_craft {
          288  +	output = 'sorcery:enchanter';
          289  +	recipe = {
          290  +		{'','sorcery:enchanter_channeler',''};
          291  +		{'sorcery:enchanter_channeler','sorcery:enchanter_pedestal','sorcery:enchanter_channeler'};
          292  +		{'group:wood','group:wood','group:wood'};
          293  +	};
          294  +}
   212    295   for i=1,10 do
   213    296   	minetest.register_node('sorcery:air_flash_' .. i, {
   214    297   		drawtype = 'airlike';
   215    298   		pointable = false; walkable = false;
   216    299   		buildable_to = true;
   217    300   		sunlight_propagates = true;
   218    301   		light_source = i + 4;
................................................................................
   226    309   			end
   227    310   		end
   228    311   	});
   229    312   end
   230    313   
   231    314   minetest.register_on_dignode(function(pos, node, puncher)
   232    315   	if puncher == nil then return end -- i don't know why
   233         -	-- this is necessary but you get rare crashed without it
          316  +	-- this is necessary but you get rare crashes without it
   234    317   
   235    318   	-- we're goint to do something VERY evil here and
   236    319   	-- replace the air with a "glow-air" that removes
   237    320   	-- itself after a short period of time, to create
   238    321   	-- a flash of light when an enchanted tool's used
   239    322   	-- to dig out a node
   240    323   	local tool = puncher:get_wielded_item()
   241         -	local meta = tool:get_meta()
          324  +	local ench = sorcery.enchant.get(tool)
          325  +	if #ench.spells == 0 then return end
          326  +	print('enchanted!')
   242    327   	local sparks = {}
   243         -	local spark = function(name,color)
   244         -		if meta:contains('enchant_' .. name) then
   245         -			sparks[#sparks + 1] = {
   246         -				color = color;
   247         -				count = meta:get_int('enchant_' .. name);
   248         -			}
   249         -		end
          328  +	-- local spark = function(name,color)
          329  +	local material = sorcery.enchant.getsubj(tool)
          330  +	local totalcost = 0
          331  +	do local done = {} for _,sp in pairs(ench.spells) do
          332  +		if done[sp.id] then goto skip end
          333  +		done[sp.id] = true
          334  +
          335  +		local data = sorcery.data.enchants[sp.id]
          336  +		local strength = sorcery.enchant.strength(tool,sp.id)
          337  +		local ch = math.random(1,100)
          338  +		local props = {
          339  +			fail = ch > sp.reliability;
          340  +			user = puncher;
          341  +			pos = pos;
          342  +			node = node;
          343  +			tool = tool;
          344  +			material = material.data;
          345  +			enchantment = ench;
          346  +			power = strength;
          347  +			spell = sp;
          348  +			sparks = sparks;
          349  +			cost = data.cost;
          350  +		} 
          351  +		if data.on_dig then data.on_dig(props) end
          352  +		if props.cost ~= 0 then totalcost = totalcost + math.max(1,math.floor(props.cost * strength)) end
          353  +
          354  +		if ch > sp.reliability then goto skip end
          355  +		sparks[#sparks + 1] = {
          356  +			color = sorcery.lib.color(data.tone):brighten(1.1);
          357  +			count = strength * 7;
          358  +		}
          359  +	::skip::end end
          360  +	if totalcost > 0 then
          361  +		local conservation = sorcery.enchant.strength(tool,'conserve') * 0.5
          362  +		totalcost = totalcost - (totalcost * conservation)
          363  +		ench.energy = math.max(0,ench.energy - (totalcost - (material.data.energysource or 0)))
   250    364   	end
   251         -	spark('durable',sorcery.lib.color(0,89,245))
   252         -	spark('fast',sorcery.lib.color(245,147,89))
   253    365   	if #sparks == 0 then return end
   254    366   	if math.random(5) == 1 then
   255    367   		minetest.set_node(pos, {name='sorcery:air_flash_' .. tostring(math.random(10))})
   256    368   	end
   257    369   	local range = function(min, max)
   258    370   		local span = max - min
   259    371   		local val = math.random() * span
   260    372   		return val + min
   261    373   	end
   262    374   	for _,s in pairs(sparks) do
   263         -		for i=0,math.floor(s.count * range(1,3))  do
          375  +		for i=1,math.floor(s.count * range(1,3))  do
   264    376   			local life = range(0.3,1);
   265    377   			minetest.add_particle {
   266    378   				pos = {
   267    379   					x = pos.x + range(-0.5,0.5);
   268    380   					z = pos.z + range(-0.5,0.5);
   269    381   					y = pos.y + range(-0.5,0.5);
   270    382   				};
................................................................................
   276    388   				velocity = {
   277    389   					x = range(-1.3,1.3);
   278    390   					z = range(-1.3,1.3);
   279    391   					y = range( 0.3,0.9);
   280    392   				};
   281    393   				expirationtime = life;
   282    394   				size = range(0.5,1.5);
   283         -				vertical = true;
   284         -				texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color:brighten(1.2)):render();
          395  +				texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color):render();
   285    396   				glow = 14;
   286    397   				animation = {
   287    398   					type = "vertical_frames";
   288    399   					aspect_w = 16;
   289    400   					aspect_h = 16;
   290         -					length = life * 1.1;
          401  +					length = life + 0.1;
   291    402   				};
   292    403   			}
   293    404   		end
   294    405   	end
          406  +
          407  +	-- destroy spells that can no longer be sustained
          408  +	if ench.energy == 0 then
          409  +		ench.spells = {}
          410  +		sorcery.enchant.set(tool,ench)
          411  +	else
          412  +		sorcery.enchant.set(tool,ench,true)
          413  +	end
          414  +	puncher:set_wielded_item(tool)
   295    415   end)
          416  +
          417  +minetest.register_chatcommand('enchants', {
          418  +	description = 'Log information about the currently held object\'s enchantment';
          419  +	privs = { server = true };
          420  +	func = function(caller,params)
          421  +		local tool = minetest.get_player_by_name(caller):get_wielded_item()
          422  +		minetest.chat_send_player(caller, dump(sorcery.enchant.get(tool)))
          423  +		local binary = tool:get_meta():get_string('sorcery_enchantment_recs')
          424  +		local dumpout = ''
          425  +		for i=1,string.len(binary) do
          426  +			dumpout = dumpout .. string.format('%x%s',string.byte(binary,i),(i%16==0 and '\n') or ' ') 
          427  +		end
          428  +		print(dumpout)
          429  +	end;
          430  +})