-- Wand Focuses local particles = minetest.settings:get_bool("mgc_particles", true) -- Constants -- TODO: make settings magicalities.magic_spray_count = 16 magicalities.elemental_focus_velocity = 16 magicalities.elemental_focus_consumption = 5 -- Teleportation minetest.register_craftitem("magicalities:focus_teleport", { description = "Wand Focus of Teleportation", groups = {wand_focus = 1}, inventory_image = "magicalities_focus_teleport.png", stack_max = 1, _wand_requirements = { ["air"] = 1 }, _wand_use = function (itemstack, user, pointed_thing) local dir = user:get_look_dir() local dest = vector.multiply(dir, 20) dest = vector.add(dest, user:get_pos()) local pos = user:get_pos() pos.x = pos.x + (dir.x * 2) pos.y = pos.y + (dir.y * 2) + 1.5 pos.z = pos.z + (dir.z * 2) local ray = Raycast(pos, dest, true, false) local targ = ray:next() local can_go = targ == nil -- Go above node if targ and targ.type == "node" then local abv = minetest.get_node(targ.above) if not abv or abv.name == "air" then local add = {x=0,y=0,z=0} if user:get_pos().y < targ.above.y - 1.5 then add.y = 1.5 end dest = vector.add(targ.above, add) can_go = true end end if can_go then itemstack = magicalities.wands.wand_take_contents(itemstack, {air = 1}) magicalities.wands.update_wand_desc(itemstack) user:set_pos(dest) end return itemstack end }) -- Node swapper minetest.register_craftitem("magicalities:focus_swap", { description = "Wand Focus of Swapping", groups = {wand_focus = 1}, inventory_image = "magicalities_focus_swap.png", stack_max = 1, _wand_requirements = { ["earth"] = 1 }, _wand_use = function (itemstack, user, pointed_thing) local meta = itemstack:get_meta() local tnode = meta:get_string("swapnode") local pname = user:get_player_name() if tnode == "" or pointed_thing.type ~= "node" then return itemstack end local pos = pointed_thing.under if minetest.is_protected(pos, pname) then minetest.record_protection_violation(pos, pname) return itemstack end local node = minetest.get_node_or_nil(pos) if not node or node.name == tnode then return itemstack end local place_node_itm = ItemStack(tnode) local inv = user:get_inventory() local ndef = minetest.registered_nodes[tnode] if not inv:contains_item("main", place_node_itm) then minetest.chat_send_player(pname, ("You don't have enough %s in your inventory."):format(ndef.description)) return itemstack end local drops = minetest.get_node_drops(node.name) if ndef.can_dig ~= nil and not ndef.can_dig(pos, user) then return itemstack end minetest.remove_node(pos) itemstack = magicalities.wands.wand_take_contents(itemstack, {earth = 1}) magicalities.wands.update_wand_desc(itemstack) inv:remove_item("main", place_node_itm) for _, stk in pairs(drops) do if inv:room_for_item("main", stk) then inv:add_item("main", stk) else minetest.item_drop(ItemStack(stk), user, vector.add(pos, {x=0,y=1,z=0})) end end minetest.place_node(pos, {name = tnode}) return itemstack end, _wand_node = function (pos, node, placer, itemstack, pointed_thing) if not node or node.name == "air" or node.name == "ignore" then return itemstack end local meta = itemstack:get_meta() local tnode = meta:get_string("swapnode") if tnode == node.name then return itemstack end meta:set_string("swapnode", node.name) local ndef = minetest.registered_nodes[node.name] minetest.chat_send_player(placer:get_player_name(), "Selected replacement node " .. ndef.description) return itemstack end }) -- Light Source minetest.register_node("magicalities:light_source", { description = "Magical Light Source", tiles = {"magicalities_light_source.png"}, groups = {cracky = 3, not_in_creative_inventory = 1}, light_source = 13, drop = "" }) minetest.register_craftitem("magicalities:focus_light", { description = "Wand Focus of Light", groups = {wand_focus = 1}, inventory_image = "magicalities_focus_light.png", stack_max = 1, _wand_requirements = { ["light"] = 1 }, _wand_use = function(itemstack, user, pointed_thing) if pointed_thing.type ~= "node" then return end local pos = pointed_thing.under local pname = user:get_player_name() if minetest.is_protected(pos, pname) then minetest.record_protection_violation(pos, pname) return end local node = minetest.get_node(pos).name if node == "default:stone" or node == "default:desert_stone" then minetest.swap_node(pos, {name = "magicalities:light_source"}) itemstack = magicalities.wands.wand_take_contents(itemstack, {light = 1}) magicalities.wands.update_wand_desc(itemstack) end return itemstack end, }) --------------- -- Tunneling -- --------------- local tunneler_memory = {} local tunneler_depth = 8 local function reset_tunnel(tid) local infos = tunneler_memory[tid] if not infos then return end local manip = minetest.get_voxel_manip() local e1, e2 = manip:read_from_map(infos.minp, infos.maxp) local area = VoxelArea:new{MinEdge=e1, MaxEdge=e2} local data = manip:get_data() for i in area:iterp(infos.minp, infos.maxp) do if infos.data[i] ~= nil then data[i] = infos.data[i] end end manip:set_data(data) manip:write_to_map() tunneler_memory['t' .. tid] = nil end local function create_tunnel(pos, dir, owner) -- Ensure no double tunnels for id,data in pairs(tunneler_memory) do if data.owner == owner then return false end end local minp local maxp if dir.x < 0 or dir.y < 0 or dir.z < 0 then maxp = vector.add(pos, dir) minp = vector.add(pos, vector.multiply(dir, tunneler_depth)) else minp = vector.add(pos, dir) maxp = vector.add(pos, vector.multiply(dir, tunneler_depth)) end if dir.z ~= 0 then minp.x = minp.x + -1 maxp.x = maxp.x + 1 minp.y = minp.y + -1 maxp.y = maxp.y + 1 end if dir.y ~= 0 then minp.z = minp.z + -1 maxp.z = maxp.z + 1 minp.x = minp.x + -1 maxp.x = maxp.x + 1 end if dir.x ~= 0 then minp.z = minp.z + -1 maxp.z = maxp.z + 1 minp.y = minp.y + -1 maxp.y = maxp.y + 1 end -- Set the nodes local manip = minetest.get_voxel_manip() local e1, e2 = manip:read_from_map(minp, maxp) local area = VoxelArea:new{MinEdge=e1, MaxEdge=e2} local data = manip:get_data() local c_air = minetest.get_content_id("air") local c_tunnel = minetest.get_content_id("magicalities:tunnel_node") local dtree = {} local abort = false for i in area:iterp(minp, maxp) do if data[i] ~= c_air then dtree[i] = data[i] data[i] = c_tunnel elseif data[i] == c_tunnel then abort = true break end end if abort then return false end -- Set nodes in map manip:set_data(data) manip:write_to_map() -- Save in cache local cnum = math.random(10, 1000) local comp1 = math.random(10, 1000) local comp2 = math.random(10, 1000) cnum = (math.ceil(comp2 + comp1 / cnum) + cnum) tunneler_memory['t' .. cnum] = { data = dtree, minp = minp, maxp = maxp, owner = owner, } minetest.after(10, reset_tunnel, 't' .. cnum) return true end minetest.register_node("magicalities:tunnel_node", { groups = {not_in_creative_inventory = 1}, walkable = false, pointable = false, diggable = false, drawtype = "glasslike_framed", paramtype = "light", sunlight_propagates = true, tiles = {"magicalities_void.png"}, }) minetest.register_craftitem("magicalities:focus_tunnel", { description = "Wand Focus of Tunneling", groups = {wand_focus = 1}, inventory_image = "magicalities_focus_tunnel.png", stack_max = 1, _wand_requirements = { ["dark"] = 10, ["light"] = 10, ["earth"] = 10, }, _wand_use = function (itemstack, user, pointed_thing) if not pointed_thing.above or pointed_thing.type ~= "node" then return itemstack end if not user or user:get_player_name() == "" then return itemstack end local dir = user:get_look_dir() local wm = minetest.dir_to_wallmounted(dir) dir = minetest.wallmounted_to_dir(wm) minetest.after(0.1, create_tunnel, pointed_thing.above, dir, user:get_player_name()) itemstack = magicalities.wands.wand_take_contents(itemstack, { ["dark"] = 10, ["light"] = 10, ["earth"] = 10, }) magicalities.wands.update_wand_desc(itemstack) return itemstack end }) minetest.register_on_shutdown(function () for id in pairs(tunneler_memory) do reset_tunnel(id) end end) ----------------------- -- Elemental Attacks -- ----------------------- local special_fn = {} -- Particles local randparticles = PcgRandom(os.clock()) local function shoot_particles (user, velocity, color) if not particles then return end if not color then color = "" else color = "^[multiply:"..color end -- Calculate velocity local dir = user:get_look_dir() local vel = {x=0,y=0,z=0} vel.x = dir.x * velocity vel.y = dir.y * velocity vel.z = dir.z * velocity -- Calculate position local pos = user:get_pos() pos.x = pos.x + (dir.x * 2) pos.y = pos.y + (dir.y * 2) + 1.5 pos.z = pos.z + (dir.z * 2) for i = 1, magicalities.magic_spray_count do -- Deviation local relvel = {x=0,y=0,z=0} relvel.x = vel.x + (randparticles:next((-i/2.5) * 1000, (i/2.5) * 1000) / 1000) relvel.y = vel.y + (randparticles:next((-i/2.5) * 1000, (i/2.5) * 1000) / 1000) relvel.z = vel.z + (randparticles:next((-i/2.5) * 1000, (i/2.5) * 1000) / 1000) minetest.add_particle({ pos = pos, velocity = relvel, acceleration = relvel, expirationtime = 1, size = 4, collisiondetection = true, collision_removal = true, texture = "magicalities_spark.png"..color, -- animation = {Tile Animation definition}, glow = 2 }) end end local function shoot_spray(user, dmg, vel, color, hit_fn) shoot_particles(user, vel, color) minetest.after(0.05, function() local pos = user:get_pos() local dir = user:get_look_dir() local x = math.random(-1,1)*0.1 local y = math.random(-1,1)*0.1 local z = math.random(-1,1)*0.1 local scatternum = math.random(2, magicalities.magic_spray_count / 2) for i = 1, scatternum do local relvel = {x=0,y=0,z=0} relvel.x = dir.x * vel + (randparticles:next((-i/2.5) * 1000, (i/2.5) * 1000) / 1000) relvel.y = dir.y * vel + (randparticles:next((-i/2.5) * 1000, (i/2.5) * 1000) / 1000) relvel.z = dir.z * vel + (randparticles:next((-i/2.5) * 1000, (i/2.5) * 1000) / 1000) local dmglow = dmg - math.floor(dmg / scatternum) local reldmg = math.random(dmglow, dmg) local e=minetest.add_entity({x=pos.x+x,y=pos.y+1.5+y,z=pos.z+z}, "magicalities:magic_spray") e:set_velocity(relvel) e:set_yaw(user:get_look_yaw()+math.pi) e:get_luaentity():set_dmg(reldmg) e:get_luaentity():set_user(user) if hit_fn then e:get_luaentity():set_hit_function(hit_fn) end end end) end -- Attack local on_hit_object = function(self, target, hp, user) target:punch(user, 1, {full_punch_interval = 1, damage_groups = {fleshy = hp, magic = hp * 2}}, nil) return self end local magic_remove = function(self) if self.object:get_attach() then self.object:set_detach() end if self.target then self.target:punch(self.object, 1,{full_punch_interval=1,damage_groups={fleshy=4}}, nil) end self.object:set_hp(0) self.object:punch(self.object, 1,{full_punch_interval=1.0,damage_groups={fleshy=4}}, nil) return self end local magic_spray = { initial_properties = { hp_max = 1, physical = false, collide_with_objects = false, collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3}, visual = "sprite", visual_size = {x = 0.4, y = 0.4}, textures = {"[combine:16x16"}, pointable = false, }, struck = false, timer = 0, hit_fn = nil, on_step = function(self, dtime) self.timer = self.timer + 1 if self.timer > 80 or self.struck then magic_remove(self) end local pos=self.object:get_pos() local no=minetest.registered_nodes[minetest.get_node(pos).name] if no.walkable and not self.struck then if self.hit_fn and special_fn[self.hit_fn] and special_fn[self.hit_fn].on_hit_node then special_fn[self.hit_fn].on_hit_node(self, pos, minetest.get_node(pos), self.user) end self.struck = true return self end for i, ob in pairs(minetest.get_objects_inside_radius(pos, 1)) do if (ob and not self.struck) and ((ob:is_player() and ob:get_player_name() ~= self.user:get_player_name()) or (ob:get_luaentity() and ob:get_luaentity().physical and ob:get_luaentity().name~="__builtin:item" )) then self.object:set_velocity({x=0, y=0, z=0}) on_hit_object(self, ob, self.dmg / 2, self.user) if self.hit_fn and special_fn[self.hit_fn] and special_fn[self.hit_fn].on_hit_object then special_fn[self.hit_fn].on_hit_object(self, ob, self.dmg / 2, self.user) end self.struck = true return self end end return self end, on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir) if not self.target then return self end if not self.hp then self.hp = self.object:get_hp() end local hp = self.object:get_hp() local hurt = self.hp-self.object:get_hp() self.hp = self.object:get_hp() self.target:set_hp(self.target:get_hp() - hurt) self.target:punch(self.object, hurt, {full_punch_interval = 1.0, damage_groups = {fleshy=4}}, "default:sword_wood", nil) if hurt > 100 or hp <= hurt then self.target:set_detach() self.target:set_velocity({x=0, y=4, z=0}) self.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir) end magic_remove(self) end return self end } function magic_spray:set_dmg(dmg) self.dmg = dmg end function magic_spray:set_user(user) self.user = user end function magic_spray:set_hit_function(hit_fn) self.hit_fn = hit_fn end function magicalities.register_elemental_focus (element, description, damage, hit_fn) local el = magicalities.elements[element] minetest.register_craftitem("magicalities:focus_atk_"..element, { description = "Wand Focus of "..el.description.."\n"..description, groups = {wand_focus = 1}, inventory_image = "magicalities_focus_atk_"..element..".png", stack_max = 1, _wand_requirements = { [element] = magicalities.elemental_focus_consumption }, _wand_use = function (itemstack, user, pointed_thing) if not user or user:get_player_name() == "" then return itemstack end itemstack = magicalities.wands.wand_take_contents(itemstack, {[element] = magicalities.elemental_focus_consumption}) magicalities.wands.update_wand_desc(itemstack) shoot_spray(user, damage, magicalities.elemental_focus_velocity, el.color, hit_fn) return itemstack end }) end function magicalities.register_focus_atk_special(name, fns) special_fn[name] = fns end -- Register everything minetest.register_entity("magicalities:magic_spray", magic_spray) magicalities.register_elemental_focus("air", "Deals some damage to enemies", 2) magicalities.register_elemental_focus("earth", "Deals some damage to enemies", 4) magicalities.register_elemental_focus("water", "Spawns water sources", 3, "setwater") magicalities.register_elemental_focus("fire", "Lights things on fire", 8, "setfire") magicalities.register_focus_atk_special("setfire", { on_hit_node = function (self, pos, node, user) local toppos = vector.add(pos, {x=0,y=1,z=0}) local topnode = minetest.get_node_or_nil(toppos) if not topnode or topnode.name ~= "air" then return end if minetest.is_protected(toppos, user:get_player_name()) then return end minetest.set_node(toppos, {name="fire:basic_flame"}) end }) local function set_water(self, _, __, user) local pos = self.object:get_pos() local toppos = vector.add(pos, {x=0,y=1,z=0}) local topnode = minetest.get_node_or_nil(toppos) if not topnode or topnode.name ~= "air" then return end if minetest.is_protected(toppos, user:get_player_name()) then return end minetest.set_node(toppos, {name="default:water_source"}) end magicalities.register_focus_atk_special("setwater", { on_hit_object = set_water, on_hit_node = set_water, })