Differences From
Artifact [f08588c5d9]:
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 +})