Lua works; narrator works

This commit is contained in:
2025-09-17 18:08:26 +03:00
parent 1977a12d8b
commit cfd9ed8708
24 changed files with 4557 additions and 100 deletions

View File

@@ -72,6 +72,7 @@ add_subdirectory(src/miniaudio)
add_subdirectory(src/sound)
add_subdirectory(audio/gui)
add_subdirectory(tests)
add_subdirectory(lua-scripts)
# add the source files as usual
add_executable(0_Bootstrap Bootstrap.cpp)
@@ -192,17 +193,6 @@ if(OGRE_STATIC)
target_link_libraries(Editor fix::assimp pugixml)
endif()
file(GLOB LUA_SCRIPTS_SRC ${CMAKE_SOURCE_DIR}/lua-scripts/*.lua)
set(LUA_SCRIPTS_OUTPUT)
foreach(LUA_SCRIPT_FILE ${LUA_SCRIPTS_SRC})
get_filename_component(FILE_NAME ${LUA_SCRIPT_FILE} NAME_WE)
set(LUA_SCRIPT_OUTPUT_FILE ${CMAKE_BINARY_DIR}/lua-scripts/${FILE_NAME}.lua)
add_custom_command(OUTPUT ${LUA_SCRIPT_OUTPUT_FILE}
COMMAND ${CMAKE_COMMAND} -E copy ${LUA_SCRIPT_FILE} ${LUA_SCRIPT_OUTPUT_FILE}
DEPENDS ${LUA_SCRIPT_FILE})
list(APPEND LUA_SCRIPTS_OUTPUT ${LUA_SCRIPT_OUTPUT_FILE})
endforeach()
add_custom_target(stage_lua_scripts ALL DEPENDS ${LUA_SCRIPTS_OUTPUT})
add_dependencies(TerrainTest stage_lua_scripts stage_files)
add_custom_command(

View File

@@ -210,7 +210,6 @@ public:
bool updated = false;
if (isGuiEnabled())
return false;
std::cout << "GUI not enabled\n";
if (evt.keysym.sym == OgreBites::SDLK_ESCAPE) {
OgreAssert(ECS::get().has<ECS::GUI>(), "");
setGuiEnabled(true);
@@ -336,6 +335,11 @@ public:
{
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"Water", true);
Ogre::ResourceGroupManager::getSingleton().createResourceGroup(
"LuaScripts", false);
Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
"./lua-scripts", "FileSystem", "LuaScripts", true,
true);
OgreBites::ApplicationContext::locateResources();
}
void loadResources() override

BIN
assets/blender/altar.blend (Stored with Git LFS)

Binary file not shown.

View File

@@ -0,0 +1,24 @@
project(lua-scripts)
set(LUA_SCRIPTS_SRC
data.lua
)
set(LUA_SCRIPTS_OUTPUT)
set(LUA_PACKAGES narrator stories)
foreach(LUA_SCRIPT_FILE ${LUA_SCRIPTS_SRC})
get_filename_component(FILE_NAME ${LUA_SCRIPT_FILE} NAME_WE)
set(LUA_SCRIPT_OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.lua)
add_custom_command(OUTPUT ${LUA_SCRIPT_OUTPUT_FILE}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${LUA_SCRIPT_FILE} ${LUA_SCRIPT_OUTPUT_FILE}
DEPENDS ${LUA_SCRIPT_FILE})
list(APPEND LUA_SCRIPTS_OUTPUT ${LUA_SCRIPT_OUTPUT_FILE})
endforeach()
set(LUA_PACKAGES_OUTPUT)
foreach(LUA_PACKAGE ${LUA_PACKAGES})
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LUA_PACKAGE}
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/${LUA_PACKAGE}
${CMAKE_CURRENT_BINARY_DIR}/${LUA_PACKAGE}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${LUA_PACKAGE})
list(APPEND LUA_PACKAGES_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LUA_PACKAGE})
endforeach()
add_custom_target(stage_lua_scripts ALL DEPENDS ${LUA_SCRIPTS_OUTPUT} ${LUA_PACKAGES_OUTPUT})

View File

@@ -36,6 +36,7 @@ function foo()
end
v = Vector3(0, 1, 2)
end
--[[
narration = {
position = 1,
narration_start = {
@@ -59,15 +60,74 @@ dropped you into the sea. Last thing you heard before you hit the water was happ
return ret
end,
}
]]--
local narrator = require('narrator.narrator')
-- Parse a book from the Ink file.
local book = narrator.parse_file('stories.game')
-- Init a story from the book
local story = narrator.init_story(book)
-- Begin the story
story:begin()
function dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
function _narration()
local ret = ""
local choices = {}
if story:can_continue() then
local paragraphs = story:continue()
for _, paragraph in ipairs(paragraphs) do
local text = paragraph.text
if paragraph.tags then
text = text .. ' #' .. table.concat(paragraph.tags, ' #')
end
ret = ret .. text
end
if story:can_choose() then
local ch = story:get_choices()
for i, choice in ipairs(ch) do
table.insert(choices, choice.text)
print(i, dump(choice))
end
end
end
if (#choices > 0) then
print("choices!!!")
narrate(ret, choices)
else
narrate(ret)
end
end
setup_handler(function(event)
print(event)
if event == "startup" then
main_menu()
elseif event == "narration_progress" then
narrate(narration:progress())
_narration()
elseif event == "narration_answered" then
local answer = narration_get_answer()
story:choose(answer)
_narration()
elseif event == "new_game" then
narrate(narration:progress())
local ret = ""
story = narrator.init_story(book)
story:begin()
_narration()
end
end)

View File

@@ -0,0 +1,37 @@
---@class Narrator.Book.Version
---@field engine number
---@field tree number
---@class Narrator.Book
---@field version Narrator.Book.Version
---@field inclusions string[]
---@field lists table
---@field constants table
---@field variables table
---@field params table
---@field tree table
---@class Narrator.ParsingParams
---@field save boolean Save a parsed book to the lua file
---@class Narrator.Paragraph
---@field text string
---@field tags string[]|nil
---@class Narrator.Choice
---@field text string
---@field tags string[]|nil
---@class Narrator.State
---@field version number
---@field temp table
---@field seeds table
---@field variables table
---@field params table|nil
---@field visits table
---@field current_path table
---@field paragraphs table
---@field choices table
---@field output table
---@field tunnels table|nil
---@field path table

View File

@@ -0,0 +1,32 @@
local enums = {
---Bump it when the state structure is changed
engine_version = 2,
---@enum Narrator.ItemType
item = {
text = 1,
alts = 2,
choice = 3,
condition = 4,
variable = 5
},
---@enum Narrator.Sequence
sequence = {
cycle = 1,
stopping = 2,
once = 3
},
---@enum Narrator.ReadMode
read_mode = {
text = 1,
choices = 2,
gathers = 3,
quit = 4
}
}
return enums

View File

@@ -0,0 +1,68 @@
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.
--
local Object = {}
Object.__index = Object
function Object:new()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:implement(...)
for _, cls in pairs({...}) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__tostring()
return "Object"
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

View File

@@ -0,0 +1,780 @@
--
-- lume
--
-- Copyright (c) 2020 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local lume = { _version = "2.3.0" }
local pairs, ipairs = pairs, ipairs
local type, assert, unpack = type, assert, unpack or table.unpack
local tostring, tonumber = tostring, tonumber
local math_floor = math.floor
local math_ceil = math.ceil
local math_atan2 = math.atan2 or math.atan
local math_sqrt = math.sqrt
local math_abs = math.abs
local noop = function()
end
local identity = function(x)
return x
end
local patternescape = function(str)
return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
end
local absindex = function(len, i)
return i < 0 and (len + i + 1) or i
end
local iscallable = function(x)
if type(x) == "function" then return true end
local mt = getmetatable(x)
return mt and mt.__call ~= nil
end
local getiter = function(x)
if lume.isarray(x) then
return ipairs
elseif type(x) == "table" then
return pairs
end
error("expected table", 3)
end
local iteratee = function(x)
if x == nil then return identity end
if iscallable(x) then return x end
if type(x) == "table" then
return function(z)
for k, v in pairs(x) do
if z[k] ~= v then return false end
end
return true
end
end
return function(z) return z[x] end
end
function lume.clamp(x, min, max)
return x < min and min or (x > max and max or x)
end
function lume.round(x, increment)
if increment then return lume.round(x / increment) * increment end
return x >= 0 and math_floor(x + .5) or math_ceil(x - .5)
end
function lume.sign(x)
return x < 0 and -1 or 1
end
function lume.lerp(a, b, amount)
return a + (b - a) * lume.clamp(amount, 0, 1)
end
function lume.smooth(a, b, amount)
local t = lume.clamp(amount, 0, 1)
local m = t * t * (3 - 2 * t)
return a + (b - a) * m
end
function lume.pingpong(x)
return 1 - math_abs(1 - x % 2)
end
function lume.distance(x1, y1, x2, y2, squared)
local dx = x1 - x2
local dy = y1 - y2
local s = dx * dx + dy * dy
return squared and s or math_sqrt(s)
end
function lume.angle(x1, y1, x2, y2)
return math_atan2(y2 - y1, x2 - x1)
end
function lume.vector(angle, magnitude)
return math.cos(angle) * magnitude, math.sin(angle) * magnitude
end
function lume.random(a, b)
if not a then a, b = 0, 1 end
if not b then b = 0 end
return a + math.random() * (b - a)
end
function lume.randomchoice(t)
return t[math.random(#t)]
end
function lume.weightedchoice(t)
local sum = 0
for _, v in pairs(t) do
assert(v >= 0, "weight value less than zero")
sum = sum + v
end
assert(sum ~= 0, "all weights are zero")
local rnd = lume.random(sum)
for k, v in pairs(t) do
if rnd < v then return k end
rnd = rnd - v
end
end
function lume.isarray(x)
return type(x) == "table" and x[1] ~= nil
end
function lume.push(t, ...)
local n = select("#", ...)
for i = 1, n do
t[#t + 1] = select(i, ...)
end
return ...
end
function lume.remove(t, x)
local iter = getiter(t)
for i, v in iter(t) do
if v == x then
if lume.isarray(t) then
table.remove(t, i)
break
else
t[i] = nil
break
end
end
end
return x
end
function lume.clear(t)
local iter = getiter(t)
for k in iter(t) do
t[k] = nil
end
return t
end
function lume.extend(t, ...)
for i = 1, select("#", ...) do
local x = select(i, ...)
if x then
for k, v in pairs(x) do
t[k] = v
end
end
end
return t
end
function lume.shuffle(t)
local rtn = {}
for i = 1, #t do
local r = math.random(i)
if r ~= i then
rtn[i] = rtn[r]
end
rtn[r] = t[i]
end
return rtn
end
function lume.sort(t, comp)
local rtn = lume.clone(t)
if comp then
if type(comp) == "string" then
table.sort(rtn, function(a, b) return a[comp] < b[comp] end)
else
table.sort(rtn, comp)
end
else
table.sort(rtn)
end
return rtn
end
function lume.array(...)
local t = {}
for x in ... do t[#t + 1] = x end
return t
end
function lume.each(t, fn, ...)
local iter = getiter(t)
if type(fn) == "string" then
for _, v in iter(t) do v[fn](v, ...) end
else
for _, v in iter(t) do fn(v, ...) end
end
return t
end
function lume.map(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
local rtn = {}
for k, v in iter(t) do rtn[k] = fn(v) end
return rtn
end
function lume.all(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
for _, v in iter(t) do
if not fn(v) then return false end
end
return true
end
function lume.any(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
for _, v in iter(t) do
if fn(v) then return true end
end
return false
end
function lume.reduce(t, fn, first)
local started = first ~= nil
local acc = first
local iter = getiter(t)
for _, v in iter(t) do
if started then
acc = fn(acc, v)
else
acc = v
started = true
end
end
assert(started, "reduce of an empty table with no first value")
return acc
end
function lume.unique(t)
local rtn = {}
for k in pairs(lume.invert(t)) do
rtn[#rtn + 1] = k
end
return rtn
end
function lume.filter(t, fn, retainkeys)
fn = iteratee(fn)
local iter = getiter(t)
local rtn = {}
if retainkeys then
for k, v in iter(t) do
if fn(v) then rtn[k] = v end
end
else
for _, v in iter(t) do
if fn(v) then rtn[#rtn + 1] = v end
end
end
return rtn
end
function lume.reject(t, fn, retainkeys)
fn = iteratee(fn)
local iter = getiter(t)
local rtn = {}
if retainkeys then
for k, v in iter(t) do
if not fn(v) then rtn[k] = v end
end
else
for _, v in iter(t) do
if not fn(v) then rtn[#rtn + 1] = v end
end
end
return rtn
end
function lume.merge(...)
local rtn = {}
for i = 1, select("#", ...) do
local t = select(i, ...)
local iter = getiter(t)
for k, v in iter(t) do
rtn[k] = v
end
end
return rtn
end
function lume.concat(...)
local rtn = {}
for i = 1, select("#", ...) do
local t = select(i, ...)
if t ~= nil then
local iter = getiter(t)
for _, v in iter(t) do
rtn[#rtn + 1] = v
end
end
end
return rtn
end
function lume.find(t, value)
local iter = getiter(t)
for k, v in iter(t) do
if v == value then return k end
end
return nil
end
function lume.match(t, fn)
fn = iteratee(fn)
local iter = getiter(t)
for k, v in iter(t) do
if fn(v) then return v, k end
end
return nil
end
function lume.count(t, fn)
local count = 0
local iter = getiter(t)
if fn then
fn = iteratee(fn)
for _, v in iter(t) do
if fn(v) then count = count + 1 end
end
else
if lume.isarray(t) then
return #t
end
for _ in iter(t) do count = count + 1 end
end
return count
end
function lume.slice(t, i, j)
i = i and absindex(#t, i) or 1
j = j and absindex(#t, j) or #t
local rtn = {}
for x = i < 1 and 1 or i, j > #t and #t or j do
rtn[#rtn + 1] = t[x]
end
return rtn
end
function lume.first(t, n)
if not n then return t[1] end
return lume.slice(t, 1, n)
end
function lume.last(t, n)
if not n then return t[#t] end
return lume.slice(t, -n, -1)
end
function lume.invert(t)
local rtn = {}
for k, v in pairs(t) do rtn[v] = k end
return rtn
end
function lume.pick(t, ...)
local rtn = {}
for i = 1, select("#", ...) do
local k = select(i, ...)
rtn[k] = t[k]
end
return rtn
end
function lume.keys(t)
local rtn = {}
local iter = getiter(t)
for k in iter(t) do rtn[#rtn + 1] = k end
return rtn
end
function lume.clone(t)
local rtn = {}
for k, v in pairs(t) do rtn[k] = v end
return rtn
end
function lume.fn(fn, ...)
assert(iscallable(fn), "expected a function as the first argument")
local args = { ... }
return function(...)
local a = lume.concat(args, { ... })
return fn(unpack(a))
end
end
function lume.once(fn, ...)
local f = lume.fn(fn, ...)
local done = false
return function(...)
if done then return end
done = true
return f(...)
end
end
local memoize_fnkey = {}
local memoize_nil = {}
function lume.memoize(fn)
local cache = {}
return function(...)
local c = cache
for i = 1, select("#", ...) do
local a = select(i, ...) or memoize_nil
c[a] = c[a] or {}
c = c[a]
end
c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)}
return unpack(c[memoize_fnkey])
end
end
function lume.combine(...)
local n = select('#', ...)
if n == 0 then return noop end
if n == 1 then
local fn = select(1, ...)
if not fn then return noop end
assert(iscallable(fn), "expected a function or nil")
return fn
end
local funcs = {}
for i = 1, n do
local fn = select(i, ...)
if fn ~= nil then
assert(iscallable(fn), "expected a function or nil")
funcs[#funcs + 1] = fn
end
end
return function(...)
for _, f in ipairs(funcs) do f(...) end
end
end
function lume.call(fn, ...)
if fn then
return fn(...)
end
end
function lume.time(fn, ...)
local start = os.clock()
local rtn = {fn(...)}
return (os.clock() - start), unpack(rtn)
end
local lambda_cache = {}
function lume.lambda(str)
if not lambda_cache[str] then
local args, body = str:match([[^([%w,_ ]-)%->(.-)$]])
assert(args and body, "bad string lambda")
local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend"
lambda_cache[str] = lume.dostring(s)
end
return lambda_cache[str]
end
local serialize
local serialize_map = {
[ "boolean" ] = tostring,
[ "nil" ] = tostring,
[ "string" ] = function(v) return string.format("%q", v) end,
[ "number" ] = function(v)
if v ~= v then return "0/0" -- nan
elseif v == 1 / 0 then return "1/0" -- inf
elseif v == -1 / 0 then return "-1/0" end -- -inf
return tostring(v)
end,
[ "table" ] = function(t, stk)
stk = stk or {}
if stk[t] then error("circular reference") end
local rtn = {}
stk[t] = true
for k, v in pairs(t) do
rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk)
end
stk[t] = nil
return "{" .. table.concat(rtn, ",") .. "}"
end
}
setmetatable(serialize_map, {
__index = function(_, k) error("unsupported serialize type: " .. k) end
})
serialize = function(x, stk)
return serialize_map[type(x)](x, stk)
end
function lume.serialize(x)
return serialize(x)
end
function lume.deserialize(str)
return lume.dostring("return " .. str)
end
function lume.split(str, sep)
if not sep then
return lume.array(str:gmatch("([%S]+)"))
else
assert(sep ~= "", "empty separator")
local psep = patternescape(sep)
return lume.array((str..sep):gmatch("(.-)("..psep..")"))
end
end
function lume.trim(str, chars)
if not chars then return str:match("^[%s]*(.-)[%s]*$") end
chars = patternescape(chars)
return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$")
end
function lume.wordwrap(str, limit)
limit = limit or 72
local check
if type(limit) == "number" then
check = function(s) return #s >= limit end
else
check = limit
end
local rtn = {}
local line = ""
for word, spaces in str:gmatch("(%S+)(%s*)") do
local s = line .. word
if check(s) then
table.insert(rtn, line .. "\n")
line = word
else
line = s
end
for c in spaces:gmatch(".") do
if c == "\n" then
table.insert(rtn, line .. "\n")
line = ""
else
line = line .. c
end
end
end
table.insert(rtn, line)
return table.concat(rtn)
end
function lume.format(str, vars)
if not vars then return str end
local f = function(x)
return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}")
end
return (str:gsub("{(.-)}", f))
end
function lume.trace(...)
local info = debug.getinfo(2, "Sl")
local t = { info.short_src .. ":" .. info.currentline .. ":" }
for i = 1, select("#", ...) do
local x = select(i, ...)
if type(x) == "number" then
x = string.format("%g", lume.round(x, .01))
end
t[#t + 1] = tostring(x)
end
print(table.concat(t, " "))
end
function lume.dostring(str)
return assert((loadstring or load)(str))()
end
function lume.uuid()
local fn = function(x)
local r = math.random(16) - 1
r = (x == "x") and (r + 1) or (r % 4) + 9
return ("0123456789abcdef"):sub(r, r)
end
return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
end
function lume.hotswap(modname)
local oldglobal = lume.clone(_G)
local updated = {}
local function update(old, new)
if updated[old] then return end
updated[old] = true
local oldmt, newmt = getmetatable(old), getmetatable(new)
if oldmt and newmt then update(oldmt, newmt) end
for k, v in pairs(new) do
if type(v) == "table" then update(old[k], v) else old[k] = v end
end
end
local err = nil
local function onerror(e)
for k in pairs(_G) do _G[k] = oldglobal[k] end
err = lume.trim(e)
end
local ok, oldmod = pcall(require, modname)
oldmod = ok and oldmod or nil
xpcall(function()
package.loaded[modname] = nil
local newmod = require(modname)
if type(oldmod) == "table" then update(oldmod, newmod) end
for k, v in pairs(oldglobal) do
if v ~= _G[k] and type(v) == "table" then
update(v, _G[k])
_G[k] = v
end
end
end, onerror)
package.loaded[modname] = oldmod
if err then return nil, err end
return oldmod
end
local ripairs_iter = function(t, i)
i = i - 1
local v = t[i]
if v ~= nil then
return i, v
end
end
function lume.ripairs(t)
return ripairs_iter, t, (#t + 1)
end
function lume.color(str, mul)
mul = mul or 1
local r, g, b, a
r, g, b = str:match("#(%x%x)(%x%x)(%x%x)")
if r then
r = tonumber(r, 16) / 0xff
g = tonumber(g, 16) / 0xff
b = tonumber(b, 16) / 0xff
a = 1
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
local f = str:gmatch("[%d.]+")
r = (f() or 0) / 0xff
g = (f() or 0) / 0xff
b = (f() or 0) / 0xff
a = f() or 1
else
error(("bad color string '%s'"):format(str))
end
return r * mul, g * mul, b * mul, a * mul
end
local chain_mt = {}
chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
function(fn)
return function(self, ...)
self._value = fn(self._value, ...)
return self
end
end)
chain_mt.__index.result = function(x) return x._value end
function lume.chain(value)
return setmetatable({ _value = value }, chain_mt)
end
setmetatable(lume, {
__call = function(_, ...)
return lume.chain(...)
end
})
return lume

View File

@@ -0,0 +1,401 @@
--
-- Dependencies
local lume = require('narrator.libs.lume')
--
-- Metatable
local mt = { lists = { } }
function mt.__tostring(self)
local pool = { }
local list_keys = { }
for key, _ in pairs(self) do
table.insert(list_keys, key)
end
table.sort(list_keys)
for i = 1, #list_keys do
local list_name = list_keys[i]
local list_items = self[list_name]
for index = 1, #mt.lists[list_name] do
pool[index] = pool[index] or { }
local item_name = mt.lists[list_name][index]
if list_items[item_name] == true then
table.insert(pool[index], 1, item_name)
end
end
end
local items = { }
for _, titles in ipairs(pool) do
for _, title in ipairs(titles) do
table.insert(items, title)
end
end
return table.concat(items, ', ')
end
--
-- Operators
function mt.__add(lhs, rhs) -- +
if type(rhs) == 'table' then
return mt.__add_list(lhs, rhs)
elseif type(rhs) == 'number' then
return mt.__shift_by_number(lhs, rhs)
else
error('Attempt to sum the list with ' .. type(rhs))
end
end
function mt.__sub(lhs, rhs) -- -
if type(rhs) == 'table' then
return mt.__subList(lhs, rhs)
elseif type(rhs) == 'number' then
return mt.__shift_by_number(lhs, -rhs)
else
error('Attempt to sub the list with ' .. type(rhs))
end
end
function mt.__mod(lhs, rhs) -- % (contain)
if type(rhs) ~= 'table' then
error('Attempt to check content of the list for ' .. type(rhs))
end
for list_name, list_items in pairs(rhs) do
if lhs[list_name] == nil then return false end
for item_name, item_value in pairs(list_items) do
if (lhs[list_name][item_name] or false) ~= item_value then return false end
end
end
return true
end
function mt.__pow(lhs, rhs) -- ^ (intersection)
if type(rhs) ~= 'table' then
error('Attempt to interselect the list with ' .. type(rhs))
end
local intersection = { }
for list_name, list_items in pairs(lhs) do
for item_name, item_value in pairs(list_items) do
local left = lhs[list_name][item_name]
local right = (rhs[list_name] or { })[item_name]
if left == true and right == true then
intersection[list_name] = intersection[list_name] or { }
intersection[list_name][item_name] = true
end
end
end
setmetatable(intersection, mt)
return intersection
end
function mt.__len(self) -- #
local len = 0
for list_name, list_items in pairs(self) do
for item_name, item_value in pairs(list_items) do
if item_value == true then len = len + 1 end
end
end
return len
end
function mt.__eq(lhs, rhs) -- ==
if type(rhs) ~= 'table' then
error('Attempt to compare the list with ' .. type(rhs))
end
local function keys_count(object)
local count = 0
for _, _ in pairs(object) do
count = count + 1
end
return count
end
local left_lists_count = keys_count(lhs)
local right_lists_count = keys_count(rhs)
if left_lists_count ~= right_lists_count then
return false
end
for list_name, left_items in pairs(lhs) do
local right_items = rhs[list_name]
if right_items == nil then
return false
end
local left_items_count = keys_count(left_items)
local right_items_count = keys_count(right_items)
if left_items_count ~= right_items_count then
return false
end
end
return mt.__mod(lhs, rhs)
end
function mt.__lt(lhs, rhs) -- <
if type(rhs) ~= 'table' then
error('Attempt to compare the list with ' .. type(rhs))
end
-- LEFT < RIGHT means "the smallest value in RIGHT is bigger than the largest values in LEFT"
local minLeft = mt.min_value_of(lhs, true)
local maxRight = mt.max_value_of(rhs, true)
return minLeft < maxRight
end
function mt.__le(lhs, rhs) -- <=
if type(rhs) ~= 'table' then
error('Attempt to compare the list with ' .. type(rhs))
end
-- LEFT => RIGHT means "the smallest value in RIGHT is at least the smallest value in LEFT,
-- and the largest value in RIGHT is at least the largest value in LEFT".
local minRight = mt.min_value_of(rhs, true)
local minLeft = mt.min_value_of(lhs, true)
local maxRight = mt.max_value_of(rhs, true)
local maxLeft = mt.max_value_of(lhs, true)
return minRight >= minLeft and maxRight >= maxLeft
end
--
-- Custom operators
function mt.__add_list(lhs, rhs)
local result = lume.clone(lhs)
for list_name, list_items in pairs(rhs) do
result[list_name] = result[list_name] or { }
for item_name, item_value in pairs(list_items) do
result[list_name][item_name] = item_value
end
end
return result
end
function mt.__subList(lhs, rhs)
local result = lume.clone(lhs)
for list_name, list_items in pairs(rhs) do
if lhs[list_name] ~= nil then
for item_name, _ in pairs(list_items) do
lhs[list_name][item_name] = nil
end
end
end
return mt.remove_empties_in_list(result)
end
function mt.__shift_by_number(list, number)
local result = { }
for list_name, list_items in pairs(list) do
result[list_name] = { }
for index, item_name in ipairs(mt.lists[list_name]) do
if list_items[item_name] == true then
local nextItem = mt.lists[list_name][index + number]
if nextItem ~= nil then
result[list_name][nextItem] = true
end
end
end
end
return mt.remove_empties_in_list(result)
end
--
-- Helpers
function mt.remove_empties_in_list(list)
local result = lume.clone(list)
for list_name, list_items in pairs(list) do
if next(list_items) == nil then
result[list_name] = nil
end
end
return result
end
function mt.min_value_of(list, raw)
local min_index = 0
local min_value = { }
local list_keys = { }
for key, _ in pairs(list) do
table.insert(list_keys, key)
end
table.sort(list_keys)
for i = 1, #list_keys do
local list_name = list_keys[i]
local list_items = list[list_name]
for item_name, item_value in pairs(list_items) do
if item_value == true then
local index = lume.find(mt.lists[list_name], item_name)
if index and index < min_index or min_index == 0 then
min_index = index
min_value = { [list_name] = { [item_name] = true } }
end
end
end
end
return raw and min_index or min_value
end
function mt.max_value_of(list, raw)
local max_index = 0
local max_value = { }
local list_keys = { }
for key, _ in pairs(list) do
table.insert(list_keys, key)
end
table.sort(list_keys)
for i = 1, #list_keys do
local list_name = list_keys[i]
local list_items = list[list_name]
for item_name, item_value in pairs(list_items) do
if item_value == true then
local index = lume.find(mt.lists[list_name], item_name)
if index and index > max_index or max_index == 0 then
max_index = index
max_value = { [list_name] = { [item_name] = true } }
end
end
end
end
return raw and max_index or max_value
end
function mt.random_value_of(list)
local items = { }
local list_keys = { }
for key, _ in pairs(list) do
table.insert(list_keys, key)
end
table.sort(list_keys)
for i = 1, #list_keys do
local list_name = list_keys[i]
local list_items = list[list_name]
local items_keys = { }
for key, _ in pairs(list_items) do
table.insert(items_keys, key)
end
table.sort(items_keys)
for i = 1, #items_keys do
local item_name = items_keys[i]
local item_value = list_items[item_name]
if item_value == true then
local result = { [list_name] = { [item_name] = true } }
table.insert(items, result)
end
end
end
local random_index = math.random(1, #items)
return items[random_index]
end
function mt.first_raw_value_of(list)
local result = 0
for list_name, list_items in pairs(list) do
for item_name, item_value in pairs(list_items) do
if item_value == true then
local index = lume.find(mt.lists[list_name], item_name)
if index then
result = index
break
end
end
end
end
return result
end
function mt.posible_values_of(list)
local result = { }
for list_name, list_items in pairs(list) do
local subList = { }
for _, item_name in ipairs(mt.lists[list_name]) do
subList[item_name] = true
end
result[list_name] = subList
end
return result
end
function mt.range_of(list, min, max)
if type(min) ~= 'table' and type(min) ~= 'number' then
error('Attempt to get a range with incorrect min value of type ' .. type(min))
end
if type(max) ~= 'table' and type(max) ~= 'number' then
error('Attempt to get a range with incorrect max value of type ' .. type(max))
end
local result = { }
local allList = mt.posible_values_of(list)
local min_index = type(min) == 'number' and min or mt.first_raw_value_of(min)
local max_index = type(max) == 'number' and max or mt.first_raw_value_of(max)
for list_name, list_items in pairs(allList) do
for item_name, item_value in pairs(list_items) do
local index = lume.find(mt.lists[list_name], item_name)
if index and index >= min_index and index <= max_index and list[list_name][item_name] == true then
result[list_name] = result[list_name] or { }
result[list_name][item_name] = true
end
end
end
return result
end
function mt.invert(list)
local result = mt.posible_values_of(list)
for list_name, list_items in pairs(list) do
for item_name, item_value in pairs(list_items) do
if item_value == true then
result[list_name][item_name] = nil
end
end
end
return result
end
return mt

View File

@@ -0,0 +1,150 @@
local lume = require('narrator.libs.lume')
local enums = require('narrator.enums')
local parser = require('narrator.parser')
local Story = require('narrator.story')
--
-- Local
local folder_separator = package.config:sub(1, 1)
---Clear path from '.lua' and '.ink' extensions and replace '.' to '/' or '\'
---@param path string
---@return string normalized_path
local function normalize_path(path)
local path = path:gsub('.lua$', '')
local path = path:gsub('.ink$', '')
if path:match('%.') and not path:match(folder_separator) then
path = path:gsub('%.', folder_separator)
end
return path
end
---Parse an .ink file to the content string.
---@param path string
---@return string content
local function read_ink_file(path)
local path = normalize_path(path) .. '.ink'
local file = io.open(path, 'r')
assert(file, 'File doesn\'t exist: ' .. path)
local content = file:read('*all')
file:close()
return content
end
---Save a book to the lua module
---@param book Narrator.Book
---@param path string
---@return boolean success
local function save_book(book, path)
local path = normalize_path(path) .. '.lua'
local data = lume.serialize(book)
data = data:gsub('%[%d+%]=', '')
data = data:gsub('[\'[%w_]+\']', function(match) return
match:sub(3, #match - 2)
end)
local file = io.open(path, 'w')
if file == nil then
return false
end
file:write('return ' .. data)
file:close()
return true
end
---Merge a chapter to the book
---@param book Narrator.Book
---@param chapter Narrator.Book
---@return Narrator.Book
local function merge_chapter_to_book(book, chapter)
-- Check a engine version compatibility
if chapter.version.engine and chapter.version.engine ~= enums.engine_version then
assert('Version ' .. chapter.version.engine .. ' of book isn\'t equal to the version ' .. enums.engine_version .. ' of Narrator.')
end
--Merge the root knot and it's stitch
book.tree._._ = lume.concat(chapter.tree._._, book.tree._._)
chapter.tree._._ = nil
book.tree._ = lume.merge(chapter.tree._, book.tree._)
chapter.tree._ = nil
--Merge a chapter to the book
book.tree = lume.merge(book.tree or { }, chapter.tree or { })
book.constants = lume.merge(book.constants or { }, chapter.constants or { })
book.lists = lume.merge(book.lists or { }, chapter.lists or { })
book.variables = lume.merge(book.variables or { }, chapter.variables or { })
book.params = lume.merge(book.params or { }, chapter.params or { })
return book
end
--
-- Public
local narrator = { }
---Parse a book from an Ink file
---Use it during development, but prefer already parsed and stored books in production
---Requires `lpeg` and `io`.
---@param path string
---@param params Narrator.ParsingParams|nil
---@return Narrator.Book
function narrator.parse_file(path, params)
local params = params or { save = false }
assert(parser, 'Can\'t parse anything without lpeg, sorry.')
local content = read_ink_file(path)
local book = parser.parse(content)
for _, inclusion in ipairs(book.inclusions) do
local folder_path = normalize_path(path):match('(.*' .. folder_separator .. ')')
local inclusion_path = folder_path .. normalize_path(inclusion) .. '.ink'
local chapter = narrator.parse_file(inclusion_path)
merge_chapter_to_book(book, chapter)
end
if params.save then
save_book(book, path)
end
return book
end
---Parse a book from the ink content string
---Use it during development, but prefer already parsed and stored books in production
---Requires `lpeg`
---@param content string
---@param inclusions string[]
---@return Narrator.Book
function narrator.parse_content(content, inclusions)
local inclusions = inclusions or { }
assert(parser, 'Can\'t parse anything without a parser.')
local book = parser.parse(content)
for _, inclusion in ipairs(inclusions) do
local chapter = parser.parse(inclusion)
merge_chapter_to_book(book, chapter)
end
return book
end
---Init a story based on the book
---@param book Narrator.Book
---@return Narrator.Story
function narrator.init_story(book)
return Story(book)
end
return narrator

View File

@@ -0,0 +1,789 @@
local lume = require('narrator.libs.lume')
local enums = require('narrator.enums')
--
-- LPeg
-- To allow to build in Defold
local lpeg_name = 'lpeg'
if not pcall(require, lpeg_name) then
return false
end
local lpeg = require(lpeg_name)
local S, C, P, V = lpeg.S, lpeg.C, lpeg.P, lpeg.V
local Cb, Ct, Cc, Cg = lpeg.Cb, lpeg.Ct, lpeg.Cc, lpeg.Cg
local Cmt = lpeg.Cmt
lpeg.locale(lpeg)
--
-- Parser
local parser = { }
local constructor = { }
---Parse ink content string
---@param content string
---@return Narrator.Book
function parser.parse(content)
--
-- Basic patterns
local function get_length(array) return
#array
end
local eof = -1
local sp = S(' \t') ^ 0
local ws = S(' \t\r\n') ^ 0
local nl = S('\r\n') ^ 1
local none = Cc(nil)
local divert_sign = P'->'
local gather_mark = sp * C('-' - divert_sign)
local gather_level = Cg(Ct(gather_mark ^ 1) / get_length + none, 'level')
local sticky_marks = Cg(Ct((sp * C('+')) ^ 1) / get_length, 'level') * Cg(Cc(true), 'sticky')
local choice_marks = Cg(Ct((sp * C('*')) ^ 1) / get_length, 'level') * Cg(Cc(false), 'sticky')
local choice_level = sticky_marks + choice_marks
local id = (lpeg.alpha + '_') * (lpeg.alnum + '_') ^ 0
local label = Cg('(' * sp * C(id) * sp * ')', 'label')
local address = id * ('.' * id) ^ -2
---Something for tunnels
local function check_tunnel(s, i, a)
local r = lpeg.match (sp * divert_sign, s, i)
return i, r ~= nil
end
-- TODO: Clean divert expression to divert and tunnel
local divert = divert_sign * sp * Cg(address, 'path') -- base search for divert symbol and path to follow
local check_tunnel = Cg(Cmt(Cb('path'), check_tunnel), 'tunnel') -- a weird way to to check tunnel
local opt_tunnel_sign = (sp * divert_sign * sp * (#nl + #S'#') ) ^ -1 -- tunnel sign in end of string, keep newline not consumed
divert = Cg(Ct(divert * sp * check_tunnel * opt_tunnel_sign), 'divert')
local divert_to_nothing = divert_sign * none
local exit_tunnel = Cg(divert_sign * divert_sign, 'exit')
local tag = '#' * sp * V'text'
local tags = Cg(Ct(tag * (sp * tag) ^ 0), 'tags')
local todo = sp * 'TODO:' * (1 - nl) ^ 0
local comment_line = sp * '//' * sp * (1 - nl) ^ 0
local comment_multi = sp * '/*' * ((P(1) - '*/') ^ 0) * '*/'
local comment = comment_line + comment_multi
local multiline_end = ws * '}'
--
-- Dynamic patterns and evaluation helpers
local function item_type(type)
return Cg(Cc(type), 'type')
end
local function balanced_multiline_item(is_restricted)
local is_restricted = is_restricted ~= nil and is_restricted or false
local paragraph = is_restricted and V'restricted_paragraph' or V'paragraph'
return sp * paragraph ^ -1 * sp * V'multiline_item' * sp * paragraph ^ -1 * ws
end
local function sentence_before(excluded, tailed)
local tailed = tailed or false
local character = P(1 - S(' \t')) - excluded
local pattern = (sp * character ^ 1) ^ 1
local with_tail = C(pattern * sp)
local without_tail = C(pattern) * sp
local without_tail_always = C(pattern) * sp * #(tags + nl)
return without_tail_always + (tailed and with_tail or without_tail)
end
local function unwrap_assignment(assignment)
local unwrapped = assignment
unwrapped = unwrapped:gsub('([%w_]*)%s*([%+%-])[%+%-]', '%1 = %1 %2 1')
unwrapped = unwrapped:gsub('([%w_]*)%s*([%+%-])=%s*(.*)', '%1 = %1 %2 %3')
local name, value = unwrapped:match('([%w_]*)%s*=%s*(.*)')
return name or '', value or assignment
end
local function check_special_escape(s, i, a)
if string.sub(s, i - 2, i - 2) == '\\' then
return
end
return i
end
--
-- Grammar rules
local ink_grammar = P({ 'root',
-- Root
root = ws * V'items' + eof,
items = Ct(V'item' ^ 0),
item = balanced_multiline_item() + V'singleline_item',
singleline_item = sp * (V'global' + V'statement' + V'paragraph' + V'gatherPoint') * ws,
multiline_item = ('{' * sp * (V'sequence' + V'switch') * sp * multiline_end) - V'inline_condition',
-- Gather points
gatherPoint = Ct(gather_level * sp * nl * item_type('gather')),
-- Global declarations
global =
Ct(V'inclusion' * item_type('inclusion')) +
Ct(V'list' * item_type('list')) +
Ct(V'constant' * item_type('constant')) +
Ct(V'variable' * item_type('variable'))
,
inclusion = 'INCLUDE ' * sp * Cg(sentence_before(nl + comment), 'filename'),
list = 'LIST ' * sp * V'assignment_pair',
constant = 'CONST ' * sp * V'assignment_pair',
variable = 'VAR ' * sp * V'assignment_pair',
-- Statements
statement =
Ct(V'return_from_func' * item_type('return')) +
Ct(V'assignment' * item_type('assignment')) +
Ct(V'func' * item_type('func')) +
Ct(V'knot' * item_type('knot')) +
Ct(V'stitch' * item_type('stitch')) +
Ct(V'choice' * item_type('choice')) +
comment + todo
,
section_name = C(id) * sp * P'=' ^ 0,
knot = P'==' * (P'=' ^ 0) * sp * Cg(V'section_name', 'knot'),
stitch = '=' * sp * Cg(V'section_name', 'stitch'),
func_param = sp * C(id) * sp * S','^0,
func_params = P'(' * Cg(Ct(V'func_param'^0), 'params') * P')',
function_name = P'function' * sp * Cg(id, 'name') * sp * V'func_params' * sp * P'=' ^ 0,
func = P'==' * (P'=' ^ 0) * sp * Cg(Ct(V'function_name'), 'func'),
return_from_func = sp * '~' * sp * P('return') * sp * Cg((P(1) - nl)^0, 'value') * nl ^ 0,
assignment = gather_level * sp * '~' * sp * V'assignment_temp' * sp * V'assignment_pair',
assignment_temp = Cg('temp' * Cc(true) + Cc(false), 'temp'),
assignment_pair = Cg(sentence_before(nl + comment) / unwrap_assignment, 'name') * Cg(Cb('name') / 2, 'value'),
choice_condition = Cg(V'expression' + none, 'condition'),
choice_fallback = choice_level * sp * V'label_optional' * sp * V'choice_condition' * sp * (divert + divert_to_nothing) * sp * V'tags_optional',
choice_normal = choice_level * sp * V'label_optional' * sp * V'choice_condition' * sp * Cg(V'text', 'text') * divert ^ -1 * sp * V'tags_optional',
choice = V'choice_fallback' + V'choice_normal',
-- Paragraph
paragraph = Ct(gather_level * sp * (V'paragraph_label' + V'paragraph_text' + V'paragraph_tags') * item_type('paragraph')),
paragraph_label = label * sp * Cg(V'text_optional', 'parts') * sp * V'tags_optional',
paragraph_text = V'label_optional' * sp * Cg(V'text_complex', 'parts') * sp * V'tags_optional',
paragraph_tags = V'label_optional' * sp * Cg(V'text_optional', 'parts') * sp * tags,
label_optional = label + none,
text_optional = V'text_complex' + none,
tags_optional = tags + none,
text_complex = Ct((Ct(
Cg(V'inline_condition', 'condition') +
Cg(V'inline_sequence', 'sequence') +
Cg(V'expression', 'expression') +
Cg(V'text' + ' ', 'text') * (exit_tunnel ^ -1) * (divert ^ -1) + exit_tunnel + divert
) - V'multiline_item') ^ 1),
special_check_escape = Cmt(S("{|}"), check_special_escape),
text = sentence_before(nl + exit_tunnel + divert + comment + tag + V'special_check_escape', true) - V'statement',
-- Inline expressions, conditions, sequences
expression = '{' * sp * sentence_before('}' + nl) * sp * '}',
inline_condition = '{' * sp * Ct(V'inline_if_else' + V'inline_if') * sp * '}',
inline_if = Cg(sentence_before(S':}' + nl), 'condition') * sp * ':' * sp * Cg(V'text_complex', 'success'),
inline_if_else = (V'inline_if') * sp * '|' * sp * Cg(V'text_complex', 'failure'),
inline_alt_empty = Ct(Ct(Cg(sp * Cc'', 'text') * sp * divert ^ -1)),
inline_alt = V'text_complex' + V'inline_alt_empty',
inline_alts = Ct(((sp * V'inline_alt' * sp * '|') ^ 1) * sp * V'inline_alt'),
inline_sequence = '{' * sp * (
'!' * sp * Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('once'), 'sequence')) +
'&' * sp * Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('cycle'), 'sequence')) +
'~' * sp * Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('stopping'), 'sequence') * Cg(Cc(true), 'shuffle')) +
Ct(Cg(V'inline_alts', 'alts') * Cg(Cc('stopping'), 'sequence'))
) * sp * '}',
-- Multiline conditions and switches
switch = Ct((V'switch_comparative' + V'switch_conditional') * item_type('switch')),
switch_comparative = Cg(V'switch_condition', 'expression') * ws * Cg(Ct((sp * V'switch_case') ^ 1), 'cases'),
switch_conditional = Cg(Ct(V'switch_cases_headed' + V'switch_cases_only'), 'cases'),
switch_cases_headed = V'switch_if' * ((sp * V'switch_case') ^ 0),
switch_cases_only = ws * ((sp * V'switch_case') ^ 1),
switch_if = Ct(Cg(V'switch_condition', 'condition') * ws * Cg(Ct(V'switch_items'), 'node')),
switch_case = ('-' - divert_sign) * sp * V'switch_if',
switch_condition = sentence_before(':' + nl) * sp * ':' * sp * comment ^ -1,
switch_items = (V'restricted_item' - V'switch_case') ^ 1,
-- Multiline sequences
sequence = Ct((V'sequence_params' * sp * nl * sp * V'sequence_alts') * item_type('sequence')),
sequence_params = (
V'sequence_shuffle_optional' * sp * V'sequence_type' +
V'sequence_shuffle' * sp * V'sequence_type' +
V'sequence_shuffle' * sp * V'sequence_type_optional'
) * sp * ':' * sp * comment ^ -1,
sequence_shuffle_optional = V'sequence_shuffle' + Cg(Cc(false), 'shuffle'),
sequence_shuffle = Cg(P'shuffle' / function() return true end, 'shuffle'),
sequence_type_optional = V'sequence_type' + Cg(Cc'cycle', 'sequence'),
sequence_type = Cg(P'cycle' + 'stopping' + 'once', 'sequence'),
sequence_alts = Cg(Ct((sp * V'sequence_alt') ^ 1), 'alts'),
sequence_alt = ('-' - divert_sign) * ws * Ct(V'sequence_items'),
sequence_items = (V'restricted_item' - V'sequence_alt') ^ 1,
-- Restricted items inside multiline items
restricted_item = balanced_multiline_item(true) + V'restricted_singleline_item',
restricted_singleline_item = sp * (V'global' + V'restricted_statement' + V'restricted_paragraph' - multiline_end) * ws,
restricted_statement = Ct(
V'choice' * item_type('choice') +
V'assignment' * item_type('assignment')
) + comment + todo,
restricted_paragraph = Ct((
Cg(V'text_complex', 'parts') * sp * V'tags_optional' +
Cg(V'text_optional', 'parts') * sp * tags
) * item_type('paragraph'))
})
--
-- Result
local parsed_items = ink_grammar:match(content)
local book = constructor.construct_book(parsed_items)
return book
end
--
-- A book construction
function constructor.unescape(text)
local result = text
result = result:gsub('\\|', '|')
result = result:gsub('\\{', '{')
result = result:gsub('\\}', '}')
return result
end
function constructor.construct_book(items)
local construction = {
current_knot = '_',
current_stitch = '_',
variables_to_compute = { }
}
construction.book = {
inclusions = { },
lists = { },
constants = { },
variables = { },
params = { },
tree = { _ = { _ = { } } }
}
construction.book.version = {
engine = enums.engine_version,
tree = 1
}
construction.nodes_chain = {
construction.book.tree[construction.current_knot][construction.current_stitch]
}
constructor.add_node(construction, items)
constructor.clear(construction.book.tree)
constructor.compute_variables(construction)
return construction.book
end
function constructor:add_node(items, is_restricted)
local is_restricted = is_restricted ~= nil and is_restricted or false
for _, item in ipairs(items) do
if is_restricted then
-- Are not allowed inside multiline blocks by Ink rules:
-- a) nesting levels
-- b) choices without diverts
item.level = nil
if item.type == 'choice' and item.divert == nil then
item.type = nil
end
end
if item.type == 'inclusion' then
-- filename
constructor.add_inclusion(self, item.filename)
elseif item.type == 'list' then
-- name, value
constructor.add_list(self, item.name, item.value)
elseif item.type == 'constant' then
-- name, value
constructor.add_constant(self, item.name, item.value)
elseif item.type == 'variable' then
-- name, value
constructor.add_variable(self, item.name, item.value)
elseif item.type == 'func' then
-- function
constructor.add_function(self, item.func.name, item.func.params)
elseif item.type == 'knot' then
-- knot
constructor.add_knot(self, item.knot)
elseif item.type == 'stitch' then
-- stitch
constructor.add_stitch(self, item.stitch)
elseif item.type == 'switch' then
-- expression, cases
constructor.add_switch(self, item.expression, item.cases)
elseif item.type == 'sequence' then
-- sequence, shuffle, alts
constructor.add_sequence(self, item.sequence, item.shuffle, item.alts)
elseif item.type == 'assignment' then
-- level, name, value, temp
constructor.add_assignment(self, item.level, item.name, item.value, item.temp)
elseif item.type == 'return' then
constructor.add_return(self, item.value)
elseif item.type == 'paragraph' then
-- level, label, parts, tags
constructor.add_paragraph(self, item.level, item.label, item.parts, item.tags)
elseif item.type == 'gather' then
constructor.add_paragraph(self, item.level, "", nil, item.tags)
elseif item.type == 'choice' then
-- level, sticky, label, condition, text, divert, tags
constructor.add_choice(self, item.level, item.sticky, item.label, item.condition, item.text, item.divert, item.tags)
end
end
end
function constructor:add_inclusion(filename)
table.insert(self.book.inclusions, filename)
end
function constructor:add_list(name, value)
local items = lume.array(value:gmatch('[%w_%.]+'))
self.book.lists[name] = items
local switched = lume.array(value:gmatch('%b()'))
switched = lume.map(switched, function(item) return item:sub(2, #item - 1) end)
self.book.variables[name] = { [name] = { } }
lume.each(switched, function(item) self.book.variables[name][name][item] = true end)
end
function constructor:add_constant(constant, value)
local value = lume.deserialize(value)
self.book.constants[constant] = value
end
function constructor:add_variable(variable, value)
self.variables_to_compute[variable] = value
end
function constructor:add_function(fname, params)
local node = { }
self.book.tree[fname] = { ['_'] = node }
self.book.params[fname] = params
self.nodes_chain = { node }
end
function constructor:add_knot(knot)
self.current_knot = knot
self.current_stitch = '_'
local node = { }
self.book.tree[self.current_knot] = { [self.current_stitch] = node }
self.nodes_chain = { node }
end
function constructor:add_stitch(stitch)
-- If a root stitch is empty we need to add a divert to the first stitch in the ink file.
if self.current_stitch == '_' then
local root_stitch_node = self.book.tree[self.current_knot]._
if #root_stitch_node == 0 then
local divertItem = { divert = { path = stitch } }
table.insert(root_stitch_node, divertItem)
end
end
self.current_stitch = stitch
local node = { }
self.book.tree[self.current_knot][self.current_stitch] = node
self.nodes_chain = { node }
end
function constructor:add_switch(expression, cases)
if expression then
-- Convert switch cases to comparing conditions with expression
for _, case in ipairs(cases) do
if case.condition ~= 'else' then
case.condition = expression .. '==' .. case.condition
end
end
end
local item = {
condition = { },
success = { }
}
for _, case in ipairs(cases) do
if case.condition == 'else' then
local failure_node = { }
table.insert(self.nodes_chain, failure_node)
constructor.add_node(self, case.node, true)
table.remove(self.nodes_chain)
item.failure = failure_node
else
local success_node = { }
table.insert(self.nodes_chain, success_node)
constructor.add_node(self, case.node, true)
table.remove(self.nodes_chain)
table.insert(item.success, success_node)
table.insert(item.condition, case.condition)
end
end
constructor.add_item(self, nil, item)
end
function constructor:add_sequence(sequence, shuffle, alts)
local item = {
sequence = sequence,
shuffle = shuffle and true or nil,
alts = { }
}
for _, alt in ipairs(alts) do
local alt_node = { }
table.insert(self.nodes_chain, alt_node)
constructor.add_node(self, alt, true)
table.remove(self.nodes_chain)
table.insert(item.alts, alt_node)
end
constructor.add_item(self, nil, item)
end
function constructor:add_return(value)
local item = {
return_value = value
}
constructor.add_item(self, nil, item)
end
function constructor:add_assignment(level, name, value, temp)
local item = {
temp = temp or nil,
var = name,
value = value
}
constructor.add_item(self, level, item)
end
function constructor:add_paragraph(level, label, parts, tags)
local items = constructor.convert_paragraph_parts_to_items(parts, true)
items = items or { }
-- If the paragraph has a label or tags we need to place them as the first text item.
if label ~= nil or tags ~= nil then
local first_item
if #items > 0 and items[1].condition == nil then
first_item = items[1]
else
first_item = { }
table.insert(items, first_item)
end
first_item.label = label
first_item.tags = tags
end
for _, item in ipairs(items) do
constructor.add_item(self, level, item)
end
end
function constructor.convert_paragraph_parts_to_items(parts, is_root)
if parts == nil then return nil end
local is_root = is_root ~= nil and is_root or false
local items = { }
local item
for index, part in ipairs(parts) do
if part.condition then -- Inline condition part
item = {
condition = part.condition.condition,
success = constructor.convert_paragraph_parts_to_items(part.condition.success),
failure = constructor.convert_paragraph_parts_to_items(part.condition.failure)
}
table.insert(items, item)
item = nil
elseif part.sequence then -- Inline sequence part
item = {
sequence = part.sequence.sequence,
shuffle = part.sequence.shuffle and true or nil,
alts = { }
}
for _, alt in ipairs(part.sequence.alts) do
table.insert(item.alts, constructor.convert_paragraph_parts_to_items(alt))
end
table.insert(items, item)
item = nil
else -- Text, expression and divert may be
local is_divert_only = part.divert ~= nil and part.text == nil
if item == nil then
item = { text = (is_root or is_divert_only) and '' or '<>' }
end
if part.text then
item.text = item.text .. part.text:gsub('%s+', ' ')
item.text = constructor.unescape(item.text)
elseif part.expression then
item.text = item.text .. '#' .. part.expression .. '#'
end
if part.divert or part.exit then
item.exit = part.exit and true or nil
item.divert = part.divert
item.text = #item.text > 0 and (item.text .. '<>') or nil
table.insert(items, item)
item = nil
else
local next = parts[index + 1]
local next_is_block = next and not (next.text or next.expression)
if not next or next_is_block then
if not is_root or next_is_block then
item.text = item.text .. '<>'
end
table.insert(items, item)
item = nil
end
end
end
end
if is_root then
-- Add a safe prefix and suffix for correct conditions gluing
local first_item = items[1]
if first_item.text == nil and first_item.divert == nil and first_item.exit == nil then
table.insert(items, 1, { text = '' } )
end
local last_item = items[#items]
if last_item.text == nil and last_item.divert == nil and last_item.exit == nil then
table.insert(items, { text = '' } )
elseif last_item.text ~= nil and last_item.divert == nil then
last_item.text = last_item.text:gsub('(.-)%s*$', '%1')
end
end
return items
end
function constructor:add_choice(level, sticky, label, condition, sentence, divert, tags)
local item = {
sticky = sticky or nil,
condition = condition,
label = label,
divert = divert,
tags = tags
}
if sentence == nil then
item.choice = 0
else
local prefix, divider, suffix = sentence:match('(.*)%[(.*)%](.*)')
prefix = prefix or sentence
divider = divider or ''
suffix = suffix or ''
local text = (prefix .. suffix):gsub('%s+', ' ')
local choice = (prefix .. divider):gsub('%s+', ' '):gsub('^%s*(.-)%s*$', '%1')
if divert and #text > 0 and text:match('%S+') then
text = text .. '<>'
else
text = text:gsub('^%s*(.-)%s*$', '%1')
end
item.text = constructor.unescape(text)
item.choice = constructor.unescape(choice)
end
constructor.add_item(self, level, item)
if divert == nil then
item.node = { }
table.insert(self.nodes_chain, item.node)
end
end
function constructor:add_item(level, item)
local level = (level ~= nil and level > 0) and level or #self.nodes_chain
while #self.nodes_chain > level do
table.remove(self.nodes_chain)
end
local node = self.nodes_chain[#self.nodes_chain]
table.insert(node, item)
end
function constructor:compute_variable(variable, value)
local constant = self.book.constants[value]
if constant then
self.book.variables[variable] = constant
return
end
local list_expression = value:match('%(([%s%w%.,_]*)%)')
local item_expressions = list_expression and lume.array(list_expression:gmatch('[%w_%.]+')) or { value }
local list_variable = list_expression and { } or nil
for _, item_expression in ipairs(item_expressions) do
local list_part, item_part = item_expression:match('([%w_]+)%.([%w_]+)')
item_part = item_part or item_expression
for list_name, list_items in pairs(self.book.lists) do
local list_is_valid = list_part == nil or list_part == list_name
local item_is_found = lume.find(list_items, item_part)
if list_is_valid and item_is_found then
list_variable = list_variable or { }
list_variable[list_name] = list_variable[list_name] or { }
list_variable[list_name][item_part] = true
end
end
end
if list_variable then
self.book.variables[variable] = list_variable
else
self.book.variables[variable] = lume.deserialize(value)
end
end
function constructor:compute_variables()
for variable, value in pairs(self.variables_to_compute) do
constructor.compute_variable(self, variable, value)
end
end
function constructor.clear(tree)
for knot, node in pairs(tree) do
for stitch, node in pairs(node) do
constructor.clear_node(node)
end
end
end
function constructor.clear_node(node)
for index, item in ipairs(node) do
-- Simplify text only items
if item.text ~= nil and lume.count(item) == 1 then
node[index] = item.text
end
if item.node ~= nil then
-- Clear choice nodes
if #item.node == 0 then
item.node = nil
else
constructor.clear_node(item.node)
end
end
if item.success ~= nil then
-- Simplify single condition
if type(item.condition) == 'table' and #item.condition == 1 then
item.condition = item.condition[1]
end
-- Clear success nodes
if item.success[1] ~= nil and item.success[1][1] ~= nil then
for index, success_node in ipairs(item.success) do
constructor.clear_node(success_node)
if #success_node == 1 and type(success_node[1]) == 'string' then
item.success[index] = success_node[1]
end
end
if #item.success == 1 then
item.success = item.success[1]
end
else
constructor.clear_node(item.success)
if #item.success == 1 and type(item.success[1]) == 'string' then
item.success = item.success[1]
end
end
-- Clear failure nodes
if item.failure ~= nil then
constructor.clear_node(item.failure)
if #item.failure == 1 and type(item.failure[1]) == 'string' then
item.failure = item.failure[1]
end
end
end
if item.alts ~= nil then
for index, alt_node in ipairs(item.alts) do
constructor.clear_node(alt_node)
if #alt_node == 1 and type(alt_node[1]) == 'string' then
item.alts[index] = alt_node[1]
end
end
end
end
end
return parser

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1,19 @@
- I looked at Monsieur Fogg
* ... and I could contain myself no longer.
'What is the purpose of our journey, Monsieur?'
'A wager,' he replied.
* * 'A wager!'[] I returned.
He nodded.
* * * 'But surely that is foolishness!'
* * * 'A most serious matter then!'
- - - He nodded again.
* * * 'But can we win?'
'That is what we will endeavour to find out,' he answered.
* * * 'A modest wager, I trust?'
'Twenty thousand pounds,' he replied, quite flatly.
* * * I asked nothing further of him then[.], and after a final, polite cough, he offered nothing more to me. <>
* * 'Ah[.'],' I replied, uncertain what I thought.
- - After that, <>
* ... but I said nothing[] and <>
- we passed the day in silence.
- -> END

View File

@@ -0,0 +1 @@
return {inclusions={},constants={},version={tree=1,engine=1},tree={_={_={"I looked at Monsieur Fogg",{text="... and I could contain myself no longer.",choice="... and I could contain myself no longer.",node={"'What is the purpose of our journey, Monsieur?'","'A wager,' he replied.",{text="'A wager!' I returned.",choice="'A wager!'",node={"He nodded.",{text="'But surely that is foolishness!'",choice="'But surely that is foolishness!'"},{text="'A most serious matter then!'",choice="'A most serious matter then!'"},"He nodded again.",{text="'But can we win?'",choice="'But can we win?'",node={"'That is what we will endeavour to find out,' he answered."}},{text="'A modest wager, I trust?'",choice="'A modest wager, I trust?'",node={"'Twenty thousand pounds,' he replied, quite flatly."}},{text="I asked nothing further of him then, and after a final, polite cough, he offered nothing more to me. <>",choice="I asked nothing further of him then."}}},{text="'Ah,' I replied, uncertain what I thought.",choice="'Ah.'"},"After that, <>"}},{text="... but I said nothing and <>",choice="... but I said nothing"},"we passed the day in silence.",{divert="END"}}}},lists={},variables={}}

View File

@@ -257,11 +257,11 @@ CharacterModule::CharacterModule(flecs::world &ecs)
if (anim.currentAnim ==
AnimationControl::ANIM_SWIMMING) {
float h = Ogre::Math::Clamp(
0.2f - ch.mBodyNode->getPosition().y,
0.0f - ch.mBodyNode->getPosition().y,
0.0f, 2000.0f);
if (h > 0.2 && h < 2.0f)
gr.gvelocity.y += 1.2 * (h + 1.0f) * h *
eng.delta;
if (h > 0.05 && h < 2.0f)
gr.gvelocity.y += 0.1f * (h + 1.0f) *
h * eng.delta;
}
});
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
@@ -279,7 +279,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
Ogre::Vector3 boneMotion = ch.mBoneMotion;
v.velocity = rot * boneMotion / eng.delta;
if (eng.startupDelay < 0.0f)
if (eng.startupDelay <= 0.0f)
v.velocity += v.gvelocity;
v.velocity.y = Ogre::Math::Clamp(v.velocity.y, -10.5f,
1000000.0f);
@@ -424,6 +424,57 @@ CharacterModule::CharacterModule(flecs::world &ecs)
}
}
});
#define TURN_SPEED 500.0f // character turning in degrees per second
ecs.system<const Input, const Camera, CharacterBase>("UpdateBody")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const Input &input,
const Camera &camera, CharacterBase &ch) {
ch.mGoalDirection = Ogre::Vector3::ZERO;
float delta = e.world().delta_time();
if (!input.motion.zeroLength()) {
// calculate actually goal direction in world based on player's key directions
ch.mGoalDirection +=
input.motion.z *
camera.mCameraNode->getOrientation()
.zAxis();
ch.mGoalDirection +=
input.motion.x *
camera.mCameraNode->getOrientation()
.xAxis();
ch.mGoalDirection.y = 0;
ch.mGoalDirection.normalise();
Ogre::Quaternion toGoal =
ch.mBodyNode->getOrientation()
.zAxis()
.getRotationTo(
ch.mGoalDirection);
// calculate how much the character has to turn to face goal direction
Ogre::Real yawToGoal =
toGoal.getYaw().valueDegrees();
// this is how much the character CAN turn this frame
Ogre::Real yawAtSpeed =
yawToGoal / Ogre::Math::Abs(yawToGoal) *
delta * TURN_SPEED;
// reduce "turnability" if we're in midair
// if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;
if (yawToGoal < 0)
yawToGoal = std::min<Ogre::Real>(
0,
std::max<Ogre::Real>(
yawToGoal,
yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
else if (yawToGoal > 0)
yawToGoal = std::max<Ogre::Real>(
0,
std::min<Ogre::Real>(
yawToGoal,
yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
ch.mBodyNode->yaw(Ogre::Degree(yawToGoal));
}
});
ecs.system<const EngineData, CharacterLocation, CharacterBase,
CharacterBody>("UpdateCharacterBase")
.kind(flecs::OnUpdate)
@@ -562,57 +613,6 @@ CharacterModule::CharacterModule(flecs::world &ecs)
Ogre::Node::TS_PARENT);
}
});
#define TURN_SPEED 500.0f // character turning in degrees per second
ecs.system<const Input, const Camera, CharacterBase>("UpdateBody")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const Input &input,
const Camera &camera, CharacterBase &ch) {
ch.mGoalDirection = Ogre::Vector3::ZERO;
float delta = e.world().delta_time();
if (!input.motion.zeroLength()) {
// calculate actually goal direction in world based on player's key directions
ch.mGoalDirection +=
input.motion.z *
camera.mCameraNode->getOrientation()
.zAxis();
ch.mGoalDirection +=
input.motion.x *
camera.mCameraNode->getOrientation()
.xAxis();
ch.mGoalDirection.y = 0;
ch.mGoalDirection.normalise();
Ogre::Quaternion toGoal =
ch.mBodyNode->getOrientation()
.zAxis()
.getRotationTo(
ch.mGoalDirection);
// calculate how much the character has to turn to face goal direction
Ogre::Real yawToGoal =
toGoal.getYaw().valueDegrees();
// this is how much the character CAN turn this frame
Ogre::Real yawAtSpeed =
yawToGoal / Ogre::Math::Abs(yawToGoal) *
delta * TURN_SPEED;
// reduce "turnability" if we're in midair
// if (mBaseAnimID == ANIM_JUMP_LOOP) yawAtSpeed *= 0.2f;
if (yawToGoal < 0)
yawToGoal = std::min<Ogre::Real>(
0,
std::max<Ogre::Real>(
yawToGoal,
yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, yawAtSpeed, 0);
else if (yawToGoal > 0)
yawToGoal = std::max<Ogre::Real>(
0,
std::min<Ogre::Real>(
yawToGoal,
yawAtSpeed)); //yawToGoal = Math::Clamp<Real>(yawToGoal, 0, yawAtSpeed);
ch.mBodyNode->yaw(Ogre::Degree(yawToGoal));
}
});
class ClosestNotMeRayResultCallback
: public btCollisionWorld::ClosestRayResultCallback {
btCollisionObject *mMe;

View File

@@ -243,12 +243,39 @@ struct GUIListener : public Ogre::RenderTargetListener {
"%s", ECS::get()
.get<GUI>()
.narrationText.c_str());
ImGui::SetCursorScreenPos(p);
if (ImGui::InvisibleButton(
"Background",
ImGui::GetWindowSize()))
ECS::get<LuaBase>().mLua->call_handler(
"narration_progress");
if (ECS::get().get<GUI>().choices.size() == 0) {
ImGui::SetCursorScreenPos(p);
if (ImGui::InvisibleButton(
"Background",
ImGui::GetWindowSize()))
ECS::get<LuaBase>().mLua->call_handler(
"narration_progress");
} else {
int i;
for (i = 0; i < ECS::get()
.get<GUI>()
.choices.size();
i++) {
if (ImGui::Button(
ECS::get()
.get<GUI>()
.choices[i]
.c_str())) {
ECS::get()
.get_mut<GUI>()
.narration_answer =
i + 1;
std::cout << "answer: "
<< i + 1
<< std::endl;
ECS::modified<GUI>();
ECS::get<LuaBase>()
.mLua
->call_handler(
"narration_answered");
}
}
}
ImGui::Spacing();
ImGui::PopFont();
ImGui::End();
@@ -448,7 +475,7 @@ GUIModule::GUIModule(flecs::world &ecs)
priv.mGuiOverlay = nullptr;
})
.add(flecs::Singleton);
ecs.set<GUI>({ false, true, false, false, false });
ecs.set<GUI>({ false, true, false, false, false, "", {}, -1 });
ecs.set<GUIData>({ nullptr, {}, nullptr });
ui_wait =
ecs.system<const RenderWindow, App, GUIData>("SetupGUI")

View File

@@ -13,6 +13,8 @@ struct GUI {
bool narrationBox;
bool mainMenu;
Ogre::String narrationText;
std::vector<Ogre::String> choices;
int narration_answer;
static void setWindowGrab(bool g = true)
{
ECS::GUI &gui = ECS::get().get_mut<ECS::GUI>();

View File

@@ -1,7 +1,12 @@
#include <OgreFileSystemLayer.h>
#include "GameData.h"
#include "Components.h"
#include "GUIModule.h"
#include "LuaData.h"
extern "C" {
int luaopen_lpeg(lua_State *L);
}
namespace ECS
{
@@ -23,11 +28,100 @@ int LuaData::call_handler(const Ogre::String &event)
}
return 0;
}
int luaLibraryLoader(lua_State *L)
{
int i;
if (!lua_isstring(L, 1)) {
luaL_error(
L,
"luaLibraryLoader: Expected string for first parameter");
}
std::string libraryFile = lua_tostring(L, 1);
std::cout << libraryFile << std::endl;
// In order to be compatible with the normal Lua file loader,
// translate '.' to the file system seperator character.
// In this case (An ogre resource) '/'
while (libraryFile.find('.') != std::string::npos)
libraryFile.replace(libraryFile.find('.'), 1, "/");
libraryFile += ".lua";
std::cout << libraryFile << std::endl;
Ogre::StringVectorPtr scripts =
Ogre::ResourceGroupManager::getSingleton().listResourceNames(
"LuaScripts", false);
std::vector<Ogre::String> &strings = *scripts;
for (i = 0; i < strings.size(); i++)
std::cout << strings[i] << std::endl;
if (0 && !Ogre::ResourceGroupManager::getSingleton()
.resourceExistsInAnyGroup(libraryFile)) {
// Could not find the file.
std::string errMessage = "\n no file '" + libraryFile +
"' found in Ogre resource archives.";
lua_pushstring(L, errMessage.c_str());
} else {
Ogre::DataStreamList streams =
Ogre::ResourceGroupManager::getSingleton().openResources(
"*.lua", "LuaScripts");
Ogre::DataStreamPtr stream =
Ogre::ResourceGroupManager::getSingleton().openResource(
libraryFile, "LuaScripts");
Ogre::String script = stream->getAsString();
if (luaL_loadbuffer(L, script.c_str(), script.length(),
libraryFile.c_str())) {
luaL_error(
L,
"Error loading library '%s' from resource archive.\n%s",
libraryFile.c_str(), lua_tostring(L, -1));
}
}
return 1;
}
static void installLibraryLoader(lua_State *L)
{
// Insert the c++ func 'luaLibraryLoader' into package.loaders.
// Inserted at the start of the table in order to take precedence.
lua_getglobal(L, "table");
lua_getfield(L, -1, "insert");
lua_remove(L, -2); // table
lua_getglobal(L, "package");
lua_getfield(L, -1, "searchers");
lua_remove(L, -2); // package
lua_pushnumber(L, 1); // index where to insert into loaders table
lua_pushcfunction(L, luaLibraryLoader);
if (lua_pcall(L, 3, 0, 0))
Ogre::LogManager::getSingleton().stream() << lua_tostring(L, 1);
}
LuaData::LuaData()
: L(luaL_newstate())
{
luaopen_base(L);
luaopen_table(L);
luaopen_package(L);
luaL_requiref(L, "table", luaopen_table, 1);
lua_pop(L, 1);
luaL_requiref(L, "math", luaopen_math, 1);
lua_pop(L, 1);
luaL_requiref(L, "package", luaopen_package, 1);
lua_pop(L, 1);
luaL_requiref(L, "string", luaopen_string, 1);
lua_pop(L, 1);
luaL_requiref(L, "io", luaopen_io, 1);
lua_pop(L, 1);
luaL_requiref(L, "lpeg", luaopen_lpeg, 1);
lua_pop(L, 1);
#if 0
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
lua_pushcfunction(L, luaopen_lpeg);
lua_setfield(L, -2, "lpeg");
lua_pop(L, 1); // remove PRELOAD table
#endif
installLibraryLoader(L);
lua_pop(L, 1);
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(false, "Crash function called");
return 0;
@@ -40,9 +134,24 @@ LuaData::LuaData()
});
lua_setglobal(L, "setup_handler");
lua_pushcfunction(L, [](lua_State *L) -> int {
int args = lua_gettop(L);
if (args < 1)
return 0;
ECS::get_mut<GUI>().choices.clear();
luaL_checktype(L, 1, LUA_TSTRING);
if (args > 1) {
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L); /* first key */
while (lua_next(L, 2) != 0) {
Ogre::String s = lua_tostring(L, -1);
ECS::get_mut<GUI>().choices.push_back(s);
lua_pop(L, 1); /* remove value but keep key */
}
}
size_t len;
Ogre::String message(luaL_tolstring(L, 1, &len));
std::cout << "narrator message: " << message
<< " length: " << message.length() << std::endl;
if (message.length() == 0 && ECS::get_mut<GUI>().narrationBox) {
ECS::get_mut<GUI>().enabled = false;
ECS::get_mut<GUI>().grab = true;
@@ -50,12 +159,11 @@ LuaData::LuaData()
ECS::get_mut<GUI>().narrationText = message;
ECS::get_mut<GUI>().narrationBox = false;
ECS::modified<GUI>();
} else {
std::cout << "narration ended\n";
} else if (message.length() > 0) {
std::replace(message.begin(), message.end(), '\n', ' ');
std::replace(message.begin(), message.end(), '\r', ' ');
std::cout << "narrator message: " << message
<< std::endl;
ECS::get_mut<GUI>().enabled = true;
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().grabChanged = true;
@@ -67,6 +175,12 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "narrate");
lua_pushcfunction(L, [](lua_State *L) -> int {
// ECS::get_mut<GUI>().mainMenu = true;
lua_pushinteger(L, ECS::get<GUI>().narration_answer);
return 1;
});
lua_setglobal(L, "narration_get_answer");
lua_pushcfunction(L, [](lua_State *L) -> int {
// ECS::get_mut<GUI>().mainMenu = true;
ECS::get_mut<GUI>().enabled = true;

View File

@@ -589,18 +589,19 @@ TerrainModule::TerrainModule(flecs::world &ecs)
.position
<< std::endl;
}
flecs::entity player = ECS::player;
CharacterLocation &loc =
player.get_mut<CharacterLocation>();
height = get_height(terrain.mTerrainGroup,
loc.position);
loc.position.y = height + 0.0f;
player.get<CharacterBase>()
.mBodyNode->setPosition(loc.position);
player.get<CharacterBase>()
.mBodyNode->setOrientation(
Ogre::Quaternion());
player.modified<CharacterLocation>();
}
flecs::entity player = ECS::player;
CharacterLocation &loc =
player.get_mut<CharacterLocation>();
float height =
get_height(terrain.mTerrainGroup, loc.position);
loc.position.y = height + 0.0f;
player.get<CharacterBase>().mBodyNode->setPosition(
loc.position);
player.get<CharacterBase>().mBodyNode->setOrientation(
Ogre::Quaternion());
player.modified<CharacterLocation>();
});
}
float TerrainModule::get_height(Ogre::TerrainGroup *group,

View File

@@ -14,17 +14,28 @@ set(LUA_OBJ
lmathlib.o loadlib.o loslib.o lstrlib.o ltablib.o
lutf8lib.o linit.o)
set(LUA_SRC)
set(LPEG_OBJ
lpvm.o lpcap.o lptree.o lpcode.o lpprint.o lpcset.o
)
set(LPEG_SRC)
foreach(LUA_FILE ${LUA_OBJ})
string(REPLACE ".o" ".c" LUA_SRC_ITEM ${LUA_FILE})
list(APPEND LUA_SRC lua-5.4.8/src/${LUA_SRC_ITEM})
endforeach()
add_library(lua ${LUA_SRC})
target_include_directories(lua PUBLIC lua-5.4.8/src)
foreach(LPEG_FILE ${LPEG_OBJ})
string(REPLACE ".o" ".c" LPEG_SRC_ITEM ${LPEG_FILE})
list(APPEND LPEG_SRC lpeg-1.1.0/${LPEG_SRC_ITEM})
endforeach()
add_library(lua ${LUA_SRC} ${LPEG_SRC})
target_include_directories(lua PUBLIC lua-5.4.8/src lpeg-1.1.0)
add_executable(luavm lua-5.4.8/src/lua.c)
target_link_libraries(luavm lua m)
target_include_directories(luavm PRIVATE lua-5.4.8/src)
add_executable(luac lua-5.4.8/src/luac.c ${LUA_SRC})
add_executable(luac lua-5.4.8/src/luac.c ${LUA_SRC} ${LPEG_SRC})
target_link_libraries(luac m)
target_include_directories(luac PRIVATE lua-5.4.8/src)
target_include_directories(luac PRIVATE lua-5.4.8/src lpeg-1.1.0)
add_executable(lualpegvm lua.c)
target_link_libraries(lualpegvm lua m)
target_include_directories(lualpegvm PRIVATE lua-5.4.8/src)

694
src/lua/lua.c Normal file
View File

@@ -0,0 +1,694 @@
/*
** $Id: lua.c $
** Lua stand-alone interpreter
** See Copyright Notice in lua.h
*/
#define lua_c
#include "lprefix.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#if !defined(LUA_PROGNAME)
#define LUA_PROGNAME "lua"
#endif
#if !defined(LUA_INIT_VAR)
#define LUA_INIT_VAR "LUA_INIT"
#endif
#define LUA_INITVARVERSION LUA_INIT_VAR LUA_VERSUFFIX
static lua_State *globalL = NULL;
static const char *progname = LUA_PROGNAME;
#if defined(LUA_USE_POSIX) /* { */
/*
** Use 'sigaction' when available.
*/
static void setsignal (int sig, void (*handler)(int)) {
struct sigaction sa;
sa.sa_handler = handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask); /* do not mask any signal */
sigaction(sig, &sa, NULL);
}
#else /* }{ */
#define setsignal signal
#endif /* } */
/*
** Hook set by signal function to stop the interpreter.
*/
static void lstop (lua_State *L, lua_Debug *ar) {
(void)ar; /* unused arg. */
lua_sethook(L, NULL, 0, 0); /* reset hook */
luaL_error(L, "interrupted!");
}
/*
** Function to be called at a C signal. Because a C signal cannot
** just change a Lua state (as there is no proper synchronization),
** this function only sets a hook that, when called, will stop the
** interpreter.
*/
static void laction (int i) {
int flag = LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT;
setsignal(i, SIG_DFL); /* if another SIGINT happens, terminate process */
lua_sethook(globalL, lstop, flag, 1);
}
static void print_usage (const char *badoption) {
lua_writestringerror("%s: ", progname);
if (badoption[1] == 'e' || badoption[1] == 'l')
lua_writestringerror("'%s' needs argument\n", badoption);
else
lua_writestringerror("unrecognized option '%s'\n", badoption);
lua_writestringerror(
"usage: %s [options] [script [args]]\n"
"Available options are:\n"
" -e stat execute string 'stat'\n"
" -i enter interactive mode after executing 'script'\n"
" -l mod require library 'mod' into global 'mod'\n"
" -l g=mod require library 'mod' into global 'g'\n"
" -v show version information\n"
" -E ignore environment variables\n"
" -W turn warnings on\n"
" -- stop handling options\n"
" - stop handling options and execute stdin\n"
,
progname);
}
/*
** Prints an error message, adding the program name in front of it
** (if present)
*/
static void l_message (const char *pname, const char *msg) {
if (pname) lua_writestringerror("%s: ", pname);
lua_writestringerror("%s\n", msg);
}
/*
** Check whether 'status' is not OK and, if so, prints the error
** message on the top of the stack.
*/
static int report (lua_State *L, int status) {
if (status != LUA_OK) {
const char *msg = lua_tostring(L, -1);
if (msg == NULL)
msg = "(error message not a string)";
l_message(progname, msg);
lua_pop(L, 1); /* remove message */
}
return status;
}
/*
** Message handler used to run all chunks
*/
static int msghandler (lua_State *L) {
const char *msg = lua_tostring(L, 1);
if (msg == NULL) { /* is error object not a string? */
if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */
lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */
return 1; /* that is the message */
else
msg = lua_pushfstring(L, "(error object is a %s value)",
luaL_typename(L, 1));
}
luaL_traceback(L, L, msg, 1); /* append a standard traceback */
return 1; /* return the traceback */
}
/*
** Interface to 'lua_pcall', which sets appropriate message function
** and C-signal handler. Used to run all chunks.
*/
static int docall (lua_State *L, int narg, int nres) {
int status;
int base = lua_gettop(L) - narg; /* function index */
lua_pushcfunction(L, msghandler); /* push message handler */
lua_insert(L, base); /* put it under function and args */
globalL = L; /* to be available to 'laction' */
setsignal(SIGINT, laction); /* set C-signal handler */
status = lua_pcall(L, narg, nres, base);
setsignal(SIGINT, SIG_DFL); /* reset C-signal handler */
lua_remove(L, base); /* remove message handler from the stack */
return status;
}
static void print_version (void) {
lua_writestring(LUA_COPYRIGHT, strlen(LUA_COPYRIGHT));
lua_writeline();
}
/*
** Create the 'arg' table, which stores all arguments from the
** command line ('argv'). It should be aligned so that, at index 0,
** it has 'argv[script]', which is the script name. The arguments
** to the script (everything after 'script') go to positive indices;
** other arguments (before the script name) go to negative indices.
** If there is no script name, assume interpreter's name as base.
** (If there is no interpreter's name either, 'script' is -1, so
** table sizes are zero.)
*/
static void createargtable (lua_State *L, char **argv, int argc, int script) {
int i, narg;
narg = argc - (script + 1); /* number of positive indices */
lua_createtable(L, narg, script + 1);
for (i = 0; i < argc; i++) {
lua_pushstring(L, argv[i]);
lua_rawseti(L, -2, i - script);
}
lua_setglobal(L, "arg");
}
static int dochunk (lua_State *L, int status) {
if (status == LUA_OK) status = docall(L, 0, 0);
return report(L, status);
}
static int dofile (lua_State *L, const char *name) {
return dochunk(L, luaL_loadfile(L, name));
}
static int dostring (lua_State *L, const char *s, const char *name) {
return dochunk(L, luaL_loadbuffer(L, s, strlen(s), name));
}
/*
** Receives 'globname[=modname]' and runs 'globname = require(modname)'.
** If there is no explicit modname and globname contains a '-', cut
** the suffix after '-' (the "version") to make the global name.
*/
static int dolibrary (lua_State *L, char *globname) {
int status;
char *suffix = NULL;
char *modname = strchr(globname, '=');
if (modname == NULL) { /* no explicit name? */
modname = globname; /* module name is equal to global name */
suffix = strchr(modname, *LUA_IGMARK); /* look for a suffix mark */
}
else {
*modname = '\0'; /* global name ends here */
modname++; /* module name starts after the '=' */
}
lua_getglobal(L, "require");
lua_pushstring(L, modname);
status = docall(L, 1, 1); /* call 'require(modname)' */
if (status == LUA_OK) {
if (suffix != NULL) /* is there a suffix mark? */
*suffix = '\0'; /* remove suffix from global name */
lua_setglobal(L, globname); /* globname = require(modname) */
}
return report(L, status);
}
/*
** Push on the stack the contents of table 'arg' from 1 to #arg
*/
static int pushargs (lua_State *L) {
int i, n;
if (lua_getglobal(L, "arg") != LUA_TTABLE)
luaL_error(L, "'arg' is not a table");
n = (int)luaL_len(L, -1);
luaL_checkstack(L, n + 3, "too many arguments to script");
for (i = 1; i <= n; i++)
lua_rawgeti(L, -i, i);
lua_remove(L, -i); /* remove table from the stack */
return n;
}
static int handle_script (lua_State *L, char **argv) {
int status;
const char *fname = argv[0];
if (strcmp(fname, "-") == 0 && strcmp(argv[-1], "--") != 0)
fname = NULL; /* stdin */
status = luaL_loadfile(L, fname);
if (status == LUA_OK) {
int n = pushargs(L); /* push arguments to script */
status = docall(L, n, LUA_MULTRET);
}
return report(L, status);
}
/* bits of various argument indicators in 'args' */
#define has_error 1 /* bad option */
#define has_i 2 /* -i */
#define has_v 4 /* -v */
#define has_e 8 /* -e */
#define has_E 16 /* -E */
/*
** Traverses all arguments from 'argv', returning a mask with those
** needed before running any Lua code or an error code if it finds any
** invalid argument. In case of error, 'first' is the index of the bad
** argument. Otherwise, 'first' is -1 if there is no program name,
** 0 if there is no script name, or the index of the script name.
*/
static int collectargs (char **argv, int *first) {
int args = 0;
int i;
if (argv[0] != NULL) { /* is there a program name? */
if (argv[0][0]) /* not empty? */
progname = argv[0]; /* save it */
}
else { /* no program name */
*first = -1;
return 0;
}
for (i = 1; argv[i] != NULL; i++) { /* handle arguments */
*first = i;
if (argv[i][0] != '-') /* not an option? */
return args; /* stop handling options */
switch (argv[i][1]) { /* else check option */
case '-': /* '--' */
if (argv[i][2] != '\0') /* extra characters after '--'? */
return has_error; /* invalid option */
*first = i + 1;
return args;
case '\0': /* '-' */
return args; /* script "name" is '-' */
case 'E':
if (argv[i][2] != '\0') /* extra characters? */
return has_error; /* invalid option */
args |= has_E;
break;
case 'W':
if (argv[i][2] != '\0') /* extra characters? */
return has_error; /* invalid option */
break;
case 'i':
args |= has_i; /* (-i implies -v) *//* FALLTHROUGH */
case 'v':
if (argv[i][2] != '\0') /* extra characters? */
return has_error; /* invalid option */
args |= has_v;
break;
case 'e':
args |= has_e; /* FALLTHROUGH */
case 'l': /* both options need an argument */
if (argv[i][2] == '\0') { /* no concatenated argument? */
i++; /* try next 'argv' */
if (argv[i] == NULL || argv[i][0] == '-')
return has_error; /* no next argument or it is another option */
}
break;
default: /* invalid option */
return has_error;
}
}
*first = 0; /* no script name */
return args;
}
/*
** Processes options 'e' and 'l', which involve running Lua code, and
** 'W', which also affects the state.
** Returns 0 if some code raises an error.
*/
static int runargs (lua_State *L, char **argv, int n) {
int i;
for (i = 1; i < n; i++) {
int option = argv[i][1];
lua_assert(argv[i][0] == '-'); /* already checked */
switch (option) {
case 'e': case 'l': {
int status;
char *extra = argv[i] + 2; /* both options need an argument */
if (*extra == '\0') extra = argv[++i];
lua_assert(extra != NULL);
status = (option == 'e')
? dostring(L, extra, "=(command line)")
: dolibrary(L, extra);
if (status != LUA_OK) return 0;
break;
}
case 'W':
lua_warning(L, "@on", 0); /* warnings on */
break;
}
}
return 1;
}
static int handle_luainit (lua_State *L) {
const char *name = "=" LUA_INITVARVERSION;
const char *init = getenv(name + 1);
if (init == NULL) {
name = "=" LUA_INIT_VAR;
init = getenv(name + 1); /* try alternative name */
}
if (init == NULL) return LUA_OK;
else if (init[0] == '@')
return dofile(L, init+1);
else
return dostring(L, init, name);
}
/*
** {==================================================================
** Read-Eval-Print Loop (REPL)
** ===================================================================
*/
#if !defined(LUA_PROMPT)
#define LUA_PROMPT "> "
#define LUA_PROMPT2 ">> "
#endif
#if !defined(LUA_MAXINPUT)
#define LUA_MAXINPUT 512
#endif
/*
** lua_stdin_is_tty detects whether the standard input is a 'tty' (that
** is, whether we're running lua interactively).
*/
#if !defined(lua_stdin_is_tty) /* { */
#if defined(LUA_USE_POSIX) /* { */
#include <unistd.h>
#define lua_stdin_is_tty() isatty(0)
#elif defined(LUA_USE_WINDOWS) /* }{ */
#include <io.h>
#include <windows.h>
#define lua_stdin_is_tty() _isatty(_fileno(stdin))
#else /* }{ */
/* ISO C definition */
#define lua_stdin_is_tty() 1 /* assume stdin is a tty */
#endif /* } */
#endif /* } */
/*
** lua_readline defines how to show a prompt and then read a line from
** the standard input.
** lua_saveline defines how to "save" a read line in a "history".
** lua_freeline defines how to free a line read by lua_readline.
*/
#if !defined(lua_readline) /* { */
#if defined(LUA_USE_READLINE) /* { */
#include <readline/readline.h>
#include <readline/history.h>
#define lua_initreadline(L) ((void)L, rl_readline_name="lua")
#define lua_readline(L,b,p) ((void)L, ((b)=readline(p)) != NULL)
#define lua_saveline(L,line) ((void)L, add_history(line))
#define lua_freeline(L,b) ((void)L, free(b))
#else /* }{ */
#define lua_initreadline(L) ((void)L)
#define lua_readline(L,b,p) \
((void)L, fputs(p, stdout), fflush(stdout), /* show prompt */ \
fgets(b, LUA_MAXINPUT, stdin) != NULL) /* get line */
#define lua_saveline(L,line) { (void)L; (void)line; }
#define lua_freeline(L,b) { (void)L; (void)b; }
#endif /* } */
#endif /* } */
/*
** Return the string to be used as a prompt by the interpreter. Leave
** the string (or nil, if using the default value) on the stack, to keep
** it anchored.
*/
static const char *get_prompt (lua_State *L, int firstline) {
if (lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2") == LUA_TNIL)
return (firstline ? LUA_PROMPT : LUA_PROMPT2); /* use the default */
else { /* apply 'tostring' over the value */
const char *p = luaL_tolstring(L, -1, NULL);
lua_remove(L, -2); /* remove original value */
return p;
}
}
/* mark in error messages for incomplete statements */
#define EOFMARK "<eof>"
#define marklen (sizeof(EOFMARK)/sizeof(char) - 1)
/*
** Check whether 'status' signals a syntax error and the error
** message at the top of the stack ends with the above mark for
** incomplete statements.
*/
static int incomplete (lua_State *L, int status) {
if (status == LUA_ERRSYNTAX) {
size_t lmsg;
const char *msg = lua_tolstring(L, -1, &lmsg);
if (lmsg >= marklen && strcmp(msg + lmsg - marklen, EOFMARK) == 0)
return 1;
}
return 0; /* else... */
}
/*
** Prompt the user, read a line, and push it into the Lua stack.
*/
static int pushline (lua_State *L, int firstline) {
char buffer[LUA_MAXINPUT];
char *b = buffer;
size_t l;
const char *prmt = get_prompt(L, firstline);
int readstatus = lua_readline(L, b, prmt);
lua_pop(L, 1); /* remove prompt */
if (readstatus == 0)
return 0; /* no input */
l = strlen(b);
if (l > 0 && b[l-1] == '\n') /* line ends with newline? */
b[--l] = '\0'; /* remove it */
if (firstline && b[0] == '=') /* for compatibility with 5.2, ... */
lua_pushfstring(L, "return %s", b + 1); /* change '=' to 'return' */
else
lua_pushlstring(L, b, l);
lua_freeline(L, b);
return 1;
}
/*
** Try to compile line on the stack as 'return <line>;'; on return, stack
** has either compiled chunk or original line (if compilation failed).
*/
static int addreturn (lua_State *L) {
const char *line = lua_tostring(L, -1); /* original line */
const char *retline = lua_pushfstring(L, "return %s;", line);
int status = luaL_loadbuffer(L, retline, strlen(retline), "=stdin");
if (status == LUA_OK) {
lua_remove(L, -2); /* remove modified line */
if (line[0] != '\0') /* non empty? */
lua_saveline(L, line); /* keep history */
}
else
lua_pop(L, 2); /* pop result from 'luaL_loadbuffer' and modified line */
return status;
}
/*
** Read multiple lines until a complete Lua statement
*/
static int multiline (lua_State *L) {
for (;;) { /* repeat until gets a complete statement */
size_t len;
const char *line = lua_tolstring(L, 1, &len); /* get what it has */
int status = luaL_loadbuffer(L, line, len, "=stdin"); /* try it */
if (!incomplete(L, status) || !pushline(L, 0)) {
lua_saveline(L, line); /* keep history */
return status; /* should not or cannot try to add continuation line */
}
lua_remove(L, -2); /* remove error message (from incomplete line) */
lua_pushliteral(L, "\n"); /* add newline... */
lua_insert(L, -2); /* ...between the two lines */
lua_concat(L, 3); /* join them */
}
}
/*
** Read a line and try to load (compile) it first as an expression (by
** adding "return " in front of it) and second as a statement. Return
** the final status of load/call with the resulting function (if any)
** in the top of the stack.
*/
static int loadline (lua_State *L) {
int status;
lua_settop(L, 0);
if (!pushline(L, 1))
return -1; /* no input */
if ((status = addreturn(L)) != LUA_OK) /* 'return ...' did not work? */
status = multiline(L); /* try as command, maybe with continuation lines */
lua_remove(L, 1); /* remove line from the stack */
lua_assert(lua_gettop(L) == 1);
return status;
}
/*
** Prints (calling the Lua 'print' function) any values on the stack
*/
static void l_print (lua_State *L) {
int n = lua_gettop(L);
if (n > 0) { /* any result to be printed? */
luaL_checkstack(L, LUA_MINSTACK, "too many results to print");
lua_getglobal(L, "print");
lua_insert(L, 1);
if (lua_pcall(L, n, 0, 0) != LUA_OK)
l_message(progname, lua_pushfstring(L, "error calling 'print' (%s)",
lua_tostring(L, -1)));
}
}
/*
** Do the REPL: repeatedly read (load) a line, evaluate (call) it, and
** print any results.
*/
static void doREPL (lua_State *L) {
int status;
const char *oldprogname = progname;
progname = NULL; /* no 'progname' on errors in interactive mode */
lua_initreadline(L);
while ((status = loadline(L)) != -1) {
if (status == LUA_OK)
status = docall(L, 0, LUA_MULTRET);
if (status == LUA_OK) l_print(L);
else report(L, status);
}
lua_settop(L, 0); /* clear stack */
lua_writeline();
progname = oldprogname;
}
/* }================================================================== */
/*
** Main body of stand-alone interpreter (to be called in protected mode).
** Reads the options and handles them all.
*/
extern int luaopen_lpeg(lua_State *L);
static int pmain (lua_State *L) {
int argc = (int)lua_tointeger(L, 1);
char **argv = (char **)lua_touserdata(L, 2);
int script;
int args = collectargs(argv, &script);
int optlim = (script > 0) ? script : argc; /* first argv not an option */
luaL_checkversion(L); /* check that interpreter has correct version */
if (args == has_error) { /* bad arg? */
print_usage(argv[script]); /* 'script' has index of bad arg. */
return 0;
}
if (args & has_v) /* option '-v'? */
print_version();
if (args & has_E) { /* option '-E'? */
lua_pushboolean(L, 1); /* signal for libraries to ignore env. vars. */
lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");
}
luaL_openlibs(L); /* open standard libraries */
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
lua_pushcfunction(L, luaopen_lpeg);
lua_setfield(L, -2, "lpeg");
lua_pop(L, 1); // remove PRELOAD table
createargtable(L, argv, argc, script); /* create table 'arg' */
lua_gc(L, LUA_GCRESTART); /* start GC... */
lua_gc(L, LUA_GCGEN, 0, 0); /* ...in generational mode */
if (!(args & has_E)) { /* no option '-E'? */
if (handle_luainit(L) != LUA_OK) /* run LUA_INIT */
return 0; /* error running LUA_INIT */
}
if (!runargs(L, argv, optlim)) /* execute arguments -e and -l */
return 0; /* something failed */
if (script > 0) { /* execute main script (if there is one) */
if (handle_script(L, argv + script) != LUA_OK)
return 0; /* interrupt in case of error */
}
if (args & has_i) /* -i option? */
doREPL(L); /* do read-eval-print loop */
else if (script < 1 && !(args & (has_e | has_v))) { /* no active option? */
if (lua_stdin_is_tty()) { /* running in interactive mode? */
print_version();
doREPL(L); /* do read-eval-print loop */
}
else dofile(L, NULL); /* executes stdin as a file */
}
lua_pushboolean(L, 1); /* signal no errors */
return 1;
}
int main (int argc, char **argv) {
int status, result;
lua_State *L = luaL_newstate(); /* create state */
if (L == NULL) {
l_message(argv[0], "cannot create state: not enough memory");
return EXIT_FAILURE;
}
lua_gc(L, LUA_GCSTOP); /* stop GC while building state */
lua_pushcfunction(L, &pmain); /* to call 'pmain' in protected mode */
lua_pushinteger(L, argc); /* 1st argument */
lua_pushlightuserdata(L, argv); /* 2nd argument */
status = lua_pcall(L, 2, 1, 0); /* do the call */
result = lua_toboolean(L, -1); /* get result */
report(L, status);
lua_close(L);
return (result && status == LUA_OK) ? EXIT_SUCCESS : EXIT_FAILURE;
}

BIN
world_map.kra (Stored with Git LFS)

Binary file not shown.