// handleSentence.js
(function () {
/*global adventurejs A*/
var p = adventurejs.Parser.prototype;
/**
* Handle multi-word input.
* @memberOf adventurejs.Parser
* @method adventurejs.Parser#handleSentence
*/
p.handleSentence = function Parser_handleSentence() {
this.game.log(
"L1198",
"log",
"high",
"handleSentence.js > BEGIN",
"Parser"
);
var this_turn = this.input_history[0];
var last_turn = this.input_history[1];
var parsed_verb_name = "",
dictionary_verb = null,
dictionary_verb_name = "",
nouns = [],
containers = [],
exclusions = [],
prepositions = [],
parsedNouns = [],
count = { phrase: 0 },
msg = "";
// --------------------------------------------------
// parsed one word
// --------------------------------------------------
if (this_turn.parsed_word.enabled) {
// we've arrived here via handleWord and
// we're probably amending last turn's input
// so copy that to start with
this_turn.verified_sentence = A.clone.call(
this.game,
last_turn.verified_sentence
);
// get verb + nouns from last turn prompt,
// which may differ from last turn's input
if (this_turn.parsed_word.verb) {
this_turn.setVerb(1, this_turn.parsed_word.verb);
}
for (let i = 1; i <= 3; i++) {
if (this_turn.parsed_word[`noun${i}`]) {
this_turn.setNoun(i, this_turn.parsed_word[`noun${i}`]);
}
if (this_turn.parsed_word[`parsedNoun${i}`]) {
this_turn.setParsedNoun(i, this_turn.parsed_word[`parsedNoun${i}`]);
}
if (this_turn.parsed_word[`container${i}`]) {
this_turn.setContainer(i, this_turn.parsed_word[`container${i}`]);
}
if (this_turn.parsed_word[`preposition${i}`]) {
this_turn.setPreposition(i, this_turn.parsed_word[`preposition${i}`]);
}
}
} // parsed_word
// --------------------------------------------------
// get verb and nouns
// --------------------------------------------------
if (this_turn.hasInput()) {
// we've got fresh input from parseInput
if (this_turn.hasVerb()) {
parsed_verb_name = this_turn.getVerb();
}
for (let i = 1; i <= 3; i++) {
if (this_turn.hasPhrase(i)) {
prepositions[i] = this_turn.getPreposition(i);
nouns[i] = this_turn.getNoun(i);
containers[i] = this_turn.getContainer(i);
exclusions[i] = this_turn.getExclusion(i);
}
}
} else {
// we shouldn't arrive here but may need an error message
this.game.debug(`D1207 | handleSentence.js | didn't receive words `);
msg += this.getUnparsedMessage(this_turn.input);
this.game.print(msg, this_turn.output_class);
}
// --------------------------------------------------
// oops
// --------------------------------------------------
if ("oops " === this_turn.input.substring(0, 5)) {
this.game.printInput(this_turn.input);
return this.game.dictionary.doVerb("oops");
}
// --------------------------------------------------
// verify verb
// --------------------------------------------------
if (!parsed_verb_name || !this.dictionary.verbs[parsed_verb_name]) {
// if no verb, did we get a two-word response to a soft prompt?
if (
this_turn.verified_sentence_structure === "preposition noun" &&
last_turn.soft_prompt.enabled
) {
for (let i = 1; i <= 3; i++) {
if (
last_turn.soft_prompt[`noun${i}`] &&
last_turn.soft_prompt[`preposition${i}`]
) {
let new_phrase = Object.assign(
{},
this_turn.verified_sentence.phrase1
);
this_turn.verified_sentence = A.clone.call(
this.game,
last_turn.verified_sentence
);
this_turn.setPhrase(i, new_phrase);
this_turn.verified_sentence_structure =
last_turn.soft_prompt.structure ||
last_turn.verified_sentence_structure;
parsed_verb_name = this_turn.getVerb();
}
}
}
}
// check for a verb again
if (!parsed_verb_name || !this.dictionary.verbs[parsed_verb_name]) {
let err = `parser.handleSentence > Verb not found. This may happen if a compound phrase is improperly found. Original input: ${this_turn.input} Parsed input: ${this_turn.parsed_input}`;
this.game.log("L1199", "warn", "high", err, "Parser");
this.game.debug(
`D1721 | handleSentence.js | verb not found. Check the console for more info. `
);
msg += this.getUnparsedMessage(this_turn.input);
this.game.print(msg, this_turn.output_class);
return false;
}
// if verb.let_verb_handle_remaining_input, bypass handleSentence
// originally used for oops and left in for future cases
if (
this.dictionary.verbs[parsed_verb_name].let_verb_handle_remaining_input
) {
this.dictionary.doVerb(parsed_verb_name);
return false;
}
// --------------------------------------------------
// qualify verb
// --------------------------------------------------
// Check verb against circumstances that prevent its use,
// such as if player is constrained, lying on the floor, etc.
dictionary_verb = this.qualifyParsedVerb({
parsed_verb_name: parsed_verb_name,
});
// If qualifyParsedVerb returned false, then it already
// printed an error msg to the player, so we can just...
if (!dictionary_verb) return false;
// --------------------------------------------------
// get phrase count
// --------------------------------------------------
for (const key in this_turn.verified_sentence) {
if (key.startsWith("phrase")) {
count.phrase++;
}
}
// --------------------------------------------------
// for each phrase
// --------------------------------------------------
for (let n = 1; n <= count.phrase; n++) {
var phrase = this_turn.verified_sentence[`phrase${n}`];
var noun = phrase.noun;
var preposition = phrase.preposition;
var exclusion = phrase.exclusion;
this.game.log(
"L1200",
"log",
"high",
`parser.handleSentence > handle phrase ${n}`,
"Parser"
);
// this shouldn't happen
if (!noun && !preposition) {
this.game.debug(
`D1034 | handleSentence.js | ${dictionary_verb.name} didn't receive a noun or preposition`
);
msg += `How did $(we) want to ${this_turn.input}? `;
this.game.print(msg, this_turn.output_class);
return false;
}
// --------------------------------------------------
// unsupported noun?
// --------------------------------------------------
if (noun && !dictionary_verb[`phrase${n}`].accepts_noun) {
this.game.debug(
`D2018 | handleSentence.js | ${dictionary_verb.name} received a noun it can't handle`
);
msg += this.getUnparsedMessage(this_turn.input);
this.game.print(msg, this_turn.output_class);
return false;
}
// --------------------------------------------------
// unsupported preposition without noun?
// --------------------------------------------------
if (
preposition &&
!noun &&
!dictionary_verb[`phrase${n}`].accepts_preposition_without_noun
) {
this.game.debug(
`D1033 | handleSentence.js | ${dictionary_verb.name} received preposition without noun, soft prompt noun${n}`
);
msg += `What would $(we) like to ${this_turn.input}? `;
this_turn.setSoftPrompt({
index: n,
type: "noun",
[`noun${n}`]: true,
verb: this_turn.input_verb,
verb_phrase: this_turn.verb_phrase,
});
this.game.print(msg, this_turn.output_class);
return false;
}
// --------------------------------------------------
// noun
// --------------------------------------------------
if (noun) {
// --------------------------------------------------
// plural
// --------------------------------------------------
// "this and that" is parsed as "this&that"
let split_noun = noun.split("&");
// "all keys" might be parsed as "key=keya,keyb,keyc"
// we deliberately do not handle that here as
// it conflicts with a later disambiguation handler
let noun_is_plural = "all" === noun || split_noun.length > 1;
if (
noun_is_plural &&
!dictionary_verb[`phrase${n}`].accepts_plural_noun
) {
this.game.debug(
`D1032 | handleSentence.js | ${dictionary_verb.name}.phrase${n}.accepts_plural_noun is false`
);
msg += `$(We) can't ${dictionary_verb.prettyname} more than one thing at a time. `;
this.game.print(msg, this_turn.output_class);
return false;
}
// Split noun string and queue verb-noun inputs for each item
if (split_noun.length > 1) {
for (let i = 1; i < split_noun.length; i++) {
this.input_queue.push({
input: this_turn.parsed_input.replace(noun, split_noun[i]),
printInput: false,
});
}
noun = split_noun[0];
} // plural
// write a parsedNoun back to the phrase
// it's possible that one was already set by a disambiguation
// so check for existing
if (!phrase.parsedNoun) phrase.parsedNoun = this.parseNoun(noun);
} // noun
// --------------------------------------------------
// unsupported noun without preposition?
// --------------------------------------------------
if (
noun &&
!preposition &&
dictionary_verb[`phrase${n}`].requires_preposition
) {
this.game.debug(
`D1041 | handleSentence.js | ${dictionary_verb.name} received noun without preposition, soft prompt preposition${n}`
);
// msg += `How did $(we) want to ${this_turn.input_verb} ${phrase.noun}? `;
switch (n) {
case 1:
msg += `Where in relation to ${
this.game.getAsset(
this_turn.verified_sentence.phrase1.parsedNoun.object_id
).articlename
} did $(we) want to ${dictionary_verb.prettyname}? `;
break;
case 2:
msg += `Where in relation to ${
this.game.getAsset(
this_turn.verified_sentence.phrase2.parsedNoun.object_id
).articlename
} did $(we) want to ${dictionary_verb.prettyname} ${
this.game.getAsset(
this_turn.verified_sentence.phrase1.parsedNoun.object_id
).articlename
}? `;
break;
case 3:
msg += `That seems to be missing a preposition. Can $(we) try phrasing that another way? `;
break;
}
this_turn.setSoftPrompt({
index: 2,
type: "preposition",
[`preposition${n}`]: true,
verb: this_turn.input_verb,
verb_phrase: this_turn.verb_phrase,
});
this.game.print(msg, this_turn.output_class);
return false;
}
// --------------------------------------------------
// unsupported preposition?
// --------------------------------------------------
if (
preposition &&
dictionary_verb[`phrase${n}`].accepts_these_prepositions.length &&
-1 ===
dictionary_verb[`phrase${n}`].accepts_these_prepositions.indexOf(
preposition
)
) {
this.game.debug(
`D1222 | handleSentence.js | ${
dictionary_verb.name
}.phrase${n}.accepts_these_prepositions: ${dictionary_verb[
`phrase${n}`
].accepts_these_prepositions.join(", ")}`
);
msg += `$(We) don't seem to know how to ${
dictionary_verb.name
} anything ${preposition} ${noun ? noun : ""}. `;
this.game.print(msg, this_turn.output_class);
return false;
}
// --------------------------------------------------
// parsedNoun empty?
// --------------------------------------------------
if (phrase.parsedNoun && !phrase.parsedNoun.matches.all.length) {
this.game.log(
"L1201",
"log",
"high",
[
"handleSentence.js > input: " +
this_turn.input +
`, phrase${n}.parsedNoun: `,
phrase.parsedNoun,
],
"Parser"
);
// save record for "oops"
this_turn.unknown_word = noun;
this.game.debug(
`D1035 | handleSentence.js | ${noun} isn't recognized `
);
// can we return the original string?
if (this_turn.replacements[noun]) {
msg += `$(We) don't know of any ${this_turn.replacements[noun].source}. `;
}
// does the unrecognized input match a class name?
else if (adventurejs[A.propercase(noun)]) {
msg += `$(We) don't know of any ${noun}. `;
}
// as last resort use if_parser_has_no_response_print_this
else {
msg += this.getUnparsedMessage(this_turn.unparsed_input);
}
this.game.print(msg, this_turn.output_class);
return false;
} // parsedNoun
this.game.log(
"L1202",
"log",
"medium",
[`handleSentence.js > phrase${n}.parsedNoun:`, phrase.parsedNoun],
"Parser"
);
// --------------------------------------------------
// in means on?
// --------------------------------------------------
// Replace 'in' with 'on' where applicable such as 'sit in chair'
if (phrase.parsedNoun && !dictionary_verb[`phrase${n}`].accepts_noun) {
this.game.debug(
`D1036 | handleSentence.js | ${dictionary_verb.name} received a noun it can't handle`
);
msg += this.getUnparsedMessage(this_turn.input);
this.game.print(msg, this_turn.output_class);
return false;
}
if (
phrase.parsedNoun &&
"in" === preposition &&
this.game.getAsset(phrase.parsedNoun.object_id).quirks.in_means_on &&
dictionary_verb.in_can_mean_on
) {
this_turn.setInPhrase(n, "preposition", "on");
}
// --------------------------------------------------
// exclusion (aka but)
// --------------------------------------------------
// examples of exclusions:
// "take all but sword"
// "take all gems but green gem"
// "take all gems but green gem and blue gem"
if (exclusion && "string" === typeof exclusion) {
var exclusion_split = exclusion.split("&");
for (let x = 0; x < exclusion_split.length; x++) {
var excluded_noun = exclusion_split[x];
var excluded_parsed_noun = this.parseNoun(excluded_noun);
this.game.log(
"L1203",
"log",
"medium",
["handleSentence.js > Exclude:", excluded_parsed_noun],
"Parser"
);
if (excluded_parsed_noun) {
for (let i = 0; i < excluded_parsed_noun.matches.all.length; i++) {
let excluded_id = excluded_parsed_noun.matches.all[i];
// remove excluded_id from parsedNoun.matches.all
phrase.parsedNoun.matches.all.splice(
phrase.parsedNoun.matches.all.indexOf(excluded_id),
1
);
// remove excluded_id from parsedNoun.matches.qualified
phrase.parsedNoun.matches.qualified.splice(
phrase.parsedNoun.matches.qualified.indexOf(excluded_id),
1
);
// remove excluded_id from parsedNoun.matches.unambiguous
if (phrase.parsedNoun.matches.unambiguous === excluded_id)
phrase.parsedNoun.matches.unambiguous = "";
} // for excluded_parsed_noun.matches.all
} // excluded_parsed_noun
} // for exclusion_split
this.game.log(
"L1204",
"log",
"medium",
[
`handleSentence.js > phrase${n}.parsedNoun minus exclusions: `,
phrase.parsedNoun,
],
"Parser"
);
} // exclusion
// --------------------------------------------------
// exclude from prior
// --------------------------------------------------
if (n > 1) {
let first_phrase = this_turn.getPhrase(1);
let last_split_noun = [];
last_split_noun = first_phrase.noun && first_phrase.noun.split("&");
let last_noun_is_plural =
first_phrase.noun === "all" || last_split_noun.length > 1;
let exclude_from_plural =
dictionary_verb[`phrase${n}`].not_in_prior_plural;
if (last_noun_is_plural && exclude_from_plural) {
// was there an unambiguous match for this?
if (phrase.parsedNoun.matches.unambiguous) {
// remove unambiguous from prior noun's matches
let excluded_id = phrase.parsedNoun.matches.unambiguous;
let allindex =
first_phrase.parsedNoun.matches.all.indexOf(excluded_id);
let qualindex =
first_phrase.parsedNoun.matches.qualified.indexOf(excluded_id);
// remove excluded_id from parsedNoun.matches.all
first_phrase.parsedNoun.matches.all.splice(allindex, 1);
// remove excluded_id from parsedNoun.matches.qualified
first_phrase.parsedNoun.matches.qualified.splice(qualindex, 1);
// remove excluded_id from parsedNoun.matches.unambiguous
if (first_phrase.parsedNoun.matches.unambiguous === excluded_id)
first_phrase.parsedNoun.matches.unambiguous = "";
if (allindex > -1 || qualindex > -1) {
// we might need to re-qualify the last noun
if (first_phrase.parsedNoun) {
first_phrase.parsedNoun = this.qualifyParsedNoun({
parsedNoun: first_phrase.parsedNoun,
parsedVerb: dictionary_verb.name,
nounIndex: n - 1,
});
// If qualifyParsedNoun returned false, then it already
// printed an error msg to the player, so we can just...
if (!phrase.parsedNoun) return false;
}
}
}
}
// remove iobj from dobj collection ----------
// @TODO reexamine - this seems like it should be caught in prior block
if (
phrase.parsedNoun &&
phrase.parsedNoun.object_id &&
first_phrase.parsedNoun.object_id &&
first_phrase.parsedNoun.matches.qualified.length > 1
) {
for (
var i = first_phrase.parsedNoun.matches.qualified.length - 1;
i > -1;
i--
) {
if (
phrase.parsedNoun.qualified_object_id ===
first_phrase.parsedNoun.matches.qualified[i]
) {
first_phrase.parsedNoun.matches.qualified.splice(i, 1);
}
}
} // remove iobj from dobj collection
} // n>1 / exclude from prior
// --------------------------------------------------
// qualify noun
// --------------------------------------------------
if (phrase.parsedNoun) {
phrase.parsedNoun = this.qualifyParsedNoun({
parsedNoun: phrase.parsedNoun,
parsedVerb: dictionary_verb.name,
nounIndex: n,
});
// If qualifyParsedNoun returned false, then it already
// printed an error msg to the player, so we can just...
if (!phrase.parsedNoun) return false;
}
// --------------------------------------------------
// redirectVerb
// --------------------------------------------------
// Has author used redirectVerb for this verb on direct_object?
if (
n === 1 &&
phrase.parsedNoun &&
phrase.parsedNoun.qualified_object_id
) {
let direct_object = this.game.getAsset(
phrase.parsedNoun.qualified_object_id
);
if (direct_object.redirected_verbs[dictionary_verb.name]) {
// requalify verb
dictionary_verb = this.qualifyParsedVerb({
parsed_verb_name:
direct_object.redirected_verbs[dictionary_verb_name],
});
if (!dictionary_verb) return false;
}
} // redirectVerb
} // for each phrase
// --------------------------------------------------
// each phrase redux
// --------------------------------------------------
// I think this is reiterating to account for changes made during the first pass
for (let n = 1; n <= count.phrase; n++) {
var phrase = this_turn.verified_sentence[`phrase${n}`];
var parsedNoun = phrase.parsedNoun;
if (!parsedNoun) continue;
this.game.log(
"L1205",
"log",
"high",
`handleSentence.js > final check on phrase ${n}`,
"Parser"
);
if (parsedNoun.matches.qualified.length > 1) {
var lookup = this.game.world_lookup[parsedNoun.serialized_input];
var is_plural = false;
if (1 < parsedNoun.original_input.split("&").length) {
is_plural = true;
}
if (lookup) {
if ("plural" === lookup.type || "group" === lookup.type) {
is_plural = true;
}
}
if (is_plural) {
if (!dictionary_verb[`phrase${n}`].accepts_plural_noun) {
// verb doesn't accept a plural noun
// can we exclude any for not being dov?
let q = parsedNoun.matches.qualified.slice();
for (let index = q.length - 1; index > -1; index--) {
let qid = q[index];
let qasset = this.game.getAsset(qid);
if (!qasset.isDOV(dictionary_verb.name)) {
q.splice(index, 1);
}
}
if (q.length === 1) {
parsedNoun.matches.qualified = q.slice();
}
// ask again
if (parsedNoun.matches.qualified.length > 1) {
this.game.debug(
`D1202 | handleSentence.js | ${dictionary_verb.name} doesn't handle multiple objects `
);
this.game.log(
"L1206",
"warn",
"high",
"handleSentence.js > " +
dictionary_verb.name +
" doesn't handle multiple objecs: " +
parsedNoun.original_input,
"Parser"
);
this.printNounDisambiguation({
parsedNoun: parsedNoun,
nounIndex: n,
});
return false;
}
}
} // is_plural
// we found multiple assets, meaning, parsedNoun.matches.qualified.length > 1
// but verb doesn't handle plurals
if (!is_plural && !dictionary_verb.let_verb_handle_disambiguation) {
if (parsedNoun.matches.substance) {
this.game.debug(
`D1447 | handleSentence.js | phrase${n}.parsedNoun found multiple substance containers `
);
let found = false;
if (this.game.settings.disambiguation_considers_last_turn) {
// did last turn refer to a container of this substance?
// if so infer that container
for (
let phrase = 1;
phrase < last_turn.getPhraseCount();
phrase++
) {
let last_turn_asset = last_turn.getAsset(phrase);
if (last_turn_asset.contains === parsedNoun.matches.substance) {
// asset contains substance but is it one of the qualified matches?
for (
let container = 0;
container < parsedNoun.matches.qualified.length;
container++
) {
let match = parsedNoun.matches.qualified[container];
let asset = this.game.getAsset(match);
if (asset.id === last_turn_asset.id) {
parsedNoun.matches.qualified = [match];
parsedNoun.qualified_object_id = match;
found = true;
}
}
}
}
}
// prefer reservoir?
if (
!found &&
this.game.settings.infer_containers_prefers_reservoir
) {
for (
let container = 0;
container < parsedNoun.matches.qualified.length;
container++
) {
let match = parsedNoun.matches.qualified[container];
let asset = this.game.getAsset(match);
if (asset.$is("reservoir")) {
parsedNoun.matches.qualified = [match];
//parsedNoun.matches.qualifiedIndex = container;
parsedNoun.qualified_object_id = match;
}
}
}
// automatically pick?
if (!found && parsedNoun.matches.qualified.length > 1) {
if (this.game.settings.infer_containers_automatically_picks) {
parsedNoun.matches.qualified = [
parsedNoun.matches.qualified[0],
];
}
}
} // parsedNoun.matches.substance
// prefer_present_if_ambiguous?
// for verbs like "go to asset" where we can handle a non-present
// asset but prefer a present asset if there's ambiguity
else if (
dictionary_verb[`phrase${n}`].noun_must_be
.prefer_present_if_ambiguous
) {
let present_assets = [];
for (
let qualified = 0;
qualified < parsedNoun.matches.qualified.length;
qualified++
) {
let match = parsedNoun.matches.qualified[qualified];
let asset = this.game.getAsset(match);
if (asset.$is("present")) {
present_assets.push(match);
}
}
if (present_assets.length === 1) {
parsedNoun.matches.qualified = present_assets;
//parsedNoun.matches.qualifiedIndex = qualified;
parsedNoun.qualified_object_id = present_assets[0];
}
} // prefer_present_if_ambiguous
// ask this again now
if (parsedNoun.matches.qualified.length > 1) {
this.game.debug(
`D1201 | handleSentence.js | phrase${n}.parsedNoun needs disambiguation `
);
this.game.log(
"L1207",
"log",
"high",
"handleSentence.js > parsedNoun" +
n +
" needs disambiguation: " +
parsedNoun.matches.qualified,
"Parser"
);
let found = false;
if (this.game.settings.disambiguation_considers_last_turn) {
// did last turn's input refer to an asset that could resolve
// this ambiguity? if so infer that asset
for (
let phrase = 1;
phrase < last_turn.getPhraseCount();
phrase++
) {
let last_turn_asset = last_turn.getAsset(phrase);
if (parsedNoun.matches.qualified.includes(last_turn_asset.id)) {
this.game.debug(
`D1589 | handleSentence.js | disambiguation defaulting to last turn's ${last_turn_asset.name}`
);
parsedNoun.matches.qualified = [last_turn_asset.id];
parsedNoun.qualified_object_id = last_turn_asset.id;
found = true;
}
}
}
if (!found) {
this.printNounDisambiguation({
parsedNoun: parsedNoun,
nounIndex: n,
});
return false;
}
}
}
} // more than one qualified
} // each phrase redux
// --------------------------------------------------
// do the thing!
// --------------------------------------------------
this.game.log("L1208", "log", "high", "handleSentence.js > END", "Parser");
let direct_object = this_turn.getAsset(1);
if (direct_object?.is.collection && dictionary_verb.enqueue_collections) {
this.game.debug(
`D1091 | handleSentence.js | ${
this_turn.getAsset(1).name
}.is.collection, enqueueing collection`
);
this.game.print(msg);
dictionary_verb.enqueueCollection(direct_object);
} else {
this.game.log(
"L1209",
"log",
"high",
`handleSentence.js > doVerb ${dictionary_verb.name}`,
"Parser"
);
return dictionary_verb.do();
}
return true;
}; // handleSentence
})();