diff --git a/mainmenu/init.lua b/mainmenu/init.lua
index f34327c..a4c5a3b 100644
--- a/mainmenu/init.lua
+++ b/mainmenu/init.lua
@@ -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]
@foreach:@WORLDS:worlds
@if:@i % 2:sel
- style[.select_world_${@name};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_${@name}:hovered+focused;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_${@path}:hovered;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
- style[.select_world_${@name};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_${@name}:hovered+focused;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_${@path}:hovered;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
- 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
scroll_container_end[]
@if:@selected_world:fi
- box[${@WIDTH * 0.4 +-0.05},${@HEIGHT * 0.1};0.1,${@HEIGHT * 0.8};#292d2fff]
- scroll_container[${@WIDTH * 0.4},${@HEIGHT * 0.1};${@WIDTH * 0.8},${@HEIGHT * 0.8};worldmodsscroll;vertical;;0,0]
- @foreach:@WORLDMODS:wm
- label[0,${@i};${@name}]
- @endforeach:wm
+ box[${@WIDTH * 0.4 +-0.05},${@HEIGHT * 0.1 +-0.1};0.1,${@HEIGHT * 0.8 + 0.2};#292d2fff]
+ scroll_container[${@WIDTH * 0.4 + 0.05},${@HEIGHT * 0.1};${@WIDTH * 0.8 +-0.05},${@HEIGHT * 0.8};worlconfigscroll;vertical;;0,0]
+ @set:j:0
+
+ @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[]
- scrollbar[-800,6;0,2;vertical;worldmodsscroll;]
+ scrollbar[-800,6;0,2;vertical;worldconfigscroll;]
@else:fi
@endif:fi
scrollbaroptions[arrows=hide]
scrollbar[-800,6;0,2;vertical;worldscroll;]
+
+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;;Available mods]
+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;;Mods for ${@selected_world_name}]
+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]
+
+
label[2,2;Add World, asset path is $ASSET_PATH]
button[2,3;3,1;.show_dialog_main;back]
@@ -177,6 +268,15 @@ local function get_game_info(game)
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 out = {}
for _, x in ipairs(minetest.get_worlds()) do
@@ -210,12 +310,50 @@ function get_mods_for_game(id)
return out
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)
- 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
x.unsatisfied = true
config.satisfied_mods[#config.satisfied_mods +1] = x
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
end
@@ -503,40 +641,36 @@ local function build_template_dialog(fs, depth)
local i = 0
-- "(.-\n?)%s-@foreach:([^:]+):(%l*)\n(.-)\n%s-@endforeach:%3(\n?.*)"
-- Extract foreach loops
- local prev = 1
+ local prev = 0
while i < 1000 do
- local unfound = 0
- local a, b, pattern, name, content = fs:find("@foreach:([^:]+):(%w*)\n(.-)\n%s-@endforeach:%2", prev)
+ local fe_start, fe_end, fe_pattern, fe_name, fe_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))
- 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..")")
- 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] = {
- foreach = pattern:trim(),
- name = name,
- content = build_template_dialog(content:trim(), (depth or 0) +1)
+ foreach = fe_pattern:trim(),
+ name = fe_name,
+ content = build_template_dialog(fe_content:trim(), (depth or 0) +1)
}
- prev = b
+ prev = fe_end
i = i +1
- else
- unfound = unfound +1
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..")")
- 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] = {
- condition = expr:trim(),
- name = name,
- content = build_template_dialog(content:trim(), (depth or 0) +1),
+ condition = if_expr:trim(),
+ name = if_name,
+ content = build_template_dialog(if_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
- else
- unfound = unfound +1
end
- if unfound > 1 then break end
end
if prev > 1 then
@@ -681,6 +815,10 @@ local function evaluate_template_expression(expr, vars, depth)
vars = {item = vars}
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.
local offset = 1
while offset < 100000 do
@@ -691,6 +829,9 @@ local function evaluate_template_expression(expr, vars, depth)
expr = expr:sub(1, a -1)..result..expr:sub(b +1)
offset = a +#result
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.
local offset = 1
@@ -703,9 +844,6 @@ local function evaluate_template_expression(expr, vars, depth)
offset = a +#result
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
local tree = split_template_expression(expr)
return tostring(reduce_template_expression(tree))
@@ -713,23 +851,22 @@ end
-- Do variable interpolation for the given formspec using the provided variable table.
local function evaluate_template_block(fs, vars)
- -- Assignment statements
- local offset = 1
- while offset < #fs do
- local a, b, name, expr = fs:find("@set:([%a_]+):([^\n]+)", offset)
- if not a then break end
- vars[name] = evaluate_template_expression(expr, vars)
- fs = fs:sub(1, a -1)..fs:sub(b +1)
- offset = a
- end
- -- Interpolations
- offset = 1
- while offset < #fs do
- local a, b, expr = fs:find("%${([^}]-)}", offset)
- if not a then break end
- local result = evaluate_template_expression(expr, vars)
- fs = fs:sub(1, a -1)..result..fs:sub(b +1)
- offset = a +#result
+ local offset = 0
+ while offset < 100000 do
+ local s_start, s_end, s_name, s_expr = fs:find("@set:@?([%w_]+):([^\n]+)", offset)
+ local i_start, i_end, i_expr = fs:find("%${([^}]-)}", offset)
+ if not s_start and not i_start then break end
+ if s_start and s_start < (i_start or math.huge) then
+ -- Assignment statements
+ vars[s_name] = evaluate_template_expression(s_expr, vars)
+ fs = fs:sub(1, s_start -1)..fs:sub(s_end +1)
+ offset = s_start
+ elseif i_start then
+ -- Interpolations
+ local result = evaluate_template_expression(i_expr, vars)
+ fs = fs:sub(1, i_start -1)..result..fs:sub(i_end +1)
+ offset = i_start +#result
+ end
end
return fs
end
@@ -747,7 +884,7 @@ local function evaluate_template_foreach(loop, vars)
elseif var == "MODS" then
list = get_mods_for_game(state.current_game.id)
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
list = vars[var]
end
@@ -777,6 +914,7 @@ local function evaluate_template_conditional(cond, vars)
else
out = out..evaluate_game_dialog(cond.else_content, vars).."\n"
end
+-- print("Evaluated condition `"..cond.condition.."` as "..out)
return out
end
@@ -889,6 +1027,25 @@ function show_game_menu(args)
if not game.menu then
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 backgrounds = {}
local headers = {}
@@ -948,6 +1105,12 @@ function show_game_menu(args)
end
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
if args.show_dialog then
@@ -957,6 +1120,9 @@ function show_game_menu(args)
elseif args.unoverlay_dialog and #state.menu_current > 1 then
state.menu_current[#state.menu_current] = nil
end
+
+ state.menu_vars.WIDTH = size.x
+ state.menu_vars.HEIGHT = size.y
local fs = ""
@@ -966,8 +1132,14 @@ function show_game_menu(args)
"
end
+
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
-- print(fs)
@@ -1616,7 +1788,17 @@ function minetest.button_handler(data)
end
elseif state.loc == "game" then
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_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
state.menu_vars.selected_world = k:sub(string.len(".select_world_>"))
+ state.menu_vars.selected_world_name = k:match "/([^/]+)$" or ""
+ 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()
elseif k == ".unoverlay_dialog" then
show_game_menu {
unoverlay_dialog = true
}
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
show_game_menu()
end