Differences From
Artifact [4908250df2]:
- File
data/runes.lua
— part of check-in
[147592b8e9]
at
2020-10-26 03:58:08
on branch trunk
— add over-time spellcasting abstraction to enable metamagic and in particular disjunction, add more animations and sound effects, add excavation spell, possibly some others, forget when the last commit was, edit a bunch of magitech to make it subject to the disjunction mechanism (throw up a disjunction aura and waltz right through those force fields bby, wheee), also illumination spells, tweak runeforge and rune frequence to better the balance and also limit player frustration, move some math functions into their own library category, various tweaks and bugfixes, probably other shit i don't remember
(user:
lexi,
size: 30324)
[annotate]
[blame]
[check-ins using]
1 1 -- a rune is an abstract object created by a runeforge, which can be
2 2 -- applied to an amulet in order to imbue that amulet with unique
3 3 -- and fearsome powers. the specific spell depends on the stone the
4 4 -- rune is applied to, and not all runes can necessarily be applied
5 5 -- to all stones.
6 6
7 -local sparkle = function(color, spell, amt,time,minsize,maxsize,sh)
8 - spell.visual_subjects {
9 - amount = amt, time = time, -- attached = s;
10 - minpos = { x = -0.3, y = -0.5, z = -0.3 };
11 - maxpos = { x = 0.3, y = sh*1.1, z = 0.3 };
7 +local sparkle_region = function(s)
8 + s.spell.visual_subjects {
9 + amount = s.amt, time = s.time, -- attached = s;
10 + minpos = s.minpos;
11 + maxpos = s.maxpos;
12 12 minvel = { x = -0.4, y = -0.2, z = -0.4 };
13 13 maxvel = { x = 0.4, y = 0.2, z = 0.4 };
14 14 minacc = { x = -0.5, y = -0.4, z = -0.5 };
15 15 maxacc = { x = 0.5, y = 0.4, z = 0.5 };
16 - minexptime = 1.0, maxexptime = 2.0;
17 - minsize = minsize, maxsize = maxsize, glow = 14;
18 - texture = sorcery.vfx.glowspark(color):render();
16 + minexptime = 1.0*(s.length or 1), maxexptime = 2.0 * (s.length or 1);
17 + minsize = s.minsize, maxsize = s.maxsize, glow = 14;
18 + texture = (s.img or sorcery.vfx.glowspark(s.color)):render();
19 19 animation = {
20 20 type = 'vertical_frames';
21 21 aspect_w = 16, aspect_h = 16;
22 + length = 0.1 + (s.length or 1)*2;
22 23 };
23 24 }
24 25 end
25 -local sparktrail = function(fn,tgt,color)
26 +local sparkle = function(color, spell, amt,time,minsize,maxsize,sh)
27 + sparkle_region { spell = spell;
28 + amt = amt, time = time, color = color;
29 + minsize = minsize, maxsize = maxsize;
30 + minpos = { x = -0.3, y = -0.5, z = -0.3 };
31 + maxpos = { x = 0.3, y = sh*1.1, z = 0.3 };
32 + }
33 +end
34 +local sparktrail = function(fn,tgt,color,time)
26 35 return (fn or minetest.add_particlespawner)({
27 - amount = 240, time = 1, attached = tgt;
36 + amount = 240, time = time or 1, attached = tgt;
28 37 minpos = {x = -0.4, y = -0.5, z = -0.4};
29 38 maxpos = {x = 0.4, y = tgt:get_properties().eye_height or 0.5, z = 0.4};
30 39 minacc = {x = 0.0, y = 0.05, z = 0.0};
31 40 maxacc = {x = 0.0, y = 0.15, z = 0.0};
32 41 minexptime = 1.5, maxexptime = 5;
33 42 minsize = 0.5, maxsize = 2.6, glow = 14;
34 43 texture = sorcery.vfx.glowspark(color):render();
................................................................................
39 48 });
40 49 end
41 50 return {
42 51 translocate = {
43 52 name = 'Translocate';
44 53 tone = {0,235,233};
45 54 minpower = 3;
46 - rarity = 15;
55 + rarity = 10;
47 56 amulets = {
48 57 amethyst = {
49 58 name = 'Joining';
50 59 desc = 'Give this amulet to another and they can arrive safely at your side in a flash from anywhere in the world — though returning whence they came may be a more difficult matter';
51 60 apply = function(ctx)
52 61 local maker = ctx.user:get_player_name()
53 62 ctx.meta:set_string('rune_join_target',maker)
................................................................................
82 91 else
83 92 local pos = minetest.string_to_pos(ctx.meta:get_string('rune_return_dest'))
84 93 ctx.meta:set_string('rune_return_dest','')
85 94 local subjects = { ctx.caster }
86 95 local center = ctx.caster:get_pos()
87 96 ctx.sparkle = false
88 97 local delay = math.max(3,10 - ctx.stats.power) + 3*(math.random()*2-1)
98 + if ctx.amulet.frame == 'tungsten' then delay = delay * 0.5 end
89 99 for _,s in pairs(subjects) do
90 100 local offset = vector.subtract(s:get_pos(), center)
91 101 local pt = sorcery.lib.node.get_arrival_point(vector.add(pos,offset))
92 102 if pt then
93 103 -- minetest.sound_play('sorcery_stutter', {
94 104 -- object = s, gain = 0.8;
95 105 -- },true)
................................................................................
123 133 minetest.sound_play('sorcery_zap', { pos = s:get_pos(), gain = 0.4 },true)
124 134 sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,s:get_pos())
125 135 s:set_pos(pt)
126 136 sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2)
127 137 end;
128 138 };
129 139 sounds = {
130 - [0] = {
131 - pos = 'subjects';
132 - sound = 'sorcery_stutter';
133 - };
140 + [0] = { sound = 'sorcery_stutter', pos = 'subjects' };
134 141 };
135 142 }
136 143 end
137 144 end
138 145 end
139 146 end;
140 147 frame = {
148 + tungsten = {
149 + name = 'Quick Return';
150 + desc = 'Use this amulet once to bind it to a particular place, then discharge its spell to translocate yourself rapidly back to that point from anywhere in the world.';
151 + };
141 152 iridium = {
142 153 name = 'Mass Return';
143 154 desc = 'Use this amulet once to bind it to a particular place, then carry yourself and everyone around you back to that point in a flash simply by using it again';
144 155 };
145 156 };
146 157 };
147 158 emerald = {
................................................................................
191 202 };
192 203 };
193 204 };
194 205 disjoin = {
195 206 name = 'Disjoin';
196 207 tone = {159,235,0};
197 208 minpower = 4;
198 - rarity = 20;
209 + rarity = 40;
199 210 amulets = {
200 211 sapphire = {
201 212 name = 'Unsealing';
202 213 desc = 'Wielding this amulet, a touch of your hand will unravel even the mightiest protective magics, leaving doors unsealed and walls free to tear down';
203 214 };
204 215 amethyst = {
205 216 name = 'Purging';
................................................................................
236 247 name = 'Nullification';
237 248 desc = 'Not only will your victim\'s spells be nullified, but all enchanted objects they carry will be stripped of their power — or possibly even destroyed outright';
238 249 };
239 250 };
240 251 };
241 252 luxite = {
242 253 name = 'Disjunctive Aura';
243 - desc = 'For a time, all magic undertaken in your vicinity will fail totally';
254 + desc = 'For a time, all magic undertaken in your vicinity will fail totally — including your own';
244 255 cast = function(ctx)
256 + local h = ctx.heading.eyeheight*1.1
245 257 sorcery.spell.cast {
246 258 caster = ctx.caster, attach = 'caster';
259 + subjects = {{player=ctx.caster}};
247 260 disjunction = true, range = 4 + ctx.stats.power;
248 261 duration = 10 + ctx.stats.power * 3;
249 262 timeline = {
250 263 [0] = function(s,_,tl)
251 - sparkle(sorcery.lib.color(120,255,30), s,
252 - 30 * tl, tl, 0.3,1.4, ctx.heading.eyeheight*1.1)
253 - end
264 + local ttns = 0.8
265 + local vel = s.range / ttns
266 + s.visual_caster {
267 + amount = 300, time = ttns, glow = 14;
268 + texture = sorcery.lib.image('sorcery_sputter.png'):glow(sorcery.lib.color(160,255,80)):render();
269 + minpos = { x = -0.0, y = h*0.5,z = -0.0 };
270 + maxpos = { x = 0.0, y = h*0.5,z = 0.0 };
271 + minvel = { x = -vel, y = -0.0, z = -vel };
272 + maxvel = { x = vel, y = 0.0, z = vel };
273 + minacc = { x = -0.2, y = -0.0, z = -0.2 };
274 + maxacc = { x = 0.2, y = 0.0, z = 0.2 };
275 + minexptime = ttns, maxexptime = ttns * 2;
276 + minsize = 0.2, maxsize = 4.5;
277 + animation = {
278 + type = 'vertical_frames', length = 0.1 + ttns*2;
279 + aspect_w = 16, aspect_h = 16;
280 + }
281 + }
282 + end;
283 + [{whence=0,secs=0.8}] = function(s,te,tl)
284 + local range = s.range
285 + sparkle_region {
286 + spell = s, amt = 150*tl, time = tl;
287 + minsize = 1, maxsize = 8.4;
288 + minpos = { x = 0-range, y = -0.5, z = 0-range };
289 + maxpos = { x = range, y = h, z = range };
290 + img = sorcery.lib.image('sorcery_flicker.png'):glow(sorcery.lib.color(120,255,30));
291 + }
292 + end;
254 293 };
255 294 sounds = {
256 - [0] = { sound = 'sorcery_disjoin', pos = 'caster' };
257 - [1] = { sound = 'sorcery_powerdown', pos = 'caster' };
295 + [0.00] = {sound='sorcery_disjoin', where='caster'};
296 + [{whence=0,secs=0.8}] = {
297 + sound='sorcery_disjoin_bg', where='subjects';
298 + gain=0.5, stop = {whence=1,secs=-1.5}
299 + };
300 + [1.00] = {sound='sorcery_powerdown', where='caster'};
258 301 };
259 302 }
260 303 end
261 304 };
262 305 diamond = {
263 306 name = 'Mundanity';
264 307 desc = 'Strip away the effects of all active potions and spells in your immediate vicinity, leaving adversaries without their magicks to enhance and protect them, and allies free of any curses they may be hobbled by -- and, of course, vice versa';
................................................................................
271 314 };
272 315 }
273 316 };
274 317 repulse = {
275 318 name = 'Repulse';
276 319 tone = {0,180,235};
277 320 minpower = 1;
278 - rarity = 7;
321 + rarity = 5;
279 322 amulets = {
280 323 amethyst = {
281 324 name = 'Hurling';
282 325 desc = 'Wielding this amulet, a mere flick of your fingers will lift any target of your choice bodily into the air and press upon them with tremendous repulsive force, throwing them like a hapless ragdoll out of your path';
283 326 cast = function(ctx)
284 327 if not (ctx.target and ctx.target.type == 'object') then return false end
285 328 local tgt = ctx.target.ref
286 329 local line = vector.subtract(ctx.caster:get_pos(), tgt:get_pos())
287 330 -- direction vector from target to caster
288 - print('line',dump(line))
289 331 local dir,mag = sorcery.lib.math.vsep(line)
290 332 if mag > 6 then return false end -- no cheating!
291 333 local force = 20 + (ctx.stats.power * 2.5)
292 334 minetest.sound_play('sorcery_hurl',{pos=tgt:get_pos()},true)
293 335 local immortal = tgt:get_luaentity():get_armor_groups().immortal or 0
294 336 if minetest.is_player(tgt) or immortal == 0 then
295 337 tgt:punch(ctx.caster, 1, {
................................................................................
309 351 desc = 'Lift yourself high into the air with a blast of violent repulsive force against the ground, and drift down safely to a position of your choice';
310 352 cast = function(ctx)
311 353 local power = 14 * (1+(ctx.stats.power * 0.2))
312 354 minetest.sound_play('sorcery_hurl',{object=ctx.caster},true)
313 355 sorcery.spell.cast {
314 356 caster = ctx.caster;
315 357 subjects = {{player=ctx.caster}};
316 - duration = power * 0.25;
358 + duration = power * 0.30;
317 359 timeline = {
318 360 [0] = function(s,_,tl)
319 361 sparktrail(s.visual_subjects,ctx.caster,sorcery.lib.color(255,252,93))
320 362 ctx.caster:add_velocity{y=power;x=0,z=0}
321 363 s.affect {
322 - duration = power * 0.25;
364 + duration = power * 0.50;
323 365 raise = 2;
324 - fall = (power * 0.25) * 0.3;
366 + -- fall = (power * 0.25) * 0.3;
325 367 impacts = {
326 368 gravity = 0.1;
327 369 };
328 370 }
329 371 end;
372 + };
373 + intervals = {
374 + {period = 0.2, after = {whence=0, secs=2}; fn = function(c)
375 + -- return gravity to normal once they touch down
376 + for si,sub in pairs(c.spell.subjects) do
377 + local p = sub.player:get_pos()
378 + for i=1,3 do
379 + local sum = vector.offset(p,0,-i,0)
380 + if not sorcery.lib.node.is_air(sum) then
381 + c.spell.release_subject(si)
382 + if #c.spell.subjects == 0 then
383 + return false
384 + end
385 + break
386 + end
387 + end
388 + end
389 + end};
330 390 };
331 391 }
332 392 end;
333 393 };
334 394 sapphire = {
335 395 name = 'Flinging';
336 396 desc = 'Toss an enemy violently into the air, and allow the inevitable impact to do your dirty work for you';
397 + cast = function(ctx)
398 + if not (ctx.target and ctx.target.type == 'object') then return false end
399 + local tgt = ctx.target.ref
400 + local power = 16 * (1+(ctx.stats.power * 0.2))
401 + minetest.sound_play('sorcery_hurl',{object=ctx.caster},true)
402 + sorcery.spell.cast {
403 + caster = ctx.caster;
404 + subjects = {{player=tgt}};
405 + duration = 4;
406 + timeline = {
407 + [0] = function(s,_,tl)
408 + for _,sub in pairs(s.subjects) do
409 + local height = (sub.player:get_properties().eye_height or 1)*1.3
410 + local scenter = vector.add(sub.player:get_pos(), {x=0,y=height/2,z=0})
411 + for i=1,math.random(64,128) do
412 + local high = (height+0.8)*math.random() - 0.8
413 + local far = (high >= -0.5 and high <= height) and
414 + (math.random() * 0.3 + 0.4) or
415 + (math.random() * 0.5)
416 + local yaw = {x=0, y = math.random()*100, z=0}
417 + local po = vector.rotate({x=far,y=high,z=0}, yaw)
418 + local ppos = vector.add(po,sub.player:get_pos())
419 + local dir = vector.direction(ppos,scenter)
420 + local vel = math.random() * 0.8 + 0.4
421 + minetest.add_particle {
422 + pos = ppos;
423 + velocity = vector.multiply(dir,vel);
424 + expirationtime = far / vel;
425 + size = math.random()*2.4 + 0.6;
426 + texture = sorcery.lib.image('sorcery_sputter.png'):glow(sorcery.lib.color{
427 + hue = math.random(41,63);
428 + saturation = 100;
429 + luminosity = 0.5 + math.random()*0.3;
430 + }):render();
431 + glow = 14;
432 + animation = {
433 + type = 'vertical_frames', length = far/vel;
434 + aspect_w = 16, aspect_h = 16;
435 + };
436 + }
437 + end
438 + end
439 + end;
440 + [0.3] = function(s,te,tl)
441 + sparktrail(s.visual_subjects,ctx.caster,sorcery.lib.color(255,252,93))
442 + for _,sub in pairs(s.subjects) do
443 + sub.player:add_velocity{y=power;x=0,z=0}
444 + end
445 + end;
446 + [1] = (ctx.amulet.frame == 'cobalt') and function(s,te,tl)
447 + -- TODO add visuals
448 + for _,sub in pairs(s.subjects) do
449 + sub.player:add_velocity{y=-power*2;x=0,z=0}
450 + end
451 + end or nil;
452 + };
453 + sounds = {
454 + [0.3] = {
455 + sound = 'sorcery_hurl';
456 + where = 'subjects';
457 + ephemeral = true;
458 + };
459 + [1] = (ctx.amulet.frame == 'cobalt') and {
460 + sound = 'sorcery_hurl';
461 + where = 'subjects';
462 + ephemeral = true;
463 + } or nil;
464 + };
465 + };
466 + end;
467 + frame = {
468 + cobalt = {
469 + name = 'Crushing';
470 + desc = 'Toss an enemy violently into the air, then bring them crashing down to earth with bone-shattering force';
471 + };
472 + iridium = {
473 + name = 'Mass Flinging';
474 + desc = 'Send everyone around you hurtling into the sky, and allow the inevitable impact to do your dirty work for you';
475 + };
476 + };
337 477 };
338 478 emerald = {
339 479 name = 'Shockwave';
340 480 desc = 'Let loose a stream of concussive force that slams into everything in your path and sends them hurtling away from you';
341 481 };
342 482 luxite = {
343 483 name = 'Repulsive Aura';
................................................................................
349 489 };
350 490 };
351 491 };
352 492 obliterate = {
353 493 name = 'Obliterate';
354 494 tone = {255,0,10};
355 495 minpower = 5;
356 - rarity = 30;
496 + rarity = 35;
357 497 amulets = {
358 498 amethyst = {
359 499 name = 'Sapping';
360 500 desc = 'Punch a hole in enemy fortifications big enough to slip through but small enough to avoid immediate attention';
361 501 };
362 502 ruby = {
363 503 name = 'Shattering';
................................................................................
375 515 local bolt = minetest.add_entity(vector.add(heading.pos,vector.multiply(heading.yaw,2.5)),'sorcery:spell_projectile_flamebolt')
376 516 bolt:set_rotation(heading.yaw)
377 517 bolt:get_luaentity()._blastradius = radius
378 518 bolt:set_velocity(vel)
379 519 end;
380 520 };
381 521 luxite = {
382 - name = 'Lethal Aura';
383 - desc = 'For a time, anyone who approaches you, whether friend or foe, will suffer immediate retaliation as they are quickly sapped of their life force';
522 + name = 'Cataclysmic Aura';
523 + desc = 'A storm of destructive force rages about you as you stand untouched, the master of its voracious dark energies';
384 524 };
385 525 mese = {
386 526 name = 'Cataclysm';
387 - desc = 'Use this amulet once to pick a target, then visit devastation upon it from afar with a mere snap of your fingers';
527 + desc = 'Use this amulet once to pick a target, then visit devastation upon it from afar whenever you so will with a mere snap of your fingers';
388 528 };
389 529 diamond = {
390 530 name = 'Killing';
391 531 mingrade = 4;
392 532 desc = 'Wield this amulet against a foe to instantly snuff the life out of their mortal form, regardless of their physical protections.';
393 533 cast = function(ctx)
394 534 if not (ctx.target and ctx.target.type == 'object') then return false end
................................................................................
408 548 };
409 549 };
410 550 };
411 551 excavate = {
412 552 name = 'Excavate';
413 553 tone = {0,68,235};
414 554 minpower = 3;
415 - rarity = 60;
555 + rarity = 30;
416 556 amulets = {
557 + luxite = {
558 + name = 'Stonestride';
559 + desc = 'Rock walls will open up before you when you brandish this amulet before them, closing up again behind you without leaving a trace of your passage';
560 + };
417 561 sapphire = {
418 562 name = 'Tunnelling';
419 - desc = 'Carve a long tunnel ahead of you into the rock';
563 + desc = 'Carve a long tunnel ahead of you into the rock and dirt';
564 + cast = function(ctx)
565 + if ctx.target.type ~= 'node' then return false end
566 + local allowed = {
567 + ['default:stone'] = true;
568 + ['default:desert_stone'] = true;
569 + ['default:dirt'] = true;
570 + ['default:gravel'] = true;
571 + }
572 + if allowed[minetest.get_node(ctx.target.under).name] ~= true then
573 + return false
574 + end
575 + local timeline,sounds = {}, {}
576 + local tunnel_depth = math.random(5,9) * ctx.stats.power
577 + local cname = ctx.caster:get_player_name()
578 + local cut = function(step,s,te,tl)
579 + local smash = function(pos)
580 + if not allowed[minetest.get_node(pos).name] then return end
581 + if minetest.is_protected(pos, cname) then return end
582 + s.visual {
583 + amount = math.random(32,48), time = 0.2, glow = 14;
584 + texture = sorcery.lib.image('sorcery_spark.png'):glow(sorcery.lib.color(10,20,255)):render();
585 + minpos = vector.subtract(pos, {x=0.5,y=0.5,z=0.5});
586 + maxpos = vector.add (pos, {x=0.5,y=0.5,z=0.5});
587 + minvel = {x = -0.3, y = -0.3, z = -0.3};
588 + maxvel = {x = 0.3, y = 0.3, z = 0.3};
589 + minacc = {x = -0.6, y = -0.6, z = -0.6};
590 + maxacc = {x = 0.6, y = 0.6, z = 0.6};
591 + minexptime = 0.4, maxexptime = 1.2;
592 + minsize = 0.3, maxsize = 1.2;
593 + animation = {
594 + type = 'vertical_frames', length = 1.3;
595 + aspect_w = 16, aspect_h = 16;
596 + };
597 + }
598 + minetest.dig_node(pos)
599 + if math.random(5) == 1 then
600 + minetest.set_node(pos, {name='sorcery:air_flash_' .. tostring(math.random(10))})
601 + end
602 + -- TODO visuals
603 + end
604 + local r = s.tunnel_radius
605 + local yaw = {x=0,y=s.tunnel_angle,z=0}
606 + s.visual {
607 + amount = 16, time = 3, glow = 14;
608 + texture = sorcery.lib.image('sorcery_sparking.png'):glow(sorcery.lib.color(20,60,255)):render();
609 + minpos = vector.subtract(s.anchor, {x=r,y=r,z=r});
610 + maxpos = vector.add (s.anchor, {x=r,y=r,z=r});
611 + minvel = {x = -0.1, y = -0.1, z = -0.1};
612 + maxvel = {x = 0.1, y = 0.1, z = 0.1};
613 + minexptime = 1.0, maxexptime = 1.4;
614 + minsize = 1.5, maxsize = 4;
615 + animation = {
616 + type = 'vertical_frames', length = 1.5;
617 + aspect_w = 64, aspect_h = 64;
618 + };
619 + }
620 + for x=-r,r do for y=-r,r do
621 + local xs = x < 0 and -1 or 1
622 + local ys = y < 0 and -1 or 1
623 + if x^2 + y^2 <= r^2 then
624 + if (x+xs)^2 + y^2 > r^2 or
625 + (y+ys)^2 + x^2 > r^2 then
626 + -- we're right at the edge - make a mess
627 + if math.random(5) == 1 then goto skip end
628 + end
629 + local p = vector.add(s.anchor,vector.rotate({x=x,y=y,z=0},yaw))
630 + smash(p)
631 + end
632 + ::skip::end end
633 + -- if math.random(1,10) == 1 then
634 + -- s.tunnel_angle = s.tunnel_angle + math.random(-0.05,0.05)
635 + -- yaw.y = s.tunnel_angle
636 + -- end
637 + if math.random(1,21) == 1 then
638 + s.tunnel_radius = math.min(6,math.max(3,s.tunnel_radius + math.random(-1,1)))
639 + end
640 + local dir = vector.rotate({x=0,y=0,z=1},yaw)
641 + if sorcery.lib.math.vdcomp(1, dir) < 1 then
642 + dir = vector.normalize(dir)
643 + end
644 + s.anchor = vector.add(s.anchor,dir)
645 + end
646 + local tp = 0
647 + for i=1,tunnel_depth do
648 + local now = {whence=0,secs=tp}
649 + timeline[now] = function(...) cut(i,...) end
650 + sounds[now] = {
651 + sound='sorcery_crunch', where='pos';
652 + ephemeral=true, gain = math.random(3,10) * 0.1;
653 + }
654 + tp = tp + (math.random(2,5) * 0.1)
655 + end
656 + sounds[1] = {sound='sorcery_powerdown', where='pos'}
657 + sorcery.spell.cast {
658 + caster = ctx.caster;
659 + duration = tp;
660 + timeline = timeline, sounds = sounds;
661 + -- spell state
662 + anchor = ctx.target.under;
663 + tunnel_angle = ctx.caster:get_look_horizontal();
664 + tunnel_radius = math.floor(math.random(3,5) * (ctx.stats.power * 0.1));
665 + }
666 + end;
420 667 };
421 668 emerald = {
422 669 name = 'Boring';
423 670 desc = 'Release the force of this amulet to punch a deep borehole down into the earth below';
424 - }
671 + };
672 + amethyst = {
673 + name = 'Shaftcutting';
674 + desc = 'Cut a wide shaft up into the ceiling of a cavern';
675 + };
425 676 };
426 677 };
427 678 genesis = {
428 679 name = 'Genesis';
429 680 tone = {235,0,175};
430 681 minpower = 5;
431 - rarity = 50;
682 + rarity = 25;
432 683 amulets = {
433 684 mese = {
685 + mingrade = 4;
434 686 name = 'Duplication';
435 687 desc = 'Generate a copy of any object or item, no matter how common or rare';
436 688 };
437 689 };
438 690 };
439 691 luminate = {
440 692 name = 'Luminate';
441 693 tone = {255,194,0};
442 694 minpower = 1;
443 - rarity = 25;
695 + rarity = 5;
444 696 amulets = {
445 697 luxite = {
446 698 name = 'Glow';
447 699 desc = 'Swathe yourself in an aura of sparkling radiance, casting light upon all the dark places where you voyage';
448 700 };
449 701 diamond = {
450 702 name = 'Radiance';
................................................................................
459 711 };
460 712 };
461 713 };
462 714 dominate = {
463 715 name = 'Dominate';
464 716 tone = {235,0,228};
465 717 minpower = 4;
466 - rarity = 40;
718 + rarity = 20;
467 719 amulets = {
468 720 amethyst = {
469 721 name = 'Suffocation';
470 722 desc = 'Wrap this spell tightly around your victim\'s throat, cutting off their oxygen until you release them.';
471 723 };
472 724 emerald = {
473 725 name = 'Caging';
474 726 desc = 'Trap your victim in an impenetrable field of force, leaving them with no way out but translocation or waiting for the field to release them';
475 727 };
728 + luxite = {
729 + name = 'Vampiric Aura';
730 + desc = 'For a time, anyone who approaches you, whether friend or foe, will suffer immediate retaliation as they are quickly sapped of their vital force in order to replenish your own';
731 + };
476 732 ruby = {
477 733 name = 'Exsanguination';
478 734 desc = 'Rip the life force out of another, leaving them on the brink of death, and use it to mend your own wounds and invigorate your being';
479 735 cast = function(ctx)
480 736 if not (ctx.target and ctx.target.type == 'object') then return false end
481 737 local tgt = ctx.target.ref
482 738 local takefac = math.min(99,50 + (ctx.stats.power * 5)) / 100
483 739 local dmg = tgt:get_hp() * takefac
484 - print("!!! dmg calc",takefac,dmg,tgt:get_hp())
485 740
486 741 local numhits = math.random(6,10+ctx.stats.power/2)
487 742 local function dohit(hitsleft)
488 743 if tgt == nil or tgt:get_properties() == nil then return end
489 744 tgt:punch(ctx.caster, 1, {
490 745 full_punch_interval = 1;
491 746 damage_groups = { fleshy = dmg / numhits }