227 lines
6.8 KiB
Lua

local S = minetest.get_translator("sf_messages")
sf_messages = {}
local current_messages = {}
local speech_queues = {}
local BIG_FONT_AT_WINDOW_WIDTH = 1920
-- Word wrap after this many characters at 100% font size at the reference window width
local WORD_WRAP_CHARS = 71
local WORD_WRAP_REFERENCE_WINDOW_WIDTH = 1920
local BUBBLE_OFFSET_X = 24 -- offset from screen border
local SPEAKER_OFFSET_X = 12 -- offset from speech bubble border
local TEXT_OFFSET_X = 144 -- offset from speaker icon
local BUBBLE_OFFSET_X_TOTAL = BUBBLE_OFFSET_X
local SPEAKER_OFFSET_X_TOTAL = BUBBLE_OFFSET_X + SPEAKER_OFFSET_X
local TEXT_OFFSET_X_TOTAL = BUBBLE_OFFSET_X + SPEAKER_OFFSET_X + TEXT_OFFSET_X
-- Legacy support: Name of the HUD type field for 'hud_add'.
local hud_type_field_name
if minetest.features.hud_def_type_field then
-- Minetest 5.9.0 and later
hud_type_field_name = "type"
else
-- All Minetest versions before 5.9.0
hud_type_field_name = "hud_elem_type"
end
-- HACK:
-- Word-wrap a translatable string
-- * player: player to wrap the text for
-- * text: text to word-wrap (must have translation markers)
-- * chars: amount of characters after which to word-wrap
--
-- This is a hack because it modifies the translated string
-- which is discouraged by the Lua API. But hey, we're only
-- in singleplayer so it couldn't be that bad, right?
--
-- Returns text if not in singleplayer.
local hacky_word_wrap = function(player, text, chars)
if not minetest.is_singleplayer() then
return text
end
local pinfo = minetest.get_player_information(player:get_player_name())
local translated_text = minetest.get_translated_string(pinfo.lang_code, text)
local wrapped_text = minetest.wrap_text(translated_text, chars, false)
return wrapped_text
end
sf_messages.is_showing_speech = function(player)
local pname = player:get_player_name()
return current_messages[pname] and current_messages[pname].speech_time ~= nil
end
sf_messages.remove_current_speech = function(from_player)
local pname = from_player:get_player_name()
local message = current_messages[pname]
if not sf_messages.is_showing_speech(from_player) then
return
end
from_player:hud_remove(message.speech_bg)
from_player:hud_remove(message.speech_text)
if message.speech_icon then
from_player:hud_remove(message.speech_icon)
end
message.speech_bg = nil
message.speech_text = nil
message.speech_icon = nil
message.speech_sound = nil
message.speech_time = nil
message.speech_duration = nil
end
sf_messages.show_speech = function(to_player, text, icon, sound, duration)
local pname = to_player:get_player_name()
if not current_messages[pname] then
current_messages[pname] = {}
end
if sf_messages.is_showing_speech(to_player) then
if not speech_queues[pname] then
speech_queues[pname] = {}
end
local new_entry = { text, icon, sound, duration }
table.insert(speech_queues[pname], new_entry)
return
end
-- Determine text size depending on window size
local text_size = { x = 1, y = 1 }
local window_info = minetest.get_player_window_information(to_player:get_player_name())
local window_width
if window_info and window_info.size then
window_width = window_info.size.x
if window_info.size.x >= BIG_FONT_AT_WINDOW_WIDTH then
text_size = { x = 2, y = 2 }
end
else
-- Assume a fallback width if window info cannot be gotten
window_width = 800
end
local id_bg = to_player:hud_add({
[hud_type_field_name] = "image",
position = { x = 1, y = 0 },
scale = { x = -41, y = 10 },
text = "sf_messages_speech_bubble.png",
alignment = { x = -1, y = 1 },
offset = { x = -BUBBLE_OFFSET_X_TOTAL, y = 24 },
z_index = 100,
})
local id_icon
if icon then
id_icon = to_player:hud_add({
[hud_type_field_name] = "image",
position = { x = 1, y = 0 },
scale = { x = 2, y = 2 },
text = "sf_messages_portrait_bg.png^("..icon..")",
offset = { x = - (SPEAKER_OFFSET_X_TOTAL), y = 40 },
alignment = { x = -1, y = 1 },
z_index = 101,
})
end
-- Apply a word-wrap to the text
local effective_text_size = text_size.x * window_info.real_gui_scaling
local chars = WORD_WRAP_CHARS / math.max(0.1, effective_text_size)
chars = chars * ( (window_width - (TEXT_OFFSET_X_TOTAL))/WORD_WRAP_REFERENCE_WINDOW_WIDTH)
chars = math.floor(chars)
chars = math.max(6, chars)
text = hacky_word_wrap(to_player, text, chars)
local id_text = to_player:hud_add({
[hud_type_field_name] = "text",
position = { x = 1, y = 0 },
scale = { x = 100, y = 100 },
text = text,
number = 0xFFFFFF,
alignment = { x = -1, y = 1 },
size = text_size,
style = 0,
offset = { x = -(TEXT_OFFSET_X_TOTAL), y = 34 },
z_index = 102,
})
if sound then
minetest.sound_play(sound, {to_player=to_player:get_player_name()}, true)
end
current_messages[pname].speech_bg = id_bg
current_messages[pname].speech_icon = id_icon
current_messages[pname].speech_sound = sound
current_messages[pname].speech_text = id_text
local now = minetest.get_us_time()
current_messages[pname].speech_time = now
current_messages[pname].speech_duration = duration
end
minetest.register_globalstep(function(dtime)
local now = minetest.get_us_time()
for playername, message in pairs(current_messages) do
local player = minetest.get_player_by_name(playername)
if player then
if message.speech_duration and (now > (message.speech_time + message.speech_duration)) then
sf_messages.remove_current_speech(player)
end
end
end
for playername, queue in pairs(speech_queues) do
local player = minetest.get_player_by_name(playername)
if player and (not sf_messages.is_showing_speech(player)) then
if #queue >= 1 then
local entry = queue[1]
sf_messages.show_speech(player, entry[1], entry[2], entry[3], entry[4])
table.remove(queue, 1)
end
end
end
end)
minetest.register_on_leaveplayer(function(player)
local pname = player:get_player_name()
current_messages[pname] = nil
speech_queues[pname] = nil
end)
-- Displays random messages in quick succession to test if it displays correctly.
-- * player: Player to show messages to
-- * count: Count of messages
-- * maxlen: Maximum length of a single message (in characters)
function sf_messages.message_test(player, count, maxlen)
for c=1, count do
local len = math.random(1, maxlen or 200)
local message = ""
local l = 0
while l < len do
if l > 0 then
message = message .. " "
end
local wordlen = math.random(1, 12)
-- Construct random word
local word = ""
for w=1, wordlen do
local capital = math.random(1,4)
local char
local first, last
if capital == 1 then
-- capital letter
first, last = 0x41, 0x5A
else
-- lowercase letter
first, last = 0x61, 0x7A
end
local char = string.char(math.random(first, last))
word = word .. char
end
message = message .. word
l = string.len(message)
end
sf_messages.show_speech(player, message, "blank.png", {}, 100000)
end
end