diff --git a/parser/mp-en.lua b/parser/mp-en.lua index e686d5c..d1369cc 100644 --- a/parser/mp-en.lua +++ b/parser/mp-en.lua @@ -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 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 = "" 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 = '' mp.keyboard_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 .]] + 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" diff --git a/parser/mp-ru.lua b/parser/mp-ru.lua index 2752cbb..24d2e68 100644 --- a/parser/mp-ru.lua +++ b/parser/mp-ru.lua @@ -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" diff --git a/parser/mp.lua b/parser/mp.lua index 242a2ca..cb60e91 100644 --- a/parser/mp.lua +++ b/parser/mp.lua @@ -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 diff --git a/parser/mplib.lua b/parser/mplib.lua index f733439..c58eef6 100644 --- a/parser/mplib.lua +++ b/parser/mplib.lua @@ -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