-- STEAD3 API

-- luacheck: globals iface init start stead
-- luacheck: read globals DEBUG doencfile instead_readdir instead_random table_get_maxn instead_srandom

stead = {
	space_delim = ' ',
	scene_delim = '^^',
	delim = '|',
	call_top = 0,
	call_ctx = { txt = nil, self = nil },
	objects = {};
	tags = {};
	next_dynamic = -1;
	max_dynamic = 32767;
	tables = {};
	functions = {};
	modules = {};
	includes = {};
	tostr = tostring;
	tonum = tonumber;
	type = type;
	setmt = setmetatable;
	getmt = getmetatable;
	table = table;
	math = math;
	newproxy = newproxy;
	pairs = pairs;
	ipairs = ipairs;
	rawset = rawset;
	rawget = rawget;
	rawequal = rawequal;
	pcall = pcall;
	io = io;
	os = os;
	readdir = instead_readdir,
	string = string;
	next = next;
	loadfile = loadfile;
	dofile = dofile;
	doencfile = doencfile;
	getinfo = debug.getinfo;
	__mod_hooks = {};
	files = {};
	busy = function() end;
	debug_xref = true;
	debug_save = false;
	random = instead_random;
	randomseed = instead_srandom;
}


local std = stead

std.strip_call = true

local error = error

function std.err(msg, lev)
	if std.noerror then
		std.dprint(msg)
	else
		error(msg, lev)
	end
end

function std.dprint(...)
	local a = { ... }
	for i = 1, #a do
		if i ~= 1 then
			std.io.stderr:write(' ')
		end
		std.io.stderr:write(std.tostr(a[i]))
	end
	std.io.stderr:write('\n')
	std.io.stderr:flush()
end

std.rnd = function(...)
	if std.random then
		return std.random(...)
	end
	return std.math.random(...);
end

std.rnd_seed = function(...)
	std.math.randomseed(...)
	if std.randomseed then
		return std.randomseed(...)
	end
end

function stead:abort()
	self.abort_cmd = true
end

function stead.savepath()
	return "./"
end

local table = std.table
local pairs = std.pairs
local ipairs = std.ipairs
local string = std.string
local rawset = std.rawset
local rawget = std.rawget
local type = std.type
local io = std.io;

if _VERSION == "Lua 5.1" then
	std.eval = loadstring
	std.unpack = unpack
	std.proxy = function(o)
		local oo = std.newproxy(true)
		local t = std.getmt(oo)
		t.__index = o
		t.__newindex = o
		t.__gc = function(_)
			o:__gc()
		end
		t.__tostring = function(_)
			return o:__tostring()
		end
		o.__proxy_type = true
		return oo
	end
else
	std.proxy = function(o)
		o.__proxy_type = true
		return o
	end
	std.eval = load
	std.unpack = table.unpack
	table.maxn = table_get_maxn
	string.gfind = string.gmatch
-- luacheck: push ignore math
	math.mod = math.fmod
	math.log10 = function(a)
		return std.math.log(a, 10)
	end
end

math.pow = function(num, n)
	return num ^ n
end

math.round = function(num, n)
	local m = 10 ^ (n or 0)
	return std.math.floor(num * m + 0.5) / m
end
-- luacheck: pop

local function __mod_callback_reg(f, hook, prio)
	if type(f) ~= 'function' then
		std.err ("Wrong parameter to mod_"..hook..".", 3);
	end

	if prio and type(prio) ~= 'number' then
		std.err ("Wrong prio parameter to mod_"..hook..".", 3);
	end

	if not std.__mod_hooks[hook] then
		std.__mod_hooks[hook] = {}
	end
	local i = { fn = f, prio = prio, unload = std.__in_include }
	table.insert(std.__mod_hooks[hook], i);
	std.sort(std.__mod_hooks[hook], function (a, b)
		a = a.prio or 0
		b = b.prio or 0
		if a == b then
			return nil
		end
		return a < b
	end)
--	f();
end

function std.mod_unload()
	local new = {}
	for k, v in pairs(std.__mod_hooks) do
		local list = {}
		for _, vv in ipairs(v) do
			if not vv.unload then
				table.insert(list, vv)
			end
		end
		new[k] = list
	end
	std.__mod_hooks = new
end

function std.mod_call(hook, ...)
	if not std.__mod_hooks[hook] then
		return
	end
	for _, v in ipairs(std.__mod_hooks[hook]) do
		local a, b = v.fn(...)
		if a ~= nil or b ~= nil then
			return a, b
		end
	end
end

function std.mod_call_rev(hook, ...)
	if not std.__mod_hooks[hook] then
		return
	end
	for i = #std.__mod_hooks[hook], 1, -1 do
		local v = std.__mod_hooks[hook][i]
		local a, b = v.fn(...)
		if a ~= nil or b ~= nil then
			return a, b
		end
	end
end

function std.mod_init(f, ...)
	__mod_callback_reg(f, 'init', ...)
	if std.initialized then -- require from game
		f(...)
	end
end

function std.mod_done(f, ...)
	__mod_callback_reg(f, 'done', ...)
end

function std.mod_start(f, ...)
	__mod_callback_reg(f, 'start', ...)
end

function std.mod_cmd(f, ...)
	__mod_callback_reg(f, 'cmd', ...)
end

function std.mod_step(f, ...)
	__mod_callback_reg(f, 'step', ...)
end

function std.mod_save(f, ...)
	__mod_callback_reg(f, 'save', ...)
end

function std.hook(o, f)
	local ff
	if type(o) ~= 'function' then
		ff = function()
			return o
		end
	else
		ff = o
	end
	return function(...)
		return f(ff, ...)
	end
end

local substs

local function xref_prep(str)
	local oo, self
	local a = {}
	local s = str
	local i = s:find('\001', 1, true)
	if not i then
		return str
	end
	oo = std.strip(s:sub(1, i - 1))
	s = s:sub(i + 1)
	if oo:find('@', 1, true) == 1 or oo:find('$', 1, true) then -- call '@' obj (aka xact) or '$' aka subst
		local o = std.split(oo)[1]
		local ii = oo:find("[ \t]")
		if ii then
			a = std.strip(oo:sub(ii))
			a = std.cmd_parse(a)
		end
		self = std.ref(o)
	else
		if oo:find("^# [0-9-]+") then
			self = std.ref(std.tonum(oo:sub(3)))
		elseif std.is_tag(oo) then -- #tag?
			self = std.here():lookup(oo)
		else
			self = std.ref(oo)
		end
	end
	if not std.is_obj(self) then
		if std.debug_xref then
			std.err("Wrong object in xref: "..std.tostr(oo), 2)
		else
			std.dprint("Wrong xref: "..std.tostr(oo))
			return s
		end
	end
	if type(self.nam) == 'string' and self.nam:find('$', 1, true) == 1 then -- subst
		table.insert(a, s)
		substs = true
		local r, v = std.method(self, 'act', std.unpack(a))
		if not v then
			return s
		end
		return std.tostr(r)
	end
	return iface:xref(s, self, std.unpack(a));
end

local fmt_refs

local function fmt_prep(str)
	local s = str:gsub("^{", ""):gsub("}$", "")
	s = s:gsub('\\?['..std.delim..']', { [std.delim] = '\001' });
	local l = s:find('\001', 1, true)
	if l == 1 then
		return str
	end
	if not l then
		s = s .. '\001'
	end
	l = s:find('\001', 1, true)
	table.insert(fmt_refs, s:sub(1, l - 1))
	local n = string.format("%d%s", #fmt_refs, std.delim)
	return "{"..n..s:sub(l + 1).."}"
end

local function fmt_post(str)
	local s = str:gsub("^{", ""):gsub("}$", ""):gsub('\\?['..std.delim..']',
		{ [std.delim] = '\001' } );
	local l = s:find('\001')
	if not l or l == 1 then
		return str
	end
	local n = std.tonum(s:sub(1, l - 1)) or 0
	if not fmt_refs[n] then
		return str
	end
	s = fmt_refs[n]..s:sub(l)
	return xref_prep(std.unesc(s))
end

function std.for_each_xref_outer(s, fn)
	local orig = s
	s = string.gsub(s, '\\?[\\{}]',
			{ ['{'] = '\001', ['}'] = '\002', [ '\\{' ] = '\\{', [ '\\}' ] = '\\}' });
	local start
	while true do
		start = s:find('\001')
		if not start then break end
		local idx = 1
		local n = start
		while idx > 0 do
			n = s:find('[\001\002]', n + 1)
			if not n then
				break
			end
			if s:sub(n, n) == '\001' then
				idx = idx + 1
			else
				idx = idx - 1
			end
		end
		if idx == 0 then
			local new = fn(s:sub(start, n):gsub('[\001\002]', {['\001'] = '{', ['\002'] = '}'}))
			if start == 1 then
				s = new..s:sub(n + 1)
			else
				s = s:sub(1, start - 1)..new..s:sub(n + 1)
			end
		else
			std.err("Unpaired '{' in:"..std.tostr(orig), 2)
			break
		end
	end
	s = s:gsub('[\001\002]', { ['\001'] = '{', ['\002'] = '}' });
	return s
end

function std.for_each_xref(s, fn)
	s = string.gsub(s, '\\?[\\{}]',
			{ ['{'] = '\001', ['}'] = '\002', [ '\\{' ] = '\\{', [ '\\}' ] = '\\}' });
	local function prep(str)
		str = str:gsub("[\001\002]", "")
		str = fn('{'..str..'}')
		return str
	end
	s = string.gsub(s, '(\001[^\001\002]+\002)', prep)
	s = s:gsub('[\001\002]', { ['\001'] = '{', ['\002'] = '}' });
	return s
end

std.fmt = function(str, fmt, state)
	if type(str) ~= 'string' then
		return
	end
	local s = str
	s = string.gsub(s, '[\t \n]+', std.space_delim);
	s = string.gsub(s, '\\?[\\^]', { ['^'] = '\n', ['\\^'] = '^'} ):gsub("\n[ \t]+", "\n")
	local first = true
	while true do
		fmt_refs = {}
		substs = false
		s = std.for_each_xref(s, fmt_prep) -- rename all {}
		if first and type(fmt) == 'function' then
			s = fmt(s, state)
		end
		s = std.for_each_xref(s, fmt_post) -- rename and xref
		if not substs then
			break
		end
		first = false
	end
	s = s:gsub('\\?'..'[{}]', { ['\\{'] = '{', ['\\}'] = '}' })
	if state then
		s = s:gsub('\\?'..std.delim, { ['\\'..std.delim] = std.delim })
	end
	return s
end


local lua_keywords = {
	["and"] = true,
	["break"] = true,
	["do"] = true,
	["else"] = true,
	["elseif"] = true,
	["end"] = true,
	["false"] = true,
	["for"] = true,
	["function"] = true,
	["goto"] = true,
	["if"] = true,
	["in"] = true,
	["local"] = true,
	["nil"] = true,
	["not"] = true,
	["or"] = true,
	["repeat"] = true,
	["return"] = true,
	["then"] = true,
	["true"] = true,
	["until"] = true,
	["while"] = true,
}

std.setmt(stead, {
	__call = function(_, k)
		return std.ref(k)
	end;
})


function std.is_system(v)
	if not std.is_obj(v) then
		return false
	end
	local n = v.nam
	if type(n) == 'string' then
		if n:byte(1) == 0x40 or n:byte(1) == 0x24 then
			return true
		end
	end
	return false
end

function std.is_obj(v, t)
	if type(v) ~= 'table' then
		return false
	end
	return v['__'..(t or 'obj')..'_type']
end

function std.class(self, inh)
--	self.__parent = function(s)
--		return inh
	--	end;
	self.nam = '*class*';
	self.__call = function(v, n, ...)
		if std.is_obj(v) and type(n) == 'string' then
			-- variable access
			return function(val)
				if std.game then
					rawset(v.__var, n, true)
					rawset(v.__ro, n, nil)
					return rawset(v, n, val or false)
				end
				return rawset(v.__ro, n, val or false)
			end
		end
		n = v:new(n, ...)
		std.setmt(n, v)
		return n
	end;
	self.__tostring = function(s)
		if not std.is_obj(s) then
			local os = self.__tostring
			self.__tostring = nil
			local t = std.tostr(s)
			self.__tostring = os
			return t
		end
		return std.dispof(s)
	end;
	self.__pow = function(s, b)
		if type(b) == 'string' or type(b) == 'number' then
			if std.is_tag(b) then
				return std.rawequal(s.tag, b)
			else
				return std.rawequal(s.nam, b)
			end
		end
		return std.rawequal(s, b)
	end;
	self.__dirty = function(s, v)
		local o = rawget(s, '__dirty_flag')
		if v ~= nil then
			if std.game then
				rawset(s, '__dirty_flag', v)
			end
			return s
		end
		return o
	end;
	self.__index = function(t, k)
		local ro = type(rawget(t, '__ro')) == 'table' and t.__ro
		local v
		if ro then
			v = rawget(ro, k)
		end
		if v == nil then
			return self[k]
		end
		if ro and std.game and type(v) == 'table' then
			-- make rw if simple table
			if type(v.__dirty) ~= 'function' then
				t.__var[k] = true
				rawset(t, k, v)
				ro[k] = nil
			end
		end
		return v
	end;
	self.__newindex = function(t, k, v)
		local ro = std.is_obj(t) and t.__ro

		if ro and not std.game then
			rawset(ro, k, v)
			return
		end

		t:__dirty(true)
		if ro then
			if (type(v) == 'function' and not std.functions[v]) then
				std.err("Wrong variable operation: "..std.tostr(k).. " at "..std.tostr(t), 2)
			end
			if std.nostrict or (type(k) == 'string' and k:find('^__')) or t.__var[k] or ro[k] ~= nil then
				t.__var[k] = true
			else
				std.err("Set unitialized variable: "..std.tostr(k).." at "..std.tostr(t), 2)
			end
			ro[k] = nil
		end
		if std.is_obj(v, 'list') and std.is_obj(t) then
			v:attach(t)
		end
		rawset(t, k, v)
	end
	std.setmt(self, inh or { __call = self.__call })
	return self
end

function std.is_tag(n)
	return type(n) == 'string' and n:byte(1) == 0x23
end

function std.sort(t, fn)
	local prio = {}
	local v
	for i = 1, #t do
		v = t[i]
		prio[i] = { v = v, i = i }
	end
	table.sort(prio, function(a, b)
		local r = fn(a.v, b.v)
		if type(r) == 'boolean' then
			return r
		end
		return a.i < b.i
	end)
	for i = 1, #prio do
		t[i] = prio[i].v
	end
end

std.list = std.class {
	__list_type = true;
	new = function(_, v)
		if type(v) ~= 'table' then
			std.err ("Wrong argument to std.list:"..std.tostr(v), 2)
		end
		if std.is_obj(v, 'list') then -- already list
			return v
		end
		v.__list = {} -- where is attached
		-- std.setmt(v, s)
		return v
	end;
	__ini = function(s, o)
		for i = 1, #s do
			local k = s[i]
			s[i] = std.ref(k)
			if not std.is_obj(s[i]) then
				if not o then
					std.err("Wrong item in list: "..std.tostr(k), 2)
				else
					std.err("Wrong item in list: "..std.tostr(k).." in "..std.dispof(o), 2)
				end
			end
			s:__attach(s[i])
		end
		if o then
			s:attach(o)
		end
		s:sort()
	end;
	sort = function(s)
		std.sort(s, function(a, b)
			local p1 = std.tonum(a.pri) or 0
			local p2 = std.tonum(b.pri) or 0
			if p1 == p2 then return nil end
			return p1 < p2
		end)
	end;
	display = function(s)
		local r
		for i = 1, #s do
			if r then
				r = r .. std.space_delim
			end
			local o = s[i]
			if o:visible() then
				local disp = o:display()
				local d = o:__xref(disp)
				if type(d) == 'string' then
					r = (r or '').. d
				end
				if not o:closed() then
					d = o.obj:display()
					if type(d) == 'string' then
						r = (r and (r .. std.space_delim) or '') .. d
					end
				end
			end
		end
		return r
	end;
	disable = function(s)
		for i = 1, #s do
			s[i]:disable()
		end
	end;
	enable = function(s)
		for i = 1, #s do
			s[i]:enable()
		end
	end;
	close = function(s)
		for i = 1, #s do
			s[i]:close()
		end
	end;
	open = function(s)
		for i = 1, #s do
			s[i]:open()
		end
	end;
	attach = function(s, o) -- attach to object
		s:detach(o)
		table.insert(s.__list, o)
	end;
	detach = function(s, o) -- detach from object
		for i = 1, #s.__list do
			if s.__list[i] == o then
				table.remove(s.__list, i)
				break
			end
		end
	end;
	__attach = function(s, o) -- attach object to list
		s:__detach(o)
		table.insert(o.__list, s)
	end;
	__detach = function(s, o) -- detach object from list
		for i = 1, #o.__list do
			if o.__list[i] == s then
				table.remove(o.__list, i)
				break
			end
		end
	end;
	add = function(s, n, pos)
		local o = s:lookup(n)
		if o then
			return o -- already here
		end
		if pos then
			if type(pos) ~= 'number' then
				std.err("Wrong parameter to list.add:"..std.tostr(pos), 2)
			end
			if pos > #s then
				pos = nil -- add to last position
			elseif pos < 0 then
				pos = #s + pos + 1
			end
			if pos and pos <= 0 then
				pos = 1
			end
		end
		o = std.ref(n)
		if not o then
			std.err("Wrong argument to list:add(): "..std.tostr(n), 2)
		end
		s:__dirty(true)
		s:__attach(o)
		if pos then
			table.insert(s, pos, o)
		else
			table.insert(s, o)
		end
		s:sort()
		return o
	end;
	for_each = function(s, fn, ...)
		if type(fn) ~= 'function' then
			std.err("Wrong parameter to list:for_each: "..std.tostr(fn), 2)
		end
		for i = 1, #s do
			local r, v = fn(s[i], ...)
			if r ~= nil or v ~= nil then
				return r, v
			end
		end
	end;
	lookup = function(s, n)
		local o, tag
		if std.is_tag(n) then
			tag = n
		else
			o = std.ref(n)
		end
		for i = 1, #s do
			if s[i] == o or (tag and s[i].tag == tag) then
				return s[i], i
			end
		end
	end;
	srch = function(s, n)
		local o, i = s:lookup(n)
		if not o or not o:visible() then
			return
		end
		return o, i
	end;
	empty = function(s)
		return (#s == 0)
	end;
	cat = function(s, from, pos)
		if not std.is_obj(from, 'list') then
			std.err("Wrong argument to list:cat(): "..std.tostr(from), 2)
		end
		if not pos then pos = #s + 1 end
		for _, v in ipairs(from) do
			s:add(v, pos)
			pos = pos + 1
		end
		return s
	end;
	zap = function(s) -- delete all objects
		local l = {}
		for i = 1, #s do
			table.insert(l, s[i])
		end
		for i = 1, #l do
			s:del(l[i])
		end
		return s
	end;
	del = function(s, n)
		local o, i = s:lookup(n)
		if i then
			s:__dirty(true)
			s:__detach(o)
			table.remove(s, i)
			s:sort()
			return o, i
		end
	end;
	replace = function(s, n, w)
		local o, i = s:del(n)
		if o then
			return s:add(w, i)
		end
	end;
	__dump = function(s, recurse)
		local rc
		for i = 1, #s do
			local v = s[i]
			if std.is_obj(v) and v:visible() then
				local vv, n
				if type(v.nam) == 'number' then
					n = '# '..std.tostr(v.nam)
				else
					n = v.nam
				end
				local disp = std.dispof(v)
				if disp then
					if rc then
						rc = rc .. std.delim
					else
						rc = ''
					end
					vv = '{'..std.esc(n)..std.delim..std.esc(disp)..'}'
					rc = rc .. vv
				end
				if recurse and not v:closed() then
					vv = v:__dump(recurse)
					if vv then
						if rc then rc = rc .. std.delim else rc = '' end
						rc = rc .. vv
					end
				end
			end
		end
		return rc
	end;
	__save = function(s, fp, n)
		if not s:__dirty() then
			return
		end
		fp:write(string.format("%s = std.list { ", n))
		for i = 1, #s do
			local vv = std.deref(s[i])
			if not vv then
				std.err ("Can not do deref on: "..std.tostr(s[i]), 2)
			end
			if i ~= 1 then
				fp:write(string.format(", "))
			end
			if type(vv) == 'number' then
				fp:write(string.format("%d", vv))
			else
				fp:write(string.format("%q", vv))
			end
		end
		fp:write(" }:__dirty(true)\n")

	end;
}
std.save_var = function(vv, fp, n)
	if type(vv) == 'boolean' or type(vv) == 'number' then
		fp:write(string.format("%s = ", n))
		fp:write(std.tostr(vv)..'\n')
	elseif type(vv) == 'string' then
		fp:write(string.format("%s = ", n))
		fp:write(string.format("%q\n", vv))
	elseif type(vv) == 'function' then
		if std.functions[vv] and std.functions[vv] ~= n then
			local k = std.functions[vv]
			fp:write(string.format("%s = %s\n", n, k))
		else
			std.err("Can not save variable (function): "..n, 2)
		end
	elseif type(vv) == 'table' then
		if std.tables[vv] and std.tables[vv] ~= n then
			local k = std.tables[vv]
			fp:write(string.format("%s = %s\n", n, k))
		elseif std.is_obj(vv) then
			local d = std.deref(vv)
			if not d then
				std.err("Can not deref object:"..std.tostr(vv), 2)
			end
			fp:write(string.format("%s = ", n))
			if type(d) == 'string' then
				fp:write(string.format("std %q\n", d))
			else
				fp:write(string.format("std(%d)\n", d))
			end
		elseif type(vv.__save) == 'function' then
			vv:__save(fp, n)
		else
			fp:write(string.format("%s = %s\n", n,  std.dump(vv, true)))
--			std.save_table(vv, fp, n)
		end
	elseif vv == nil then
		fp:write(string.format("%s = nil\n", n))
	elseif type(vv) == 'userdata' and type(vv.__save) == 'function' then
		vv:__save(fp, n)
	else
		std.err("Can not save var: "..n, 2)
	end
end

std.save_members = function(vv, fp, n)
	local l
	for k, v in pairs(vv) do
		if type(k) == 'number' then
			l = string.format("%s%s", n, std.varname(k))
			std.save_var(v, fp, l)
		elseif type(k) == 'string' then
			l = string.format("%s%s", n, std.varname(k))
			std.save_var(v, fp, l)
		end
	end
end

std.save_table = function(vv, fp, n)
	fp:write(string.format("%s = {}\n", n))
	std.save_members(vv, fp, n)
end

function std:reset(fn) -- reset state
	local reset
	if std.ref 'game'.__started or fn then
		self:done()
		self:init()
		reset = true
	end
	if fn ~= 'main3.lua' then
		std.startfile = fn -- another start file
	end
	if reset then
		std.dofile(fn or 'main3.lua')
	end
end

function std:load(fname) -- load save
	self:reset()
	std.ref 'game':__ini()

	local f, err = std.loadfile(fname) -- load all diffs
	if not f then
		std.err(err, 2)
	end

	local strict = std.nostrict; std.nostrict = true;
	if DEBUG then
		std.noerror = true
		local st, r = std.pcall(f)
		if not st then
			std.dprint(r)
		end
		std.noerror = false
	else
		f();
	end
	std.nostrict = strict

	std.ref 'game':__ini(true)
	std.ref 'game':__start(true)
	return self.game:lastdisp()
end

local function in_section(name, fn)
	name = "__in_"..name
	local old = std[name]
	std[name] = true
	local r, v = fn()
	std[name] = old or false
	return r, v
end

function std.gamefile(fn, reset) -- load game file
	if type(fn) ~= 'string' then
		std.err("Wrong paramter to stead:file: "..std.tostr(fn), 2)
	end
	if not fn:find("%.lua$") then
		fn = fn .. '.lua'
	end
	if reset then
		std:reset(fn)
		std.ref 'game':__ini()
		local r, v = std.ref 'game':__start()
		if type(r) == 'string' and std.cctx() then
			std.pr(r)
		end
		return r, v
	end
	std.game = nil
	in_section ('gamefile', function() std.dofile(fn) end)
	std.ref 'game':__ini()
	table.insert(std.files, fn) -- remember it
end

-- luacheck: no self

function std:save(fp)
	local close
	local name, name_tmp
	if type(fp) == 'string' then
		name, name_tmp = fp, fp .. '.tmp'
		fp = io.open(name_tmp, "wb");
		if not fp then
			return nil, false -- can create file
		end
		close = true
	end
	local n
	if std.type(std.savename) == 'function' then
		n = std.savename()
	end
	if std.type(n) == 'string' then
		fp:write("-- $Name: "..n:gsub("\n","\\n").."$\n");
	end
	fp:write("local std = stead\n");
	-- reset
	if std.startfile then
		fp:write(string.format("std:reset(%q)\n", std.startfile))
		fp:write(string.format("std 'game':ini()\n"))
	end
	-- files
	for i = 1, #std.files do
		fp:write(string.format("std.gamefile(%q)\n", std.files[i]))
	end

	std.busy(true)
	std.for_each_obj(function(v)
		if v.__dynamic then
			std.busy(true)
			v:save(fp, string.format("std(%s)", std.deref_str(v)))
		end
	end)

	std.mod_call('save', fp)

	std.for_each_obj(function(v)
		if not v.__dynamic then
			std.busy(true)
			v:save(fp, string.format("std(%s)", std.deref_str(v)))
		end
	end)
	if close then
		fp:flush();
		fp:close();
		std.os.remove(name);
		std.os.rename(name_tmp, name);
	end
	std.busy(false)
	return std.game:lastdisp() -- same scene
end

local rnd_seed = 1980 + 1978
function std:init()
	std.rawset(_G, 'iface', std.ref '@iface') -- force iface override
	std.world { nam = 'game', player = 'player', codepage = 'UTF-8',
		dsc = [[STEAD3, 2020 by Peter Kosyh^https://instead-hub.github.io^^]] };
	std.room { nam = 'main' }
	std.player { nam = 'player', room = 'main' }

	rnd_seed = (std.os.time(stead.os.date("*t")) + rnd_seed)
	std.rnd_seed(rnd_seed)

	std.mod_call('init') -- init modules
	std.initialized = true
end

function std:done()
	std.mod_call_rev('done')
	std.mod_unload() -- unload hooks from includes
	local objects = {}
	std.for_each_obj(function(v)
		local k = std.deref(v)
		if std.is_system(v) then
			objects[k] = v
		else
			std.dprint("Deleting "..k)
		end
	end)
	std.objects = objects
	std.tags = {}
	std.next_dynamic = -1
	std.files = {}
	std.startfile = false
--	std.modules = {}
	std.includes = {}
	std.initialized = false
	std.game = nil
	std.rawset(_G, 'init', nil)
	std.rawset(_G, 'start', nil)
end
-- luacheck: self

function std.for_all(fn, ...)
	if type(fn) ~= 'function' then
		std.err("Wrong 1-st argument to for_all(): "..std.tostr(fn), 2)
	end
	local a = {...}
	for i = 1, #a do
		fn(a[i])
	end
end

function std.for_each_obj(fn, ...)
	local oo = std.objects
	for _, v in pairs(oo) do
		if std.is_obj(v) then
			local a, b = fn(v, ...)
			if a ~= nil and b ~= nil then
				return a, b
			end
		end
	end
end

function std.dirty(o)
	if type(o) ~= 'table' or type(o.__dirty) ~= 'function' then
		return false
	end
	return o:__dirty()
end

function std.deref_str(o)
	local k = std.deref(o)
	if type(k) == 'number' then
		return std.tostr(k)
	elseif type(k) == 'string' then
		return std.string.format("%q", k)
	end
	return
end

function std.varname(k)
	if type(k) == 'number' then
		return string.format("[%d]", k)
	elseif type(k) == 'string' then
		if not lua_keywords[k] then
			return string.format(".%s", k)
		else
			return string.format("[%q]", k)
		end
	end
end

local function next_dynamic(n)
	if n then
		std.next_dynamic = n
	end
	std.next_dynamic = std.next_dynamic - 1
	if std.next_dynamic < -std.max_dynamic then
		std.next_dynamic = - 1
	end
	return std.next_dynamic
end

local function dyn_name()
	local oo = std.objects
	if not oo[std.next_dynamic] then
		local n = std.next_dynamic
		next_dynamic()
		return n
	end

	local on = std.next_dynamic
	local n = next_dynamic()

	while oo[n] and n ~= on do
		n = n - 1
		if n < -std.max_dynamic then
			n = -1
		end
	end

	if oo[n] then
		std.err("No free ids for dynamic objects", 2)
	end

	next_dynamic(n)

	return n
end

local function tag_name(t)
	local oo = std.objects
	local tt = std.tags[t] or {}
	local n = t:sub(2) .. '#'..std.tonum(#tt)
	if oo[n] then return #oo + 1 end -- collision
	table.insert(tt, n)
	std.tags[t] = tt
	return n
end

std.obj = std.class {
	__obj_type = true;
	type = function(self, t)
		return std.is_obj(self, t)
	end;
	with = function(self, ...)
		local a = {...}
		for i = 1, #a do
			if type(a[i]) == 'table' then
				for k = 1, #a[i] do
					table.insert(self.obj, a[i][k])
				end
			else
				table.insert(self.obj, a[i])
			end
		end
		return self
	end;
	new = function(self, v)
		if std.game and not std.__in_new and not std.__in_gamefile then
			std.err ("Use std.new() to create dynamic objects:"..std.tostr(v), 2)
		end
		local oo = std.objects
		if type(v) ~= 'table' then
			std.err ("Wrong argument to std.obj:"..std.tostr(v), 2)
		end
		if std.is_tag(v.nam) then
			rawset(v, 'tag', v.nam)
			rawset(v, 'nam', nil)
		end

		if v.nam == nil then
			if std.__in_new then
				rawset(v, 'nam', dyn_name())
			elseif std.is_tag(v.tag) then
				rawset(v, 'nam', tag_name(v.tag))
			else
				rawset(v, 'nam', #oo + 1)
			end
			rawset(v, '__autoname', true)
		elseif type(v.nam) ~= 'string' and type(v.nam) ~= 'number' then
			std.err ("Wrong .nam in object.", 2)
--		elseif type(v.nam) == 'string' and v.nam:find('#[0-9]+$') then
--			std.err ("You can not use #<number> in object name: "..v.nam, 2)
		end

		if oo[v.nam] and not std.is_system(oo[v.nam]) then
			if v.nam ~= 'main' and v.nam ~= 'player' and v.nam ~= 'game' then
				std.err ("Duplicated object: "..v.nam, 2)
			end
		end
		local ro = {}
		local vars = {}
		local raw = {}
		for i = 1, #v do
			if type(v[i]) ~= 'table' then
				std.err("Wrong declaration: "..std.tostr(v[i]), 2)
			end
			local var = (v[i].__var_type == true) -- raw or var mode
			for key, val in pairs(v[i]) do
				if type(key) ~= 'string' then
					std.err("Wrong var name: "..std.tostr(key), 2)
				end
				raw[key] = not var
				rawset(v, key, val)
			end
		end
		for _ = 1, #v do
			table.remove(v, 1)
		end
		if not v.obj then
			rawset(v, 'obj', {})
		end
		if type(v.obj) ~= 'table' then
			std.err ("Wrong .obj attr in object:" .. v.nam, 2)
		end
		v.obj = std.list(v.obj)
--		v.obj:attach(v)
		for key, val in pairs(v) do
			if not std.nostrict_new and type(self[key]) == 'function' and type(val) ~= 'function' then
				std.err("Overwrited object method: '"..std.tostr(key).. "' in: "..std.tostr(v.nam), 2)
			end
			if not raw[key] then
				ro[key] = val
				rawset(v, key, nil)
			end
		end
		rawset(v, '__ro', ro)
		rawset(v, '__var', vars)
		rawset(v, '__list', {}) -- in what list(s)
		oo[ro.nam] = v
		-- std.setmt(v, self)
		if std.__in_init then
			table.insert(std.__in_init, v)
		end
		return v
	end;
	actions = function(s, t, v)
		t = t or 'act'
		if type(t) ~= 'string' then
			std.err("Wrong argument to obj:actions(): "..std.tostr(t), 2)
		end
		local ov = s['__nr_'..t] or 0
		if type(v) == 'number' or v == false then
			s['__nr_'..t] = v or nil
		end
		return ov
	end;
	__renam = function(s, new)
		local oo = std.objects
		if new == s.nam then
			return
		end
		if oo[new] then
			std.err ("Duplicated obj name: "..std.tostr(new), 2)
		end
		oo[s.nam] = nil
		oo[new] = s
		rawset(s, 'nam', new)
		return s
	end;
	__ini = function(s, ...)
		for _, v in pairs(s) do
			if std.is_obj(v, 'list') then
				v:__ini(s)
			end
		end
		for _, v in pairs(s.__ro) do
			if std.is_obj(v, 'list') then
				v:__ini(s)
			end
		end
		return std.call(s, 'ini', ...)
	end;
	inroom = function(s, r)
		local rooms = r or {}
		local ww = {}
		local o
		if type(rooms) ~= 'table' then
			std.err("Wrong argument to room: "..std.tostr(r), 2)
		end
		s:where(ww)
		while #ww > 0 do
			local nww = {}
			for _, v in ipairs(ww) do
				if std.is_obj(v, 'room') then
					if not o then
						o = v
					end
					table.insert(rooms, v)
				else
					v:where(nww)
				end
			end
			ww = nww
		end
		return o
	end;
	__where = function(s) -- lists
		local list = s.__list
		local r = { }
		for i = 1, #list do
			table.insert(r, list[i])
		end
		return r
	end;
	where = function(s, w)
		local list = s.__list
		local r = w or { }
		local o
		if type(r) ~= 'table' then
			std.err("Wrong argument to obj:where: "..std.tostr(w), 2)
		end
		for i = 1, #list do
			local l = list[i]
			local ll = l.__list

			for k = 1, #ll do
				local oo = ll[k]
				if std.is_obj(oo) and oo:lookup(s) then
					o = o or oo
					if o and not w then
						break
					end
					table.insert(r, oo)
				end
			end
		end
		return o
	end;
	purge = function(s)
		local list = s:__where()
		for i = 1, #list do
			list[i]:del(s)
		end
		return s
	end;
	remove = function(s, w)
		if w then
			w = std.ref(w)
			if not w then
				std.err ("Wrong where in obj:remove()", 2)
			end
			local o, l = w:lookup(s)
			if not o then
				return o
			end
			l:del(o)
			return o, { w }
		end
		local where = {}
		s:where(where)
		for i = 1, #where do
			local o = where[i]
			local _, l = o:lookup(s)
			if l then
				l:del(s)
			end
		end
		return s, where
	end;
	close = function(s)
		s.__closed = true
		return s
	end;
	open = function(s)
		s.__closed = nil
		return s
	end;
	closed = function(s)
		return s.__closed or false
	end;
	disable = function(s)
		s.__disabled = true
		return s
	end;
	enable = function(s)
		s.__disabled = nil
		return s
	end;
	disabled = function(s)
		return s.__disabled or false
	end;
	empty = function(s)
		for i = 1, #s.obj do
			local o = s.obj[i]
			if not o:disabled() then
				return false
			end
		end
		return true
	end;
	save = function(s, fp, n)
		if std.debug_save then
			std.dprint("Saving: "..std.nameof(s))
		end
		if s.__dynamic then -- create
			local nn = std.functions[s.__dynamic.fn]
			if not nn then
				std.err("Error while saving dynamic object: "..std.tostr(s), 2)
			end
			local arg = s.__dynamic.arg
			local l = ''
			for i = 1, #arg do
				if arg[i] == s then
					std.err("Error while saving dynamic object: "..std.tostr(s).." Argument is self-obj.", 2)
				end
				l = l .. ', '..std.dump(arg[i], true, true) -- strict, nested
			end
			if type(s.nam) == 'number' then
				l = string.format("std.new(%s%s):__renam(%d)\n", nn, l, s.nam)
			else
				l = string.format("std.new(%s%s)\n", nn, l, s.nam)
			end
			fp:write(l)
		end
		for k, _ in pairs(s.__ro) do
			local o = s.__ro[k]
			if std.dirty(o) then
				local l = string.format("%s%s", n, std.varname(k))
				std.save_var(s[k], fp, l)
			end
		end
		for k, _ in pairs(s.__var) do
			local l = string.format("%s%s", n, std.varname(k))
			std.save_var(s[k], fp, l)
		end
	end;
	display = function(self)
		local d = std.call(self, 'dsc')
		return d
	end;
	__xref = function(self, text, force)
		if type(text) ~= 'string' then
			return
		end

		local nam = self.nam

		if type(nam) == 'number' then
			nam = '# '..std.tostr(nam)
		end
		local rep = false
		local s = std.for_each_xref_outer(text, function(str)
			rep = true
			local s = str:gsub("^{", ""):gsub("}$", "")
			local test = string.gsub(s, '\\?[\\{}'..std.delim..']',
				{ ['{'] = '\001', ['}'] = '\003',
				  [std.delim] = '\002' });
			while true do
				local sub = test:gsub("\001[^\001\003]+\003", "")
				if sub == test then
					break
				end
				test = sub
			end
			local a = test:find('\002', 1, true)
			if not a or test:byte(a) == 1 then -- need to be |
				return '{'..(std.esc(nam)..std.delim..s)..'}'
			end
			return str
		end)
		if not rep and force then -- nothing todo?
			return '{'..(std.esc(nam)..std.delim..s)..'}'
		end
		return s;
	end;
	visible = function(s)
		return not s:disabled()
	end;
	srch = function(s, w)
		local o, l, idx

		if not s:visible() or s:closed() then
			return
		end

		l = s.obj

		o, idx = l:srch(w)

		if o then
			return o, l, idx
		end

		for i = 1, #s.obj do
			local v = s.obj[i]
			o, l, i = v:srch(w)
			if o then
				return o, l, i
			end
		end
	end;
	lookup = function(s, w)
		local l = s.obj
		local o, idx = l:lookup(w)
		if o then
			return o, l, idx
		end
		for i = 1, #s.obj do
			local v = s.obj[i]
			o, l, i = v:lookup(w)
			if o then
				return o, l, i
			end
		end
	end;
	for_each = function(s, fn, ...)
		local r, v
		for i = 1, #s.obj do
			r, v = fn(s.obj[i], ...)
			if r ~= nil then
				return r, v
			end
			if v ~= false then -- recurse
				r, v = s.obj[i]:for_each(fn, ...)
			end
			if r ~= nil then
				return r, v
			end
		end
	end;
	__dump = function(s)
		if not s:visible() or s:closed() then
			return
		end
		return s.obj:__dump(true)
	end;
	lifeon = function(s)
		local game = std.ref 'game'
		game:lifeon(s)
		return s
	end;
	lifeoff = function(s)
		local game = std.ref 'game'
		game:lifeoff(s)
		return s
	end;
	live = function(s)
		local game = std.ref 'game'
		return game:live(s)
	end;
};

function std.var(v)
	if type(v) ~= 'table' then
		std.err("Wrong std.var() argument", 2)
	end
	v.__var_type = true
	return v
end

std.room = std.class({
	__room_type = true;
	from  = function(s)
		return s.__from or s
	end;
	new = function(_, v)
		if type(v) ~= 'table' then
			std.err ("Wrong argument to std.room:"..std.tostr(v), 2)
		end
		if not v.way then
			rawset(v, 'way',  {})
		end
		if type(v.way) ~= 'table' then
			std.err ("Wrong .way attr in object:" .. v.nam, 2)
		end
		v.way = std.list(v.way)
--		v.way:attach(v)
		v = std.obj(v)
		-- std.setmt(v, self)
		return v
	end;
	visited = function(s)
		return s.__visits
	end;
	visits = function(s)
		return s.__visits or 0
	end;
	srch = function(self, w)
		local r, v, i = std.obj.srch(self, w)
		if std.is_obj(r) then
			return r, v, i
		end
		r, v = self.way:lookup(w)
		if not std.is_obj(r) or r:disabled() or r:closed() then
			return
		end
		return r, self.way, v
	end;
	lookup = function(self, w)
		local r, v, i = std.obj.lookup(self, w)
		if std.is_obj(r) then
			return r, v, i
		end
		r, v = self.way:lookup(w)
		if std.is_obj(r) then
			return r, self.way, v
		end
		return
	end;
	scene = function(s)
		local title, dsc
		title = iface:title(std.titleof(s))
		dsc = std.call(s, 'dsc')
		return std.par(std.scene_delim, title or false, dsc)
	end;
	display = function(s)
		local deco = std.call(s, 'decor'); -- static decorations
		return std.par(std.scene_delim, deco or false, s.obj:display())
	end;
	visible = function(s)
		return not s:disabled() and not s:closed()
	end;
	__dump = function(s)
		return s.way:__dump()
	end;
}, std.obj);

std.world = std.class({
	__game_type = true;
	new = function(_, v)
		if type(v) ~= 'table' then
			std.err ("Wrong argument to std.pl:"..std.tostr(v), 2)
		end
		if not v.player then
			v.player = 'player'
		end
		if v.lifes == nil then
			rawset(v, 'lifes', {})
		end
		v.lifes = std.list(v.lifes)
		v = std.obj(v)
		-- std.setmt(v, self)
		return v
	end;
	time = function(s, t)
		local ov = s.__time or 0
		if t ~= nil then
			if type(t) ~= 'number' then
				std.err ("Wrong parameter to game:time: "..stead.tostr(t), 2)
			end
			s.__time = t
		end
		return ov
	end;
	__ini =  function(s, load)
		return s:ini(load)
	end;
	__start = function(s, load)
		std.mod_call('start', load)
		if type(std.rawget(_G, 'start')) == 'function' then
			start(load) -- start after load
		end
		s.__started = true
		if load ~= true then
			if std.game.player.room.__from then -- already entered
				return nil, true
			end
			local r, v = std.game.player:walk(std.game.player.room, false)
			return r, v
		end
	end;
	ini = function(s, load)
		if s.__in_ini then
			return -- break recursion
		end
		s.__in_ini = true std.obj.__ini(s, load) s.__in_ini = false

		s.player = std.ref(s.player) -- init game
		if not s.player then
			std.err ("Wrong player", 2)
		end

		std.for_each_obj(function(v)
			rawset(v, '__list', {}) -- reset all links
		end)

		std.for_each_obj(function(v) -- call ini of all objects
			if v ~= s then
				v:__ini(load)
			end
		end)

		if not std.game then
			if type(std.rawget(_G, 'init')) == 'function' then
				std.__in_init = {}
				init()
				for _, v in ipairs(std.__in_init) do
					v:__ini(load)
				end
				std.__in_init = false
			end
			std.game = s
		end

		local d = std.method(s, 'dsc')
		return std.fmt(d)
	end;
	lifeon = function(s, w, ...)
		if not w then
			s.__lifeoff = nil
			return
		end
		return s.lifes:add(w, ...)
	end;
	lifeoff = function(s, w)
		if not w then
			s.__lifeoff = true
			return
		end
		return s.lifes:del(w)
	end;
	live = function(s, w)
		if not w then
			return not s.__lifeoff
		end
		return s.lifes:lookup(w)
	end;
	set_pl = function(s, w)
		if not std.is_obj(w, 'player') then
			std.err("Wrong parameter to game:set_pl(): "..std.tostr(w), 2)
		end
		s.player = w
		w:need_scene(true)
	end;
	life = function(s)
		local av, vv
		s:events(false, false)
		if s.__lifeoff then
			return
		end
		local ll = {}

		for i = 1, #s.lifes do
			table.insert(ll, s.lifes[i])
		end

		for i = 1, #ll do
			local v, pre, st
			local o = ll[i]
			if not o:disabled() then
				v, st, pre = std.call(o, 'life');
				av, vv = s:events()
				if pre then -- hi-pri msg
					av = std.par(std.space_delim, av or false, v)
				else
					vv = std.par(std.space_delim, vv or false, v)
				end
				s:events(av or false, vv or false)
				if st == false then -- break cycle
					break
				end
			end
		end
	end;
	step = function(s)
		s:life()
		s.__time = s:time() + 1
	end;
	lastdisp = function(s, str)
		local ov = s.__lastdisp
		if str ~= nil then
			s.__lastdisp = str
		end
		return ov
	end;
	display = function(s, state)
		local l, av, pv
		local reaction = s:reaction() or nil
		if state then
			reaction = iface:em(reaction)
			av, pv = s:events()
			av = iface:em(av)
			pv = iface:em(pv)
			l = s.player:look() -- objects [and scene]
		end
		l = std.par(std.scene_delim, reaction or false,
			    av or false, l or false,
			    pv or false) or ''
		return l
	end;
	lastreact = function(s, t)
		local o = s.__lreaction
		if t == nil then
			return o
		end
		s.__lreaction = t or nil
		return o
	end;
	reaction = function(s, t)
		local o = s.__reaction
		if t == nil then
			return o
		end
		s.__reaction = t or nil
		return o
	end;
	events = function(s, av, pv)
		local oa = s.__aevents
		local op = s.__pevents
		if av ~= nil then
			s.__aevents = av or nil
		end
		if pv ~= nil then
			s.__pevents = pv or nil
		end
		return oa, op
	end;
	cmd = function(s, cmd)
		local r, v
		if not std.is_obj(s.player) then
			std.err("Wrong player object.", 2)
		end
		s.player:moved(false)
		s.player:need_scene(false)
		std.abort_cmd = false
		r, v = std.mod_call('cmd', cmd)
			-- luacheck: push ignore
		if r ~= nil or v ~= nil then
			-- luacheck: pop
		elseif cmd[1] == nil or cmd[1] == 'look' then
			if not s.__started then
				r, v = s:__start()
			else
				s.player:need_scene(true)
				v = true
			end
--			r, v = s.player:look()
		elseif cmd[1] == 'act' then
			if #cmd < 2 then
				return nil, false
			end
			local o = std.ref(cmd[2]) -- on what?
			if std.is_system(o) then
				local a = {}
				for i = 3, #cmd do
					table.insert(a, cmd[i])
				end
				r, v = std.call(o, 'act', std.unpack(a))
			else
				o = s.player:inspect(o)
				if not o then
					return nil, false -- wrong input
				end
				r, v = s.player:take(o)
				if not r and not v then
					r, v = s.player:action(o)
				end
			end
			-- if s.player:search(o)
		elseif cmd[1] == 'use' then
			if #cmd < 2 then
				return nil, false
			end
			local o1 = std.ref(cmd[2])
			local o2 = std.ref(cmd[3])
			o1 = s.player:srch(o1)

			if not o1 then
				return nil, false -- wrong input
			end
			if o1 == o2 or not o2 then -- inv?
				if not o1 then
					return nil, false -- wrong input
				end
				r, v = s.player:useit(o1)
			else
				r, v = s.player:useon(o1, o2)
			end
		elseif cmd[1] == 'go' then
			if #cmd < 2 then
				return nil, false
			end
			local o = std.ref(cmd[2])
			if not o then
				return nil, false -- wrong input
			end
			r, v = s.player:go(o)
		elseif cmd[1] == 'inv' then -- show inv
			r = s.player:__dump() -- just info
			v = nil
		elseif cmd[1] == 'way' then -- show ways
			r = s.player:where():__dump()
			v = nil
		elseif cmd[1] == 'save' then -- todo
			if #cmd < 2 then
				return nil, false
			end
			r = std:save(cmd[2])
			v = true
			std.abort()
		elseif cmd[1] == 'load' then -- todo
			if #cmd < 2 then
				return nil, false
			end
			r = std:load(cmd[2])
			v = true
			std.abort()
		end
		if r == nil and v == nil then
			v = false -- no reaction
		end

		if v == false or std.abort_cmd then
			if cmd[1] == 'save' then
				std.mod_call('step', nil)
			else
				std.mod_call('step', v)
			end
			return r, v
		end
-- v is true or nil
		s = std.game -- after reset game is recreated
		s:reaction(r or false)

		if v then
			s:step()
		end
		std.mod_call('step', v)
		r = s:display(v)
		if v then
			s:lastreact(s:reaction() or false)
			s:lastdisp(r)
		end
		return r, v
	end;
	aftertak = function(s, w) -- standard take action
		w = std.ref(w)
		local o = w:remove()
		s.player:inventory():add(o)
	end;
}, std.obj);

std.player = std.class ({
	__player_type = true;
	new = function(_, v)
		if type(v) ~= 'table' then
			std.err ("Wrong argument to std.pl:"..std.tostr(v), 2)
		end
		if not v.room then
			v.room = 'main'
		end
		v = std.obj(v)
		-- std.setmt(v, self)
		return v
	end;
	__ini = function(s, ...)
		s.room = std.ref(s.room)
		if not s.room then
			std.err ("Wrong player location: "..std.tostr(s), 2)
		end
		std.obj.__ini(s, ...)
	end;
	moved = function(s, v)
		local ov = s.__moved or false
		if v == nil then
			return ov
		end
		if type(v) ~= 'boolean' then
			std.err("Wrong parameter to player:moved: "..std.tostr(v), 2)
		end
		if v == false then v = nil end
		s.__moved = v
		return ov
	end;
	need_scene = function(s, v)
		local ov = s.__need_scene or false
		if v == nil then
			return ov
		end
		if type(v) ~= 'boolean' then
			std.err("Wrong parameter to player:need_scene: "..std.tostr(v), 2)
		end
		if v == false then v = nil end
		s.__need_scene = v
		return ov
	end;
	look = function(s)
		local scene
		local r = s:where()
		if s:need_scene() then
			scene = r:scene()
		end
		return std.par(std.scene_delim, scene or false, r:display())
	end;
	inspect = function(s, w)
		local r, v, i
		r, v, i = s:where():srch(w)
		if r ~= nil then
			return r, v, i
		end
		r, v, i = s:srch(w)
		if r ~= nil then
			return r, v, i
		end
		return
	end;
	lookup = function(self, w)
		local r, v, i = std.obj.lookup(self, w)
		if std.is_obj(r) then
			return r, v, i
		end
		r, v = self:inventory():lookup(w)
		if std.is_obj(r) then
			return r, self:inventory(), v
		end
		return
	end;
	have = function(s, w)
		local o, i = s:inventory():lookup(w)
		if not o then
			return o, i
		end
		if o:disabled() then
			return
		end
		return o, i
	end;
	useit = function(s, w, ...)
		return s:call('inv', w, ...)
	end;
	useon = function(s, w1, w2)
		w1 = std.ref(w1)
		w2 = std.ref(w2)

		if w2 and w1 ~= w2 then
			return s:call('use', w1, w2)
		end
		-- inv mode?
		return s:call('inv', w1, w2)
	end;
	call = function(_, m, w1, w2, ...)
		local w
		if type(m) ~= 'string' then
			std.err ("Wrong method in player.call: "..std.tostr(m), 2)
		end

		w = std.ref(w1)
		if not std.is_obj(w) then
			std.err ("Wrong parameter to player.call: "..std.tostr(w1), 2)
		end

		local r, v, t, _
		r, v = std.call(std.ref 'game', 'on'..m, w, w2, ...)
		t = std.par(std.scene_delim, false, r)
		if v == false then
			return t or r, true, false
		end

		if m == 'use' and w2 then
			r, v = std.call(w2, 'used', w, ...)
			t = std.par(std.scene_delim, t or false, r)
			if v == true then -- false from used --> pass to use
				w2['__nr_used'] = (w2['__nr_used'] or 0) + 1
				r, _ = std.call(std.ref 'game', 'afteruse', w, w2, ...)
				t = std.par(std.scene_delim, t or false, r)
				return t or r, true -- stop chain
			end
		end

		r, v = std.call(w, m, w2, ...)
		t = std.par(std.scene_delim, t or false, r)
		if v == true then
			w['__nr_'..m] = (w['__nr_'..m] or 0) + 1
			r, _ = std.call(std.ref 'game', 'after'..m, w, w2, ...)
			t = std.par(std.scene_delim, t or false, r)
			return t or r, true
		end
		if r == true and v == false then
			return r, v -- menu hack
		end
		if not v and not r then -- no reaction
			r, v = std.call(std.ref 'game', m, w, w2, ...)
			t = std.par(std.scene_delim, t or false, r)
			if not v then
				return
			end
		end
		return t or r, true
	end;
	action = function(s, w, ...)
		return s:call('act', w, ...)
	end;
	inventory = function(s)
		return s.obj
	end;
	take = function(s, w, ...)
		return s:call('tak', w, ...)
	end;
	walkin = function(s, w, ...)
		return s:walk(w, false, true, ...)
	end;
	walkout = function(s, w, ...)
		if w == nil then
			w = s:where():from()
		end
		return s:walk(w, true, false, ...)
	end;
	walk = function(s, w, doexit, doenter, dofrom)
		local noexit = (doexit == false)
		local noenter = (doenter == false)
		local nofrom = (dofrom == false)
		local moved = s:moved()
		if moved then
			s:moved(false)
		end
		local ww = w
		w = std.ref(w)
		if not w then
			std.err("Wrong parameter to walk: "..std.tostr(ww))
		end

--		if w == std.here() then -- nothing todo
--			return
--		end

		local inwalk = w

		local r, v, t, _
		local f = s:where()

		r, v = std.call(std.ref 'game', 'onwalk', f, inwalk)

		t = std.par(std.scene_delim, false, r)

		if v == false or s:moved() then -- stop walk
			if not s:moved() then s:moved(moved) end
			return t, true
		end

		r, v = std.call(inwalk, 'onwalk', f)

		t = std.par(std.scene_delim, t or false, r)

		if v == false or s:moved() then -- stop walk
			if not s:moved() then s:moved(moved) end
			return t, true
		end

		if not noexit and not s.__in_onexit then
			s.__in_onexit = true
			r, v = std.call(s:where(), 'onexit', inwalk)
			s.__in_onexit = false
			t = std.par(std.scene_delim, t or false, r)
			if v == false or s:moved() then
				if not s:moved() then s:moved(moved) end
				return t, true
			end
		end

		if not noenter then
			r, v = std.call(inwalk, 'onenter', s:where())
			t = std.par(std.scene_delim, t or false, r)
			if v == false or s:moved() then
				if not s:moved() then s:moved(moved) end
				return t, true
			end
		end

		if not noexit and not s.__in_exit then
			s.__in_exit = true
			r, _ = std.call(s:where(), 'exit', inwalk)
			s.__in_exit = false
			t = std.par(std.scene_delim, t or false, r)
			if s:moved() then
				return t, true
			end
		end
		-- enter is done
		s.room = inwalk
		if not nofrom and (f ~= inwalk or not s.room.__from) then -- brake self-recursion
			s.room.__from = f
		end
		if not noenter then
			r, _ = std.call(inwalk, 'enter', f)
			t = std.par(std.scene_delim, t or false, r)
			if s:moved() then
				return t, true
			end
		end
		s:where().__visits = (s:where().__visits or 0) + 1
		s:need_scene(true)
		s:moved(true)
		if not s.__in_afterwalk then
			s.__in_afterwalk = true
			r, _ = std.call(std.ref 'game', 'afterwalk', f, inwalk)
			s.__in_afterwalk = false
			t = std.par(std.scene_delim, t or false, r)
		end
		return t, true
	end;
	go = function(s, w)
		local r
		r = s:where():srch(w)
		if not std.is_obj(r, 'room') then
			return nil, false
		end
		return s:walk(w)
	end;
	where = function(s, where)
		if type(where) == 'table' then
			table.insert(where, std.ref(s.room))
		end
		return std.ref(s.room)
	end;
}, std.obj);

-- merge strings with "space" as separator
std.par = function(space, ...)
	local res
	local a = { ... };
	for i = 1, #a do
		if type(a[i]) == 'string' then
			if res == nil then
				res = ""
			else
				res = res .. space;
			end
			res = res .. a[i];
		end
	end
	return res;
end
-- add to not nill string any string
std.cat = function(v,...)
	if not v then
		return nil
	end
	if type(v) ~= 'string' then
		std.err("Wrong parameter to std.cat: "..std.tostr(v), 2);
	end
	local a = { ... }
	for i = 1, #a do
		if type(a[i]) == 'string' then
			v = v .. a[i];
		end
	end
	return v;
end

std.cctx = function()
	return std.call_ctx[std.call_top];
end

std.callpush = function(v)
	std.call_top = std.call_top + 1;
	std.call_ctx[std.call_top] = { txt = nil, self = v };
end

std.callpop = function()
	std.call_ctx[std.call_top] = nil;
	std.call_top = std.call_top - 1;
	if std.call_top < 0 then
		std.err ("callpush/callpop mismatch")
	end
end

std.pclr = function()
	std.cctx().txt = nil
end

std.pget = function()
	return std.cctx().txt;
end

std.pr = function(...)
	local a = {...}
	if std.cctx() == nil then
		error ("Call from global context.", 2);
	end
	for i = 1, #a do
		std.cctx().txt = std.par('', std.cctx().txt or false, std.tostr(a[i]));
	end
--	std.cctx().txt = std.cat(std.cctx().txt, std.space_delim);
end

std.p = function(...)
	std.pr(...)
	std.cctx().txt = std.cat(std.cctx().txt, std.space_delim);
end

std.pn = function(...)
	std.pr(...)
	std.cctx().txt = std.cat(std.cctx().txt, '^');
end

std.pf = function(fmt, ...)
	if type(fmt) ~= 'string' then
		std.err("Wrong argument to std.pf: "..std.tostr(fmt))
	end
	std.pr(string.format(fmt, ...))
end

function std.strip(s)
	if type(s) ~= 'string' and type(s) ~= 'number' then
		return
	end
	s = tostring(s)
	s = s:gsub("^[ \t]*", ""):gsub("[ \t]*$", "")
	return s
end

function std.join(a, sep)
	sep = sep or ' '
	local rc
	for i = 1, #a do
		if type(a[1]) == 'string' then
			rc = (rc and rc .. sep or '') .. a[i]
		end
	end
	return rc
end

function std.split(s, separator)
	local sep, fields = separator or " ", {}
	local pattern = string.format("([^%s]+)", sep)
	if type(s) ~= 'string' and type(s) ~= 'number' then
		return fields
	end
	s = tostring(s)
	s:gsub(pattern, function(c) fields[#fields+1] = std.strip(c) end)
	return fields
end

function std.esc(s, sym)
	sym = sym or std.delim
	if type(s) ~= 'string' then return s end
	s = s:gsub("\\?["..sym.."]", { [sym] = '\\'..sym, ['\\'..sym] = '\\\\'..sym})
	return s
end

function std.unesc(s, sym)
	sym = sym or std.delim
	s = s:gsub("\\?[\\"..sym.."]", { ['\\'..sym] = sym, ['\\\\'] = '\\' })
	return s
end

function std.is_proxy(t)
	return (type(t) == 'userdata') or (type(t) == 'table' and t.__proxy_type)
end

local function __dump(t, strict, nested)
	local rc = '';
	if type(t) == 'string' then
		rc = string.format("%q", t):gsub("\\\n", "\\n")
	elseif type(t) == 'number' then
		rc = std.tostr(t)
	elseif type(t) == 'boolean' then
		rc = std.tostr(t)
	elseif type(t) == 'function' then
		if std.functions[t] then
			local k = std.functions[t]
			return string.format("%s", k)
		elseif strict then
			std.err("Can not save undeclared function", 2)
		end
	elseif type(t) == 'table' and not t.__visited then
		if std.tables[t] and nested then
			local k = std.tables[t]
			return string.format("%s", k)
		elseif std.is_obj(t) then
			local d = std.deref(t)
			if type(d) == 'number' then
				rc = string.format("std(%d)", d)
			elseif type(d) == 'string' then
				rc = string.format("std %q", d)
			end
			return rc
		end
		if strict and std.getmt(t) then
			std.err("Can not save classes", 2)
		end
		t.__visited = true
		local nkeys = {}
		local keys = {}
		for k, v in pairs(t) do
			if strict and type(k) ~= 'number' and type(k) ~= 'string' then
				std.err("Wrong key type in table: "..type(k), 2)
			end
			if type(k) ~= 'string' or k:find("__", 1, true) ~= 1 then
				if (type(v) ~= 'function' or std.functions[v]) and not std.is_proxy(v) then
					if type(k) == 'number' then
						table.insert(nkeys, { key = k, val = v })
					elseif type(k) == 'string' then
						table.insert(keys, { key = k, val = v })
					end
				elseif strict then
					std.err("Can not save table item ("..std.tostr(k)..") with type: "..type(v), 2)
				end
			end
		end
		table.sort(nkeys, function(a, b) return a.key < b.key end)
		rc = "{ "
		local n, v
		for k = 1, #nkeys do
			v = nkeys[k]
			if v.key == k then
				rc = rc .. __dump(v.val, strict, true)..", "
			else
				n = k
				break
			end
		end
		if n then
			for k = n, #nkeys do
				v = nkeys[k]
				rc = rc .. "["..std.tostr(v.key).."] = "..__dump(v.val, strict, true)..", "
			end
		end
		for k = 1, #keys do
			v = keys[k]
			if type(v.key) == 'string' then
				if v.key:find("^[a-zA-Z_]+[a-zA-Z0-9_]*$") and not lua_keywords[v.key] then
					rc = rc .. v.key .. " = "..__dump(v.val, strict, true)..", "
				else
					rc = rc .. "[" .. string.format("%q", v.key) .. "] = "..__dump(v.val, strict, true)..", "
				end
			else
				rc = rc .. std.tostr(v.key) .. " = "..__dump(v.val, strict, true)..", "
			end
		end
		rc = rc:gsub(",[ \t]*$", "") .. " }"
	elseif type(t) == 'table' then -- visited!
		std.err("Can not save table with cross-references.", 2)
	end
	return rc
end

local function cleardump(t)
	if type(t) ~= 'table' or not t.__visited then
		return
	end
	t.__visited = nil
	for _, v in pairs(t) do
		cleardump(v)
	end
end

function std.dump(t, strict, nested)
	local rc = __dump(t, strict, nested)
	cleardump(t)
	return rc
end

local function clone(src)
	if type(src) ~= 'table' then return src end
	if std.is_obj(src) then return src end
	if src.__visited then
		std.err("Recursive tables not supported by std.clone")
	end
	src.__visited = true
	local dst = {}
	for k, _ in pairs(src) do
		if k ~= '__visited' then
			dst[std.clone(k)] = clone(src[k])
		end
	end
	return dst
end

function std.clone(src)
	cleardump(src)
	local t = clone(src)
	cleardump(src)
	return t
end

function std.new(fn, ...)
	if not std.game then
		std.err ("You can not use new() from global context.", 2)
	end
	if type(fn) ~= 'function' then
		std.err ("Wrong parameter to std.new", 2)
	end
	if not std.functions[fn] then
		std.err ("Function is not declared in 1-st argument of std.new", 2)
	end
	local arg = std.clone({...})

	local o = in_section ('new', function() return fn(std.unpack(arg)) end)

	if type(o) ~= 'table' then
		std.err ("Constructor did not return object:"..std.functions[fn], 2)
	end
	rawset(o, '__dynamic', { fn = fn, arg = {...} })
	if std.game then
		o:__ini() -- do initialization
	end
	return o
end

function std.delete(s)
	s = std.ref(s)
	if std.is_obj(s) then
		if type(s.nam) == 'number' and not s.__dynamic then -- static objects
			std.objects[s.nam] = false
		else
			std.objects[s.nam] = nil
		end
	else
		std.err("Delete non object table", 2)
	end
end

function std.nameof(o)
	o = std.ref(o)
	if not std.is_obj(o) then
		std.err("Wrong parameter to std.nameof: "..std.tostr(o), 2)
		return
	end
	return o.nam
end
function std.dispof(o)
	o = std.ref(o)
	if not std.is_obj(o) then
		std.err("Wrong parameter to std.dispof", 2)
		return
	end
	if o.disp ~= nil then
		local d = std.call(o, 'disp')
		return d
	end
	if o.__autoname then
		if std.is_tag(o.tag) then
			o = o.tag:sub(2)
			return o
		end
		if type(o.nam) == 'number' then
			return std.tostr(o.nam)
		end
		std.err("No nam nor disp are specified for obj: "..std.tostr(o.nam), 2)
	end
	return o.nam
end

function std.titleof(o)
	o = std.ref(o)
	if not std.is_obj(o) then
		std.err("Wrong parameter to std.titleof", 2)
		return
	end
	if o.title ~= nil then
		return std.call(o, 'title')
	end
	return std.dispof(o)
end

function std.ref(o)
	if type(o) == 'table' then
		if not std.is_obj(o) then
			std.err("Reference to wrong object: "..std.tostr(o), 2)
		end
		return o
	end
	local oo = std.objects
	if oo[o] then
		return oo[o]
	end
	if std.noerror then
		std.dprint("Reference to non-existing object: ", std.tostr(o))
		return {} -- give fake object
	end
end

function std.deref(o)
	if std.is_obj(o) then
		return o.nam
	elseif std.ref(o) then
		return o
	end
end

std.method = function(v, n, ...)
	if type(v) ~= 'table' then
		std.err ("Call on non table object:"..std.tostr(n), 2);
	end
	if v[n] == nil then
		return
	end
	if type(v[n]) == 'string' then
		return v[n], true;
	end
	if type(v[n]) == 'function' then
		std.callpush(v, ...)
		local c
		local a, b = v[n](v, ...);
		c = b
		if b == nil and (type(a) == 'boolean' or a == nil) then
			a, b = std.pget(), a
			c = b
		end
		if b == nil then
			b = true -- the fact of call
		end
		std.callpop()
		return a, b, c
	end
	if type(v[n]) == 'boolean' or type(v[n]) == 'table' then
		return v[n], true
	end
	std.err ("Method not string nor function:"..std.tostr(n), 2);
end

std.call = function(o, n, ...)
	if type(o) ~= 'table' then
		std.err("Call on non table object: "..std.tostr(n), 2)
	end
	local r, v, c = std.method(o, n, ...)
	if std.strip_call and type(r) == 'string' then
		r = r:gsub("^[%^\n\r\t ]+", "") -- extra heading ^ and spaces
		r = r:gsub("[%^\n\r\t ]+$", "") -- extra trailing ^ and spaces
		return r, v, c
	end
	return r or nil, v, c
end

local function get_token(inp)
	local q, k
	local rc = ''
	k = 1
	if inp:sub(1, 1) == '"' then
		q = true
		k = k + 1
	end
	while true do
		local c = inp:sub(k, k)
		if c == '' then
			if q then
				return nil -- error
			end
			break
--			return rc, k
		end
		if c == '"' and q then
			k = k + 1
			break
		end
		if not q and (c == ' ' or c == ',' or c == '\t') then
			break
		end
		if q and c == '\\' then
			k = k + 1
			c = inp:sub(k, k)
			rc = rc .. c
		else
			rc = rc .. c
		end
		k = k + 1
	end
	if not q then
		if std.tonum(rc) then
			rc = std.tonum(rc)
		elseif rc == 'true' then
			rc = true
		elseif rc == 'false' then
			rc = false
		end
	end
	return rc, k
end

local function cmd_parse(inp)
	local cmd = {}
	if type(inp) ~= 'string' then
		return false
	end
	if inp:find("^save[ \t]+") then
		cmd[1] = 'save'
		cmd[2] = inp:gsub("^save[ \t]+", "")
		return cmd
	elseif inp:find("^load[ \t]+") then
		cmd[1] = 'load'
		cmd[2] = inp:gsub("^load[ \t]+", "")
		return cmd
	end
	inp = inp:gsub("[ \t]*$", "")
	while true do
		inp = inp:gsub("^[ ,\t]*","")
		local v, i = get_token(inp)
		if v == nil or v == '' then
			break
		end
		inp = inp:sub(i)
		table.insert(cmd, v)
	end
	return cmd
end

std.cmd_parse = cmd_parse

function std.me()
	return std.ref(std.ref 'game'.player)
end

function std.here()
	return std.ref(std.me().room)
end

function std.cacheable(n, f)
	return function(...)
		local s = std.cache[n]
		if s ~= nil then
			if s == -1 then s = nil end
			return s
		end
		std.cache[n] = -1
		s = f(...)
		if s ~= nil then
			std.cache[n] = s
		end
		return s
	end
end

local iface = std.obj {
	nam = '@iface';
	cmd = function(_, inp)
		local cmd = std.cmd_parse(inp)
		if std.debug_input then
			std.dprint("* input: ", inp)
		end
		if not cmd then
			return "Error in cmd arguments", false
		end

		std.cmd = cmd
		std.cache = {}
		local r, v = std.ref 'game':cmd(cmd)
		if r == true and v == false then
			return nil, true -- hack for menu mode
		end
		r = iface:fmt(r, v) -- to force fmt
		if std.debug_output then
			std.dprint("* output: ", r, v)
		end
		return r, v
	end;
	xref = function(_, str, obj, ...)
		obj = std.ref(obj)
		if not obj then
			return str
		end
		local a = { ... }
		local args = ''
		for i = 1, #a do
			if type(a[i]) ~= 'string' and type(a[i]) ~= 'number' and type(a[i]) ~= 'boolean' then
				std.err ("Wrong argument to iface:xref: "..std.tostr(a[i]), 2)
			end
			args = args .. ' '..std.dump(a[i])
		end
		local xref = std.string.format("%s%s", std.deref_str(obj), args)
		return std.cat(str, "("..xref..")");
	end;
	title = function(_, str)
		return "[ "..std.tostr(str).." ]"
	end;
	raw_mode = function(s, v)
		local ov = s.__raw
		if v ~= nil then
			s.__raw = v or nil
		end
		return ov
	end;
	fmt = function(self, str, state)
		if self:raw_mode() or type(str) ~= 'string' then
			return str
		end
		str = std.fmt(str, std.format, state)

		return std.cat(str, '\n')
	end;
	em = function(_, str)
		return str
	end;
};

local function fmt_stub(_, str)
	return str
end

iface.em = fmt_stub
iface.center = fmt_stub
iface.just = fmt_stub
iface.left = fmt_stub
iface.right = fmt_stub
iface.bold = fmt_stub
iface.top = fmt_stub
iface.bottom = fmt_stub
iface.middle = fmt_stub
iface.nb = fmt_stub
iface.anchor = fmt_stub
iface.img = fmt_stub
iface.imgl = fmt_stub
iface.imgr = fmt_stub
iface.under = fmt_stub
iface.st = fmt_stub
iface.tab = fmt_stub
iface.y = function() return '' end

function std.loadmod(f)
	if std.game and not std.__in_gamefile then
		std.err("Use loadmod() only in global context", 2)
	end
	if type(f) ~= 'string' then
		std.err("Wrong argument to loadmod(): "..std.tostr(f), 2)
	end
	if not f:find("%.lua$") then
		f = f .. '.lua'
	end
	if not std.modules[f] then
		std.modules[f] = true
		std.dofile(f)
	end
end

function std.include(f)
	if std.game and not std.__in_gamefile then
		std.err("Use include() only in global context", 2)
	end
	if type(f) ~= 'string' then
		std.err("Wrong argument to include(): "..std.tostr(f), 2)
	end
	if not f:find("%.lua$") then
		f = f .. '.lua'
	end
	if not std.includes[f] then
		std.includes[f] = true
		in_section('include', function()
			std.dofile(f)
		end)
	end
end

function std.abort()
	std.abort_cmd = true
end

function std.nop()
	std.abort()
	if std.cctx() then
		std.pr(std.game:lastdisp())
	end
	return std.game:lastdisp(), true
end

-- require "ext/gui"
require "declare"
require "dlg"
require "stdlib"
