diff --git a/morph/lang-ru.lua b/morph/lang-ru.lua index eeec796..b519e97 100644 --- a/morph/lang-ru.lua +++ b/morph/lang-ru.lua @@ -338,6 +338,7 @@ local function gram_score(an, g) g = gram_norm(g) if an["фам"] then score = score - 0.1 end if an["арх"] then score = score - 0.1 end + if not g["од"] and not g["но"] and an["од"] and not an["но"] then score = score - 0.1 end for _, vv in ipairs(g or {}) do if vv:sub(1, 1) == '~' then vv = vv:sub(2) @@ -400,6 +401,14 @@ lang = { yo = false, "деревьях/пр"; "деревьям/дт"; }; + ["листья/мр,но,мн,С"] = { + "листья/им"; + "листья/вн"; + "листьев/рд"; + "листьями/тв"; + "листьях/пр"; + "листьям/дт"; + }; ["дерево/ср,но,С"] = { "дерево/им", "деревья/им,мн"; "дерево/вн", "деревья/вн,мн"; diff --git a/morph/mrd.lua b/morph/mrd.lua index 58149d4..c7d3402 100644 --- a/morph/mrd.lua +++ b/morph/mrd.lua @@ -605,9 +605,9 @@ end function mrd:dict(dict, word) if not dict then return end local tab = {} - local w, hints = str_hint(word) + local wrd, hints = str_hint(word) hints = str_split(hints, ",") - local tt = dict[w] + local tt = dict[wrd] if not tt then return diff --git a/parser/mp-en.lua b/parser/mp-en.lua index ba6a80b..3ae08c4 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 @@ -97,9 +103,17 @@ function mp.msg.SCORE(d) pn ("{$fmt em|(Score is decreased by ", d, ")}") end 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?" @@ -110,20 +124,20 @@ mp.msg.CUTSCENE_HELP = "Press or enter {$fmt em|next} to continue." 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) - pn (iface:em("(dropping "..w:noun'вн'..")")) + pn (iface:em("(dropping "..w:the_noun()..")")) end mp.msg.TAKING_ALL = function(w) - pn (iface:em("(taking "..w:noun'вн'..")")) + 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) @@ -136,7 +150,7 @@ end function mp.shortcut.thenoun(hint) local w = str_split(hint, ",") - if #w ~= 2 then + if #w ~= 1 then return "" end local ob = mp:shortcut_obj(w[1]) @@ -329,10 +343,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 @@ -352,10 +379,9 @@ 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.MULTIPLE = "Here are" +mp.msg.OR = "or" +mp.msg.MULTIPLE = "Here is" mp.msg.LIVE_ACTION = function(w) p (mp:It(w), " would not like it.") end @@ -363,11 +389,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}." @@ -379,7 +411,7 @@ mp.msg.ACCESS1 = "{#Thefirst} {#is/#first} not accessible from here." mp.msg.ACCESS2 = "{#Thesecond} {#is/#second} not accessible from here." mp.msg.Look.HEREIS = "Here is" -mp.msg.Look.HEREARE = "Here are" +mp.msg.Look.HEREARE = "Here is" mp.msg.NOROOM = function(w) if w == std.me() then @@ -392,12 +424,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}."; @@ -409,7 +463,7 @@ mp.msg.Enter.ALREADY = "{#Me} {#is/#me} already {#if_has/#first,supporter,on,in} mp.msg.Enter.INV = "{#Me} {#is/#me} unable to enter the thing {#me} {#is/#me} holding." mp.msg.Enter.IMPOSSIBLE = "But {#me} {#is/#me} unable to enter in/on {#thefirst}." mp.msg.Enter.CLOSED = "{#Thefirst} {#is/#first} closed and {#me} can't enter there." -mp.msg.Enter.ENTERED = "{#Me} {#word/залезать,нст,#me} {#if_has/#first,supporter,на,в} {#first/вн}." +mp.msg.Enter.ENTERED = "{#Me} {#present/#me,get} {#if_has/#first,supporter,on,into} {#thefirst}." mp.msg.Enter.DOOR_NOWHERE = "{#Thefirst} {#present/#first,lead} nowhere." mp.msg.Enter.DOOR_CLOSED = "{#Thefirst} {#is/#first} closed." @@ -418,7 +472,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} {#thefirst} 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." @@ -432,7 +487,7 @@ mp.msg.Inv.INV = "{#Me} {#have/#me}" mp.msg.Open.OPEN = "{#Me} {#present/#me,open} {#thefirst}." mp.msg.Open.NOTOPENABLE = "{#Thefirst} {#is/#first} not openable." -mp.msg.Open.WHENOPEN = "{#Thenoun/first/} {#is/#first} already opened." +mp.msg.Open.WHENOPEN = "{#Thenoun/#first} {#is/#first} already opened." mp.msg.Open.WHENLOCKED = "It's seems that {#thefirst} {#is/#first} locked." mp.msg.Close.CLOSE = "{#Me} {#present/#me,close} {#thefirst}." @@ -608,7 +663,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 = '' @@ -617,21 +673,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) @@ -684,7 +740,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^ @@ -698,13 +755,17 @@ 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.]] + 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(_) @@ -713,15 +774,29 @@ end std.mod_init(function(_) Verb { "#Walk", - "go,walk,run,enter", + "go,walk,run,enter,come", "{compass1} : Walk", - "in|into|inside {noun}/scene,enterable : Enter", + "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", + "?down in|into|inside|on {noun}/scene,enterable : Enter" } + +Verb { "#Lie", + "lie", + "down in|into|inside|on {noun}/scene,enterable : Enter" } Verb { "#Exit", - "exit,out", + "exit,out,leave", "?from {noun}/scene : Exit", ": Exit"} @@ -732,6 +807,7 @@ Verb { "#Exam", "inventory : Inv", "~ under {noun} : LookUnder", "~ in|inside|into|through|on {noun} : Search", + "~ ?at {noun} : Exam", "~ up * in {noun} : Consult reverse", } @@ -818,6 +894,7 @@ Verb { Verb { "#Remove", + "remove", "~ {noun}/held : Disrobe", "{noun} from {noun} : Remove", "~ {noun}/scene : Take", @@ -833,6 +910,7 @@ Verb { Verb { "#SwitchOff", + "switch", "off {noun}: SwitchOff", "~ {noun} off : SwitchOff", } @@ -959,7 +1037,7 @@ Verb { Verb { "#Listen", - "listen.hear", + "listen,hear", "Listen", "?to {noun}: Listen", } @@ -1070,8 +1148,7 @@ Verb { Verb { "#Talk", "talk", - "with {noun}/live : Talk" - + "with|to {noun}/live : Talk" } Verb { @@ -1160,6 +1237,8 @@ MetaVerb { "~parser", "expert on : MetaExpertOn", "expert off : MetaExpertOff", + "verbs : MetaVerbs", + "version : MetaVersion", } MetaVerb { @@ -1199,12 +1278,18 @@ std.mod_start(function() "MetaUndo", } end + if mp.score then + MetaVerb { + "~ счёт", + "MetaScore", + } + end end) -- Dialog std.phr.default_Event = "Exam" Verb ({"~ say", "{select} : Exam" }, std.dlg) -Verb ({'#Next', "more|next", "Next" }, mp.cutscene) +Verb ({'#Next', "more,next", "Next" }, mp.cutscene) Verb ({'#Exam', "~ exam/ine", "Look" }, std.dlg) mp.cutscene.default_Verb = "more" diff --git a/parser/mp-ru.lua b/parser/mp-ru.lua index 17af7de..e86e1b9 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 = "Возможно, это потому что в темноте ничего не видно?" @@ -130,11 +171,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 +207,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 +220,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 +252,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 +264,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 +341,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/слезать {#so/{#first/рд},#me,нст}},".. + "{#word/покидать,#me,нст} {#first/вн}}." mp.msg.GetOff.NOWHERE = "Но {#me/дт} не с чего слезать." @@ -294,8 +396,11 @@ mp.msg.Take.SCENERY = "{#First/вн} невозможно взять." mp.msg.Take.WORN = "{#First} {#word/надет,#first} на {#firstwhere/вн}." mp.msg.Take.PARTOF = "{#First} {#if_hint/#first,plural,являются,является} частью {#firstwhere/рд}." -mp.msg.Remove.WHERE = "{#First} не находится {#if_has/#second,supporter,на,в} {#second/пр,2}." -mp.msg.Remove.REMOVE = "{#First} {#if_has/#second,supporter,поднят,извлечён из} {#second/рд}." +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.Drop.SELF = "У {#me/рд} не хватит ловкости." mp.msg.Drop.WORN = "{#First/вн} сначала нужно снять." @@ -480,13 +585,13 @@ end function mp:myself(_, hint) local ww = dict({ - ["вн"] = "себя"; - ["дт"] = "себе"; - ["тв"] = "собой"; - ["пр"] = "себе"; - ["рд"] = "себя"; + ["вн"] = { "себя" }; + ["дт"] = { "себе" }; + ["тв"] = {"собой" }; + ["пр"] = { "себе" }; + ["рд"] = { "себя" }; }, hint) - return { ww } + return ww end function mp:it(w, hint) @@ -560,27 +665,52 @@ 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 w = std.split(mp.mrd.lang.norm(hint)) + local utf = mp.utf + w = w[#w] + if utf.len(w) > 2 and + (not lang.is_vowel(utf.char(w, 1) and + not lang.is_vowel(utf.char(w, 2)))) + or utf.char(w, 1) == 'щ' 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 +721,8 @@ function mp:before_Enter(w) return false end -mp.msg.HELP = [[{$fmt b|КАК ИГРАТЬ?}^^ +mp.msg.HELP = function() + p [[{$fmt b|КАК ИГРАТЬ?}^^ Вводите ваши действия в виде простых предложений вида: глагол -- существительное. Например:^ > открыть дверь^ @@ -607,14 +738,17 @@ mp.msg.HELP = [[{$fmt b|КАК ИГРАТЬ?}^^ ^ Чтобы узнать какие предметы у вас с собой, наберите "инвентарь" или "инв".^ ^ -Для перемещений используйте стороны света, например: "идти на север" или "север" или просто "с".^ -Кроме сторон света можно перемещаться вверх ("вверх" или "вв") и вниз ("вниз" или "вн"). -^^ -Вы можете воспользоваться клавишей "TAB" для автодополнения ввода. -]] +Для перемещений используйте стороны света, например: "идти на север" или "север" или просто "с". Кроме сторон света можно перемещаться вверх ("вверх" или "вв") и вниз ("вниз" или "вн"), "внутрь" и "наружу".]] + if not instead.tiny then + p [[^^Вы можете воспользоваться клавишей "TAB" для автодополнения ввода.]] + 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 +793,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 +802,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 +855,7 @@ Verb { "#Take", } Verb { "#Insert", - "воткн/уть,втык/ать,вставить,влож/ить", + "воткн/уть,втык/ать,вставить,влож/ить,".. "[|про|за]сун/уть,вставь/", "{noun}/вн,held в|во {noun}/вн,inside : Insert", "~ {noun}/вн,held внутрь {noun}/рд : Insert", @@ -742,7 +876,7 @@ Verb { "#Drop", Verb { "#ThrowAt", - "брос/ить,выбро/сить,кин/уть,кида/ть,швыр/нуть,метн/уть,метать", + "брос/ить,выбро/сить,кину/ть,кинь/,кида/ть,швыр/нуть,метн/уть,метать", "{noun}/вн,held : Drop", "{noun}/вн,held в|во|на {noun}/вн : ThrowAt", "~ в|во|на {noun}/вн {noun}/вн : ThrowAt reverse", @@ -878,7 +1012,7 @@ Verb { "[|под]жечь,жг/и,подожги/,поджиг/ай,зажг/и,зажиг/ай,зажечь", "{noun}/вн : Burn", "{noun}/вн {noun}/тв,held : Burn", - "~ {noun}/тв,held {noun}/вн reverse", + "~ {noun}/тв,held {noun}/вн : Burn reverse", } Verb { @@ -1053,7 +1187,7 @@ Verb { "сказать,сообщи/ть,сообщу,рассказать,расскаж/ите", "{noun}/дт,live о|об|обо|про * : Tell", "~ * {noun}/дт,live : Tell reverse", - "~ {noun}/дт * : AskTo", + "~ {noun}/дт * : Tell" } Verb { @@ -1098,6 +1232,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 +1273,11 @@ if DEBUG then "~_дамп", "MetaDump" } + MetaVerb { + "#МетаForm", + "~_форм/ы", + "* :MetaForm" + } end MetaVerb { "#MetaTranscript", @@ -1133,6 +1292,8 @@ MetaVerb { "~парсер", "эксперт да : MetaExpertOn", "эксперт нет : MetaExpertOff", + "глаголы : MetaVerbs", + "версия : MetaVersion", } MetaVerb { @@ -1177,6 +1338,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.lua b/parser/mp.lua index 1a29f3f..6f20a0f 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 @@ -194,6 +226,9 @@ end function input:key(press, key) local mod + if mp:noparser() then + return false + end if key:find("alt") then mp.alt = press mod = true @@ -220,7 +255,9 @@ end mp = std.obj { nam = '@metaparser'; + started = false; score = false; + maxscore = false; expert_mode = true; autohelp = false; autohelp_limit = 1000; @@ -234,7 +271,7 @@ mp = std.obj { detailed_inv = false; daemons = std.list {}; { - version = "1.10"; + version = "2.2"; cache = { tokens = {}, nouns = false }; scope = std.list {}; logfile = false; @@ -251,6 +288,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; @@ -340,7 +378,14 @@ function mp:key(key) self:autoscript() return true end - + if key == 'home' or key == '[7]' then + self.cur = 1 + return true + end + if key == 'end' or key == '[1]' then + self.cur = self.inp:len() + 1 + return true + end if key == 'left' then return self:inp_left() end @@ -601,24 +646,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 @@ -679,6 +729,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 @@ -698,7 +753,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 @@ -929,6 +984,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() @@ -938,8 +1001,18 @@ function mp:lookup_verb(words, lev) 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 + if not i and not lev and vv.morph then i, len = self:lookup_short(words, 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 @@ -1184,11 +1257,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 @@ -1294,6 +1368,7 @@ function mp:compl_ctx_poss() table.insert(res, v) end end + res.eol = ctx.eol return res end @@ -1308,6 +1383,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) @@ -1323,13 +1399,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 @@ -1446,14 +1525,13 @@ function mp:match(verb, w, compl) local hints = {} local unknown = {} local multi = {} - local vargs local parsed_verb = {} local fixed_verb = verb.verb[verb.word_nr] fixed_verb = fixed_verb.word .. (fixed_verb.morph or '') 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 @@ -1466,9 +1544,10 @@ function mp:match(verb, w, compl) local rlev = 1 local need_required = false local default = false + 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}") @@ -1486,18 +1565,19 @@ 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 not required and k ~= 1 then k = false end -- ?word is only in 1st pos if k and ((k < best or len > best_len) or (not new_wildcard and wildcard and k <= best and len >= best_len)) then wildcard = new_wildcard @@ -1552,19 +1632,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 @@ -1590,9 +1678,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 @@ -1608,10 +1701,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 @@ -1624,7 +1721,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 @@ -1635,29 +1736,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 @@ -1959,6 +2070,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..")")) @@ -1979,7 +2098,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 'noprompt' then return false end if self.prompt then @@ -2044,12 +2163,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 @@ -2070,8 +2195,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() @@ -2258,6 +2385,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 = {} @@ -2275,6 +2415,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 @@ -2442,12 +2583,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) @@ -2455,6 +2597,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 @@ -2471,7 +2614,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 @@ -2481,6 +2624,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) @@ -2506,10 +2650,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() @@ -2808,5 +2952,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 1851355..b704fb1 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 @@ -460,8 +466,8 @@ std.player.look = function(s) if s:need_scene() then scene = r:scene() end - return (std.par(std.scene_delim, scene or false, r:display() or false)) -end; + return (std.par(std.scene_delim, scene or false, r:display() or false, std.call(mp, 'footer') or false)) +end -- local function check_persist(w) @@ -604,9 +610,8 @@ end -- dialogs std.phr.raw_word = function(s) local dsc = std.call(s, 'dsc') - if (dsc == nil) then - error("Phrase without dsc", 2) - dsc = '' + if type(dsc) ~= 'string' then + std.err("Empty dsc in phrase", 2) end return dsc .. '|'.. (tostring(s.__ph_idx) or std.dispof(s)) end @@ -761,16 +766,64 @@ mp.compass_dir = function(_, w, dir) return w ^ ('@'..dir) end +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 @@ -790,17 +843,20 @@ 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 +function mp:defpri(w) + if mp:animate(w) then + return -1 + end + return 0 end mp.msg.Exam = {} @@ -842,8 +898,8 @@ function mp:content(w, exam) self:objects(self.persistent, oo, false) end std.sort(oo, function (a, b) - a = std.tonum(a.pri) or 0 - b = std.tonum(b.pri) or 0 + a = std.tonum(a.pri) or mp:defpri(a) + b = std.tonum(b.pri) or mp:defpri(b) if a == b then return nil end @@ -895,38 +951,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:multidsc(oo) else - if std.me():where() == w or std.here() == w then - mp:message('Look.HEREARE', w) - else - if w:has 'supporter' then - pnoun (w, 'Exam.ON') - else - pnoun (w, 'Exam.IN') - end - mp:message 'Exam.ARE' - end - mp:multidsc(oo) + mp:message('Exam.CONTENT', w, oo) end -- expand? for _, o in ipairs(expand) do @@ -938,6 +966,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) @@ -960,32 +996,31 @@ 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) std.abort_cmd = true end - +local last_gfx = false 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 @@ -1011,8 +1046,9 @@ function mp:post_action() if not gfx and std.game.gfx ~= nil then gfx = std.call(std.game, 'gfx') end - if gfx then + if gfx and gfx ~= last_gfx then pn(fmt.c(fmt.img(gfx))) + last_gfx = gfx end p(l, std.scene_delim) game.player:need_scene(false) @@ -1277,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) @@ -1312,6 +1344,7 @@ function mp:after_Inv() else p() mp:multidsc(oo, true) + p "." end end @@ -1382,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 @@ -1713,6 +1746,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 @@ -2772,6 +2809,11 @@ function mp:MetaHelp() pn(mp:mesg 'HELP') end +function mp:MetaScore() + mp:message'TITLE_TURNS' + mp:message'TITLE_SCORE' +end + function mp:MetaTranscript() if self.logfile then p("Log file: ", self.logfile) @@ -2801,6 +2843,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 = {} @@ -3080,13 +3137,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)) + 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