waypoint_announce/init.lua
whosit 92ecbd898a make color of the flag item configurable
Only changes the look of the item.

Original intention was to use dye to change colors (craft from
different colored wool, etc), but probably not worth it.
2023-07-02 13:32:17 +03:00

357 lines
13 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 = 128 -- players within this radius will see a waypoint
local ANNOUNCE_ICON = "waypoint_announce_exclamation_mark.png"
local ANNOUNCE_TIMEOUT = 20 -- time until waypoint disappears
local WAYPOINT_COLOR = 0xFFFFFF -- color of the waypoint label
local ICON_SCALE = {x=-3,y=-3} -- size is 3% of the screen
local ICON_COLOR = "#E78C13"
local ARROW_FORMAT = "waypoint_announce_arrow_%03d.png"
local ARROW_QUADS_STEPS = 9
local POINT_RANGE = 256 -- maximun range player can point at (raycast range)
local POINT_TO_OBJECTS = true
local POINT_TO_LIQUIDS = true
local CHAT_MESSAGE_COLOR = "#2B74FF" -- argument to minetest.colorize()
local waypoint_lib = dofile(minetest.get_modpath("waypoint_announce") .. DIR_DELIM .. "waypoint_lib.lua")
local function show_hud_waypoint(player, point_pos, name, icon_name)
local size = {x=-9,y=-16} -- scale for 16:9 displays
local alignment = {x=100/9,y=100/16*0.7} -- int the middle above the crosshair
local do_auto_update = true
local hud = waypoint_lib.WaypointHUD:new(player, point_pos,
name, WAYPOINT_COLOR,
icon_name, ICON_SCALE, ICON_COLOR,
size, alignment, ARROW_FORMAT, ICON_COLOR, ARROW_QUADS_STEPS,
do_auto_update)
hud:show(player)
return hud
end
local Announcement = {}
function Announcement:new(announcer_name, radius, message, icon, timeout)
local a = {
announcer_name = announcer_name,
announcer_pos = nil, -- TODO check before posting? post without?
radius = radius,
message = message,
point = nil,
icon = icon,
timeout = timeout,
players_hud = {},
timeout_job = nil,
}
self.__index = self
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
local function remove_announcement(announcement)
for player_name, hud in pairs(announcement.players_hud) do
local player = minetest.get_player_by_name(player_name)
if player then
hud:hide(player)
end
end
announcement.timeout_job.cancel()
end
local function post_announcement(announcement)
local pointed_pos = announcement.point
local announcer_pos = announcement.announcer_pos
local announcer_name = announcement.announcer_name
if not pointed_pos or not announcer_pos or not announcer_name then
minetest.log("error","[MOD] waypoint_announce: nil required fields " .. minetest.serialize(announcement))
return nil
end
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 = show_hud_waypoint(player, pointed_pos, announcement.message, announcement.icon)
announcement.players_hud[player_name] = hud
end
end
local rounded_pos = vector.round(pointed_pos)
local chat_message = "[ANNOUNCE] (" ..
announcer_name ..
") Look: " ..
announcement.message .. " " ..
minetest.pos_to_string(rounded_pos)
minetest.chat_send_all(minetest.colorize(CHAT_MESSAGE_COLOR, chat_message))
announcement.timeout_job = minetest.after(announcement.timeout, remove_announcement, announcement)
for modname,func in pairs(waypoint_announce.post_announcement_hooks) do
func(announcement.announcer_name, announcement.message, announcement.point)
end
return announcement
end
local function get_pointed_and_post_announcement(announcer, announcement)
local announcer_pos = announcer:get_pos()
announcement.announcer_pos = announcer_pos
-- TODO use pointed thing to provide some description?
local pointed_pos, pointed_thing = waypoint_lib.get_pointed_position(announcer, POINT_RANGE, POINT_TO_OBJECTS, POINT_TO_LIQUIDS)
if not pointed_pos then
-- player is pointing into the void, use him as target
pointed_pos = announcer_pos
end
announcement.point = pointed_pos
post_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
get_pointed_and_post_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)
minetest.register_chatcommand(ANNOUNCE_PREPARE_COMMAND_NAME, {
params = "<message>",
description = "Type your message, then point to somewhere and press <punch> button. This will send your message to chat and place a waypoint where you pointed. Waypoint will be visible to people around you.",
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, {
params = "<message>",
description = "Send to chat your message and place a waypoint where you are looking at. Waypoint will be visible to people around you.",
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)
get_pointed_and_post_announcement(player, announcement)
return true
else
return false
end
end,
})
local flag_dialog_context = {}
local function show_flag_config_dialog(itemstack, player)
local meta = itemstack:get_meta()
local formspec_head = "formspec_version[4]size[8,4]set_focus[close;]"
local message = meta:get_string("message") or "Flag point"
local field_message = ("field[0.5,0.6;7,0.7;message;Message;%s]"):format(minetest.formspec_escape(message))
local color_str = meta:get_string("color_str")
if color_str == "" then
color_str = meta:get_string("color")
if color_str == "" then
-- probably an old flag, provide default color
color_str = "red"
end
end
-- strip alpha (it should never appear here anyway)
color_str = string.match(color_str, "#[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]") or color_str
local field_color = ("field[0.5,1.75;4,0.7;color;Color;%s]"):format(
minetest.formspec_escape(color_str))
minetest.show_formspec(player:get_player_name(), "waypoint_announce:flag_edit",
formspec_head ..
field_message ..
field_color ..
--field_time .. -- TODO set time too? (within limits)
"button_exit[0.5,2.7;3,0.8;save;save]" ..
"button_exit[4.5,2.7;3,0.8;close;close]")
flag_dialog_context[player:get_player_name()] = itemstack
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "waypoint_announce:flag_edit" then
return
end
-- FIXME has same wielded item swapping problems as compass had
if fields.save or (fields.key_enter_field and fields.quit) then
local flag_item = flag_dialog_context[player:get_player_name()]
if not flag_item then
-- should not happen normally
minetest.log("error","[MOD] waypoint_announce: " ..
player:get_player_name() ..
" Closed flag dialog without opening it?")
return
end
local meta = flag_item:get_meta()
local message = fields.message
meta:set_string("message", message)
if fields.color and #(fields.color) < 20 then
local colorstring = minetest.colorspec_to_colorstring(fields.color)
if colorstring then
-- strip alpha
colorstring = string.match(colorstring, "#[0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F][0-9A-F]") or colorstring
meta:set_string("color", colorstring)
-- preserve original color name for editing
meta:set_string("color_str", fields.color)
end
-- else
-- -- something sus is going on, don't set it
-- meta:set_string("color_str", "")
end
local success = player:set_wielded_item(flag_item)
if not success then
-- no idea why this can happen
minetest.log("error","[MOD] waypoint_announce: " ..
player:get_player_name() ..
" Failed to swap flag after editing it")
end
-- assume dialog is closed and reset context
flag_dialog_context[player:get_player_name()] = nil
end
end)
local last_flag_use = {}
local FLAG_COOLDOWN = 3
local function flag_use_callback(itemstack, user, pointed_thing)
if not (user and user:is_player()) then
return
end
if user:get_player_control()["sneak"] then
show_flag_config_dialog(itemstack, user)
return
end
if not minetest.check_player_privs(user, "shout") then
return
end
local player_name = user:get_player_name()
local meta = itemstack:get_meta()
local pointed_pos = nil
if pointed_thing.type == "nothing" then
pointed_pos, pointed_thing = waypoint_lib.get_pointed_position(user, POINT_RANGE, POINT_TO_OBJECTS, POINT_TO_LIQUIDS)
else
pointed_pos = pointed_thing.above
end
if not pointed_pos then
return
end
local current_time = minetest.get_gametime()
local last_time = last_flag_use[player_name]
if last_time and current_time - last_time < FLAG_COOLDOWN then
-- FIXME check after we know we can post it?
minetest.chat_send_player(player_name, ("Don't spam, wait %ds."):format(FLAG_COOLDOWN - (current_time - last_time)))
return
end
last_flag_use[player_name] = current_time
local message = meta:get_string("message") or "Flag point"
local announcement = Announcement:new(user:get_player_name(), ANNOUNCE_RADIUS, message, ANNOUNCE_ICON, ANNOUNCE_TIMEOUT)
announcement.point = pointed_pos
announcement.announcer_pos = user:get_pos()
post_announcement(announcement) -- TODO maybe do not show the chat message?
return
end
minetest.register_tool(
"waypoint_announce:flag", {
description = "Announcement flag\n<place> to place the waypoint\n<sneak+place> to edit the message",
inventory_image = "waypoint_announce_flag.png",
wield_scale = {x = 1.5, y = 1.5, z = 1.0},
range = 2.0, -- TODO what's the good range?
on_place = function(itemstack, placer, pointed_thing)
return flag_use_callback(itemstack, placer, pointed_thing)
end,
on_secondary_use = function(itemstack, user, pointed_thing)
return flag_use_callback(itemstack, user, pointed_thing)
end,
})
minetest.register_craft({
type = "shaped",
output = "waypoint_announce:flag",
recipe = {
{"default:stick", "wool:red", "wool:red"},
{"default:stick", "wool:red", "wool:red"},
{"default:stick", "", ""},
}
})
waypoint_announce = {
post_announcement = post_announcement,
-- table, use as:
-- hooks['modname'] = function(announcer_name, message, pointed_pos) end
post_announcement_hooks = {},
}
return waypoint_announce