instead-unittest/parser/mplib.lua

3089 lines
54 KiB
Lua

--luacheck: globals mp
--luacheck: no self
local tostring = std.tostr
--- Error handler
-- @param err error code
function mp:err(err)
local parsed = false
if self:comment() then
return
end
if std.here().OnError then
std.here():OnError(err)
return
end
if err == "UNKNOWN_VERB" then
local verbs
if mp.errhints then
verbs = self:lookup_verb(self.words, true)
end
local hint = false
if verbs and #verbs > 0 then
for _, verb in ipairs(verbs) do
local fixed = verb.verb[verb.word_nr]
if verb.verb_nr == 1 then
hint = true
p (mp:mesg 'UNKNOWN_VERB', " ", iface:em(self.words[verb.verb_nr]), ".")
p (mp:mesg 'UNKNOWN_VERB_HINT', " ", iface:em(fixed.word .. (fixed.morph or "")), "?")
break
end
end
end
if not hint then
p (mp:mesg('UNKNOWN_VERB') or "Unknown verb:",
" ", iface:em(self.words[1]), ".")
end
elseif err == "EMPTY_INPUT" then
p (mp:mesg('EMPTY') or "Empty input.")
elseif err == "INCOMPLETE" or err == "UNKNOWN_WORD" then
local need_noun
local second_noun
for _, v in ipairs(self.hints) do
local verb = ''
if self.hints.match then
for _, vv in pairs(self.hints.match.verb) do
verb = verb .. vv .. ' '
end
verb = verb:gsub(" $", "")
for _, vv in pairs(self.hints.match.args) do
if vv.word then
verb = verb .. ' '.. vv.word
end
if vv.ob then
second_noun = true
end
end
if not parsed then
parsed = verb
end
if second_noun then second_noun = verb end
end
if v:find("^~?{noun}") then need_noun = v break end
end
if #self.unknown > 0 and (not need_noun or
self.unknown.lev == self.hints.lev) then
local unk = ''
for _, v in ipairs(self.unknown) do
if unk ~= '' then unk = unk .. ' ' end
unk = unk .. v
end
if need_noun then
if mp.errhints then
mp:message('UNKNOWN_OBJ', unk);
else
mp:message('UNKNOWN_OBJ');
end
else
if mp.errhints then
mp:message('UNKNOWN_WORD', unk);
else
mp:message('UNKNOWN_WORD');
end
end
if mp:thedark() and need_noun then
mp:message 'UNKNOWN_THEDARK'
return
end
if need_noun then
if #self.hints == 0 or not self.hints.fuzzy then
return
end
end
elseif err == "UNKNOWN_WORD" then
mp:message('UNKNOWN_WORD')
else
if need_noun then
if second_noun then
p (mp:mesg('INCOMPLETE_SECOND_NOUN') or mp:mesg('INCOMPLETE_NOUN'),
" \"", second_noun, " ", mp:err_noun(need_noun), "\"?")
elseif parsed then
p (mp:mesg('INCOMPLETE_NOUN'), " \"", parsed, "\"?")
else
p (mp:mesg('INCOMPLETE_NOUN'), "?")
end
else
mp:message 'INCOMPLETE'
end
end
if not mp.errhints then
return
end
local words = {}
local dups = {}
for _, v in ipairs(self.hints) do
if v:find("^~?{noun}") or v == '*' then
v = mp:err_noun(v)
if not dups[v] and not need_noun then
table.insert(words, v)
dups[v] = true
end
else
local pat = self:pattern(v)
for _, vv in ipairs(pat) do
if not vv.hidden and not dups[vv.word] then
table.insert(words, vv.word)
dups[vv.word] = true
end
end
end
-- if need_noun then
-- break
-- end
end
if #words > 0 then
if err == 'INCOMPLETE' then
if #words > 2 and parsed then
parsed = parsed .. ': '
end
p (mp:mesg 'HINT_WORDS', ", ", parsed or '')
else
p (mp:mesg 'HINT_WORDS', " ")
end
end
for k, v in ipairs(words) do
if k ~= 1 then
if k == #words then
pr (" ", mp.msg.HINT_OR, " ")
else
pr (", ")
end
end
pr(iface:em(v))
end
if #words > 0 then
p "?"
end
elseif err == "MULTIPLE" then
pr (mp:mesg 'MULTIPLE', " ", self.multi[1])
for k = 2, #self.multi do
if k == #self.multi then
pr (" ", mp.msg.HINT_AND, " ", self.multi[k])
else
pr (", ", self.multi[k])
end
end
pr "."
elseif err then
pr (err)
end
end
local everything = std.obj {
nam = '@all';
hint_noun = false;
before_Any = function(_, ev)
if ev == 'Exam' then
mp:xaction("Look")
return
end
if not mp.expert_mode or
(ev ~= 'Drop' and ev ~= 'Take' and ev ~= 'Remove') then
mp:message 'NO_ALL'
return
end
return false
end;
}:attr 'concealed':persist()
--- Clear the metaparser window
function mp:clear()
self.text = ''
end
--- Clear the metaparser prompt
-- @see mp.clear_on_move
function mp:cls_prompt()
if std.call_ctx[1] then
std.call_ctx[1].txt = ''
end
end
--- Standard door class
mp.door = std.class({
before_Walk = function(s)
return s:before_Enter();
end;
before_Enter = function(s)
if mp:check_inside(s) then
return
end
if not s:has 'open' then
local t = std.call(s, 'when_closed')
if t then
p(t)
else
mp:message 'Enter.DOOR_CLOSED'
end
return
end
local r, v = mp:runorval(s, 'door_to')
if not v then
mp:message 'Enter.DOOR_NOWHERE'
return
end
if r then
if not mp:move(std.me(), r) then
return true
end
end
return v
end;
}, std.obj):attr 'enterable,openable,door'
local function pnoun(noun, m, ...)
local ctx = mp:save_ctx()
mp.first = noun
mp.first_hint = noun:gram().hint
mp:message(m, ...)
mp:restore_ctx(ctx)
end
mp.cutscene =
std.class({
enter = function(s)
s.__num = 1
end;
ini = function(s)
std.rawset(s, 'text', s.text)
std.rawset(s.__var, 'text', nil)
if not s.__num then
s.__num = 1
end
end;
title = false;
nouns = function() return {} end;
dsc = function(s)
if type(s.text) == 'function' then
local t = std.call(s, 'text', s.__num)
if not t then
s:Next(true)
end
p (t)
else
if type(s.text) == 'string' then
p (s.text)
else
p (s.text[s.__num])
end
end
if mp.msg.CUTSCENE_MORE then
p("^", mp.msg.CUTSCENE_MORE)
end
end;
OnError = function(_, _) -- s, err
mp:message 'CUTSCENE_HELP'
end;
Next = function(s, force)
if game:time() == 0 then
return
end
s.__num = s.__num + 1
if force or type(s.text) == 'string' or (type(s.text) == 'table' and s.__num > #s.text) then
local r, v = mp:runorval(s, 'next_to')
if r then
walk(r)
elseif v == false then
walkback()
end
return
end
s:dsc()
end;
}, std.room):attr'cutscene'
mp.gameover =
std.class({
before_Default = function()
mp:message 'GAMEOVER_HELP';
end;
before_Look = function()
mp:message 'GAMEOVER_HELP';
end;
OnError = function()
mp:message 'GAMEOVER_HELP';
end;
}, std.room)
-- player
mp.msg.Look = {}
function std.obj:multi_alias(n)
if n then
self.__word_alias = n
end
return self.__word_alias
end
std.room.dsc = function(_)
mp:message 'SCENE';
end
local function trace_light(v)
if v:has 'light' then
return true
end
if v:has 'container' and not v:has 'transparent' and not v:has 'open' then
return nil, false
end
end
--- Check if the player or the specified object is in darkness
-- @param what check if this is in darkness; player by default
function mp:thedark(what)
if std.me():has'light' or mp:traceinside(std.me(), trace_light) then
return false
end
local w = what or std.me():where()
local h = mp:light_scope(w)
if h:has'light' then return false end
return not mp:traceinside(h, trace_light)
end
function std.obj:scene()
local s = self
local sc = mp:visible_scope(s)
local title = iface:title(std.titleof(sc))
if s ~= sc then
local r = std.call(std.me():where(), "title")
title = title .. ' '..mp.fmt("(".. (r or mp:mesg('TITLE_INSIDE')) .. ")")
end
return title
end
std.room.scene = std.obj.scene
local owalk = std.player.walk
std.obj.from = std.room.from
function std.player:walk(w, doexit, doenter, dofrom)
w = std.object(w)
if std.is_obj(w, 'room') then
if w == std.here() then
self.__room_where = false
self:need_scene(true)
return nil, true
end
local r, v = owalk(self, w, doexit, doenter, dofrom)
if mp.clear_on_move and player_moved() then
mp:cls_prompt()
end
self.__room_where = false
return r, v
end
if std.is_obj(w) then -- into object
if dofrom ~= false and std.me():where() ~= w then
w.__from = std.me():where()
end
if w:inroom() == std.ref(self.room) then
self.__room_where = w
self:need_scene(true)
return nil, true
end
local r, v = owalk(self, w:inroom(), doexit, doenter, dofrom)
if mp.clear_on_move and player_moved() then
mp:cls_prompt()
end
self.__room_where = w
return r, v
end
std.err("Can not enter into: "..std.tostr(w), 2)
end
function std.player:walkout(w, ...)
if w == nil then
w = self:where():from()
end
return self:walk(w, true, false, ...)
end;
std.player.where = function(s, where)
local inexit = s.__in_onexit or s.__in_exit
local inwalk = (s.__room_where and s.__room_where:inroom() ~= std.here())
if inexit or inwalk then -- fallback to room
if type(where) == 'table' then
table.insert(where, std.ref(s.room))
end
return std.ref(s.room)
end
if type(where) == 'table' then
table.insert(where, std.ref(s.__room_where or s.room))
end
return std.ref(s.__room_where or s.room)
end
std.room.display = function(s)
local c = std.call(mp, 'content', s)
return c
end
function mp:light_scope(s)
local h = s
if not s:has 'container' or s:has 'transparent' or s:has 'open' then
mp:trace(s, function(v)
h = v
if v:has 'container' and not v:has'transparent' and not v:has 'open' then
return nil, false
end
end)
end
return h
end
--- Find the maximum visible scope for an object
-- @param s where
function mp:visible_scope(s)
local h = s
if s:has 'transparent' or s:has 'supporter' then
mp:trace(s, function(v)
h = v
if not v:has'transparent' and not v:has'supporter' then
return nil, false
end
end)
end
return h
end
std.obj.display = function(s)
local c = std.call(mp, 'content', mp:visible_scope(s))
return c
end
std.player.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() or false))
end;
--
local function check_persist(w)
if not w:has 'persist' then
return false
end
if not w.found_in then
return true
end
local _, v = std.call(w, 'found_in')
return v
end
function std.obj:access()
local plw = {}
if std.me():where() == self then
return true
end
if self:has 'persist' then
if not self.found_in then
return true
end
local _, v = std.call(self, 'found_in')
return v
end
if mp.scope:lookup(self) then
return true
end
mp:trace(std.me(), function(v)
-- if v:has 'concealed' then
-- return nil, false
-- end
plw[v] = true
if v:has 'container' then -- or v:has 'supporter' then
return nil, false
end
end)
return mp:trace(self, function(v)
-- if v:has 'concealed' then
-- return nil, false
-- end
if check_persist(v) then
return true
end
if plw[v] then
return true
end
if v:has 'container' and not v:has 'open' then
return nil, false
end
end)
end
function mp:distance(v, wh)
local plw = {}
wh = wh or std.me()
local a = 0
mp:trace(wh, function(s)
plw[s] = a
table.insert(plw, s)
a = a + 1
if s:has 'container' then
return nil, false
end
end)
local dist
if v:where() ~= wh then
dist = 1
if not mp:trace(v, function(o)
if plw[o] then
dist = dist + plw[o]
return true
end
dist = dist + 1
end) then
dist = 10000 -- infinity
end
else
dist = 0
end
return dist
end
--- Check if the object is lit
-- @param what
function mp:offerslight(what)
if what and what:has'light' or what:has'luminous' or mp:inside(what, std.me()) then
return true
end
return not mp:thedark()
end
function std.obj:visible()
local plw = { }
if std.me():where() == self then
return true
end
if not mp:offerslight(self) then
return false
end
if check_persist(self) then
return true
end
if mp.scope:lookup(self) then
return true
end
mp:trace(std.me(), function(v)
-- if v:has 'concealed' then
-- return nil, false
-- end
table.insert(plw, v)
if v:has 'container' and not v:has 'transparent' and not v:has 'open' then
return nil, false
end
end)
return mp:trace(self, function(v)
-- if v:has 'concealed' then
-- return nil, false
-- end
if check_persist(v) then
return true
end
for _, o in ipairs(plw) do
if v == o then
return true
end
end
if v:has 'container' and not v:has 'transparent' and not v:has 'open' then
return nil, false
end
end)
end
-- dialogs
std.phr.raw_word = function(s)
local dsc = std.call(s, 'dsc')
return dsc .. '|'.. (tostring(s.__ph_idx) or std.dispof(s))
end
std.phr.Exam = function(s, ...)
std.me():need_scene(true)
return s:act(...)
end
std.phr.__xref = function(_, str)
return str
end
std.dlg.ini = function(s, load)
if std.here() == s and not visited(s) and not load then
s:enter()
end
end
std.dlg.scene = std.obj.scene
std.dlg.title = false
std.dlg.OnError = function(_, _) -- s, err
mp:message 'DLG_HELP'
end;
std.dlg.nouns = function(s)
local nr
local nouns = {}
nr = 1
local oo = s.current
if not oo then -- nothing to show
return
end
for i = 1, #oo.obj do
local o = oo.obj[i]
o = o:__alias()
std.rawset(o, '__ph_idx', nr)
end
for i = 1, #oo.obj do
local o = oo.obj[i]
o = o:__alias()
if o:visible() then
std.rawset(o, '__ph_idx', nr)
nr = nr + 1
table.insert(nouns, o)
end
end
return nouns
end;
std.phrase_prefix = function(n)
if not n then
return '-- '
end
return (string.format("%d) ", n))
end
--- Construct a compass direction from action name.
local function compass_dir(dir)
return obj {
nam = '@'..dir;
default_Event = 'Walk';
before_Any = function(_, ev, ...)
return std.object '@compass':action(dir, ev, ...)
end
}:attr'light,enterable,concealed':persist()
end
obj {
nam = '@compass';
visible = function() return false end;
action = function(s, dir, ev, ...)
if ev == 'Exam' then
local d = dir
local r, v, _
_, v = mp:runorval(std.here(), 'compass_look', d)
if v then
return
end
r, v = mp:runorval(std.here(), d, d)
if r then -- somewhat?
if std.object(r):type 'room' then
mp:message 'COMPASS_EXAM_NO'
return
end
mp:message('COMPASS_EXAM', d, std.object(r))
return
end
if not v then
mp:message 'COMPASS_EXAM_NO'
return
end
return v
end
if ev == 'Walk' or ev == 'Enter' then
local d = dir
if not std.me():where():type'room' then
mp:message 'Enter.EXITBEFORE'
return
end
if std.here()[d] == nil and d == 'out_to' then
mp:xaction("Exit")
return
end
local r, v = mp:runorval(std.here(), d, d)
if not v then
local t, vv = mp:runorval(std.here(), 'cant_go', dir)
if vv then
if t then p(t) end
return
end
mp:message 'COMPASS_NOWAY'
return
end
if not r then
return v
end
if std.object(r):type 'room' then
if not mp:move(std.me(), r) then return true end
else
mp:xaction("Enter", std.object(r))
end
return
end
return std.call(s, 'before_Default', ev, ...)
end;
}:persist():attr'~light,transparent':with {
compass_dir 'n_to',
compass_dir 'ne_to',
compass_dir 'e_to',
compass_dir 'se_to',
compass_dir 's_to',
compass_dir 'sw_to',
compass_dir 'w_to',
compass_dir 'nw_to',
compass_dir 'd_to',
compass_dir 'u_to',
compass_dir 'in_to',
compass_dir 'out_to',
}
--- Check if object is a compass direction.
-- @param w the object to check
-- @param dir optional arg to check againist selected dir
mp.compass_dir = function(_, w, dir)
if not dir then
local nam = tostring(w.nam):gsub("^@", "")
return w:where() and w:where() ^ '@compass' and nam
end
return w ^ ('@'..dir)
end
function mp:multidsc(oo, inv)
local t = {}
local dup = {}
for _, v in ipairs(oo) do
local n
if not v:has'concealed' then
if inv then
n = std.call(v, 'inv')
end
n = n or v:noun(1)
if dup[n] then
dup[n] = dup[n] + 1
else
table.insert(t, { ob = v, noun = n })
dup[n] = 1
end
end
end
for _, vv in ipairs(t) do
local v = vv.noun
local ob = vv.ob
if _ ~= 1 then
if _ == #t then
p (" ", mp.msg.AND or "and")
else
p ","
end
end
if dup[v] > 1 then
pr (vv.ob:noun(self.mrd.lang.gram_t.plural, 1), " (", dup[v], " ", mp.msg.ENUM, ")")
else
pr (v)
if ob:has'worn' then
mp:message('WORN', ob)
elseif ob:has'openable' and ob:has'open' then
mp:message('OPEN', ob)
end
end
end
p "."
end
mp.msg.Exam = {}
--- Display the object contents
function mp:content(w, exam)
if w:type 'dlg' then
return
end
local oo = {}
local ooo = {}
local expand = {}
local inside
if (w == std.me():where() or std.here() == w) and
(mp.event == 'Look' or mp.event == 'Exam' or std.me():need_scene()) then
inside = true
local dsc, v
-- pn()
if mp:thedark(w) then
dsc, v = std.call(w, 'dark_dsc')
if dsc then p(dsc) end
if not v then
mp:message 'WHEN_DARK'
end
else
if w:type'room' and not w:has'visited' and w.init_dsc ~= nil then
dsc, v = std.call(w, 'init_dsc')
else
dsc, v = std.call(w, w:type'room' and 'dsc' or 'inside_dsc')
end
if dsc then p(dsc) end
if not v then
mp:message 'INSIDE_SCENE'
end
end
p(std.scene_delim)
end
self:objects(w, oo, false)
if w == std.here() then
self:objects(self.persistent, oo, false)
end
std.sort(oo, function (a, b)
a = std.tonum(a.pri) or 0
b = std.tonum(b.pri) or 0
if a == b then
return nil
end
return a < b
end)
local something
for _, v in ipairs(oo) do
local r, rc, desc
if not v:has'scenery' and not v:has'concealed' then
if std.me():where() == v then
r, rc = std.call(v, 'inside_dsc')
if r then p(r); desc = true; end
end
if not rc and not v:has 'moved' then
r, rc = std.call(v, 'init_dsc')
if r then p(r); desc = true; end
end
if not rc then
r, rc = std.call(v, 'dsc')
if r then p(r); desc = true; end
end
if not rc and (v:has'openable') then
if v.when_open ~= nil and v:has'open' then
r, rc = std.call(v, 'when_open')
elseif v.when_closed ~= nil and not v:has'open' then
r, rc = std.call(v, 'when_closed')
end
if r then p(r); desc = true; end
elseif not rc and (v:has'switchable') then
if v.when_on ~= nil and v:has'on' then
r, rc = std.call(v, 'when_on')
elseif v.when_off ~= nil and not v:has'on' then
r, rc = std.call(v, 'when_off')
end
if r then p(r); desc = true; end
end
something = something or desc
if not rc then
table.insert(expand, v)
if not desc then
table.insert(ooo, v)
end
end
end
end
-- if #ooo > 0 then
-- p(std.scene_delim)
-- end
oo = ooo
if #oo == 0 then
if not inside and exam and mp.first == w and not something then
if w:has 'supporter' then
pnoun (w, 'Exam.ON')
else
pnoun (w, 'Exam.IN')
end
mp:message 'Exam.NOTHING'
end
elseif #oo == 1 and not oo[1]:hint 'plural' then
if std.me():where() == w or std.here() == w then
mp:message('Look.HEREIS', w)
else
if w:has 'supporter' then
pnoun (w, 'Exam.ON')
else
pnoun (w, 'Exam.IN')
end
mp:message 'Exam.IS'
end
-- p(oo[1]:noun(1), ".")
mp:multidsc(oo)
else
if std.me():where() == w or std.here() == w then
mp:message('Look.HEREARE', w)
else
if w:has 'supporter' then
pnoun (w, 'Exam.ON')
else
pnoun (w, 'Exam.IN')
end
mp:message 'Exam.ARE'
end
mp:multidsc(oo)
end
-- expand?
for _, o in ipairs(expand) do
if (o:has'supporter' or o:has'transparent' or (o:has'container' and o:has'open')) and not o:closed() then
self:content(o)
end
end
end
std.room:attr 'enterable,light'
function mp:step()
local old_daemons = {}
game.__daemons:for_each(function(o)
table.insert(old_daemons, o)
end)
for _, o in ipairs(old_daemons) do
if not o:disabled() then
local r, v = mp:runorval(o, 'daemon')
if r == true and v == true then break end
end
end
local oo = mp:nouns()
std.here():attr 'visited'
for _, v in ipairs(oo) do
if v.each_turn ~= nil then
local r = mp:runorval(v, 'each_turn')
if r == true then
break
end
end
end
local s = std.game -- after reset game is recreated
local r = std.pget()
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
end
s:reaction(r or false)
std.pclr()
s:step()
r = s:display(true)
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
end
s:lastreact(s:reaction() or false)
s:lastdisp(r)
std.pr(r)
std.abort_cmd = true
end
function mp:post_action()
if self:noparser() or
(self.event and self.event:find("Meta", 1, true)) or
self:comment() then
if not std.abort_cmd then
game:time(game:time() - 1)
end
return
end
if mp.undo > 0 then
local nr = #snapshots.data
if nr > mp.undo then
table.remove(snapshots.data, 1)
nr = nr - 1
end
mp.snapshot = nr + 1
end
if self.score and (self.score ~= (self.__old_score or 0)) then
mp:message('SCORE', self.score - (self.__old_score or 0))
self.__old_score = self.score
end
if game.player:need_scene() then
-- pn(iface:nb'')
local l = game.player:look() -- objects [and scene]
local gfx
if std.here().gfx ~= nil then
gfx = std.call(std.here(), 'gfx')
end
if not gfx and std.game.gfx ~= nil then
gfx = std.call(std.game, 'gfx')
end
if gfx then
pn(fmt.c(fmt.img(gfx)))
end
p(l, std.scene_delim)
game.player:need_scene(false)
end
mp:step()
end
--- Check if mp.first and mp.second objects are in touch zone
-- Returns true if we have to terminate the sequence.
function mp:check_touch()
if self.first and not self.first:access() and not self.first:type'room' then
p (mp:mesg('ACCESS1') or "{#First} is not accessible.")
if std.here() ~= std.me():where() then
mp:message 'EXITBEFORE'
end
return true
end
if self.second and not self.second:access() and not self.first:type'room' then
p (mp:mesg('ACCESS2') or "{#Second} is not accessible.")
if std.here() ~= std.me():where() then
mp:message 'EXITBEFORE'
end
return true
end
return false
end
--[[
function mp:before_Any(ev)
if ev == 'Exam' then
return false
end
if self.first and not self.first:access() and not self.first:type'room' then
p (self.msg.ACCESS1 or "{#First} is not accessible.")
if std.here() ~= std.me():where() then
mp:message 'EXITBEFORE'
end
return
end
if self.second and not self.second:access() and not self.first:type'room' then
p (self.msg.ACCESS2 or "{#Second} is not accessible.")
if std.here() ~= std.me():where() then
mp:message 'EXITBEFORE'
end
return
end
return false
end
]]--
function mp:Look()
std.me():need_scene(true)
return false
end
function mp:after_Look()
end
--luacheck: push ignore w wh
function mp:Exam(w)
return false
end
function mp:after_Exam(w)
local r, v = std.call(w, 'description')
local something = false
if r then
p(r)
something = true
end
if v then
return false
end
if w:has 'container' and (w:has'transparent' or w:has'open') then
self:content(w, not something)
elseif w:has 'supporter' then
self:content(w, not something)
else
if w:has'openable' then
if w:has 'open' then
local t = std.call(w, 'when_open')
if t then
p(t)
else
mp:message 'Exam.OPENED'
end
else
local t = std.call(w, 'when_closed')
if t then
p(t)
else
mp:message 'Exam.CLOSED'
end
end
return
end
if w:has'switchable' then
local t
if w:has'on' and w.when_on ~= nil then
t = std.call(w, 'when_on')
else
t = std.call(w, 'when_off')
end
if t then
p(t)
else
mp:message 'Exam.SWITCHSTATE'
end
return
end
if w == std.here() then
std.me():need_scene(true)
else
if w == std.me() then
mp:message 'Exam.SELF'
else
mp:message 'Exam.DEFAULT'
end
end
end
end
mp.msg.Enter = {}
function mp:Enter(w)
if mp:check_touch() then
return
end
if mp:check_live(w) then
return
end
if w == std.me():where() then
mp:message 'Enter.ALREADY'
return
end
if w:has'clothing' and not w:has'enterable' then
mp:xaction ("Wear", w)
return
end
if seen(w, std.me()) then
mp:message 'Enter.INV'
return
end
if not w:has 'enterable' then
mp:message 'Enter.IMPOSSIBLE'
return
end
if w:has 'container' and not w:has 'open' then
mp:message 'Enter.CLOSED'
return
end
if mp:check_inside(w) then
return
end
if not mp:move(std.me(), w) then return true end
return false
end
function mp:after_Enter(w)
mp:message 'Enter.ENTERED'
end
mp.msg.Walk = {}
function mp:Walk(w)
if mp:check_touch() then
return
end
if w == std.me():where() then
mp:message 'Walk.ALREADY'
return
end
if seen(w, std.me()) then
mp:message 'Walk.INV'
return
end
-- if std.me():where() ~= std.here() then
-- mp:message 'Enter.EXITBEFORE'
-- return
-- end
return false
end
function mp:after_Walk(w)
if not w then
mp:message 'Walk.NOWHERE'
else
mp:message 'Walk.WALK'
end
end
mp.msg.Exit = {}
function mp:before_Exit(w)
if not w then
self:xaction('Exit', std.me():where())
return true
end
return false
end
function mp:Exit(w)
if mp:check_touch() then
return
end
local wh = std.me():where()
w = w or std.me():where()
if wh ~= w then
if have(w) and w:has'worn' then
mp:xaction ("Disrobe", w)
return
end
if wh:inside(w) then
mp:message 'Enter.EXITBEFORE'
return
end
mp:message 'Exit.NOTHERE'
return
end
if wh:has'container' and not wh:has'open' then
mp:message 'Exit.CLOSED'
return
end
if wh:type'room' and wh.out_to ~= nil then
mp:xaction("Walk", _'@out_to')
return
end
if wh:from() == wh or wh:type 'room' then
mp:message 'Exit.NOWHERE'
return
end
-- if wh:type'room' then
-- local r = std.call(w, 'out_to')
-- mp:move(std.me(), wh:from())
-- else
if not mp:move(std.me(), wh:where()) then return true end
-- end
return false
end
function mp:after_Exit(w)
if w and not w:type 'room' then
mp:message 'Exit.EXITED'
end
end
mp.msg.Inv = {}
function mp:detailed_Inv(wh, indent)
local oo = {}
self:objects(wh, oo, false)
for _, o in ipairs(oo) do
if not o:has'concealed' then
for _ = 1, indent do pr(iface:nb' ') end
local inv = std.call(o, 'inv') or o:noun(1)
pr(inv)
if o:has'worn' then
mp:message('WORN', o)
elseif o:has'openable' and o:has'open' then
mp:message('OPEN', o)
end
pn()
if o:has'supporter' or o:has'container' then
mp:detailed_Inv(o, indent + 1)
end
end
end
end
function mp:after_Inv()
local oo = {}
self:objects(std.me(), oo, false)
if #oo == 0 then
mp:message 'Inv.NOTHING'
return
end
local empty = true
for _, v in ipairs(oo) do
if not v:has'concealed' then empty = false break end
end
if empty then
mp:message 'Inv.NOTHING'
return
end
pr(mp:mesg 'Inv.INV')
if mp.detailed_inv then
pn(":")
mp:detailed_Inv(std.me(), 1)
else
p()
mp:multidsc(oo, true)
end
end
mp.msg.Open = {}
function mp:Open(w)
if mp:check_touch() then
return
end
if mp:check_live(w) then
return
end
if not w:has'openable' then
mp:message 'Open.NOTOPENABLE'
return
end
if w:has'open' then
mp:message 'Open.WHENOPEN'
return
end
if w:has'locked' then
mp:message 'Open.WHENLOCKED'
return
end
w:attr'open'
return false
end
function mp:after_Open(w)
mp:message 'Open.OPEN'
if w:has'container' then
self:content(w)
end
end
mp.msg.Close = {}
function mp:Close(w)
if mp:check_touch() then
return
end
if not w:has'openable' then
mp:message 'Close.NOTOPENABLE'
return
end
if not w:has'open' then
mp:message 'Close.WHENCLOSED'
return
end
w:attr'~open'
return false
end
function mp:after_Close(w)
mp:message 'Close.CLOSE'
end
--- Show mp message using mp.msg constant
-- args uses only for functions
function mp:message(m, ...)
p(mp:mesg(m, ...))
end
-- same as above, but do not call p
function mp:mesg(m, ...)
local t = std.split(m, ".")
m = mp.msg
for _, n in ipairs(t) do
m = m[n]
if not m then
std.err("Wrong message id", 2)
end
end
if type(m) ~= 'function' then
return m
else
std.callpush()
local v = m(...)
local r = std.pget()
std.callpop()
return r or v
end
end
--- Check if the object is alive.
-- If yes, return standard message.
-- Returns true if we have to terminate the sequence.
-- @param w object to check
function mp:check_live(w)
if self:animate(w) then
mp:message('LIVE_ACTION', w)
return true
end
return false
end
function mp:check_no_live(w)
if not self:animate(w) then
mp:message('NO_LIVE_ACTION', w)
return true
end
return false
end
--- Check if the object is held by the player.
-- If not, attempt to take it.
-- Returns true if we have to terminate the sequence.
-- @param t object to check
function mp:check_held(t)
if have(t) or std.me() == t then
-- if (std:me():lookup(t) and t:visible()) or std.me() == t then
return false
end
mp:message('TAKE_BEFORE', t)
mp:subaction('Take', t)
if not have(t) then
-- mp:message('NOTINV', t)
return true
end
return false
end
function mp:check_inside(w)
if std.me():where() ~= std.here() and not w:inside(std.me():where()) then
mp:message 'Enter.EXITBEFORE'
return true
end
return false
end
--- Check if the object is worn by the player.
-- If yes, attempt to take it off.
-- Returns true if we have to terminate the sequence.
-- @param w object to check
function mp:check_worn(w)
if w:has'worn' then
mp:message('DISROBE_BEFORE', w)
mp:subaction('Disrobe', w)
if w:has'worn' then
-- mp:message 'Drop.WORN'
return true
end
end
return false
end
mp.msg.Lock = {}
function mp:Lock(w, t)
if mp:check_touch() then
return
end
if mp:check_held(t) then
return
end
local r = std.call(w, 'with_key')
if not w:has 'lockable' or not r then
mp:message 'Lock.IMPOSSIBLE'
return
end
if w:has 'locked' then
mp:message 'Lock.LOCKED'
return
end
if w:has 'open' then
mp:message('CLOSE_BEFORE', w)
mp:subaction('Close', w)
if w:has 'open' then
mp:message 'Lock.OPEN'
return
end
end
if std.object(r) ~= t then
mp:message 'Lock.WRONGKEY'
return
end
w:attr'locked'
return false
end
function mp:after_Lock(w, wh)
mp:message 'Lock.LOCK'
end
mp.msg.Unlock = {}
function mp:Unlock(w, t)
if mp:check_touch() then
return
end
if mp:check_held(t) then
return
end
local r = std.call(w, 'with_key')
if not w:has 'lockable' or not r then
mp:message 'Unlock.IMPOSSIBLE'
return
end
if not w:has 'locked' then
mp:message 'Unlock.NOTLOCKED'
return
end
if std.object(r) ~= t then
mp:message 'Unlock.WRONGKEY'
return
end
w:attr'~locked'
-- w:attr'open'
return false
end
function mp:after_Unlock(w, wh)
mp:message 'Unlock.UNLOCK'
end
--- Check if the object is inside an object.
-- @param w what
-- @param wh where
function mp:inside(w, wh)
wh = std.object(wh)
w = std.object(w)
return mp:trace(w, function(v)
if v == wh then return true end
end)
end
--- Check if the object is inside an object.
-- @see mp:inside
function inside(w, wh)
return mp:inside(w, wh)
end
--- Check if the object is inside an object.
-- @see mp:inside
std.obj.inside = function(s, wh)
return mp:inside(s, wh)
end
std.obj.move = function(s, wh)
return mp:move(s, wh, true)
end
--- Move an object
-- @see mp:move
function move(w, wh)
return mp:move(w, wh, true)
end
--- Move an object
-- @param w what
-- @param wh where
-- @param force ignore capacity flag if true
function mp:move(w, wh, force)
wh = wh or std.here()
wh = std.object(wh)
w = std.object(w)
local r
local ww = {}
if not force then
local n = self:runorval(wh, 'capacity')
local capacity = n and tonumber(n)
if capacity and #wh.obj >= capacity then
mp:message('NOROOM', wh)
return false
end
w:where(ww)
for _, o in ipairs(ww) do
if mp:runmethods('before', 'LetGo', o, w, wh) then
return false
end
end
if mp:runmethods('before', 'LetIn', wh, w) then
return false
end
end
if w:type'player' then
r = w:walk(wh)
if r then p(r) end
else
local wpl = mp:inside(std.me(), w)
place(w, wh)
if wpl then
r = std.me():walk(w)
if r then p(r) end
end
end
w:attr 'moved'
if not force then
for _, o in ipairs(ww) do
if mp:runmethods('after', 'LetGo', o, w, wh) then
return false
end
end
if mp:runmethods('after', 'LetIn', wh, w) then
return false
end
end
return true
end
mp.msg.Take = {}
local function cont_taken(ob, taken)
for _, o in ipairs(taken) do
if ob:inside(o) then
return true
end
end
end
function mp:TakeAll(wh)
local empty = true
wh = wh or std.me():where()
local oo = {}
mp:objects(wh, oo)
local taken = {}
for _, o in ipairs(oo) do
if o:hasnt 'static' and o:hasnt'scenery' and o:hasnt 'concealed'
and not mp:animate(o)
and not cont_taken(o, taken) then
empty = false
mp:message('TAKING_ALL', o)
mp:subaction('Take', o)
if not have(o) then
break
end
table.insert(taken, o)
end
end
if empty then
mp:message 'NOTHING_OBJ'
end
end
function mp:Take(w, wh)
if w == everything then
return mp:TakeAll(wh)
end
if mp:check_touch() then
return
end
if w == std.me() then
mp:message 'Take.SELF'
return
end
if have(w) then
mp:message 'Take.HAVE'
return
end
local n = mp:trace(std.me(), function(v)
if v == w then return true end
end)
if n then
mp:message 'Take.WHERE'
return
end
if mp:animate(w) then
mp:message 'Take.LIFE'
return
end
if w:has'static' then
mp:message 'Take.STATIC'
return
end
if w:has'scenery' then
mp:message 'Take.SCENERY'
return
end
if w:where() and not w:where():type'room' and
not w:where():has'container' and
not w:where():has'supporter' then
if w:has'worn' and mp:animate(w:where()) then
mp:message 'Take.WORN'
else
mp:message 'Take.PARTOF'
end
return
end
if not mp:move(w, std.me()) then return true end
return false
end
function mp:after_Take(w)
mp:message 'Take.TAKE'
end
mp.msg.Remove = {}
function mp:Remove(w, wh)
if mp:check_touch() then
return
end
if w == std.me() then
mp:xaction("Exit", wh)
return
end
if w:where() ~= wh and w:inroom() ~= wh and w ~= everything then
mp:message 'Remove.WHERE'
return
end
mp:xaction('Take', w, wh)
end
function mp:after_Remove(w, wh)
mp:message 'Remove.REMOVE'
end
mp.msg.Drop = {}
function mp:DropAll(wh)
local empty = true
local oo = {}
mp:objects(std.me(), oo, false)
for _, o in ipairs(oo) do
if o:hasnt 'concealed' then
empty = false
mp:message('DROPPING_ALL', o)
mp:subaction('Drop', o)
if have(o) then
break
end
end
end
if empty then
mp:message 'NOTHING_OBJ'
end
end
function mp:Drop(w)
if w == everything then
return mp:DropAll()
end
if mp:check_touch() then
return
end
if mp:check_held(w) then
return
end
if mp:check_worn(w) then
return
end
if w == std.me() then
mp:message 'Drop.SELF'
return
end
if not mp:move(w, std.me():where()) then return true end
return false
end
function mp:after_Drop(w)
mp:message 'Drop.DROP'
end
mp.msg.Insert = {}
function mp:Insert(w, wh)
if mp:check_touch() then
return
end
if wh == std.me() then
mp:xaction('Take', w)
return
end
if w == std.me() then
mp:xaction('Enter', wh)
return
end
if wh == w:where() then
mp:message 'Insert.ALREADY'
return
end
if wh == std.me():where() or mp:compass_dir(wh, 'd_to') then
mp:xaction('Drop', w)
return
end
if mp:check_held(w) then
return
end
if mp:check_worn(w) then
return
end
if mp:check_live(wh) then
return
end
local n = mp:trace(wh, function(v)
if v == w then return true end
end)
if n or w == wh then
mp:message 'Insert.WHERE'
return
end
if mp:runmethods('before', 'Receive', wh, w) then
return
end
if not wh:has'container' then
if wh:has'supporter' then
mp:xaction("PutOn", w, wh)
return
end
mp:message 'Insert.NOTCONTAINER'
return
end
if not wh:has'open' then
mp:message 'Insert.CLOSED'
return
end
if not mp:move(w, wh) then return true end
return false
end
function mp:after_Insert(w, wh)
if mp:runmethods('after', 'Receive', wh, w) then
return
end
mp:message 'Insert.INSERT'
end
mp.msg.PutOn = {}
function mp:PutOn(w, wh)
if mp:check_touch() then
return
end
if wh == std.me() then
mp:xaction('Take', w)
return
end
if w == std.me() then
mp:xaction('Climb', wh)
return
end
if wh == std.me():where() or mp:compass_dir(wh, 'd_to') then
mp:xaction('Drop', w)
return
end
if mp:check_held(w) then
return
end
if mp:check_live(wh) then
return
end
if mp:check_worn(w) then
return
end
local n = mp:trace(wh, function(v)
if v == w then return true end
end)
if n or w == wh then
mp:message 'PutOn.WHERE'
return
end
if mp:runmethods('before', 'Receive', wh, w) then
return
end
if not wh:has'supporter' then
mp:message 'PutOn.NOTSUPPORTER'
return
end
if not mp:move(w, wh) then return true end
return false
end
function mp:after_PutOn(w, wh)
if mp:runmethods('after', 'Receive', wh, w) then
return
end
mp:message 'PutOn.PUTON'
end
mp.msg.ThrowAt = {}
function mp:ThrowAt(w, wh)
if mp:check_touch() then
return
end
if wh == std.me():where() or mp:compass_dir(wh, 'd_to') then
mp:xaction('Drop', w)
return
end
if mp:check_held(w) then
return
end
if mp:check_worn(w) then
return
end
if mp:runmethods('before', 'ThrownAt', wh, w) then
return
end
if mp:runmethods('life', 'ThrowAt', wh, w) then
return
end
if not self:animate(wh) then
if wh:has'container' then
mp:xaction("Insert", w, wh)
return
end
if wh:has'supporter' then
mp:xaction("PutOn", w, wh)
return
end
mp:message 'ThrowAt.NOTLIFE'
return
end
mp:message 'ThrowAt.THROW'
end
mp.msg.Wear = {}
function mp:Wear(w)
if mp:check_touch() then
return
end
if mp:check_held(w) then
return
end
if not w:has'clothing' then
mp:message 'Wear.NOTCLOTHES'
return
end
if w:has'worn' then
mp:message 'Wear.WORN'
return
end
w:attr'worn'
return false
end
function mp:after_Wear(w)
mp:message 'Wear.WEAR'
end
mp.msg.Disrobe = {}
function mp:Disrobe(w)
if mp:check_touch() then
return
end
if not have(w) or not w:has'worn' then
mp:message 'Disrobe.NOTWORN'
return
end
w:attr'~worn'
return false
end
function mp:after_Disrobe(w)
mp:message 'Disrobe.DISROBE'
end
mp.msg.SwitchOn = {}
function mp:SwitchOn(w)
if mp:check_touch() then
return
end
if not w:has'switchable' then
mp:message 'SwitchOn.NONSWITCHABLE'
return
end
if w:has'on' then
mp:message 'SwitchOn.ALREADY'
return
end
w:attr'on'
return false
end
function mp:after_SwitchOn(w)
mp:message 'SwitchOn.SWITCHON'
end
mp.msg.SwitchOff = {}
function mp:SwitchOff(w)
if mp:check_touch() then
return
end
if not w:has'switchable' then
mp:message 'SwitchOff.NONSWITCHABLE'
return
end
if not w:has'on' then
mp:message 'SwitchOff.ALREADY'
return
end
w:attr'~on'
return false
end
function mp:after_SwitchOff(w)
mp:message 'SwitchOff.SWITCHOFF'
end
mp.msg.Search = {}
function mp:Search(w)
mp:xaction('Exam', w)
end
mp.msg.LookUnder = {}
function mp:LookUnder(w)
mp:message 'LookUnder.NOTHING'
end
mp.msg.Eat = {}
function mp:Eat(w)
if mp:check_touch() then
return
end
if not w:has'edible' then
mp:message 'Eat.NOTEDIBLE'
return
end
if mp:check_held(w) then
return
end
if mp:check_worn(w) then
return
end
remove(w)
return false
end
function mp:after_Eat(w)
mp:message 'Eat.EAT'
end
mp.msg.Taste = {}
function mp:Taste(w)
if mp:check_touch() then
return
end
if w:has'edible' then
mp:xaction("Eat", w)
return
end
if mp:check_live(w) then
return
end
return false
end
function mp:after_Taste(w)
mp:message 'Taste.TASTE'
end
mp.msg.Drink = {}
function mp:after_Drink(w)
mp:message 'Drink.IMPOSSIBLE'
end
mp.msg.Transfer = {}
function mp:Transfer(w, ww)
if mp:check_touch() then
return
end
if mp:compass_dir(ww) then
mp:xaction('PushDir', w, ww)
return
end
if ww:has 'supporter' then
mp:xaction('PutOn', w, ww)
return
end
mp:xaction('Insert', w, ww)
end
mp.msg.Push = {}
function mp:Push(w)
if mp:check_touch() then
return
end
if w:has 'switchable' then
if w:has'on' then
mp:xaction('SwitchOff', w)
else
mp:xaction('SwitchOn', w)
end
return
end
if w:has 'static' then
mp:message 'Push.STATIC'
return
end
if w:has 'scenery' then
mp:message 'Push.SCENERY'
return
end
if mp:check_live(w) then
return
end
return false
end
function mp:after_Push()
mp:message 'Push.PUSH'
end
mp.msg.Pull = {}
function mp:Pull(w)
if mp:check_touch() then
return
end
if w:has 'static' then
mp:message 'Pull.STATIC'
return
end
if w:has 'scenery' then
mp:message 'Pull.SCENERY'
return
end
if mp:check_live(w) then
return
end
return false
end
function mp:after_Pull()
mp:message 'Pull.PULL'
end
mp.msg.Turn = {}
function mp:Turn(w)
if mp:check_touch() then
return
end
if w:has 'static' then
mp:message 'Turn.STATIC'
return
end
if w:has 'scenery' then
mp:message 'Turn.SCENERY'
return
end
if mp:check_live(w) then
return
end
return false
end
function mp:after_Turn()
mp:message 'Turn.TURN'
end
mp.msg.Wait = {}
function mp:after_Wait()
mp:message 'Wait.WAIT'
end
mp.msg.Rub = {}
function mp:Rub(w)
if mp:check_touch() then
return
end
return false
end
function mp:after_Rub()
mp:message 'Rub.RUB'
end
mp.msg.Sing = {}
function mp:after_Sing(w)
mp:message 'Sing.SING'
end
mp.msg.Touch = {}
function mp:Touch(w)
if mp:check_touch() then
return
end
if w == std.me() then
mp:message 'Touch.MYSELF'
return
end
if self:animate(w) then
mp:message 'Touch.LIVE'
return
end
return false
end
function mp:after_Touch()
mp:message 'Touch.TOUCH'
end
mp.msg.Give = {}
function mp:Give(w, wh)
if mp:check_touch() then
return
end
if mp:check_held(w) then
return
end
if wh == std.me() then
mp:message 'Give.MYSELF'
return
end
if mp:runmethods('life', 'Give', wh, w) then
return
end
if mp:check_no_live(wh) then
return
end
return false
end
function mp:after_Give()
mp:message 'Give.GIVE'
end
mp.msg.Show = {}
function mp:Show(w, wh)
if mp:check_touch() then
return
end
if mp:check_held(w) then
return
end
if wh == std.me() then
mp:xaction("Exam", w)
return
end
if mp:runmethods('life', 'Show', wh, w) then
return
end
if mp:check_no_live(wh) then
return
end
return false
end
function mp:after_Show()
mp:message 'Show.SHOW'
end
mp.msg.Burn = {}
function mp:Burn(w, wh)
if mp:check_touch() then
return
end
if wh and mp:check_held(wh) then
return
end
return false
end
function mp:after_Burn(w, wh)
if wh then
mp:message 'Burn.BURN2'
else
mp:message 'Burn.BURN'
end
end
mp.msg.Wake = {}
function mp:after_Wake()
mp:message 'Wake.WAKE'
end
mp.msg.WakeOther = {}
function mp:WakeOther(w)
if mp:check_touch() then
return
end
if w == std.me() then
mp:xaction('Wake')
return
end
if mp:runmethods('life', 'WakeOther', w) then
return
end
if not mp:animate(w) then
mp:message 'WakeOther.NOTLIVE'
return
end
return false
end
function mp:after_WakeOther()
mp:message 'WakeOther.WAKE'
end
mp.msg.PushDir = {}
function mp:PushDir(w, wh)
if mp:check_touch() then
return
end
if mp:check_live(w) then
return
end
return false
end
function mp:after_PushDir()
mp:message 'PushDir.PUSH'
end
mp.msg.Kiss = {}
function mp:Kiss(w)
if mp:check_touch() then
return
end
if mp:runmethods('life', 'Kiss', w) then
return
end
if not mp:animate(w) then
mp:message 'Kiss.NOTLIVE'
return
end
if w == std.me() then
mp:message 'Kiss.MYSELF'
return
end
return false
end
function mp:after_Kiss()
mp:message 'Kiss.KISS'
end
mp.msg.Think = {}
function mp:after_Think()
mp:message 'Think.THINK'
end
mp.msg.Smell = {}
function mp:Smell(w)
if mp:check_touch() then
return
end
return false
end
function mp:after_Smell(w)
if w then
mp:message 'Smell.SMELL2'
return
end
mp:message 'Smell.SMELL'
end
mp.msg.Listen = {}
function mp:Listen(w)
if mp:check_touch() then
return
end
return false
end
function mp:after_Listen(w)
if w then
mp:message 'Listen.LISTEN2'
return
end
mp:message 'Listen.LISTEN'
end
mp.msg.Dig = {}
function mp:Dig(w, wh)
if mp:check_touch() then
return
end
if w and mp:check_live(w) then
return
end
if wh then
if mp:check_held(wh) then
return
end
end
return false
end
function mp:after_Dig(w, wh)
if wh then
mp:message 'Dig.DIG3'
return
end
if w then
mp:message 'Dig.DIG2'
return
end
mp:message 'Dig.DIG'
end
mp.msg.Cut = {}
function mp:Cut(w, wh)
if mp:check_touch() then
return
end
if mp:check_live(w) then
return
end
if wh then
if mp:check_live(wh) then
return
end
if mp:check_held(wh) then
return
end
end
return false
end
function mp:after_Cut(w, wh)
if wh then
mp:message 'Cut.CUT2'
else
mp:message 'Cut.CUT'
end
end
mp.msg.Tear = {}
function mp:Tear(w)
if mp:check_touch() then
return
end
if mp:check_live(w) then
return
end
return false
end
function mp:after_Tear()
mp:message 'Tear.TEAR'
end
mp.msg.Tie = {}
function mp:Tie(w, wh)
if mp:check_touch() then
return
end
if mp:check_live(w) then
return
end
if wh and mp:check_live(wh) then
return
end
return false
end
function mp:after_Tie(w, wh)
if wh then
mp:message 'Tie.TIE2'
return
end
mp:message 'Tie.TIE'
end
mp.msg.Blow = {}
function mp:Blow(w)
if mp:check_touch() then
return
end
if mp:check_live(w) then
return
end
return false
end
function mp:after_Blow()
mp:message 'Blow.BLOW'
end
mp.msg.Attack = {}
function mp:Attack(w)
if mp:check_touch() then
return
end
if mp:runmethods('life', 'Attack', w) then
return
end
return false
end
function mp:after_Attack(w)
if mp:animate(w) then
mp:message 'Attack.LIFE'
return
end
mp:message 'Attack.ATTACK'
end
mp.msg.Sleep = {}
function mp:after_Sleep()
mp:message 'Sleep.SLEEP'
end
mp.msg.Swim = {}
function mp:after_Swim()
mp:message 'Swim.SWIM'
end
mp.msg.Consult = {}
function mp:Consult(w, wh)
if mp:check_touch() then
return
end
return false
end
function mp:after_Consult()
mp:message 'Consult.CONSULT'
end
mp.msg.Fill = {}
function mp:Fill(w)
if mp:check_touch() then
return
end
return false
end
function mp:after_Fill()
mp:message 'Fill.FILL'
end
mp.msg.Jump = {}
function mp:after_Jump()
mp:message 'Jump.JUMP'
end
mp.msg.JumpOver = {}
function mp:JumpOver(w)
if mp:check_touch() then
return
end
return false
end
function mp:after_JumpOver()
mp:message 'JumpOver.JUMPOVER'
end
mp.msg.WaveHands = {}
function mp:after_WaveHands()
mp:message 'WaveHands.WAVE'
end
mp.msg.Wave = {}
function mp:Wave(w)
if mp:check_touch() then
return
end
if mp:check_held(w) then
return
end
return false
end
function mp:after_Wave()
mp:message 'Wave.WAVE'
end
function mp:Climb(w)
mp:xaction('Enter', w)
end
mp.msg.GetOff = {}
function mp:GetOff(w)
if not w and std.me():where() == std.here() then
mp:message 'GetOff.NOWHERE'
return
end
mp:xaction('Exit', w)
end
mp.msg.Buy = {}
function mp:Buy(w)
if mp:check_touch() then
return
end
return false
end
function mp:after_Buy()
mp:message 'Buy.BUY'
end
mp.msg.Talk = {}
function mp:Talk(w)
if mp:check_touch() then
return
end
local r, v = mp:runorval(w, 'talk_to')
if v then
if r then
walkin(r)
end
return
end
if w == std.me() then
mp:message 'Talk.SELF'
return
end
if mp:runmethods('life', 'Talk', w) then
return
end
return false
end
function mp:after_Talk(w)
if not mp:animate(w) then
mp:message 'Talk.NOTLIVE'
return
end
mp:message 'Talk.LIVE'
end
mp.msg.Tell = {}
function mp:Tell(w, t)
if mp:check_touch() then
return
end
if #self.vargs == 0 then
mp:message 'Tell.EMPTY'
return
end
if w == std.me() then
mp:message 'Tell.SELF'
return
end
if mp:runmethods('life', 'Tell', w, t) then
return
end
return false
end
function mp:after_Tell(w)
if not mp:animate(w) then
mp:message 'Tell.NOTLIVE'
return
end
mp:message 'Tell.LIVE'
end
mp.msg.Ask = {}
function mp:Ask(w, t)
if mp:check_touch() then
return
end
if #self.vargs == 0 then
mp:message 'Ask.EMPTY'
return
end
if w == std.me() then
mp:message 'Ask.SELF'
return
end
if mp:runmethods('life', 'Ask', w, t) then
return
end
return false
end
function mp:after_Ask(w)
if not mp:animate(w) then
mp:message 'Ask.NOTLIVE'
return
end
mp:message 'Ask.LIVE'
end
function mp:AskFor(w, t)
if w == std.me() then
mp:xaction('Inv')
return
end
mp:xaction('Ask', w, t)
end
function mp:AskTo(w, t)
mp:xaction('Ask', w, t)
end
mp.msg.Answer = {}
function mp:Answer(w, t)
if mp:check_touch() then
return
end
if #self.vargs == 0 then
mp:message 'Answer.EMPTY'
return
end
if w == std.me() then
mp:message 'Answer.SELF'
return
end
if mp:runmethods('life', 'Answer', w, t) then
return
end
return false
end
function mp:after_Answer(w)
if not mp:animate(w) then
mp:message 'Answer.NOTLIVE'
return
end
mp:message 'Answer.LIVE'
end
mp.msg.Yes = {}
function mp:after_Yes()
mp:message 'Yes.YES'
end
function mp:after_No()
mp:message 'Yes.YES'
end
mp.msg.Use = {}
function mp:Use(w)
if mp:check_touch() then
return
end
return false
end
function mp:after_Use()
mp:message 'Use.USE'
end
function mp:MetaHelp()
pn(mp:mesg 'HELP')
end
function mp:MetaTranscript()
if self.logfile then
p("Log file: ", self.logfile)
else
self:MetaTranscriptOn()
end
end
function mp:MetaTranscriptOff()
self.logfile = false
self.lognum = self.lognum + 1
p("Logging is stopped.")
end
function mp:MetaTranscriptOn()
while true do
local logfile = string.format("%s/log%03d.txt", instead.gamepath(), self.lognum)
local f = io.open(logfile, "rb")
if not f then
self.logfile = logfile
if std.cctx() then
p ("Logging is enabled: ", logfile)
end
return
end
f:close()
self.lognum = self.lognum + 1
end
end
mp.msg.MetaRestart = {}
local old_pre_input
function mp:MetaRestart()
mp:message 'MetaRestart.RESTART'
if old_pre_input then return end
old_pre_input = mp.pre_input
std.rawset(mp, 'pre_input', function(_, str)
std.rawset(mp, 'pre_input', old_pre_input)
old_pre_input = false
if mp:eq(str, mp.msg.YES) then
instead.restart()
end
return false
end)
end
function mp:MetaSave()
instead.menu 'save'
end
function mp:MetaExpertOn()
mp.autocompl = false
mp.autohelp = false
p [[Expert mode on.]]
end
function mp:MetaExpertOff()
mp.autocompl = true
mp.autohelp = true
p [[Expert mode off.]]
end
function mp:MetaLoad()
instead.menu 'load'
end
--luacheck: pop
local function attr_string(o)
local a = ''
for k, _ in pairs(o.__ro) do
if type(k) == 'string' and k:find("__attr__", 1, true) == 1 then
if a ~= '' then a = a .. ', ' end
a = a .. k:sub(9)
end
end
local b = ''
for k, _ in pairs(o) do
if type(k) == 'string' and k:find("__attr__", 1, true) == 1 then
if b ~= '' then b = b .. ', ' end
b = b .. k:sub(9)
end
end
if b ~= '' then b = '!'..b..'' end
a = a .. b
if a ~= '' then a = ' [' .. a .. '] ' end
return a
end
function mp:MetaDump()
local oo = mp:nouns()
for _, o in ipairs(oo) do
if not std.is_system(o) and o ~= std.me() then
local d = mp:distance(o)
if d > 8 then d = 8 end
for _ = 1, d do pr(fmt.nb' ') end
local t = '<'..std.tostr(o)..'>'
t = t .. (std.call(o, 'word') or std.call(o, 'raw_word') or '')
if have(o) then t = fmt.em(t) end
pn(t, attr_string(o))
end
end
end
function mp:MetaWord(w)
if not w then return end
w = w:gsub("_", "/")
local g
w, g = self.mrd:word(w)
pn(w)
for _, v in ipairs(g) do
pn (_, ":")
for k, vv in pairs(v) do
pn(k, " = ", vv)
end
end
end
mp.msg.MetaUndo = {}
function mp:MetaUndo()
local nr = #snapshots.data
if nr > 1 then
snapshots:restore(nr - 1)
table.remove(snapshots.data, nr)
else
mp:message 'MetaUndo.EMPTY'
end
end
local function getobj(w)
if std.is_tag(w) then
return std.here():lookup(w) or std.me():lookup(w)
end
return std.ref(w)
end
function mp:MetaNoun(_)
local varg = self.vargs
local o = getobj(varg[1])
if not o then
p ("Wrong object: ", varg[1])
return
end
local t = {}
local w
if #varg == 2 then
w = o:noun(varg[2], t)
else
w = o:noun(t)
end
pn "== Words:"
for _, v in ipairs(w or {}) do
pn(v)
end
pn "== Grams:"
for _, v in ipairs(t or {}) do
for kk, vv in pairs(v) do
pn(kk, " = ", vv)
end
end
end
function mp:MetaTraceOn()
pn "Tracing is on"
self.debug.trace_action = true
end
function mp:MetaTraceOff()
pn "Tracing is off"
self.debug.trace_action = false
end
function mp:MetaAutoplay(w)
mp:autoscript(w)
if mp.autoplay then
pn ([[Script file: ]], w)
else
pn ([[Can not open script file: ]], w)
end
end
local __oini = std.obj.__ini
local function fn_aliases(wh)
local new = {}
for k, f in pairs(wh) do -- "before_Take,Drop..."
if (type(f) == 'function' or type(f) == 'string') and
type(k) == 'string' and k:find("[a-zA-Z]+,") then
local ss, ee = k:find("^[a-z]+_")
local pref = ''
local str = k
if ss then
pref = k:sub(1, ee);
if pref == 'before_' or pref == 'after_' or pref == 'post_' or pref == 'life_' then
str = k:sub(ee + 1)
else
pref = ''
end
end
local m = std.split(str, ",")
for _, v in ipairs(m) do
new[pref .. v] = f
end
end
end
for k, v in pairs(new) do
wh[k] = v
end
end
std.obj.for_plural = function(s, fn)
fn = fn or function() end
if not s:hint'plural' then
fn(s)
return false
end
for _, v in ipairs(mp.multi[s] or { s }) do
fn(v)
end
return true
end
std.obj.__ini = function(s, ...)
if s.__mp_ini then
return __oini(s, ...)
end
if type(s.found_in) == 'string' then
s.found_in = { s.found_in }
end
if type(s.found_in) == 'table' then
for _, v in ipairs(s.found_in) do
local vv = v
v = std.ref(v)
if not v then
std.err("Wrong object in found_in list of: "..tostring(s).."/"..vv, 2)
end
v.obj:add(s)
end
std.rawset(s, 'found_in', nil)
elseif type(s.found_in) == 'function' then
s:persist()
end
if type(s.scope) == 'table' and not std.is_obj('list', s.scope) then
s.scope = std.list (s.scope)
end
fn_aliases(s.__ro)
std.rawset(s, "__mp_ini", true)
return __oini(s, ...)
end
function parent(w)
w = std.object(w)
return w:where()
end
function Class(t, w)
fn_aliases(t)
if not w then
return std.class(t, std.obj)
end
return std.class(t, w)
end
std.obj.once = function(s, n)
if type(n) == 'string' then
n = '__once_'..n
else
n = '__once'
end
if not s[n] then
s[n] = true
return true
end
return false
end
std.obj.daemonStart = function(s)
game.__daemons:add(s)
end
std.obj.daemonStop = function(s)
game.__daemons:del(s)
end
std.obj.isDaemon = function(s)
return game.__daemons:lookup(s)
end
function DaemonStart(w)
std.object(w):daemonStart()
end
function DaemonStop(w)
std.object(w):daemonStop()
end
function isDaemon(w)
return std.object(w):isDaemon()
end
instead.notitle = true
instead.get_title = function(_)
if instead.notitle then
return
end
local w = instead.theme_var('win.w')
local title = std.titleof(std.here()) or ''
local col = instead.theme_var('win.col.fg')
local score = ''
if mp.score then
score = fmt.tab('70%', 'center')..fmt.nb(mp:mesg('TITLE_SCORE') .. tostring(mp.score))
end
local moves = fmt.tab('100%', 'right')..fmt.nb(mp:mesg('TITLE_TURNS') .. tostring(game:time() - 1))
return iface:left((title.. score .. moves).."\n".. iface:img(string.format("box:%dx1,%s", w, col)))
end
--luacheck: globals content
function content(...)
return mp:content(...)
end