// 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] handleSentence() 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}`]) {
// @TODO re review this - I've gone back and forth between setNoun and setAsset
// the thing is that the word received may not have been parsed into a full name
// which breaks setAsset
// haven't we already derived a parsednoun from this word in an earlier step?
this_turn.setNoun(i, this_turn.parsed_word[`noun${i}`]);
//this_turn.setAsset(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.game.settings.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.game.settings.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",
`[handleSentence.js] 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.game.settings.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) {
// --------------------------------------------------
// noun and global_string
// --------------------------------------------------
if (noun === "global_string") {
// if there's a string, ensure that we have the value
// it should be on global_string.values
// but if we're following up on a soft prompt,
// global_string.values will have been reset
// and we'll have to get it from this_turn.strings
// or this_turn.verified_sentence.phraseX.strings
// which will have been copied from last turn
const authoritative = this.game.world.global_string.values.length
? this.game.world.global_string.values
: phrase.strings?.length
? phrase.strings
: this_turn.strings?.length
? this_turn.strings
: [];
phrase.strings =
this.game.world.global_string.values =
this_turn.strings =
authoritative;
}
// similarly...
if (noun === "global_number") {
const authoritative = this.game.world.global_number.values.length
? this.game.world.global_number.values
: phrase.strings?.length
? phrase.strings
: this_turn.strings?.length
? this_turn.strings
: [];
phrase.strings =
this.game.world.global_number.values =
this_turn.strings =
authoritative;
}
// --------------------------------------------------
// 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}`].preposition_must_be.length &&
-1 ===
dictionary_verb[`phrase${n}`].preposition_must_be.indexOf(preposition)
) {
this.game.debug(
`D1222 | handleSentence.js | ${
dictionary_verb.name
}.phrase${n}.preposition_must_be: ${dictionary_verb[
`phrase${n}`
].preposition_must_be.join(", ")}`
);
let asset = this.game.getAsset(
this_turn.verified_sentence[`phrase${n}`].parsedNoun.object_id
);
msg += `{We} {don't} seem to know how to ${
dictionary_verb.name
} anything ${preposition} ${
asset && this.game.getPlayer().knowsAbout(asset)
? asset.articlename
: 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.game.settings.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.game.settings.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 (may be redundant)
// --------------------------------------------------
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
// --------------------------------------------------
// 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"
);
// --------------------------------------------------
// multiple assets found?
// --------------------------------------------------
// special test for substances
// if input referred to a substance, we wrote the substance id to
// parsedNoun.matches.unambiguous but parsedNoun.matches.qualified
// may contain multiple substance containers
// this is something like "take water" returning three glasses
// we have no handling for multiple instances of substances
if (
(parsedNoun.matches.qualified.length > 1 &&
!parsedNoun.matches.unambiguous) ||
(parsedNoun.matches.qualified.length > 1 &&
parsedNoun.matches.unambiguous &&
parsedNoun.matches.substance)
) {
// parsedNoun.matches.unambiguous = "";
var is_plural = false;
// check input word against world_lookup which may return an object
// with multiple asset IDs, for example "take silverware" may return:
// {
// IDs: (3) ['knife', 'fork', 'spoon']
// singular: "key"
// type: "plural"
// }
var lookup = this.game.world_lookup[parsedNoun.serialized_input];
// if lookup returned a list, we're interpreting player's input
// as referring to plural assets as in our "take silverware" example
if (lookup) {
if ("plural" === lookup.type || "group" === lookup.type) {
is_plural = true;
}
}
// alternately if there's an "&" in the original input,
// it means player explicitly referenced multiple objects,
// for example "take knife and fork"
if (1 < parsedNoun.original_input.split("&").length) {
is_plural = true;
}
// --------------------------------------------------
// if noun is plural...
// --------------------------------------------------
if (is_plural) {
// --------------------------------------------------
// ...can the current verb handle a plural noun?
// --------------------------------------------------
if (!dictionary_verb[`phrase${n}`].accepts_plural_noun) {
// verb doesn't accept a plural noun
// can we exclude any of our multiple assets 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();
}
// is there a collection among the assets?
// for example, there are three drawers
// and also a drawers collection
// if so, prefer that
if (parsedNoun.matches.qualified.length > 1) {
let collections = [];
for (let cid in parsedNoun.matches.qualified) {
let casset = this.game.getAsset(
parsedNoun.matches.qualified[cid]
);
if (!casset) continue;
if (casset.hasClass("Collection")) {
collections.push(casset);
}
}
if (collections.length === 1) {
parsedNoun.matches.qualified = [collections[0].id];
}
}
// --------------------------------------------------
// can we exclude assets marked as exclude_from_disambiguation?
// don't exclude them if they were the only assets found,
// but only if there is a mix of excluded and non-excluded assets
// chiefly used for global placeholders like global_wall and global_exit
// note that parser.selectPresent should have already narrowed global
// assets according to room_scenery / zone_scenery / global_scenery
// --------------------------------------------------
let { excludable_assets } = this.categorizeParsedNoun(parsedNoun);
if (
parsedNoun.matches.qualified.length > 1 &&
excludable_assets.length
) {
// reset the excludable_assets list to only include things from
// parsedNoun.matches.qualified, which may have been modified by a prior block
let { excludable_assets } = this.categorizeParsedNoun(parsedNoun);
// only exclude excludable assets if there are non excludable assets
// ex: if there is one tangible exit and three global exits,
// default to the one tangible exit
// but if there are only global exits, offer those
if (
excludable_assets.length < parsedNoun.matches.qualified.length
) {
parsedNoun = this.excludeFromParsedNoun(
parsedNoun,
excludable_assets
);
}
} // exclude_from_disambiguation
// did we manage to narrow down the list?
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
// the input was not found to be plural,
// but the parser did find multiple - aka ambiguous - assets
// for example "unlock door with key" returned [ "silver key", "gold key", "bronze key" ]
// and the current verb can't handle this - so we need to disambiguate - aka pick one
// this is one of the famously chief issues with text games
// we'll do our best to narrow down the list of options
// and if we can't we'll have to ask the player to pick one
if (!is_plural && !dictionary_verb.let_verb_handle_disambiguation) {
// --------------------------------------------------
// is parsedNoun a substance?
// we handle substance disambiguation differently from others
// --------------------------------------------------
if (parsedNoun.matches.substance) {
this.game.debug(
`D1447 | handleSentence.js | phrase${n}.parsedNoun found multiple substance containers `
);
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;
}
}
}
}
}
// --------------------------------------------------
// do we prefer reservoirs?
// for example, if player input "throw sand"
// is there a body of sand at hand?
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
this.game.settings.infer_containers_prefers_reservoir
) {
let { reservoirs, emitters } =
this.categorizeParsedNoun(parsedNoun);
if (reservoirs.length) {
parsedNoun.matches.qualified = [reservoirs[0]];
parsedNoun.qualified_object_id = reservoirs[0];
} else if (emitters.length) {
parsedNoun.matches.qualified = [emitters[0]];
parsedNoun.qualified_object_id = emitters[0];
}
}
// --------------------------------------------------
// if we still haven't found a singular vessel, do
// game settings allow us to pick one automatically?
// --------------------------------------------------
if (parsedNoun.matches.qualified.length > 1) {
if (this.game.settings.auto_pick_inferred_container) {
let match = parsedNoun.matches.qualified[0];
parsedNoun.matches.qualified = [match];
parsedNoun.qualified_object_id = match;
}
}
} // parsedNoun.matches.substance
// --------------------------------------------------
// handling for anything besides substances
// --------------------------------------------------
else if (!parsedNoun.matches.substance) {
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"
);
var phrase = this_turn.verified_sentence[`phrase${n}`];
var parsedNoun = phrase.parsedNoun;
if (
parsedNoun.matches.qualified.length > 1 &&
this.game.settings.disambiguation_considers_last_turn
) {
for (
let phrasenum = 1;
phrasenum <= last_turn.getPhraseCount();
phrasenum++
) {
let phrase = last_turn.verified_sentence[`phrase${phrasenum}`];
if (phrase?.parsedNoun?.matches?.unambiguous) {
let prior_id = phrase.parsedNoun.matches.unambiguous;
if (prior_id) {
if (parsedNoun.matches.qualified.includes(prior_id)) {
this.game.debug(
`D1373 | handleSentence.js | disambiguation defaulting to last turn's ${prior_id}`
);
parsedNoun.matches.qualified = [prior_id];
parsedNoun.qualified_object_id = prior_id;
}
}
}
}
} // disambiguation_considers_last_turn
// --------------------------------------------------
// categorize the found assets
// to help us narrow the list
// --------------------------------------------------
let {
present_assets,
carried_assets,
uncarried_assets,
local_assets,
global_assets,
excludable_assets,
} = this.categorizeParsedNoun(parsedNoun);
// --------------------------------------------------
// 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
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
dictionary_verb[`phrase${n}`].noun_must_be
.prefer_present_if_ambiguous
) {
// parsedNoun.matches.qualified = present_assets;
if (present_assets.length === 1) {
parsedNoun.matches.qualified = present_assets;
parsedNoun.qualified_object_id = present_assets[0];
}
} // prefer_present_if_ambiguous
// --------------------------------------------------
// prefer_carried_if_ambiguous?
// for verbs like "throw asset" where we can handle an uncarried
// asset but prefer a carried asset if there's ambiguity
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
dictionary_verb[`phrase${n}`].noun_must_be
.prefer_carried_if_ambiguous
) {
// parsedNoun.matches.qualified = carried_assets;
if (carried_assets.length === 1) {
parsedNoun.matches.qualified = carried_assets;
parsedNoun.qualified_object_id = carried_assets[0];
}
} // prefer_carried_if_ambiguous
// --------------------------------------------------
// prefer_uncarried_if_ambiguous?
// for verbs like "hit asset" where we can handle a carried
// asset but prefer an uncarried asset if there's ambiguity
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
dictionary_verb[`phrase${n}`].noun_must_be
.prefer_uncarried_if_ambiguous
) {
// parsedNoun.matches.qualified = uncarried_assets;
if (uncarried_assets.length === 1) {
parsedNoun.matches.qualified = uncarried_assets;
parsedNoun.qualified_object_id = uncarried_assets[0];
}
} // prefer_uncarried_if_ambiguous
// --------------------------------------------------
// local vs global?
// if we will have multiple assets, does omitting
// globals get us down to one?
// --------------------------------------------------
if (parsedNoun.matches.qualified.length > 1) {
if (local_assets.length === 1) {
parsedNoun.matches.qualified = local_assets;
parsedNoun.qualified_object_id = local_assets[0];
}
}
// --------------------------------------------------
// can we consider assets mentioned in the last turn?
// this doesn't apply if last turn was plural
// ex: if "take all keys", then "x key" should not
// default to the last key handled
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
this.game.settings.disambiguation_considers_last_turn &&
!last_turn.plural
) {
// 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 (
last_turn_asset &&
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;
}
}
}
// --------------------------------------------------
// can we exclude assets marked as exclude_from_disambiguation?
// don't exclude them if they were the only assets found,
// but only if there is a mix of excluded and non-excluded assets
// chiefly used for global placeholders like global_wall and global_exit
// note that parser.selectPresent should have already narrowed global
// assets according to room_scenery / zone_scenery / global_scenery
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
excludable_assets.length
) {
// reset the excludable_assets list to only include things from
// parsedNoun.matches.qualified, which may have been modified by a prior block
let { excludable_assets } = this.categorizeParsedNoun(parsedNoun);
// only exclude excludable assets if there are non excludable assets
// ex: if there is one tangible exit and three global exits,
// default to the one tangible exit
// but if there are only global exits, offer those
if (
excludable_assets.length < parsedNoun.matches.qualified.length
) {
parsedNoun = this.excludeFromParsedNoun(
parsedNoun,
excludable_assets
);
}
} // exclude_from_disambiguation
// --------------------------------------------------
// did player input something like "go east through exit"?
// parser will have returned all available exits
// but we should be able to infer the correct one
// --------------------------------------------------
if (parsedNoun.matches.qualified.length > 1 && n === 2) {
let phrase1 = this_turn.verified_sentence.phrase1;
if (
phrase1.parsedNoun?.matches.direction &&
phrase1.parsedNoun.qualified_object_id &&
parsedNoun.matches.qualified.includes(
phrase1.parsedNoun.qualified_object_id
)
) {
parsedNoun.matches.qualified = [
phrase1.parsedNoun.qualified_object_id,
];
parsedNoun.qualified_object_id =
phrase1.parsedNoun.qualified_object_id;
}
}
} // if not substance
// --------------------------------------------------
// regardless if substance or other,
// if we made it here without an unambiguous asset,
// we just have to ask the player
// --------------------------------------------------
if (parsedNoun.matches.qualified.length > 1) {
this.printNounDisambiguation({
parsedNoun: parsedNoun,
nounIndex: n,
});
return false;
}
} // ! let verb handle disambiguation
} // more than one qualified
} // each phrase redux
// --------------------------------------------------
// it looks like we're ready to do the thing!
// --------------------------------------------------
this.game.log(
"L1208",
"log",
"high",
"[handleSentence.js] handleSentence() 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
})();