Differences From
Artifact [24333d0a0e]:
- File
data/spells.lua
— part of check-in
[3f6a913e4e]
at
2020-09-29 12:40:28
on branch trunk
— * remove former hacky registration system, replace with consistent and flexible API; rewrite metal/gem generation to take advantage of this new API; tweaks to init system to enable world-local tweaks to lore and sorcery behavior
* initial documentation commit
* initial steps towards calendar - add default date format, astrolabe; prepare infra for division/melding/transmutation spells, various tweaks and fixes
(user:
lexi,
size: 22648)
[annotate]
[blame]
[check-ins using]
- File
data/spells.lua
— part of check-in
[ea6e475e44]
at
2020-10-19 09:52:11
on branch trunk
— continue dev on celestial mechanics, add melding+division spells (resonance), refine itemclasses, add keypunch and punchcards, add paper pulp, add a shitload of visuals, add convenience scripts for working with the wiki, make the flamebolt spell actually useful instead of just a pretty lightshow, add essences, inferno crystal, and other goodies; iterate on wands, lots of shit i can't remember, various bugfixes
(user:
lexi,
size: 25710)
[annotate]
[blame]
[check-ins using]
1 +local target_node = function(ctx,tgt)
2 + if not ctx.target or ctx.target.type ~= 'node' then return false end
3 + local node = minetest.get_node(ctx.target.under)
4 + if node.name ~= tgt then return false end
5 + return node
6 +end;
7 +
8 +local get_enchanter = function(ctx)
9 + local ench = target_node(ctx, 'sorcery:enchanter')
10 + if not ench then return false end
11 + return minetest.get_meta(ctx.target.under):get_inventory()
12 +end
13 +
1 14 local cast_sparkle = function(ctx,color,strength,duration)
2 - minetest.add_particlespawner {
3 - amount = 70 * strength;
4 - time = duration or 1.5;
5 - attached = ctx.caster;
6 - texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render();
7 - minpos = { x = -0.1, z = 0.5, y = 1.2};
8 - maxpos = { x = 0.1, z = 0.3, y = 1.6};
9 - minvel = { x = -0.5, z = -0.5, y = -0.5};
10 - maxvel = { x = 0.5, z = 0.5, y = 0.5};
11 - minacc = { x = 0.0, z = 0.0, y = 0.5};
12 - maxacc = { x = 0.0, z = 0.0, y = 0.5};
13 - minsize = 0.4, maxsize = 0.8;
14 - minexptime = 1, maxexptime = 1;
15 - glow = 14;
16 - animation = {
17 - type = 'vertical_frames';
18 - aspect_w = 16;
19 - aspect_h = 16;
20 - length = 1.1;
21 - };
22 - }
15 + sorcery.vfx.cast_sparkle(ctx.caster,color,strength,duration)
16 +end
17 +
18 +local enchantment_sparkle = function(ctx,color)
19 + sorcery.vfx.enchantment_sparkle(ctx.target,color)
23 20 end
24 -local enchantment_sparkle = function(ctx,color)
25 - local minvel, maxvel
26 - if minetest.get_node(vector.add(ctx.target.under,{y=1,z=0,x=0})).name == 'air' then
27 - minvel = {x=0,z=0,y= 0.3} maxvel = {x=0,z=0,y= 1.5};
28 - else
29 - local dir = vector.subtract(ctx.target.under,ctx.target.above)
30 - minvel = vector.multiply(dir, 0.3)
31 - maxvel = vector.multiply(dir, 1.2)
32 - end
33 - return minetest.add_particlespawner {
34 - amount = 50;
35 - time = 0.5;
36 - minpos = vector.subtract(ctx.target.under, 0.5);
37 - maxpos = vector.add(ctx.target.under, 0.5);
38 - minvel = minvel, maxvel = maxvel;
39 - minexptime = 1, maxexptime = 2;
40 - minsize = 0.5, maxsize = 2;
41 - texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render();
42 - animation = {
43 - type = 'vertical_frames';
44 - aspect_w = 16, aspect_h = 16;
45 - length = 2;
46 - };
47 - glow = 14;
48 - }
49 -end
21 +
50 22 local anchorwand = function(aff,uses,recipe)
51 23 local affcolor = sorcery.lib.color(sorcery.data.affinities[aff].color)
52 24 return {
53 25 name = aff .. ' anchor';
54 26 desc = 'With an enchanter, anchor ' .. aff .. ' spells into an object to enable it to produce preternatural effects';
55 27 uses = uses;
56 28 affinity = recipe;
57 29 color = affcolor;
58 30 sound = 'xdecor_enchanting'; -- FIXME make own
59 31 cast = function(ctx)
60 - if (not ctx.target) or ctx.target.type ~= 'node' then return false end
61 - local node = minetest.get_node(ctx.target.under)
62 - if node.name ~= 'sorcery:enchanter' then return false end
32 + local node = target_node(ctx, 'sorcery:enchanter')
33 + if not node then return false end
63 34
64 35 local inv = minetest.get_meta(ctx.target.under):get_inventory()
65 36 if inv:is_empty('item') then return false end
66 37 local subj = inv:get_stack('item',1)
67 38
68 39 -- now we have everything we need. this part is complex.
69 40 -- first, we need to check the item to see if it is in a
................................................................................
193 164 -- note: this was written before terminology was standardized,
194 165 -- and "leytype" corresponds to what is otherwise known as an
195 166 -- "affinity"; "affinity" after this comment is widely misused
196 167 return {
197 168 flame = {
198 169 name = 'flamebolt';
199 170 color = {255,89,16};
200 - uses = 64;
171 + uses = 32;
201 172 affinity = {'acacia','blazing'};
202 173 leytype = 'praxic';
203 174 desc = 'Conjure a gout of fire to scorch your foes with a flick of this wand';
204 175 cast = function(ctx)
205 176 local speed = 30 -- TODO maybe amethyst tip increases speed?
177 + local radius
178 + if ctx.base.gem == 'sapphire'
179 + then radius = math.random(2,3)
180 + else radius = math.random(1,2)
181 + end
206 182 local heading = ctx.heading
207 - heading.pos.y = heading.pos.y + 1.5 -- TODO maths
183 + heading.pos.y = heading.pos.y + heading.eyeheight*0.9
208 184 local bolt = minetest.add_entity(heading.pos,'sorcery:spell_projectile_flamebolt')
209 185 bolt:set_rotation(heading.yaw)
210 186 local vel = {
211 187 x = heading.yaw.x * speed;
212 188 y = heading.yaw.y * speed;
213 189 z = heading.yaw.z * speed;
214 190 };
191 + bolt:get_luaentity()._blastradius = radius
215 192 bolt:set_velocity(vel)
216 193 end;
217 194 };
218 195 seal = {
219 196 name = 'sealing';
220 197 color = {255,238,16};
221 198 uses = 32;
................................................................................
223 200 leytype = 'imperic';
224 201 affinity = {'pine','dark'};
225 202 cast = function(ctx)
226 203 if ctx.target == nil or ctx.target.type ~= 'node' then return false end
227 204 local meta = minetest.get_meta(ctx.target.under)
228 205 -- first we need to check if the wand has an identifying 'key' yet,
229 206 -- and set one if not.
230 - local wandmode = ctx.base.gem == 'sapphire'
207 + local modes = {
208 + sapphire = 'lockdown';
209 + diamond = 'steal';
210 + }
211 + local wandmode = modes[ctx.base.gem] or 'seal'
231 212 local keycode
232 213 if ctx.meta:contains('sorcery_wand_key') then
233 214 keycode = ctx.meta:get_string('sorcery_wand_key')
234 215 else
235 216 keycode = sorcery.lib.str.rand(32)
236 217 ctx.meta:set_string('sorcery_wand_key', keycode)
237 218 -- ctx.meta:mark_as_private('sorcery_wand_key')
238 219 end
239 220 if meta:contains('owner') then
240 221 -- owner is already set -- can we break the enchantment?
222 + if wandmode == 'steal' then
223 + if meta:get_string('owner') ~= ctx.caster:get_player_name() then
224 + meta:set_string('owner',ctx.caster:get_player_name())
225 + enchantment_sparkle(ctx,sorcery.lib.color(101,255,238))
226 + end
227 + return
228 + end
229 +
241 230 if meta:get_string('sorcery_wand_key') == keycode then
242 231 meta:set_string('owner','')
243 232 meta:set_string('sorcery_wand_key','')
244 233 meta:set_string('sorcery_seal_mode','')
245 234 enchantment_sparkle(ctx,sorcery.lib.color(101,255,142))
246 235 else return false end
247 236 else
248 237 meta:set_string('sorcery_wand_key',keycode)
249 238 meta:mark_as_private('sorcery_wand_key')
250 239 meta:set_string('owner',ctx.caster:get_player_name())
251 - if wandmode then
240 + if wandmode == 'lockdown' then
252 241 meta:set_string('sorcery_seal_mode','wand')
253 242 end
254 243 enchantment_sparkle(ctx,sorcery.lib.color(255,201,27))
255 244 end
256 245 end;
257 246 };
258 247 leyspark = {
................................................................................
374 363 };
375 364 meld = {
376 365 name = 'melding';
377 366 uses = 48;
378 367 leytype = 'syncretic';
379 368 color = {172,65,255};
380 369 affinity = {'apple','verdant'};
381 - desc = 'Meld the properties of three balanced items on an enchanter to create a new one with special properties, but destroying the old ones and losing two thirds of the mass in the process. The precise outcome is not always predictable.';
370 + desc = 'Meld the properties of three balanced items on an enchanter to create a new one with special properties, but destroying the old ones and losing two thirds of the mass in the process. The precise outcome is not always predictable, and may vary with the moons and the stars.';
371 + cast = function(ctx)
372 + local e = get_enchanter(ctx)
373 + if not e then return false end
374 +
375 + for _,m in pairs(sorcery.data.resonance.meld) do
376 + if m.restrict and not m.restrict(ctx)
377 + then goto next_meld end
378 +
379 + local g = {}
380 + for i,set in ipairs(m.set) do
381 + if type(set) == 'table' then
382 + g[i] = set
383 + else g[i] = { take = set; } end
384 +
385 + local found = false
386 + for j=1,e:get_size('foci') do
387 + local match,res = sorcery.lib.item.groupmatch(g[i].take, e:get_stack('foci',j),false)
388 + if match then
389 + found = true
390 + g[i].slot = j
391 + g[i].leftover = res
392 + break
393 + end
394 + end
395 + if not found then goto next_meld end
396 + end
397 + -- we've made it past the tests; this meld
398 + -- matches the spec
399 +
400 + for _,t in pairs(g) do
401 + if t.leftover and t.leftover:get_count() > 0 then
402 + e:set_stack('foci',t.slot,t.leftover)
403 + if t.replacement then
404 + minetest.add_item(ctx.target.above, ItemStack(t.replacement))
405 + end
406 + else
407 + e:set_stack('foci',t.slot,ItemStack(t.replacement))
408 + end
409 + end
410 +
411 + local res
412 + if type(m.results) == 'function' then
413 + res = m.results(ctx)
414 + elseif type(m.results) == 'table' and m.results[1] then -- haaaack
415 + res = select(2,sorcery.lib.tbl.pick(m.results))
416 + else
417 + res = m.results
418 + end
419 +
420 + e:set_stack('item',1,ItemStack(res))
421 + enchantment_sparkle(ctx,sorcery.lib.color(228,4,201))
422 + ::next_meld::end
423 + end;
382 424 };
383 425 divide = {
384 426 name = 'division';
385 427 uses = 19;
386 428 leytype = 'syncretic';
387 429 color = {255,65,121};
388 430 affinity = {'apple','shimmering'};
389 - desc = 'Shatter an item on an enchanter, dividing its essence equally into three parts and precipitating it into new items embodying various properties of the destroyed item. The outcome is not always predictable.';
431 + desc = 'Shatter an item on an enchanter, dividing its essence equally into three parts and precipitating it into new items embodying various properties of the destroyed item. The outcome is not always predictable, and may vary with the moons and the stars.';
432 + cast = function(ctx)
433 + local e = get_enchanter(ctx)
434 + if not e then return false end
435 +
436 + local orig = e:get_stack('item',1)
437 + local div = sorcery.data.resonance.divide[orig:get_name()]
438 + if not div then return false end
439 +
440 + local bitch = function(err)
441 + sorcery.log('data/spells(divide)', err .. ' for ' .. orig:get_name())
442 + return false
443 + end
444 +
445 + if not (div.mode and div.give) then
446 + return bitch('improperly specified division')
447 + end
448 +
449 + if div.restrict and not div.restrict(ctx) then
450 + return false
451 + end
452 +
453 + local dst
454 + if div.mode == 'any' then
455 + local lst = sorcery.lib.tbl.cshuf(div.give)
456 + dst = function(i) return lst[i] end
457 + elseif div.mode == 'random' then
458 + dst = function() return sorcery.lib.tbl.pick(div.give) end
459 + elseif div.mode == 'set' then
460 + dst = function(i) return div.give[i] end
461 + elseif div.mode == 'all' then
462 + dst = function() return div.give end
463 + elseif div.mode == 'fn' then
464 + dst = function(i) return div.give(i,ctx) end
465 + else return bitch('invalid division mode') end
466 + for i=1,e:get_size('foci') do
467 + e:set_stack('foci',i,ItemStack(dst(i)))
468 + end
469 + e:set_stack('item',1,ItemStack(div.replacement))
470 +
471 + for _,color in pairs{{245,63,63},{63,245,178}} do
472 + enchantment_sparkle(ctx, sorcery.lib.color(color))
473 + end
474 + end;
390 475 };
391 476 obliterate = {
392 477 name = 'obliteration';
393 478 uses = 129;
394 479 color = {175,6,212};
395 480 affinity = {'aspen','dark'};
396 481 leytype = 'occlutic';
397 - desc = 'Totally and irreversibly obliterate all items on an enchanter.';
482 + desc = 'Incinerate all items on an enchanter, rendering them down to ash or obliterating them entirely.';
398 483 cast = function(ctx)
399 - if not ctx.target or ctx.target.type ~= 'node' then return false end
400 - local tgt = minetest.get_node(ctx.target.under)
401 - if tgt.name ~= 'sorcery:enchanter' then return false end
484 + local tgt = target_node(ctx, 'sorcery:enchanter')
485 + if not tgt then return false end
402 486
403 487 local inv = minetest.get_meta(ctx.target.under):get_inventory()
404 488 for _,name in pairs{'foci','item'} do
405 489 for i=1,inv:get_size(name) do
406 - inv:set_stack(name,i,ItemStack(nil))
490 + local stack = 'sorcery:ash'
491 + if ctx.base.gem == 'sapphire' then
492 + stack = nil
493 + end
494 + inv:set_stack(name,i,ItemStack(stack))
407 495 end
408 496 end
409 497
410 498 enchantment_sparkle(ctx,sorcery.lib.color(255,12,0))
411 499 enchantment_sparkle(ctx,sorcery.lib.color(85,18,35))
412 500 enchantment_sparkle(ctx,sorcery.lib.color(0,0,0))
413 501 end
................................................................................
430 518 };
431 519 transmute = {
432 520 name = 'transmutation';
433 521 uses = 7;
434 522 color = {255,90,18};
435 523 leytype = 'imperic';
436 524 affinity = {'aspen','shimmering','dark','blazing'};
437 - desc = 'Transmute three ingots into one of a different metal, determined by chance and influenced by configuration of the wand';
525 + desc = 'Transmute three ingots into one of a different metal, determined by chance, and influenced by configuration of the wand as well as the stars and the phase of the moon';
526 + -- diamond = quantity varies between 1-3
438 527 };
439 528 disjoin = {
440 529 name = 'disjunction';
441 530 uses = 32;
442 531 color = {17,6,212};
443 532 leytype = 'occlutic';
444 533 affinity = {'jungle','silent'};
445 534 desc = 'With an enchanter, disjoin the anchor holding a spell into an object so a new spell can instead be bound in';
535 + cast = function(ctx)
536 + local ench = target_node(ctx, 'sorcery:enchanter')
537 + if not ench then return false end
538 + local ei = minetest.get_meta(ctx.target.under):get_inventory()
539 + local item = ei:get_stack('item',1)
540 + local e = sorcery.enchant.get(item)
541 + if next(e.spells) == nil then return false end
542 + if #e.spells == 1 then e = nil else
543 + if ctx.base.gem == 'sapphire'
544 + then e.spells = {} e.energy = 0
545 + else table.remove(e.spells, math.random(#e.spells))
546 + end
547 + end
548 + sorcery.enchant.set(item,e)
549 + ei:set_stack('item',1,item)
550 + enchantment_sparkle(ctx,sorcery.lib.color(255,154,44))
551 + enchantment_sparkle(ctx,sorcery.lib.color(226,44,255))
552 + end;
446 553 };
447 554 divine = {
448 555 name = 'divining';
449 556 desc = 'Steal away the secrets of the cosmos';
450 557 uses = 16;
451 558 color= {97,97,255};
452 559 sound = 'xdecor:enchanting';
................................................................................
455 562 cast = function(ctx)
456 563 local inks = {'black','red','white','violet','blue','green'}
457 564 local getcolor = function(stack)
458 565 if stack:is_empty() then return nil end
459 566 if minetest.get_item_group(stack:get_name(), 'dye') == 0 then return nil end
460 567 for _,ink in pairs(inks) do
461 568 if minetest.get_item_group(stack:get_name(), 'color_' ..ink) ~= 0
462 - then print('found',ink,'ink') return ink end
569 + then return ink end
463 570 end
464 571 end
465 572 if not ctx.target or ctx.target.type ~= 'node' then return false end
466 573 local tgt = minetest.get_node(ctx.target.under)
467 574 if tgt.name == 'sorcery:enchanter' then
468 575 local meta = minetest.get_meta(ctx.target.under)
469 576 local inv = meta:get_inventory()
................................................................................
472 579 and not inv:is_empty('foci') then
473 580 local ink1 = getcolor(inv:get_stack('foci',2))
474 581 local ink2 = getcolor(inv:get_stack('foci',3))
475 582 local restrict, kind, mod = {} do
476 583 local ms = inv:get_stack('foci',1)
477 584 if not ms:is_empty() then mod = ms:get_name() end
478 585 end
479 - print(ink1,ink2,mod)
480 586 if ink1 == 'black' and ink2 == 'black' then kind = 'craft'
481 587 if mod then
482 588 if mod == sorcery.data.metals.cobalt.parts.powder then
483 589 restrict.group = 'sorcery_magitech'
484 590 elseif mod == sorcery.data.metals.vidrium.parts.powder then
485 591 restrict.group = 'sorcery_ley_device'
486 592 elseif mod == sorcery.data.metals.aluminum.parts.powder then
................................................................................
516 622 if mod == sorcery.data.metals.cobalt.parts.powder then
517 623 restrict.aff = 'praxic'
518 624 elseif mod == sorcery.data.metals.tungsten.parts.powder then
519 625 restrict.aff = 'counterpraxic'
520 626 elseif mod == sorcery.data.metals.aluminum.parts.powder then
521 627 restrict.aff = 'syncretic'
522 628 elseif mod == sorcery.data.metals.lithium.parts.powder then
523 - -- restrict.aff = 'mandatic' -- no enchants yet, will cause infinite loop
629 + -- restrict.aff = 'mandatic' -- no enchants yet, will cause infinite loop 🙃
524 630 elseif mod == sorcery.data.metals.iridium.parts.powder then
525 631 restrict.aff = 'entropic'
526 632 elseif mod == sorcery.data.metals.gold.parts.powder then
527 633 restrict.aff = 'cognic'
528 634 elseif mod == sorcery.data.metals.silver.parts.powder then
529 635 -- restrict.aff = 'occlutic'
530 636 elseif mod == sorcery.data.metals.electrum.parts.powder then
531 637 -- restrict.aff = 'imperic'
532 638 else return false end
533 639 end
534 640 elseif ink1 == 'red' and ink2 == 'yellow' then kind = 'cook';
535 641 -- elseif ink1 == 'red' and ink2 == 'orange' then kind = 'smelt';
536 642 end
537 - print('result',kind,dump(restrict))
538 643 if kind then
539 - print('found kind')
540 644 local rec = ItemStack('sorcery:recipe')
541 645 local m = rec:get_meta()
542 646 if ctx.base.gem == 'diamond' then
543 647 -- make recipe for thing in slot 1
544 648 else
545 649 sorcery.cookbook.setrecipe(rec,kind,nil,restrict)
546 650 end
................................................................................
574 678 color = {244,255,157};
575 679 affinity = {'acacia','shimmering','blazing'};
576 680 leytype = 'cognic';
577 681 cast = function(ctx)
578 682 local center = ctx.heading.pos
579 683 local maxpower = 20
580 684 local power = (ctx.base.gem == 'sapphire' and maxpower) or maxpower/2
581 - local range = (ctx.base.gem == 'emerald' and 10) or 5
685 + local range = (ctx.base.gem == 'emerald' and 6) or 3
582 686 local duration = (ctx.base.gem == 'amethyst' and 60) or 30
583 687 if ctx.base.gem == 'diamond' then
584 688 power = power * (math.random()*2)
585 689 range = range * (math.random()*2)
586 690 duration = duration * (math.random()*2)
587 691 end
588 692 local lum = math.ceil((power/maxpower) * minetest.LIGHT_MAX)