diff --git a/init.lua b/init.lua index adf6b0e..4cd28cb 100644 --- a/init.lua +++ b/init.lua @@ -5,7 +5,10 @@ local TARGET_ABOVE = false -- place waypoint inside the block or above it local POINT_TO_OBJECTS = false -- unimplemented local POINT_TO_LIQUIDS = true local COMPASS_RANGE = 180 +local WAYPOINT_ICON = "waypoint.png" +local WAYPOINT_SCALE = {x=-1/16*9,y=-1} +local waypoint_lib = dofile(minetest.get_modpath("waypoint_announce") .. "/waypoint_lib.lua") -- internal mod state local player_waypoints = {} -- store a current player waypoint to see if it needs to be updated @@ -66,16 +69,6 @@ local function get_compass_meta_owner(meta) end --- local function set_compass_meta_pos(meta, pos) --- local pos_hash = minetest.hash_node_position(pos) --- meta:set_int("waypoint_compass:position", pos_hash) --- end - - --- local function get_compass_meta_pos(meta) --- return minetest.get_position_from_hash(meta:get_int("waypoint_compass:position")) --- end - local function set_waypoint_at_pointed_place(itemstack, pointed_thing) if pointed_thing and pointed_thing.type == "node" then local pointed_pos = TARGET_ABOVE and pointed_thing.above or pointed_thing.under @@ -90,27 +83,11 @@ local function set_waypoint_at_pointed_place(itemstack, pointed_thing) end --- return first thing player is pointing at -local function raycast_crosshair(player, range) - local p_pos = player:get_pos() - local p_eye_height = player:get_properties().eye_height - local p_eye_pos = { x = p_pos.x, y = p_pos.y + p_eye_height, z = p_pos.z } - 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() - return pointed_thing - -- while pointed_thing do - -- print(pointed_thing.type, minetest.pos_to_string(pointed_thing.under)) - -- pointed_thing = ray:next() - -- end -end - - local function hide_hud_waypoint(player) local player_name = player:get_player_name() - local hud_id = player_waypoints[player_name].hud_id + local hud = player_waypoints[player_name] player_waypoints[player_name] = nil - player:hud_remove(hud_id) + hud:hide(player) end @@ -121,24 +98,29 @@ local function show_hud_waypoint(player, compass_item_meta) -- do not show unset compass position return end + -- Show this waypoint local waypoint_name = get_compass_meta_label(compass_item_meta) local waypoint_color = get_compass_meta_color(compass_item_meta) - local hud_id = player:hud_add({ - hud_elem_type = "waypoint", - name = waypoint_name, - text = "m", - precision= COMPASS_PRECISION, - number = waypoint_color, - world_pos = waypoint_pos, - }) + local hexcolor = ("#%06X"):format(waypoint_color) + + local size = {x=-9, y=-16} + local alignment = {x=100/9*1.47,y=100/16*1.63} -- near compass in your hand + + local waypoint = waypoint_lib.WaypointHUD:new(player, waypoint_pos, + waypoint_name, waypoint_color, + WAYPOINT_ICON, WAYPOINT_SCALE, hexcolor, + size, alignment, "arrow_%03d.png", hexcolor, 9, + true) + + waypoint:show(player) -- store HUD elemnt id to remove it later - if not player_waypoints[player_name] then - player_waypoints[player_name] = {} + if player_waypoints[player_name] then + minetest.log("error","[MOD] waypoint_compass: " .. + player:get_player_name() .. + " got their HUD stuck on screen?") end - --print("hud_id add", hud_id) - player_waypoints[player_name].pos = waypoint_pos - player_waypoints[player_name].hud_id = hud_id + player_waypoints[player_name] = waypoint end @@ -155,7 +137,7 @@ local function update_hud_waypoint(player, itemstack, force) local waypoint_pos = get_compass_meta_pos(meta) -- remove different waypoint if it exists if player_waypoints[player_name] and - (player_waypoints[player_name].pos ~= waypoint_pos + (player_waypoints[player_name].point_pos ~= waypoint_pos or force or not get_compass_meta_is_set(meta)) then @@ -260,7 +242,7 @@ local function compass_use_callback(itemstack, user, pointed_thing) if user:get_player_control()["sneak"] then if pointed_thing.type == "nothing" then if user and user:is_player() then - pointed_thing = raycast_crosshair(user, COMPASS_RANGE) + pointed_thing = waypoint_lib.raycast_crosshair(user, COMPASS_RANGE, POINT_TO_OBJECTS, POINT_TO_LIQUIDS) end end set_waypoint_at_pointed_place(itemstack, pointed_thing) diff --git a/textures/arrow_000.png b/textures/arrow_000.png new file mode 100644 index 0000000..93a79f8 Binary files /dev/null and b/textures/arrow_000.png differ diff --git a/textures/arrow_010.png b/textures/arrow_010.png new file mode 100644 index 0000000..5a93302 Binary files /dev/null and b/textures/arrow_010.png differ diff --git a/textures/arrow_020.png b/textures/arrow_020.png new file mode 100644 index 0000000..2e3314e Binary files /dev/null and b/textures/arrow_020.png differ diff --git a/textures/arrow_030.png b/textures/arrow_030.png new file mode 100644 index 0000000..933660b Binary files /dev/null and b/textures/arrow_030.png differ diff --git a/textures/arrow_040.png b/textures/arrow_040.png new file mode 100644 index 0000000..0638941 Binary files /dev/null and b/textures/arrow_040.png differ diff --git a/textures/arrow_050.png b/textures/arrow_050.png new file mode 100644 index 0000000..be52a61 Binary files /dev/null and b/textures/arrow_050.png differ diff --git a/textures/arrow_060.png b/textures/arrow_060.png new file mode 100644 index 0000000..a50a7b6 Binary files /dev/null and b/textures/arrow_060.png differ diff --git a/textures/arrow_070.png b/textures/arrow_070.png new file mode 100644 index 0000000..2bb7a8c Binary files /dev/null and b/textures/arrow_070.png differ diff --git a/textures/arrow_080.png b/textures/arrow_080.png new file mode 100644 index 0000000..22fdd12 Binary files /dev/null and b/textures/arrow_080.png differ diff --git a/textures/waypoint.png b/textures/waypoint.png new file mode 100644 index 0000000..5c22d15 Binary files /dev/null and b/textures/waypoint.png differ diff --git a/waypoint.svg b/waypoint.svg new file mode 100644 index 0000000..27e29b7 --- /dev/null +++ b/waypoint.svg @@ -0,0 +1,68 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/waypoint_lib.lua b/waypoint_lib.lua new file mode 100644 index 0000000..b2ba0db --- /dev/null +++ b/waypoint_lib.lua @@ -0,0 +1,255 @@ +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 nil +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() + 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.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) + 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, + player_hud_add_waypoint_compass = player_hud_add_waypoint_compass, + player_hud_update_waypoint_compass = player_hud_update_waypoint_compass, + IconWaypointHUD = IconWaypointHUD, + CompassHUD = CompassHUD, + WaypointHUD = WaypointHUD, +} + +return waypoint_lib