337 lines
11 KiB
Lua
337 lines
11 KiB
Lua
-- TODO show some text on screen for a short while?
|
|
-- TODO "admin" command version with configurable pos, radius and timeout
|
|
|
|
local announce_prepare_command_name = "look"
|
|
local announce_command_name = "look!"
|
|
local announce_radius = 256 -- players within this radius will see a waypoint
|
|
local announce_icon = "exclamation_mark.png"
|
|
local announce_timeout = 20 -- time until waypoint disappears
|
|
local waypoint_precision = 1 -- set to 1 to show whole number or 10 for 1 decimal
|
|
local waypoint_color = 0xFFFFFF
|
|
local point_range = 180 -- maximun range player can point at (raycast range)
|
|
local point_to_objects = true
|
|
local point_to_liquids = true
|
|
local chat_message_color = "#FF90A8" -- argument to minetest.colorize()
|
|
|
|
|
|
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
|
|
|
|
|
|
-- 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)
|
|
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)
|
|
local pointed_thing = raycast_crosshair(player, range)
|
|
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 get_texture_for_direction(start_pos, end_pos)
|
|
local angle_steps = 9 -- number pre-rotated textures
|
|
|
|
-- vector pointing towards point_pos
|
|
local v = vector.subtract(end_pos, start_pos)
|
|
-- angle relative to player position
|
|
local angle = math.atan2(v.x, v.z)
|
|
local pi = math.pi
|
|
-- snap/round to increments and convert to degrees
|
|
local bias = 1/angle_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/angle_steps
|
|
local angle_quad_step = (round((math.deg(angle) - angle_quad)/step_size) * step_size) % 90
|
|
|
|
local arrow_texture = ("arrow_%03d.png"):format(angle_quad_step)
|
|
return arrow_texture .. "^[transform" .. angle_quad_map[angle_quad]
|
|
end
|
|
|
|
|
|
local function show_hud_waypoint(player, point_pos, name, icon_name)
|
|
local hud_id1 = player:hud_add({
|
|
hud_elem_type = "waypoint",
|
|
name = name,
|
|
text = "m", -- distance suffix
|
|
precision = waypoint_precision,
|
|
number = waypoint_color,
|
|
world_pos = point_pos,
|
|
offset = {x=0,y=0},
|
|
alignment = {x=0, y=1},
|
|
})
|
|
|
|
local hud_id2 = player:hud_add({
|
|
hud_elem_type = "image_waypoint",
|
|
scale = {x=-3,y=-3},
|
|
text = icon_name,
|
|
alignment = {x=0,y=-1},
|
|
world_pos = point_pos,
|
|
offset = {x=0,y=0},
|
|
--name = name,
|
|
--precision = waypoint_precision,
|
|
--number = waypoint_color,
|
|
})
|
|
|
|
local size_x = 9
|
|
local size_y = 16
|
|
|
|
local texture = get_texture_for_direction(player:get_pos(), point_pos)
|
|
|
|
-- TODO it's possible to color the texture: use it to make waypoints have different colors
|
|
--local arrow_texture = arrow_texture .. "^[multiply:#11FFFF"
|
|
|
|
local hud_id3 = player:hud_add({
|
|
hud_elem_type = "compass",
|
|
size = {x=-size_x,y=-size_y},
|
|
--scale = {x=0.5,y=0.5}, -- unused
|
|
text = texture,
|
|
--alignment = {x=1,y=1}, -- top-left?
|
|
alignment = {x=100/size_x,y=100/size_y*0.7}, -- above cursor
|
|
--offset = {x=0,y=0}, -- in pixels?
|
|
dir = 1,
|
|
})
|
|
local hud_ids = {hud_id1, hud_id2, hud_id3}
|
|
return hud_ids
|
|
end
|
|
|
|
|
|
local function hide_hud_waypoint(player_name, hud_ids)
|
|
local player = minetest.get_player_by_name(player_name)
|
|
if player then
|
|
for _,hud_id in pairs(hud_ids) do
|
|
player:hud_remove(hud_id)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local Announcement = {}
|
|
|
|
function Announcement:new(announcer_name, radius, message, icon, timeout)
|
|
local a = {
|
|
announcer_name = announcer_name,
|
|
radius = radius,
|
|
message = message,
|
|
point = nil,
|
|
icon = icon,
|
|
timeout = timeout,
|
|
players_huds = {},
|
|
players_compass = {},
|
|
timeout_job = nil,
|
|
}
|
|
setmetatable(a, self)
|
|
return a
|
|
end
|
|
|
|
|
|
local prepared_announcements = {}
|
|
local function prepare_announcement(announcer_name, radius, message, icon, timeout)
|
|
--prepared_announcements[select(1,...)] = table.pack(...) -- does not work in older lua?
|
|
prepared_announcements[announcer_name] = Announcement:new(announcer_name, radius, message, icon, timeout)
|
|
end
|
|
|
|
|
|
-- limit to one per player for now
|
|
local posted_announcements = {}
|
|
|
|
local function remove_announcement(announcement)
|
|
local announcer_name = announcement.announcer_name
|
|
posted_announcements[announcer_name] = nil
|
|
for player_name, hud_ids in pairs(announcement.players_huds) do
|
|
hide_hud_waypoint(player_name, hud_ids)
|
|
end
|
|
announcement.timeout_job.cancel()
|
|
end
|
|
|
|
|
|
local function add_announcement(announcer, announcement)
|
|
local announcer_pos = announcer:get_pos()
|
|
-- TODO use pointed thing to provide some description?
|
|
local pointed_pos, pointed_thing = get_pointed_position(announcer, point_range)
|
|
if not pointed_pos then
|
|
-- player is pointing into the void, use him as target
|
|
pointed_pos = announcer_pos
|
|
end
|
|
|
|
announcement.point = pointed_pos
|
|
|
|
for i, object in pairs(minetest.get_objects_inside_radius(announcer_pos, announcement.radius)) do
|
|
if object:is_player() then
|
|
local player = object
|
|
local player_name = player:get_player_name()
|
|
local hud_ids = show_hud_waypoint(player, pointed_pos, announcement.message, announcement.icon)
|
|
announcement.players_huds[player_name] = hud_ids
|
|
announcement.players_compass[player_name] = hud_ids[3] -- FIXME this assumes number and order of HUDS
|
|
end
|
|
end
|
|
|
|
local rounded_pos = vector.round(pointed_pos)
|
|
local chat_message = "[ANNOUNCE] (" ..
|
|
announcement.announcer_name ..
|
|
") Look: " ..
|
|
announcement.message .. " " ..
|
|
minetest.pos_to_string(rounded_pos)
|
|
minetest.chat_send_all(minetest.colorize(chat_message_color, chat_message))
|
|
|
|
|
|
local previous = posted_announcements[announcement.announcer_name]
|
|
if previous then
|
|
remove_announcement(previous)
|
|
end
|
|
|
|
posted_announcements[announcement.announcer_name] = announcement
|
|
announcement.timeout_job = minetest.after(announcement.timeout, remove_announcement, announcement)
|
|
end
|
|
|
|
|
|
local function check_click_for_prepared_players(dtime)
|
|
for player_name, announcement in pairs(prepared_announcements) do
|
|
local player = minetest.get_player_by_name(player_name)
|
|
if player then
|
|
local player_controls = player:get_player_control()
|
|
if player_controls['dig'] then
|
|
-- place prepared announcement at pointed position
|
|
add_announcement(player, announcement)
|
|
prepared_announcements[player_name] = nil
|
|
elseif player_controls['place'] then
|
|
-- cancel prepared announcement
|
|
minetest.chat_send_player(player_name, "Announcement canceled.")
|
|
prepared_announcements[player_name] = nil
|
|
end
|
|
else
|
|
-- player is not online anymore, remove prepared announcement
|
|
prepared_announcements[player_name] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
minetest.register_globalstep(check_click_for_prepared_players)
|
|
|
|
|
|
local function update_announcement(announcement)
|
|
for player_name,hud_id in pairs(announcement.players_compass) do
|
|
local player = minetest.get_player_by_name(player_name)
|
|
if player then
|
|
player:hud_change(hud_id, "text", get_texture_for_direction(player:get_pos(), announcement.point))
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local timer = 0
|
|
local update_interval = 0.33
|
|
minetest.register_globalstep(function(dtime)
|
|
timer = timer + dtime
|
|
if timer > update_interval then
|
|
timer = 0
|
|
else
|
|
return
|
|
end
|
|
for announcer_name,announcement in pairs(posted_announcements) do
|
|
update_announcement(announcement)
|
|
end
|
|
end)
|
|
|
|
|
|
minetest.register_chatcommand(announce_prepare_command_name, {
|
|
privs = {
|
|
shout = true,
|
|
},
|
|
func = function(name, param)
|
|
local player = minetest.get_player_by_name(name)
|
|
if player then -- player is online
|
|
local player_name = player:get_player_name()
|
|
-- TODO limit message length?
|
|
prepare_announcement(player_name, announce_radius, param, announce_icon, announce_timeout)
|
|
return true, "Prepared announcement: \"" .. param .. "\". Point and <hit> where to put it. (<place> to cancel)."
|
|
else
|
|
return false
|
|
end
|
|
end,
|
|
})
|
|
|
|
|
|
minetest.register_chatcommand(announce_command_name, {
|
|
privs = {
|
|
shout = true,
|
|
},
|
|
func = function(name, param)
|
|
local player = minetest.get_player_by_name(name)
|
|
if player then -- player is online
|
|
local player_name = player:get_player_name()
|
|
local announcement = Announcement:new(player_name, announce_radius, param, announce_icon, announce_timeout)
|
|
add_announcement(player, announcement)
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end,
|
|
})
|
|
|
|
|
|
local test_time = 0
|
|
function announce_waypoint_test_spam_look()
|
|
minetest.register_globalstep(function(dtime)
|
|
test_time = test_time + dtime
|
|
if test_time < 0.2 then
|
|
return
|
|
else
|
|
test_time = 0
|
|
end
|
|
local player = minetest.get_player_by_name("singleplayer")
|
|
if player then -- player is online
|
|
local player_name = player:get_player_name()
|
|
local param = ""
|
|
local announcement = Announcement:new(player_name, announce_radius, param, announce_icon, announce_timeout)
|
|
add_announcement(player, announcement)
|
|
end
|
|
end)
|
|
end
|
|
|