// getModifiedDescription.js
(function () {
/* global adventurejs A */
var p = adventurejs.Game.prototype;
/**
* Get modified descriptions,
* such as "look at this through magnifying glass with candle",
* where "through magnifying glass, with candle" is a key at
* asset.descriptions.look["through magnifying glass,
* with candle"].
* "look" is always the default description, but any identifier
* can have modifiers.
* <br><br>
* This is scoped to Game rather than Asset because there are
* cases that handle simple unclassed objects with description
* properties, notably: properties of room_scenery.
* @memberOf adventurejs.Game
* @method adventurejs.Game#getModifiedDescription
* @param {Object} options
* @param {Object} options.asset The asset under consideration.
* @param {String} options.identifier
* @param {Boolean} options.find_modifiers Optional instruction to find view modifiers such as light sources and lenses.
* @param {Boolean} options.fallback_base Optional instruction to fall back to asset's default description.
* @param {Boolean} options.append_base Optional instruction to append modified description to default description.
* @param {Boolean} options.prepend_base Optional instruction to prepend modified description to default description.
* @return {String}
*/
p.getModifiedDescription = function Game_getModifiedDescription({
asset,
identifier = "look",
find_modifiers,
fallback_base,
append_base,
prepend_base,
}) {
let msg = "";
if (identifier === "at") identifier = "look";
if (asset.proxy) {
let proxy = this.game.getAsset(asset.proxy);
if (proxy) {
let proxy_description = this.game.getModifiedDescription({
asset: proxy,
identifier: identifier,
find_modifiers: find_modifiers,
fallback_base: fallback_base,
});
if (proxy_description) return proxy_description;
}
}
var input = this.game.getInput();
var base_description = this.getDescription({ asset, identifier }) || "";
// does object have descriptions at all?
if (!asset.descriptions) {
return asset.description
? asset.description
: fallback_base
? base_description
: "";
}
// does this description identifier exist?
if ("undefined" === typeof asset.descriptions[identifier]) {
// if identifier was look then we got nuthin
if (identifier === "look" || !asset.descriptions.look) {
return fallback_base ? base_description : "";
}
// we have look - does it exist under look?
if ("undefined" === typeof asset.descriptions.look[identifier]) {
// no such identifier
return fallback_base ? base_description : "";
}
// it exists - treat it as a modifier on look
input.pushViewModifier(identifier, null, "input");
identifier = "look";
}
// view_modifiers is expected to look like:
// view_modifiers: [ { asset: ClassedObject, prep: "string", type: "string" } ]
if (find_modifiers) {
this.game.findViewModifiers();
}
if (input.view_modifiers.length === 0) {
return fallback_base ? base_description : "";
}
// look for valid descriptions
for (let index = 0; index < input.view_modifiers.length; index++) {
let indirect_identifier = input.view_modifiers[index].identifier;
let indirect_asset = input.view_modifiers[index].asset;
let indirect_aspects;
if (!indirect_identifier || !indirect_asset) {
continue;
}
// account for equivalencies: for example,
// in the case of glasses, binoculars, etc player might say
// "look at x with binoculars" while author has coded for
// "look at x through binoculars" and we'll allow it
// though it means we have to search for all acceptable equivalencies
indirect_aspects = [indirect_identifier]; // convert to array
// "look with" may be equivalent to "look through"
if (
indirect_identifier === "with" &&
indirect_asset.hasQuirk("look_with_means_look_through")
) {
if (!indirect_aspects.includes("through"))
indirect_aspects.push("through");
}
if (
indirect_identifier === "through" &&
indirect_asset.hasQuirk("look_with_means_look_through")
) {
if (!indirect_aspects.includes("with")) indirect_aspects.push("with");
}
// "look in" may be equivalent to "look on"
if (
indirect_identifier === "in" &&
indirect_asset.hasQuirk("in_means_on")
) {
if (!indirect_aspects.includes("on")) indirect_aspects.push("on");
}
// save equivalencies
for (let i = 0; i < indirect_aspects.length; i++) {
let indirect_identifier = indirect_aspects[i];
let target = `${indirect_identifier} ${indirect_asset.name}`;
if (!input.view_modifiers[index].equivalencies) {
input.view_modifiers[index].equivalencies = [];
}
if (!input.view_modifiers[index].equivalencies.includes(target)) {
input.view_modifiers[index].equivalencies.push(target);
}
}
}
// example of a view_modifier
// {aspect: 'with', asset: Candle, type: 'auto', equivalencies: Array(2)}
// equivalencies: (2) ['with occult candle', 'through occult candle']
let provided_modifier_targets = [];
for (let index = 0; index < input.view_modifiers.length; index++) {
provided_modifier_targets.push(input.view_modifiers[index].equivalencies);
}
// we're allowing authors to set view modifiers in any order:
// for instance "in pit, from tree" or "from tree, in pit"
// so we have to look for every possible combination
let expanded_modifier_targets = A.generateCombinations(
provided_modifier_targets
);
if ("string" === typeof asset.descriptions[identifier]) {
return asset.descriptions[identifier];
}
let comparisons = [];
let description_keys = Object.keys(asset.descriptions[identifier]);
// loop through expanded_modifier_targets
// inside each loop, loop through description keys
for (let i = 0; i < expanded_modifier_targets.length; i++) {
// console.warn(`expanded_modifier_targets ${i}`, expanded_modifier_targets);
for (let j = 0; j < description_keys.length; j++) {
// modifier can be greater than description
// but description can not be greater than modifier
// alternately
// modifier can have elements that are not in description
// description can't have any elements that are not in modifier
if (
description_keys[j].split(",").length >
expanded_modifier_targets[i].split(",").length
) {
continue;
}
let count = A.countCommonElements(
expanded_modifier_targets[i],
description_keys[j]
);
comparisons.push({
view_modifiers: expanded_modifier_targets[i],
description: description_keys[j],
count: count,
});
// console.warn(`description_keys[${j}]:`, description_keys[j]);
}
}
// if it finds a description that matches multiple conditions,
// it prints that but if it finds multiple single conditions
// it prints those
let maxstack =
this.game.settings.max_level_of_modified_descriptions_to_stack;
let stacked_descriptions = [];
let highestcount = -1;
let highestindex = -1;
// loop through to get the highest number of matching descriptions
for (let i = 0; i < comparisons.length; i++) {
// console.warn(`comparisons[${i}]`, comparisons[i]);
let count = comparisons[i].count;
let description_length = comparisons[i].description.split(",").length;
if (count === description_length && count > highestcount) {
highestcount = comparisons[i].count;
highestindex = i;
}
}
// console.warn(
// "getModifiedDescription > Highest count is " + highestcount,
// comparisons[highestindex]
// );
// now loop through again to get all items that match highest count
for (let i = 0; i < comparisons.length; i++) {
// console.warn(`comparisons[${i}]`, comparisons[i]);
let count = comparisons[i].count;
let description_length = comparisons[i].description.split(",").length;
// if highest count is 1 and there are multiple descriptions, stack them
if (
/* count === 1 && */
count <= maxstack &&
count === description_length &&
count === highestcount
) {
stacked_descriptions.push(comparisons[i]);
}
// if highest count is more than 1, only print the first
// this is to avoid overlaps
// if (count > 1 && stacked_descriptions.length === 0) {
if (count > maxstack && stacked_descriptions.length === 0) {
stacked_descriptions.push(comparisons[i]);
}
}
// console.warn(
// "getModifiedDescription > stacked_descriptions",
// stacked_descriptions
// );
if (highestcount > -1) {
// save a record of use back to the original view_modifiers
// switching from one to many
// console.warn(
// `getModifiedDescription > comparisons[${highestindex}].description`,
// comparisons[highestindex].description
// );
// let modifiers_set = comparisons[highestindex].description;
// let best_description =
// asset.descriptions[identifier][comparisons[highestindex].description];
let modifiers_sets = "";
let best_descriptions = [];
for (let i = 0; i < stacked_descriptions.length; i++) {
best_descriptions.push(
asset.descriptions[identifier][stacked_descriptions[i].description]
);
if (modifiers_sets.length) modifiers_sets += ",";
modifiers_sets += stacked_descriptions[i].description;
}
// switching from one to many
// let modifiers = modifiers_set.split(",").map((item) => item.trim());
let modifiers = modifiers_sets.split(",").map((item) => item.trim());
let user_modified = false;
for (let i = 0; i < modifiers.length; i++) {
let modifier = modifiers[i];
// console.warn({ modifier });
if (!modifier.length) continue;
for (let j = 0; j < input.view_modifiers.length; j++) {
if (input.view_modifiers[j].string === modifier) {
input.view_modifiers[j].used = true;
if (input.view_modifiers[j].type === "input") user_modified = true;
}
}
}
// call the description(s)
// this version prints one
// msg = A.getSAF.call(this.game, best_description, asset);
// this version prints many
for (let i = 0; i < best_descriptions.length; i++) {
// print the count number for debugging
// msg += i + ") " + A.getSAF.call(this.game, best_descriptions[i], asset);
msg += A.getSAF.call(this.game, best_descriptions[i], asset);
}
// did we want to prepend the base description?
if (prepend_base || this.game.settings.concatenate_descriptions) {
msg = base_description + msg;
}
// did we want to append the base description?
if (append_base) {
msg = msg + base_description;
}
return msg;
}
// fallback
return fallback_base ? base_description : "";
};
})();