// Asset.js
(function () {
/*global adventurejs A*/
"use strict";
/**
* @augments adventurejs.Atom
* @class adventurejs.Asset
* @ajsconstruct MyGame.createAsset({ "class":"Asset", "name":"foo" })
* @ajsconstructedby adventurejs.Game#createAsset
* @param {String} game_name The name of the top level game object.
* @param {String} name A name for the object, to be serialized and used as ID.
* @ajsnavheading BaseClasses
* @summary Framework class that is the ancestor for all classes in the game world.
* @tutorial AssetReference__Overview
* @classdesc
* <p>
* <strong>Asset</strong> is subclassed from the
* foundational class {@link adventurejs.Atom|Atom},
* and is the most basic game world class
* from which all other asset classes,
* {@link adventurejs.Tangible|Tangible},
* {@link adventurejs.Substance|Substance}, and
* {@link adventurejs.Intangible|Intangible},
* are derived. Besides setting
* the prototypal validation and initialization functions,
* it also defines many common properties used to determine
* how assets appear in printed statements. It's unlikely
* that authors would want to subclass Asset directly as
* it has few properties, unless it's to create a whole new
* low-level Asset type.
* </p>
**/
class Asset extends adventurejs.Atom {
constructor(name, game_name) {
super(name, game_name);
this.class = "Asset";
/**
* <strong>is</strong> is a generalized container for
* asset states. Many states are / can be stored here.
* A chief benefit of this is being able to get specific
* states by testing <code>asset.is.state</code>.
* Note that there is also an asset.$is() method which
* is related to this, but is a distinct function.
* @var {Object} adventurejs.Asset#is
*/
this.is = new adventurejs.Asset_Is("is", this.game_name, this.id).set({
parent_id: this.id,
});
/**
* <strong>can</strong> is a generalized container for
* asset boolean properties.
* @var {Object} adventurejs.Asset#can
*/
this.can = new adventurejs.Asset_Can("can", this.game_name, this.id).set({
parent_id: this.id,
});
/**
* <strong>must</strong> is a generalized container for
* asset boolean properties.
* @var {Object} adventurejs.Asset#must
*/
this.must = new adventurejs.Asset_Must(
"must",
this.game_name,
this.id
).set({
parent_id: this.id,
});
/**
* <strong>dov</strong> is a container for direct object
* verb subscriptions.
* @var {Boolean} adventurejs.Asset#dov
* @default {}
*/
this.dov = {};
this.setDOV("think");
/**
* <strong>iov</strong> is a container for direct object
* verb subscriptions.
* @var {Boolean} adventurejs.Asset#iov
* @default {}
*/
this.iov = {};
this.setIOV("ask");
this.setIOV("tell");
/**
* <strong>quirks</strong> is a container for setting how
* to handle ambiguous verbs based on context. For example,
* if player inputs "stand" while sitting in a chair with
* <code>quirks.stand_means_get_off</code> set to true, player
* will get off the chair, as opposed to trying to stand in
* place on the chair.
* @var {Object} adventurejs.Asset#quirks
*/
this.quirks = {};
/**
* Set to true to have an object's name be printed bold.
* @var {Boolean} adventurejs.Asset#print_bold
* @default false
*/
this.print_bold = false;
/**
* Set to true to have an object's name be printed italic.
* @var {Boolean} adventurejs.Asset#print_italic
* @default false
*/
this.print_italic = false;
/**
* Almost all assets will be listed in the world_lookup table
* that is used to find assets from player input ,
* with some exceptions, such as SceneryZones.
* @var {Boolean} adventurejs.Asset#exclude_from_lookup
* @default false
*/
this.exclude_from_lookup = false;
/**
* Object names are split into individual words for the world_lookup table.
* For example, given an object named "crystal sword", adventurejs creates
* lookup table entries for "crystal" and "sword". This lets the game
* recognize either word as player input, such as "get crystal" or
* "swing sword".<br><br>
* But, an author might want to name a thing, eg, "hole in the ground",
* in which case we wind up with lookup table entries for "hole" and
* "in" and "the" and "ground", which is likely to lead to bad input parsing.
* To avoid name splitting, set split_name_for_world_lookup to false.
* The object's full name will still be added to the lookup.
* @var {Boolean} adventurejs.Asset#split_name_for_world_lookup
* @default true
*
*/
this.split_name_for_world_lookup = true;
/**
* A list of descriptions for a variety of contexts. Only
* <code class="property">description</code> is required, all others
* are optional. Most of these apply only to Tangible Asset.
* <ul>
* <li><code class="property">descriptions.description</code> - </li>
* <li><code class="property">descriptions.brief</code> - used for room descriptions if player has typed "brief"</li>
* <li><code class="property">descriptions.verbose</code> - used for room descriptions if player has typed "verbose"</li>
* <li><code class="property">descriptions.listen</code> - used if player types "listen" or "listen to thing"</li>
* <li><code class="property">descriptions.in</code> - used if player types "look in thing"</li>
* <li><code class="property">descriptions.through</code> - used if player types "look through thing"</li>
* <li><code class="property">descriptions.smell</code> - used if player types "smell thing"</li>
* <li><code class="property">descriptions.taste</code> - used if player types "taste thing"</li>
* <li><code class="property">descriptions.touch</code> - used if player types "touch thing"</li>
* <li><code class="property">descriptions.careful</code> - used if player types "carefully examine thing"</li>
* </ul>
* @var {Object} adventurejs.Asset#descriptions
*/
this.descriptions = {
look: "",
brief: "",
verbose: "",
};
/**
* Collection is a special property that lets one object
* stand in for a group of objects.
* For example, object desk has three drawers:
* top_drawer, middle_drawer, bottom_drawer.
* desk_drawers is a scenery object attached to desk which
* intercepts player input like "examine drawers", so that author
* can return a response like "You see three drawers."
* @var {Getter/Setter} adventurejs.Asset#collection
*
*/
this.collection = [];
this.name = "";
/**
* Used to determine whether to display a name or proper name.
* @var {Boolean} adventurejs.Asset#name_is_proper
* @default false
*/
this.name_is_proper = false;
/**
* The asset's proper name, as provided in the game file. Assets
* can have both a name and a proper name. Used chiefly for
* characters, so that, for example, an asset can be both 'cat' and
* 'Mister Whiskers'.
* @var {String} adventurejs.Asset#propername
*/
this.propername = "";
/**
* The asset's preferred definite article.
* Can be overridden in game file.
* @var {String} adventurejs.Asset#definite_article
* @default 'the'
*/
this.definite_article = "the"; // TODO but maybe not? honorific? "Mr." "Mrs."
/**
* The asset's preferred indefinite article.
* Can be overridden in game file.
* @var {String} adventurejs.Asset#indefinite_article
* @default 'a'
*/
this.indefinite_article = "a"; // "a" or "an", or "the" if the object is singular
/**
* A preference for how an asset's name appears when it is printed
* in certain contexts. Chiefly used for room names.
* For example, you might name a room 'Sitting Room', but want it
* to appear as 'the Sitting Room' in some contexts for narrative clarity.
* Can be overridden in game file.
* @var {Boolean} adventurejs.Asset#use_definite_article_in_lists
* @default false
*/
this.use_definite_article_in_lists = false;
/**
* An option to prevent definite/indefinite articles from
* appearing before an asset name. Used chiefly for characters,
* to prevent phrases such as "the Mrs. Beasley".
* Can be overridden in game file.
* @var {Boolean} adventurejs.Asset#dont_use_articles
* @default false
*/
this.dont_use_articles = false;
/**
* <strong>noun</strong> is a string representing the type of asset
* this is, for example 'mug' or 'rock'. Players can input this word
* to refer to any asset of its type.
* @var {String} adventurejs.Asset#noun
*
*/
this.noun = "";
/**
* <strong>pronoun</strong> is a string representing the generic
* pronoun.
* @var {String} adventurejs.Asset#pronoun
*
*/
this.pronoun = "it";
/**
* <strong>singlePluralPairs</strong> is a collection of strings
* representing the singular and plural forms of this asset, for
* example ['mug', 'mugs']. Players can use the singular to refer
* to a single asset or the plural to refer to a group of assets.
* These are stored in pairs so we can check for correspondence
* between the forms.
* @var {Array} adventurejs.Asset#singlePluralPairs
*
*/
this.singlePluralPairs = [];
/**
* <strong>plural</strong> is a string representing multiple assets
* of this type, for example 'swords'. Players can input this word
* to refer to groups of assets.
* @var {String} adventurejs.Asset#plural
*
*/
this.plural = "";
/**
* <strong>group</strong> is a string representing a collective
* category. For example, chair, bed, and desk all share a group
* of furniture. Players can input this word
* to refer to any asset of its type.
* @var {String} adventurejs.Asset#noun
*
*/
this.group = [];
/**
* <strong>synonyms</strong> is a collection of alternate words that
* can be used to refer to this asset. For example, an object called
* 'rock' might have synonyms of ['pebble', 'stone'].
* Players can input synonyms to refer to this asset.
* @var {String} adventurejs.Asset#noun
*
*/
this.synonyms = [];
/**
* A list of adjectives defined for this asset in the game file.
* Some asset classes may have predefined adjectives.
* If a class has predefined adjectives, and an author creates
* an instance of the class with custom adjectives, the custom
* adjectives will be appended to the predefined ones.
* @var {Getter/Setter} adventurejs.Asset#adjectives
*/
this.adjectives = [];
/**
* <strong>did_do_verb</strong> is used to track
* which verbs have acted upon this object.
* This is a backup of the verb counter used
* in verb subscriptions.
* @var {Object} adventurejs.Asset#did_do_verb
*/
this.did_do_verb = {};
/**
* <strong>did_do_verb_count</strong> is used to track
* the number of times that given verbs have acted
* upon this object.
* This is a backup of the verb counter used
* in verb subscriptions.
* @var {Object} adventurejs.Asset#did_do_verb_count
*/
this.did_do_verb_count = {};
/**
* <strong>did_try_verb</strong> is used to track
* which verbs have been tried upon this object.
* This is a backup of the verb counter used
* in verb subscriptions.
* @var {Object} adventurejs.Asset#did_try_verb
*/
this.did_try_verb = {};
/**
* <strong>did_try_verb_count</strong> is used to track
* the number of times that given verbs have been tried
* upon this object.
* This is a backup of the verb counter used
* in verb subscriptions.
* @var {Object} adventurejs.Asset#did_try_verb_count
*/
this.did_try_verb_count = {};
/**
* <strong>image</strong> is used to store the id
* of an image in game.image_lookup that is
* associated with this asset.
* @var {String} adventurejs.Asset#image
*/
this.image = "";
/**
* <strong>redirected_verbs</strong> is an object used
* for storing verb redirections via redirectVerb().
* @var {Boolean} adventurejs.Tangible#redirected_verbs
* @default {}
*/
this.redirected_verbs = {};
}
/**
* Getter function returns asset name preceded by its definite article,
* example: 'the asset'.
* @var {Getter} adventurejs.Asset#definite_name
* @returns {String}
*/
get definite_name() {
//return this.definite_article + " " + this.name;
var name = this.definite_article + " " + this.name;
if (this.print_bold) name = "<strong>" + name + "</strong>";
if (this.print_italic) name = "<em>" + name + "</em>";
return name;
}
/**
* Getter function returns asset name preceded by its indefinite article,
* example: 'an asset'.
* @var {Getter} adventurejs.Asset#indefinite_name
* @returns {String}
*/
get indefinite_name() {
//return this.indefinite_article + " " + this.name;
var name = this.indefinite_article + " " + this.name;
if (this.print_bold) name = "<strong>" + name + "</strong>";
if (this.print_italic) name = "<em>" + name + "</em>";
return name;
}
/**
* Getter function returns asset name preceded either by definite
* article or indefinite article, depending on what's set
* for the asset's use_definite_article_in_lists property.
* @var {Getter} adventurejs.Asset#article_name
* @returns {String}
*/
get article_name() {
var article = this.use_definite_article_in_lists
? this.definite_article
: this.indefinite_article;
return article + " " + this.name;
}
/**
* Getter function returns asset name or proper name, depending
* on settings.
* @var {Getter} adventurejs.Asset#articlename
* @returns {String}
*/
get articlename() {
var name;
if (this.propername) {
name = this.propername;
} else if (this.name_is_proper) {
name = this.name;
} else {
name = this.definite_article + " " + this.name;
}
if (this.print_bold) name = "<strong>" + name + "</strong>";
if (this.print_italic) name = "<em>" + name + "</em>";
return name;
}
/**
* Returns this.articlename with propercase.
* @var {Getter} adventurejs.Asset#Articlename
* @returns {String}
*/
get Articlename() {
return A.propercase(this.articlename);
}
get adjectives() {
return this.__adjectives;
}
set adjectives(arr) {
if (false === Array.isArray(this.__adjectives)) {
this.__adjectives = [];
}
this.__adjectives = arr;
}
/**
* An alias to descriptions.look.
* @var {*} adventurejs.Asset#description
*/
get description() {
if (
Array.isArray(this.descriptions.look) ||
"string" === typeof this.descriptions.look ||
"function" === typeof this.descriptions.look
) {
return this.descriptions.look;
}
if (
"object" === typeof this.descriptions.look &&
this.descriptions.look.default
) {
return this.descriptions.look.default;
}
return `${this.Articlename} is undescribed. `;
}
set description(look) {
this.descriptions.look = look;
}
get collection() {
return this.__collection;
}
set collection(arr) {
if (false === Array.isArray(this.__collection)) {
this.__collection = [];
}
this.__collection = A.validateAssetList(arr);
}
/**
* <strong>validate()</strong> provides an opportunity to
* check author-made Assets for errors before initializing them.
* @method adventurejs.Asset#validate
* @memberOf adventurejs.Asset
*/
validate(game) {
this.game.log("log", 3, ["Validating " + this.id], "Asset");
// has it got an id?
if ("undefined" === typeof this.name) {
var msg = "This " + this.constructor.name + " has no id: ";
//console.error(msg, this);
this.game.log("warn", 0, msg, "Asset");
return false;
}
this.setVerbSubscriptionsWithAssets();
this.setVerbSubscriptionsWithConnection();
return true;
}
/**
* <strong>initialize()</strong> is the final step of
* creating a new game world asset, following
* construction and validation. The chief job of
* initialization is to add all of
* this asset's words to
* {@link adventurejs.Game#world_lookup | MyGame.world_lookup}.
* <br><br>
* Because we're not doing natural language processing
* and everything is indexed, we want to increase the chances
* that the parser understands player input that is partial or
* has words jumbled. We add a lookup entry for every
* combination of an object's adjectives with its noun(s).
* For example we have a "green colored pencil", and we want
* the parser to understand "green pencil" or "colored pencil".
* If there are other colored pencils, we want the parser to
* understand "colored pencils". If there are colored pencils
* in three shades of green, we want the parser to understand
* "green pencils". We store all of these combinations to the lookup.
* @method adventurejs.Asset#initialize
* @memberOf adventurejs.Asset
*/
initialize(game) {
if (this.is.initialized) return this;
this.game.log("log", 3, "Initializing " + this.id);
var id;
var name;
var names = [];
var pairs = [];
var synonyms = [];
var adjectives = [];
var group = [];
var serialized_group = [];
for (var a = 0; a < this.adjectives.length; a++) {
var adjective = this.adjectives[a].trim();
for (var i = 0; i < this.singlePluralPairs.length; i++) {
if ("" === adjective || " " === adjective) {
continue;
}
pairs.push([
// example: brass lantern
A.deserialize(adjective + " " + this.singlePluralPairs[i][0]),
// example: brass lanterns
A.deserialize(adjective + " " + this.singlePluralPairs[i][1]),
]);
// TODO SERIALIZED LOOKUP?
// does world_lookup really need serialized entries?
// pairs.push(
// [
// // example: brass_lantern
// A.serialize( adjective + "_" + this.singlePluralPairs[i][0] ),
// // example: brass_lanterns
// A.serialize( adjective + "_" + this.singlePluralPairs[i][1] )
// ]
// );
}
}
this.singlePluralPairs = this.singlePluralPairs.concat(pairs);
// Most objects will be listed in world_lookup.
// zones are excluded
if (this.exclude_from_lookup) {
} else {
// There shouldn't be any spaces by this point, but just in case.
id = this.id.toLowerCase().split(" ");
name = [this.name].slice(0);
/**
* By default we add the name, each word of the name
* and each pair of words in the name.
* Possibly excessive, but helps catch
* typos in long names for use with "oops".
* Can be disabled per asset by setting
* asset.split_name_for_world_lookup = false.
*/
if (this.split_name_for_world_lookup) {
// add each word of the name
names = this.name.toLowerCase().split(" ");
if (2 <= names.length) {
for (let i = names.length - 1; i > 0; i--) {
// add each pair of words in the name
// ie for "tiny silver key" we add "tiny silver" and "silver key"
let wordPair = `${names[i - 1]} ${names[i]}`;
if (-1 === names.indexOf(wordPair)) {
names.push(wordPair);
}
}
}
}
/**
* We're going to pass these properties through addWordsToLookup which
* is destructive, so we use copies in order to retain the originals.
*/
synonyms = this.synonyms.slice(0);
adjectives = this.adjectives.slice(0);
group = this.group.slice(0);
// added this because non-serialized group words
// were being omitted from lookup
serialized_group = this.group.slice(0);
serialized_group = A.serializeArray(serialized_group);
this.addWordsToLookup(["all"], "plural");
this.addWordsToLookup(id, "id");
this.addWordsToLookup(name, "name");
this.addWordsToLookup(names, "name");
this.addWordsToLookup(synonyms, "synonym");
this.addWordsToLookup(adjectives, "adjective");
this.addWordsToLookup(group, "group");
this.addWordsToLookup(serialized_group, "group");
//this.addWordsToLookup( serialized_adjectives, "group" );
if (this.singlePluralPairs.length > 0) {
for (var i = 0; i < this.singlePluralPairs.length; i++) {
var singular = this.singlePluralPairs[i][0];
var plural = this.singlePluralPairs[i][1];
// is this unneccessarily indirect?
this.addWordsToLookup([singular], "singular");
this.addWordsToLookup([plural], "plural");
/*
* Add lookup entry for "all [plural]"
* which is really the same as just "[plural]"
* Example:
* take keys
* take all keys
* Are the same.
* And don't add "all all".
*/
if (false === this instanceof adventurejs.All) {
this.addWordsToLookup([A.deserialize("all " + plural)], "plural");
// TODO SERIALIZED LOOKUP?
//this.addWordsToLookup( [ A.serialize( "all_" + plural ) ], "plural" );
}
// i'm not sure the singular needs a reference to the plural
this.game.world_lookup[singular].plural = plural;
// the plural word def needs a reference to the singular
this.game.world_lookup[plural].singular = singular;
if (false === this instanceof adventurejs.All) {
// all plural
this.game.world_lookup[A.deserialize("all " + plural)].singular =
singular;
// TODO SERIALIZED LOOKUP?
//this.game.world_lookup[ A.serialize( "all_" + plural ) ].singular = singular;
}
}
}
}
return this;
}
}
adventurejs.Asset = Asset;
var p = Asset.prototype;
/**
* <strong>aliases()</strong> is a collection of method
* names that are meant for authors to use. Since they
* don't exist on all classes, we set up these aliases
* so that, if authors call them on classes they're not
* applicable to, they will politely return null instead
* of throwing a "not a function" error.
* @method adventurejs.Asset#aliases
* @memberOf adventurejs.Asset
*/
p.aliases =
p.$isIn =
p.$is =
p.$has =
p.$put =
p.$moveTo =
p.$moveFrom =
p.$get =
p.$room =
p.$exits =
p.$directions =
p.$player =
p.$inventory =
p.$nest =
p.$nested =
p.$getKeys =
p.$getKey =
p.$hasKey =
p.$parent =
p.$ =
function Asset_aliases() {
return null;
};
})();