Update mainmenu/init.lua

This commit is contained in:
Signal 2025-07-11 17:11:52 +00:00
parent c7c04d9221
commit 779410c782

View file

@ -77,32 +77,123 @@ local default_game_menu = [[
scroll_container[${@WIDTH * 0.1},${@HEIGHT * 0.1};${@list_width},${@HEIGHT * 0.8};worldscroll;vertical;;0,0] scroll_container[${@WIDTH * 0.1},${@HEIGHT * 0.1};${@list_width},${@HEIGHT * 0.8};worldscroll;vertical;;0,0]
@foreach:@WORLDS:worlds @foreach:@WORLDS:worlds
@if:@i % 2:sel @if:@i % 2:sel
style[.select_world_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] style[.select_world_${@path};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
style[.select_world_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] style[.select_world_${@path}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
style[.select_world_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530] style[.select_world_${@path}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
@else:sel @else:sel
style[.select_world_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] style[.select_world_${@path};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
style[.select_world_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] style[.select_world_${@path}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
style[.select_world_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39] style[.select_world_${@path}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
@endif:sel @endif:sel
button[0,${(@i +-1) * 0.5};${@list_width},0.5;.select_world_${@name};${@name}] button[0,${(@i +-1) * 0.5};${@list_width},0.5;.select_world_${@path};${@name}]
@endforeach:worlds @endforeach:worlds
scroll_container_end[] scroll_container_end[]
@if:@selected_world:fi @if:@selected_world:fi
box[${@WIDTH * 0.4 +-0.05},${@HEIGHT * 0.1};0.1,${@HEIGHT * 0.8};#292d2fff] box[${@WIDTH * 0.4 +-0.05},${@HEIGHT * 0.1 +-0.1};0.1,${@HEIGHT * 0.8 + 0.2};#292d2fff]
scroll_container[${@WIDTH * 0.4},${@HEIGHT * 0.1};${@WIDTH * 0.8},${@HEIGHT * 0.8};worldmodsscroll;vertical;;0,0] scroll_container[${@WIDTH * 0.4 + 0.05},${@HEIGHT * 0.1};${@WIDTH * 0.8 +-0.05},${@HEIGHT * 0.8};worlconfigscroll;vertical;;0,0]
@foreach:@WORLDMODS:wm @set:j:0
label[0,${@i};${@name}]
@endforeach:wm @if:@setting_damage:fii
@if:@damage_enabled:fiii
image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_filled.png;.set_damage_enabled_to_0;]
@else:fiii
image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_empty.png;.set_damage_enabled_to_1;]
@endif:fiii
label[0.5,${@j + 0.25};Damage]
@set:j:@j + 0.5
@else:fii
@endif:fii
@if:@setting_creative:fii
@if:@creative_enabled:fiii
image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_filled.png;.set_creative_enabled_to_0;]
@else:fiii
image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_empty.png;.set_creative_enabled_to_1;]
@endif:fiii
label[0.5,${@j + 0.25};Creative]
@set:j:@j + 0.5
@else:fii
@endif:fii
@set:play_str:Play
@if:@setting_server:fii
@if:@server_enabled:fiii
image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_filled.png;.set_server_enabled_to_0;]
@set:play_str:Host server
@else:fiii
image_button[0,${@j};0.5,0.5;$DEFAULT_ASSET_PATH/checkbox_empty.png;.set_server_enabled_to_1;]
@set:play_str:Play
@endif:fiii
label[0.5,${@j + 0.25};Server]
@set:j:@j + 0.5
@else:fii
@endif:fii
@set:j:@j + 1
button[${@WIDTH * 0.05},${@j};${@WIDTH * 0.4},0.75;.overlay_dialog_modconfig;Configure mods]
button[${@WIDTH * 0.05},${@j + 1};${@WIDTH * 0.4},0.75;.play;${@play_str}]
scroll_container_end[] scroll_container_end[]
scrollbar[-800,6;0,2;vertical;worldmodsscroll;] scrollbar[-800,6;0,2;vertical;worldconfigscroll;]
@else:fi @else:fi
@endif:fi @endif:fi
scrollbaroptions[arrows=hide] scrollbaroptions[arrows=hide]
scrollbar[-800,6;0,2;vertical;worldscroll;] scrollbar[-800,6;0,2;vertical;worldscroll;]
</main> </main>
<modconfig>
image[${@WIDTH * 0.05 +-0.1},${@HEIGHT * 0.05 +-0.1};${@WIDTH * 0.4 + 0.2},${@HEIGHT * 0.9 +-0.75};$DEFAULT_ASSET_PATH/btn_bg.png;8,8]
hypertext[${@WIDTH * 0.05},${@HEIGHT * 0.05};${@WIDTH * 0.4},0.75;;<global valign=middle halign=center><b>Available mods</b>]
scroll_container[${@WIDTH * 0.05},${@HEIGHT * 0.05 + 0.75};${@WIDTH * 0.4},${@HEIGHT * 0.9 +-1.7};modsscroll;vertical;;0,0]
@foreach:@MODS:m
@if:@i % 2:sel
style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
@else:sel
style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
@endif:sel
button[0,${@i * 0.5 +-0.5};${@WIDTH * 0.4},0.5;.select_mod_${@name};${@name}]
@endforeach:m
scroll_container_end[]
scrollbar[-800,6;0,2;vertical;modsscroll;]
image[${@WIDTH * 0.55 +-0.1},${@HEIGHT * 0.05 +-0.1};${@WIDTH * 0.4 + 0.2},${@HEIGHT * 0.9 +-0.75};$DEFAULT_ASSET_PATH/btn_bg.png;8,8]
hypertext[${@WIDTH * 0.55},${@HEIGHT * 0.05};${@WIDTH * 0.4},0.75;;<global valign=middle halign=center><b>Mods for ${@selected_world_name}</b>]
scroll_container[${@WIDTH * 0.55},${@HEIGHT * 0.05 + 0.75};${@WIDTH * 0.4},${@HEIGHT * 0.9 +-1.7};worldmodsscroll;vertical;;0,0]
@foreach:@WORLDMODS:wm
@if:@i % 2:sel
style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#373530]
@else:sel
style[.select_mod_${@name};bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
style[.select_mod_${@name}:hovered;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
style[.select_mod_${@name}:hovered+focused;bgimg=$DEFAULT_ASSET_PATH/white.png;bgimg_middle=0;bgcolor=#403e39]
@endif:sel
button[0,${@i * 0.5 +-0.5};${@WIDTH * 0.4},0.5;.select_mod_${@name};${@name}]
@endforeach:wm
scroll_container_end[]
scrollbar[-800,6;0,2;vertical;worldmodsscroll;]
image_button[${@WIDTH * 0.45},${@HEIGHT * 0.5 +-(@WIDTH * 0.1)};${@WIDTH * 0.1},${@WIDTH * 0.1};$DEFAULT_ASSET_PATH/arrow_right.png;.add_mod_to_world;]
image_button[${@WIDTH * 0.45},${@HEIGHT * 0.5};${@WIDTH * 0.1},${@WIDTH * 0.1};$DEFAULT_ASSET_PATH/arrow_left.png;.remove_mod_from_world;]
tooltip[.add_mod_to_world;Add mod to world;#444;#aaa]
tooltip[.remove_mod_from_world;Remove mod from world;#444;#aaa]
button[${@WIDTH * 0.1},${@HEIGHT * 0.95 +-0.75};${@WIDTH * 0.4},0.75;.unoverlay_dialog;Cancel]
button[${@WIDTH * 0.5},${@HEIGHT * 0.95 +-0.75};${@WIDTH * 0.4},0.75;.unoverlay_dialog;Confirm]
</modconfig>
<addworld> <addworld>
label[2,2;Add World, asset path is $ASSET_PATH] label[2,2;Add World, asset path is $ASSET_PATH]
button[2,3;3,1;.show_dialog_main;back] button[2,3;3,1;.show_dialog_main;back]
@ -177,6 +268,15 @@ local function get_game_info(game)
end end
end end
local function get_world_index(path)
for i, x in ipairs(minetest.get_worlds()) do
if x.path == path then
return i
end
end
return -1
end
local function get_worlds_for_game(id) local function get_worlds_for_game(id)
local out = {} local out = {}
for _, x in ipairs(minetest.get_worlds()) do for _, x in ipairs(minetest.get_worlds()) do
@ -210,12 +310,50 @@ function get_mods_for_game(id)
return out return out
end end
local function get_game_mods(id)
local out = {}
local base = get_game_info(id).path.."/mods/"
for _, x in pairs(minetest.get_dir_list(base, true)) do
out[#out +1] = {
name = x,
path = base..x
}
end
return out
end
function get_mods_for_world(world) function get_mods_for_world(world)
local config = minetest.check_mod_configuration(world) local conf = Settings(world.."/world.mt")
local mods = {}
local game
local game_mods = {}
for k, v in pairs(conf:to_table()) do
if k == "gameid" then
game = v
elseif k:sub(1, 9) == "load_mod_" then
if v ~= "false" and v ~= "nil" and v then
mods[k:sub(10)] = minetest.get_user_path().."/"..v
end
end
end
if game then
for _, x in ipairs(get_game_mods(game)) do
game_mods[x.name] = x.path
end
end
local config = minetest.check_mod_configuration(world, mods)
-- print(dump(config))
for _, x in ipairs(config.unsatisfied_mods) do for _, x in ipairs(config.unsatisfied_mods) do
x.unsatisfied = true x.unsatisfied = true
config.satisfied_mods[#config.satisfied_mods +1] = x config.satisfied_mods[#config.satisfied_mods +1] = x
end end
if game then
for _, x in ipairs(config.satisfied_mods) do
if game_mods[x.name] then
x.game_provided = true
end
end
end
return config.satisfied_mods return config.satisfied_mods
end end
@ -503,40 +641,36 @@ local function build_template_dialog(fs, depth)
local i = 0 local i = 0
-- "(.-\n?)%s-@foreach:([^:]+):(%l*)\n(.-)\n%s-@endforeach:%3(\n?.*)" -- "(.-\n?)%s-@foreach:([^:]+):(%l*)\n(.-)\n%s-@endforeach:%3(\n?.*)"
-- Extract foreach loops -- Extract foreach loops
local prev = 1 local prev = 0
while i < 1000 do while i < 1000 do
local unfound = 0 local fe_start, fe_end, fe_pattern, fe_name, fe_content = fs:find("@foreach:([^:]+):(%w-)\n(.-)\n%s-@endforeach:%2", prev)
local a, b, pattern, name, content = fs:find("@foreach:([^:]+):(%w*)\n(.-)\n%s-@endforeach:%2", prev) local if_start, if_end, if_expr, if_name, if_content, else_content = fs:find("@if:([^:]+):(%w-)\n(.-)\n%s-@else:%2\n(.-)\n%s-@endif:%2", prev)
-- print(string.rep("-", 20)..(depth or 0)) -- print(string.rep("-", 20)..(depth or 0))
if a then if not fe_start and not if_start then break end
if fe_start and fe_start < (if_start or math.huge) then
-- print("for each "..pattern.." ("..name..")\n"..content.."\nend for each ("..name..")") -- print("for each "..pattern.." ("..name..")\n"..content.."\nend for each ("..name..")")
dialog[#dialog +1] = build_template_dialog(fs:sub(prev, a -1), (depth or 0) +1) dialog[#dialog +1] = build_template_dialog(fs:sub(prev +1, fe_start -1), (depth or 0) +1)
dialog[#dialog +1] = { dialog[#dialog +1] = {
foreach = pattern:trim(), foreach = fe_pattern:trim(),
name = name, name = fe_name,
content = build_template_dialog(content:trim(), (depth or 0) +1) content = build_template_dialog(fe_content:trim(), (depth or 0) +1)
} }
prev = b prev = fe_end
i = i +1 i = i +1
else
unfound = unfound +1
end end
local a, b, expr, name, content, else_content = fs:find("@if:([^:]+):(%w*)\n(.-)\n%s-@else:%2\n(.-\n?)@endif:%2", prev)
if a then if if_start and if_start < (fe_start or math.huge) then
-- print("if "..expr.." ("..name..")\n"..content.."\nend if ("..name..")") -- print("if "..expr.." ("..name..")\n"..content.."\nend if ("..name..")")
dialog[#dialog +1] = build_template_dialog(fs:sub(prev +1, a -1), (depth or 0) +1) dialog[#dialog +1] = build_template_dialog(fs:sub(prev +1, if_start -1), (depth or 0) +1)
dialog[#dialog +1] = { dialog[#dialog +1] = {
condition = expr:trim(), condition = if_expr:trim(),
name = name, name = if_name,
content = build_template_dialog(content:trim(), (depth or 0) +1), content = build_template_dialog(if_content:trim(), (depth or 0) +1),
else_content = build_template_dialog(else_content:trim(), (depth or 0) +1) else_content = build_template_dialog(else_content:trim(), (depth or 0) +1)
} }
prev = b prev = if_end
i = i +1 i = i +1
else
unfound = unfound +1
end end
if unfound > 1 then break end
end end
if prev > 1 then if prev > 1 then
@ -681,6 +815,10 @@ local function evaluate_template_expression(expr, vars, depth)
vars = {item = vars} vars = {item = vars}
end end
-- Check for operators early, because variables may contain punctuation when expanded.
-- A class is used instead of %p because %p matches @, which is used for variables.
local has_operators = expr:find("[|&><=+*/%%^]")
-- Expand all variables so we can deal with a constexpr. -- Expand all variables so we can deal with a constexpr.
local offset = 1 local offset = 1
while offset < 100000 do while offset < 100000 do
@ -692,6 +830,9 @@ local function evaluate_template_expression(expr, vars, depth)
offset = a +#result offset = a +#result
end end
-- If there are no operators, this is a constant expression and we can just return.
if not has_operators then return expr end
-- Condense sub-expressions. -- Condense sub-expressions.
local offset = 1 local offset = 1
while offset < 100000 do while offset < 100000 do
@ -703,9 +844,6 @@ local function evaluate_template_expression(expr, vars, depth)
offset = a +#result offset = a +#result
end end
-- If there are no operators, this is a constant expression and we can just return.
if not expr:find("%p") then return expr end
-- Expression parsing -- Expression parsing
local tree = split_template_expression(expr) local tree = split_template_expression(expr)
return tostring(reduce_template_expression(tree)) return tostring(reduce_template_expression(tree))
@ -713,23 +851,22 @@ end
-- Do variable interpolation for the given formspec using the provided variable table. -- Do variable interpolation for the given formspec using the provided variable table.
local function evaluate_template_block(fs, vars) local function evaluate_template_block(fs, vars)
-- Assignment statements local offset = 0
local offset = 1 while offset < 100000 do
while offset < #fs do local s_start, s_end, s_name, s_expr = fs:find("@set:@?([%w_]+):([^\n]+)", offset)
local a, b, name, expr = fs:find("@set:([%a_]+):([^\n]+)", offset) local i_start, i_end, i_expr = fs:find("%${([^}]-)}", offset)
if not a then break end if not s_start and not i_start then break end
vars[name] = evaluate_template_expression(expr, vars) if s_start and s_start < (i_start or math.huge) then
fs = fs:sub(1, a -1)..fs:sub(b +1) -- Assignment statements
offset = a vars[s_name] = evaluate_template_expression(s_expr, vars)
end fs = fs:sub(1, s_start -1)..fs:sub(s_end +1)
-- Interpolations offset = s_start
offset = 1 elseif i_start then
while offset < #fs do -- Interpolations
local a, b, expr = fs:find("%${([^}]-)}", offset) local result = evaluate_template_expression(i_expr, vars)
if not a then break end fs = fs:sub(1, i_start -1)..result..fs:sub(i_end +1)
local result = evaluate_template_expression(expr, vars) offset = i_start +#result
fs = fs:sub(1, a -1)..result..fs:sub(b +1) end
offset = a +#result
end end
return fs return fs
end end
@ -747,7 +884,7 @@ local function evaluate_template_foreach(loop, vars)
elseif var == "MODS" then elseif var == "MODS" then
list = get_mods_for_game(state.current_game.id) list = get_mods_for_game(state.current_game.id)
elseif var == "WORLDMODS" then elseif var == "WORLDMODS" then
list = state.current_world and get_mods_for_world(state.current_world) or {} list = state.menu_vars.selected_world and get_mods_for_world(state.menu_vars.selected_world) or {}
else else
list = vars[var] list = vars[var]
end end
@ -777,6 +914,7 @@ local function evaluate_template_conditional(cond, vars)
else else
out = out..evaluate_game_dialog(cond.else_content, vars).."\n" out = out..evaluate_game_dialog(cond.else_content, vars).."\n"
end end
-- print("Evaluated condition `"..cond.condition.."` as "..out)
return out return out
end end
@ -889,6 +1027,25 @@ function show_game_menu(args)
if not game.menu then if not game.menu then
local file = read_file(game.path.."/menu/mainmenu.txt") or default_game_menu local file = read_file(game.path.."/menu/mainmenu.txt") or default_game_menu
game.disabled_settings = {}
local settings = Settings(game.path.."/game.conf")
local disabled_settings = string.split(settings:get("disabled_settings") or "", ",")
for _, x in ipairs(disabled_settings) do
x = x:trim()
if x == "enable_damage" then
game.disabled_settings.damage = false
elseif x == "!enable_damage" then
game.disabled_settings.damage = true
elseif x == "creative_mode" then
game.disabled_settings.creative = false
elseif x == "!creative_mode" then
game.disabled_settings.creative = true
elseif x == "enable_server" then
game.disabled_settings.server = false
end
end
local overlays = {} local overlays = {}
local backgrounds = {} local backgrounds = {}
local headers = {} local headers = {}
@ -948,6 +1105,12 @@ function show_game_menu(args)
end end
game.menu = build_game_menu(file) game.menu = build_game_menu(file)
state.menu_vars = {}
state.menu_vars.setting_damage = game.disabled_settings.damage == nil
state.menu_vars.setting_creative = game.disabled_settings.creative == nil
state.menu_vars.setting_server = game.disabled_settings.server == nil
end end
if args.show_dialog then if args.show_dialog then
@ -958,6 +1121,9 @@ function show_game_menu(args)
state.menu_current[#state.menu_current] = nil state.menu_current[#state.menu_current] = nil
end end
state.menu_vars.WIDTH = size.x
state.menu_vars.HEIGHT = size.y
local fs = "" local fs = ""
if not game.background and not game.overlay then if not game.background and not game.overlay then
@ -966,8 +1132,14 @@ function show_game_menu(args)
" "
end end
for i, x in ipairs(state.menu_current) do for i, x in ipairs(state.menu_current) do
fs = fs..evaluate_game_dialog(game.menu[x], setmetatable({WIDTH = size.x, HEIGHT = size.y}, {__index = state.menu_vars})) if i > 1 then
fs = fs.."\
box[0,0;"..size.x..","..size.y..";#0009]\
"
end
fs = fs..evaluate_game_dialog(game.menu[x], state.menu_vars)
end end
-- print(fs) -- print(fs)
@ -1616,7 +1788,17 @@ function minetest.button_handler(data)
end end
elseif state.loc == "game" then elseif state.loc == "game" then
for k, v in pairs(data) do for k, v in pairs(data) do
if k:sub(1, string.len(".show_dialog_")) == ".show_dialog_" then if k == ".play" then
gamedata = {
playername = "singleplayer",
password = "",
address = nil,
port = nil,
selected_world = get_world_index(state.menu_vars.selected_world),
singleplayer = true
}
minetest.start()
elseif k:sub(1, string.len(".show_dialog_")) == ".show_dialog_" then
show_game_menu { show_game_menu {
show_dialog = k:sub(string.len(".show_dialog_>")) show_dialog = k:sub(string.len(".show_dialog_>"))
} }
@ -1626,13 +1808,31 @@ function minetest.button_handler(data)
} }
elseif k:sub(1, string.len(".select_world_")) == ".select_world_" then elseif k:sub(1, string.len(".select_world_")) == ".select_world_" then
state.menu_vars.selected_world = k:sub(string.len(".select_world_>")) state.menu_vars.selected_world = k:sub(string.len(".select_world_>"))
state.menu_vars.selected_world_name = k:match "/([^/]+)$" or "<unknown world>"
local conf = Settings(state.menu_vars.selected_world.."/world.mt")
local de = conf:get("enable_damage")
if de == "true" then
state.menu_vars.damage_enabled = true
elseif de == "false" then
state.menu_vars.damage_enabled = false
else
state.menu_vars.creative_damage = not state.current_game.disabled_settings.damage
end
local cm = conf:get("creative_mode")
if cm == "true" then
state.menu_vars.creative_enabled = true
elseif cm == "false" then
state.menu_vars.creative_enabled = false
else
state.menu_vars.creative_enabled = not not state.current_game.disabled_settings.creative
end
show_game_menu() show_game_menu()
elseif k == ".unoverlay_dialog" then elseif k == ".unoverlay_dialog" then
show_game_menu { show_game_menu {
unoverlay_dialog = true unoverlay_dialog = true
} }
elseif k:sub(1, string.len(".set_")) == ".set_" then elseif k:sub(1, string.len(".set_")) == ".set_" then
local name, value = k:match "%.set_(.-)_to_(.*)" local name, value = k:match "%.set_(.+)_to_(.*)"
state.menu_vars[name] = value == "" and "0" or value state.menu_vars[name] = value == "" and "0" or value
show_game_menu() show_game_menu()
end end