sorcery  Diff

Differences From Artifact [47aa367750]:

To Artifact [4908250df2]:


     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 };
           12  +		minvel = { x = -0.4, y = -0.2, z = -0.4 };
           13  +		maxvel = { x =  0.4, y =  0.2, z =  0.4 };
           14  +		minacc = { x = -0.5, y = -0.4, z = -0.5 };
           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();
           19  +		animation = {
           20  +			type = 'vertical_frames';
           21  +			aspect_w = 16, aspect_h = 16;
           22  +		};
           23  +	}
           24  +end
           25  +local sparktrail = function(fn,tgt,color)
           26  +	return (fn or minetest.add_particlespawner)({
           27  +		amount = 240, time = 1, attached = tgt;
           28  +		minpos = {x = -0.4, y = -0.5, z = -0.4};
           29  +		maxpos = {x =  0.4, y = tgt:get_properties().eye_height or 0.5, z =  0.4};
           30  +		minacc = {x =  0.0, y = 0.05, z =  0.0};
           31  +		maxacc = {x =  0.0, y = 0.15, z =  0.0};
           32  +		minexptime = 1.5, maxexptime = 5;
           33  +		minsize = 0.5, maxsize = 2.6, glow = 14;
           34  +		texture = sorcery.vfx.glowspark(color):render();
           35  +		animation = {
           36  +			type = 'vertical_frames', length = 5.1;
           37  +			aspect_w = 16, aspect_h = 16;
           38  +		};
           39  +	});
           40  +end
     7     41   return {
     8     42   	translocate = {
     9     43   		name = 'Translocate';
    10     44   		tone = {0,235,233};
    11     45   		minpower = 3;
    12     46   		rarity = 15;
    13     47   		amulets = {
................................................................................
    48     82   					else
    49     83   						local pos = minetest.string_to_pos(ctx.meta:get_string('rune_return_dest'))
    50     84   						ctx.meta:set_string('rune_return_dest','')
    51     85   						local subjects = { ctx.caster }
    52     86   						local center = ctx.caster:get_pos()
    53     87   						ctx.sparkle = false
    54     88   						local delay = math.max(3,10 - ctx.stats.power) + 3*(math.random()*2-1)
    55         -						print('teledelay',delay,ctx.stats.power)
    56     89   						for _,s in pairs(subjects) do
    57     90   							local offset = vector.subtract(s:get_pos(), center)
    58     91   							local pt = sorcery.lib.node.get_arrival_point(vector.add(pos,offset))
    59     92   							if pt then
    60         -								minetest.sound_play('sorcery_stutter', {
    61         -									object = s, gain = 0.8;
    62         -								},true)
    63         -								local windup = minetest.sound_play('sorcery_windup',{
    64         -									object = s, gain = 0.4;
    65         -								})
           93  +								-- minetest.sound_play('sorcery_stutter', {
           94  +								-- 	object = s, gain = 0.8;
           95  +								-- },true)
    66     96   								local mydelay = delay + math.random(-10,10)*.1;
    67         -								local spark = sorcery.lib.image('sorcery_spark.png')
    68     97   								local sh = s:get_properties().eye_height
    69         -								local sparkle = function(amt,time,minsize,maxsize)
    70         -									minetest.add_particlespawner {
    71         -										amount = amt, time = time, attached = s;
    72         -										minpos = { x = -0.3, y = -0.5, z = -0.3 };
    73         -										maxpos = { x =  0.3, y = sh*1.1, z = 0.3 };
    74         -										minvel = { x = -0.4, y = -0.2, z = -0.4 };
    75         -										maxvel = { x =  0.4, y =  0.2, z =  0.4 };
    76         -										minacc = { x = -0.5, y = -0.4, z = -0.5 };
    77         -										maxacc = { x =  0.5, y =  0.4, z =  0.5 };
    78         -										minexptime = 1.0, maxexptime = 2.0;
    79         -										minsize = minsize, maxsize = maxsize, glow = 14;
    80         -										texture = spark:blit(spark:multiply(sorcery.lib.color(29,205,247))):render();
    81         -										animation = {
    82         -											type = 'vertical_frames';
    83         -											aspect_w = 16, aspect_h = 16;
           98  +								local color = sorcery.lib.color(29,205,247)
           99  +								sorcery.lib.node.preload(pt,s)
          100  +								sorcery.spell.cast {
          101  +									duration = mydelay;
          102  +									caster = ctx.caster;
          103  +									subjects = {{player=s,dest=pt}};
          104  +									timeline = {
          105  +										[0] = function(sp,_,timeleft)
          106  +											sparkle(color,sp,timeleft*100, timeleft, 0.3,1.3, sh)
          107  +											sp.windup = (sp.play_now{
          108  +												sound = 'sorcery_windup';
          109  +												where = 'subjects';
          110  +												gain = 0.4;
          111  +												fade = 1.5;
          112  +											})[1]
          113  +										end;
          114  +										[0.4] = function(sp,_,timeleft)
          115  +											sparkle(color,sp,timeleft*150, timeleft, 0.6,1.8, sh)
          116  +										end;
          117  +										[0.7] = function(sp,_,timeleft)
          118  +											sparkle(color,sp,timeleft*80, timeleft, 2,4, sh)
          119  +										end;
          120  +										[1] = function(sp)
          121  +											sp.silence(sp.windup)
          122  +											minetest.sound_play('sorcery_zap', { pos = pt, gain = 0.4 },true)
          123  +											minetest.sound_play('sorcery_zap', { pos = s:get_pos(), gain = 0.4 },true)
          124  +											sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,s:get_pos())
          125  +											s:set_pos(pt)
          126  +											sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2)
          127  +										end;
          128  +									};
          129  +									sounds = {
          130  +										[0] = {
          131  +											pos = 'subjects';
          132  +											sound = 'sorcery_stutter';
    84    133   										};
    85         -									}
    86         -								end
    87         -								sparkle(mydelay*100,mydelay,0.3,1.3)
    88         -								minetest.after(mydelay*0.4, function()
    89         -									local timeleft = mydelay - (mydelay*0.4)
    90         -									sparkle(timeleft*150, timeleft, 0.6,1.8)
    91         -								end)
    92         -								minetest.after(mydelay*0.7, function()
    93         -									local timeleft = mydelay - (mydelay*0.7)
    94         -									sparkle(timeleft*80, timeleft, 2,4)
    95         -								end)
    96         -								sorcery.lib.node.preload(pt,s)
    97         -								minetest.after(mydelay, function()
    98         -									minetest.sound_stop(windup)
    99         -									minetest.sound_play('sorcery_zap', { pos = pt, gain = 0.4 },true)
   100         -									minetest.sound_play('sorcery_zap', { pos = s:get_pos(), gain = 0.4 },true)
   101         -									sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,s:get_pos())
   102         -									s:set_pos(pt)
   103         -									sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2)
   104         -								end)
          134  +									};
          135  +								}
   105    136   							end
   106    137   						end
   107    138   					end
   108    139   				end;
   109    140   				frame = {
   110    141   					iridium = {
   111    142   						name = 'Mass Return';
................................................................................
   162    193   	};
   163    194   	disjoin = {
   164    195   		name = 'Disjoin';
   165    196   		tone = {159,235,0};
   166    197   		minpower = 4;
   167    198   		rarity = 20;
   168    199   		amulets = {
   169         -			amethyst = {
          200  +			sapphire = {
   170    201   				name = 'Unsealing';
   171    202   				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  +			};
          204  +			amethyst = {
          205  +				name = 'Purging';
          206  +				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';
          207  +				cast = function(ctx)
          208  +					local h = ctx.heading.eyeheight * 1.1
          209  +					minetest.add_particlespawner {
          210  +						time = 0.2, amount = math.random(200,250), attached = ctx.caster;
          211  +						glow = 14, texture = sorcery.vfx.glowspark(sorcery.lib.color(156,255,10)):render();
          212  +						minpos = {x = -0.3, y = -0.5, z = -0.3};
          213  +						maxpos = {x =  0.3, y =  h,   z =  0.3};
          214  +						minvel = {x = -1.8, y = -1.8, z = -1.8};
          215  +						maxvel = {x =  1.8, y =  1.8, z =  1.8};
          216  +						minsize = 0.2, maxsize = 5;
          217  +						animation = {
          218  +							type = 'vertical_frames', length = 4.1;
          219  +							aspect_w = 16, aspect_h = 16;
          220  +						};
          221  +						minexptime = 2, maxexptime = 4;
          222  +					}
          223  +					minetest.sound_play('sorcery_disjoin',{object=ctx.caster},true)
          224  +					sorcery.spell.disjoin{target=ctx.caster}
          225  +				end;
   172    226   			};
   173    227   			emerald = {
          228  +				name = 'Disjunction Field';
          229  +				desc = 'Render an area totally opaque to spellwork for a period of time, disrupting any existing spells and preventing further spellcasting therein';
          230  +			};
          231  +			ruby = {
          232  +				name = 'Disjunction';
          233  +				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';
          234  +				frame = {
          235  +					iridium = {
          236  +						name = 'Nullification';
          237  +						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  +					};
          239  +				};
          240  +			};
          241  +			luxite = {
          242  +				name = 'Disjunctive Aura';
          243  +				desc = 'For a time, all magic undertaken in your vicinity will fail totally';
          244  +				cast = function(ctx)
          245  +					sorcery.spell.cast {
          246  +						caster = ctx.caster, attach = 'caster';
          247  +						disjunction = true, range = 4 + ctx.stats.power;
          248  +						duration = 10 + ctx.stats.power * 3;
          249  +						timeline = {
          250  +							[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
          254  +						};
          255  +						sounds = {
          256  +							[0] = { sound = 'sorcery_disjoin',   pos = 'caster' };
          257  +							[1] = { sound = 'sorcery_powerdown', pos = 'caster' };
          258  +						};
          259  +					}
          260  +				end
          261  +			};
          262  +			diamond = {
   174    263   				name = 'Mundanity';
   175    264   				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';
          265  +				frame = {
          266  +					iridium = {
          267  +						name = 'Spellshatter';
          268  +						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';
          269  +					};
          270  +				};
   176    271   			};
   177    272   		}
   178    273   	};
   179    274   	repulse = {
   180    275   		name = 'Repulse';
   181    276   		tone = {0,180,235};
   182    277   		minpower = 1;
   183    278   		rarity = 7;
   184    279   		amulets = {
   185    280   			amethyst = {
   186    281   				name = 'Hurling';
   187    282   				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  +				cast = function(ctx)
          284  +					if not (ctx.target and ctx.target.type == 'object') then return false end
          285  +					local tgt = ctx.target.ref
          286  +					local line = vector.subtract(ctx.caster:get_pos(), tgt:get_pos())
          287  +					-- direction vector from target to caster
          288  +					print('line',dump(line))
          289  +					local dir,mag = sorcery.lib.math.vsep(line)
          290  +					if mag > 6 then return false end -- no cheating!
          291  +					local force = 20 + (ctx.stats.power * 2.5)
          292  +					minetest.sound_play('sorcery_hurl',{pos=tgt:get_pos()},true)
          293  +					local immortal = tgt:get_luaentity():get_armor_groups().immortal or 0
          294  +					if minetest.is_player(tgt) or immortal == 0 then
          295  +						tgt:punch(ctx.caster, 1, {
          296  +							full_punch_interval = 1;
          297  +							damage_groups = { fleshy = force / 10 };
          298  +						})
          299  +					end
          300  +					sparktrail(nil,tgt,sorcery.lib.color(101,226,255))
          301  +					if dir.y > 0 then dir.y = 0 end -- spell always lifts
          302  +					dir = vector.add(dir, {x=0,z=0,y=-0.25})
          303  +					local vel = vector.multiply(dir,0-force)
          304  +					tgt:add_velocity(vel)
          305  +				end;
          306  +			};
          307  +			ruby = {
          308  +				name = 'Liftoff';
          309  +				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  +				cast = function(ctx)
          311  +					local power = 14 * (1+(ctx.stats.power * 0.2))
          312  +					minetest.sound_play('sorcery_hurl',{object=ctx.caster},true)
          313  +					sorcery.spell.cast {
          314  +						caster = ctx.caster;
          315  +						subjects = {{player=ctx.caster}};
          316  +						duration = power * 0.25;
          317  +						timeline = {
          318  +							[0] = function(s,_,tl)
          319  +								sparktrail(s.visual_subjects,ctx.caster,sorcery.lib.color(255,252,93))
          320  +								ctx.caster:add_velocity{y=power;x=0,z=0}
          321  +								s.affect {
          322  +									duration = power * 0.25;
          323  +									raise = 2;
          324  +									fall = (power * 0.25) * 0.3;
          325  +									impacts = {
          326  +										gravity = 0.1;
          327  +									};
          328  +								}
          329  +							end;
          330  +						};
          331  +					}
          332  +				end;
   188    333   			};
   189    334   			sapphire = {
   190    335   				name = 'Flinging';
   191    336   				desc = 'Toss an enemy violently into the air, and allow the inevitable impact to do your dirty work for you';
   192    337   			};
   193    338   			emerald = {
   194    339   				name = 'Shockwave';
................................................................................
   212    357   		amulets = {
   213    358   			amethyst = {
   214    359   				name = 'Sapping';
   215    360   				desc = 'Punch a hole in enemy fortifications big enough to slip through but small enough to avoid immediate attention';
   216    361   			};
   217    362   			ruby = {
   218    363   				name = 'Shattering';
   219         -				desc = 'Tear a violent wound in the earth with the destructive force of this amulet';
          364  +				desc = 'Tear a violent wound in the land with the destructive force of this amulet';
   220    365   			};
   221    366   			emerald = {
   222    367   				name = 'Detonate';
   223    368   				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';
   224    369   				cast = function(ctx)
   225    370   					local speed = 40
   226    371   					local radius = math.random(math.floor(ctx.stats.power*0.5),math.ceil(ctx.stats.power))
................................................................................
   233    378   					bolt:set_velocity(vel)
   234    379   				end;
   235    380   			};
   236    381   			luxite = {
   237    382   				name = 'Lethal Aura';
   238    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';
   239    384   			};
          385  +			mese = {
          386  +				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';
          388  +			};
   240    389   			diamond = {
   241    390   				name = 'Killing';
   242    391   				mingrade = 4;
   243    392   				desc = 'Wield this amulet against a foe to instantly snuff the life out of their mortal form, regardless of their physical protections.';
   244    393   				cast = function(ctx)
   245    394   					if not (ctx.target and ctx.target.type == 'object') then return false end
   246    395   					local tgt = ctx.target.ref