--[[ To Do: --Create mesh for marker circle --Add crafting Add number of uses --Add height check for destination --Add blink behind mobs/players Check if marker was recently put down and if blinking close enough to it, don't recalculate destination Localize --Add swoosh sound Flash different color if no space to blink --Add Raycast to check line-of-sight when blinking behind a player/entity --Fix valid_entities and add more compatibility Possibly switch to first weapon in hotbar when blinking behind players/mobs -Make cooldown time condider calculation from distance travelled ]] blink = {} local S = core.get_translator("blink") -- SETTINGS -- -- Maximum distance to teleport local blink_distance = core.settings:get("blink:blink_distance") or 20 -- Allow teleport into a protected area local tp_into_prot = core.settings:get_bool("blink:tp_into_prot") or false -- Allow teleport out of a protected area local tp_from_prot = core.settings:get_bool("blink:tp_from_prot") or false -- Cooldown period before next blink local cooldown_base = core.settings:get("blink:cooldown_base") or 0 -- Multiply this by distance travelled and add to base local cooldown_factor = core.settings:get("blink:cooldown_factor") or 0.1 -- Blink Behind players or mobs and face their back local blink_behind = core.settings:get("blink:blink_behind") or true -- Time to show destination marker local display_time = core.settings:get("blink:display_time") or 6.0 blink.valid_entities = { ["mobs_animal"] = true, ["mobs_monster"] = true, ["mobs_npc"] = true, ["mob_horse"] = true, ["mobs_sharks"] = true, ["mobs_crocs"] = true, ["mobs_fish"] = true, ["mobs_jellyfish"] = true, ["mobs_turtles"] = true, ["nssm"] = true, ["creeper"] = true, ["dmobs"] = true } local color_loops = 2 -- If display_time is short this the colors will cycle quickly. If it is long then they will cycle very slowly -- GLOBALS -- blink.active_marker = {} -- Table of users marker objects blink.cooldown = {} -- Collection of users still in cooldown -- marker should be a registered entity function display_marker(user, marker) if marker == "" or marker == nil then marker = "blink:marker" end blink_tp(user, marker) end function blink_tp(user, marker) local username = user:get_player_name() local markername = "" -- change this to 2 if we can't tp to this destination if not marker then if blink.cooldown[username] then core.chat_send_player(username, S("You must wait before using Blink again")) return end end local origin = user:get_pos() local yaw -- use these if we move behind a player or mob local reset_pitch = false if not tp_from_prot and core.is_protected(origin, username) then core.chat_send_player(username, S("Cannot blink from protected areas!")) return end -- Move origin up to eye_height origin.y = origin.y + user:get_properties().eye_height local dir = user:get_look_dir() local dpos = table.copy(origin) dpos.x = dpos.x + (dir.x * blink_distance) dpos.y = dpos.y + (dir.y * blink_distance) dpos.z = dpos.z + (dir.z * blink_distance) local no_space_to_blink = false local rc = Raycast(origin, dpos, true, false) for pt in rc do if blink_behind and pt.type == "object" then if pt.ref ~= user then -- Raycast intersects with players head first if pt.ref:is_player() or ( pt.ref:get_luaentity() and blink.valid_entities[pt.ref:get_luaentity().name:split(":")[1]]) then local npos = pt.ref:get_pos() if pt.ref:is_player() then yaw = pt.ref:get_look_horizontal() else yaw = pt.ref:get_yaw() + pt.ref:get_luaentity().rotate end npos.y = npos.y + 0.5 reset_pitch = true -- check line-of-site -- this is to prevent someone blinking past blocks behind a player or entity local lookdir = minetest.yaw_to_dir(yaw) dpos.x = npos.x - (lookdir.x * 2) dpos.z = npos.z - (lookdir.z * 2) dpos.y = npos.y npos.y = npos.y + 1 -- cast the ray at an angle to hopefully catch edge case scenarios if Raycast(npos, dpos, false, false):next() then no_space_to_blink = true end break --else -- other entity -- core.chat_send_player(username, tostring(pt.ref:get_luaentity().name)) end end elseif pt.type == "node" then local npos = core.get_pointed_thing_position(pt) local n = core.get_node(npos).name if core.registered_nodes[n] == nil or core.registered_nodes[n].walkable then dpos.x = npos.x + pt.intersection_normal.x dpos.y = npos.y + pt.intersection_normal.y dpos.z = npos.z + pt.intersection_normal.z break end end end if blink.active_marker[username] then blink.active_marker[username]:remove() blink.active_marker[username] = nil end if not tp_into_prot and core.is_protected(dpos, username) then core.chat_send_player(username, S("Cannot blink into protected areas")) return else if not no_space_to_blink then no_space_to_blink = true for i = 1,-1,-2 do local p = table.copy(dpos) p.y = p.y + i local n = core.get_node(p).name if core.registered_nodes[n] and not core.registered_nodes[n].walkable then no_space_to_blink = false if i == 1 then break else dpos.y = dpos.y - 1 end end end end if no_space_to_blink then markername = "2" core.chat_send_player(username, S("Not enough space to blink here")) end end if marker == nil then if not no_space_to_blink then dpos.y = dpos.y - 0.5 user:set_pos(dpos) core.sound_play("blink_swoosh", {pos = dpos, max_hear_distance = 10}) if yaw then user:set_look_horizontal(yaw) end if reset_pitch then user:set_look_vertical(0) end if not core.is_creative_enabled(username) then local cooldown = cooldown_base + cooldown_factor * vector.distance(origin, dpos) blink.cooldown[username] = true core.after(cooldown, function() blink.cooldown[username] = false end) end end else blink.active_marker[username] = core.add_entity(dpos, "blink:marker" .. markername, username) end end core.register_tool("blink:rune", { description = S("Blink Rune"), inventory_image = "blink_rune.png", wield_image = "blink_rune_wield.png", on_use = function(itemstack, user, pointed_thing) return display_marker(user) end, on_place = function(itemstack, user, pointed_thing) return blink_tp(user) end, on_secondary_use = function(itemstack, user, pointed_thing) return blink_tp(user) end }) e_def = { physical = false, collisionbox = {0,0,0,0,0,0}, visual = "mesh", visual_size = {x = 3.5, y = 3.5}, textures = {"blink_spectrum.png"}, tframe = 1, tlast = 28, tstep = display_time / (28 * color_loops), mesh = "sphere.obj", timer = 0, glow = 7, owner, on_activate = function(self, staticdata, dtime_s) self.owner = staticdata end, on_step = function(self, ftime) self.timer = self.timer + ftime local tframe = math.fmod(math.floor(self.timer / self.tstep), self.tlast) if self.tframe ~= tframe then self.tframe = tframe self.object:set_texture_mod("^[verticalframe:".. self.tlast .. ":" .. tframe) end self.timer = self.timer + ftime if self.timer > display_time then blink.active_marker[self.owner] = nil self.object:remove() end end }) core.register_entity("blink:marker", e_def) e_def.textures = {"blink_spectrum2.png"} e_def.tlast = 7 core.register_entity("blink:marker2", e_def) -- Register Craft -- local mod_main if core.get_modpath("default") then mod_main = "default" elseif core.get_modpath("mcl_core") then mod_main = "mcl_core" end local ing1, ing2, ing3 if core.get_modpath("moreores") then ing1 = "moreores:mithril_ingot" else if mod_main == "default" then ing1 = "default:diamondblock" elseif mod_main == "mcl_core" then ing1 = "mcl_core:diamondblock" end end if core.get_modpath("quartz") then ing2 = "quartz:block" else if mod_main == "default" then ing2 = "default:mese" elseif mod_main == "mcl_core" and core.get_modpath("mesecons_torch") then ing2 = "mesecons_torch:redstoneblock" end end if core.get_modpath("bonemeal") then ing3 = "bonemeal:bone" elseif core.get_modpath("bones") then ing3 = "bones:bones" else if mod_main == "default" then ing3 = "default:clay_lump" elseif mod_main == "mcl_core" then ing3 = "mcl_core:clay_lump" end end if ing1 and ing2 and ing3 then core.register_craft({ output = "blink:rune", recipe = { {ing3, ing1, ing3}, {ing3, ing2, ing3}, {ing3, ing1, ing3}, } }) end