This commit is contained in:
vvollo 2021-10-04 22:52:51 +03:00 committed by GitHub
parent bd274293aa
commit 4bb6186dc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 748 additions and 276 deletions

View File

@ -70,7 +70,8 @@ mp.shorten = {
["se"] = "southeast";
["sw"] = "southwest";
["nw"] = "northwest";
["u"] = "up";
["d"] = "down";
}
mp.shorten_expert = {
@ -87,8 +88,13 @@ function mp:skip_filter(w)
end
return true
end
_'@compass'.before_Default = 'Try to verb "go".'
function mp:ignore_filter(w)
if w == 'the' or w == 'a' or w == 'an' then
return true
end
return false
end
_'@compass'.before_Default = function() p('"{#First}" is the direction. You can not ', mp.parsed[1], ' {#firstit}.') end
function mp.msg.SCORE(d)
if d > 0 then
@ -98,19 +104,16 @@ function mp.msg.SCORE(d)
end
end
function mp.msg.MULTIDSC(oo, inv)
if #oo > 0 then
local s = oo[1]
if not s:hint'proper' and not s:hint'surname' then
p "the"
end
end
mp:multidsc(oo, inv)
end
mp.door.word = "door"
mp.msg.TITLE_SCORE = "Score: "
mp.msg.TITLE_TURNS = "Turns: "
mp.msg.TITLE_SCORE = function()
if mp.maxscore then
pr ("Score: ", mp.score, "/", mp.maxscore)
end
pr ("Score: ", mp.score)
end
mp.msg.TITLE_TURNS = function()
pr ("Turns: ", game:time() - 1)
end
mp.msg.YES = "Yes"
mp.msg.WHEN_DARK = "Darkness."
mp.msg.UNKNOWN_THEDARK = "Probably, it is because there is no light?"
@ -118,6 +121,9 @@ mp.msg.COMPASS_NOWAY = "{#Me} can't go that way."
mp.msg.COMPASS_EXAM_NO = "Nothing interesting in that direction."
mp.msg.ENUM = "items."
mp.msg.CUTSCENE_HELP = "Press <Enter> or enter {$fmt em|next} to continue."
if instead.reinstead then
mp.msg.CUTSCENE_MORE = "^{$fmt em|(more)}"
end
mp.msg.DLG_HELP = "Enter number to select the phrase."
mp.msg.NO_ALL = "This verb can not be used with all."
mp.msg.DROPPING_ALL = function(w)
@ -127,14 +133,14 @@ mp.msg.TAKING_ALL = function(w)
pn (iface:em("(taking "..w:the_noun()..")"))
end
mp.msg.TAKE_BEFORE = function(w)
pn (iface:em("(taking "..w:the_noun().." before)"))
pn (iface:em("(taking "..w:the_noun().." first)"))
end
mp.msg.DISROBE_BEFORE = function(w)
pn (iface:em("(disrobing "..w:the_noun().." before)"))
pn (iface:em("(disrobing "..w:the_noun().." first)"))
end
mp.msg.CLOSE_BEFORE = function(w)
pn (iface:em("(closing "..w:the_noun() .. " before)"))
pn (iface:em("(closing "..w:the_noun() .. " first)"))
end
local function str_split(str, delim)
@ -340,10 +346,23 @@ end
mp.msg.enter = "<Enter>"
mp.msg.EMPTY = 'Excuse me?'
mp.msg.UNKNOWN_VERB = "Unknown verb"
mp.msg.UNKNOWN_VERB_HINT = "Maybe you meant"
mp.msg.UNKNOWN_VERB = function(w)
p ("Unknown verb ", iface:em(w), ".")
end
mp.msg.UNKNOWN_VERB_HINT = function(w)
p ("The most similar word is ", iface:em(w), ".")
end
mp.msg.INCOMPLETE = "The sentence must be supplemented."
mp.msg.INCOMPLETE_NOUN = "What do you want to apply the command to"
mp.msg.INCOMPLETE_NOUN = function(w)
if w then
p ('What do you want to apply the command "'..w..'" to?')
else
p "What do you want to apply the command to?"
end
end
mp.msg.INCOMPLETE_SECOND_NOUN = function(w)
p ('Clarify the command: "', w, '"?')
end
mp.msg.UNKNOWN_OBJ = "Here is no such thing"
mp.msg.UNKNOWN_OBJ = function(w)
if not w then
@ -363,9 +382,8 @@ mp.msg.UNKNOWN_WORD = function(w)
end
mp.msg.NOTHING_OBJ = "Nothing."
mp.msg.HINT_WORDS = "Maybe you meant"
mp.msg.HINT_OR = "or"
mp.msg.HINT_AND = "and"
mp.msg.AND = "and"
mp.msg.OR = "or"
mp.msg.MULTIPLE = "Here is"
mp.msg.LIVE_ACTION = function(w)
p (mp:It(w), " would not like it.")
@ -374,11 +392,17 @@ mp.msg.NO_LIVE_ACTION = "{#Me} can only do that to something animate."
mp.msg.NOTINV = function(t)
p (lang.cap(t:the_noun()) .. " must be taken first.")
end
mp.msg.WORN = function(_)
pr (" (worn)")
mp.msg.HAS_WORN = function(_)
return "worn"
end
mp.msg.OPEN = function(_)
pr (" (opened)")
mp.msg.HAS_OPEN = function(_)
return "opened"
end
mp.msg.HAS_ON = function(_)
return "switched on"
end
mp.msg.HAS_LIGHT = function(_)
return "providing light"
end
mp.msg.EXITBEFORE = "May be, {#me} should to {#if_has/#where,supporter,get off,get out of} {#thenoun/#where}."
@ -403,12 +427,34 @@ mp.msg.NOROOM = function(w)
end
mp.msg.Exam.SWITCHSTATE = "{#Thefirst} {#is/#first} switched {#if_has/#first,on,on,off}."
mp.msg.Exam.NOTHING = "nothing."
mp.msg.Exam.IS = "there is"
mp.msg.Exam.ARE = "there are"
mp.msg.Exam.IN = "In {#thefirst}"
mp.msg.Exam.ON = "On {#thefirst}"
mp.msg.Exam.NOTHING = function(w)
p "There is nothing "
if w:has 'supporter' then
mp:pnoun (w, "on {#thefirst}.")
else
mp:pnoun (w, "in {#thefirst}.")
end
end
mp.msg.Exam.CONTENT = function(w, oo)
local single = not oo[1]:hint 'plural'
if std.me():where() == w or std.here() == w then
p "{#Me} can see"
mp:multidsc(oo)
p " here."
return
end
if single then
p "There is"
else
p "There are"
end
mp:multidsc(oo)
if w:has 'supporter' then
mp:pnoun (w, " on {#thefirst}.")
else
mp:pnoun (w, " in {#thefirst}.")
end
end
mp.msg.Exam.DEFAULT = "{#Me} {#does/#me} not see anything unusual in {#thefirst}.";
mp.msg.Exam.SELF = "{#Me} {#does/#me} not see anything unusual in {#yourself/#me}.";
@ -429,7 +475,8 @@ mp.msg.Walk.WALK = "But {#thefirst} {#is/#first} already here."
mp.msg.Walk.NOWHERE = "Where?"
mp.msg.Walk.INV = "{#Me} {#is/#me} holding this."
mp.msg.Enter.EXITBEFORE = "{#Me} {#present/#me,need} to {#if_has/#where,supporter,get off from,leave} {#thenoun/#where} first."
mp.msg.Enter.EXITBEFORE = "{#Me} {#present/#me,need} to "..
"{#if_has/#where,supporter,get off from,leave} {#thenoun/#where} first."
mp.msg.Exit.NOTHERE = "But {#me} {#is/#me} not {#if_has/#first,supporter,on,in} {#thefirst}."
mp.msg.Exit.NOWHERE = "But {#me} {#have/#me} no way to exit."
@ -619,7 +666,8 @@ mp.msg.Answer.EMPTY = "{#Me} can't find anything to answer."
mp.msg.Answer.SELF = "Good answer."
mp.msg.Yes.YES = "That was a rhetorical question."
mp.msg.Buy.USE = "How exactly?"
mp.msg.Buy.BUY = "Nothing is on sale."
mp.msg.Use.USE = "How exactly?"
mp.keyboard_space = '<space>'
mp.keyboard_backspace = '<backspace>'
@ -628,21 +676,21 @@ mp.msg.GAMEOVER_HELP = [[Use restart to restart game.]];
function mp:myself(ob)
if ob:hint'first' then
return { "myself", "me" }
return { "myself", "self", "me" }
end
if ob:hint'second' then
return { "yourself", "me", "myself" }
return { "yourself", "myself", "self", "me" }
end
if ob:hint'plural' then
return { "themselves", "our" }
return { "themselves", "ourselves", "self" }
end
if ob:hint'female' then
return { "herself", "me" }
return { "herself", "myself", "self", "me" }
end
if ob:hint'male' then
return { "himself", "me" }
return { "himself", "myself", "self", "me" }
end
return { "itself" }
return { "itself", "myself", "self", "me" }
end
function mp:it(w)
@ -695,7 +743,8 @@ function mp:before_Enter(w)
return false
end
mp.msg.HELP = [[{$fmt b|INSTRUCTIONS}^^
mp.msg.HELP = function()
p [[{$fmt b|INSTRUCTIONS}^^
Enter your actions in verb noun form. For example:^
> open door^
@ -709,13 +758,27 @@ To examine whole scene, enter "exam" or press "Enter".^
^
To exam your inventory, enter "inv".^
^
Use compass directions to walk. For example: "go north" or "north" or just "n".
^^
You may use the "TAB" key for autocompletion.
]]
Use compass directions to walk. For example: "go north" or "north" or just "n". There are also up and down directions, outside and inside.]]
if not instead.tiny then
p [[^^You may use the "TAB" key for autocompletion.]]
else
p [[^^Use "save" and "load" to save and load game.]]
if instead.tiny then
p [[For ex. "save 1".]]
end
p [[Restart game: "restart".]]
if instead.reinstead then
p [[^^Also available: !restart, !quit, !info, !save, !load and !font <size>.]]
end
end
end
function mp.token.compass1(_)
return "{noun_obj}/@n_to,compass|{noun_obj}/@ne_to,compass|{noun_obj}/@e_to,compass|{noun_obj}/@se_to,compass|{noun_obj}/@s_to,compass|{noun_obj}/@sw_to,compass|{noun_obj}/@w_to,compass|{noun_obj}/@nw_to,compass"
return "{noun_obj}/@n_to,compass|{noun_obj}/@ne_to,compass|"..
"{noun_obj}/@e_to,compass|{noun_obj}/@se_to,compass|"..
"{noun_obj}/@s_to,compass|{noun_obj}/@sw_to,compass|"..
"{noun_obj}/@w_to,compass|{noun_obj}/@nw_to,compass"
end
function mp.token.compass2(_)
@ -724,12 +787,18 @@ end
std.mod_init(function(_)
Verb { "#Walk",
"go,walk,run,enter",
"go,walk,run,enter,come",
"{compass1} : Walk",
"in|into|inside|on {noun}/scene,enterable : Enter",
"{noun}/scene : Walk",
"{compass2}: Walk",
"outside|out|away: Exit" }
"outside|out|away: Exit"
}
Verb { "#Enter",
"enter",
"{noun}/scene,enterable : Enter"
}
Verb { "#Sit",
"sit,stand",
@ -740,7 +809,7 @@ Verb { "#Lie",
"down in|into|inside|on {noun}/scene,enterable : Enter" }
Verb { "#Exit",
"exit,out",
"exit,out,leave",
"?from {noun}/scene : Exit",
": Exit"}
@ -751,6 +820,7 @@ Verb { "#Exam",
"inventory : Inv",
"~ under {noun} : LookUnder",
"~ in|inside|into|through|on {noun} : Search",
"~ ?at {noun} : Exam",
"~ up * in {noun} : Consult reverse",
}
@ -853,6 +923,7 @@ Verb {
Verb {
"#SwitchOff",
"switch",
"off {noun}: SwitchOff",
"~ {noun} off : SwitchOff",
}
@ -979,7 +1050,7 @@ Verb {
Verb {
"#Listen",
"listen.hear",
"listen,hear",
"Listen",
"?to {noun}: Listen",
}
@ -1090,8 +1161,7 @@ Verb {
Verb {
"#Talk",
"talk",
"with {noun}/live : Talk"
"with|to {noun}/live : Talk"
}
Verb {
@ -1180,6 +1250,8 @@ MetaVerb {
"~parser",
"expert on : MetaExpertOn",
"expert off : MetaExpertOff",
"verbs : MetaVerbs",
"version : MetaVersion",
}
MetaVerb {
@ -1219,6 +1291,12 @@ std.mod_start(function()
"MetaUndo",
}
end
if mp.score then
MetaVerb {
"~ счёт",
"MetaScore",
}
end
end)
-- Dialog
std.phr.default_Event = "Exam"

View File

@ -67,7 +67,40 @@ function mp:skip_filter(w)
return true
end
_'@compass'.before_Default = 'Попробуйте глагол "идти".'
local function endswith(w, t)
return not not w:find(t..'$')
end
function mp:verb_filter(w)
if #w > 1 then
return true
end
local utf = mp.utf
local verb = w[1]
local t = utf.chars(w[1])
if endswith(verb, 'ся') or endswith(verb, 'сь') or endswith(verb, 'те') then
local len = #verb
len = len - utf.bb(verb, len)
len = len - utf.bb(verb, len)
verb = verb:sub(1, len)
end
if endswith(verb, 'и') or endswith(verb, 'ь') then
return true
end
local t = utf.chars(verb)
local a = { ['а'] = true, ['е'] = true, ['и'] = true,
['о'] = true, ['у'] = true, ['ы'] = true,
['ю'] = true, ['я'] = true };
local len = #t
if len >= 2 and a[t[len - 1]] and t[len] == 'й' then -- or a[t[len]] then
return true
end
return false
end
_'@compass'.before_Default = function()
p('"{#First}" это направление. {#Firstit/вн} нельзя ', mp.parsed[1], ".")
end
function mp.msg.SCORE(d)
if d > 0 then
@ -77,8 +110,16 @@ function mp.msg.SCORE(d)
end
end
mp.door.word = -"дверь";
mp.msg.TITLE_SCORE = "Счёт: "
mp.msg.TITLE_TURNS = "Ходы: "
mp.msg.TITLE_SCORE = function()
if mp.maxscore then
pr ("Счёт: ", mp.score, "/", mp.maxscore)
else
pr ("Счёт: ", mp.score)
end
end
mp.msg.TITLE_TURNS = function()
pr ("Ходы: ", game:time() - 1)
end
mp.msg.YES = "Да"
mp.msg.WHEN_DARK = "Кромешная тьма."
mp.msg.UNKNOWN_THEDARK = "Возможно, это потому что в темноте ничего не видно?"
@ -86,6 +127,9 @@ mp.msg.COMPASS_NOWAY = "Этот путь недоступен."
mp.msg.COMPASS_EXAM_NO = "В этом направлении не видно ничего примечательного."
mp.msg.ENUM = "шт."
mp.msg.CUTSCENE_HELP = "Для продолжения нажмите <ввод> или введите {$fmt em|дальше}."
if instead.reinstead then
mp.msg.CUTSCENE_MORE = "^{$fmt em|(дальше)}"
end
mp.msg.DLG_HELP = "Для выбора фразы введите цифру."
mp.msg.NO_ALL = "Это действие нельзя применить на всё."
mp.msg.DROPPING_ALL = function(w)
@ -130,11 +174,25 @@ end
mp.msg.enter = "<ввод>"
mp.msg.EMPTY = 'Простите?'
mp.msg.UNKNOWN_VERB = "Непонятный глагол"
mp.msg.UNKNOWN_VERB_HINT = "Возможно, вы имели в виду"
mp.msg.UNKNOWN_VERB = function(w)
p ("Непонятный глагол ", iface:em(w), ".")
end
mp.msg.UNKNOWN_VERB_HINT = function(w)
p ("Самое похожее слово: ", iface:em(w), ".")
end
mp.msg.INCOMPLETE = "Нужно дополнить предложение."
mp.msg.INCOMPLETE_NOUN = "К чему вы хотите применить команду"
mp.msg.INCOMPLETE_SECOND_NOUN = "Уточните команду:"
mp.msg.INCOMPLETE_NOUN = function(w)
if w then
p('К чему вы хотите применить команду "',w, '"?')
else
p"К чему вы хотите применить команду?"
end
end
mp.msg.INCOMPLETE_SECOND_NOUN = function(w)
p ('Уточните команду: "',w,'"?')
end
mp.msg.UNKNOWN_OBJ = function(w)
if not w then
p "Об этом предмете ничего не известно."
@ -152,10 +210,9 @@ mp.msg.UNKNOWN_WORD = function(w)
p ("(",w,"?).")
end
end
mp.msg.HINT_WORDS = "Может быть"
mp.msg.HINT_OR = "или"
mp.msg.HINT_AND = "и"
mp.msg.HINT_WORDS = "Возможно"
mp.msg.AND = "и"
mp.msg.OR = "или"
mp.msg.MULTIPLE = "Тут есть"
mp.msg.LIVE_ACTION = function(w)
p (w:It'дт'," это не понравится.")
@ -166,16 +223,28 @@ mp.msg.NOTINV = function(t)
p (lang.cap(t:noun'вн') .. " сначала нужно взять.")
end
--"надет"
mp.msg.WORN = function(w)
mp.msg.HAS_WORN = function(w)
local hint = w:gram().hint
pr (" (",mp.mrd:word('надет/' .. hint), ")")
return mp.mrd:word('надет/' .. hint)
end
--"открыт"
mp.msg.OPEN = function(w)
mp.msg.HAS_OPEN = function(w)
local hint = w:gram().hint
pr (" (",mp.mrd:word('открыт/' .. hint), ")")
return mp.mrd:word('открыт/' .. hint)
end
mp.msg.EXITBEFORE = "Возможно, {#me/дт} нужно сначала {#if_has/#where,supporter,слезть с {#where/рд}.,покинуть {#where/вн}.}"
--"включён"
mp.msg.HAS_ON = function(w)
local hint = w:gram().hint
return mp.mrd:word('включён/' .. hint)
end
--"светится"
mp.msg.HAS_LIGHT = function(w)
local hint = w:gram().hint
return mp.mrd:word('светится/' .. hint)
end
mp.msg.EXITBEFORE = "Возможно, {#me/дт} нужно сначала "..
"{#if_has/#where,supporter,слезть с {#where/рд}.,покинуть {#where/вн}.}"
mp.default_Event = "Exam"
mp.default_Verb = "осмотреть"
@ -186,7 +255,6 @@ mp.msg.ACCESS2 = "{#Second} отсюда не{#word/доступен,#second}."
mp.msg.Look.HEREIS = "Здесь находится"
mp.msg.Look.HEREARE = "Здесь находятся"
mp.msg.NOROOM = function(w)
if w == std.me() then
p ("У {#me/рд} слишком много вещей.")
@ -199,11 +267,47 @@ end
--"включён"
--"выключен"
mp.msg.Exam.SWITCHSTATE = "{#First} сейчас {#if_has/#first,on,{#word/включён,#first},{#word/выключен,#first}}."
mp.msg.Exam.NOTHING = "ничего нет."
mp.msg.Exam.IS = "находится"
mp.msg.Exam.ARE = "находятся"
mp.msg.Exam.IN = "В {#first/пр,2}"
mp.msg.Exam.ON = "На {#first/пр,2}"
mp.msg.Exam.NOTHING = function(w)
if w:has 'supporter' then
mp:pnoun (w, "На {#first/пр,2}")
else
mp:pnoun (w, "В {#first/пр,2}")
end
p "ничего нет."
end
mp.msg.Exam.CONTENT = function(w, oo)
local single = #oo == 1 and not oo[1]:hint 'plural'
if std.me():where() == w or std.here() == w then
if false then
if single then
p "Здесь находится"
else
p "Здесь находятся"
end
mp:multidsc(oo)
else
p "{#Me} {#word/видеть,#me,нст} здесь";
mp:multidsc(oo, 'вн')
end
p "."
return
end
if w:has 'supporter' then
mp:pnoun (w, "На {#first/пр,2}")
else
mp:pnoun (w, "В {#first/пр,2}")
end
if single then
p "находится"
else
p "находятся"
end
mp:multidsc(oo)
p "."
end
--"видеть"
mp.msg.Exam.DEFAULT = "{#Me} не {#word/видеть,#me,нст} {#vo/{#first/пр}} ничего необычного.";
mp.msg.Exam.SELF = "{#Me} не {#word/видеть,#me,нст} в себе ничего необычного.";
@ -240,7 +344,8 @@ mp.msg.Exit.CLOSED = "Но {#first} {#word/закрыт,#first}."
--"покидать"
--"слезать"
mp.msg.Exit.EXITED = "{#Me} {#if_has/#first,supporter,{#word/слезать с,#me,нст} {#first/рд},{#word/покидать,#me,нст} {#first/вн}}."
mp.msg.Exit.EXITED = "{#Me} {#if_has/#first,supporter,{#word/слезать,#me,нст} {#so/{#first/рд}},"..
"{#word/покидать,#me,нст} {#first/вн}}."
mp.msg.GetOff.NOWHERE = "Но {#me/дт} не с чего слезать."
@ -295,7 +400,10 @@ mp.msg.Take.WORN = "{#First} {#word/надет,#first} на {#firstwhere/вн}."
mp.msg.Take.PARTOF = "{#First} {#if_hint/#first,plural,являются,является} частью {#firstwhere/рд}."
mp.msg.Remove.WHERE = "{#First} не {#word/находиться,#first,нст} {#if_has/#second,supporter,на,в} {#second/пр,2}."
mp.msg.Remove.REMOVE = "{#First} {#if_has/#second,supporter,{#word/поднят с,#first},{#word/извлечён из,#first}} {#second/рд}."
--"поднят"
--"извлечён"
mp.msg.Remove.REMOVE = "{#First} {#if_has/#second,supporter,{#word/поднят с,#first},"..
"{#word/извлечён из,#first}} {#second/рд}."
mp.msg.Drop.SELF = "У {#me/рд} не хватит ловкости."
mp.msg.Drop.WORN = "{#First/вн} сначала нужно снять."
@ -480,13 +588,13 @@ end
function mp:myself(_, hint)
local ww = dict({
["вн"] = "себя";
["дт"] = "себе";
["тв"] = "собой";
["пр"] = "себе";
["рд"] = "себя";
["вн"] = { "себя" };
["дт"] = { "себе" };
["тв"] = {"собой" };
["пр"] = { "себе" };
["рд"] = { "себя" };
}, hint)
return { ww }
return ww
end
function mp:it(w, hint)
@ -560,27 +668,65 @@ function mp:err_noun(noun)
end
function mp.shortcut.vo(hint)
local w = std.split(mp.mrd.lang.norm(hint))
local utf = mp.utf
local vow = lang.is_vowel
local char = utf.char
local excl = {
["льве"] = true,
["львах"] = true,
["льду"] = true,
["льдах"] = true,
["льне"] = true,
["льнах"] = true,
["лбу"] = true,
["лбах"] = true,
["лжи"] = true,
["лжах"] = true,
["мху"] = true,
["мхах"] = true,
["рву"] = true,
["рвах"] = true,
["ржи"] = true,
["ржах"] = true,
["рту"] = true,
["ртах"] = true,
["мне"] = true,
["что"] = true,
}
w = w[#w]
if mp.utf.len(w) > 2 and
(vow(char(w, 1) == 'в' or vow(char(w, 1) == 'ф') and
not vow(char(w, 2)))) or excl[w] then
return "во ".. hint
end
return "в ".. hint
-- local w = std.split(hint)
-- w = w[#w]
-- if mp.utf.len(w) > 2 and
-- (lang.is_vowel(utf.char(w, 1)) or
-- lang.is_vowel(utf.char(w, 2))) then
-- return "в ".. hint
-- end
-- return "во ".. hint
end
function mp.shortcut.so(hint)
local so = {
["с"] = true,
["з"] = true,
["ш"] = true,
["ж"] = true,
["л"] = true,
["р"] = true,
["м"] = true,
}
local w = std.split(mp.mrd.lang.norm(hint))
local utf = mp.utf
w = w[#w]
if utf.len(w) > 2 and
((so[utf.char(w, 1)] and
not lang.is_vowel(utf.char(w, 2))) or utf.char(w, 1) == 'щ') then
return "со ".. hint
end
if utf.len(w) > 2 and utf.char(w, 1) == 'л' and utf.char(w, 2) == 'ь' and
not lang.is_vowel(utf.char(w, 2)) then
return "со ".. hint
end
return "с ".. hint
-- local w = std.split(hint)
-- w = w[#w]
-- if mp.utf.len(w) > 2 and
-- (lang.is_vowel(utf.char(w, 1)) or
-- lang.is_vowel(utf.char(w, 2))) then
-- return "с ".. hint
-- end
-- return "со ".. hint
end
function mp:before_Enter(w)
@ -591,7 +737,8 @@ function mp:before_Enter(w)
return false
end
mp.msg.HELP = [[{$fmt b|КАК ИГРАТЬ?}^^
mp.msg.HELP = function()
p [[{$fmt b|КАК ИГРАТЬ?}^^
Вводите ваши действия в виде простых предложений вида: глагол -- существительное. Например:^
> открыть дверь^
@ -607,14 +754,27 @@ mp.msg.HELP = [[{$fmt b|КАК ИГРАТЬ?}^^
^
Чтобы узнать какие предметы у вас с собой, наберите "инвентарь" или "инв".^
^
Для перемещений используйте стороны света, например: "идти на север" или "север" или просто "с".^
Кроме сторон света можно перемещаться вверх ("вверх" или "вв") и вниз ("вниз" или "вн").
^^
Вы можете воспользоваться клавишей "TAB" для автодополнения ввода.
]]
Для перемещений используйте стороны света, например: "идти на север" или "север" или просто "с". Кроме сторон света можно перемещаться вверх ("вверх" или "вв") и вниз ("вниз" или "вн"), "внутрь" и "наружу".]]
if not instead.tiny then
p [[^^Вы можете воспользоваться клавишей "TAB" для автодополнения ввода.]]
else
p [[^^Вы можете сокращать названия объектов.]]
p [[^^Чтобы сохранять и загружать игру используйте "сохранить" и "загрузить".]]
if instead.tiny then
p [[Например, "сохранить 1".]]
end
p [[Начать заново: "заново".]]
if instead.reinstead then
p [[^^Также доступны команды: !restart, !quit, !info, !save, !load и !font <размер>.]]
end
end
end
function mp.token.compass1(_)
return "{noun_obj}/@n_to,compass|{noun_obj}/@ne_to,compass|{noun_obj}/@e_to,compass|{noun_obj}/@se_to,compass|{noun_obj}/@s_to,compass|{noun_obj}/@sw_to,compass|{noun_obj}/@w_to,compass|{noun_obj}/@nw_to,compass"
return "{noun_obj}/@n_to,compass|{noun_obj}/@ne_to,compass|"..
"{noun_obj}/@e_to,compass|{noun_obj}/@se_to,compass|"..
"{noun_obj}/@s_to,compass|{noun_obj}/@sw_to,compass|"..
"{noun_obj}/@w_to,compass|{noun_obj}/@nw_to,compass"
end
function mp.token.compass2(_)
@ -659,8 +819,8 @@ Verb { "#Exam",
"~ в|во|на {noun}/пр,2 : Search",
"~ внутри {noun}/рд : Search",
"~ в|во {noun}/вн : Search",
"~ в|во {noun}/пр,2 о|об|обо|про * : Consult",
"~ о|об|обо|про * в|во {noun}/пр,2 : Consult reverse",
"~ в|во {noun}/пр,2 ?о|?об|?обо|?про * : Consult",
"~ ?о|?об|?обо|?про * в|во {noun}/пр,2 : Consult reverse",
}
Verb { "#Search",
@ -668,8 +828,8 @@ Verb { "#Search",
"{noun}/вн : Search",
"в|во|на {noun}/пр,2 : Search",
"под {noun}/тв : LookUnder",
"~ в|во {noun}/пр,2 * : Consult",
"~ * в|во {noun}/пр,2 : Consult reverse",
"~ в|во {noun}/пр,2 ?о|?об|?обо|?про * : Consult",
"~ ?о|?об|?обо|?про * в|во {noun}/пр,2 : Consult reverse",
}
Verb { "#Open",
@ -721,7 +881,7 @@ Verb { "#Take",
}
Verb { "#Insert",
"воткн/уть,втык/ать,вставить,влож/ить",
"воткн/уть,втык/ать,вставить,влож/ить,"..
"[|про|за]сун/уть,вставь/",
"{noun}/вн,held в|во {noun}/вн,inside : Insert",
"~ {noun}/вн,held внутрь {noun}/рд : Insert",
@ -742,7 +902,7 @@ Verb { "#Drop",
Verb {
"#ThrowAt",
"брос/ить,выбро/сить,кин/уть,кида/ть,швыр/нуть,метн/уть,метать",
"брос/ить,выбро/сить,кину/ть,кинь/,кида/ть,швыр/нуть,метн/уть,метать",
"{noun}/вн,held : Drop",
"{noun}/вн,held в|во|на {noun}/вн : ThrowAt",
"~ в|во|на {noun}/вн {noun}/вн : ThrowAt reverse",
@ -1098,6 +1258,26 @@ Verb {
}
if DEBUG then
function mp:MetaForm(w)
if not w then return end
local t, hint
w = w:gsub("_", "/")
if w:find "/" then
hint = true
end
for _, f in ipairs { "им", "рд", "дт", "вн", "тв", "пр", "пр,2" } do
local ww = w
if hint then
ww = ww .. ','.. f
else
ww = ww .. '/' .. f
end
t = self.mrd:word(ww)
pn(t, " (", f, ")")
end
end
MetaVerb {
"#MetaWord",
"~_слово",
@ -1119,6 +1299,11 @@ if DEBUG then
"~_дамп",
"MetaDump"
}
MetaVerb {
"#МетаForm",
"~_форм/ы",
"* :MetaForm"
}
end
MetaVerb {
"#MetaTranscript",
@ -1133,6 +1318,8 @@ MetaVerb {
"~парсер",
"эксперт да : MetaExpertOn",
"эксперт нет : MetaExpertOff",
"глаголы : MetaVerbs",
"версия : MetaVersion",
}
MetaVerb {
@ -1173,10 +1360,16 @@ std.mod_start(function()
mp.msg.MetaUndo.EMPTY = "Отменять нечего."
MetaVerb {
"#MetaUndo",
"~отмен/ить",
"~отмен/а",
"MetaUndo",
}
end
if mp.score then
MetaVerb {
"~ счёт",
"MetaScore",
}
end
end)
-- Dialog
std.phr.default_Event = "Exam"

View File

@ -1,5 +1,5 @@
local curdir = std.getinfo(1).source:gsub("^(.+[\\/])[^\\/]+$", "%1"):gsub("^@", "");
local table = std.table
require "fmt"
require "snapshots"
@ -97,6 +97,38 @@ local function utf_chars(b)
return res
end
local function utf_similar(str1, str2, lev)
local chars1 = utf_chars(str1)
local chars2 = utf_chars(str2)
local len1 = #chars1
local len2 = #chars2
if len1 < lev or len2 < lev then
return false
end
for i = 0, len2 - lev do
local ok = true
for k = 1, lev do
if chars1[k] ~= chars2[i + k] then
ok = false
break
end
end
if ok then return true end
end
for i = 0, len1 - lev do
local ok = true
for k = 1, lev do
if chars2[k] ~= chars1[i + k] then
ok = false
break
end
end
if ok then return true end
end
return false
end
--- Returns the Levenshtein distance between the two given strings.
-- https://gist.github.com/Badgerati/3261142
@ -223,8 +255,11 @@ end
mp = std.obj {
nam = '@metaparser';
started = false;
score = false;
maxscore = false;
expert_mode = true;
strict_mode = false;
autohelp = false;
autohelp_limit = 1000;
autohelp_noverbs = false;
@ -237,7 +272,7 @@ mp = std.obj {
detailed_inv = false;
daemons = std.list {};
{
version = "1.11";
version = "2.2.2";
cache = { tokens = {}, nouns = false };
scope = std.list {};
logfile = false;
@ -254,6 +289,7 @@ mp = std.obj {
ff = std.rawget(_G, 'utf8_next') or utf_ff;
len = std.rawget(_G, 'utf8_len') or utf_len;
char = std.rawget(_G, 'utf8_char') or utf_char;
chars = utf_chars;
};
lev_thresh = 3;
lev_ratio = 0.20;
@ -611,24 +647,29 @@ function mp.token.noun(w)
for _, o in ipairs(oo) do
local d = {}
local r = o:noun(attr, d)
for k, v in ipairs(d) do
local hidden = (k ~= 1) or w.hidden
if o:has 'multi' then
hidden = w.hidden or (v.idx ~= 1)
end
if o == std.me() and mp.myself then
for _, vm in ipairs(mp:myself(o, w.morph)) do
table.insert(ww, { optional = w.optional, word = vm, morph = attr, ob = o, alias = o.alias, hidden = hidden })
end
break
else
table.insert(ww, { optional = w.optional, word = r[k], ob = o, morph = attr, alias = v.alias, hidden = hidden })
if o == std.me() and mp.myself then
for _, vm in ipairs(mp:myself(o, w.morph) or {}) do
table.insert(ww, { optional = w.optional, word = vm, morph = attr, ob = o, alias = o.alias,
hidden = w.hidden or _ ~= 1 })
end
end
if o == mp.first_it then
table.insert(syms, 1, o)
elseif o == mp.second_it then
table.insert(syms, o)
if o ~= std.me() or (not o:hint'first' and not o:hint'second') then
for k, v in ipairs(d) do
local hidden = (k ~= 1) or w.hidden
if o:has 'multi' then
hidden = w.hidden or (v.idx ~= 1)
end
table.insert(ww,
{ optional = w.optional,
word = r[k], ob = o,
morph = attr, alias = v.alias,
hidden = hidden })
end
if o == mp.first_it then
table.insert(syms, 1, o)
elseif o == mp.second_it then
table.insert(syms, o)
end
end
end
@ -689,6 +730,11 @@ function mp:eq(t1, t2, lev)
return self:__startswith(t2, t)
end
if lev then
t1 = self:norm(t1)
t2 = self:norm(t2)
if not utf_similar(t1, t2, 3) then -- 3 is hardcoded
return false
end
local l = utf_lev(t1, t2)
if l < lev and l / (utf_len(t1) + utf_len(t2)) <= self.lev_ratio then
return l
@ -708,7 +754,7 @@ end
function mp:pattern(t, delim)
local words = {}
local pat = str_split(self:norm(t), delim or "|")
local pat = str_split(t, delim or "|")
for _, v in ipairs(pat) do
local w = { }
local ov = v
@ -939,6 +985,14 @@ function mp:skip_filter()
return true
end
function mp:ignore_filter(w)
return false
end
function mp:verb_filter(words)
return true
end
function mp:lookup_verb(words, lev)
local ret = {}
local w = self:verbs()
@ -947,9 +1001,20 @@ function mp:lookup_verb(words, lev)
for _, vv in ipairs(v.verb) do
local verb = vv.word .. (vv.morph or "")
local i, len, rlev
i, len, rlev = word_search(words, verb, lev and self.lev_thresh)
if not i and not lev and verb ~= vv.word then
i, len = self:lookup_short(words, vv.word)
local vwords = mp.strict_mode and { words[1] } or words
i, len, rlev = word_search(vwords, verb, lev and self.lev_thresh)
if not i and not lev and vv.morph then
i, len = self:lookup_short(vwords, vv.word)
if i then
local v = {}
for k = i, i + len - 1 do
table.insert(v, words[k])
end
if verb:find(table.concat(v, ' '), 1, true) ~= 1 and
not self:verb_filter(v) then
i = false
end
end
end
if i and i > 1 and not self:skip_filter({words[i - 1]}) then
i = nil
@ -1194,11 +1259,12 @@ function mp:compl_filter(v)
hidden = not v.ob.hint_noun
end
end
if hidden and self.compl_thresh == 0 then
local _, pre = self:compl_ctx()
local nsym = mp.utf.len(pre)
if hidden and self.compl_thresh == 0 and nsym == 0 then
return false
end
local _, pre = self:compl_ctx()
if mp.utf.len(pre) < self.compl_thresh then
if nsym < self.compl_thresh then
return false
end
if not v.ob or not v.morph then
@ -1304,6 +1370,7 @@ function mp:compl_ctx_poss()
table.insert(res, v)
end
end
res.eol = ctx.eol
return res
end
@ -1318,6 +1385,7 @@ function mp:compl(str)
collectgarbage("stop")
self:compl_ctx_current();
poss = self:compl_ctx_poss()
eol = poss.eol
if (#poss == 0 and e) or #words == 0 then -- no context
if #words == 0 or (#words == 1 and not e) then -- verb?
poss, eol = self:compl_verb(words)
@ -1333,13 +1401,16 @@ function mp:compl(str)
end
else -- matches
self.cache.nouns = self:nouns()
poss, eol, vargs = self:compl_match(words)
poss, eol = self:compl_match(words)
end
poss.eol = eol
self:compl_ctx_push(poss)
end
local _, pre = self:compl_ctx()
for _, v in ipairs(poss) do
if v.word == '*' then vargs = true end
if v.word == '*' and not v.hidden then
vargs = true
end
if self:startswith(v.word, pre) and not v.word:find("%*$") then
if not dups[v.word] then
dups[v.word] = v
@ -1462,7 +1533,7 @@ function mp:match(verb, w, compl)
table.insert(parsed_verb, fixed_verb)
for _, d in ipairs(verb.dsc) do -- verb variants
-- local was_noun = false
local match = { args = {}, vargs = {}, ev = d.ev, wildcards = 0, verb = parsed_verb, defaults = 0 }
local match = { args = {}, vargs = {}, skip = 0, ev = d.ev, wildcards = 0, verb = parsed_verb, defaults = 0 }
local a = {}
found = (#d.pat == 0)
for k, v in ipairs(w) do
@ -1478,7 +1549,7 @@ function mp:match(verb, w, compl)
local vargs
for lev, v in ipairs(d.pat) do -- pattern arguments
if v == '*' or v == '~*' then
vargs = true -- found
vargs = v -- found
v = '*'
end
local noun = not not v:find("^~?{noun}")
@ -1496,19 +1567,20 @@ function mp:match(verb, w, compl)
need_required = true
all_optional = false
end
if pp.default then
default = pp.default
if default then
word = pp.word
default = true
end
local new_wildcard
local k, len = word_search(a, pp.word)
if not k and mp.compare_len > 0 then
if not k and mp.compare_len > 0 and not pp.synonym then
k, len = word_search(a, pp.word, starteq)
new_wildcard = true
else
new_wildcard = false
end
if k and ((k < best or len > best_len) or
if (not required or mp.strict_mode) and k ~= 1 then k = false end -- ?word is only in 1st pos
if k and ((k < best or (k == best and len > best_len)) or
(not new_wildcard and wildcard and k <= best and len >= best_len)) then
wildcard = new_wildcard
best = k
@ -1562,19 +1634,27 @@ function mp:match(verb, w, compl)
break
end
rlev = rlev + 1
end
if (wildcard or match.wildcards > 0) and best > 1 then -- do not skip words if wildcard used
found = false
vargs = false
break
end
-- if false then
-- a = tab_exclude(a, best, best + best_len - 1)
-- else
-- if not was_noun then
if not vargs then
match.skip = match.skip + (best - 1)
for i = 1, best - 1 do
table.insert(skip, a[i])
end
end
-- end
a = tab_sub(a, best + best_len)
-- table.remove(a, 1)
-- end
vargs = false
table.insert(match, word)
table.insert(match.args, found)
if wildcard then
@ -1600,9 +1680,14 @@ function mp:match(verb, w, compl)
else
found = false
if #a > 0 or #match.vargs > 0 then
table.insert(hints, { word = v, lev = rlev })
while #a > 0 do
table.insert(match.vargs, a[1])
table.insert(match, a[1])
table.remove(a, 1)
end
table.insert(hints, { word = v, lev = rlev, match = match })
else
table.insert(hints, { word = '*', lev = rlev })
table.insert(hints, { word = vargs, lev = rlev, match = match })
end
end
if not found then
@ -1618,10 +1703,14 @@ function mp:match(verb, w, compl)
end
end
if not compl and mp.errhints then
local objs = {}
for _, pp in ipairs(pat) do -- single argument
if mp.utf.len(pp.word) >= 3 then
if not pp.synonym and not objs[pp.ob or 0] then
local k, _ = word_search(a, pp.word, self.lev_thresh)
if k then table.insert(hints, { word = pp.word, lev = rlev, fuzzy = true, match = match }) end
if k then
table.insert(hints, { word = pp.word, lev = rlev, fuzzy = true, match = match })
objs[pp.ob or 1] = true
end
end
end
end
@ -1634,7 +1723,11 @@ function mp:match(verb, w, compl)
match.defaults = match.defaults + 1
end
end
table.insert(match.args, { word = false, optional = true } )
if default then
table.insert(match.args, { word = word, default = true } )
else
table.insert(match.args, { word = false, optional = true } )
end
-- table.insert(hints, { word = v, lev = rlev })
found = true
end
@ -1645,29 +1738,39 @@ function mp:match(verb, w, compl)
-- end
if found or all_optional then
match.extra = (#a ~= 0)
table.insert(match, 1, fixed_verb) -- w[verb.verb_nr])
if self:skip_filter(skip) then
table.insert(matches, match)
end
if #match.vargs == 0 and not vargs then
match.vargs = false
if not match.extra or match.wildcards == 0 then
table.insert(match, 1, fixed_verb) -- w[verb.verb_nr])
if self:skip_filter(skip) then
table.insert(matches, match)
end
if #match.vargs == 0 and not vargs then
match.vargs = false
end
end
end
end
for k, v in ipairs(matches) do
v.nr = k
--[[
if false then
print("-----------", k)
for kk, vv in ipairs(v) do
print(vv)
end
end
]]--
end
table.sort(matches,
function(a, b)
local na, nb = #a - a.defaults, #b - b.defaults
if not a.extra and a.skip == 0 then
na = na + 100
end
if not b.extra and b.skip == 0 then
nb = nb + 100
end
if na == nb and a.wildcards == b.wildcards then
return a.nr < b.nr
end
@ -1969,6 +2072,14 @@ function mp:correct(inp)
if rinp ~= '' then rinp = rinp .. ' ' end
rinp = rinp .. v
end
local strip_inp = str_split(inp, inp_split)
inp = ''
for _, v in ipairs(strip_inp) do
if not mp:ignore_filter(v) then
if inp ~= '' then inp = inp .. ' ' end
inp = inp .. v
end
end
local cmprinp = rinp:gsub("["..inp_split.."]+", " ")
if not self:eq(cmprinp, inp) then
pn(fmt.em("("..rinp..")"))
@ -1989,7 +2100,7 @@ function mp:show_prompt(inp)
if std.cmd[1] == 'look' then
return false
end
if std.here():has 'cutscene' or std.here():has 'noprompt' or player_moved() or std.abort_cmd then
if std.here():has 'cutscene' or std.here():has 'noprompt' then
return false
end
if self.prompt then
@ -2018,7 +2129,7 @@ function mp:parse(inp)
mp:log("> "..inp)
local noprompt = not mp:show_prompt(inp)
local prompt = mp:show_prompt(inp)
inp = inp:gsub("[ ]+", " "):gsub("["..inp_split.."]+", " "):gsub("[ \t]+$", "")
@ -2044,7 +2155,7 @@ function mp:parse(inp)
return r
end
else
if std.cmd[1] ~= 'look' and not noprompt then
if std.cmd[1] ~= 'look' and prompt ~= false then
self:correct(inp)
end
-- here we do action
@ -2054,12 +2165,18 @@ function mp:parse(inp)
end
std.world.display = function(s, state)
local l, av, pv
if mp.text == '' and game:time() == 1 and state ~= false then
local l, av, pv, first
if not mp.started and mp.text == '' and game:time() == 1 and state ~= false then
local r = std.call(game, 'dsc')
if type(r) == 'string' then
mp.text = r .. '^^'
first = true
if mp._pager_mode then
mp.text = fmt.anchor() .. r .. '^^' -- .. fmt.anchor()
else
mp.text = r .. '^^'
end
end
mp.started = true
end
if mp.clear_on_move and game:time() ~= 1 then
if player_moved() then mp:clear() end
@ -2080,8 +2197,10 @@ std.world.display = function(s, state)
l = std.par(std.scene_delim, reaction or false,
av or false, l or false,
pv or false) or ''
mp:log(l)
if mp._pager_mode then
if l ~= '' then
mp:log(l)
end
if mp._pager_mode and not first then
mp.text = mp.text .. fmt.anchor() .. l .. '^^' -- .. fmt.anchor()
else
mp.text = mp.text .. l .. '^^' -- .. fmt.anchor()
@ -2268,6 +2387,19 @@ function mp:shorten_input(w)
end
end
function mp:strip_input(w)
local i = 1
local len = #w
while i < len do
if mp:ignore_filter(w[i]) then
table.remove(w, i)
len = len - 1
else
i = i + 1
end
end
end
function mp:input(str)
-- self.cache = { tokens = {} };
local hints = {}
@ -2285,6 +2417,7 @@ function mp:input(str)
if not str then return false end
end
local w = str_split(str, inp_split)
mp:strip_input(w)
mp:shorten_input(w)
self.words = w
if #w == 0 then
@ -2452,12 +2585,13 @@ function(cmd)
std.game:__start()
end
if mp:noparser() then
return true, false
return
end
-- mp.inp = mp:docompl(mp.inp)
local r, v, n
repeat
if n then
std.busy(true)
std.abort_cmd = false
std.me():moved(false)
std.me():need_scene(false)
@ -2465,6 +2599,7 @@ function(cmd)
r, v = mp:key_enter(cmd[1] == 'look')
n = true
until not mp:autoplay_pending() or mp:noparser()
std.busy(false)
mp:onedit()
return r, v
end
@ -2481,7 +2616,7 @@ function mp:autoscript(w)
end
self.autoplay = io.open(w or 'autoscript') or false
if self.autoplay then
self:MetaTranscriptOn();
-- self:MetaTranscriptOn();
std.cmd = { 'autoscript' }
return true
end
@ -2491,6 +2626,7 @@ end
std.mod_init(
function()
if DEBUG and mp.undo == 0 then mp.undo = 5 end
mp:pager_mode(true)
_'game'.__daemons = std.list {}
end)
@ -2516,10 +2652,10 @@ instead.mouse_filter(0)
-- speedup undo
local obusy = std.busy
local busy_count = 0
function std.busy()
function std.busy(b)
busy_count = busy_count + 1
if (busy_count % 100) == 0 then
obusy()
if not b or (busy_count % 100) == 0 then
obusy(b)
end
end
function instead.fading()
@ -2818,5 +2954,15 @@ function std.obj:has(attr)
end
function iface:title(t)
return(iface:bold( mrd.lang.cap(t)))
return(iface:bold(mrd.lang.cap(t)))
end
std.getmt("").__pow = function(a, b)
if b then
if std.is_obj(b) then
return b ^ a
end
return std.rawequal(a, b)
end
return false
end

View File

@ -2,7 +2,9 @@
--luacheck: no self
local tostring = std.tostr
local table = std.table
local type = type
local string = string
--- Error handler
-- @param err error code
function mp:err(err)
@ -25,15 +27,14 @@ function mp:err(err)
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 "")), "?")
mp:message('UNKNOWN_VERB', self.words[verb.verb_nr])
mp:message('UNKNOWN_VERB_HINT', 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]), ".")
mp:message('UNKNOWN_VERB', self.words[1])
end
elseif err == "EMPTY_INPUT" then
p (mp:mesg('EMPTY') or "Empty input.")
@ -47,10 +48,10 @@ function mp:err(err)
verb = verb .. vv .. ' '
end
verb = verb:gsub(" $", "")
for _, vv in ipairs(self.hints.match) do
verb = verb .. ' '.. vv
end
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
@ -96,33 +97,38 @@ function mp:err(err)
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, "\"?")
mp:message('INCOMPLETE_SECOND_NOUN', second_noun .." " ..mp:err_noun(need_noun))
else
p (mp:mesg('INCOMPLETE_NOUN'), "?")
mp:message('INCOMPLETE_NOUN', parsed)
end
else
mp:message 'INCOMPLETE'
end
end
if not mp.errhints then
if not mp.errhints or need_noun then
return
end
local words = {}
local dups = {}
for _, v in ipairs(self.hints) do
if v:find("^~?{noun}") or v == '*' then
if v:find("^~?{noun}") or v == '*' or v == '~*' then
if v:sub(1,1) == '~' then v = v:sub(2) end
v = mp:err_noun(v)
if not dups[v] and not need_noun then
if not dups[v] then
table.insert(words, v)
dups[v] = true
end
else
local pat = self:pattern(v)
local empty = true
for _, vv in ipairs(pat) do
if not vv.hidden and not dups[vv.word] then
if not vv.hidden then
empty = false
break
end
end
for _, vv in ipairs(pat) do
if (empty or not vv.hidden) and not dups[vv.word] then
table.insert(words, vv.word)
dups[vv.word] = true
end
@ -139,14 +145,14 @@ function mp:err(err)
end
p (mp:mesg 'HINT_WORDS', ", ", parsed or '')
else
p (mp:mesg 'HINT_WORDS', " ")
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, " ")
pr (" ", mp.msg.OR, " ")
else
pr (", ")
end
@ -160,7 +166,7 @@ function mp:err(err)
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])
pr (" ", mp.msg.AND, " ", self.multi[k])
else
pr (", ", self.multi[k])
end
@ -234,11 +240,11 @@ mp.door = std.class({
end;
}, std.obj):attr 'enterable,openable,door'
local function pnoun(noun, m, ...)
function mp:pnoun(noun, msg)
local ctx = mp:save_ctx()
mp.first = noun
mp.first_hint = noun:gram().hint
mp:message(m, ...)
std.p(mp.fmt(msg)) -- first is available only here, so fmt is forced
mp:restore_ctx(ctx)
end
@ -454,14 +460,25 @@ std.obj.display = function(s)
return c
end
local last_gfx = false
std.player.look = function(s)
local scene
local scene, img
local r = s:where()
if s:need_scene() then
local gfx
gfx = std.call(std.here(), 'gfx') or std.call(std.game, 'gfx')
if not gfx and instead.tiny then
gfx = stead.call(std.here(), 'pic') or stead.call(std.ref 'game', 'pic')
end
if gfx and gfx ~= last_gfx then
img = fmt.c(fmt.img(gfx))
last_gfx = gfx
end
scene = r:scene()
end
return (std.par(std.scene_delim, scene or false, r:display() or false))
end;
return (std.par(std.scene_delim, img or false, scene or false, r:display() or false, std.call(mp, 'footer') or false))
end
--
local function check_persist(w)
@ -760,20 +777,64 @@ mp.compass_dir = function(_, w, dir)
return w ^ ('@'..dir)
end
mp.msg.MULTIDSC = function(oo, inv)
return mp:multidsc(oo, inv)
mp.msg.INFODSC = function(o)
return mp:infodsc(o)
end
mp.detailed_attr = {
{ 'worn' },
{ 'open', 'openable'},
-- { 'on', 'switchable'},
-- { 'light' }
}
function mp:infodsc(ob)
local info = {}
for _, v in ipairs(self.detailed_attr) do
local hit = #v > 0
for _, vv in ipairs(v) do
if ob:hasnt(vv) then
hit = false
break
end
end
if hit then
local n = 'HAS_'..string.upper(v[1])
if mp.msg[n] then
table.insert(info, mp:mesg(n, ob))
end
end
end
if #info > 0 then
pr(" (")
for k, i in ipairs(info) do
if #info > 1 and k == #info then
pr(' ', mp.msg.AND, ' ')
elseif k > 1 then
pr(", ")
end
pr(i)
end
pr(")")
end
end
function mp:multidsc(oo, inv)
local t = {}
local dup = {}
local hint = type(inv) == 'string' and inv or ''
for _, v in ipairs(oo) do
local n
if not v:has'concealed' then
if inv then
if inv == true then
n = std.call(v, 'inv')
end
n = n or v:noun(1)
if type(v.a_noun) == 'function' then
n = n or v:a_noun(hint, 1)
else
n = n or v:noun(hint, 1)
end
if dup[n] then
dup[n] = dup[n] + 1
else
@ -793,17 +854,12 @@ function mp:multidsc(oo, inv)
end
end
if dup[v] > 1 then
pr (vv.ob:noun(self.mrd.lang.gram_t.plural, 1), " (", dup[v], " ", mp.msg.ENUM, ")")
pr (ob:noun(hint .. ','..self.mrd.lang.gram_t.plural, 1), " (", dup[v], " ", mp:mesg('ENUM', dup[v], ob), ")")
else
pr (v)
if ob:has'worn' then
pr(mp:mesg('WORN', ob))
elseif ob:has'openable' and ob:has'open' then
pr(mp:mesg('OPEN', ob))
end
pr(mp:mesg('INFODSC', ob))
end
end
p "."
end
-- Default priority in content
@ -906,38 +962,10 @@ function mp:content(w, exam)
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'
mp:message ('Exam.NOTHING', w)
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:message('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:message('MULTIDSC', oo)
mp:message('Exam.CONTENT', w, oo)
end
-- expand?
for _, o in ipairs(expand) do
@ -949,6 +977,14 @@ end
std.room:attr 'enterable,light'
function mp:strip(r)
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
return r
end
function mp:step()
local old_daemons = {}
game.__daemons:for_each(function(o)
@ -971,19 +1007,11 @@ function mp:step()
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
local r = mp:strip(std.pget())
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
r = mp:strip(s:display(true))
s:lastreact(s:reaction() or false)
s:lastdisp(r)
std.pr(r)
@ -991,12 +1019,19 @@ function mp:step()
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)
if (self.event and self.event:find("Meta", 1, true)) or self:comment() or self:noparser() then
if std.abort_cmd then
return
end
local s = std.game
local r = mp:strip(std.pget())
s:reaction(r or false)
std.pclr()
r = mp:strip(s:display(self:noparser()))
s:lastdisp(r)
s:lastreact(s:reaction() or false)
std.pr(r)
std.abort_cmd = true
return
end
if mp.undo > 0 then
@ -1015,16 +1050,6 @@ function mp:post_action()
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
@ -1288,11 +1313,7 @@ function mp:detailed_Inv(wh, indent)
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
mp:message('INFODSC', o)
pn()
if o:has'supporter' or o:has'container' then
mp:detailed_Inv(o, indent + 1)
@ -1322,7 +1343,8 @@ function mp:after_Inv()
mp:detailed_Inv(std.me(), 1)
else
p()
mp:message('MULTIDSC', oo, true)
mp:multidsc(oo, true)
p "."
end
end
@ -1393,7 +1415,7 @@ function mp:mesg(m, ...)
for _, n in ipairs(t) do
m = m[n]
if not m then
std.err("Wrong message id", 2)
std.err("Wrong message id: "..tostring(n), 2)
end
end
if type(m) ~= 'function' then
@ -1634,6 +1656,14 @@ local function cont_taken(ob, taken)
end
end
--- Check if object is part of parent
-- @param w what
function mp:partof(w)
return w:where() and not w:where():type'room' and
not w:where():has'container' and
not w:where():has'supporter'
end
function mp:TakeAll(wh)
local empty = true
wh = wh or std.me():where()
@ -1643,7 +1673,8 @@ function mp:TakeAll(wh)
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
and not cont_taken(o, taken)
and not mp:partof(o) then
empty = false
mp:message('TAKING_ALL', o)
mp:subaction('Take', o)
@ -1692,9 +1723,7 @@ function mp:Take(w, wh)
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 mp:partof(w) then
if w:has'worn' and mp:animate(w:where()) then
mp:message 'Take.WORN'
else
@ -1724,6 +1753,10 @@ function mp:Remove(w, wh)
mp:message 'Remove.WHERE'
return
end
if wh == std.me() then
mp:xaction('Disrobe', w, wh)
return
end
mp:xaction('Take', w, wh)
end
@ -2783,6 +2816,12 @@ function mp:MetaHelp()
pn(mp:mesg 'HELP')
end
function mp:MetaScore()
mp:message'TITLE_TURNS'
pn()
mp:message'TITLE_SCORE'
end
function mp:MetaTranscript()
if self.logfile then
p("Log file: ", self.logfile)
@ -2812,6 +2851,21 @@ function mp:MetaTranscriptOn()
self.lognum = self.lognum + 1
end
end
function mp:MetaVersion()
p(mp.version)
end
function mp:MetaVerbs()
local verbs = {}
for _, v in ipairs(mp:verbs()) do
local vv = v.verb[1]
if vv and not vv.hidden then
local verb = vv.word .. (vv.morph or "")
table.insert(verbs, verb)
end
end
table.sort(verbs)
for _, v in ipairs(verbs) do p(v) end
end
mp.msg.MetaRestart = {}
@ -3091,13 +3145,14 @@ instead.get_title = function(_)
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).."/"..tostring(mp.maxscore))
score = fmt.tab('70%', 'center')..fmt.nb(mp:mesg('TITLE_SCORE'))
end
local moves = fmt.tab('100%', 'right')..fmt.nb(mp:mesg('TITLE_TURNS') .. tostring(game:time() - 1))
local moves = fmt.tab('100%', 'right')..fmt.nb(mp:mesg('TITLE_TURNS'))
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(...)
function content(w, ...)
w = std.object(w)
return mp:content(w, ...)
end