sorcery  vfx.lua

File vfx.lua from the latest check-in


sorcery.vfx = {}
local L = sorcery.lib

sorcery.vfx.particle = {
	flicker = { fw = 64 };
	crackle = { fw = 64 };
	sparking = { fw = 16 };
	spark = { fw = 16 };
	sputter = { fw = 16 };
	glitter = { fw = 16 };
	poof = { fw = 16 };
	fog = { fw = 16};
}

sorcery.vfx.show = function(def)
	local sp = {}
	local p
	if def.kind then
		p = sorcery.vfx.particle[def.kind]
		local img = L.image(p.img or string.format('sorcery_%s.png', def.kind))
		if def.color then img = img:glow(def.color) end
		if def.warp then img = def:warp(img) end
		sp.texture = img:render()
	else
		sp.texture = def.texture
	end

	sp.collisiondetection = def.collisiondetection
	sp.collision_removal  = def.collision_removal
	sp.object_collision = def.object_collision

	local function q(bp, rp, ao, so)
		ao = ao or bp
		so = so or bp
		local min, max

		if def[bp] or def[rp] then
			local b = def[bp] or vector.new(0,0,0)
			if def[rp] then
				local r
				if type(def[rp]) == 'number' then
					r = vector.new(def[rp],def[rp],def[rp])
				else r = def[rp] end
				min = vector.subtract(b, r)
				max = vector.add     (b, r)
			else
				min, max = b,b
			end
		else
			local dflt = vector.new(0,0,0)
			min, max = def['min'..bp] or dflt, def['max'..bp] or dflt
		end

		if def.amount and def.amount > 1 then
			sp['min'..ao], sp['max'..ao] = min, max
		else
			if vector.equals(min,max) then sp[so] = min else
				local d = vector.subtract(max, min)
				sp[so]  = vector.add(min, vector.multiply(d, math.random()))
			end
		end
	end
	q('pos','radius')
	q('vel','velrange',   'vel', 'velocity')
	q('acc','accrange',   'acc', 'acceleration')
	local vary = def.varysize or 0
	if def.amount and def.amount > 1 then
		sp.amount = def.amount
		sp.time = def.time or def.life or 1
		def.life = def.life or sp.time

		sp.minsize = def.minsize or ((def.size or 1) - vary)
		sp.maxsize = def.maxsize or ((def.size or 1) + vary)
		sp.attached = def.attached
	else
		if def.minsize and def.maxsize then
			sp.size = def.minsize + ((def.maxsize - def.minsize) * math.random())
		elseif vary > 0 then
			sp.size = ((def.size or 1) - vary) + (vary*2*math.random())
		else sp.size = def.size end
		if def.attached then
			sp.pos = vector.add(
				vector.rotate(sp.pos,
					{x = 0, y = def.attached:get_yaw() or def.attached:get_look_horizontal(), z = 0}
				),
				def.attached:get_pos()
			)

			sp.velocity = vector.add(sp.velocity, def.attached:get_velocity())
		end
	end
	sp.node = def.node
	sp.playername = def.playername
	sp.vertical = def.vertical
	sp.glow = def.glow
	if def.life then
		if sp.amount then
			if def.varylife then
				sp.minexptime = def.life - def.varylife
				sp.maxexptime = def.life + def.varylife
			else
				sp.minexptime, sp.maxexptime = def.life, def.life
			end
		else
			sp.expirationtime = def.life
		end
	end
	if p and p.fw then
		sp.animation = {
			type = 'vertical_frames';
			aspect_w = p.fw, aspect_h = p.fh or p.fw;
			length = (sp.maxexptime or sp.life or 1) + 0.1;
		}
	end
	if sp.amount then
		minetest.add_particlespawner(sp)
	else
		minetest.add_particle(sp)
	end
end

sorcery.vfx.glowspark = function(color)
	local spark = L.image('sorcery_spark.png')
	return spark:blit(spark:multiply(color))
end

sorcery.vfx.cast_sparkle = function(caster,color,strength,duration,pos)
	local ofs = pos
		and function(x) return vector.add(pos,x) end
		or  function(x) return x end
	local height = caster:get_properties().eye_height
	sorcery.vfx.show {
		amount = 4 * (0.5+strength*0.5), time = duration or 1.5;
		kind = 'spark', color = color, glow = 14, life = 0.5;
		-- minpos = ofs({ x =  0.0, z =  0.6, y =  height - 0.7});
		-- maxpos = ofs({ x =  0.4, z =  0.2, y =  height - 0.3});
		pos = ofs(vector.new(0.2, height - 0.5, 0.4)), radius = 0.2;
		vel = vector.new(0,0,0.8);
		velrange = 0.7, acc = vector.new(0,0.5,0);
		minsize = 5, maxsize = 10;
		attached = caster;
	}
	sorcery.vfx.show {
		amount = 140 * strength, time = 0.3 + (duration or 1.5);
		kind = 'sputter', color = color, glow = 14, life = 3;
		pos = ofs(vector.new(0.2, height - 0.5, 0.5)), radius = 0.3;
		vel = vector.new(0,0,1.2);
		velrange = 0.4, acc = vector.new(0,0.5,-0.7);
		minsize = 0.05, maxsize = 0.4;
		attached = caster;
	}
	-- minetest.add_particlespawner {
	-- 	amount = 40 * strength;
	-- 	time = duration or 1.5;
	-- 	attached = caster;
	-- 	-- texture = L.image('sorcery_spark.png'):multiply(color):render();
	-- 	texture = sorcery.vfx.glowspark(color):render();
	-- 	minvel = { x = -0.5, z = -0.5, y = -0.5};
	-- 	maxvel = { x =  0.5, z =  0.5, y =  0.5};
	-- 	minacc = { x =  0.0, z =  0.0, y =  0.5};
	-- 	maxacc = { x =  0.0, z =  0.0, y =  0.5};
	-- 	minsize = 0.2, maxsize = 1.5;
	-- 	minexptime = 1, maxexptime = 1;
	-- 	glow = 14;
	-- 	animation = {
	-- 		type = 'vertical_frames';
	-- 		aspect_w = 16;
	-- 		aspect_h = 16;
	-- 		length = 1.1;
	-- 	};
	-- }
end

sorcery.vfx.body_sparkle = function(body,color,str,pos)
	local tex = L.image('sorcery_spark.png')
	local pi = tex:blit(tex:multiply(color)):render()
	local ofs = pos
		and function(x) return vector.add(pos,x) end
		or  function(x) return x end
	return minetest.add_particlespawner {
		amount = 25 * str;
		time = 0.5;
		attached = body;
		minpos = ofs{x = -0.5, y = -0.5, z = -0.5};
		maxpos = ofs{x =  0.5, y =  1.5, z =  0.5};
		minacc = {x = -0.3, y =  0.0, z =  0.3};
		maxacc = {x = -0.3, y =  0.0, z =  0.3};
		minvel = {x = -0.6, y = -0.2, z =  0.6};
		maxvel = {x = -0.6, y =  0.2, z =  0.6};
		minexptime = 1.0;
		maxexptime = 1.5;
		texture = pi;
		glow = 14;
		animation = {
			type = 'vertical_frames';
			aspect_w = 16, aspect_h = 16;
			length = 1.6;
		};
	}
end

sorcery.vfx.enchantment_sparkle = function(tgt,color)
	local minvel, maxvel
	if minetest.get_node(vector.add(tgt.under,{y=1,z=0,x=0})).name == 'air' then
		minvel = vector.new(-0.3,0.3,-0.3)  maxvel = vector.new(0.6,1.5,0.6);
	else
		local dir = vector.subtract(tgt.above,tgt.under)
		minvel = vector.multiply(dir, 0.3)
		maxvel = vector.multiply(dir, 1.2)
	end
	print(minetest.get_player_by_name('singleplayer'))
	return minetest.add_particlespawner {
		amount = 50;
		time = 0.5;
		-- old syntax
		minpos = vector.subtract(tgt.under, 0.5);
		maxpos = vector.add(tgt.under, 0.5);
		minvel = minvel, maxvel = maxvel;
		minexptime = 1, maxexptime = 2;
		minsize = 0.5, maxsize = 2;
		texture = L.image('sorcery_spark.png'):multiply(color):render();
		animation = {
			type = 'vertical_frames';
			aspect_w = 16, aspect_h = 16;
			length = 2.1;
		};
		glow = 14;
	}
end

sorcery.vfx.bloodburst = function(pos,size)
	for i=0, size or 48 do
		minetest.add_particle{
			texture = 'sorcery_blood_' .. math.random(5) .. '.png',
			size = 7,
			expirationtime = 2 + math.random(),
			glow = 1,
			pos = pos,
			velocity = {
				x = (math.random() * 3.0) - 1.5,
				y = math.random(),
				z = (math.random() * 3.0) - 1.5
			},
			acceleration = {
				x = 0,
				y = -1,
				z = 0
			}
		}
	end
end

-- target can be an entity or a pos vector
sorcery.vfx.imbue = function(color, target, strength, height)
	local tpos if target.get_pos then
		tpos = target:get_pos()
		if target.get_properties then
			height = height or ((target:get_properties().eye_height or 1)*1.3)
		end
	else
		tpos = vector.offset(target, 0,0.5,0)
	end
	height = height or 1
	local scenter = vector.add(tpos, {x=0,y=height/2,z=0})
	for i=1,math.random(24*(strength or 1),32*(strength or 1)) 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()*(2*math.pi), z=0}
		local po = vector.rotate({x=far,y=high,z=0}, yaw)
		local ppos = vector.add(po,tpos)
		local dir = vector.direction(ppos,scenter)
		local vel = math.random() * 0.8 + 0.4
		local col if type(color) == 'function'
			then col = color(i, {high = high, far = far, dir = dir, vel = vel, pos = po})
			else col = color
		end
		local et = math.floor((far/vel)*10)*.1 -- avoid freeze
		minetest.add_particle {
			pos = ppos;
			velocity = vector.multiply(dir,vel);
			expirationtime = 0.4;
			size = math.random()*2.4 + 0.6;
			texture = L.image('sorcery_sputter.png'):glow(col):render();
			glow = 14;
			animation = {
				type = 'vertical_frames', length = 0.4;
				aspect_w = 16, aspect_h = 16
			};
		}
	end
end

function sorcery.vfx.drip(liquid, noz, amt, time, exp)
	if type(liquid) == 'string' then liquid = sorcery.register.liquid.db[liquid] end
	local minnoz = vector.offset(noz, -0.03, 0.0, -0.03);
	local maxnoz = vector.offset(noz,  0.03, 0.0,  0.03);
	local drop = L.image('sorcery_drop.png'):multiply(L.color(liquid.color))
	return minetest.add_particlespawner {
		amount = amt, time = time;
		texture = drop:render();
		minpos = minnoz, maxpos = maxnoz;
		minvel = vector.new(0,0,0);
		maxvel = vector.new(0,-0.2,0);
		minacc = vector.new(0,-0.2,0);
		maxacc = vector.new(0,-0.23,0);
		minsize = 0.4, maxsize = 1;
		glow = liquid.glow or 2;
		minexptime = exp, maxexptime = exp;
		animation = {
			type = 'sheet_2d';
			frames_w = 10;
			frames_h = 1;
			frame_length = (exp/10) + 0.01;
		};
		vertical = true;
	}
end