local WAYPOINT_PRECISION = 1 -- set to 1 to show whole number or 10 for 1 decimal local UPDATE_INTERVAL = 0.22 -- get actual eye_pos of the player (including eye_offset) local function player_get_eye_pos(player) local p_pos = player:get_pos() local p_eye_height = player:get_properties().eye_height p_pos.y = p_pos.y + p_eye_height local p_eye_pos = p_pos local p_eye_offset = vector.multiply(player:get_eye_offset(), 0.1) local yaw = player:get_look_horizontal() p_eye_pos = vector.add(p_eye_pos, vector.rotate_around_axis(p_eye_offset, {x=0,y=1,z=0}, yaw)) return p_eye_pos end -- return first thing player is pointing at local function raycast_crosshair(player, range, point_to_objects, point_to_liquids) local p_eye_pos = player_get_eye_pos(player) local to = vector.add(p_eye_pos, vector.multiply(player:get_look_dir(), range)) local ray = minetest.raycast(p_eye_pos, to, point_to_objects, point_to_liquids) local pointed_thing = ray:next() while pointed_thing do if pointed_thing.type == "object" and pointed_thing.ref == player then -- exclude the player themselves from the raycast pointed_thing = ray:next() else return pointed_thing end end -- FIXME return "nothing" pointed thing? return { type = 'nothing', pos = to, -- return the end of the ray } end -- get position and thing that player is pointing at or nil local function get_pointed_position(player, range, point_to_objects, point_to_liquids) local pointed_thing = raycast_crosshair(player, range, point_to_objects, point_to_liquids) local pointed_pos = nil if pointed_thing then if pointed_thing.type == "node" then -- middle between "above" and "under" pointed_pos = vector.multiply(vector.add(pointed_thing.above, pointed_thing.under), 0.5) elseif pointed_thing.type == "object" then -- TODO point at the middle of collision box? (or not, ground may be better) pointed_pos = pointed_thing.ref:get_pos() elseif pointed_thing.type == "nothing" then -- This should allow you to point in general direction, but -- it can place the waypoint inside mountains or behind walls -- if those were not loaded. pointed_pos = pointed_thing.pos -- this is non-standard, but useful end end return pointed_pos, pointed_thing end local function round(x) local f = math.floor(x) if (x == f) or (x % 2.0 == 0.5) then return f else return math.floor(x + 0.5) end end local function get_texture_rotation_for_direction(dir, quad_steps) -- angle relative to player position local angle = math.atan2(dir.x, dir.z) local pi = math.pi -- snap/round to increments and convert to degrees local bias = 1/quad_steps/2 -- add bias to floor into correct quardant local angle_quad = math.floor(angle/(2*pi) * 4 + bias) * (360 / 4) % 360 local angle_quad_map = {[0] = "I", [90] = "R270", [180] = "R180", [270] = "R90"} local step_size = 90/quad_steps local angle_quad_step = (round((math.deg(angle) - angle_quad)/step_size) * step_size) % 90 return angle_quad_step, angle_quad_map[angle_quad] end -------------------------------------------------------------------------------- -- [[ Waypoint HUD element with distance, label an icon above it ]] -- local IconWaypointHUD = {} function IconWaypointHUD:new(point_pos, label, label_color, icon, icon_scale, icon_color) local w = { hud_id_label = nil, hud_id_icon = nil, } self.__index = self setmetatable(w, self) w.huddef_label = { hud_elem_type = "waypoint", name = label, text = "m", -- distance suffix precision = WAYPOINT_PRECISION, -- TODO make this configurable? number = label_color, world_pos = point_pos, offset = {x=0,y=0}, alignment = {x=0, y=1}, -- move down } w.huddef_icon = { hud_elem_type = "image_waypoint", scale = icon_scale, text = icon .. "^[multiply:" .. icon_color, alignment = {x=0,y=-1}, world_pos = point_pos, offset = {x=0,y=0}, --name = name, --precision = WAYPOINT_PRECISION, --number = waypoint_color, } return w end function IconWaypointHUD:show(player) --self.player_name = player:get_player_name() self.hud_id_label = player:hud_add(self.huddef_label) self.hud_id_icon = player:hud_add(self.huddef_icon) end function IconWaypointHUD:hide(player) player:hud_remove(self.hud_id_label) player:hud_remove(self.hud_id_icon) end function IconWaypointHUD:update(player) error("No reason to call IconWaypointHUD:update(), fix your code") end -------------------------------------------------------------------------------- -- [[ Just an arrow HUD element. Can be turned any direction with update() ]] -- local CompassHUD = {} function CompassHUD:new(size, alignment, direction, icon_format, icon_color, quad_steps) local c = { icon_format = icon_format, icon_color = icon_color, quad_steps = quad_steps, -- TODO have a callback to auto-update the direction? } self.__index = self setmetatable(c, self) local texture = c:get_texture(direction) c.huddef_compass = { hud_elem_type = "compass", size = size, text = texture, alignment = alignment, dir = 1, } return c end function CompassHUD:get_texture(direction) local angle_quad_step, rotation_t = get_texture_rotation_for_direction(direction, self.quad_steps) local texture = self.icon_format:format(angle_quad_step) .. "^[transform" .. rotation_t .. "^[multiply:" .. self.icon_color return texture end function CompassHUD:show(player) --self.player_name = player:get_player_name() self.hud_id_compass = player:hud_add(self.huddef_compass) end function CompassHUD:hide(player) player:hud_remove(self.hud_id_compass) end function CompassHUD:update(player, direction) player:hud_change(self.hud_id_compass, "text", self:get_texture(direction)) end -------------------------------------------------------------------------------- local WaypointHUD = {} function WaypointHUD:new(player, point_pos, label, label_color, point_icon, point_icon_scale, point_color, size, alignment, arrow_icon_format, arrow_icon_color, quad_steps, do_auto_update) local w = { point_pos = point_pos, do_auto_update = do_auto_update, auto_update_job = nil, } self.__index = self setmetatable(w, self) w.background = player:hud_add({ hud_elem_type = "image", scale = size, text = "waypoint_announce_background.png", alignment = alignment, }) w.waypoint_hud = IconWaypointHUD:new(point_pos, label, label_color, point_icon, point_icon_scale, point_color) local d = vector.subtract(point_pos, player:get_pos()) w.compass_hud = CompassHUD:new(size, alignment, d, arrow_icon_format, arrow_icon_color, quad_steps) return w end function WaypointHUD:show(player) self.waypoint_hud:show(player) self.compass_hud:show(player) self:update(player) -- FIXME? needed to start auto update end function WaypointHUD:hide(player) self.waypoint_hud:hide(player) self.compass_hud:hide(player) player:hud_remove(self.background) if self.auto_update_job then self.auto_update_job:cancel() self.auto_update_job = nil end end function WaypointHUD:update(player) local direction = vector.subtract(self.point_pos, player:get_pos()) self.compass_hud:update(player, direction) if self.do_auto_update then local player_name = player:get_player_name() local job = minetest.after(UPDATE_INTERVAL, function() local player = minetest.get_player_by_name(player_name) if player then self:update(player) end end ) self.auto_update_job = job end end -------------------------------------------------------------------------------- waypoint_lib = { raycast_crosshair = raycast_crosshair, get_pointed_position = get_pointed_position, IconWaypointHUD = IconWaypointHUD, CompassHUD = CompassHUD, WaypointHUD = WaypointHUD, } return waypoint_lib