Initial commit
BIN
arrow_left.png
Normal file
|
After Width: | Height: | Size: 177 B |
BIN
arrow_right.png
Normal file
|
After Width: | Height: | Size: 182 B |
BIN
bg128.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
bg_translucent.png
Normal file
|
After Width: | Height: | Size: 300 B |
BIN
btn_bg.png
Normal file
|
After Width: | Height: | Size: 288 B |
BIN
btn_bg_2.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
btn_bg_2_dark.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
btn_bg_2_dark_hover.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
btn_bg_2_hover.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
btn_bg_2_light.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
cancel.png
Normal file
|
After Width: | Height: | Size: 178 B |
BIN
checkbox_empty.png
Normal file
|
After Width: | Height: | Size: 140 B |
BIN
checkbox_filled.png
Normal file
|
After Width: | Height: | Size: 177 B |
BIN
circle.png
Normal file
|
After Width: | Height: | Size: 171 B |
BIN
circle_light.png
Normal file
|
After Width: | Height: | Size: 171 B |
BIN
content.png
Normal file
|
After Width: | Height: | Size: 180 B |
BIN
darken64.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
games.png
Normal file
|
After Width: | Height: | Size: 168 B |
BIN
glow.png
Normal file
|
After Width: | Height: | Size: 287 B |
BIN
logo_3d.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
main_bg.png
Normal file
|
After Width: | Height: | Size: 101 B |
BIN
menu_content.png
Normal file
|
After Width: | Height: | Size: 285 B |
BIN
menu_content_hovered.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
menu_servers.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
menu_servers_creative.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
menu_servers_favorite.png
Normal file
|
After Width: | Height: | Size: 216 B |
BIN
menu_servers_hovered.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
menu_servers_icon_ping_0.png
Normal file
|
After Width: | Height: | Size: 117 B |
BIN
menu_servers_icon_ping_1.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
menu_servers_icon_ping_2.png
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
menu_servers_icon_ping_3.png
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
menu_servers_icon_ping_4.png
Normal file
|
After Width: | Height: | Size: 145 B |
BIN
menu_servers_mods.png
Normal file
|
After Width: | Height: | Size: 246 B |
BIN
menu_servers_peaceful.png
Normal file
|
After Width: | Height: | Size: 188 B |
BIN
menu_servers_players.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
menu_servers_unfavorite.png
Normal file
|
After Width: | Height: | Size: 265 B |
BIN
menu_tab_bg.png
Normal file
|
After Width: | Height: | Size: 265 B |
BIN
menu_tab_content.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
menu_tab_servers.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
mtmenu.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
new_package.png
Normal file
|
After Width: | Height: | Size: 121 B |
BIN
refresh.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
search.png
Normal file
|
After Width: | Height: | Size: 170 B |
589
templates.lua
Normal file
|
|
@ -0,0 +1,589 @@
|
||||||
|
--[[ MARK: - Template Engine
|
||||||
|
|
||||||
|
Builds a formspec from the contents of a game-provided main menu file.
|
||||||
|
|
||||||
|
=================================================================================
|
||||||
|
Menu File Documentation
|
||||||
|
=================================================================================
|
||||||
|
|
||||||
|
Menu files are essentially just formspecs, but with special semantics and extended syntax.
|
||||||
|
|
||||||
|
First, menu files are made up of a number of dialog definitions. A dialog definition is
|
||||||
|
basically an HTML tag, where the tag name is the name of the dialog. For example this:
|
||||||
|
```
|
||||||
|
<main>
|
||||||
|
label[2,2;A label]
|
||||||
|
</main>
|
||||||
|
<other>
|
||||||
|
button[1,1;4,1;button;This is a button]
|
||||||
|
</other>
|
||||||
|
```
|
||||||
|
will define a dialog named 'main', with a label in it, and a dialog named 'other',
|
||||||
|
with a button in it. Every menu file must have a dialog named 'main', which serves
|
||||||
|
as the entry point of the game's menu.
|
||||||
|
|
||||||
|
Note that to prevent dialog definitions from conflicting with the contents of
|
||||||
|
hypertext[] elements, they must not have leading whitespace before the opening
|
||||||
|
delimiter.
|
||||||
|
|
||||||
|
Being able to define these other dialogs would be pretty pointless if they couldn't
|
||||||
|
be used for anything. Accordingly, you can use standard formspec actions, e.g. buttons,
|
||||||
|
to segue to a different dialog. To do this, set the action name to
|
||||||
|
'.show_dialog_<name>', where <name> is the name of the dialog you want to open.
|
||||||
|
You can also use '.overlay_dialog_<name>' to draw the target dialog on top of the
|
||||||
|
current dialog, and '.unoverlay_dialog' to hide it again.
|
||||||
|
|
||||||
|
There are several other action patterns that invoke special behavior:
|
||||||
|
-
|
||||||
|
- '.play': Start the game.
|
||||||
|
- '.play_with_world_named_<name>': Start the game on the specified world. If the
|
||||||
|
world in question does not exist, it will be created.
|
||||||
|
|
||||||
|
It is also possible to define metadata for the main menu. To do this, create a dialog
|
||||||
|
section named 'meta'. The contents of this section will then be parsed in the same way
|
||||||
|
as minetest.conf instead of being treated as a dialog.
|
||||||
|
|
||||||
|
Available metadata options are:
|
||||||
|
- 'enable_clouds': true/false (defaults to false)
|
||||||
|
|
||||||
|
|
||||||
|
Now, all this is good when we know exactly what content we want on the main menu.
|
||||||
|
But what if we want to render e.g. a list of worlds, or even a list of game modes?
|
||||||
|
This can be done using the @foreach construct. @foreach takes in a list of values,
|
||||||
|
the name of a JSON file, or a reference to a menu-exposed list.
|
||||||
|
|
||||||
|
The major advantage of a foreach loop, however, is interpolation. Inside a foreach
|
||||||
|
loop, the pattern '${<expression>}' will be replaces with the result of <expression>.
|
||||||
|
|
||||||
|
Inside an expression, you can reference variables as '@<name>'. Which variables are
|
||||||
|
available depends on the iterated list. In the case of a list literal, the current
|
||||||
|
item is exposed as a variable named @item, while for a JSON file, each key in the
|
||||||
|
file's top-level object will correspond to a variable. (Note that variable names may
|
||||||
|
only contain letters or numbers, even if mapped from a JSON file.)
|
||||||
|
|
||||||
|
The menu-provided lists are:
|
||||||
|
- @WORLDS: The list of worlds for this game. Exposes @name (the world name),
|
||||||
|
@path (the world's full absolute path), @gameid (the ID of the current game),
|
||||||
|
and @selected (whether the world is currently selected).
|
||||||
|
- @MODS: The list of installed mods that are not incompatible with this game.
|
||||||
|
Exposes @name, @title, @description, @author, @path, @depends, and @optional_depends.
|
||||||
|
- @WORLDMODS: The list of mods installed on the current world. No-op if no world
|
||||||
|
is selected.
|
||||||
|
|
||||||
|
Expressions also support rudimentary mathematical operations, namely addition (+),
|
||||||
|
'subtraction' (+-), multiplication (*), division (/), and exponentiation (^). Trying
|
||||||
|
to perform math on a non-tonumber()-able variable will treat the variable as 0.
|
||||||
|
|
||||||
|
As an example, this:
|
||||||
|
```
|
||||||
|
label[1,1;Worlds:]
|
||||||
|
@foreach:$WORLDS:name
|
||||||
|
label[1,${@i + 1};World named ${@name}]
|
||||||
|
@endforeach:name
|
||||||
|
|
||||||
|
@foreach:[one,two,three]:name2
|
||||||
|
label[4,${@i + 1};Item ${@i} is named: ${@item}]
|
||||||
|
@endforeach:name2
|
||||||
|
```
|
||||||
|
will:
|
||||||
|
- Create a "Worlds" label at 1,1;
|
||||||
|
- Create a label for every world with that world's name and a Y coordinate that
|
||||||
|
corresponds to the world's position in the list, plus 1 so these labels start
|
||||||
|
below the existing label.
|
||||||
|
- Create labels for "one", "two", and "three" that display the item's value and
|
||||||
|
its position in the list, with similarly increasing Y-coordinates.
|
||||||
|
|
||||||
|
Besides foreach loops, menu files also support conditionals. Conditionals are
|
||||||
|
written as '@if:<condition>:<name> ... @else:<name> ... @endif:<name>', where
|
||||||
|
<condition> is any expression. The conditional will be replaced with the contents
|
||||||
|
of its first block if the condition evaluates to non-zero, and the contents of
|
||||||
|
its second block otherwise. Note that the second block may not be omitted due to
|
||||||
|
how the parser works.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
@if: @name = test :<name>
|
||||||
|
<formspec 1>
|
||||||
|
@else:<name>
|
||||||
|
<formspec 2>
|
||||||
|
@endif:<name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- The only uniqueness requirements for the name of a block is that it not be
|
||||||
|
the name of a statment of the same type contained in the body of that block.
|
||||||
|
This is so that the parser knows which `end` belongs to which block without
|
||||||
|
having to manage state.
|
||||||
|
|
||||||
|
Note: Because of the way the main menu works, image paths must be specified in full.
|
||||||
|
To make this non-painful, when referencing images use '$ASSET_PATH/<image name>'
|
||||||
|
instead of just the image name. $ASSET_PATH will be replaced with the actual path
|
||||||
|
to the game's menu/ directory, so images used by the main menu should be stored
|
||||||
|
there. You can use $ASSET_PATH in any context. Additionally, $DEFAULT_ASSET_PATH is
|
||||||
|
the path to the builtin assets folder, and $NO_IMAGE is the path to blank.png, in
|
||||||
|
case you need to stylistically unset a background image defined by a global style.
|
||||||
|
--]]
|
||||||
|
|
||||||
|
local fe = minetest.formspec_escape
|
||||||
|
local hte = minetest.hypertext_escape
|
||||||
|
|
||||||
|
local function build_template_dialog(fs, depth)
|
||||||
|
if fs:trim() == "" then return end
|
||||||
|
local dialog = {}
|
||||||
|
local i = 0
|
||||||
|
-- "(.-\n?)%s-@foreach:([^:]+):(%l*)\n(.-)\n%s-@endforeach:%3(\n?.*)"
|
||||||
|
-- Extract foreach loops
|
||||||
|
local prev = 0
|
||||||
|
while i < 1000 do
|
||||||
|
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 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 +1, fe_start -1), (depth or 0) +1)
|
||||||
|
dialog[#dialog +1] = {
|
||||||
|
foreach = fe_pattern:trim(),
|
||||||
|
name = fe_name,
|
||||||
|
content = build_template_dialog(fe_content:trim(), (depth or 0) +1)
|
||||||
|
}
|
||||||
|
prev = fe_end
|
||||||
|
i = i +1
|
||||||
|
end
|
||||||
|
|
||||||
|
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, if_start -1), (depth or 0) +1)
|
||||||
|
dialog[#dialog +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 = if_end
|
||||||
|
i = i +1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if prev > 1 then
|
||||||
|
dialog[#dialog +1] = build_template_dialog(fs:sub(prev +1))
|
||||||
|
end
|
||||||
|
|
||||||
|
if #dialog == 1 then dialog = dialog[1] end
|
||||||
|
if i == 0 then dialog = fs end
|
||||||
|
-- minetest.log(dump(dialog, " "))
|
||||||
|
return dialog
|
||||||
|
end
|
||||||
|
|
||||||
|
function build_game_menu(input)
|
||||||
|
-- MARK: Environment variables
|
||||||
|
input = "\n"..input
|
||||||
|
:gsub("%$ASSET_PATH", fe(state.current_game.path.."/menu"))
|
||||||
|
:gsub("%$DEFAULT_ASSET_PATH", fe(assets:sub(1, #assets -1)))
|
||||||
|
:gsub("%$NO_IMAGE", fe(default_textures.."blank.png"))
|
||||||
|
:gsub("/%*.-%*/", "")
|
||||||
|
local menu = {}
|
||||||
|
for name, fs in input:gmatch("%f[^\n]<(%l+)>(.-)%f[^\n]</%1>") do
|
||||||
|
if name == "meta" then
|
||||||
|
menu[name] = parse_conf_text(fs)
|
||||||
|
else
|
||||||
|
menu[name] = build_template_dialog(fs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- print(dump(menu, " "))
|
||||||
|
return menu
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Split a template expression into a binary-tree node.
|
||||||
|
local function split_template_expression(expr)
|
||||||
|
|
||||||
|
-- These are basically the operator definitions, listed from lowest
|
||||||
|
-- (evaluated last) to highest (evaluated first) precedence.
|
||||||
|
|
||||||
|
local a, b, op
|
||||||
|
-- Or
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(|)")
|
||||||
|
end
|
||||||
|
-- And
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(&)")
|
||||||
|
end
|
||||||
|
-- Greater than
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(>)")
|
||||||
|
end
|
||||||
|
-- Greater than or equal to
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(>=)")
|
||||||
|
end
|
||||||
|
-- Less than
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(<)")
|
||||||
|
end
|
||||||
|
-- Less than or equal to
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(<=)")
|
||||||
|
end
|
||||||
|
-- Equal to
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(==)")
|
||||||
|
end
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(=)")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Addition
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(+)")
|
||||||
|
end
|
||||||
|
-- Multiplication
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(*)")
|
||||||
|
end
|
||||||
|
-- Division
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(/)")
|
||||||
|
end
|
||||||
|
-- Modulo
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(%%)")
|
||||||
|
end
|
||||||
|
-- Exponent
|
||||||
|
if not a then
|
||||||
|
a, b, op = expr:find("(%^)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not a then
|
||||||
|
return {value = expr}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
op = op,
|
||||||
|
lhs = split_template_expression(expr:sub(1, a -1)),
|
||||||
|
rhs = split_template_expression(expr:sub(b +1))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Reduce a template expression from a binary-tree node into a single value.
|
||||||
|
local function reduce_template_expression(tree)
|
||||||
|
if tree.op == "|" then
|
||||||
|
return (reduce_template_expression(tree.lhs) ~= "0" or reduce_template_expression(tree.rhs) ~= "0") and "1" or "0"
|
||||||
|
elseif tree.op == "&" then
|
||||||
|
return (reduce_template_expression(tree.lhs) ~= "0" and reduce_template_expression(tree.rhs) ~= "0") and "1" or "0"
|
||||||
|
elseif tree.op == ">" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) > (tonumber(reduce_template_expression(tree.rhs)) or 0) and "1" or "0"
|
||||||
|
elseif tree.op == ">=" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) >= (tonumber(reduce_template_expression(tree.rhs)) or 0) and "1" or "0"
|
||||||
|
elseif tree.op == "<" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) < (tonumber(reduce_template_expression(tree.rhs)) or 0) and "1" or "0"
|
||||||
|
elseif tree.op == "<=" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) <= (tonumber(reduce_template_expression(tree.rhs)) or 0) and "1" or "0"
|
||||||
|
elseif tree.op == "=" or tree.op == "==" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) == (tonumber(reduce_template_expression(tree.rhs)) or 0) and "1" or "0"
|
||||||
|
elseif tree.op == "+" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) + (tonumber(reduce_template_expression(tree.rhs)) or 0)
|
||||||
|
elseif tree.op == "*" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) * (tonumber(reduce_template_expression(tree.rhs)) or 0)
|
||||||
|
elseif tree.op == "/" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) / (tonumber(reduce_template_expression(tree.rhs)) or 0)
|
||||||
|
elseif tree.op == "%" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) % (tonumber(reduce_template_expression(tree.rhs)) or 0)
|
||||||
|
elseif tree.op == "^" then
|
||||||
|
return (tonumber(reduce_template_expression(tree.lhs)) or 0) ^ (tonumber(reduce_template_expression(tree.rhs)) or 0)
|
||||||
|
else
|
||||||
|
return tree.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Evaluate an interpolation expression, with an optional table of variables.
|
||||||
|
local function evaluate_template_expression(expr, vars, depth)
|
||||||
|
if expr == "" then return "0" end
|
||||||
|
if not depth then depth = 0 end
|
||||||
|
-- This handles the case where vars is omitted, because then it ends up
|
||||||
|
-- just setting vars to an empty table.
|
||||||
|
if type(vars) ~= "table" then
|
||||||
|
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
|
||||||
|
local a, b, name = expr:find("@([%a_]+)", offset)
|
||||||
|
if not a then break end
|
||||||
|
-- If referencing an undefined variable, default to 0 because it's safest that way.
|
||||||
|
local result = minetest.formspec_escape(tostring(vars[name] or "0"))
|
||||||
|
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
|
||||||
|
while offset < 100000 do
|
||||||
|
local a, b, se = expr:find("(%b())", offset)
|
||||||
|
if not a then break end
|
||||||
|
se = se:gsub("^%((.*)%)$", "%1")
|
||||||
|
local result = evaluate_template_expression(se, vars, depth +1)
|
||||||
|
expr = expr:sub(1, a -1)..result..expr:sub(b +1)
|
||||||
|
offset = a +#result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Expression parsing
|
||||||
|
local tree = split_template_expression(expr)
|
||||||
|
return tostring(reduce_template_expression(tree))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do variable interpolation for the given formspec using the provided variable table.
|
||||||
|
local function evaluate_template_block(fs, vars)
|
||||||
|
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
|
||||||
|
|
||||||
|
-- Interpret the list expression of a foreach loop, then iterate.
|
||||||
|
local function evaluate_template_foreach(loop, vars)
|
||||||
|
local out = ""
|
||||||
|
local list = {}
|
||||||
|
if loop.foreach:sub(1,1) == "[" then
|
||||||
|
list = loop.foreach:gsub("^%[(.*)%]$", "%1"):split(",")
|
||||||
|
elseif loop.foreach:sub(1, 1) == "@" then
|
||||||
|
local var = loop.foreach:sub(2)
|
||||||
|
-- Builtin variables take precedence.
|
||||||
|
if var == "WORLDS" then
|
||||||
|
list = get_worlds_for_game(state.current_game.id)
|
||||||
|
elseif var == "MODS" then
|
||||||
|
list = get_mods_for_game(state.current_game.id)
|
||||||
|
elseif var == "WORLDMODS" then
|
||||||
|
list = state.menu_vars.selected_world and get_mods_for_world(state.menu_vars.selected_world) or {}
|
||||||
|
else
|
||||||
|
list = vars[var]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for i, x in ipairs(list) do
|
||||||
|
local vars2 = {}
|
||||||
|
if type(x) ~= "table" then
|
||||||
|
vars2.item = x
|
||||||
|
else
|
||||||
|
for k, v in pairs(x) do
|
||||||
|
vars2[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
vars.i = i
|
||||||
|
out = out..evaluate_game_dialog(loop.content, setmetatable(vars2, {__index = vars, __newindex = vars})).."\n"
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Determine which branch of an if statement should be evaluated.
|
||||||
|
local function evaluate_template_conditional(cond, vars)
|
||||||
|
local out = ""
|
||||||
|
local list = {}
|
||||||
|
local condition = evaluate_template_expression(cond.condition, vars)
|
||||||
|
if condition ~= "0" then
|
||||||
|
out = out..evaluate_game_dialog(cond.content, vars).."\n"
|
||||||
|
else
|
||||||
|
out = out..evaluate_game_dialog(cond.else_content, vars).."\n"
|
||||||
|
end
|
||||||
|
-- print("Evaluated condition `"..cond.condition.."` as "..out)
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Process the syntax tree for a game dialog and output the resulting string.
|
||||||
|
function evaluate_game_dialog(dialog, vars)
|
||||||
|
local out = ""
|
||||||
|
if not dialog then return out end
|
||||||
|
if type(dialog) == "string" then
|
||||||
|
return evaluate_template_block(dialog, vars)
|
||||||
|
elseif dialog.condition then
|
||||||
|
out = out..evaluate_template_conditional(dialog, vars)
|
||||||
|
elseif dialog.foreach then
|
||||||
|
out = out..evaluate_template_foreach(dialog, vars)
|
||||||
|
else
|
||||||
|
for _, c in ipairs(dialog) do
|
||||||
|
out = out..evaluate_game_dialog(c, vars)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template = {}
|
||||||
|
|
||||||
|
function template.build_expr(expr)
|
||||||
|
return {type = "expr", value = expr}
|
||||||
|
end
|
||||||
|
|
||||||
|
function template.build(menu)
|
||||||
|
-- First pass: Collect all tokens into a flat list
|
||||||
|
local block = {}
|
||||||
|
local item
|
||||||
|
local offset = 1
|
||||||
|
local pos = 1
|
||||||
|
while true do
|
||||||
|
local matched = false
|
||||||
|
local stmt_start, stmt_end, stmt = menu:find("{%%%s*(.-)%s*%%}", pos)
|
||||||
|
local expr_start, expr_end, expr = menu:find("{{(.-)}}", pos)
|
||||||
|
local comment_start, comment_end, comment = menu:find("{#(.-)#}", pos)
|
||||||
|
|
||||||
|
-- Statements
|
||||||
|
if stmt_start and stmt_start < (expr_start or math.huge) and stmt_start < (comment_start or math.huge) then
|
||||||
|
block[#block +1] = menu:sub(offset, stmt_start -1)
|
||||||
|
|
||||||
|
local verb = stmt:match "^(%a+)"
|
||||||
|
if verb == "if" then
|
||||||
|
item = {
|
||||||
|
type = "if",
|
||||||
|
parent = block,
|
||||||
|
conditions = {{condition = template.build_expr(stmt:match "if%s*(.*)"), body = {}}}
|
||||||
|
}
|
||||||
|
block[#block +1] = item
|
||||||
|
block = item.conditions[1].body
|
||||||
|
elseif verb == "elseif" then
|
||||||
|
local x = {
|
||||||
|
condition = template.build_expr(stmt:match "if%s*(.*)"),
|
||||||
|
body = {}
|
||||||
|
}
|
||||||
|
item.conditions[#item.conditions +1] = x
|
||||||
|
block = x.body
|
||||||
|
elseif verb == "else" then
|
||||||
|
local x = {
|
||||||
|
condition = template.build_expr("true"),
|
||||||
|
body = {}
|
||||||
|
}
|
||||||
|
item.conditions[#item.conditions +1] = x
|
||||||
|
block = x.body
|
||||||
|
elseif verb == "endif" then
|
||||||
|
block = item.parent
|
||||||
|
elseif verb == "for" then
|
||||||
|
|
||||||
|
elseif verb == "endfor" then
|
||||||
|
|
||||||
|
elseif verb == "macro" then
|
||||||
|
|
||||||
|
elseif verb == "endmacro" then
|
||||||
|
|
||||||
|
elseif verb == "set" then
|
||||||
|
|
||||||
|
elseif verb == "endset" then
|
||||||
|
|
||||||
|
elseif verb == "include" then
|
||||||
|
|
||||||
|
end
|
||||||
|
print(verb)
|
||||||
|
|
||||||
|
offset = stmt_end +1
|
||||||
|
pos = stmt_end
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Interpolations
|
||||||
|
if expr_start and expr_start < (stmt_start or math.huge) and expr_start < (comment_start or math.huge) then
|
||||||
|
block[#block +1] = menu:sub(offset, expr_start -1)
|
||||||
|
|
||||||
|
block[#block +1] = template.build_expr(expr)
|
||||||
|
|
||||||
|
offset = expr_end +1
|
||||||
|
pos = expr_end
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Comments
|
||||||
|
if comment_start and comment_start < (stmt_start or math.huge) and comment_start < (expr_start or math.huge) then
|
||||||
|
offset = expr_end
|
||||||
|
pos = expr_end
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if not matched then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return block
|
||||||
|
end
|
||||||
|
|
||||||
|
function template.evaluate_expr(expr)
|
||||||
|
if expr.type == "" then
|
||||||
|
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function template.evaluate(dialog, vars)
|
||||||
|
local out = ""
|
||||||
|
for _, item in ipairs(dialog) do
|
||||||
|
if type(item) == "string" then
|
||||||
|
out = out..item
|
||||||
|
else
|
||||||
|
if item.type == "if" then
|
||||||
|
for _, cond in ipairs(item.conditions) do
|
||||||
|
if template.evaluate_expr(cond.condition) then
|
||||||
|
out = out..template.evaluate(cond.body)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif item.type == "for" then
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
print(template.evaluate(template.build [[
|
||||||
|
This is a {{blah}}.
|
||||||
|
|
||||||
|
{% if foo %}
|
||||||
|
Foo
|
||||||
|
{% elseif bar %}
|
||||||
|
Bar
|
||||||
|
{% else %}
|
||||||
|
Baz
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for x in [a,b,c] %}
|
||||||
|
The item is {{ x }}!
|
||||||
|
Uppercase: {{ x:upper() }}
|
||||||
|
{% endfor %}
|
||||||
|
]], {
|
||||||
|
foo = false
|
||||||
|
}))
|
||||||
BIN
white.png
Normal file
|
After Width: | Height: | Size: 95 B |