// Tangible.js
(function () {
/*global adventurejs A*/
"use strict";
/**
* @ajspath adventurejs.Atom.Asset.Matter.Tangible
* @augments adventurejs.Matter
* @class adventurejs.Tangible
* @ajsconstruct MyGame.createAsset({ "class":"Tangible", "name":"foo", [...] })
* @ajsconstructedby adventurejs.Game#createAsset
* @ajsnavheading BaseClasses
* @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.
* @summary Base class for all game objects with physical properties.
* @tutorial Tangibles_AboutTangibles
* @classdesc
* <p>
* <strong>Tangible</strong> is the base class for all
* {@link adventurejs.Asset|Assets} with physical properties
* in the game world, with the exception of Substances.
* All of the properties of Tangible are inherited by all of
* its subclasses, and most subclasses don't define new properties.
* Most subclasses are essentially convenience methods that set
* a group of properties particular to a type of object.
* In theory, an instance of almost any subclass could be made to
* behave like an instance of almost any other subclass,
* simply by setting the right properties. This provides the
* flexibility to mix and match properties to construct game
* assets with customized behaviors.
* </p>
* <p>
* For example, {@link adventurejs.Chair|Chair} defines
* something you can sit on, and
* {@link adventurejs.Bed|Bed} defines something
* you can lie on, but perhaps you want to make something
* like a divan, that you can sit on and lie on. You could
* create a Chair and set asset.aspects.on.player.can.lie
* property to true.
* </p>
* <pre class="display"><code class="language-javascript">MyGame.createAsset({
* class: "Chair",
* name: "divan",
* descriptions: {look:"A comfortable looking fainting couch. ",),
* aspects:
* {
* on: { player: { can: { lie: true } } }
* }
* });
* </code></pre>
* This is a simple example of customizing an object, but hopefully
* it gives you an idea of how flexible the Tangible class is.
* Alternatively if you wanted to extensively customize your divan,
* you could define your own Divan class to extend
* {@link adventurejs.Furniture|Furniture}.
**/
class Tangible extends adventurejs.Matter {
constructor(name, game_name) {
super(name, game_name);
this.class = "Tangible";
this.is = new adventurejs.Tangible_Is("is", this.game_name, this.id).set({
parent_id: this.id,
});
this.can = new adventurejs.Tangible_Can(
"can",
this.game_name,
this.id
).set({
parent_id: this.id,
});
this.must = new adventurejs.Tangible_Must(
"must",
this.game_name,
this.id
).set({
parent_id: this.id,
});
/**
* Containing object for Aspects, aka "in", "out", "under", "behind"
* etc.
* @var {Object} adventurejs.Tangible#aspects
* @default {}
*/
this.aspects = {};
// set direct verb subscriptions
this.setDOVs([
"attach",
"detach",
"go",
"hit",
"kick",
"move",
"poke",
"pull",
"push",
"shake",
//"throw",
]);
// set indirect verb subscriptions
this.setIOVs(["hit", "throw", "flick"]);
//this.player_has_used = false;
/**
* It's possible for player to become nested to an asset without a
* specified preposition. In such cases, this default aspect is used.
* @var {String} adventurejs.Tangible#default_aspect
* @default "on"
*/
this.default_aspect = "on";
/**
* When player is nested, some verbs can only be applied to assets that
* have been specified as being within reach.
* @var {Array} adventurejs.Tangible#things_player_can_do_all_verbs_to_from_this
* @default []
* @todo Revisit this. Use things_within_reach for all reach settings?
*/
this.things_player_can_do_all_verbs_to_from_this = [];
/**
* When player climbs asset, set their preposition to this default.
* @var {String} adventurejs.Tangible#default_aspect_for_climb
* @default "on"
* @todo Move string to const
*/
this.default_aspect_for_climb = "on";
/**
* Set whether 'climb' means 'go on', which might apply to assets
* such as stairs and ladders.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!climb_means_go_on
* @default false
*/
this.quirks.climb_means_go_on = false;
/**
* Set whether 'climb' means 'stand on', which might apply to
* furniture and things like boulders which players can stand on.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!climb_means_stand_on
* @default false
*/
this.quirks.climb_means_stand_on = false;
/**
* Set list of other assets that player can climb to from this.
* @var {Array} adventurejs.Tangible#things_player_can_climb_to_from_this
* @default []
*/
this.things_player_can_climb_to_from_this = [];
/**
* Set whether 'crawl' means 'go on', which might apply to
* furniture and things like boulders which players can stand on.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!crawl_means_go
* @default false
*/
this.quirks.crawl_means_go = true;
/**
* Set whether player can go off (aka get off) this asset. Used for Rooms.
* @var {Boolean} adventurejs.Tangible#player_can_exit
* @default false
*/
this.player_can_exit = false;
/**
* Set whether 'stand' means 'go off' (aka 'get off'), which might apply
* to furniture such as chairs.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!stand_means_get_off
* @default false
*/
this.quirks.stand_means_get_off = false;
/**
* Set whether 'get up' means 'go off' (aka 'get off'), which might apply
* to furniture such as chairs or beds.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!get_up_means_get_off
* @default false
*/
this.quirks.get_up_means_get_off = false;
/**
* Set whether 'get on' means 'climb', which might apply
* to things such as trees.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!go_on_means_climb
* @default false
*/
this.quirks.go_on_means_climb = false;
/**
* If set to true, "in" and "on" become interchangeable for some verbs.
* Intended chiefly for things like chairs where "sit in chair"
* and "sit on chair" can be interpreted to mean the same thing.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!in_means_on
* @default false
*/
this.quirks.in_means_on = false;
/**
* If set to true, "put" means "pour" for some verbs.
* Intended chiefly for things like "put syrup on pancakes" which is
* a common phrasing where clearly the speaker means to pour.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!put_means_pour
* @default false
*/
this.quirks.put_means_pour = false;
/**
* Set whether 'jump' means 'jump on' as in jumping up and down on a bed.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!jump_means_jump_on
* @default true
*/
this.quirks.jump_means_jump_on = true;
/**
* Set whether 'jump' means 'jump off' as in jumping off a tree.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!jump_means_jump_off
* @default false
*/
this.quirks.jump_means_jump_off = false;
/**
* List of other assets player can jump to while nested within this asset.
* @var {Array} adventurejs.Tangible#things_player_can_jump_to_from_this
* @default []
*/
this.things_player_can_jump_to_from_this = [];
/**
* Set whether player can step on this asset.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!step_on_means_stamp_on
* @default false
*/
this.quirks.step_on_means_stamp_on = false;
/**
* Set whether ver 'step on' means 'stand on', as with a skateboard or some
* types of furniture.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!step_on_means_stand_on
* @default false
*/
this.quirks.step_on_means_stand_on = false;
/**
* If player becomes nested within this asset via 'swing to', set the player's
* posture to this default.
* @var {Boolean} adventurejs.Tangible#default_posture_for_swing_to
* @default false
*/
this.default_posture_for_swing_to = "stand";
/**
* If player becomes nested within this asset via 'swing to', set the player's
* preposition to this default.
* @var {Boolean} adventurejs.Tangible#default_aspect_for_swing_to
* @default false
*/
this.default_aspect_for_swing_to = "on";
/**
* List of other assets that player can swing to while nested within this asset.
* @var {Array} adventurejs.Tangible#things_player_can_swing_to_from_this
* @default []
*/
this.things_player_can_swing_to_from_this = [];
/**
* Set whether player can hang on this asset.
* @var {Boolean} adventurejs.Tangible#player_can_hang_on_this
* @default false
*/
this.player_can_hang_on_this = false;
/**
* A string that describes the position the
* {@link adventurejs.Asset|Asset}
* is in, ex "resting" for most non-Character Assets,
* or "lying", "sitting", "standing" etc for
* {@link adventurejs.Characters|Characters}.
* <code class="property">this.game.dictionary.getStringLookup( type, value )</code>.
* Can be referenced in custom code through
* <code class="property">MyGame.dictionary.getStringLookup( type, value )</code>.
* @var {Boolean} adventurejs.Tangible#posture_position
* @default "default"
*/
this.posture_position = this.game.dictionary.getStringLookup(
"posture_positions",
"default"
);
/**
* Set whether player knows a thing is hidden. Useful if player is the one doing
* the hiding, or if player has found an object but not picked it up.
* @var {Boolean} adventurejs.Tangible#player_knows_its_hidden
* @default false
*/
this.player_knows_its_hidden = false;
/**
* Set an asset's percent of buoyancy. No logic has been implemented around this.
* @var {float} adventurejs.Tangible#buoyancy
* @default 0
* @todo Implement.
*/
this.buoyancy = 0; // range 0 to 1
/**
* Set the amount of liquid an asset has absorbed.
* No particular logic has been implemented around absorption.
* @var {Boolean} adventurejs.Tangible#absorption_quantity
* @default 0
*/
this.absorption_quantity = 0;
/**
* Set whether a closed thing can be auto-opened. This is used chiefly for 'go to',
* which tries to auto-open doors, something which authors might want to prevent
* depending on other logic.
* @var {Boolean} adventurejs.Tangible#can!auto_open
* @default false
*/
this.can.auto_open = false;
/**
* Set whether a locked thing can be auto-unlocked. This is used chiefly for 'go to',
* which tries to auto-unlock doors, something which authors might want to prevent
* depending on other logic.
* @var {Boolean} adventurejs.Tangible#can!auto_unlock
* @default false
*/
this.can.auto_unlock = false;
/**
* Set whether a sealed thing can be auto-unsealed. This is used chiefly for 'go to',
* which tries to auto-unseal apertures, something which authors might want to prevent
* depending on other logic.
* @var {Boolean} adventurejs.Tangible#can!auto_unseal
* @default false
*/
this.can.auto_unseal = false;
/**
* Set whether 'pick' means 'unlock'. This is provided to give authors the option
* to choose whether pick and unlock are treated as distinct verbs.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!pick_means_unlock
* @default false
*/
this.quirks.pick_means_unlock = false;
/**
* Set a message to print for asset's single use.
* @var {Boolean} adventurejs.Tangible#use_once_message
* @default false
*/
this.use_once_message = "";
/* *
* Tangible Assets may have numerous descriptions, though all but
* default are optional.
* @var {Object} adventurejs.Tangible#descriptions
* @default { look: "", careful: "", long: "", short: "", brief: "", verbose: "", closed: "", open: "", taste: "", smell:"", listen:"", in:"", through:"", }
*/
this.descriptions = {
look: "",
brief: "",
verbose: "",
careful: "",
closed: "",
open: "",
taste: "",
touch: "",
smell: "",
sound: "",
exits: "",
for_exits_list: "",
behind: "",
in: "",
on: "",
over: "",
through: "",
under: "",
};
/* *
* @var {String} adventurejs.Tangible#description
* @default ""
*/
//this.description = "";//"$(We) see nothing worth mentioning.";
/**
* Set whether this asset should be destroyed when drunk.
* @var {Boolean} adventurejs.Tangible#on_drink_destroy
* @default false
*/
this.on_drink_destroy = false;
/**
* Set whether this asset should be emptied when drunk,
* versus only subtracting a mouthful as set in Settings.
* @var {Boolean} adventurejs.Tangible#on_drink_empty
* @default false
*/
this.on_drink_empty = false;
/**
* Set whether this asset should be destroyed when eaten.
* @var {Boolean} adventurejs.Tangible#on_eat_destroy
* @default false
*/
this.on_eat_destroy = false;
/**
* Set whether "look with" is equivalent to "look through". For example,
* in the case of a telescope, "look with telescope" is equivalent to
* "look through telescope". In the case of a window, you can look through
* it, but not look with it. In the case of a candle, you can look with it,
* but not look through it.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!look_with_means_look_through
* @default false
*/
this.quirks.look_with_means_look_through = false;
/**
* This property was superseded by the GraduatedController class. It may still be useful
* for simpler interactions. Use in conjunction with turn_rotation to save current position.
* @var {int} adventurejs.Tangible#turn_positions
* @default 1
*/
this.turn_positions = 1;
/**
* This property was superseded by the GraduatedController class. It may still be useful
* for simpler interactions. Use in conjunction with turn_positions to set number of positions.
* @var {float} adventurejs.Tangible#turn_rotation
* @default 0
*/
this.turn_rotation = 0;
/**
* @var {String} adventurejs.Tangible#turn_target_id
* @default ""
*/
this.turn_target_id = "";
/**
* Used by GraduatedController class to set target for the controller.
* @var {String} adventurejs.Tangible#control_target_id
* @default ""
*/
this.control_target_id = "";
/**
* Used by GraduatedController class to set number of control positions for a
* controller, where 2 is a toggle and anything more than 2 is a dial.
* @var {int} adventurejs.Tangible#control_positions
* @default 1
*/
this.control_positions = 1;
/**
* Used by GraduatedController class to set current position of controller.
* 0 means off. If 2 positions are available, 1 means on. If more than
* 2 positions, each position above 0 is treated as a percent of total capacity.
* For example in a 3 position controller, 1 = 50% and 2 = 100%;
* @var {int} adventurejs.Tangible#current_position
* @default 0
*/
this.current_position = 0;
/**
* Set whether this asset can be swung at another asset, as in swinging a bat at a ball.
* @var {Boolean} adventurejs.Tangible#can!be_swung_at
* @default true
*/
this.can.be_swung_at = true;
// readin'n'writin'
/**
* Set whether this asset can be written in
* like a book.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!write_on_means_write_in
* @default false
*/
this.quirks.write_on_means_write_in = false;
/**
* List of strings that have been written on this asset.
* @var {Array} adventurejs.Tangible#written_strings
* @default []
*/
this.written_strings = [];
/**
* Set whether strings written on this asset are appended to its description.
* @var {Boolean} adventurejs.Tangible#append_written_strings_to_description
* @default false
*/
this.append_written_strings_to_description = false;
/**
* Set the ID of a target asset for this asset to type on, as in a screen for a keyboard.
* @var {String} adventurejs.Tangible#typing_target_id
* @default ""
*/
this.typing_target_id = "";
// ROPES
/**
* Set whether this is taken into player inventory when another asset is tied
* to it. For example, on tying a string that is in inventory to a tin can that
* is not in inventory, it's assumed that player would then pick up the can.
* @var {Boolean} adventurejs.Tangible#on_tie_to_this_take_this
* @default false
*/
this.on_tie_to_this_take_this = false;
/**
* If player uses a rope in inventory to tie to an object that is not in inventory,
* this sets whether the object gets dragged behind when player leaves the current room.
* @var {Boolean} adventurejs.Tangible#on_tie_to_drag_behind_rope
* @default false
*/
this.on_tie_to_drag_behind_rope = false;
/**
* Set whether this asset's description includes things that it is tied to.
* @var {Boolean} adventurejs.Tangible#show_things_this_is_tied_to_in_description
* @default true
*/
this.show_things_this_is_tied_to_in_description = true;
/**
* Set whether 'take' means 'hold', as in the case of hanging ropes.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!take_means_hold
* @default false
*/
this.quirks.take_means_hold = false; // used for swingable things
/**
* Set whether 'let go of' means 'get off', as in the case of assets that
* the player is suspended from, such as hanging from a rope.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!let_go_of_means_go_off
* @default false
*/
this.quirks.let_go_of_means_go_off = false;
/**
* Set whether 'let go of' means 'go down', as in the case of player being
* suspended by a rope above a pit, where letting go means moving to a new Room.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!let_go_of_means_go_down
* @default false
*/
this.quirks.let_go_of_means_go_down = false;
/**
* Set properties for a variety of things a tangible asset can emit.
* @var {Boolean} adventurejs.Tangible#emits
* @default false
* @todo Write logic for this.
*/
this.emits = {
gravity: { enabled: false, level: 0, description: "", results: null },
light: { enabled: false, level: 0, description: "", results: null },
heat: { enabled: false, level: 0, description: "", results: null },
sound: { enabled: false, level: 0, description: "", results: null },
smell: { enabled: false, level: 0, description: "", results: null },
// liquid: { enabled: false, level: 0, description: '', results: null, id:'' },
// gas: { enabled: false, level: 0, description: '', results: null, id:'' },
// slurry: { enabled: false, level: 0, description: '', results: null, id:'' },
// solid: { enabled: false, level: 0, description: '', results: null, id:'' },
substance: {
enabled: false,
level: 0,
description: "",
results: null,
id: "",
},
};
/**
* Set whether this asset must have a location. Chiefly for use with Aperture class.
* Apertures must have a location because they're tied to Exits. Other assets can
* exist without a location, though unlocated tangible assets won't be available to player.
* @var {Boolean} adventurejs.Tangible#location_required
* @default false
*/
this.location_required = false; // some classes must have a location
/**
* Explicitly set whether this asset can exist without a location. Chiefly for use
* with Room class. Rooms will not have a location.
* @var {Boolean} adventurejs.Tangible#location_unneccessary
* @default false
*/
this.location_unneccessary = false; // some classes don't need a location
/**
* Set the minimum light required to see this asset. Meant for situations with
* variable lighting where some objects might be hidden in shadow.
* @var {float} adventurejs.Tangible#min_light_required_to_see
* @default 0.5
* @todo Write logic for this in selectVisible.js
*/
this.min_light_required_to_see = 0.5; // TODO amount of ambient light required to be visible
/**
* @var {Object} adventurejs.Tangible#dimensions
* @default {}
*/
this.dimensions = {
depth: -1,
/**
* Set a height for this asset. Used to calculate whether some assets can fit in other assets.
* Also used for climbing and reachability.
* Currently, 1 is considered to be human height, or about six feet.
* @var {float} adventurejs.Tangible#dimensions.height
* @default 1
*/
height: 0,
/**
* Set the opacity for this asset. Used for assets made of transparent materials
* such as glass, and helps determine whether player can look in or through assets.
* @var {float} adventurejs.Tangible#opacity
* @default 1
*/
opacity: 1,
/**
* Set a size for this asset. Meant for figuring whether some assets can fit in other assets.
* Logic for this is incomplete and may be duplicative of width / height / depth.
* @var {float} adventurejs.Tangible#dimensions.size
* @default -1
* @todo Revisit this. Does it conflict with width / height / depth?
*/
size: -1,
/**
* Set a weight for this asset. Used to calculate whether some assets can fit in other assets.
* May also be used for buoyancy though no logic has been written for that.
* @var {float} adventurejs.Tangible#dimensions.weight
* @default -1
* @todo Figure weight into buoyancy?
*/
weight: -1,
/**
* Set a width for this asset. Used to calculate whether some assets can fit in other assets.
* @var {float} adventurejs.Tangible#dimensions.width
* @default 1
*/
width: -1,
};
/**
* XYZ coordinates. Defaults to 0,0,0.
* It's safe to ignore these if you don't want to use them.
* They can be used for things like:
* <ul>
* <li>managing reachability of objects that are on top of other things</li>
* <li>managing reachability of objects in the room while player is climbing or standing atop a thing</li>
* <li>dividing a room up into reachable/unreachable spaces</li>
* <li>managing player depth in an underwater location</li>
* <li>managing player position while flying/floating/levitating</li>
* </ul>
* Here's an example of how to set an object's position.
* <pre class="display"><code class="language-javascript">MyGame.createAsset({
* class: "Stalactite",
* name: "stalactite",
* place: { on: "Colossal Cave" },
* descriptions: {look: "It clings tight to the ceiling. ",},
* height: -2,
* position: { x:0, y:5, z:0 },
* });
* </code></pre>
* Also see related <a href="#height">height</a>.
* @var {Object} adventurejs.Tangible#position
* @default {x:0,y:0,z:0}
*
* @related height
*/
this.position = {
x: 0,
y: 0,
z: 0,
};
/**
* Meant for handling screen output, by breaking listed items into subgroups
* to be divided into paragraphs, but not implemented.
* @var {int} adventurejs.Tangible#list_group
* @default 0
*
* @todo Implement this or remove it.
*/
this.list_group = 0; // @TODO list visible items in separate paragraphs
/**
* Set whether verb point means aim for this asset.
* If so, point verb will redirect to aim.
* @nestedproprty
* @var {Boolean} adventurejs.Tangible#quirks!point_means_aim
* @default false
*
*/
this.quirks.point_means_aim = false;
/**
* When player is nested within an aspect of an asset, other assets may not be reachable.
* <strong>things_player_can_reach_from_this</strong> helps manage reachability.
* For example, if player is on a ladder, things on the ground might not be reachable.
* But, a player seated on a chair at a desk should be able to reach the desk and anything on it.
* Use this property to explicitly make some things reachable from other things.
* <br/><br/>
* <em>things_player_can_reach_from_this is a broad catchall that covers all
* nesting options.</em> What this means is that players can reach assets in this
* list no matter how they are nested within this asset.
* Going back to the example of the chair and desk, if the chair has this property set thusly...
* <pre class="display"><code class="language-javascript">this.things_player_can_reach_from_this = [ 'desk' ];
* </code></pre>
* ...it means that the player, seated in the chair,
* can reach any part of the desk: not just stuff that's on it, but also stuff
* under it or behind it. That might be fine for your purposes.
* Or, you might want more granular control. Let's say there's an electrical outlet under the desk,
* and you want that players shouldn't be able to reach it without actually crawling under the desk.
* If you need that kind of precision, you can manage reachability for each aspect of an asset.
* For more information about that, see
* {@link adventurejs.Aspect#things_player_can_reach_from_this_aspect|Aspect.things_player_can_reach_from_this_aspect}
* and
* {@link adventurejs.Aspect#things_player_can_reach_from_positions_of_this_aspect|Aspect.things_player_can_reach_from_positions_of_this_aspect}.
* @var {Array} adventurejs.Tangible#things_player_can_reach_from_this
* @default []
*
*/
this.things_player_can_reach_from_this = []; // defined
/**
* When player is nested within an asset, other assets may not be reachable.
* For example, if player is on a ladder, things on the ground won't be reachable.
* Use this property to explicitly list things that player can reach while nested
* at the top of asset. For example, perhaps there is a window that player should
* only be able to reach from the top of a ladder.
* <strong>things_player_can_reach_from_top_of_this</strong> allows that to be set.
* For example:
* <pre class="display"><code class="language-javascript">this.things_player_can_reach_from_top_of_this = [ 'window' ];
* </code></pre>
* <em>things_player_can_reach_from_this is a convenience method for a common situation.</em>
* If you need more control over reachability from specific positions within an aspect,
* like left/right/front/back, you can manage that at the aspect level.
* For more information about that, see
* {@link adventurejs.Aspect#things_player_can_reach_from_this_aspect|Aspect.things_player_can_reach_from_this_aspect}
* and
* {@link adventurejs.Aspect#things_player_can_reach_from_positions_of_this_aspect|Aspect.things_player_can_reach_from_positions_of_this_aspect}.
* @var {Array} adventurejs.Tangible#things_player_can_reach_from_top_of_this
* @default []
*
*/
this.things_player_can_reach_from_top_of_this = []; // defined
/**
* When player is nested within an asset, other assets may not be reachable.
* For example, if player is on a ladder, things on the ground won't be reachable.
* Use this property to explicitly list things that player can reach while nested
* at the bottom of asset. For example, perhaps there is an alcove at the bottom
* of a pit, with a ladder leading down, where player should
* only be able to reach the alcove from the bottom of the ladder.
* <strong>things_player_can_reach_from_bottom_of_this</strong> allows that to be set.
* For example:
* <pre class="display"><code class="language-javascript">this.things_player_can_reach_from_bottom_of_this = [ 'alcove' ];
* </code></pre>
* <em>things_player_can_reach_from_bottom_of_this is a convenience method for a common situation.</em>
* If you need more control over reachability from specific positions within an aspect,
* like left/right/front/back, you can manage that at the aspect level.
* For more information about that, see
* {@link adventurejs.Aspect#things_player_can_reach_from_this_aspect|Aspect.things_player_can_reach_from_this_aspect}
* and
* {@link adventurejs.Aspect#things_player_can_reach_from_positions_of_this_aspect|Aspect.things_player_can_reach_from_positions_of_this_aspect}.
* @var {Array} adventurejs.Tangible#things_player_can_reach_from_top_of_this
* @default []
*
*/
this.things_player_can_reach_from_bottom_of_this = []; // defined
/**
* Some Tangible subclasses come pre-coded to handle certain
* "parts", classes which can automatically be registered with
* each other to form complex associations.
* For example, a
* {@link adventurejs.Sink|Sink}
* can have matching
* {@link adventurejs.Faucet|Faucet},
* {@link adventurejs.FaucetHandle|FaucetHandles},
* {@link adventurejs.Drain|Drain} and
* {@link adventurejs.Plug|Plug}.
* Not all classes have parts. See each class's documentation
* header for "Can have parts:" and "Can be part of:".
* The registerParts() method is called during initialization.
* Set up parts like so:
* <pre class="display"><code class="language-javascript">MyGame.createAsset({
* class: "Sink",
* name: "sink",
* place: { in: "Bathroom" },
* descriptions:{
* look: function()
* {
* return "A pedestal sink with porcelain handles and
* a stainless steel faucet. Its drain appears to be
* $( sink drain is| open or| closed ). ";
* }
* },
* parts: [
* // each of these is a name of another Asset
* "hot water handle",
* "cold water handle",
* "faucet",
* "drain",
* "plug"
* ],
* });
* </code></pre>
* @var {Array} adventurejs.Tangible#parts
* @default []
*
*/
this.parts = [];
/**
*
* @var {Object} adventurejs.Tangible#registered_parts
* @default {}
*
*/
this.registered_parts = {};
/**
*
* @var {Object} adventurejs.Tangible#registerableClasses
* @default {}
*
*/
this.registerableClasses = {};
this.place = {};
} // function Tangible(id)
// PROTOTYPE DEFINED PROPERTIES
/*doc@this*/
get parts() {
return this.__parts;
}
set parts(arr) {
this.__parts = A.validateAssetList(arr);
}
/*doc@this*/
get things_player_can_do_all_verbs_to_from_this() {
return this.__things_player_can_do_all_verbs_to_from_this;
}
set things_player_can_do_all_verbs_to_from_this(arr) {
this.__things_player_can_do_all_verbs_to_from_this =
A.validateAssetList(arr);
}
/*doc@this*/
get things_player_can_climb_to_from_this() {
return this.__things_player_can_climb_to_from_this;
}
set things_player_can_climb_to_from_this(arr) {
this.__things_player_can_climb_to_from_this = A.validateAssetList(arr);
}
/*doc@this*/
get things_player_can_jump_to_from_this() {
return this.__things_player_can_jump_to_from_this;
}
set things_player_can_jump_to_from_this(arr) {
this.__things_player_can_jump_to_from_this = A.validateAssetList(arr);
}
/**
* @var {Getter/Setter} adventurejs.Tangible#things_player_can_swing_to_across_this
* @default false
*/
get things_player_can_swing_to_across_this() {
return this.__things_player_can_swing_to_across_this;
}
set things_player_can_swing_to_across_this(arr) {
this.__things_player_can_swing_to_across_this = A.validateAssetList(arr);
}
/*doc@this*/
get things_player_can_swing_to_from_this() {
return this.__things_player_can_swing_to_from_this;
}
set things_player_can_swing_to_from_this(arr) {
this.__things_player_can_swing_to_from_this = A.validateAssetList(arr);
}
/**
* Get a string representing open / closed state of this asset.
* @var {Boolean} adventurejs.Tangible#print_open_or_closed
* @default false
*/
get print_open_or_closed() {
var state = "neither open nor closed";
if (this.is.closed) state = "closed";
if (false === this.is.closed) state = "open";
return state;
}
/**
* Get / set place. The private var __place is an
* object with two properties: aspect and asset.
* For example: { aspect:"in", asset:"room" }
* However, the public var place appears in the form
* { in: "room" }. This is to make it easier and more
* intuitive for authors to set asset places.
* @var {Object} adventurejs.Tangible#place
*/
get place() {
return { [this.__place.aspect]: this.__place.asset };
}
set place(value) {
var newplace = { asset: "", aspect: "" };
if (Object(value) !== value) {
var msg = this.id + ".place received no value. Setting no place. ";
this.game.log("warn", "critical", msg, "Tangible");
} else {
// for some reason world.copy was returning {"":"","key":"value"}
// so we need to delete the empty string key
delete value[""];
var keys = Object.keys(value);
if (value.asset && value.aspect) {
// received for example: { aspect:"in", asset:"room" }
// serialize asset name
newplace.aspect = value.aspect;
newplace.asset = A.serialize(value.asset);
} else if (keys.length === 0) {
} else if (keys.length === 1) {
// received for example: { in: "room" }
// verify asset
if ("string" !== typeof value[keys[0]]) {
var msg = this.id + ".place set to an invalid asset ";
this.game.log("error", "critical", msg, "Tangible");
}
// serialize asset name
newplace.asset = A.serialize(value[keys[0]]);
newplace.aspect = keys[0];
} else if (keys.length > 1) {
newplace.asset = value[keys[0]];
newplace.aspect = keys[0];
var msg =
this.id +
".place received more than one location. Using the first. ";
for (var i = 0; i < keys.length; i++) {
msg += keys[i] + ": " + value[keys[i]] + ", ";
}
this.game.log("error", "critical", msg, "Tangible");
}
}
if (
this.__place &&
this.__place.asset &&
this.__place.aspect &&
(this.__place.asset !== newplace.asset ||
this.__place.aspect !== newplace.aspect)
) {
this.game
.getAsset(this.__place.asset)
.removeAssetAt(this.id, this.__place.aspect);
}
if (
newplace.asset &&
newplace.aspect &&
this.game.getAsset(newplace.asset)
) {
this.game.getAsset(newplace.asset).addAssetAt(this.id, newplace.aspect);
}
this.__place = newplace;
}
/*doc@this*/
get things_player_can_reach_from_this() {
return this.__things_player_can_reach_from_this;
}
set things_player_can_reach_from_this(arr) {
this.__things_player_can_reach_from_this = A.validateAssetList(arr);
}
/*doc@this*/
get things_player_can_reach_from_top_of_this() {
return this.__things_player_can_reach_from_top_of_this;
}
set things_player_can_reach_from_top_of_this(arr) {
this.__things_player_can_reach_from_top_of_this =
A.validateAssetList(arr);
}
/*doc@this*/
get things_player_can_reach_from_bottom_of_this() {
return this.__things_player_can_reach_from_bottom_of_this;
}
set things_player_can_reach_from_bottom_of_this(arr) {
this.__things_player_can_reach_from_bottom_of_this =
A.validateAssetList(arr);
}
/**
* <strong>Contains</strong> is a shortcut for creating
* a substance container and filling it with an infinite
* amount of a specified substance.
* It is the equivalent of this code:
* <pre class="display"><code class="language-javascript">this.aspects.in = new adventurejs.Aspect( "in", this.game_name )
* .set({
* "parent_id": this.id,
* });
* this.aspects.in.vessel = new adventurejs.Vessel( "in", game_name )
* .set({
* "volume": Infinity,
* "maxvolume": Infinity,
* "substance_id": substance_id,
* });
* </code></pre>
* <br><br>
* This is to make it easier and more
* intuitive for authors to set things like sand
* in a desert room or water in a swamp room, so that
* if player inputs "fill bowl with water", it can be
* assumed that the room is the source of the substance.
* When multiple substance containers are available,
* usually disambiguation occurs, but in the case of a
* room containing a substance, the room is assumed to
* be the source.
* @var {Object} adventurejs.Tangible#place
*/
get contains() {
if (this.hasVesselAtAspect("in")) {
return this.aspects.in.vessel.substance_id;
}
return "";
}
set contains(params) {
if ("string" === typeof params) {
params = { substance_id: params };
}
if (!params) params = {};
if (!params.substance_id) {
params.substance_id = "";
}
if (!params.maxvolume) params.maxvolume = Infinity;
if (!params.volume) params.volume = Infinity;
params.vessel_is_known = true;
this.setVesselAt("in", params);
}
// METHODS
/**
* Inherited from superclass {@link adventurejs.Asset|Asset}.
* Tangible adds validation methods that are used for all Tangible assets,
* including:
* <ul>
* <li>check for implied dependencies and make them explicit</li>
* <li>check for proper asset location</li>
* <li>set parent associations</li>
* </ul>
*
* @memberOf adventurejs.Tangible
* @method adventurejs.Tangible#validate
* @param {Object} game
* @returns {Boolean}
*/
validate(game) {
super.validate(game);
// Validate place. Many tangibles, generally globals, have no place,
// and that's valid, but if it does have a place, ensure that it refers
// to a valid asset.
if (this.__place.asset) {
var place_asset, place_aspect;
place_aspect = Object.keys(this.place)[0];
place_asset = this.game.getAsset(A.serialize(this.__place.asset));
// is place_object a tangible game asset?
if (!place_asset || !(place_asset instanceof adventurejs.Tangible)) {
var msg = `${this.constructor.name} ${this.name}'s place ${this.__place.asset} is unset or invalid. `;
this.game.log("error", "critical", msg, "Tangible");
return false;
}
// has place had an Aspect instantiated?
if (
!place_asset.hasAspectAt(place_aspect) ||
!place_asset.getAspectAt(place_aspect).class
) {
place_asset.aspects[place_aspect] = new adventurejs.Aspect(
place_aspect,
this.game_name
).set({
parent_id: place_asset.id,
});
this.game.debug(
`F1568 | Tangible.js | ${this.constructor.name} ${this.name}'s container, ${place_asset.name}.${place_aspect}, was not set. A new Aspect has been constructed for ${place_asset.name}.${place_aspect}. `
);
this.game.print(msg);
}
}
// has it got no location but requires a location?
if (this.location_required && !this.hasPlace()) {
msg += this.constructor.name + " " + this.name + " hasn't got a place.";
console.error(msg);
return false;
}
return true;
} // validate
/**
* Inherited from superclass {@link adventurejs.Asset|Asset}.
* Tangible adds initialization methods that are used for all
* Tangible assets, including:
* <ul>
* <li>link related objects</li>
* <li>register parts</li>
* </ul>
* @memberOf adventurejs.Tangible
* @method adventurejs.Tangible#initialize
* @param {Object} game
* @returns {Boolean}
*/
initialize(game) {
super.initialize(game);
if (this.getPlaceAsset()) {
this.getPlaceAsset().addAssetAt(this.id, this.getPlacePreposition());
}
this.registerParts.call(this);
this.linkRegisteredParts.call(this);
return true;
} // p.initialize
/**
* Remove this asset from the world before calling superclass.destroy.
* @memberOf adventurejs.Tangible
* @method adventurejs.Tangible#destroy
*/
destroy() {
this.setPlace(); // calling without param removes from parent
// in most cases we call the super method first
// in the case of destroy the last thing to happen
// is removing from lookups
super.destroy();
}
}
adventurejs.Tangible = Tangible;
})();