677 lines
22 KiB
Lua
677 lines
22 KiB
Lua
--functions
|
|
|
|
eye_spy = {}
|
|
local S = minetest.get_translator(minetest.get_current_modname())
|
|
eye_spy.S = S
|
|
-- eye_spy.cg is set in init.lua after game detection
|
|
|
|
------------------------------------------------------------
|
|
-- Mod storage (called ONCE at load time)
|
|
------------------------------------------------------------
|
|
eye_spy.storage = minetest.get_mod_storage()
|
|
|
|
------------------------------------------------------------
|
|
-- Shared runtime tables
|
|
------------------------------------------------------------
|
|
eye_spy.state = { players = {} }
|
|
eye_spy.cache = { tool_info = {} }
|
|
eye_spy.edit_mode = {}
|
|
eye_spy.pending_preview_updates = {}
|
|
eye_spy.pending_layout_refreshes = {}
|
|
eye_spy.ui_drafts = {}
|
|
|
|
------------------------------------------------------------
|
|
-- Shared helper functions
|
|
------------------------------------------------------------
|
|
|
|
-- Returns a human-readable name from a node/item id string, or nil on failure.
|
|
-- Callers are responsible for providing their own fallback string.
|
|
function eye_spy.readable_name_from_id(name)
|
|
if not name or name == "" then
|
|
return nil
|
|
end
|
|
|
|
local raw = tostring(name)
|
|
local stripped = raw:match(":(.+)$") or raw
|
|
|
|
stripped = stripped
|
|
:gsub("_source$", "")
|
|
:gsub("_flowing$", "")
|
|
:gsub("[_%-%./]+", " ")
|
|
:gsub("%s+", " ")
|
|
:gsub("^%s+", "")
|
|
:gsub("%s+$", "")
|
|
|
|
if stripped == "" then
|
|
return nil
|
|
end
|
|
|
|
return (stripped:gsub("(%a)([%w']*)", function(a, b)
|
|
return string.upper(a) .. string.lower(b)
|
|
end))
|
|
end
|
|
|
|
-- Returns the mod portion of a namespaced id (e.g. "default" from "default:stone").
|
|
function eye_spy.get_modname_from_obj(obj_name)
|
|
if not obj_name then return nil end
|
|
return obj_name:match("([^:]+)")
|
|
end
|
|
|
|
-- Rounds x to one decimal place, or returns nil if x is falsy.
|
|
function eye_spy.round1(x)
|
|
if x then
|
|
return math.floor(x * 10 + 0.5) / 10
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
-- Strips everything from the first texture-modifier caret (^) onwards.
|
|
function eye_spy.strip_texture_modifiers(tex)
|
|
if type(tex) ~= "string" then
|
|
return tex
|
|
end
|
|
|
|
local pos = tex:find("%^")
|
|
if not pos then
|
|
return tex
|
|
end
|
|
|
|
return tex:sub(1, pos - 1)
|
|
end
|
|
|
|
-- Parses an "R, G, B" string and returns three numbers.
|
|
-- Falls back to 26, 26, 27 for any component that cannot be parsed.
|
|
function eye_spy.get_rgb(v)
|
|
if not v or v == "" then
|
|
return 26, 26, 27
|
|
end
|
|
|
|
v = v:gsub("%s", "")
|
|
|
|
local r, g, b = v:match("^(%d+),(%d+),(%d+)$")
|
|
r, g, b = tonumber(r), tonumber(g), tonumber(b)
|
|
|
|
return r or 26, g or 26, b or 27
|
|
end
|
|
|
|
-- Converts three 0-255 channel values to an uppercase hex string.
|
|
function eye_spy.rgb_to_hex(r, g, b)
|
|
return string.format("%02X%02X%02X", r, g, b)
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Interval system
|
|
------------------------------------------------------------
|
|
|
|
local function clamp_interval_ms(value)
|
|
return math.max(50, math.min(5000, tonumber(value) or 250))
|
|
end
|
|
|
|
local function default_interval_for_mode()
|
|
if minetest.is_singleplayer and minetest.is_singleplayer() then
|
|
return 100
|
|
end
|
|
return 150
|
|
end
|
|
|
|
function eye_spy.get_server_default_interval_ms()
|
|
local raw = eye_spy.storage:get_string("server_default_interval_ms")
|
|
if raw and raw ~= "" then
|
|
return clamp_interval_ms(raw)
|
|
end
|
|
return default_interval_for_mode()
|
|
end
|
|
|
|
function eye_spy.set_server_default_interval_ms(interval_ms)
|
|
eye_spy.storage:set_string(
|
|
"server_default_interval_ms",
|
|
tostring(clamp_interval_ms(interval_ms))
|
|
)
|
|
end
|
|
|
|
function eye_spy.get_player_interval_override_ms(player_name)
|
|
if not player_name or player_name == "" then
|
|
return nil
|
|
end
|
|
|
|
local raw = eye_spy.storage:get_string("player_interval_ms:" .. player_name)
|
|
if raw == "" then
|
|
return nil
|
|
end
|
|
|
|
return clamp_interval_ms(raw)
|
|
end
|
|
|
|
function eye_spy.set_player_interval_override_ms(player_name, interval_ms)
|
|
if not player_name or player_name == "" then
|
|
return
|
|
end
|
|
|
|
local key = "player_interval_ms:" .. player_name
|
|
|
|
if interval_ms == nil then
|
|
eye_spy.storage:set_string(key, "")
|
|
return
|
|
end
|
|
|
|
eye_spy.storage:set_string(key, tostring(clamp_interval_ms(interval_ms)))
|
|
end
|
|
|
|
function eye_spy.get_effective_interval_ms(player_name)
|
|
return eye_spy.get_player_interval_override_ms(player_name)
|
|
or eye_spy.get_server_default_interval_ms()
|
|
end
|
|
|
|
function eye_spy.apply_player_interval(player)
|
|
if not player then return end
|
|
local meta = player:get_meta()
|
|
local player_name = player:get_player_name()
|
|
meta:set_int("es_update_interval_ms", eye_spy.get_effective_interval_ms(player_name))
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Admin / player disable system
|
|
------------------------------------------------------------
|
|
|
|
-- Returns true if a server admin has suppressed this player's HUD.
|
|
function eye_spy.is_player_server_disabled(player_name)
|
|
if not player_name or player_name == "" then return false end
|
|
return eye_spy.storage:get_string("player_disabled:" .. player_name) == "true"
|
|
end
|
|
|
|
-- Sets or clears the server-level disable flag for a player.
|
|
function eye_spy.set_player_server_disabled(player_name, disabled)
|
|
if not player_name or player_name == "" then return end
|
|
eye_spy.storage:set_string(
|
|
"player_disabled:" .. player_name,
|
|
disabled and "true" or ""
|
|
)
|
|
end
|
|
|
|
-- Returns true if the player has self-disabled their HUD via meta.
|
|
function eye_spy.is_player_self_disabled(player_name)
|
|
local player = minetest.get_player_by_name(player_name)
|
|
if not player then return false end
|
|
return player:get_meta():get_string("es_hud_enabled") == "false"
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Player state management
|
|
------------------------------------------------------------
|
|
|
|
-- Returns the runtime state table for a player, creating it if absent.
|
|
function eye_spy.get_player_state(player)
|
|
local player_name = player:get_player_name()
|
|
local state = eye_spy.state.players[player_name]
|
|
|
|
if not state then
|
|
state = {
|
|
timer = 0,
|
|
edit_mode = false,
|
|
last_target_key = nil,
|
|
last_render_key = nil,
|
|
last_geom_key = nil,
|
|
last_probe_pos = nil,
|
|
last_probe_dir = nil,
|
|
cached_target = nil,
|
|
skip_static_acquire = false,
|
|
hud_hidden_since_us = nil,
|
|
last_recover_try_us = 0,
|
|
hud_ids = { lines = {} },
|
|
}
|
|
eye_spy.state.players[player_name] = state
|
|
end
|
|
|
|
return state
|
|
end
|
|
|
|
-- Clears all cached per-player data and the layout signature.
|
|
function eye_spy.invalidate_player(player)
|
|
local player_name = player:get_player_name()
|
|
local state = eye_spy.get_player_state(player)
|
|
|
|
state.last_target_key = nil
|
|
state.last_render_key = nil
|
|
state.last_geom_key = nil
|
|
state.cached_target = nil
|
|
state.last_probe_pos = nil
|
|
state.last_probe_dir = nil
|
|
state.skip_static_acquire = false
|
|
state.hud_hidden_since_us = nil
|
|
state.last_recover_try_us = 0
|
|
|
|
if eye_spy.render and eye_spy.render.invalidate_layout_sig then
|
|
eye_spy.render.invalidate_layout_sig(player_name)
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Edit mode
|
|
------------------------------------------------------------
|
|
|
|
function eye_spy.enter_edit_mode(player)
|
|
if not player then return end
|
|
|
|
local player_name = player:get_player_name()
|
|
local state = eye_spy.get_player_state(player)
|
|
|
|
if not state.edit_mode then
|
|
eye_spy.ensure_player_defaults(player)
|
|
state.edit_mode = true
|
|
state.timer = 0
|
|
eye_spy.edit_mode[player_name] = true
|
|
|
|
if eye_spy.ui_drafts then
|
|
eye_spy.ui_drafts[player_name] = nil
|
|
end
|
|
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = false }))
|
|
end
|
|
end
|
|
|
|
function eye_spy.exit_edit_mode(player)
|
|
if not player then return end
|
|
|
|
local player_name = player:get_player_name()
|
|
local state = eye_spy.get_player_state(player)
|
|
|
|
if state.edit_mode then
|
|
local meta = player:get_meta()
|
|
meta:set_string("es_last_obj", "")
|
|
meta:set_int("es_last_health", -1)
|
|
|
|
state.edit_mode = false
|
|
state.last_render_key = nil
|
|
state.timer = 0
|
|
|
|
eye_spy.edit_mode[player_name] = false
|
|
eye_spy.pending_preview_updates[player_name] = nil
|
|
|
|
if eye_spy.ui_drafts then
|
|
eye_spy.ui_drafts[player_name] = nil
|
|
end
|
|
|
|
minetest.close_formspec(player_name, "eye_spy:ui")
|
|
minetest.close_formspec(player_name, "eye_spy:layout_ui")
|
|
|
|
if eye_spy.render and eye_spy.render.hide then
|
|
eye_spy.render.hide(state, player)
|
|
end
|
|
|
|
if eye_spy.render and eye_spy.render.invalidate_layout_sig then
|
|
eye_spy.render.invalidate_layout_sig(player_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Player defaults
|
|
------------------------------------------------------------
|
|
|
|
function eye_spy.ensure_player_defaults(player)
|
|
local meta = player:get_meta()
|
|
|
|
if meta:get_string("es_hud_color") == "" then
|
|
meta:set_string("es_hud_color", "26, 26, 27")
|
|
end
|
|
|
|
if meta:get_string("es_hud_alignment") == "" then
|
|
meta:set_string("es_hud_alignment", "Top-Middle")
|
|
end
|
|
|
|
if meta:get_string("es_hud_health_in") == "" then
|
|
meta:set_string("es_hud_health_in", "Points")
|
|
end
|
|
|
|
if meta:get_string("es_auto_text_color") == "" then
|
|
meta:set_string("es_auto_text_color", "true")
|
|
end
|
|
|
|
if meta:get_string("es_title_color_val") == "" then
|
|
meta:set_int("es_title_color_val", 255)
|
|
end
|
|
|
|
if meta:get_string("es_subtitle_color_val") == "" then
|
|
meta:set_int("es_subtitle_color_val", 255)
|
|
end
|
|
|
|
if meta:get_string("es_footer_color_val") == "" then
|
|
meta:set_int("es_footer_color_val", 255)
|
|
end
|
|
|
|
if meta:get_string("es_line_color_val") == "" then
|
|
meta:set_int("es_line_color_val", 255)
|
|
end
|
|
|
|
if meta:get_string("es_growth_color_val") == "" then
|
|
meta:set_int("es_growth_color_val", 110)
|
|
end
|
|
|
|
if meta:get_string("es_soil_color_val") == "" then
|
|
meta:set_int("es_soil_color_val", 165)
|
|
end
|
|
|
|
if meta:get_string("es_show_growth") == "" then
|
|
meta:set_string("es_show_growth", eye_spy.config.show_growth and "true" or "false")
|
|
end
|
|
|
|
if meta:get_string("es_show_icons") == "" then
|
|
meta:set_string("es_show_icons", eye_spy.config.show_icons and "true" or "false")
|
|
end
|
|
|
|
if meta:get_string("es_layout_target") == "" then
|
|
meta:set_string("es_layout_target", "Global")
|
|
end
|
|
|
|
if meta:get_string("es_layout_step") == "" then
|
|
meta:set_int("es_layout_step", 2)
|
|
end
|
|
|
|
if meta:get_string("es_layout_page") == "" then
|
|
meta:set_string("es_layout_page", "Position")
|
|
end
|
|
|
|
if meta:get_string("es_footer_mode") == "" then
|
|
meta:set_string("es_footer_mode", "compact")
|
|
end
|
|
|
|
if meta:get_string("es_show_coords") == "" then
|
|
meta:set_string("es_show_coords", eye_spy.config.default_show_coords and "true" or "false")
|
|
end
|
|
|
|
if meta:get_string("es_show_light_level") == "" then
|
|
meta:set_string("es_show_light_level", eye_spy.config.default_show_light_level and "true" or "false")
|
|
end
|
|
|
|
if meta:get_string("es_show_spawn_hint") == "" then
|
|
meta:set_string("es_show_spawn_hint", eye_spy.config.default_show_spawn_hint and "true" or "false")
|
|
end
|
|
|
|
if meta:get_string("es_show_liquid_info") == "" then
|
|
meta:set_string("es_show_liquid_info", eye_spy.config.default_show_liquid_info and "true" or "false")
|
|
end
|
|
|
|
if meta:get_string("es_show_dig_time") == "" then
|
|
meta:set_string("es_show_dig_time", eye_spy.config.default_show_dig_time and "true" or "false")
|
|
end
|
|
|
|
if meta:get_string("es_spawn_safe_light_threshold") == "" then
|
|
meta:set_int("es_spawn_safe_light_threshold", 8)
|
|
end
|
|
|
|
-- Initialise hud-enabled flag so the disable check has a reliable baseline.
|
|
if meta:get_string("es_hud_enabled") == "" then
|
|
meta:set_string("es_hud_enabled", "true")
|
|
end
|
|
|
|
eye_spy.apply_player_interval(player)
|
|
|
|
local int_defaults = {
|
|
es_global_offset_x = 0,
|
|
es_global_offset_y = 0,
|
|
es_bg_offset_x = 0,
|
|
es_bg_offset_y = 0,
|
|
es_icon_offset_x = 0,
|
|
es_icon_offset_y = 0,
|
|
es_title_offset_x = 0,
|
|
es_title_offset_y = 0,
|
|
es_subtitle_offset_x = 0,
|
|
es_subtitle_offset_y = 0,
|
|
es_lines_offset_x = 0,
|
|
es_lines_offset_y = 0,
|
|
es_footer_offset_x = 0,
|
|
es_footer_offset_y = 0,
|
|
es_bg_pad_left = 7,
|
|
es_bg_pad_right = 0,
|
|
es_bg_pad_top = 5,
|
|
es_bg_pad_bottom = 0,
|
|
es_bg_extra_w = 0,
|
|
es_bg_extra_h = 0,
|
|
es_bg_scale_x_pct = 100,
|
|
es_bg_scale_y_pct = 100,
|
|
es_icon_size = 56,
|
|
es_title_scale_pct = 100,
|
|
es_subtitle_scale_pct = 100,
|
|
es_line_scale_pct = 100,
|
|
es_footer_scale_pct = 100,
|
|
es_first_line_y_adj = 0,
|
|
es_line_step_adj = -2, -- matches LAYOUT_META_DEFAULTS in ui.lua
|
|
es_footer_nudge_adj = 0,
|
|
es_text_base_x_adj = 0,
|
|
es_icon_base_x_adj = 0,
|
|
es_top_margin_adj = 0,
|
|
es_bottom_margin_adj = 0,
|
|
}
|
|
|
|
for key, value in pairs(int_defaults) do
|
|
if meta:get_string(key) == "" then
|
|
meta:set_int(key, value)
|
|
end
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Core update pipeline
|
|
------------------------------------------------------------
|
|
|
|
local function probe_state_matches(state, player)
|
|
if not state.last_probe_pos or not state.last_probe_dir then
|
|
return false
|
|
end
|
|
|
|
local pos = player:get_pos()
|
|
local dir = player:get_look_dir()
|
|
local dx = math.abs((pos.x or 0) - (state.last_probe_pos.x or 0))
|
|
local dy = math.abs((pos.y or 0) - (state.last_probe_pos.y or 0))
|
|
local dz = math.abs((pos.z or 0) - (state.last_probe_pos.z or 0))
|
|
local ddx = math.abs((dir.x or 0) - (state.last_probe_dir.x or 0))
|
|
local ddy = math.abs((dir.y or 0) - (state.last_probe_dir.y or 0))
|
|
local ddz = math.abs((dir.z or 0) - (state.last_probe_dir.z or 0))
|
|
|
|
return dx < 0.05 and dy < 0.05 and dz < 0.05
|
|
and ddx < 0.01 and ddy < 0.01 and ddz < 0.01
|
|
end
|
|
|
|
local function remember_probe_state(state, player, target)
|
|
local pos = player:get_pos()
|
|
local dir = player:get_look_dir()
|
|
state.last_probe_pos = vector.new(pos)
|
|
state.last_probe_dir = vector.new(dir)
|
|
state.cached_target = target
|
|
end
|
|
|
|
function eye_spy.update_player(player, opts)
|
|
if not player or not eye_spy.target or not eye_spy.render then
|
|
return
|
|
end
|
|
|
|
local perf_on = eye_spy.perf.enabled and minetest.get_us_time
|
|
local update_start_us = perf_on and minetest.get_us_time() or 0
|
|
local options = opts or {}
|
|
local state = eye_spy.get_player_state(player)
|
|
local target
|
|
|
|
if not options.force
|
|
and not options.preview
|
|
and state.cached_target
|
|
and probe_state_matches(state, player)
|
|
and state.skip_static_acquire
|
|
then
|
|
target = state.cached_target
|
|
state.skip_static_acquire = false
|
|
else
|
|
local acquire_start_us = perf_on and minetest.get_us_time() or 0
|
|
target = eye_spy.target.acquire(player, options)
|
|
if perf_on then
|
|
eye_spy.perf_record("target_acquire", minetest.get_us_time() - acquire_start_us)
|
|
end
|
|
|
|
if not options.preview then
|
|
remember_probe_state(state, player, target)
|
|
state.skip_static_acquire = target and target.kind ~= "air"
|
|
end
|
|
end
|
|
|
|
if not target or target.kind == "air" then
|
|
local now_us = minetest.get_us_time and minetest.get_us_time() or 0
|
|
|
|
eye_spy.render.hide(state, player)
|
|
state.last_target_key = nil
|
|
state.last_render_key = nil
|
|
state.cached_target = nil
|
|
state.skip_static_acquire = false
|
|
|
|
if now_us > 0 and not state.hud_hidden_since_us then
|
|
state.hud_hidden_since_us = now_us
|
|
end
|
|
|
|
if perf_on then
|
|
eye_spy.perf_record("update_player_total", minetest.get_us_time() - update_start_us)
|
|
end
|
|
return
|
|
end
|
|
|
|
local build_start_us = perf_on and minetest.get_us_time() or 0
|
|
local view_model = eye_spy.render.build_view_model(player, target, options)
|
|
if perf_on then
|
|
eye_spy.perf_record("build_view_model", minetest.get_us_time() - build_start_us)
|
|
end
|
|
|
|
if not options.force and view_model.key == state.last_render_key then
|
|
state.last_target_key = target.key
|
|
eye_spy.perf_inc_counter("render_skip_same_key")
|
|
if perf_on then
|
|
eye_spy.perf_record("update_player_total", minetest.get_us_time() - update_start_us)
|
|
end
|
|
return
|
|
end
|
|
|
|
local render_start_us = perf_on and minetest.get_us_time() or 0
|
|
eye_spy.render.apply(state, player, view_model)
|
|
if perf_on then
|
|
eye_spy.perf_record("render_apply", minetest.get_us_time() - render_start_us)
|
|
end
|
|
|
|
state.last_target_key = target.key
|
|
state.last_render_key = view_model.key
|
|
state.hud_hidden_since_us = nil
|
|
state.last_recover_try_us = 0
|
|
|
|
if perf_on then
|
|
eye_spy.perf_record("update_player_total", minetest.get_us_time() - update_start_us)
|
|
end
|
|
end
|
|
|
|
-- Thin wrapper used by the UI and preview system.
|
|
function eye_spy.get_hud(player, data)
|
|
eye_spy.update_player(player, data or {})
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Main step function
|
|
------------------------------------------------------------
|
|
|
|
function eye_spy.step(dtime)
|
|
local perf_on = eye_spy.perf.enabled and minetest.get_us_time
|
|
local step_start_us = perf_on and minetest.get_us_time() or 0
|
|
local now_us = minetest.get_us_time and minetest.get_us_time() or 0
|
|
|
|
for _, player in ipairs(minetest.get_connected_players()) do
|
|
local state = eye_spy.get_player_state(player)
|
|
local player_name = player:get_player_name()
|
|
local pending_preview = eye_spy.pending_preview_updates[player_name]
|
|
|
|
-- Service pending edit-mode preview updates.
|
|
if state.edit_mode and pending_preview then
|
|
local due_at = pending_preview.due_at_us or 0
|
|
|
|
if pending_preview.force or due_at <= now_us then
|
|
eye_spy.pending_preview_updates[player_name] = nil
|
|
eye_spy.update_player(player, {
|
|
preview = true,
|
|
preview_type = pending_preview.preview_type,
|
|
force = pending_preview.force == true,
|
|
})
|
|
end
|
|
end
|
|
|
|
-- Service pending layout-UI refreshes.
|
|
local pending_layout_refresh = eye_spy.pending_layout_refreshes[player_name]
|
|
if state.edit_mode
|
|
and pending_layout_refresh
|
|
and pending_layout_refresh.due_at_us <= now_us
|
|
then
|
|
eye_spy.pending_layout_refreshes[player_name] = nil
|
|
minetest.show_formspec(player_name, "eye_spy:layout_ui", eye_spy.get_layout_ui(player))
|
|
end
|
|
|
|
if not state.edit_mode then
|
|
local meta = player:get_meta()
|
|
|
|
-- Check both server-admin and player-self disable flags.
|
|
local hud_disabled = eye_spy.is_player_server_disabled(player_name)
|
|
or (meta:get_string("es_hud_enabled") == "false")
|
|
|
|
if hud_disabled then
|
|
if state.last_render_key then
|
|
eye_spy.render.hide(state, player)
|
|
state.last_target_key = nil
|
|
state.last_render_key = nil
|
|
state.cached_target = nil
|
|
state.skip_static_acquire = false
|
|
end
|
|
state.timer = 0
|
|
goto continue_player
|
|
end
|
|
|
|
local interval_ms = meta:get_int("es_update_interval_ms")
|
|
local interval = eye_spy.get_server_default_interval_ms() / 1000
|
|
|
|
if interval_ms > 0 then
|
|
interval = interval_ms / 1000
|
|
end
|
|
|
|
interval = math.max(0.05, math.min(5, interval))
|
|
state.timer = state.timer + math.min(dtime, interval)
|
|
|
|
if state.timer >= interval then
|
|
state.timer = 0
|
|
eye_spy.update_player(player, { preview = false })
|
|
end
|
|
|
|
-- Focus-loss / self-heal: if the HUD appears stuck hidden while we still
|
|
-- have a valid cached target, force an occasional refresh attempt.
|
|
if state.hud_hidden_since_us
|
|
and state.cached_target
|
|
and state.cached_target.kind ~= "air"
|
|
then
|
|
local hidden_for_us = now_us - state.hud_hidden_since_us
|
|
local recover_cooldown_us = 900000
|
|
|
|
if hidden_for_us >= 1200000
|
|
and (state.last_recover_try_us or 0) + recover_cooldown_us <= now_us
|
|
then
|
|
state.last_recover_try_us = now_us
|
|
eye_spy.perf_inc_counter("hud_recover_forced_refresh")
|
|
eye_spy.update_player(player, { preview = false, force = true })
|
|
end
|
|
end
|
|
end
|
|
|
|
::continue_player::
|
|
end
|
|
|
|
if perf_on then
|
|
eye_spy.perf_record("step_total", minetest.get_us_time() - step_start_us)
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- HUD cleanup
|
|
------------------------------------------------------------
|
|
|
|
function eye_spy.remove_huds(player)
|
|
if eye_spy.render then eye_spy.render.remove(player) end
|
|
end
|