1174 lines
46 KiB
Lua
1174 lines
46 KiB
Lua
--ui
|
|
|
|
local S = minetest.get_translator(minetest.get_current_modname())
|
|
|
|
local old_values = {}
|
|
local index = {}
|
|
local change_preview_type = {}
|
|
local FOOTER_MODE_OPTIONS = { "Compact", "Advanced" }
|
|
local SPAWN_THRESHOLD_OPTIONS = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" }
|
|
local HUD_ALIGNMENT_OPTIONS = {
|
|
"Top-Middle",
|
|
"Top-Left",
|
|
"Top-Right",
|
|
"Middle-Left",
|
|
"Middle-Right",
|
|
"Bottom-Left",
|
|
"Bottom-Right",
|
|
}
|
|
local PREVIEW_DEBOUNCE_SECONDS = 0.4
|
|
local LAYOUT_REBUILD_DRAG_IDLE_US = 450000
|
|
local LAYOUT_REBUILD_NUDGE_IDLE_US = 60000
|
|
local LAYOUT_VAL_AS_NUDGE_GAP_US = 140000
|
|
|
|
local layout_last_chg_us = {}
|
|
|
|
eye_spy.ui_drafts = eye_spy.ui_drafts or {}
|
|
|
|
local LAYOUT_TARGET_OPTIONS = { "Global", "BG", "Icon", "Title", "Subtitle", "Lines", "Footer" }
|
|
local LAYOUT_STEP_OPTIONS = { 1, 2, 4, 8 }
|
|
local LAYOUT_PAGE_OPTIONS = { "Position", "Panel", "Text" }
|
|
local LAYOUT_META_DEFAULTS = {
|
|
es_layout_page = "Position",
|
|
es_layout_target = "Global",
|
|
es_layout_step = 2,
|
|
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, -- Fix 4: canonical default is -2
|
|
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,
|
|
}
|
|
|
|
local LAYOUT_TARGET_KEYS = {
|
|
Global = { x = "es_global_offset_x", y = "es_global_offset_y" },
|
|
BG = { x = "es_bg_offset_x", y = "es_bg_offset_y" },
|
|
Icon = { x = "es_icon_offset_x", y = "es_icon_offset_y" },
|
|
Title = { x = "es_title_offset_x", y = "es_title_offset_y" },
|
|
Subtitle = { x = "es_subtitle_offset_x", y = "es_subtitle_offset_y" },
|
|
Lines = { x = "es_lines_offset_x", y = "es_lines_offset_y" },
|
|
Footer = { x = "es_footer_offset_x", y = "es_footer_offset_y" },
|
|
}
|
|
|
|
-- Fix 5: Global reset group now includes es_text_base_x_adj and es_icon_base_x_adj
|
|
local LAYOUT_RESET_GROUPS = {
|
|
Global = { "es_global_offset_x", "es_global_offset_y", "es_top_margin_adj", "es_bottom_margin_adj",
|
|
"es_text_base_x_adj", "es_icon_base_x_adj" },
|
|
BG = {
|
|
"es_bg_offset_x",
|
|
"es_bg_offset_y",
|
|
"es_bg_pad_left",
|
|
"es_bg_pad_right",
|
|
"es_bg_pad_top",
|
|
"es_bg_pad_bottom",
|
|
"es_bg_extra_w",
|
|
"es_bg_extra_h",
|
|
"es_bg_scale_x_pct",
|
|
"es_bg_scale_y_pct",
|
|
},
|
|
Icon = { "es_icon_offset_x", "es_icon_offset_y", "es_icon_size", "es_icon_base_x_adj" },
|
|
Title = { "es_title_offset_x", "es_title_offset_y", "es_title_scale_pct" },
|
|
Subtitle = { "es_subtitle_offset_x", "es_subtitle_offset_y", "es_subtitle_scale_pct" },
|
|
Lines = {
|
|
"es_lines_offset_x",
|
|
"es_lines_offset_y",
|
|
"es_line_scale_pct",
|
|
"es_first_line_y_adj",
|
|
"es_line_step_adj",
|
|
"es_text_base_x_adj",
|
|
},
|
|
Footer = { "es_footer_offset_x", "es_footer_offset_y", "es_footer_scale_pct", "es_footer_nudge_adj" },
|
|
}
|
|
|
|
local LAYOUT_PAGE_SLIDERS = {
|
|
Position = {
|
|
["*"] = {
|
|
{ label = "Offset X", key = "$target_x", min = -400, max = 400, x = 0.7, y = 3.7, width = 4.0 },
|
|
{ label = "Offset Y", key = "$target_y", min = -400, max = 400, x = 5.0, y = 3.7, width = 4.0 },
|
|
},
|
|
Global = {
|
|
{ label = "Top Margin", key = "es_top_margin_adj", min = -50, max = 120, x = 9.3, y = 3.7, width = 4.0 },
|
|
{ label = "Bottom Margin", key = "es_bottom_margin_adj", min = -50, max = 140, x = 0.7, y = 5.1, width = 4.0 },
|
|
{ label = "Text Base X", key = "es_text_base_x_adj", min = -180, max = 220, x = 5.0, y = 5.1, width = 4.0 },
|
|
{ label = "Icon Base X", key = "es_icon_base_x_adj", min = -180, max = 220, x = 9.3, y = 5.1, width = 4.0 },
|
|
},
|
|
},
|
|
Panel = {
|
|
BG = {
|
|
{ label = "Pad Left", key = "es_bg_pad_left", min = -200, max = 400, x = 0.7, y = 3.35, width = 4.0 },
|
|
{ label = "Pad Right", key = "es_bg_pad_right", min = -200, max = 400, x = 5.0, y = 3.35, width = 4.0 },
|
|
{ label = "Pad Top", key = "es_bg_pad_top", min = -200, max = 400, x = 9.3, y = 3.35, width = 4.0 },
|
|
{ label = "Pad Bottom", key = "es_bg_pad_bottom", min = -200, max = 400, x = 0.7, y = 4.75, width = 4.0 },
|
|
{ label = "Extra W", key = "es_bg_extra_w", min = -300, max = 500, x = 5.0, y = 4.75, width = 4.0 },
|
|
{ label = "Extra H", key = "es_bg_extra_h", min = -300, max = 500, x = 9.3, y = 4.75, width = 4.0 },
|
|
{ label = "Scale X%", key = "es_bg_scale_x_pct", min = 25, max = 300, x = 0.7, y = 6.15, width = 4.0 },
|
|
{ label = "Scale Y%", key = "es_bg_scale_y_pct", min = 25, max = 300, x = 5.0, y = 6.15, width = 4.0 },
|
|
{ label = "BG Offset X", key = "es_bg_offset_x", min = -400, max = 400, x = 9.3, y = 6.15, width = 4.0 },
|
|
{ label = "BG Offset Y", key = "es_bg_offset_y", min = -400, max = 400, x = 0.7, y = 7.55, width = 4.0 },
|
|
},
|
|
Icon = {
|
|
{ label = "Icon Offset X", key = "es_icon_offset_x", min = -400, max = 400, x = 0.7, y = 3.35, width = 4.0 },
|
|
{ label = "Icon Offset Y", key = "es_icon_offset_y", min = -400, max = 400, x = 5.0, y = 3.35, width = 4.0 },
|
|
{ label = "Icon Size", key = "es_icon_size", min = 16, max = 128, x = 9.3, y = 3.35, width = 4.0 },
|
|
{ label = "Icon Base X", key = "es_icon_base_x_adj", min = -180, max = 220, x = 0.7, y = 4.75, width = 4.0 },
|
|
},
|
|
},
|
|
Text = {
|
|
Title = {
|
|
{ label = "Title Scale", key = "es_title_scale_pct", min = 40, max = 300, x = 0.7, y = 3.35, width = 4.0 },
|
|
{ label = "Title Off X", key = "es_title_offset_x", min = -400, max = 400, x = 5.0, y = 3.35, width = 4.0 },
|
|
{ label = "Title Off Y", key = "es_title_offset_y", min = -400, max = 400, x = 9.3, y = 3.35, width = 4.0 },
|
|
},
|
|
Subtitle = {
|
|
{ label = "Main Scale", key = "es_subtitle_scale_pct", min = 40, max = 300, x = 0.7, y = 3.35, width = 4.0 },
|
|
{ label = "Main Off X", key = "es_subtitle_offset_x", min = -400, max = 400, x = 5.0, y = 3.35, width = 4.0 },
|
|
{ label = "Main Off Y", key = "es_subtitle_offset_y", min = -400, max = 400, x = 9.3, y = 3.35, width = 4.0 },
|
|
},
|
|
Lines = {
|
|
{ label = "Line Scale", key = "es_line_scale_pct", min = 40, max = 300, x = 0.7, y = 3.35, width = 4.0 },
|
|
{ label = "Lines Off X", key = "es_lines_offset_x", min = -400, max = 400, x = 5.0, y = 3.35, width = 4.0 },
|
|
{ label = "Lines Off Y", key = "es_lines_offset_y", min = -400, max = 400, x = 9.3, y = 3.35, width = 4.0 },
|
|
{ label = "First Line Y", key = "es_first_line_y_adj", min = -120, max = 220, x = 0.7, y = 4.75, width = 4.0 },
|
|
{ label = "Line Step", key = "es_line_step_adj", min = -8, max = 50, x = 5.0, y = 4.75, width = 4.0 },
|
|
{ label = "Text Base X", key = "es_text_base_x_adj", min = -180, max = 220, x = 9.3, y = 4.75, width = 4.0 },
|
|
},
|
|
Footer = {
|
|
{ label = "Footer Scale", key = "es_footer_scale_pct", min = 40, max = 300, x = 0.7, y = 3.35, width = 4.0 },
|
|
{ label = "Footer Off X", key = "es_footer_offset_x", min = -400, max = 400, x = 5.0, y = 3.35, width = 4.0 },
|
|
{ label = "Footer Off Y", key = "es_footer_offset_y", min = -400, max = 400, x = 9.3, y = 3.35, width = 4.0 },
|
|
{ label = "Footer Nudge", key = "es_footer_nudge_adj", min = -120, max = 120, x = 0.7, y = 4.75, width = 4.0 },
|
|
},
|
|
},
|
|
}
|
|
|
|
local LAYOUT_SLIDER_BOUNDS = {
|
|
es_global_offset_x = { min = -400, max = 400 },
|
|
es_global_offset_y = { min = -400, max = 400 },
|
|
es_bg_offset_x = { min = -400, max = 400 },
|
|
es_bg_offset_y = { min = -400, max = 400 },
|
|
es_icon_offset_x = { min = -400, max = 400 },
|
|
es_icon_offset_y = { min = -400, max = 400 },
|
|
es_title_offset_x = { min = -400, max = 400 },
|
|
es_title_offset_y = { min = -400, max = 400 },
|
|
es_subtitle_offset_x = { min = -400, max = 400 },
|
|
es_subtitle_offset_y = { min = -400, max = 400 },
|
|
es_lines_offset_x = { min = -400, max = 400 },
|
|
es_lines_offset_y = { min = -400, max = 400 },
|
|
es_footer_offset_x = { min = -400, max = 400 },
|
|
es_footer_offset_y = { min = -400, max = 400 },
|
|
es_bg_pad_left = { min = -200, max = 400 },
|
|
es_bg_pad_right = { min = -200, max = 400 },
|
|
es_bg_pad_top = { min = -200, max = 400 },
|
|
es_bg_pad_bottom = { min = -200, max = 400 },
|
|
es_bg_extra_w = { min = -300, max = 500 },
|
|
es_bg_extra_h = { min = -300, max = 500 },
|
|
es_bg_scale_x_pct = { min = 25, max = 300 },
|
|
es_bg_scale_y_pct = { min = 25, max = 300 },
|
|
es_icon_size = { min = 16, max = 128 },
|
|
es_title_scale_pct = { min = 40, max = 300 },
|
|
es_subtitle_scale_pct = { min = 40, max = 300 },
|
|
es_line_scale_pct = { min = 40, max = 300 },
|
|
es_footer_scale_pct = { min = 40, max = 300 },
|
|
es_first_line_y_adj = { min = -120, max = 220 },
|
|
es_line_step_adj = { min = -8, max = 50 },
|
|
es_footer_nudge_adj = { min = -120, max = 120 },
|
|
es_text_base_x_adj = { min = -180, max = 220 },
|
|
es_icon_base_x_adj = { min = -180, max = 220 },
|
|
es_top_margin_adj = { min = -50, max = 120 },
|
|
es_bottom_margin_adj = { min = -50, max = 140 },
|
|
}
|
|
|
|
local DRAFT_KEY_TYPES = {
|
|
es_hud_color = "string",
|
|
es_hud_alignment = "string",
|
|
es_hud_health_in = "string",
|
|
es_auto_text_color = "string",
|
|
es_title_color_val = "int",
|
|
es_subtitle_color_val = "int",
|
|
es_footer_color_val = "int",
|
|
es_line_color_val = "int",
|
|
es_growth_color_val = "int",
|
|
es_soil_color_val = "int",
|
|
es_footer_mode = "string",
|
|
es_show_coords = "string",
|
|
es_show_light_level = "string",
|
|
es_show_spawn_hint = "string",
|
|
es_show_liquid_info = "string",
|
|
es_show_dig_time = "string",
|
|
es_spawn_safe_light_threshold = "int",
|
|
es_layout_page = "string",
|
|
es_layout_target = "string",
|
|
es_layout_step = "int",
|
|
es_hud_enabled = "string", -- Fix 6: add es_hud_enabled to draft key types
|
|
}
|
|
|
|
for key, default_value in pairs(LAYOUT_META_DEFAULTS) do
|
|
if type(default_value) == "number" then
|
|
DRAFT_KEY_TYPES[key] = "int"
|
|
else
|
|
DRAFT_KEY_TYPES[key] = "string"
|
|
end
|
|
end
|
|
|
|
local function get_player_draft(player_name, create)
|
|
if not player_name then
|
|
return nil
|
|
end
|
|
|
|
local draft = eye_spy.ui_drafts[player_name]
|
|
|
|
if not draft and create then
|
|
draft = {}
|
|
eye_spy.ui_drafts[player_name] = draft
|
|
end
|
|
|
|
return draft
|
|
end
|
|
|
|
local function clear_player_draft(player_name)
|
|
if player_name then
|
|
eye_spy.ui_drafts[player_name] = nil
|
|
end
|
|
end
|
|
|
|
local function set_draft_value(player_name, key, value)
|
|
local draft = get_player_draft(player_name, true)
|
|
|
|
if draft then
|
|
draft[key] = value
|
|
end
|
|
end
|
|
|
|
local function get_effective_string(meta, player_name, key)
|
|
local draft = get_player_draft(player_name, false)
|
|
|
|
if draft and draft[key] ~= nil then
|
|
return tostring(draft[key])
|
|
end
|
|
|
|
return meta:get_string(key)
|
|
end
|
|
|
|
local function get_effective_int(meta, player_name, key)
|
|
local draft = get_player_draft(player_name, false)
|
|
|
|
if draft and draft[key] ~= nil then
|
|
return tonumber(draft[key]) or 0
|
|
end
|
|
|
|
return meta:get_int(key)
|
|
end
|
|
|
|
local function commit_player_draft(player)
|
|
if not player then
|
|
return
|
|
end
|
|
|
|
local player_name = player:get_player_name()
|
|
local draft = get_player_draft(player_name, false)
|
|
|
|
if not draft then
|
|
return
|
|
end
|
|
|
|
local meta = player:get_meta()
|
|
|
|
for key, value in pairs(draft) do
|
|
local key_type = DRAFT_KEY_TYPES[key]
|
|
|
|
if key_type == "int" then
|
|
meta:set_int(key, tonumber(value) or 0)
|
|
elseif key_type == "string" then
|
|
meta:set_string(key, tostring(value))
|
|
end
|
|
end
|
|
|
|
clear_player_draft(player_name)
|
|
|
|
if eye_spy.render and eye_spy.render.invalidate_layout_sig then
|
|
eye_spy.render.invalidate_layout_sig(player_name)
|
|
end
|
|
end
|
|
|
|
local function get_option_index(options, value)
|
|
for i, option in ipairs(options) do
|
|
if tostring(option) == tostring(value) then
|
|
return i
|
|
end
|
|
end
|
|
|
|
return 1
|
|
end
|
|
|
|
-- Fix 2: save_old_values now includes show_growth and show_icons
|
|
local function save_old_values(meta)
|
|
local values = {
|
|
color = meta:get_string("es_hud_color"),
|
|
hud_alignment = meta:get_string("es_hud_alignment"),
|
|
health_in = meta:get_string("es_hud_health_in"),
|
|
auto_text_color = meta:get_string("es_auto_text_color"),
|
|
title_color_val = meta:get_int("es_title_color_val"),
|
|
subtitle_color_val = meta:get_int("es_subtitle_color_val"),
|
|
footer_color_val = meta:get_int("es_footer_color_val"),
|
|
line_color_val = meta:get_int("es_line_color_val"),
|
|
growth_color_val = meta:get_int("es_growth_color_val"),
|
|
soil_color_val = meta:get_int("es_soil_color_val"),
|
|
footer_mode = meta:get_string("es_footer_mode"),
|
|
show_coords = meta:get_string("es_show_coords"),
|
|
show_light_level = meta:get_string("es_show_light_level"),
|
|
show_spawn_hint = meta:get_string("es_show_spawn_hint"),
|
|
show_liquid_info = meta:get_string("es_show_liquid_info"),
|
|
show_dig_time = meta:get_string("es_show_dig_time"),
|
|
spawn_safe_light_threshold = meta:get_int("es_spawn_safe_light_threshold"),
|
|
show_growth = meta:get_string("es_show_growth"),
|
|
show_icons = meta:get_string("es_show_icons"),
|
|
layout = {},
|
|
}
|
|
|
|
for key, default_value in pairs(LAYOUT_META_DEFAULTS) do
|
|
if type(default_value) == "number" then
|
|
values.layout[key] = meta:get_int(key)
|
|
else
|
|
values.layout[key] = meta:get_string(key)
|
|
end
|
|
end
|
|
|
|
return values
|
|
end
|
|
|
|
local function restore_old_values(meta, values)
|
|
if not values then
|
|
return
|
|
end
|
|
|
|
meta:set_string("es_hud_color", values.color)
|
|
meta:set_string("es_hud_alignment", values.hud_alignment)
|
|
meta:set_string("es_hud_health_in", values.health_in)
|
|
meta:set_string("es_auto_text_color", values.auto_text_color)
|
|
meta:set_int("es_title_color_val", values.title_color_val)
|
|
meta:set_int("es_subtitle_color_val", values.subtitle_color_val)
|
|
meta:set_int("es_footer_color_val", values.footer_color_val)
|
|
meta:set_int("es_line_color_val", values.line_color_val)
|
|
meta:set_int("es_growth_color_val", values.growth_color_val)
|
|
meta:set_int("es_soil_color_val", values.soil_color_val)
|
|
meta:set_string("es_footer_mode", values.footer_mode or "compact")
|
|
meta:set_string("es_show_coords", values.show_coords or "false")
|
|
meta:set_string("es_show_light_level", values.show_light_level or "true")
|
|
meta:set_string("es_show_spawn_hint", values.show_spawn_hint or "false")
|
|
meta:set_string("es_show_liquid_info", values.show_liquid_info or "true")
|
|
meta:set_string("es_show_dig_time", values.show_dig_time or "true")
|
|
meta:set_int("es_spawn_safe_light_threshold", values.spawn_safe_light_threshold or 8)
|
|
meta:set_string("es_show_growth", values.show_growth or "true")
|
|
meta:set_string("es_show_icons", values.show_icons or "true")
|
|
|
|
for key, default_value in pairs(LAYOUT_META_DEFAULTS) do
|
|
local saved_value = values.layout and values.layout[key]
|
|
|
|
if type(default_value) == "number" then
|
|
meta:set_int(key, tonumber(saved_value) or default_value)
|
|
else
|
|
meta:set_string(key, tostring(saved_value or default_value))
|
|
end
|
|
end
|
|
end
|
|
|
|
local function reset_layout_target(meta, target)
|
|
local keys = LAYOUT_RESET_GROUPS[target]
|
|
|
|
if not keys then
|
|
return
|
|
end
|
|
|
|
for _, key in ipairs(keys) do
|
|
local default_value = LAYOUT_META_DEFAULTS[key]
|
|
|
|
if type(default_value) == "number" then
|
|
meta:set_int(key, default_value)
|
|
else
|
|
meta:set_string(key, default_value)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function reset_layout_all(meta)
|
|
for key, default_value in pairs(LAYOUT_META_DEFAULTS) do
|
|
if type(default_value) == "number" then
|
|
meta:set_int(key, default_value)
|
|
else
|
|
meta:set_string(key, default_value)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function update_preview(player, preview_type, force)
|
|
local player_name = player and player:get_player_name()
|
|
|
|
if not player_name then
|
|
return
|
|
end
|
|
|
|
if force then
|
|
eye_spy.pending_preview_updates[player_name] = nil
|
|
eye_spy.get_hud(player, {
|
|
preview = true,
|
|
preview_type = preview_type,
|
|
force = true,
|
|
})
|
|
return
|
|
end
|
|
|
|
local due_at_us = 0
|
|
local now_us = minetest.get_us_time and minetest.get_us_time() or 0
|
|
due_at_us = now_us + math.floor(PREVIEW_DEBOUNCE_SECONDS * 1000000)
|
|
|
|
eye_spy.pending_preview_updates[player_name] = {
|
|
preview_type = preview_type,
|
|
force = false,
|
|
due_at_us = due_at_us,
|
|
}
|
|
end
|
|
|
|
local function parse_scroll_input(raw, fallback)
|
|
local parsed
|
|
local event_type
|
|
|
|
if type(raw) == "string" and minetest.explode_scrollbar_event then
|
|
local event = minetest.explode_scrollbar_event(raw)
|
|
|
|
if event and event.value then
|
|
parsed = tonumber(event.value)
|
|
event_type = event.type
|
|
end
|
|
end
|
|
|
|
if parsed == nil then
|
|
parsed = tonumber((tostring(raw) or ""):match("-?%d+"))
|
|
end
|
|
|
|
return parsed or fallback, event_type
|
|
end
|
|
|
|
local function layout_slider(label, field_name, value, minv, maxv, x, y, width)
|
|
local slider_width = width or 4.2
|
|
|
|
return "label[" .. x .. "," .. y .. ";" .. label .. ": " .. value .. "]" ..
|
|
"scrollbaroptions[min=" .. minv .. ";max=" .. maxv .. ";smallstep=1]" ..
|
|
"scrollbar[" .. x .. "," .. (y + 0.35) .. ";" .. slider_width .. ",0.5;horizontal;" ..
|
|
field_name .. ";" .. value .. "]"
|
|
end
|
|
|
|
local function resolve_layout_slider_key(entry, target_keys)
|
|
if entry.key == "$target_x" then
|
|
return target_keys.x
|
|
end
|
|
|
|
if entry.key == "$target_y" then
|
|
return target_keys.y
|
|
end
|
|
|
|
return entry.key
|
|
end
|
|
|
|
local function get_layout_page_entries(selected_page, selected_target)
|
|
local page = LAYOUT_PAGE_SLIDERS[selected_page] or LAYOUT_PAGE_SLIDERS.Position
|
|
local entries = {}
|
|
local common = page["*"] or {}
|
|
local specific = page[selected_target] or {}
|
|
|
|
for _, entry in ipairs(common) do
|
|
entries[#entries + 1] = entry
|
|
end
|
|
|
|
for _, entry in ipairs(specific) do
|
|
entries[#entries + 1] = entry
|
|
end
|
|
|
|
return entries
|
|
end
|
|
|
|
local function build_layout_page_content(meta, player_name, selected_page, selected_target, target_keys)
|
|
local page_entries = get_layout_page_entries(selected_page, selected_target)
|
|
local parts = {
|
|
"box[0.4,3.0;14.7,6.8;#00000020]",
|
|
}
|
|
|
|
if selected_page == "Position" then
|
|
parts[#parts + 1] = "label[0.7,3.25;" .. S("Selected Target Position") .. "]"
|
|
elseif #page_entries == 0 then
|
|
parts[#parts + 1] = "label[0.7,3.45;" .. S("No controls for this target on this page") .. "]"
|
|
end
|
|
|
|
for _, entry in ipairs(page_entries) do
|
|
local key = resolve_layout_slider_key(entry, target_keys)
|
|
local value = get_effective_int(meta, player_name, key)
|
|
|
|
parts[#parts + 1] = layout_slider(
|
|
S(entry.label),
|
|
key,
|
|
value,
|
|
entry.min,
|
|
entry.max,
|
|
entry.x,
|
|
entry.y,
|
|
entry.width
|
|
)
|
|
end
|
|
|
|
return table.concat(parts)
|
|
end
|
|
|
|
function eye_spy.get_layout_ui(player)
|
|
local player_name = player:get_player_name()
|
|
local meta = player:get_meta()
|
|
local selected_page = get_effective_string(meta, player_name, "es_layout_page")
|
|
local selected_target = get_effective_string(meta, player_name, "es_layout_target")
|
|
|
|
if selected_page == "" then
|
|
selected_page = "Position"
|
|
meta:set_string("es_layout_page", selected_page)
|
|
end
|
|
|
|
if selected_target == "" then
|
|
selected_target = "Global"
|
|
meta:set_string("es_layout_target", selected_target)
|
|
end
|
|
|
|
local step = get_effective_int(meta, player_name, "es_layout_step")
|
|
|
|
if step <= 0 then
|
|
step = 2
|
|
meta:set_int("es_layout_step", step)
|
|
end
|
|
|
|
local target_keys = LAYOUT_TARGET_KEYS[selected_target] or LAYOUT_TARGET_KEYS.Global
|
|
local page_content = build_layout_page_content(meta, player_name, selected_page, selected_target, target_keys)
|
|
|
|
local formspec = (
|
|
"formspec_version[6]" ..
|
|
"size[15.5,11.2]" ..
|
|
"no_prepend[]" ..
|
|
"bgcolor[#0F1319C0;false]" ..
|
|
"label[0.4,0.4;" .. S("Advanced Layout Editor") .. "]" ..
|
|
"label[0.4,0.85;" .. S("Use pages to edit layout without crowding the form") .. "]" ..
|
|
"dropdown[0.4,1.25;2.5,0.8;layout_page;Position,Panel,Text;" .. get_option_index(LAYOUT_PAGE_OPTIONS, selected_page) .. ";false]" ..
|
|
"dropdown[3.1,1.25;2.6,0.8;layout_target;Global,BG,Icon,Title,Subtitle,Lines,Footer;" .. get_option_index(LAYOUT_TARGET_OPTIONS, selected_target) .. ";false]" ..
|
|
"dropdown[5.9,1.25;1.4,0.8;layout_step;1,2,4,8;" .. get_option_index(LAYOUT_STEP_OPTIONS, step) .. ";false]" ..
|
|
"label[7.7,1.33;" .. S("Step") .. ": " .. step .. "]" ..
|
|
"button[10.0,1.05;2.2,0.8;reset_layout_target;" .. S("Reset Target") .. "]" ..
|
|
"button[12.4,1.05;2.0,0.8;reset_layout_all;" .. S("Reset All") .. "]" ..
|
|
page_content ..
|
|
"button[11.6,10.05;1.7,0.8;back_main;" .. S("Back") .. "]" ..
|
|
"label[0.5,10.2;" .. S("Tips: use step 1 for fine tuning and drag sliders for live preview") .. "]"
|
|
)
|
|
|
|
return formspec
|
|
end
|
|
|
|
minetest.register_on_joinplayer(function(player)
|
|
index[player:get_player_name()] = 0
|
|
end)
|
|
|
|
-- Fix 3: only restore old values when edit_mode is active; always clean up old_values
|
|
minetest.register_on_dieplayer(function(player)
|
|
local player_name = player:get_player_name()
|
|
local meta = player:get_meta()
|
|
local state = eye_spy.get_player_state(player)
|
|
if state.edit_mode and old_values[player_name] then
|
|
restore_old_values(meta, old_values[player_name])
|
|
end
|
|
old_values[player_name] = nil -- always clean up
|
|
eye_spy.exit_edit_mode(player)
|
|
end)
|
|
|
|
function eye_spy.get_ui(player, data)
|
|
local player_name = player:get_player_name()
|
|
|
|
index[player_name] = index[player_name] + 1
|
|
|
|
local player_index = index[player_name]
|
|
|
|
local meta = player:get_meta()
|
|
local string_to_num = {
|
|
health_in = {
|
|
["Points"] = 1, ["Hearts"] = 2
|
|
}
|
|
}
|
|
|
|
if not data or not data.reopen then
|
|
change_preview_type[player_name] = "Entity"
|
|
old_values[player_name] = save_old_values(meta)
|
|
end
|
|
|
|
-- Fix 7: detect admin-disable state for warning display
|
|
local admin_disabled = eye_spy.is_player_server_disabled(player_name)
|
|
|
|
local r, g, b = eye_spy.get_rgb(get_effective_string(meta, player_name, "es_hud_color"))
|
|
local hud_alignment = get_effective_string(meta, player_name, "es_hud_alignment")
|
|
local health_in = get_effective_string(meta, player_name, "es_hud_health_in")
|
|
local auto_text_color = get_effective_string(meta, player_name, "es_auto_text_color") ~= "false"
|
|
local show_light_level = get_effective_string(meta, player_name, "es_show_light_level") ~= "false"
|
|
local show_spawn_hint = get_effective_string(meta, player_name, "es_show_spawn_hint") ~= "false"
|
|
local show_liquid_info = get_effective_string(meta, player_name, "es_show_liquid_info") ~= "false"
|
|
local show_dig_time = get_effective_string(meta, player_name, "es_show_dig_time") ~= "false"
|
|
local show_growth = get_effective_string(meta, player_name, "es_show_growth") ~= "false"
|
|
local show_icons = get_effective_string(meta, player_name, "es_show_icons") ~= "false"
|
|
local show_coords = get_effective_string(meta, player_name, "es_show_coords") ~= "false"
|
|
-- Fix 6: read hud_enabled from effective meta/draft
|
|
local hud_enabled = get_effective_string(meta, player_name, "es_hud_enabled") ~= "false"
|
|
local footer_mode = get_effective_string(meta, player_name, "es_footer_mode") == "advanced" and "Advanced" or
|
|
"Compact"
|
|
local spawn_threshold = math.max(0,
|
|
math.min(15, get_effective_int(meta, player_name, "es_spawn_safe_light_threshold")))
|
|
local footer_mode_idx = get_option_index(FOOTER_MODE_OPTIONS, footer_mode)
|
|
local spawn_threshold_idx = get_option_index(SPAWN_THRESHOLD_OPTIONS, tostring(spawn_threshold))
|
|
local title_color_val = get_effective_int(meta, player_name, "es_title_color_val")
|
|
local subtitle_color_val = get_effective_int(meta, player_name, "es_subtitle_color_val")
|
|
local footer_color_val = get_effective_int(meta, player_name, "es_footer_color_val")
|
|
local line_color_val = get_effective_int(meta, player_name, "es_line_color_val")
|
|
local growth_color_val = get_effective_int(meta, player_name, "es_growth_color_val")
|
|
local soil_color_val = get_effective_int(meta, player_name, "es_soil_color_val")
|
|
|
|
local formspec = table.concat({
|
|
"formspec_version[6]",
|
|
"size[13.4,12.5]",
|
|
"no_prepend[]",
|
|
"bgcolor[#0F1319C0;false]",
|
|
"box[0.2,0.2;13.0,0.5;#1C2433C0]",
|
|
"label[0.45,0.28;Eye Spy]",
|
|
-- Fix 8: show what is currently being previewed directly (no inversion)
|
|
"label[11.4,0.28;" .. S("Preview") .. ": " .. S(change_preview_type[player_name]) .. "]",
|
|
-- Fix 7: admin-disable warning banner
|
|
admin_disabled and ("box[0.3,0.75;12.8,0.45;#7A2020C0]" ..
|
|
"label[0.55,0.82;" .. minetest.formspec_escape(S("Eye Spy is currently disabled for you by the server admin.")) .. "]") or
|
|
"",
|
|
"box[0.3,0.9;6.35,3.9;#00000020]",
|
|
"label[0.55,1.15;" .. S("Background Color") .. "]",
|
|
"scrollbaroptions[min=0;max=255;smallstep=1]",
|
|
"label[0.55,1.55;R]",
|
|
"box[0.88,1.57;5.3,0.34;#FF0000CC]",
|
|
"scrollbar[0.88,1.55;5.3,0.42;horizontal;r_" .. player_index .. ";" .. (r or 26) .. "]",
|
|
"label[0.55,2.25;G]",
|
|
"box[0.88,2.27;5.3,0.34;#00FF00CC]",
|
|
"scrollbar[0.88,2.25;5.3,0.42;horizontal;g_" .. player_index .. ";" .. (g or 26) .. "]",
|
|
"label[0.55,2.95;B]",
|
|
"box[0.88,2.97;5.3,0.34;#0000FFCC]",
|
|
"scrollbar[0.88,2.95;5.3,0.42;horizontal;b_" .. player_index .. ";" .. (b or 27) .. "]",
|
|
"button[0.55,3.85;2.5,0.7;default_color;" .. S("Set to Default") .. "]",
|
|
|
|
"box[6.75,0.9;6.35,3.9;#00000020]",
|
|
"label[7.0,1.15;" .. S("HUD Setup") .. "]",
|
|
"label[7.0,1.55;" .. S("Hud Alignment") .. "]",
|
|
"dropdown[7.0,1.8;3.2,0.8;hud_alignment;" ..
|
|
table.concat(HUD_ALIGNMENT_OPTIONS, ",") ..
|
|
";" .. get_option_index(HUD_ALIGNMENT_OPTIONS, hud_alignment) .. ";false]",
|
|
"button[10.35,1.75;2.55,0.7;default_hud_alignment;" .. S("Default") .. "]",
|
|
"label[7.0,2.85;" .. S("Health in") .. "]",
|
|
"dropdown[7.0,3.1;3.2,0.8;health_in;Points,Hearts;" .. (string_to_num.health_in[health_in] or 1) .. ";false]",
|
|
"button[10.35,3.05;2.55,0.7;default_health_in;" .. S("Default") .. "]",
|
|
"button[7.0,3.85;2.8,0.7;change_change_preview_type;" .. S("Preview Type") .. "]",
|
|
"button[10.0,3.85;2.9,0.7;advanced_layout;" .. S("Advanced Layout") .. "]",
|
|
-- Fix 6: HUD Enabled checkbox in HUD Setup box
|
|
"checkbox[7.0,4.55;hud_enabled;" .. S("HUD Enabled") .. ";" .. (hud_enabled and "true" or "false") .. "]",
|
|
"box[0.3,5.05;12.8,3.5;#00000020]",
|
|
"label[0.55,5.3;" .. S("Text Styling") .. "]",
|
|
"checkbox[0.55,5.65;auto_text_color;" .. S("Auto Text Colors") ..
|
|
";" .. (auto_text_color and "true" or "false") .. "]",
|
|
"scrollbaroptions[min=0;max=255;smallstep=1]",
|
|
"label[0.55,6.05;" .. S("Title") .. "]",
|
|
"scrollbar[0.55,6.3;3.9,0.45;horizontal;title_color_" .. player_index .. ";" .. title_color_val .. "]",
|
|
"label[4.8,6.05;" .. S("Info") .. "]",
|
|
"scrollbar[4.8,6.3;3.9,0.45;horizontal;subtitle_color_" .. player_index .. ";" .. subtitle_color_val .. "]",
|
|
"label[9.05,6.05;" .. S("Footer") .. "]",
|
|
"scrollbar[9.05,6.3;3.6,0.45;horizontal;footer_color_" .. player_index .. ";" .. footer_color_val .. "]",
|
|
"label[0.55,6.95;" .. S("Line") .. "]",
|
|
"scrollbar[0.55,7.2;3.9,0.45;horizontal;line_color_" .. player_index .. ";" .. line_color_val .. "]",
|
|
"label[4.8,6.95;" .. S("Growth") .. "]",
|
|
"scrollbar[4.8,7.2;3.9,0.45;horizontal;growth_color_" .. player_index .. ";" .. growth_color_val .. "]",
|
|
"label[9.05,6.95;" .. S("Soil") .. "]",
|
|
"scrollbar[9.05,7.2;3.6,0.45;horizontal;soil_color_" .. player_index .. ";" .. soil_color_val .. "]",
|
|
"box[0.3,8.8;12.8,2.15;#00000020]",
|
|
"label[0.55,9.05;" .. S("Info Lines") .. "]",
|
|
"checkbox[0.55,9.4;show_light_level;" .. S("Light Level") .. ";" .. (show_light_level and "true" or "false") ..
|
|
"]",
|
|
"checkbox[0.55,9.78;show_spawn_hint;" .. S("Spawn Hint") .. ";" .. (show_spawn_hint and "true" or "false") .. "]",
|
|
"label[3.25,9.43;" .. S("Spawn Thresh") .. "]",
|
|
"dropdown[3.25,9.66;1.6,0.8;spawn_threshold;0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15;" ..
|
|
spawn_threshold_idx .. ";false]",
|
|
"checkbox[5.45,9.4;show_liquid_info;" .. S("Liquid Info") .. ";" .. (show_liquid_info and "true" or "false") ..
|
|
"]",
|
|
"checkbox[5.45,9.78;show_dig_time;" .. S("Dig Time") .. ";" .. (show_dig_time and "true" or "false") .. "]",
|
|
"checkbox[8.65,9.4;show_growth;" .. S("Growth") .. ";" .. (show_growth and "true" or "false") .. "]",
|
|
"checkbox[11.6,9.4;show_icons;" .. S("Icons") .. ";" .. (show_icons and "true" or "false") .. "]",
|
|
"checkbox[11.6,9.78;show_coords;" .. S("Coords") .. ";" .. (show_coords and "true" or "false") .. "]",
|
|
"label[10.7,10.06;" .. S("Footer Mode") .. "]",
|
|
"dropdown[10.7,10.29;2.0,0.8;footer_mode;Compact,Advanced;" .. footer_mode_idx .. ";false]",
|
|
"style[cancel;bgcolor=#8D2930C0]",
|
|
"style[apply;bgcolor=#2D7F46C0]",
|
|
"button_exit[7.9,11.5;2.4,0.8;cancel;" .. S("Cancel") .. "]",
|
|
"button[10.55,11.5;2.55,0.8;apply;" .. S("Apply Changes") .. "]",
|
|
}, "")
|
|
|
|
update_preview(player, change_preview_type[player_name], true)
|
|
|
|
return formspec
|
|
end
|
|
|
|
-- Fix 11: is_live_scrollbar hoisted to module level — it's a pure function with no upvalue captures
|
|
local function is_live_scrollbar(raw)
|
|
if type(raw) ~= "string" or not minetest.explode_scrollbar_event then
|
|
return false
|
|
end
|
|
local ev = minetest.explode_scrollbar_event(raw)
|
|
return ev and (ev.type == "CHG" or ev.type == "VAL")
|
|
end
|
|
|
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|
if formname ~= "eye_spy:ui" then
|
|
return false
|
|
end
|
|
|
|
local player_name = player:get_player_name()
|
|
local meta = player:get_meta()
|
|
|
|
local player_index = index[player_name]
|
|
local player_change_preview_type = change_preview_type[player_name]
|
|
local main_slider_event = false
|
|
|
|
if fields["r_" .. player_index] or fields["g_" .. player_index] or fields["b_" .. player_index] then
|
|
local current_r, current_g, current_b = eye_spy.get_rgb(get_effective_string(meta, player_name, "es_hud_color"))
|
|
local r = current_r
|
|
local g = current_g
|
|
local b = current_b
|
|
local color_changed = false
|
|
|
|
if fields["r_" .. player_index] and is_live_scrollbar(fields["r_" .. player_index]) then
|
|
local value = parse_scroll_input(fields["r_" .. player_index], current_r)
|
|
if value ~= current_r then
|
|
r = value; color_changed = true
|
|
end
|
|
end
|
|
|
|
if fields["g_" .. player_index] and is_live_scrollbar(fields["g_" .. player_index]) then
|
|
local value = parse_scroll_input(fields["g_" .. player_index], current_g)
|
|
if value ~= current_g then
|
|
g = value; color_changed = true
|
|
end
|
|
end
|
|
|
|
if fields["b_" .. player_index] and is_live_scrollbar(fields["b_" .. player_index]) then
|
|
local value = parse_scroll_input(fields["b_" .. player_index], current_b)
|
|
if value ~= current_b then
|
|
b = value; color_changed = true
|
|
end
|
|
end
|
|
|
|
if color_changed then
|
|
set_draft_value(player_name, "es_hud_color", r .. ", " .. g .. ", " .. b)
|
|
main_slider_event = true
|
|
end
|
|
end
|
|
|
|
-- Fix 11: apply_main_int_slider stays inner (captures fields, player_name, meta, main_slider_event)
|
|
local function apply_main_int_slider(field_name, meta_key)
|
|
if not fields[field_name] or not is_live_scrollbar(fields[field_name]) then
|
|
return
|
|
end
|
|
|
|
local current = get_effective_int(meta, player_name, meta_key)
|
|
local value = parse_scroll_input(fields[field_name], current)
|
|
|
|
if value ~= current then
|
|
set_draft_value(player_name, meta_key, value)
|
|
main_slider_event = true
|
|
end
|
|
end
|
|
|
|
apply_main_int_slider("title_color_" .. player_index, "es_title_color_val")
|
|
apply_main_int_slider("subtitle_color_" .. player_index, "es_subtitle_color_val")
|
|
apply_main_int_slider("footer_color_" .. player_index, "es_footer_color_val")
|
|
apply_main_int_slider("line_color_" .. player_index, "es_line_color_val")
|
|
apply_main_int_slider("growth_color_" .. player_index, "es_growth_color_val")
|
|
apply_main_int_slider("soil_color_" .. player_index, "es_soil_color_val")
|
|
|
|
if main_slider_event then
|
|
local now_us = minetest.get_us_time and minetest.get_us_time() or 0
|
|
eye_spy.pending_preview_updates[player_name] = {
|
|
preview_type = player_change_preview_type,
|
|
force = true,
|
|
due_at_us = now_us,
|
|
}
|
|
return true
|
|
end
|
|
|
|
if fields.auto_text_color then
|
|
set_draft_value(player_name, "es_auto_text_color", fields.auto_text_color)
|
|
update_preview(player, player_change_preview_type)
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
end
|
|
|
|
local info_settings_changed = false
|
|
|
|
if fields.show_light_level then
|
|
set_draft_value(player_name, "es_show_light_level", fields.show_light_level)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.show_spawn_hint then
|
|
set_draft_value(player_name, "es_show_spawn_hint", fields.show_spawn_hint)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.show_liquid_info then
|
|
set_draft_value(player_name, "es_show_liquid_info", fields.show_liquid_info)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.show_dig_time then
|
|
set_draft_value(player_name, "es_show_dig_time", fields.show_dig_time)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.show_growth then
|
|
set_draft_value(player_name, "es_show_growth", fields.show_growth)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.show_icons then
|
|
set_draft_value(player_name, "es_show_icons", fields.show_icons)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.show_coords then
|
|
set_draft_value(player_name, "es_show_coords", fields.show_coords)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
-- Fix 6: handle hud_enabled checkbox
|
|
if fields.hud_enabled ~= nil then
|
|
set_draft_value(player_name, "es_hud_enabled", fields.hud_enabled)
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.footer_mode then
|
|
if fields.footer_mode == "Advanced" then
|
|
set_draft_value(player_name, "es_footer_mode", "advanced")
|
|
else
|
|
set_draft_value(player_name, "es_footer_mode", "compact")
|
|
end
|
|
|
|
info_settings_changed = true
|
|
end
|
|
|
|
if fields.spawn_threshold then
|
|
local threshold = tonumber(fields.spawn_threshold)
|
|
|
|
if threshold then
|
|
set_draft_value(player_name, "es_spawn_safe_light_threshold", math.max(0, math.min(15, threshold)))
|
|
info_settings_changed = true
|
|
end
|
|
end
|
|
|
|
if info_settings_changed then
|
|
update_preview(player, player_change_preview_type)
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
end
|
|
|
|
if fields.default_color then
|
|
set_draft_value(player_name, "es_hud_color", "26, 26, 27")
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
update_preview(player, player_change_preview_type)
|
|
end
|
|
|
|
if fields.hud_alignment then
|
|
set_draft_value(player_name, "es_hud_alignment", fields.hud_alignment)
|
|
update_preview(player, player_change_preview_type)
|
|
end
|
|
|
|
if fields.default_hud_alignment then
|
|
set_draft_value(player_name, "es_hud_alignment", "Top-Middle")
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
update_preview(player, player_change_preview_type)
|
|
end
|
|
|
|
if fields.health_in then
|
|
set_draft_value(player_name, "es_hud_health_in", fields.health_in)
|
|
update_preview(player, player_change_preview_type)
|
|
end
|
|
|
|
if fields.default_health_in then
|
|
set_draft_value(player_name, "es_hud_health_in", "Points")
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
update_preview(player, player_change_preview_type)
|
|
end
|
|
|
|
if fields.change_change_preview_type then
|
|
if player_change_preview_type == "Node" then
|
|
change_preview_type[player_name] = "Entity"
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
else
|
|
change_preview_type[player_name] = "Node"
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
end
|
|
end
|
|
|
|
if fields.advanced_layout then
|
|
eye_spy.pending_layout_refreshes[player_name] = nil
|
|
layout_last_chg_us[player_name] = nil
|
|
minetest.close_formspec(player_name, "eye_spy:ui")
|
|
minetest.show_formspec(player_name, "eye_spy:layout_ui", eye_spy.get_layout_ui(player))
|
|
return true
|
|
end
|
|
|
|
-- Fix 1: clear old_values before closing so death cannot revert applied settings
|
|
if fields.apply then
|
|
commit_player_draft(player)
|
|
old_values[player_name] = nil -- prevent death from reverting applied settings
|
|
minetest.close_formspec(player_name, formname)
|
|
eye_spy.exit_edit_mode(player)
|
|
eye_spy.update_player(player, { preview = false, force = true })
|
|
return true
|
|
end
|
|
|
|
if fields.quit then
|
|
restore_old_values(meta, old_values[player_name])
|
|
clear_player_draft(player_name)
|
|
eye_spy.pending_preview_updates[player_name] = nil
|
|
|
|
eye_spy.exit_edit_mode(player)
|
|
eye_spy.update_player(player, { preview = false, force = true })
|
|
end
|
|
|
|
return true
|
|
end)
|
|
|
|
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|
if formname ~= "eye_spy:layout_ui" then
|
|
return false
|
|
end
|
|
|
|
local player_name = player:get_player_name()
|
|
local meta = player:get_meta()
|
|
local player_change_preview_type = change_preview_type[player_name] or "Entity"
|
|
local selected_page = get_effective_string(meta, player_name, "es_layout_page")
|
|
|
|
local selected_target = get_effective_string(meta, player_name, "es_layout_target")
|
|
|
|
if selected_page == "" then
|
|
selected_page = "Position"
|
|
set_draft_value(player_name, "es_layout_page", selected_page)
|
|
end
|
|
|
|
if selected_target == "" then
|
|
selected_target = "Global"
|
|
set_draft_value(player_name, "es_layout_target", selected_target)
|
|
end
|
|
|
|
if fields.back_main then
|
|
eye_spy.pending_layout_refreshes[player_name] = nil
|
|
layout_last_chg_us[player_name] = nil
|
|
minetest.close_formspec(player_name, "eye_spy:layout_ui")
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
return true
|
|
end
|
|
|
|
if fields.quit then
|
|
eye_spy.pending_layout_refreshes[player_name] = nil
|
|
layout_last_chg_us[player_name] = nil
|
|
minetest.close_formspec(player_name, "eye_spy:layout_ui")
|
|
minetest.show_formspec(player_name, "eye_spy:ui", eye_spy.get_ui(player, { reopen = true }))
|
|
return true
|
|
end
|
|
|
|
local rebuild = false
|
|
|
|
if fields.layout_page and fields.layout_page ~= selected_page then
|
|
set_draft_value(player_name, "es_layout_page", fields.layout_page)
|
|
selected_page = fields.layout_page
|
|
rebuild = true
|
|
end
|
|
|
|
if fields.layout_target and LAYOUT_TARGET_KEYS[fields.layout_target] then
|
|
if fields.layout_target ~= selected_target then
|
|
set_draft_value(player_name, "es_layout_target", fields.layout_target)
|
|
selected_target = fields.layout_target
|
|
rebuild = true
|
|
end
|
|
end
|
|
|
|
if fields.layout_step then
|
|
local new_step = tonumber(fields.layout_step)
|
|
|
|
if new_step then
|
|
local clamped_step = math.max(1, new_step)
|
|
|
|
if clamped_step ~= get_effective_int(meta, player_name, "es_layout_step") then
|
|
set_draft_value(player_name, "es_layout_step", clamped_step)
|
|
rebuild = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local step = math.max(1, get_effective_int(meta, player_name, "es_layout_step"))
|
|
|
|
local slider_event = false
|
|
local slider_had_chg = false
|
|
local slider_had_val = false
|
|
local slider_chg_count = 0
|
|
local slider_val_count = 0
|
|
local slider_max_delta = 0
|
|
local slider_had_small_step_val = false
|
|
|
|
-- Fix 11: apply_slider stays inner (captures fields, player_name, meta, and slider tracking locals)
|
|
local function apply_slider(name, minv, maxv)
|
|
if fields[name] == nil then
|
|
return
|
|
end
|
|
|
|
local current = get_effective_int(meta, player_name, name)
|
|
local value, event_type = parse_scroll_input(fields[name], current)
|
|
|
|
-- Ignore values from full form submissions (button clicks, quit).
|
|
-- Only act on live scrollbar CHG/VAL events.
|
|
if event_type ~= "CHG" and event_type ~= "VAL" then
|
|
return
|
|
end
|
|
|
|
if event_type == "CHG" then
|
|
slider_had_chg = true
|
|
slider_chg_count = slider_chg_count + 1
|
|
elseif event_type == "VAL" then
|
|
slider_had_val = true
|
|
slider_val_count = slider_val_count + 1
|
|
end
|
|
|
|
if minv then
|
|
value = math.max(minv, value)
|
|
end
|
|
|
|
if maxv then
|
|
value = math.min(maxv, value)
|
|
end
|
|
|
|
local delta = math.abs(value - current)
|
|
slider_max_delta = math.max(slider_max_delta, delta)
|
|
|
|
if event_type == "VAL" and delta <= 2 then
|
|
slider_had_small_step_val = true
|
|
end
|
|
|
|
if value ~= current then
|
|
set_draft_value(player_name, name, value)
|
|
end
|
|
|
|
slider_event = true
|
|
end
|
|
|
|
for key, bounds in pairs(LAYOUT_SLIDER_BOUNDS) do
|
|
apply_slider(key, bounds.min, bounds.max)
|
|
end
|
|
|
|
if slider_event and not fields.reset_layout_target and not fields.reset_layout_all then
|
|
-- Queue a HUD update rather than calling get_hud directly. Multiple CHG
|
|
-- events between step ticks overwrite the same entry, so the HUD fires at
|
|
-- most once per step tick instead of on every CHG.
|
|
local now_us = minetest.get_us_time and minetest.get_us_time() or 0
|
|
eye_spy.pending_preview_updates[player_name] = {
|
|
preview_type = player_change_preview_type,
|
|
force = true,
|
|
due_at_us = now_us,
|
|
}
|
|
|
|
if slider_had_chg then
|
|
layout_last_chg_us[player_name] = now_us
|
|
end
|
|
|
|
local rebuild_delay_us = LAYOUT_REBUILD_DRAG_IDLE_US
|
|
local last_chg_us = layout_last_chg_us[player_name] or 0
|
|
local recent_chg = last_chg_us > 0 and (now_us - last_chg_us) <= LAYOUT_VAL_AS_NUDGE_GAP_US
|
|
|
|
-- Treat tiny VAL deltas as click-like nudges unless they are part of a
|
|
-- recent drag burst.
|
|
if slider_had_small_step_val and slider_chg_count <= 1 and not recent_chg then
|
|
rebuild_delay_us = LAYOUT_REBUILD_NUDGE_IDLE_US
|
|
elseif slider_had_val and slider_val_count > 0 and slider_chg_count == 0 and not recent_chg then
|
|
rebuild_delay_us = LAYOUT_REBUILD_NUDGE_IDLE_US
|
|
end
|
|
|
|
eye_spy.pending_layout_refreshes[player_name] = {
|
|
due_at_us = now_us + rebuild_delay_us,
|
|
}
|
|
return true
|
|
end
|
|
|
|
if fields.reset_layout_target then
|
|
local keys = LAYOUT_RESET_GROUPS[selected_target]
|
|
|
|
if keys then
|
|
for _, key in ipairs(keys) do
|
|
set_draft_value(player_name, key, LAYOUT_META_DEFAULTS[key])
|
|
end
|
|
end
|
|
|
|
rebuild = true
|
|
end
|
|
|
|
if fields.reset_layout_all then
|
|
for key, default_value in pairs(LAYOUT_META_DEFAULTS) do
|
|
set_draft_value(player_name, key, default_value)
|
|
end
|
|
|
|
selected_target = "Global"
|
|
set_draft_value(player_name, "es_layout_target", selected_target)
|
|
rebuild = true
|
|
end
|
|
|
|
if rebuild then
|
|
eye_spy.pending_preview_updates[player_name] = nil
|
|
eye_spy.get_hud(player, {
|
|
preview = true,
|
|
preview_type = player_change_preview_type,
|
|
force = true,
|
|
})
|
|
minetest.show_formspec(player_name, "eye_spy:layout_ui", eye_spy.get_layout_ui(player))
|
|
end
|
|
|
|
return true
|
|
end)
|
|
|
|
-- Fix 10: clean up all UI-local tables when a player leaves
|
|
minetest.register_on_leaveplayer(function(player)
|
|
local player_name = player:get_player_name()
|
|
old_values[player_name] = nil
|
|
index[player_name] = nil
|
|
change_preview_type[player_name] = nil
|
|
layout_last_chg_us[player_name] = nil
|
|
end)
|