// 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() {
const fx = `handleSentence.js`;
this.game.log(
"L1198",
"log",
"high",
`[${fx}] handleSentence() begin`,
"Parser"
);
var this_turn = this.input_history[0];
var last_turn = this.input_history[1];
var subject = this_turn.getSubject();
var parsed_verb_name = "",
dictionary_verb = null,
dictionary_verb_name = "",
nouns = [],
containers = [],
exclusions = [],
prepositions = [],
parsedNouns = [],
count = { phrase: 0 },
msg = "";
const phrase1 = this_turn.verified_sentence.phrase1;
// --------------------------------------------------
// 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.FX.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`, `${fx} `, ` 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.display.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.FX.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`,
`${fx} `,
` 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
// this is the first of two passes
// ==================================================
for (let n = 1; n <= count.phrase; n++) {
const phrase = this_turn.verified_sentence[`phrase${n}`];
let noun = phrase.noun;
let preposition = phrase.preposition;
let exclusion = phrase.exclusion;
let noun_is_plural = false;
let noun_is_all = false;
this.game.log(
"L1200",
"log",
"high",
`[${fx}] handle phrase ${n}`,
"Parser"
);
// this shouldn't happen
if (!noun && !preposition) {
this.game.debug(
`D1034`,
`${fx} `,
` ${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`,
`${fx} `,
` ${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`,
`${fx} `,
` ${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
noun_is_plural = "all" === noun || split_noun.length > 1;
noun_is_all = "all" === noun;
if (
noun_is_plural &&
!dictionary_verb[`phrase${n}`].accepts_plural_noun
) {
this.game.debug(
`D1032`,
`${fx} `,
` ${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) {
let context = null;
// if (noun === "all") {
if (noun_is_plural) {
// if player is putting/dropping/giving all,
// set context to player's inventory
if (["put", "drop", "give"].includes(dictionary_verb.name)) {
context = subject;
}
}
phrase.parsedNoun = this.parseNoun(noun, context);
}
} // noun
// --------------------------------------------------
// unsupported noun without preposition?
// --------------------------------------------------
if (
noun &&
!preposition &&
dictionary_verb[`phrase${n}`].requires_preposition
) {
// console.warn(`verified_sentence`, this_turn.verified_sentence);
this.game.debug(
`D1041`,
`${fx} `,
` ${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.asset_id
).article_name
} did {we} want to ${dictionary_verb.prettyname}? `;
break;
case 2:
msg += `Where in relation to ${
this.game.getAsset(
this_turn.verified_sentence.phrase2.parsedNoun.asset_id
).article_name
} did {we} want to ${dictionary_verb.prettyname} ${
this.game.getAsset(
this_turn.verified_sentence.phrase1.parsedNoun.asset_id
).article_name
}? `;
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`,
`${fx} `,
` ${
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.asset_id
);
msg += `{We} {don't} seem to know how to ${
dictionary_verb.name
} anything ${preposition} ${
asset && this.game.getPlayer().knowsAbout(asset)
? asset.article_name
: noun
? noun
: ""
}. `;
this.game.print(msg, this_turn.output_class);
return false;
}
// --------------------------------------------------
// unknown noun?
// --------------------------------------------------
if (phrase.parsedNoun && !phrase.parsedNoun.matches.all.length) {
this.game.log(
"L1201",
"log",
"high",
[
`[${fx}] input: ${this_turn.input}, phrase${n}.parsedNoun: `,
phrase.parsedNoun,
],
"Parser"
);
// save record for "oops"
this_turn.unknown_word = noun;
this.game.debug(`D1035`, `${fx} `, ` ${noun} isn't recognized `);
// can we return the original string?
if (this_turn.tokens[noun]) {
msg += `{We} {don't} know of any ${this_turn.tokens[noun].source}. `;
}
// does the unrecognized input match a class name?
else if (
AdventureJS[A.FX.propercase(noun)] ||
AdventureJS.Assets[A.FX.propercase(noun)]
) {
msg += `{We} {don't} know of any ${noun}. `;
}
// as last resort use response_to_no_response
else {
msg += this.game.settings.getUnparsedMessage(
this_turn.normalized_input
);
}
this.game.print(msg, this_turn.output_class);
return false;
} // parsedNoun
this.game.log(
"L1202",
"log",
"high",
[`[${fx}] phrase${n}.parsedNoun:`, phrase.parsedNoun.matches.qualified],
"Parser"
);
// --------------------------------------------------
// verb phrase accepts parsedNoun?
// is this redundant to D2018?
// --------------------------------------------------
if (phrase.parsedNoun && !dictionary_verb[`phrase${n}`].accepts_noun) {
this.game.debug(
`D1036`,
`${fx} `,
` ${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;
}
// --------------------------------------------------
// in means on?
// --------------------------------------------------
if (
phrase.parsedNoun &&
"in" === preposition &&
this.game.getAsset(phrase.parsedNoun.asset_id).quirks.in_means_on &&
dictionary_verb.in_can_mean_on
) {
// Replace 'in' with 'on' where applicable such as 'sit in chair'
this_turn.setInPhrase(n, "preposition", "on");
}
// --------------------------------------------------
// exclusion (aka but)
// supported syntaxes:
// "take all but green gem"
// "take all gems but green gem"
// "take all gems but green gem and blue gem"
// "take all but gems from table"
// "take all from table but gems"
// --------------------------------------------------
if (exclusion && "string" === typeof exclusion) {
const exclusions = exclusion.split("&");
// if this isn't phrase1, move exclusions there
// using this target_phrase var because it seems
// like we might need to make this more flexible
const target_phrase = phrase1; // n > 1 ? this_turn.getPhrase(1) : phrase;
for (let x = 0; x < exclusions.length; x++) {
const excluded_noun = exclusions[x];
const excluded_parsed_noun = this.parseNoun(excluded_noun);
// copy exclusions to phrase1 if necessary
if ("undefined" === typeof target_phrase.exclusions)
target_phrase.exclusions = [];
if (!target_phrase.exclusions.includes(excluded_noun))
target_phrase.exclusions.push(excluded_noun);
this.game.log(
"L1203",
"log",
"high",
[
`[${fx}] phrase${n} excludes:`,
excluded_parsed_noun.matches.qualified,
],
"Parser"
);
if (excluded_parsed_noun) {
for (
let exp = 0;
exp < excluded_parsed_noun.matches.all.length;
exp++
) {
let excluded_id = excluded_parsed_noun.matches.all[exp];
// remove excluded_id from parsedNoun.matches.all
if (target_phrase.parsedNoun.matches.all.includes(excluded_id)) {
target_phrase.parsedNoun.matches.all.splice(
target_phrase.parsedNoun.matches.all.indexOf(excluded_id),
1
);
// remove excluded_id from parsedNoun.matches.qualified
target_phrase.parsedNoun.matches.qualified.splice(
target_phrase.parsedNoun.matches.qualified.indexOf(
excluded_id
),
1
);
// remove excluded_id from parsedNoun.is_unambiguous
// @NOTE I think this is the only place we compare
// is_unambiguous, which is a precursor to is_qualified
if (target_phrase.parsedNoun.is_unambiguous === excluded_id)
target_phrase.parsedNoun.is_unambiguous = "";
} // parsedNoun includes excluded
} // for excluded_parsed_noun.matches.all
} // excluded_parsed_noun
} // for exclusions
this.game.log(
"L1204",
"log",
"high",
[`[${fx}] removed phrase${n} exclusions from phrase1: `, exclusions],
"Parser"
);
} // exclusion
// --------------------------------------------------
// exclude noun from first phrase's qualified assets
// --------------------------------------------------
if (n > 1) {
//let phrase1 = this_turn.getPhrase(1);
let last_split_noun = [];
last_split_noun = phrase1.noun && phrase1.noun.split("&");
let last_noun_is_plural =
phrase1.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.is_unambiguous) {
// remove unambiguous from prior noun's matches
let excluded_id = phrase.parsedNoun.is_unambiguous;
let allindex = phrase1.parsedNoun.matches.all.indexOf(excluded_id);
let qualindex =
phrase1.parsedNoun.matches.qualified.indexOf(excluded_id);
// remove excluded_id from parsedNoun.matches.all
phrase1.parsedNoun.matches.all.splice(allindex, 1);
// remove excluded_id from parsedNoun.matches.qualified
phrase1.parsedNoun.matches.qualified.splice(qualindex, 1);
// remove excluded_id from parsedNoun.is_unambiguous
if (phrase1.parsedNoun.is_unambiguous === excluded_id)
phrase1.parsedNoun.is_unambiguous = "";
if (allindex > -1 || qualindex > -1) {
// we might need to re-qualify the last noun
if (phrase1.parsedNoun) {
phrase1.parsedNoun = this.qualifyParsedNoun({
parsedNoun: phrase1.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.asset_id &&
phrase1.parsedNoun.asset_id &&
phrase1.parsedNoun.matches.qualified.length > 1
) {
for (
var i = phrase1.parsedNoun.matches.qualified.length - 1;
i > -1;
i--
) {
if (
phrase.parsedNoun.is_qualified ===
phrase1.parsedNoun.matches.qualified[i]
) {
phrase1.parsedNoun.matches.qualified.splice(i, 1);
}
}
} // remove iobj from dobj collection
} // n>1 / exclude from prior
// --------------------------------------------------
// qualify noun
// --------------------------------------------------
let context = null;
// if (phrase.noun === "all") {
if (noun_is_plural) {
if (["put", "drop", "give"].includes(dictionary_verb.name)) {
// if player is putting/dropping/giving all,
// set context to player's inventory
context = subject;
}
if (["take"].includes(dictionary_verb.name)) {
// if player input "take all" we can handle that as is
// but if they input "take all from thing" we don't yet
// have the context of thing, so we'll have to do a
// followup pass after we've handled phrase2
}
}
if (phrase.parsedNoun) {
phrase.parsedNoun = this.qualifyParsedNoun({
parsedNoun: phrase.parsedNoun,
parsedVerb: dictionary_verb.name,
nounIndex: n,
context: context,
});
// If qualifyParsedNoun returned false, then it already
// printed an error msg to the player, so we can just...
if (!phrase.parsedNoun) return false;
}
// --------------------------------------------------
// qualify possessive
// --------------------------------------------------
if (phrase.possessive) {
const asset = this.game.getAsset(phrase.parsedNoun.asset_id);
if (!phrase.possessive.has(asset)) {
console.warn(`phrase.possessive`, phrase.possessive);
let err = `parser.handleSentence > ${phrase.possessive.id} doesn't contain ${asset.id}`;
this.game.log("L1569", "warn", "high", err, "Parser");
this.game.debug(`D1634`, `${fx} `, `${err}`);
let player = this.game.getPlayer();
let name = "";
if (phrase.possessive.id === player.id) {
name = A.FX.sentencecase(player.inflect("we"));
}
msg += `${name || phrase.possessive.proper_name || phrase.possessive.name} ${phrase.possessive.inflect("don't")} appear to be carrying any ${asset.name}. `;
this.game.print(msg, this_turn.output_class);
return false;
}
} // phrase.possessive
// --------------------------------------------------
// redirectVerb
// --------------------------------------------------
// Has author used redirectVerb for this verb on direct_object?
if (n === 1 && phrase.parsedNoun && phrase.parsedNoun.is_qualified) {
let direct_object = this.game.getAsset(phrase.parsedNoun.is_qualified);
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
// ==================================================
// END first pass for each phrase
// BEGIN second pass cleanup
// ==================================================
// --------------------------------------------------
// handle "take all from asset"
// we'll have already set
// phrase1.parsedNoun.matches.qualified
// in the first loop, and now we need
// to reset it to asset scope.
// --------------------------------------------------
// let phrase1 = this_turn.verified_sentence[`phrase1`];
const phrase2 = this_turn.verified_sentence[`phrase2`];
// if (phrase1?.parsedNoun?.input === "all" && phrase2?.noun) {
if (phrase1?.parsedNoun?.is_plural && phrase2?.noun) {
let context,
context_contents,
shift_exclusions = null;
// does phrase2 have exclusions?
// if so those should apply to phrase 1
console.warn(`phrase2.exclusions`, phrase2.exclusions);
if (phrase2.exclusions?.length) {
console.warn(`phrase 2 has exclusions`);
// phrase2 exclusions probably belong with phrase 1
if ("undefined" === typeof phrase1.exclusions) phrase1.exclusions = [];
// phrase1.exclusions = phrase1.exclusions.concat(phrase2.exclusions);
for (let x = 0; x < phrase2.exclusions.length; x++) {
if (!phrase1.exclusions.includes(phrase2.exclusions[x]))
phrase1.exclusions.push(phrase2.exclusions[x]);
}
// phrase2.exclusion = "";
phrase2.exclusions = [];
// shift_exclusions = true;
}
if (["put", "give"].includes(dictionary_verb.name) && phrase2.noun) {
context = subject;
// exclude phrase 2 target asset from phrase1 qualified list
if (phrase1.parsedNoun.matches.qualified.includes(context.id)) {
let index = phrase1.parsedNoun.matches.qualified.indexOf(context.id);
phrase1.parsedNoun.matches.qualified.splice(index, 1);
if (phrase1.parsedNoun.matches.qindex >= index) {
phrase1.parsedNoun.matches.qindex = index - 1;
}
}
}
// take / get
if (
["take"].includes(dictionary_verb.name) &&
phrase2.preposition === "from"
) {
context = this.game.getAsset(phrase2.parsedNoun.asset_id);
}
if (context) {
context_contents = context.getAllContents(); // getAllNestedContents ?
// did we have an exclusion?
// something like: "take all but a from b"
if (phrase1.exclusions?.length) {
// we already split .exclusion into .exclusions in the first pass
const exclusions = phrase1.exclusions;
for (let x = 0; x < exclusions.length; x++) {
const excluded_noun = exclusions[x];
const excluded_parsed_noun = this.parseNoun(excluded_noun, context);
this.game.log(
"L1052",
"log",
"high",
["[${fx}] exclude:", excluded_parsed_noun.matches.qualified],
"Parser"
);
if (excluded_parsed_noun) {
for (
let exp = 0;
exp < excluded_parsed_noun.matches.qualified.length;
exp++
) {
let excluded_id = excluded_parsed_noun.matches.qualified[exp];
if (context_contents.includes(excluded_id)) {
context_contents.splice(
context_contents.indexOf(excluded_id),
1
);
}
}
}
}
}
if (phrase1.parsedNoun.is_all) {
// if player asked for all
// set matches.qualified to contents of phrase2 asset
phrase1.parsedNoun.matches.qualified = context_contents;
// reset is_qualified in case it was set by the first loop
phrase1.parsedNoun.is_qualified = "";
// requalify the list
phrase1.parsedNoun = this.qualifyParsedNoun({
parsedNoun: phrase1.parsedNoun,
parsedVerb: dictionary_verb.name,
nounIndex: 1,
context: context,
});
// if no parsedNoun, qualifyParsedNoun
// will have already printed an error, so...
if (!phrase1.parsedNoun) return false;
} else {
// player asked for something like "all keys"
// and phrase1.parsedNoun.matches.qualified
// has already been parsed for keys but now
// needs to remove anything not in context
for (
let q = phrase1.parsedNoun.matches.qualified.length - 1;
q > -1;
q--
) {
if (
!context_contents.includes(
phrase1.parsedNoun.matches.qualified[q]
)
) {
phrase1.parsedNoun.matches.qualified.splice(q, 1);
if (phrase1.parsedNoun.qindex === q)
phrase1.parsedNoun.qindex = "";
if (phrase1.parsedNoun.qindex && phrase1.parsedNoun.qindex > q) {
phrase1.parsedNoun.qindex--;
}
}
}
if (phrase1.parsedNoun.matches.qualified.length === 0) {
this.game.debug(
`D1515`,
`${fx} `,
` parser.handleSentence() eliminated all possibilities`
);
let msg = `{We} don't see any ${phrase1.parsedNoun.input} ${context.default_aspect} ${context.article_name}. `;
this.game.print(msg, this_turn.output_class);
return false;
}
}
}
}
// --------------------------------------------------
// 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 next_phrase = this_turn.verified_sentence[`phrase${n + 1}`];
var parsedNoun = phrase.parsedNoun;
if (!parsedNoun) continue;
this.game.log(
"L1205",
"log",
"high",
`[${fx}] final check on phrase ${n}`,
"Parser"
);
if (parsedNoun.is_substance) {
phrase["specified_substance"] = parsedNoun.is_substance;
}
// --------------------------------------------------
// multiple assets found?
// --------------------------------------------------
// special test for substances
// If input referred to a substance, we wrote the substance id to
// parsedNoun.is_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
// because subatances are singular.
if (
(parsedNoun.matches.qualified.length > 1 &&
!parsedNoun.is_unambiguous) ||
(parsedNoun.matches.qualified.length > 1 &&
parsedNoun.is_unambiguous &&
parsedNoun.is_substance)
) {
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.normalized_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 area_scenery / area_scenery / global_scenery
// --------------------------------------------------
let { excludable_assets } = this.categorizeAssets(
parsedNoun.matches.qualified
);
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.categorizeAssets(
parsedNoun.matches.qualified
);
// 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`,
`${fx} `,
` ${dictionary_verb.name} doesn't handle multiple objects `
);
this.game.log(
"L1206",
"warn",
"high",
`[${fx}] ${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.is_substance) {
this.game.debug(
`D1447`,
`${fx} `,
` 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.is_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.is_qualified = match;
parsedNoun.matches.setby = "MC01";
}
}
}
}
}
// --------------------------------------------------
// infer_containers_prefers_reservoir?
// For example, if player input "throw sand"
// is there a body of sand at hand?
// Does not prompt for disambiguation,
// just uses the first one found.
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
this.game.settings.infer_containers_prefers_reservoir
) {
let { reservoir_assets, emitter_assets } = this.categorizeAssets(
parsedNoun.matches.qualified
);
// this takes substance from the first available reservoir
// aka pond, lake, ocean
if (reservoir_assets.length) {
parsedNoun.matches.qualified = [reservoir_assets[0]];
parsedNoun.is_qualified = reservoir_assets[0];
parsedNoun.matches.setby = "MC02";
}
// this takes substance from the first available emitter
// aka hose, faucet, waterfall
else if (emitter_assets.length) {
parsedNoun.matches.qualified = [emitter_assets[0]];
parsedNoun.is_qualified = emitter_assets[0];
parsedNoun.matches.setby = "MC03";
}
}
// --------------------------------------------------
// 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.is_qualified = match;
parsedNoun.matches.setby = "MC04";
}
}
} // parsedNoun.is_substance
// --------------------------------------------------
// handling for everything else besides substances
// --------------------------------------------------
else if (!parsedNoun.is_substance) {
this.game.debug(
`D1201`,
`${fx} `,
` phrase${n}.parsedNoun needs disambiguation `
);
this.game.log(
"L1207",
"log",
"high",
`[${fx}] parsedNoun ${n} needs disambiguation: ${parsedNoun.matches.qualified.join(", ")}`,
"Parser"
);
var phrase = this_turn.verified_sentence[`phrase${n}`];
var parsedNoun = phrase.parsedNoun;
// --------------------------------------------------
// Categorize the found assets
// to help us narrow the list
// based on verb phrase options.
// --------------------------------------------------
let {
present_assets,
absent_assets,
carried_assets,
uncarried_assets,
local_assets,
global_assets,
excludable_assets,
dispenser_assets,
fungible_assets,
room_assets,
} = this.categorizeAssets(parsedNoun.matches.qualified);
// --------------------------------------------------
// disambiguation considers last turn
// --------------------------------------------------
// @TODO Broaden this to consider whether this turn's
// items are related to last turn, for example, here,
// obviously the player meant sink's hot water.
// > x sink
// > turn on hot water
// did you mean sink's hot water or tub's hot water?
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
this.game.settings.disambiguation_considers_last_turn
) {
this.game.log(
"L1687",
"log",
"high",
`[${fx}] parsedNoun ${n} disambiguation_considers_last_turn`,
"Parser"
);
// look at each phrase of last turn to see if this asset was mentioned
for (
let phrasenum = 1;
phrasenum <= last_turn.getPhraseCount();
phrasenum++
) {
let phrase = last_turn.verified_sentence[`phrase${phrasenum}`];
// @TODO should this get parsedNoun.asset_id? See ParsedNoun.js
// 5/15/26 there is some nuance to be worked out here
// parsedNoun.is_unambiguous is set unambiguous asset is found
// parsedNoun.asset_id is set when any asset is resolved
// using is_unambiguous leads to an issue with fungibles
// where you can't take two different fungibles in a row because
// the parser defaults to the fungible found last turn.
if (phrase?.parsedNoun?.is_unambiguous) {
let prior_id = phrase.parsedNoun.is_unambiguous;
// if (phrase?.parsedNoun?.asset_id) {
// let prior_id = phrase.parsedNoun.asset_id;
if (prior_id) {
if (parsedNoun.matches.qualified.includes(prior_id)) {
this.game.debug(
`D1373`,
`${fx} `,
` disambiguation defaulting to last turn's ${prior_id}`
);
parsedNoun.matches.qualified = [prior_id];
parsedNoun.is_qualified = prior_id;
parsedNoun.matches.setby = "MC05";
}
}
}
}
} // disambiguation_considers_last_turn
// --------------------------------------------------
// present?
// 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_prefers.present
) {
this.game.log(
"L1688",
"log",
"high",
`[${fx}] parsedNoun ${n} prefers present assets`,
"Parser"
);
if (present_assets.length && absent_assets.length) {
absent_assets.forEach((item) => {
console.warn(`remove absent asset`, item);
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
parsedNoun.matches.qualified.splice(index, 1);
}
});
if (parsedNoun.matches.qualified.length === 1) {
parsedNoun.is_qualified = parsedNoun.matches.qualified[0];
parsedNoun.matches.setby = "MC06";
}
}
} // present
// --------------------------------------------------
// absent?
// for verbs like "go to asset" where we can handle present
// or absent but prefer an absent asset if there's ambiguity
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
dictionary_verb[`phrase${n}`].noun_prefers.absent
) {
this.game.log(
"L1689",
"log",
"high",
`[${fx}] parsedNoun ${n} prefers absent assets`,
"Parser"
);
if (present_assets.length && absent_assets.length) {
present_assets.forEach((item) => {
console.warn(`remove absent asset`, item);
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
parsedNoun.matches.qualified.splice(index, 1);
}
});
if (parsedNoun.matches.qualified.length === 1) {
parsedNoun.is_qualified = parsedNoun.matches.qualified[0];
parsedNoun.matches.setby = "MC07";
}
}
} // absent
// --------------------------------------------------
// carried?
// 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_prefers.carried
) {
this.game.log(
"L1690",
"log",
"high",
`[${fx}] parsedNoun ${n} prefers carried assets`,
"Parser"
);
if (carried_assets.length && uncarried_assets.length) {
uncarried_assets.forEach((item) => {
console.warn(`remove uncarried asset`, item);
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
parsedNoun.matches.qualified.splice(index, 1);
}
});
if (parsedNoun.matches.qualified.length === 1) {
parsedNoun.is_qualified = parsedNoun.matches.qualified[0];
parsedNoun.matches.setby = "MC08";
}
}
} // carried
// --------------------------------------------------
// uncarried?
// 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_prefers.uncarried
) {
this.game.log(
"L1691",
"log",
"high",
`[${fx}] parsedNoun ${n} prefers uncarried assets`,
"Parser"
);
if (carried_assets.length && uncarried_assets.length) {
carried_assets.forEach((item) => {
console.warn(`remove carried asset`, item);
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
parsedNoun.matches.qualified.splice(index, 1);
}
});
if (parsedNoun.matches.qualified.length === 1) {
parsedNoun.is_qualified = parsedNoun.matches.qualified[0];
parsedNoun.matches.setby = "MC09";
}
}
} // prefer_uncarried_over_carried
// --------------------------------------------------
// discard duplicate fungibles
// we'll just take the first one
// --------------------------------------------------
if (fungible_assets.length > 1) {
for (let i = fungible_assets.length - 1; i > 0; i--) {
let item = fungible_assets[i];
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
console.warn(`remove fungible asset`, item);
parsedNoun.matches.qualified.splice(index, 1);
}
fungible_assets.pop();
}
}
// --------------------------------------------------
// discard duplicate dispensers?
// @TODO do we need to verify that dispensers dispense the same thing?
// or hasn't this already been handled?
// Do we want to take the first available dispenser
// or prompt for disambiguation?
// --------------------------------------------------
if (dispenser_assets.length > 1) {
for (let i = dispenser_assets.length - 1; i > 0; i--) {
let item = dispenser_assets[i];
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
console.warn(`remove dispenser asset`, item);
parsedNoun.matches.qualified.splice(index, 1);
}
dispenser_assets.pop();
}
}
// --------------------------------------------------
// dispenser?
// for verbs like "take asset" where we might have a fungible
// asset and a dispenser of same and prefer to take from the
// dispenser if there's ambiguity
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
dictionary_verb[`phrase${n}`].noun_prefers.dispenser
) {
this.game.log(
"L1692",
"log",
"high",
`[${fx}] parsedNoun ${n} prefers dispenser assets`,
"Parser"
);
if (dispenser_assets.length && fungible_assets.length) {
fungible_assets.forEach((item) => {
console.warn(`remove fungible`, item);
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
parsedNoun.matches.qualified.splice(index, 1);
}
});
if (parsedNoun.matches.qualified.length === 1) {
parsedNoun.is_qualified = parsedNoun.matches.qualified[0];
parsedNoun.matches.setby = "MC10";
}
}
} // dispenser
// --------------------------------------------------
// fungible?
// for verbs like "drop asset" where we might have a fungible
// asset and a dispenser of same and prefer to drop the
// fungible if there's ambiguity
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
dictionary_verb[`phrase${n}`].noun_prefers.fungible
) {
this.game.log(
"L1693",
"log",
"high",
`[${fx}] parsedNoun ${n} prefers fungible assets`,
"Parser"
);
if (dispenser_assets.length && fungible_assets.length) {
dispenser_assets.forEach((item) => {
console.warn(`remove dispenser`, item);
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
parsedNoun.matches.qualified.splice(index, 1);
}
});
if (parsedNoun.matches.qualified.length === 1) {
parsedNoun.is_qualified = parsedNoun.matches.qualified[0];
parsedNoun.matches.setby = "MC11";
}
}
} // fungible
// --------------------------------------------------
// local vs global?
// if we will have multiple assets, does omitting
// globals get us down to one?
// --------------------------------------------------
if (parsedNoun.matches.qualified.length > 1) {
this.game.log(
"L1694",
"log",
"high",
`[${fx}] parsedNoun ${n} prefer local over global`,
"Parser"
);
if (local_assets.length && global_assets.length) {
global_assets.forEach((item) => {
console.warn(`remove global`, item);
let index = parsedNoun.matches.qualified.indexOf(item);
if (index > -1) {
parsedNoun.matches.qualified.splice(index, 1);
}
});
if (parsedNoun.matches.qualified.length === 1) {
parsedNoun.is_qualified = parsedNoun.matches.qualified[0];
parsedNoun.matches.setby = "MC12";
}
}
}
// --------------------------------------------------
// 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
//
// @TODO is this redundant with L1687?
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
this.game.settings.disambiguation_considers_last_turn &&
!last_turn.plural
) {
this.game.log(
"L1695",
"log",
"high",
`[${fx}] parsedNoun ${n} disambiguation_considers_last_turn`,
"Parser"
);
// 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.is_qualified === last_turn_asset.id
) {
this.game.debug(
`D1589`,
`${fx} `,
` disambiguation defaulting to last turn's ${last_turn_asset.name}`
);
parsedNoun.matches.qualified = [last_turn_asset.id];
parsedNoun.is_qualified = last_turn_asset.id;
parsedNoun.matches.setby = "MC13";
}
}
}
// --------------------------------------------------
// 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 area_scenery / area_scenery / global_scenery
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
excludable_assets.length
) {
this.game.log(
"L1696",
"log",
"high",
`[${fx}] parsedNoun ${n} excludable_assets`,
"Parser"
);
// 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.categorizeAssets(
parsedNoun.matches.qualified
);
// 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
// --------------------------------------------------
// can we exclude the current room from disambiguation?
// --------------------------------------------------
if (
parsedNoun.matches.qualified.length > 1 &&
room_assets.length &&
this.game.settings.disambiguation_can_exclude_room
) {
this.game.log(
"L1697",
"log",
"high",
`[${fx}] parsedNoun ${n} disambiguation_can_exclude_room`,
"Parser"
);
// reset the room_assets list to only include things from
// parsedNoun.matches.qualified, which may have been modified by a prior block
let { room_assets } = this.categorizeAssets(
parsedNoun.matches.qualified
);
// only exclude rooms if there are other assets
if (room_assets.length < parsedNoun.matches.qualified.length) {
parsedNoun = this.excludeFromParsedNoun(
parsedNoun,
room_assets
);
}
} // disambiguation_can_exclude_room
// --------------------------------------------------
// 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) {
this.game.log(
"L1698",
"log",
"high",
`[${fx}] parsedNoun ${n} infer second noun following direction`,
"Parser"
);
let phrase1 = this_turn.verified_sentence.phrase1;
if (
phrase1.parsedNoun?.is_direction &&
phrase1.parsedNoun.is_qualified &&
parsedNoun.matches.qualified.includes(
phrase1.parsedNoun.is_qualified
)
) {
parsedNoun.matches.qualified = [
phrase1.parsedNoun.is_qualified,
];
parsedNoun.is_qualified = phrase1.parsedNoun.is_qualified;
parsedNoun.matches.setby = "MC14";
}
}
} // 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.game.log(
"L1699",
"log",
"high",
`[${fx}] parsedNoun ${n} unable to resolve disambiguation`,
"Parser"
);
this.printNounDisambiguation({
parsedNoun: parsedNoun,
nounIndex: n,
});
return false;
}
} // ! let verb handle disambiguation
} // more than one qualified
// --------------------------------------------------
// did player ask for a fungible class?
// --------------------------------------------------
let [type, Class, aspect, dispenser] =
parsedNoun.matches.qualified[0].split(":");
if (dispenser && type === "fungible") {
this.game.log(
"L1700",
"log",
"high",
`[${fx}] parsedNoun ${n} found fungible dispenser`,
"Parser"
);
// is the class listed in fungible_classes?
const fungible_class = this.game.fungible_classes[Class];
if ("undefined" !== typeof fungible_class) {
// @TODO check max_extant and max_count
// let msg = `{We} can't take any more pistachios. `;
// this.game.print(msg, output_class);
// return false;
// create a new instance of the fungible asset
dispenser = this.game.getAsset(dispenser);
const dispensed = this.game.dispenseAsset(Class, aspect, dispenser);
if (!this.game.world._fungibles.includes(dispensed.id)) {
this.game.world._fungibles.push(dispensed.id);
}
parsedNoun.matches.qualified[0] = dispensed.id;
parsedNoun.is_qualified = dispensed.id;
parsedNoun.matches.setby = "MC15";
}
}
// end fungible
} // end each phrase redux
// --------------------------------------------------
// ensure that the verb handles the sentence structure
// --------------------------------------------------
if (false === this.verifySentenceStructure()) return false;
// --------------------------------------------------
// it looks like we're ready to do the thing!
// --------------------------------------------------
this.game.log(
"L1208",
"log",
"high",
"[${fx}] handleSentence() end",
"Parser"
);
let direct_object = this_turn.getAsset(1);
if (direct_object?.is?.collection && dictionary_verb.enqueue_collections) {
this.game.debug(
`D1091`,
`${fx} `,
` ${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",
`[${fx}] doVerb ${dictionary_verb.name}`,
"Parser"
);
return dictionary_verb.do();
}
return true;
}; // handleSentence
})();