// constructAsset.js
(function () {
/*global adventurejs A*/
"use strict";
var p = adventurejs.Game.prototype;
/**
* Takes a generic object and returns a classed object.
* @method adventurejs.Game#constructAsset
* @memberOf adventurejs.Game
* @param {Object} source A generic object containing properties to copy to a game object.
* @returns {Object} Returns an instance of whatever class was defined.
*/
p.constructAsset = function Game_constructAsset(source) {
var id;
var dest;
/**
* Most classes convert name to ID,
* with the exception of Exits
* which use location + direction.
*/
if (source.class === "Exit") {
id = A.serialize(
source.place[Object.keys(source.place)[0]] + " " + source.direction
);
} else if (source.name) {
id = A.serialize(source.name);
}
if (!this.class_lookup[source.class]) this.class_lookup[source.class] = [];
this.class_lookup[source.class].push(id);
/**
* if author has set source.place
* ensure that the name of its parent object is serialized
*/
if (
"object" === typeof source.place &&
Object.keys(source.place).length > 0
) {
source.place[Object.keys(source.place)[0]] = A.serialize(
source.place[Object.keys(source.place)[0]]
);
}
// create new class object with serialized name
dest = new adventurejs[source.class](id, this.game_name);
/**
* Give it a reference back to game.
* We can't give game objects a direct reference because
* circular references cause problems when writing saves out to JSON.
* Instead we save the instance name that is scoped to window,
* and get it with object.game property.
*
*/
dest.game_name = this.game_name;
/**
* We allow author to define noun & plural as strings, but we
* actually store those as a nested array in singlePluralPairs,
* eg [ ["key","keys"] ];
*
* We don't expect authors to define singlePluralPairs themselves,
* but we do allow for it, for cases of advanced users.
*/
if ("undefined" === typeof source.singlePluralPairs) {
source.singlePluralPairs = [];
}
// strip spaces
if ("string" === typeof source.noun) source.noun = source.noun.trim();
if ("string" === typeof source.plural) source.plural = source.plural.trim();
// TODO singlePluralPairs setup was being duplicated here and in Asset.js
// are we losing anything if we shut down this one?
// make new pair
// if( "string" === typeof source.noun
// && "string" === typeof source.plural
// && "" !== source.noun
// && "" !== source.plural
// ) {
// source.singlePluralPairs.push( [ source.noun, source.plural ] );
// }
// the base class may already have noun/plural pairs so append those
//source.singlePluralPairs = source.singlePluralPairs.concat( dest.singlePluralPairs );
/**
* HANDLE ASPECTS
* Look for uninitialized Aspects in game file.
* Tangible Assets have Aspects, aka aspects,
* which are containers for in/on/under etc.
* Some predefined Tangible subclasses, like Table or Bathtub, may have
* preset Aspects for things like 'on' or 'in'. But, in case author has
* defined properties for an Aspect that hasn't been defined for this class
* of asset, we'll be nice and instantiate an Aspect for them.
* Also, if we encounter an aspect that is not in our dictionary's list of
* prepositions, add the preposition to the dictionary.
*/
if (source.aspects && dest.aspects) {
// did we receive an array?
if (Array.isArray(source.aspects)) {
// authors are invited to use arrays as a shorthand to create aspects
// aspects: [ 'on', 'under', 'behind' ],
for (var i = 0; i < source.aspects.length; i++) {
var aspect = source.aspects[i];
if (dest.aspects[aspect]) {
dest.aspects[aspect].enabled = true;
} else {
dest.aspects[aspect] = new adventurejs.Aspect(
aspect,
this.game_name
).set({ parent_id: id, enabled: true });
}
}
delete source.aspects;
} else {
for (var aspect in source.aspects) {
// Has dest got an Aspect instance here?
if (!dest.aspects[aspect]?.class) {
dest.aspects[aspect] = new adventurejs.Aspect(
aspect,
this.game_name
).set({ parent_id: id, enabled: true });
}
if (source.aspects[aspect].vessel) {
if ("boolean" === typeof source.aspects[aspect].vessel) {
// we accept aspects:{on:{vessel:true}} as a shortcut
if (dest.aspects[aspect].vessel?.class) {
// if existing vessel, set enabled
dest.aspects[aspect].enabled = source.aspects[aspect].vessel;
} else if (source.aspects[aspect].vessel) {
// only if true, create new vessel
dest.aspects[aspect].vessel = new adventurejs.Vessel(
aspect,
this.game_name
).set({ parent_id: id, enabled: true });
}
// else if false and no vessel, do nothing
// since source vessel was boolean, delete so it doesn't get cloned
delete source.aspects[aspect].vessel;
} // vessel is not boolean
else {
if (!dest.aspects[aspect].vessel?.class) {
dest.aspects[aspect].vessel = new adventurejs.Vessel(
aspect,
this.game_name
).set({ parent_id: id, enabled: true });
}
}
}
// if aspect is an unknown preposition, add it to dictionary
if (-1 === this.game.dictionary.prepositions.indexOf(aspect)) {
this.game.dictionary.prepositions.push(aspect);
}
// we allow authors to set aspects to true/false as a shortcut
// which we write to aspect.enabled - if we got one of those
// we want to set it & delete it so it doesn't get cloned
if (
true === source.aspects[aspect] ||
false === source.aspects[aspect]
) {
dest.aspects[aspect].enabled = source.aspects[aspect];
delete source.aspects[aspect];
}
} // for
}
}
/**
* HANDLE VERB SUBSCRIPTIONS
* Each asset subscribes to verbs to allow them to act upon it.
* VerbSubscriptions are pre-defined for classes, but if author
* has added new VerbSubscriptions or modifiers to pre-defined
* ones, we want to merge them in.
*/
var object = ["dov", "iov"];
for (var of = 0; of < object.length; of++) {
if (source && source[object[of]]) {
// is source[ object[of] ] an array?
if (Array.isArray(source[object[of]])) {
dest.setObjectOfVerbs(object[of], source[object[of]]);
} else {
for (var verb in source[object[of]]) {
dest.setVerbSubscription(object[of], {
[verb]: source[object[of]][verb],
});
}
}
// we've copied everything we need from source and
// we don't want to recopy in the clone block, so...
delete source[object[of]];
}
}
// ALL OTHER PROPERTIES
var keys = Object.keys(source);
for (var i = 0; i < keys.length; i++) {
var prop = keys[i];
// ignore native prototype properties
if (!Object.prototype.hasOwnProperty.call(source, prop)) continue;
// if("undefined" !== typeof source[ prop ].class
// && "undefined" !== typeof dest[ prop ]
// && "undefined" === typeof dest[ prop ].class)
// {
// dest[ prop ] = new adventurejs[ source[ prop ].class ]( source[ prop ].id, this.game_name );
// }
// convert strings to arrays as needed
if ("string" === typeof source[prop] && Array.isArray(dest[prop])) {
source[prop] = source[prop].toLowerCase().split(",");
}
if (Array.isArray(source[prop])) {
// trim spaces
for (var p = 0; p < source[prop].length; p++) {
if ("string" === typeof source[prop][p]) {
source[prop][p] = source[prop][p].trim();
}
}
/*
* Some classes may have predefined words for some properties,
* and we don't want to overwrite those.
* If author has added more words we want
* to concat rather than replace.
*
* Example: Say we have class SteelSword with adjectives=["steel"]
* and author creates an instance and adds adjectives=["sharp"]
* We need to ensure that both "steel sword" and "sharp sword" will work.
*/
source[prop] = source[prop].concat(dest[prop]);
}
}
/**
* Clone remaining properties from the generic source
* to the dest using the Set() method of Atom,
* which is the superclass for all game objects.
*/
dest.set(source);
// if( source.class === "Exit" ) {
// var directionSynonyms = this.game.dictionary.directionLookup[ source.direction ];
// for( var i = 0; i < directionSynonyms.length; i++ )
// {
// dest.adjectives.push( directionSynonyms[i] );
// }
// }
return dest;
};
})();