diff --git a/README.md b/README.md index c58f3a5..14ccfce 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,28 @@ # Guider API for creating waypoints that fire callbacks on player approach. Useful for quests management, etc. ## API * `guider.add_waypoint(player, def)`: Create a waypoint for a player. Returns a handler. * `guider.destroy_waypoint(player, handler)`: Destructs a waypoint by the player object and the handler. ## Waypoint definition ```lua { name = "Display Name", -- string, optional, default: stringified position image = "guider_marker_default.png", -- string, optional, default: guider_marker_default.png suffix = " m", -- string, optional, default: none percision = 10, -- integer >= 0, optional, default: 10 (1 d.p.) text_color = 0xffffff, -- integer of color, optional, default: 0xffffff (white) scale = { x = 2, y = 2 }, -- image scale in table (x, y) or number, optional, default: 2 image_height = 16, -- image height in pixel, optional, default: 16 pos = vector.new(0, 0, 0), -- posiiton of waypoint, mandantory on_approach = function(player, handler) end, -- Called on approach, optional approach_distance = 5, -- Maximum distance to be considered approaching, optional + max_age = 60, -- Maximum age in seconds, optional, default: infinity } ``` Both `on_approach` and `approach_distance` must be set for approach detection to work. diff --git a/init.lua b/init.lua index 5d7e958..34db03b 100644 --- a/init.lua +++ b/init.lua @@ -1,172 +1,181 @@ -- guider/init.lua -- HUD markers for navigation -- Copyright (C) 2025 1F616EMO -- SPDX-License-Identifier: LGPL-3.0-or-later guider = {} -- player name: handler: data guider.markers_by_player_name = {} function guider.destroy_waypoint(player, handler) assert(player and player.is_player and player:is_player(), "Non-player object passed into guider.add_waypoint") local name = player:get_player_name() local player_data = guider.markers_by_player_name[name] local data = player_data and player_data[handler] if not data then return false end player:hud_remove(data.text_hud_id) player:hud_remove(data.image_hud_id) player_data[handler] = nil if not next(player_data) then guider.markers_by_player_name[name] = nil end return true end function guider.add_waypoint(player, def) assert(player and player.is_player and player:is_player(), "Non-player object passed into guider.add_waypoint") assert(type(def) == "table", "Invalid type of def (table expected, got " .. type(def) .. ")") assert(type(def.name) == "string" or def.name == nil, "Invalid type of def.name (string or nil expected, got " .. type(def.name) .. ")") assert(type(def.image) == "string" or def.image == nil, "Invalid type of def.image (string or nil expected, got " .. type(def.image) .. ")") assert(type(def.suffix) == "string" or def.suffix == nil, "Invalid type of def.suffix (string or nil expected, got " .. type(def.suffix) .. ")") assert(type(def.percision) == "number" or def.percision == nil, "Invalid type of def.percision (number or nil expected, got " .. type(def.percision) .. ")") assert(type(def.text_color) == "number" or def.text_color == nil, "Invalid type of def.text_color (number or nil expected, got " .. type(def.text_color) .. ")") assert(type(def.scale) == "number" or type(def.scale) == "table" or def.scale == nil, "Invalid type of def.scale (number, table or nil expected, got " .. type(def.scale) .. ")") assert(type(def.image_height) == "number" or def.image_height == nil, "Invalid type of def.image_height (number or nil expected, got " .. type(def.image_height) .. ")") assert(type(def.pos) == "table", "Invalid type of def.pos (table expected, got " .. type(def.pos) .. ")") assert(type(def.pos.x) == "number", "Invalid type of def.pos.x (number expected, got " .. type(def.pos.x) .. ")") assert(type(def.pos.y) == "number", "Invalid type of def.pos.y (number expected, got " .. type(def.pos.y) .. ")") assert(type(def.pos.z) == "number", "Invalid type of def.pos.z (number expected, got " .. type(def.pos.z) .. ")") assert(type(def.on_approach) == "function" or def.on_approach == nil, "Invalid type of def.on_approach (function or nil expected, got " .. type(def.on_approach) .. ")") assert(type(def.approach_distance) == "number" or def.approach_distance == nil, "Invalid type of def.approach_distance (number or nil expected, got " .. type(def.approach_distance) .. ")") + assert(type(def.max_age) == "number" or def.max_age == nil, + "Invalid type of def.max_age (number or nil expected, got " .. type(def.max_age) .. ")") if def.scale == nil then def.scale = { x = 2, y = 2 } elseif type(def.scale) == "number" then def.scale = { x = def.scale, y = def.scale } end local data = {} data.text_hud_id = player:hud_add({ type = "waypoint", name = def.name or core.pos_to_string(def.pos, 0), text = def.suffix or "", precision = def.percision or 10, number = def.text_color or 0xffffff, world_pos = def.pos, offset = {x = 0, y = def.scale.y * (def.image_height or 16) * -1 - 7}, z_index = -300, }) data.image_hud_id = player:hud_add({ type = "image_waypoint", scale = def.scale, text = def.image or "guider_marker_default.png", world_pos = def.pos, z_index = -300, }) data.def = def + data.created_on = os.time() local name = player:get_player_name() guider.markers_by_player_name[name] = guider.markers_by_player_name[name] or {} local handler = 1 while guider.markers_by_player_name[name][handler] do handler = handler + 1 end guider.markers_by_player_name[name][handler] = data return handler end do local passed = 0.1 core.register_globalstep(function(dtime) passed = passed + dtime if passed < 0.3 then return end passed = 0 + local now = os.time() + for _, player in ipairs(core.get_connected_players()) do local name = player:get_player_name() local pos = player:get_pos() local player_data = guider.markers_by_player_name[name] for handler, data in pairs(player_data or {}) do if data.def.approach_distance and data.def.on_approach then if vector.distance(pos, data.def.pos) <= data.def.approach_distance then core.after(0, data.def.on_approach, player, handler) end end + + if data.def.max_age and (data.created_at + data.def.max_age > now) then + guider.destroy_waypoint(player, handler) + end end end end) end core.register_on_leaveplayer(function(player) local name = player:get_player_name() guider.markers_by_player_name[name] = nil end) core.register_chatcommand("guider_test_waypoint", { privs = { server = true }, description = "Test Guider Waypoints: Create a waypoint at (0, 0, 0)", param = "[]", func = function(name, param) local player = core.get_player_by_name(name) if not player then return false, "This command can only be executed when you're online." end if param == "" then param = nil end local handler = guider.add_waypoint(player, { name = param, suffix = " m", pos = vector.new(0, 0, 0), approach_distance = 5, on_approach = guider.destroy_waypoint, }) return true, "Created a waypoint with handler: " .. tostring(handler) .. ". Use /guider_test_waypoint_destruct " .. tostring(handler) .. " to remove it." end, }) core.register_chatcommand("guider_test_waypoint_destruct", { privs = { server = true }, description = "Test Guider Waypoints: Destruct a waypoint with the given handler.", param = "", func = function(name, param) local player = core.get_player_by_name(name) if not player then return false, "This command can only be executed when you're online." end local handler = tonumber(param) if guider.destroy_waypoint(player, handler) then return true, "Successfully destroyed waypoint with handler " .. param .. "." else return false, "Failed to destroy waypoint with handler " .. param .. "." end end, })