blink-cd2025/init.lua

297 lines
7.7 KiB
Lua

--[[ 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
]]
blink = {}
local S = core.get_translator("blink")
-- SETTINGS --
-- Maximum distance to teleport
local blink_distance = core.settings:get("blink:blink_distance") or 15
-- 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 = core.settings:get("blink:cooldown") or 3.0
-- 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 = nil -- objref of active marker
blink.cooldown = false -- Blink can't be used while this is true
-- 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()
if not marker then
if blink.cooldown 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 then
blink.active_marker:remove()
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 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)
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
blink.cooldown = true
core.after(cooldown, function() blink.cooldown = false end)
end
end
else
blink.active_marker = core.add_entity(dpos, "blink:marker")
end
end
function end_cooldown()
blink.cooldown = false
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
})
core.register_entity("blink:marker", {
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,
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:28:" .. tframe)
end
self.timer = self.timer + ftime
if self.timer > display_time then
self.object:remove()
end
end
})
--[[core.register_node("blink:marker_node", {
walkable = false,
drawtype = "mesh",
tiles = {name = "blink_spectrum30.png",
animation = {
type = "vertical_frames",
aspect_w = 1,
aspect_h = 1,
length = 5,
},
},
mesh = "dodecagon.obj",
--not_in_creative_inventory = true
})
]]
-- 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