-- a rune is an abstract object created by a runeforge, which can be
-- applied to an amulet in order to imbue that amulet with unique
-- and fearsome powers. the specific spell depends on the stone the
-- rune is applied to, and not all runes can necessarily be applied
-- to all stones.
local sparkle_region = function(s)
s.spell.visual_subjects {
amount = s.amt, time = s.time, -- attached = s;
minpos = s.minpos;
maxpos = s.maxpos;
minvel = { x = -0.4, y = -0.2, z = -0.4 };
maxvel = { x = 0.4, y = 0.2, z = 0.4 };
minacc = { x = -0.5, y = -0.4, z = -0.5 };
maxacc = { x = 0.5, y = 0.4, z = 0.5 };
minexptime = 1.0*(s.length or 1), maxexptime = 2.0 * (s.length or 1);
minsize = s.minsize, maxsize = s.maxsize, glow = 14;
texture = (s.img or sorcery.vfx.glowspark(s.color)):render();
animation = {
type = 'vertical_frames';
aspect_w = 16, aspect_h = 16;
length = 0.1 + (s.length or 1)*2;
};
}
end
local sparkle = function(color, spell, amt,time,minsize,maxsize,sh)
sparkle_region { spell = spell;
amt = amt, time = time, color = color;
minsize = minsize, maxsize = maxsize;
minpos = { x = -0.3, y = -0.5, z = -0.3 };
maxpos = { x = 0.3, y = sh*1.1, z = 0.3 };
}
end
local sparktrail = function(fn,tgt,color,time)
return (fn or minetest.add_particlespawner)({
amount = 240, time = time or 1, attached = tgt;
minpos = {x = -0.4, y = -0.5, z = -0.4};
maxpos = {x = 0.4, y = tgt:get_properties().eye_height or 0.5, z = 0.4};
minacc = {x = 0.0, y = 0.05, z = 0.0};
maxacc = {x = 0.0, y = 0.15, z = 0.0};
minexptime = 1.5, maxexptime = 5;
minsize = 0.5, maxsize = 2.6, glow = 14;
texture = sorcery.vfx.glowspark(color):render();
animation = {
type = 'vertical_frames', length = 5.1;
aspect_w = 16, aspect_h = 16;
};
});
end
return {
translocate = {
name = 'Translocate';
tone = {0,235,233};
minpower = 3;
rarity = 10;
amulets = {
amethyst = {
name = 'Joining';
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';
apply = function(ctx)
local maker = ctx.user:get_player_name()
ctx.meta:set_string('rune_join_target',maker)
end;
remove = function(ctx) ctx.meta:set_string('rune_join_target','') end;
frame = {
gold = {
name = 'Exchange';
desc = 'Give this amulet to another and they will be able to trade places with you no matter where in the world each of you might be.';
};
cobalt = {
name = 'Sending';
desc = 'Give this amulet to another and by wielding this amulet against another they will be able to transport them instantly to your side';
};
iridium = {
name = 'Arrival';
desc = "Give this amulet to another and they will be able to arrive at your side in a flash from anywhere in the world, carrying others with them in the spell's grip";
};
};
};
sapphire = {
name = 'Return';
desc = 'Use this amulet once to bind it to a particular place, then discharge its spell to translocate yourself back to that point from anywhere in the world.';
remove = function(ctx)
ctx.meta:set_string('rune_return_dest','')
end;
cast = function(ctx)
if not ctx.meta:contains('rune_return_dest') then
local pos = ctx.caster:get_pos()
ctx.meta:set_string('rune_return_dest',minetest.pos_to_string(pos))
return true -- play effects but do not break spell
else
local pos = minetest.string_to_pos(ctx.meta:get_string('rune_return_dest'))
ctx.meta:set_string('rune_return_dest','')
local subjects = { ctx.caster }
local center = ctx.caster:get_pos()
ctx.sparkle = false
local delay = math.max(3,10 - ctx.stats.power) + 3*(math.random()*2-1)
if ctx.amulet.frame == 'tungsten' then delay = delay * 0.5 end
for _,s in pairs(subjects) do
local offset = vector.subtract(s:get_pos(), center)
local pt = sorcery.lib.node.get_arrival_point(vector.add(pos,offset))
if pt then
-- minetest.sound_play('sorcery_stutter', {
-- object = s, gain = 0.8;
-- },true)
local mydelay = delay + math.random(-10,10)*.1;
local sh = s:get_properties().eye_height
local color = sorcery.lib.color(29,205,247)
sorcery.lib.node.preload(pt,s)
sorcery.spell.cast {
duration = mydelay;
caster = ctx.caster;
subjects = {{player=s,dest=pt}};
timeline = {
[0] = function(sp,_,timeleft)
sparkle(color,sp,timeleft*100, timeleft, 0.3,1.3, sh)
sp.windup = (sp.play_now{
sound = 'sorcery_windup';
where = 'subjects';
gain = 0.4;
fade = 1.5;
})[1]
end;
[0.4] = function(sp,_,timeleft)
sparkle(color,sp,timeleft*150, timeleft, 0.6,1.8, sh)
end;
[0.7] = function(sp,_,timeleft)
sparkle(color,sp,timeleft*80, timeleft, 2,4, sh)
end;
[1] = function(sp)
sp.silence(sp.windup)
minetest.sound_play('sorcery_zap', { pos = pt, gain = 0.4 },true)
minetest.sound_play('sorcery_zap', { pos = s:get_pos(), gain = 0.4 },true)
sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,s:get_pos())
s:set_pos(pt)
sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2)
end;
};
sounds = {
[0] = { sound = 'sorcery_stutter', pos = 'subjects' };
};
}
end
end
end
end;
frame = {
tungsten = {
name = 'Quick Return';
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.';
};
iridium = {
name = 'Mass Return';
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';
};
};
};
emerald = {
name = 'Banishment';
desc = 'Use this amulet once to bind it to a particular point in the world, then wield it against a foe to whisk them away immediately to your chosen prison';
frame = {
iridium = {
name = 'Mass Banishment';
desc = 'Use this amulet once to bind it to a particular point in the world, then use it again to seize up everyone surrounding you in the grip of a fearsome magic that will deport them all in the blink of an eye to whatever destination you have chosen';
};
};
};
ruby = minetest.get_modpath('beds') and {
name = 'Escape';
desc = 'Immediately transport yourself out of a dangerous situation back to the last place you slept';
cast = function(ctx)
-- if not beds.spawns then beds.read_spawns() end
local subjects = {ctx.caster}
for _,s in pairs(subjects) do
local spp = beds.spawn[ctx.caster:get_player_name()]
if spp then
local oldpos = s:get_pos()
minetest.sound_play('sorcery_splunch', {pos=oldpos}, true)
sorcery.vfx.body_sparkle(nil,sorcery.lib.color(244,38,131),2,oldpos)
s:set_pos(spp)
minetest.sound_play('sorcery_splunch', {pos=spp}, true)
sorcery.vfx.body_sparkle(nil,sorcery.lib.color(244,38,89),2,spp)
end
-- TODO decide what happens to the people who don't have
-- respawn points already set
end
end;
frame = {
cobalt = {
name = 'Vengeful Exit';
desc = 'Translocate away to the safety of your boudoir with a fearsome blast of dangerous radiance that will send bodies flying and deal heavy damage to those nearby';
};
iridium = {
name = 'Mass Escape';
desc = 'Break up even the fiercest of quarrels by transporting yourself and everyone around you out of harms\' way and immediately back to the last place each slept';
};
};
};
diamond = {
name = 'Elevation';
desc = 'Lift yourself and everything around you high up into the sky';
};
};
};
disjoin = {
name = 'Disjoin';
tone = {159,235,0};
minpower = 4;
rarity = 40;
amulets = {
sapphire = {
name = 'Unsealing';
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';
};
amethyst = {
name = 'Purging';
desc = 'Free yourself from the grip of any malicious spellwork with a snap of your fingers — interrupting all of your own active spells in the process, including impending translocations';
cast = function(ctx)
local h = ctx.heading.eyeheight * 1.1
minetest.add_particlespawner {
time = 0.2, amount = math.random(200,250), attached = ctx.caster;
glow = 14, texture = sorcery.vfx.glowspark(sorcery.lib.color(156,255,10)):render();
minpos = {x = -0.3, y = -0.5, z = -0.3};
maxpos = {x = 0.3, y = h, z = 0.3};
minvel = {x = -1.8, y = -1.8, z = -1.8};
maxvel = {x = 1.8, y = 1.8, z = 1.8};
minsize = 0.2, maxsize = 5;
animation = {
type = 'vertical_frames', length = 4.1;
aspect_w = 16, aspect_h = 16;
};
minexptime = 2, maxexptime = 4;
}
minetest.sound_play('sorcery_disjoin',{object=ctx.caster},true)
sorcery.spell.disjoin{target=ctx.caster}
end;
};
emerald = {
name = 'Disjunction Field';
desc = 'Render an area totally opaque to spellwork for a period of time, disrupting any existing spells and preventing further spellcasting therein';
};
ruby = {
name = 'Disjunction';
desc = 'Wield this amulet against a spellcaster to disrupt and abort all their spells in progress, perhaps to trap a foe intent on translocating away, or unleash its force upon the victim of a malign hex to free them from its clutches';
frame = {
iridium = {
name = 'Nullification';
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';
};
};
};
luxite = {
name = 'Disjunctive Aura';
desc = 'For a time, all magic undertaken in your vicinity will fail totally — including your own';
cast = function(ctx)
local h = ctx.heading.eyeheight*1.1
sorcery.spell.cast {
caster = ctx.caster, attach = 'caster';
subjects = {{player=ctx.caster}};
disjunction = true, range = 4 + ctx.stats.power;
duration = 10 + ctx.stats.power * 3;
timeline = {
[0] = function(s,_,tl)
local ttns = 0.8
local vel = s.range / ttns
s.visual_caster {
amount = 300, time = ttns, glow = 14;
texture = sorcery.lib.image('sorcery_sputter.png'):glow(sorcery.lib.color(160,255,80)):render();
minpos = { x = -0.0, y = h*0.5,z = -0.0 };
maxpos = { x = 0.0, y = h*0.5,z = 0.0 };
minvel = { x = -vel, y = -0.0, z = -vel };
maxvel = { x = vel, y = 0.0, z = vel };
minacc = { x = -0.2, y = -0.0, z = -0.2 };
maxacc = { x = 0.2, y = 0.0, z = 0.2 };
minexptime = ttns, maxexptime = ttns * 2;
minsize = 0.2, maxsize = 4.5;
animation = {
type = 'vertical_frames', length = 0.1 + ttns*2;
aspect_w = 16, aspect_h = 16;
}
}
end;
[{whence=0,secs=0.8}] = function(s,te,tl)
local range = s.range
sparkle_region {
spell = s, amt = 150*tl, time = tl;
minsize = 1, maxsize = 8.4;
minpos = { x = 0-range, y = -0.5, z = 0-range };
maxpos = { x = range, y = h, z = range };
img = sorcery.lib.image('sorcery_flicker.png'):glow(sorcery.lib.color(120,255,30));
}
end;
};
sounds = {
[0.00] = {sound='sorcery_disjoin', where='caster'};
[{whence=0,secs=0.8}] = {
sound='sorcery_disjoin_bg', where='subjects';
gain=0.5, stop = {whence=1,secs=-1.5}
};
[1.00] = {sound='sorcery_powerdown', where='caster'};
};
}
end
};
diamond = {
name = 'Mundanity';
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';
frame = {
iridium = {
name = 'Spellshatter';
desc = 'Blast out a tidal wave of anti-magic that will nullify active spells, but also disenchant or destroy all magical items in range of its violently mundane grip';
};
};
};
}
};
repulse = {
name = 'Repulse';
tone = {0,180,235};
minpower = 1;
rarity = 5;
amulets = {
amethyst = {
name = 'Hurling';
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';
cast = function(ctx)
if not (ctx.target and ctx.target.type == 'object') then return false end
local tgt = ctx.target.ref
local line = vector.subtract(ctx.caster:get_pos(), tgt:get_pos())
-- direction vector from target to caster
local dir,mag = sorcery.lib.math.vsep(line)
if mag > 6 then return false end -- no cheating!
local force = 20 + (ctx.stats.power * 2.5)
minetest.sound_play('sorcery_hurl',{pos=tgt:get_pos()},true)
local immortal = tgt:get_luaentity():get_armor_groups().immortal or 0
if minetest.is_player(tgt) or immortal == 0 then
tgt:punch(ctx.caster, 1, {
full_punch_interval = 1;
damage_groups = { fleshy = force / 10 };
})
end
sparktrail(nil,tgt,sorcery.lib.color(101,226,255))
if dir.y > 0 then dir.y = 0 end -- spell always lifts
dir = vector.add(dir, {x=0,z=0,y=-0.25})
local vel = vector.multiply(dir,0-force)
tgt:add_velocity(vel)
end;
};
ruby = {
name = 'Liftoff';
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';
cast = function(ctx)
local power = 14 * (1+(ctx.stats.power * 0.2))
minetest.sound_play('sorcery_hurl',{object=ctx.caster},true)
sorcery.spell.cast {
caster = ctx.caster;
subjects = {{player=ctx.caster}};
duration = power * 0.30;
timeline = {
[0] = function(s,_,tl)
sparktrail(s.visual_subjects,ctx.caster,sorcery.lib.color(255,252,93))
ctx.caster:add_velocity{y=power;x=0,z=0}
s.affect {
duration = power * 0.50;
raise = 2;
-- fall = (power * 0.25) * 0.3;
impacts = {
gravity = 0.1;
};
}
end;
};
intervals = {
{period = 0.2, after = {whence=0, secs=2}; fn = function(c)
-- return gravity to normal once they touch down
for si,sub in pairs(c.spell.subjects) do
local p = sub.player:get_pos()
for i=1,3 do
local sum = vector.offset(p,0,-i,0)
if not sorcery.lib.node.is_air(sum) then
c.spell.release_subject(si)
if #c.spell.subjects == 0 then
return false
end
break
end
end
end
end};
};
}
end;
};
sapphire = {
name = 'Flinging';
desc = 'Toss an enemy violently into the air, and allow the inevitable impact to do your dirty work for you';
cast = function(ctx)
if not (ctx.target and ctx.target.type == 'object') then return false end
local tgt = ctx.target.ref
local power = 16 * (1+(ctx.stats.power * 0.2))
minetest.sound_play('sorcery_hurl',{object=ctx.caster},true)
sorcery.spell.cast {
caster = ctx.caster;
subjects = {{player=tgt}};
duration = 4;
timeline = {
[0] = function(s,_,tl)
for _,sub in pairs(s.subjects) do
local height = (sub.player:get_properties().eye_height or 1)*1.3
local scenter = vector.add(sub.player:get_pos(), {x=0,y=height/2,z=0})
for i=1,math.random(64,128) do
local high = (height+0.8)*math.random() - 0.8
local far = (high >= -0.5 and high <= height) and
(math.random() * 0.3 + 0.4) or
(math.random() * 0.5)
local yaw = {x=0, y = math.random()*100, z=0}
local po = vector.rotate({x=far,y=high,z=0}, yaw)
local ppos = vector.add(po,sub.player:get_pos())
local dir = vector.direction(ppos,scenter)
local vel = math.random() * 0.8 + 0.4
minetest.add_particle {
pos = ppos;
velocity = vector.multiply(dir,vel);
expirationtime = far / vel;
size = math.random()*2.4 + 0.6;
texture = sorcery.lib.image('sorcery_sputter.png'):glow(sorcery.lib.color{
hue = math.random(41,63);
saturation = 100;
luminosity = 0.5 + math.random()*0.3;
}):render();
glow = 14;
animation = {
type = 'vertical_frames', length = far/vel;
aspect_w = 16, aspect_h = 16;
};
}
end
end
end;
[0.3] = function(s,te,tl)
sparktrail(s.visual_subjects,ctx.caster,sorcery.lib.color(255,252,93))
for _,sub in pairs(s.subjects) do
sub.player:add_velocity{y=power;x=0,z=0}
end
end;
[1] = (ctx.amulet.frame == 'cobalt') and function(s,te,tl)
-- TODO add visuals
for _,sub in pairs(s.subjects) do
sub.player:add_velocity{y=-power*2;x=0,z=0}
end
end or nil;
};
sounds = {
[0.3] = {
sound = 'sorcery_hurl';
where = 'subjects';
ephemeral = true;
};
[1] = (ctx.amulet.frame == 'cobalt') and {
sound = 'sorcery_hurl';
where = 'subjects';
ephemeral = true;
} or nil;
};
};
end;
frame = {
cobalt = {
name = 'Crushing';
desc = 'Toss an enemy violently into the air, then bring them crashing down to earth with bone-shattering force';
};
iridium = {
name = 'Mass Flinging';
desc = 'Send everyone around you hurtling into the sky, and allow the inevitable impact to do your dirty work for you';
};
};
};
emerald = {
name = 'Shockwave';
desc = 'Let loose a stream of concussive force that slams into everything in your path and sends them hurtling away from you';
};
luxite = {
name = 'Repulsive Aura';
desc = 'For a period of time, anyone who approaches you will be violently thrust aside';
};
diamond = {
name = 'Blastwave';
desc = 'Unleash a tidal wave of force in every direction, blasting friends and foes alike away from you with enough violence to sprain and fracture bone';
};
};
};
obliterate = {
name = 'Obliterate';
tone = {255,0,10};
minpower = 5;
rarity = 35;
amulets = {
amethyst = {
name = 'Sapping';
desc = 'Punch a hole in enemy fortifications big enough to slip through but small enough to avoid immediate attention';
};
ruby = {
name = 'Shattering';
desc = 'Tear a violent wound in the land with the destructive force of this amulet';
};
emerald = {
name = 'Detonate';
desc = 'Wielding this amulet, you can loose an extraordinarily powerful bolt of flame from your fingertips that will explode violently on impact, wreaking total havoc wherever it lands';
cast = function(ctx)
local speed = 40
local radius = math.random(math.floor(ctx.stats.power*0.5),math.ceil(ctx.stats.power))
local heading = ctx.heading
heading.pos.y = heading.pos.y + heading.eyeheight*0.9
local vel = vector.multiply(heading.yaw,speed)
local bolt = minetest.add_entity(vector.add(heading.pos,vector.multiply(heading.yaw,2.5)),'sorcery:spell_projectile_flamebolt')
bolt:set_rotation(heading.yaw)
bolt:get_luaentity()._blastradius = radius
bolt:set_velocity(vel)
end;
};
luxite = {
name = 'Cataclysmic Aura';
desc = 'A storm of destructive force rages about you as you stand untouched, the master of its voracious dark energies';
};
mese = {
name = 'Cataclysm';
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';
};
diamond = {
name = 'Killing';
mingrade = 4;
desc = 'Wield this amulet against a foe to instantly snuff the life out of their mortal form, regardless of their physical protections.';
cast = function(ctx)
if not (ctx.target and ctx.target.type == 'object') then return false end
local tgt = ctx.target.ref
if not minetest.is_player(obj) then return false end
local tgth = tgt:get_properties().eye_height
sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),20)
minetest.sound_play('sorcery_bloody_burst', { pos = pos, gain = 1.5 })
tgt:set_hp(0)
end;
frame = {
iridium = {
name = 'Massacre';
desc = "Unleash the dark and wicked force that lurks within this fell amulet to instantaneously slay all those who surround you, friend and foe alike";
};
};
};
};
};
excavate = {
name = 'Excavate';
tone = {0,68,235};
minpower = 3;
rarity = 30;
amulets = {
luxite = {
name = 'Stonestride';
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';
};
sapphire = {
name = 'Tunnelling';
desc = 'Carve a long tunnel ahead of you into the rock and dirt';
cast = function(ctx)
if ctx.target.type ~= 'node' then return false end
local allowed = {
['default:stone'] = true;
['default:desert_stone'] = true;
['default:dirt'] = true;
['default:gravel'] = true;
}
if allowed[minetest.get_node(ctx.target.under).name] ~= true then
return false
end
local timeline,sounds = {}, {}
local tunnel_depth = math.random(5,9) * ctx.stats.power
local cname = ctx.caster:get_player_name()
local cut = function(step,s,te,tl)
local smash = function(pos)
if not allowed[minetest.get_node(pos).name] then return end
if minetest.is_protected(pos, cname) then return end
s.visual {
amount = math.random(32,48), time = 0.2, glow = 14;
texture = sorcery.lib.image('sorcery_spark.png'):glow(sorcery.lib.color(10,20,255)):render();
minpos = vector.subtract(pos, {x=0.5,y=0.5,z=0.5});
maxpos = vector.add (pos, {x=0.5,y=0.5,z=0.5});
minvel = {x = -0.3, y = -0.3, z = -0.3};
maxvel = {x = 0.3, y = 0.3, z = 0.3};
minacc = {x = -0.6, y = -0.6, z = -0.6};
maxacc = {x = 0.6, y = 0.6, z = 0.6};
minexptime = 0.4, maxexptime = 1.2;
minsize = 0.3, maxsize = 1.2;
animation = {
type = 'vertical_frames', length = 1.3;
aspect_w = 16, aspect_h = 16;
};
}
minetest.dig_node(pos)
if math.random(5) == 1 then
minetest.set_node(pos, {name='sorcery:air_flash_' .. tostring(math.random(10))})
end
-- TODO visuals
end
local r = s.tunnel_radius
local yaw = {x=0,y=s.tunnel_angle,z=0}
s.visual {
amount = 16, time = 3, glow = 14;
texture = sorcery.lib.image('sorcery_sparking.png'):glow(sorcery.lib.color(20,60,255)):render();
minpos = vector.subtract(s.anchor, {x=r,y=r,z=r});
maxpos = vector.add (s.anchor, {x=r,y=r,z=r});
minvel = {x = -0.1, y = -0.1, z = -0.1};
maxvel = {x = 0.1, y = 0.1, z = 0.1};
minexptime = 1.0, maxexptime = 1.4;
minsize = 1.5, maxsize = 4;
animation = {
type = 'vertical_frames', length = 1.5;
aspect_w = 64, aspect_h = 64;
};
}
for x=-r,r do for y=-r,r do
local xs = x < 0 and -1 or 1
local ys = y < 0 and -1 or 1
if x^2 + y^2 <= r^2 then
if (x+xs)^2 + y^2 > r^2 or
(y+ys)^2 + x^2 > r^2 then
-- we're right at the edge - make a mess
if math.random(5) == 1 then goto skip end
end
local p = vector.add(s.anchor,vector.rotate({x=x,y=y,z=0},yaw))
smash(p)
end
::skip::end end
-- if math.random(1,10) == 1 then
-- s.tunnel_angle = s.tunnel_angle + math.random(-0.05,0.05)
-- yaw.y = s.tunnel_angle
-- end
if math.random(1,21) == 1 then
s.tunnel_radius = math.min(6,math.max(3,s.tunnel_radius + math.random(-1,1)))
end
local dir = vector.rotate({x=0,y=0,z=1},yaw)
if sorcery.lib.math.vdcomp(1, dir) < 1 then
dir = vector.normalize(dir)
end
s.anchor = vector.add(s.anchor,dir)
end
local tp = 0
for i=1,tunnel_depth do
local now = {whence=0,secs=tp}
timeline[now] = function(...) cut(i,...) end
sounds[now] = {
sound='sorcery_crunch', where='pos';
ephemeral=true, gain = math.random(3,10) * 0.1;
}
tp = tp + (math.random(2,5) * 0.1)
end
sounds[1] = {sound='sorcery_powerdown', where='pos'}
sorcery.spell.cast {
caster = ctx.caster;
duration = tp;
timeline = timeline, sounds = sounds;
-- spell state
anchor = ctx.target.under;
tunnel_angle = ctx.caster:get_look_horizontal();
tunnel_radius = math.floor(math.random(3,5) * (ctx.stats.power * 0.1));
}
end;
};
emerald = {
name = 'Boring';
desc = 'Release the force of this amulet to punch a deep borehole down into the earth below';
};
amethyst = {
name = 'Shaftcutting';
desc = 'Cut a wide shaft up into the ceiling of a cavern';
};
};
};
genesis = {
name = 'Genesis';
tone = {235,0,175};
minpower = 5;
rarity = 25;
amulets = {
mese = {
mingrade = 4;
name = 'Duplication';
desc = 'Generate a copy of any object or item, no matter how common or rare';
};
};
};
luminate = {
name = 'Luminate';
tone = {255,194,0};
minpower = 1;
rarity = 5;
amulets = {
luxite = {
name = 'Glow';
desc = 'Swathe yourself in an aura of sparkling radiance, casting light upon all the dark places where you voyage';
};
diamond = {
name = 'Radiance';
desc = 'Set the air around you alight with a mystic luminance, letting you see clearly a great distance in every direction for several minutes';
frame = {
iridium = {
name = 'Sunshine';
mingrade = 5;
desc = 'Unleash the power of this amulet to seize ultimate control over the forces of nature and summon the Sun high into the nighttime sky';
};
};
};
};
};
dominate = {
name = 'Dominate';
tone = {235,0,228};
minpower = 4;
rarity = 20;
amulets = {
amethyst = {
name = 'Suffocation';
desc = 'Wrap this spell tightly around your victim\'s throat, cutting off their oxygen until you release them.';
};
emerald = {
name = 'Caging';
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';
};
luxite = {
name = 'Vampiric Aura';
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';
};
ruby = {
name = 'Exsanguination';
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';
cast = function(ctx)
if not (ctx.target and ctx.target.type == 'object') then return false end
local tgt = ctx.target.ref
local takefac = math.min(99,50 + (ctx.stats.power * 5)) / 100
local dmg = tgt:get_hp() * takefac
local numhits = math.random(6,10+ctx.stats.power/2)
local function dohit(hitsleft)
if tgt == nil or tgt:get_properties() == nil then return end
tgt:punch(ctx.caster, 1, {
full_punch_interval = 1;
damage_groups = { fleshy = dmg / numhits }
})
local tgth = tgt:get_properties().eye_height
sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),math.random(10 * takefac, 40 * takefac))
ctx.caster:set_hp(ctx.caster:get_hp() + math.max(1,(dmg/numhits)*takefac))
local sound = {'sorcery_bloody_hit','sorcery_crunch',false}
sound = sound[math.random(#sound)]
if sound ~= false then
minetest.sound_play(sound, { pos = pos, gain = math.random(5,15)*0.1 })
end
local nexthit = math.random() * 0.4 + 0.1
local dir = vector.subtract(ctx.caster:get_pos(), tgt:get_pos())
local spark = sorcery.lib.image('sorcery_spark.png')
minetest.add_particlespawner {
amount = math.random(80*takefac,150*takefac);
texture = spark:blit(spark:multiply(sorcery.lib.color(255,20,10))):render();
time = nexthit;
attached = tgt;
minpos = {x = -0.3, y = -0.5, z = -0.3};
maxpos = {x = 0.3, y = tgth, z = 0.3};
minvel = vector.multiply(dir,0.5);
maxvel = vector.multiply(dir,0.9);
minacc = vector.multiply(dir,0.1);
maxacc = vector.multiply(dir,0.2);
minexptime = nexthit * 1.5;
maxexptime = nexthit * 2;
minsize = 0.5;
maxsize = 5 * takefac;
glow = 14;
animation = {
type = 'vertical_frames';
aspect_w = 16, aspect_h = 16;
length = nexthit*2 + 0.1;
};
}
if hitsleft > 0 then
minetest.after(nexthit, function() dohit(hitsleft-1) end)
end
end
dohit(numhits)
end;
};
amethyst = {
name = 'Disarming';
desc = 'Wield this amulet against a foe to rip all the weapons in their possession out of their grasp';
frame = {
iridium = {
name = 'Peacemaking';
desc = 'Confiscate all weapons held by those surrounding you';
};
};
};
};
};
}